diff --git a/src/OutlookGoogleCalendarSync/Console/Console.cs b/src/OutlookGoogleCalendarSync/Console/Console.cs index 10409d54..e9be9725 100644 --- a/src/OutlookGoogleCalendarSync/Console/Console.cs +++ b/src/OutlookGoogleCalendarSync/Console/Console.cs @@ -15,7 +15,18 @@ public class Console { private String content = ""; public String DocumentText { get { - return (this.wb == null ? null : this.wb.DocumentText); + String documentText = ""; + if (this.wb == null) + return null; + else { + if (this.wb.InvokeRequired) { + this.wb.Invoke((MethodInvoker)(() => { + documentText = wb.DocumentText; + })); + } else + documentText = this.wb.DocumentText; + } + return documentText; } } @@ -271,7 +282,7 @@ public void Update(String moreOutput, Markup? markupPrefix = null, bool newLine } //Don't add append line break to Markup that's already wrapped in
tags - if (markupPrefix != null && (new Markup[] { Markup.info, Markup.warning, Markup.error }.ToList()).Contains((Markup)markupPrefix)) + if (markupPrefix != null && (new Markup[] { Markup.info, Markup.warning, Markup.fail, Markup.error }.ToList()).Contains((Markup)markupPrefix)) newLine = false; contentInnerHtml += htmlOutput + (newLine ? "
" : ""); diff --git a/src/OutlookGoogleCalendarSync/Extensions/Exception.cs b/src/OutlookGoogleCalendarSync/Extensions/Exception.cs index 3c0b3299..38a0f41f 100644 --- a/src/OutlookGoogleCalendarSync/Extensions/Exception.cs +++ b/src/OutlookGoogleCalendarSync/Extensions/Exception.cs @@ -129,7 +129,17 @@ public static System.Exception LogAsFail(System.Exception ex) { LogAsFail(ref ex); return ex; } - + + /// + /// Capture this exception as log4net FAIL (not ERROR) when logged + /// + public static void LogAsFail(ref Google.GoogleApiException ex) { + if (ex.Data.Contains(LogAs)) + ex.Data[LogAs] = OGCSexception.LogLevel.FAIL; + else + ex.Data.Add(LogAs, OGCSexception.LogLevel.FAIL); + } + /// /// Capture this exception as log4net FAIL (not ERROR) when logged /// diff --git a/src/OutlookGoogleCalendarSync/GoogleOgcs/Authenticator.cs b/src/OutlookGoogleCalendarSync/GoogleOgcs/Authenticator.cs index 31c13b77..2985dfc7 100644 --- a/src/OutlookGoogleCalendarSync/GoogleOgcs/Authenticator.cs +++ b/src/OutlookGoogleCalendarSync/GoogleOgcs/Authenticator.cs @@ -90,7 +90,7 @@ private static ClientSecrets getCalendarClientSecrets() { return provider; } - private async Task getAuthenticated(ClientSecrets cs) { + private async Task getAuthenticated(ClientSecrets cs) { log.Debug("Authenticating with Google calendar service..."); FileDataStore tokenStore = new FileDataStore(Program.UserFilePath); @@ -141,17 +141,43 @@ private async Task getAuthenticated(ClientSecrets cs) { log.Debug("Access token needs refreshing."); //This will happen automatically when using the calendar service //But we need a valid token before we call getGaccountEmail() which doesn't use the service - try { - GoogleOgcs.Calendar.Instance.Service.Settings.Get("useKeyboardShortcuts").Execute(); - } catch (System.Exception ex) { - if (ex is Google.Apis.Auth.OAuth2.Responses.TokenResponseException) - OGCSexception.AnalyseTokenResponse(ex as Google.Apis.Auth.OAuth2.Responses.TokenResponseException, false); - else { - OGCSexception.Analyse(ex); - Forms.Main.Instance.Console.Update("Unable to communicate with Google services. " + (ex.InnerException != null ? ex.InnerException.Message : ex.Message), Console.Markup.warning); + int backoff = 0; + while (backoff < Calendar.backoffLimit) { + try { + GoogleOgcs.Calendar.Instance.Service.Settings.Get("useKeyboardShortcuts").Execute(); + } catch (Google.GoogleApiException ex) { + switch (Calendar.HandleAPIlimits(ex, null)) { + case Calendar.ApiException.throwException: throw; + case Calendar.ApiException.freeAPIexhausted: + OGCSexception.LogAsFail(ref ex); + OGCSexception.Analyse(ex); + System.ApplicationException aex = new System.ApplicationException(Calendar.Instance.SubscriptionInvite); + OGCSexception.LogAsFail(ref aex); + authenticated = false; + return authenticated; + case Calendar.ApiException.backoffThenRetry: + backoff++; + if (backoff == Calendar.backoffLimit) { + log.Fail("API limit backoff was not successful. Retrieving useKeyboardShortcuts setting failed."); + authenticated = false; + return authenticated; + } else { + log.Warn("API rate limit reached. Backing off " + backoff + "sec before retry."); + System.Threading.Thread.Sleep(backoff * 1000); + } + break; + } + + } catch (System.Exception ex) { + if (ex is Google.Apis.Auth.OAuth2.Responses.TokenResponseException) + OGCSexception.AnalyseTokenResponse(ex as Google.Apis.Auth.OAuth2.Responses.TokenResponseException, false); + else { + OGCSexception.Analyse(ex); + Forms.Main.Instance.Console.Update("Unable to communicate with Google services. " + (ex.InnerException != null ? ex.InnerException.Message : ex.Message), Console.Markup.warning); + } + authenticated = false; + return authenticated; } - authenticated = false; - return; } log.Debug("Access token refreshed."); } @@ -159,6 +185,7 @@ private async Task getAuthenticated(ClientSecrets cs) { getGaccountEmail(credential.Token.AccessToken); authenticated = true; Forms.Main.Instance.Console.Update("Handshake successful.", verbose: true); + return authenticated; } public void Reset(Boolean reauthorise = true) { @@ -312,7 +339,9 @@ public Boolean UserSubscriptionCheck() { switch (GoogleOgcs.Calendar.HandleAPIlimits(ex, null)) { case Calendar.ApiException.throwException: throw; case Calendar.ApiException.freeAPIexhausted: - System.ApplicationException aex = new System.ApplicationException(GoogleOgcs.Calendar.Instance.SubscriptionInvite, ex); + OGCSexception.LogAsFail(ref ex); + OGCSexception.Analyse(ex); + System.ApplicationException aex = new System.ApplicationException(GoogleOgcs.Calendar.Instance.SubscriptionInvite); OGCSexception.LogAsFail(ref aex); GoogleOgcs.Calendar.Instance.Service = null; throw aex; diff --git a/src/OutlookGoogleCalendarSync/GoogleOgcs/GoogleCalendar.cs b/src/OutlookGoogleCalendarSync/GoogleOgcs/GoogleCalendar.cs index c4794624..6205f416 100644 --- a/src/OutlookGoogleCalendarSync/GoogleOgcs/GoogleCalendar.cs +++ b/src/OutlookGoogleCalendarSync/GoogleOgcs/GoogleCalendar.cs @@ -69,7 +69,7 @@ public CalendarService Service { set { service = value; } } public static Boolean APIlimitReached_attendee = false; - private const int backoffLimit = 5; + public const int backoffLimit = 5; public enum ApiException { justContinue, backoffThenRetry, @@ -105,7 +105,9 @@ public List GetCalendars() { switch (HandleAPIlimits(ex, null)) { case ApiException.throwException: throw; case ApiException.freeAPIexhausted: - System.ApplicationException aex = new System.ApplicationException(SubscriptionInvite, ex); + OGCSexception.LogAsFail(ref ex); + OGCSexception.Analyse(ex); + System.ApplicationException aex = new System.ApplicationException(SubscriptionInvite); OGCSexception.LogAsFail(ref aex); throw aex; case ApiException.backoffThenRetry: @@ -156,7 +158,9 @@ public List GetCalendarEntriesInRecurrence(String recurringEventId) { switch (HandleAPIlimits(ex, null)) { case ApiException.throwException: throw; case ApiException.freeAPIexhausted: - System.ApplicationException aex = new System.ApplicationException(SubscriptionInvite, ex); + OGCSexception.LogAsFail(ref ex); + OGCSexception.Analyse(ex); + System.ApplicationException aex = new System.ApplicationException(SubscriptionInvite); OGCSexception.LogAsFail(ref aex); throw aex; case ApiException.backoffThenRetry: @@ -207,7 +211,9 @@ public Event GetCalendarEntry(String eventId) { switch (HandleAPIlimits(ex, null)) { case ApiException.throwException: throw; case ApiException.freeAPIexhausted: - System.ApplicationException aex = new System.ApplicationException(SubscriptionInvite, ex); + OGCSexception.LogAsFail(ref ex); + OGCSexception.Analyse(ex); + System.ApplicationException aex = new System.ApplicationException(SubscriptionInvite); OGCSexception.LogAsFail(ref aex); throw aex; case ApiException.backoffThenRetry: @@ -229,8 +235,8 @@ public Event GetCalendarEntry(String eventId) { else throw new System.Exception("Returned null"); } catch (System.Exception ex) { + if (ex is ApplicationException) throw; Forms.Main.Instance.Console.Update("Failed to retrieve Google event", Console.Markup.error); - log.Error(ex.Message); return null; } } @@ -265,7 +271,9 @@ public List GetCalendarEntriesInRange(DateTime from, DateTime to) { switch (HandleAPIlimits(ex, null)) { case ApiException.throwException: throw; case ApiException.freeAPIexhausted: - System.ApplicationException aex = new System.ApplicationException(SubscriptionInvite, ex); + OGCSexception.LogAsFail(ref ex); + OGCSexception.Analyse(ex); + System.ApplicationException aex = new System.ApplicationException(SubscriptionInvite); OGCSexception.LogAsFail(ref aex); throw aex; case ApiException.backoffThenRetry: @@ -445,7 +453,9 @@ private Event createCalendarEntry_save(Event ev, AppointmentItem ai) { switch (HandleAPIlimits(ex, ev)) { case ApiException.throwException: throw; case ApiException.freeAPIexhausted: - System.ApplicationException aex = new System.ApplicationException(SubscriptionInvite, ex); + OGCSexception.LogAsFail(ref ex); + OGCSexception.Analyse(ex); + System.ApplicationException aex = new System.ApplicationException(SubscriptionInvite); OGCSexception.LogAsFail(ref aex); throw aex; case ApiException.justContinue: break; @@ -814,7 +824,9 @@ public void UpdateCalendarEntry_save(ref Event ev) { switch (HandleAPIlimits(ex, ev)) { case ApiException.throwException: throw; case ApiException.freeAPIexhausted: - System.ApplicationException aex = new System.ApplicationException(SubscriptionInvite, ex); + OGCSexception.LogAsFail(ref ex); + OGCSexception.Analyse(ex); + System.ApplicationException aex = new System.ApplicationException(SubscriptionInvite); OGCSexception.LogAsFail(ref aex); throw aex; case ApiException.backoffThenRetry: @@ -906,7 +918,9 @@ private void deleteCalendarEntry_save(Event ev) { switch (HandleAPIlimits(ex, ev)) { case ApiException.throwException: throw; case ApiException.freeAPIexhausted: - System.ApplicationException aex = new System.ApplicationException(SubscriptionInvite, ex); + OGCSexception.LogAsFail(ref ex); + OGCSexception.Analyse(ex); + System.ApplicationException aex = new System.ApplicationException(SubscriptionInvite); OGCSexception.LogAsFail(ref aex); throw aex; case ApiException.backoffThenRetry: @@ -1663,7 +1677,7 @@ public static ApiException HandleAPIlimits(Google.GoogleApiException ex, Event e return ApiException.justContinue; } else if (ex.Message.Contains("Daily Limit Exceeded") || - (ex.Error.Errors.First().Reason == "rateLimitExceeded" && ex.Message.Contains("limit 'Queries per day'"))) { + (ex.Message.Contains("limit 'Queries per day'") && ex.Error != null && ex.Error.Errors != null && ex.Error.Errors.First().Reason == "rateLimitExceeded")) { log.Warn("Google's free Calendar quota has been exhausted! New quota comes into effect 08:00 GMT."); Forms.Main.Instance.SyncNote(Forms.Main.SyncNotes.QuotaExhaustedInfo, null); @@ -1696,5 +1710,17 @@ public static ApiException HandleAPIlimits(Google.GoogleApiException ex, Event e } } #endregion + + /// + /// This is solely for purposefully causing an error to assist when developing + /// + private void throwApiException() { + Google.GoogleApiException ex = new Google.GoogleApiException("Service", "limit 'Queries per day'"); + Google.Apis.Requests.SingleError err = new Google.Apis.Requests.SingleError(); + err.Reason = "rateLimitExceeded"; + ex.Error = new Google.Apis.Requests.RequestError { Errors = new List() }; + ex.Error.Errors.Add(err); + throw ex; + } } } diff --git a/src/OutlookGoogleCalendarSync/Sync/Engine.cs b/src/OutlookGoogleCalendarSync/Sync/Engine.cs index 221211d6..481770b9 100644 --- a/src/OutlookGoogleCalendarSync/Sync/Engine.cs +++ b/src/OutlookGoogleCalendarSync/Sync/Engine.cs @@ -288,7 +288,8 @@ public void Start(Boolean updateSyncSchedule = true) { } } else { consecutiveSyncFails += failedAttempts; - mainFrm.Console.Update("Sync aborted after " + failedAttempts + " failed attempts!", syncResult == SyncResult.UserCancelled ? Console.Markup.fail : Console.Markup.error); + mainFrm.Console.Update("Sync aborted after " + failedAttempts + " failed attempts!", + new SyncResult[] { SyncResult.UserCancelled, SyncResult.Abandon }.Contains(syncResult) ? Console.Markup.fail : Console.Markup.error); } setNextSync(syncStarted, syncResult == SyncResult.OK, updateSyncSchedule, cacheNextSync); @@ -499,8 +500,8 @@ private SyncResult synchronize() { } } } - } catch (System.Exception) { - console.Update("Failed to retrieve master for Google recurring event outside of sync range.", Console.Markup.error); + } catch (System.Exception ex) { + console.Update("Failed to retrieve master for Google recurring event outside of sync range.", OGCSexception.LoggingAsFail(ex) ? Console.Markup.fail : Console.Markup.error); throw; } finally { oPattern = (RecurrencePattern)OutlookOgcs.Calendar.ReleaseObject(oPattern);