Skip to content

Commit

Permalink
Implement more sophisticated error logging during web refresh
Browse files Browse the repository at this point in the history
  • Loading branch information
marcelltoth committed Sep 7, 2019
1 parent 2fdf978 commit fbdfcd5
Show file tree
Hide file tree
Showing 6 changed files with 91 additions and 9 deletions.
5 changes: 5 additions & 0 deletions NeptunLight/NeptunLight.Android/App.cs
Original file line number Diff line number Diff line change
Expand Up @@ -35,10 +35,15 @@ public class App : Application
.As<IRefreshManager>()
.SingleInstance();

builder.RegisterType<AppCenterLogger>()
.As<ILogger>()
.SingleInstance();

builder.RegisterType<AndroidWebScraperClient>()
.As<WebScraperClient>()
.InstancePerDependency();


builder.Register(context => MainActivity)
.As<INavigator>();

Expand Down
1 change: 1 addition & 0 deletions NeptunLight/NeptunLight.Android/NeptunLight.Android.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,7 @@
<Compile Include="RefreshService.cs" />
<Compile Include="Secrets.cs" />
<Compile Include="Services\AndroidWebScraperClient.cs" />
<Compile Include="Services\AppCenterLogger.cs" />
<Compile Include="Services\PrimitiveStorage.cs" />
<Compile Include="Services\RefreshManager.cs" />
<Compile Include="SettingsActivity.cs" />
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android" android:installLocation="auto" package="com.infinitx.neptunlite" android:versionName="1.18.144" android:versionCode="34">
<manifest xmlns:android="http://schemas.android.com/apk/res/android" android:installLocation="auto" package="com.infinitx.neptunlite" android:versionName="1.18.150" android:versionCode="35">
<uses-sdk android:minSdkVersion="21" android:targetSdkVersion="28" />
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
Expand Down
18 changes: 18 additions & 0 deletions NeptunLight/NeptunLight.Android/Services/AppCenterLogger.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
using System;
using System.Collections.Generic;
using Microsoft.AppCenter.Crashes;
using NeptunLight.Services;

namespace NeptunLight.Droid.Services
{
/// <summary>
/// Implements <see cref="ILogger"/> using the AppCenter SDK
/// </summary>
public class AppCenterLogger : ILogger
{
public void LogError(Exception ex, Dictionary<string, string> context = null)
{
Crashes.TrackError(ex, context);
}
}
}
64 changes: 56 additions & 8 deletions NeptunLight/NeptunLight/DataAccess/WebNeptunInterface.cs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
using System.Threading.Tasks;
using AngleSharp.Dom;
using AngleSharp.Dom.Html;
using AngleSharp.Extensions;
using AngleSharp.Parser.Html;
using JetBrains.Annotations;
using NeptunLight.Helpers;
Expand All @@ -31,18 +32,20 @@ public class WebNeptunInterface : INeptunInterface
private readonly IPrimitiveStorage _primitiveStorage;

private readonly Func<WebScraperClient> _clientFactory;
private readonly ILogger _logger;

private Uri _baseUri;
private WebScraperClient _client;
private string _password;

private string _username;

public WebNeptunInterface([CanBeNull] IMailContentCache mailContentCache, [CanBeNull] IPrimitiveStorage primitiveStorage, Func<WebScraperClient> clientFactory)
public WebNeptunInterface([CanBeNull] IMailContentCache mailContentCache, [CanBeNull] IPrimitiveStorage primitiveStorage, Func<WebScraperClient> clientFactory, ILogger logger)
{
_mailContentCache = mailContentCache;
_primitiveStorage = primitiveStorage;
_clientFactory = clientFactory;
_logger = logger;
}

public string Username
Expand Down Expand Up @@ -125,7 +128,7 @@ public IObservable<Mail> RefreshMessages(IProgress<MessageLoadingProgress> progr
IDocument rawMessagesDocument = await parser.ParseAsync(rawMessages, ct);
IHtmlTableElement messageHeaderTable = (IHtmlTableElement) rawMessagesDocument.GetElementById("c_messages_gridMessages_bodytable");

IList<MailHeader> mailHeaders = ParseMailHeaderTable(messageHeaderTable);
IList<MailHeader> mailHeaders = ParseMailHeaderTable(messageHeaderTable, _logger);
int messagesToLoad = Math.Min(mailHeaders.Count, 100);
for (int i = 0; i < messagesToLoad; i++)
{
Expand Down Expand Up @@ -176,7 +179,7 @@ await Task.Run(async () =>
catch (Exception e)
{
// Do not crash if a single mail throws
// TODO: implement logging
_logger.LogError(new MailParsingException(mailHeader, e));
}
}, ct);
}
Expand Down Expand Up @@ -215,6 +218,7 @@ public async Task<IReadOnlyDictionary<Semester, IReadOnlyCollection<Subject>>> R
IEnumerable<IElement> semesterOptions = subjectsPage.GetElementById("cmb_cmb").Children.Where(opt => opt.GetAttribute("value") != "-1");
foreach (IElement option in semesterOptions)
{
IDocument semesterSubjectData = null;
try
{
await Task.Delay(200);
Expand All @@ -224,7 +228,7 @@ public async Task<IReadOnlyDictionary<Semester, IReadOnlyCollection<Subject>>> R
// load subcject data in semester
subjectsPage = await _client.GetDocumentAsnyc("main.aspx?ismenuclick=true&ctrl=0304");
await Task.Delay(100);
IDocument semesterSubjectData = await _client.PostFormAsnyc("main.aspx?ismenuclick=true&ctrl=0304", subjectsPage, new[] {new KeyValuePair<string, string>("upFilter$cmb$m_cmb", option.GetAttribute("value"))});
semesterSubjectData = await _client.PostFormAsnyc("main.aspx?ismenuclick=true&ctrl=0304", subjectsPage, new[] {new KeyValuePair<string, string>("upFilter$cmb$m_cmb", option.GetAttribute("value"))});
IHtmlTableElement subjectDataTable = (IHtmlTableElement) semesterSubjectData.GetElementById("h_addedsubjects_gridAddedSubjects_bodytable");

// load course data
Expand Down Expand Up @@ -266,7 +270,7 @@ public async Task<IReadOnlyDictionary<Semester, IReadOnlyCollection<Subject>>> R
catch (Exception e)
{
// do not fail because of a single semester failing
// TODO: log
_logger.LogError(new SubjectListParsingException(semesterSubjectData?.DocumentElement.OuterHtml ?? "NOT LOADED", e));
}
}

Expand All @@ -282,6 +286,7 @@ public async Task<IReadOnlyDictionary<Semester, IReadOnlyCollection<Exam>>> Refr
IEnumerable<IElement> semesterOptions = examsPage.GetElementById("upFilter_cmbTerms").Children.Where(opt => opt.GetAttribute("value") != "-1");
foreach (IElement option in semesterOptions.Take(8))
{
IDocument semesterExamData = null;
try
{
Semester semester = Semester.Parse(option.TextContent);
Expand All @@ -294,7 +299,7 @@ public async Task<IReadOnlyDictionary<Semester, IReadOnlyCollection<Exam>>> Refr
await Task.Delay(200);
examsPage = await _client.GetDocumentAsnyc("main.aspx?ismenuclick=true&ctrl=0402");
await Task.Delay(100);
IDocument semesterExamData = await _client.PostFormAsnyc("main.aspx?ismenuclick=true&ctrl=0402", examsPage, new[] {new KeyValuePair<string, string>("upFilter$cmbTerms", option.GetAttribute("value"))});
semesterExamData = await _client.PostFormAsnyc("main.aspx?ismenuclick=true&ctrl=0402", examsPage, new[] {new KeyValuePair<string, string>("upFilter$cmbTerms", option.GetAttribute("value"))});
IHtmlTableElement subjectDataTable = (IHtmlTableElement) semesterExamData.GetElementById("h_signedexams_gridExamList_bodytable");

foreach (IHtmlTableRowElement dataRow in subjectDataTable.Bodies[0].Rows)
Expand Down Expand Up @@ -334,6 +339,7 @@ public async Task<IReadOnlyDictionary<Semester, IReadOnlyCollection<Exam>>> Refr
catch (Exception ex)
{
// don't fail the whole sync if one semester load fails
_logger.LogError(new ExamListParsingException(semesterExamData?.DocumentElement.OuterHtml ?? "NOT LOADED", ex));
}
}

Expand Down Expand Up @@ -419,7 +425,7 @@ public async Task<IReadOnlyCollection<Period>> RefreshPeriodsAsnyc()
return result;
}

private static IList<MailHeader> ParseMailHeaderTable(IHtmlTableElement table)
private static IList<MailHeader> ParseMailHeaderTable(IHtmlTableElement table, ILogger logger)
{
List<MailHeader> result = new List<MailHeader>();
foreach (IHtmlTableRowElement row in table.Bodies[0].Rows)
Expand All @@ -432,11 +438,53 @@ private static IList<MailHeader> ParseMailHeaderTable(IHtmlTableElement table)
bool isNew = row.ClassName == "Row1_Bold";
result.Add(new MailHeader(receiveTime, sender, title) {TrId = trid, IsNew = isNew});
}
catch (Exception)
catch (Exception e)
{
// skip unparsable stuff
logger.LogError(new MailHeaderParsingException(row.OuterHtml, e));
}
return result;
}

private class MailParsingException : Exception
{
public MailHeader Header { get; }
public override string Message => $"Unable to load mail for header: ID={Header.TrId}, ReceivedTime={Header.ReceivedTime}, IsNew={Header.IsNew}";

public MailParsingException(MailHeader header, Exception innerException) : base(String.Empty, innerException)
{
Header = header;
}
}

private class MailHeaderParsingException : Exception
{
public string InputHtml { get; }

public MailHeaderParsingException(string inputHtml, Exception innerException) : base($"Unable to parse mail header, HTML: '{inputHtml}'", innerException)
{
InputHtml = inputHtml;
}
}

private class SubjectListParsingException : Exception
{
public string InputHtml { get; }

public SubjectListParsingException(string inputHtml, Exception innerException) : base($"Unable to parse semester subject list, HTML: '{inputHtml}'", innerException)
{
InputHtml = inputHtml;
}
}

private class ExamListParsingException : Exception
{
public string InputHtml { get; }

public ExamListParsingException(string inputHtml, Exception innerException) : base($"Unable to parse semester exam list, HTML: '{inputHtml}'", innerException)
{
InputHtml = inputHtml;
}
}
}
}
10 changes: 10 additions & 0 deletions NeptunLight/NeptunLight/Services/ILogger.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
using System;
using System.Collections.Generic;

namespace NeptunLight.Services
{
public interface ILogger
{
void LogError(Exception ex, Dictionary<string, string> context = null);
}
}

0 comments on commit fbdfcd5

Please sign in to comment.