private SyncResult synchronize() { Console console = Forms.Main.Instance.Console; console.Update("Finding Calendar Entries", Console.Markup.mag_right, newLine: false); List <AppointmentItem> outlookEntries = null; List <Event> googleEntries = null; try { #region Read Outlook items console.Update("Scanning Outlook calendar..."); outlookEntries = OutlookOgcs.Calendar.Instance.GetCalendarEntriesInRange(false); console.Update(outlookEntries.Count + " Outlook calendar entries found.", Console.Markup.sectionEnd, newLine: false); if (CancellationPending) { return(SyncResult.UserCancelled); } #endregion #region Read Google items console.Update("Scanning Google calendar..."); try { googleEntries = GoogleOgcs.Calendar.Instance.GetCalendarEntriesInRange(); } catch (AggregateException agex) { OGCSexception.AnalyseAggregate(agex); } catch (Google.Apis.Auth.OAuth2.Responses.TokenResponseException ex) { OGCSexception.AnalyseTokenResponse(ex, false); return(SyncResult.Fail); } catch (System.Net.Http.HttpRequestException ex) { OGCSexception.Analyse(ex); ex.Data.Add("OGCS", "ERROR: Unable to connect to the Google calendar. Please try again."); throw ex; } catch (System.Exception ex) { OGCSexception.Analyse(ex); ex.Data.Add("OGCS", "ERROR: Unable to connect to the Google calendar."); if (OGCSexception.GetErrorCode(ex) == "0x8013153B") //ex.Message == "A task was canceled." - likely timed out. { ex.Data["OGCS"] += " Please try again."; } throw ex; } Recurrence.Instance.SeparateGoogleExceptions(googleEntries); if (Recurrence.Instance.GoogleExceptions != null && Recurrence.Instance.GoogleExceptions.Count > 0) { console.Update(googleEntries.Count + " Google calendar entries found."); console.Update(Recurrence.Instance.GoogleExceptions.Count + " are exceptions to recurring events.", Console.Markup.sectionEnd, newLine: false); } else { console.Update(googleEntries.Count + " Google calendar entries found.", Console.Markup.sectionEnd, newLine: false); } if (CancellationPending) { return(SyncResult.UserCancelled); } #endregion #region Normalise recurring items in sync window console.Update("Total inc. recurring items spanning sync date range..."); //Outlook returns recurring items that span the sync date range, Google doesn't //So check for master Outlook items occurring before sync date range, and retrieve Google equivalent for (int o = outlookEntries.Count - 1; o >= 0; o--) { log.Fine("Processing " + (o + 1) + "/" + outlookEntries.Count); AppointmentItem ai = null; try { if (outlookEntries[o] is AppointmentItem) { ai = outlookEntries[o]; } else if (outlookEntries[o] is MeetingItem) { log.Info("Calendar object appears to be a MeetingItem, so retrieving associated AppointmentItem."); MeetingItem mi = outlookEntries[o] as MeetingItem; outlookEntries[o] = mi.GetAssociatedAppointment(false); ai = outlookEntries[o]; } else { log.Warn("Unknown calendar object type - cannot sync it."); skipCorruptedItem(ref outlookEntries, outlookEntries[o], "Unknown object type."); outlookEntries[o] = (AppointmentItem)OutlookOgcs.Calendar.ReleaseObject(outlookEntries[o]); continue; } } catch (System.Exception ex) { log.Warn("Encountered error casting calendar object to AppointmentItem - cannot sync it."); log.Debug(ex.Message); skipCorruptedItem(ref outlookEntries, outlookEntries[o], ex.Message); outlookEntries[o] = (AppointmentItem)OutlookOgcs.Calendar.ReleaseObject(outlookEntries[o]); ai = (AppointmentItem)OutlookOgcs.Calendar.ReleaseObject(ai); continue; } //Now let's check there's a start/end date - sometimes it can be missing, even though this shouldn't be possible!! String entryID; try { entryID = outlookEntries[o].EntryID; DateTime checkDates = ai.Start; checkDates = ai.End; } catch (System.Exception ex) { log.Warn("Calendar item does not have a proper date range - cannot sync it."); log.Debug(ex.Message); skipCorruptedItem(ref outlookEntries, outlookEntries[o], ex.Message); outlookEntries[o] = (AppointmentItem)OutlookOgcs.Calendar.ReleaseObject(outlookEntries[o]); ai = (AppointmentItem)OutlookOgcs.Calendar.ReleaseObject(ai); continue; } if (ai.IsRecurring && ai.Start.Date < Settings.Instance.SyncStart && ai.End.Date < Settings.Instance.SyncStart) { //We won't bother getting Google master event if appointment is yearly reoccurring in a month outside of sync range //Otherwise, every sync, the master event will have to be retrieved, compared, concluded nothing's changed (probably) = waste of API calls RecurrencePattern oPattern = ai.GetRecurrencePattern(); try { if (oPattern.RecurrenceType.ToString().Contains("Year")) { log.Fine("It's an annual event."); Boolean monthInSyncRange = false; DateTime monthMarker = Settings.Instance.SyncStart; while (Convert.ToInt32(monthMarker.ToString("yyyyMM")) <= Convert.ToInt32(Settings.Instance.SyncEnd.ToString("yyyyMM")) && !monthInSyncRange) { if (monthMarker.Month == ai.Start.Month) { monthInSyncRange = true; } monthMarker = monthMarker.AddMonths(1); } log.Fine("Found it to be " + (monthInSyncRange ? "inside" : "outside") + " sync range."); if (!monthInSyncRange) { outlookEntries.Remove(ai); log.Fine("Removed."); continue; } } Event masterEv = Recurrence.Instance.GetGoogleMasterEvent(ai); if (masterEv != null && masterEv.Status != "cancelled") { Event cachedEv = googleEntries.Find(x => x.Id == masterEv.Id); if (cachedEv == null) { googleEntries.Add(masterEv); } else { if (masterEv.Updated > cachedEv.Updated) { log.Debug("Refreshing cache for this Event."); googleEntries.Remove(cachedEv); googleEntries.Add(masterEv); } } } } catch (System.Exception ex) { console.Update("Failed to retrieve master for Google recurring event outside of sync range.", Console.Markup.error); throw ex; } finally { oPattern = (RecurrencePattern)OutlookOgcs.Calendar.ReleaseObject(oPattern); } } //Completely dereference object and retrieve afresh (due to GetRecurrencePattern earlier) ai = (AppointmentItem)OutlookOgcs.Calendar.ReleaseObject(ai); OutlookOgcs.Calendar.Instance.IOutlook.GetAppointmentByID(entryID, out ai); outlookEntries[o] = ai; } console.Update("Outlook " + outlookEntries.Count + ", Google " + googleEntries.Count); GoogleOgcs.Calendar.ExportToCSV("Outputting all Events.", "google_events.csv", googleEntries); OutlookOgcs.Calendar.ExportToCSV("Outputting all Appointments.", "outlook_appointments.csv", outlookEntries); if (CancellationPending) { return(SyncResult.UserCancelled); } #endregion Boolean success = true; String bubbleText = ""; if (Settings.Instance.SyncDirection != Direction.GoogleToOutlook) { success = outlookToGoogle(outlookEntries, googleEntries, ref bubbleText); if (CancellationPending) { return(SyncResult.UserCancelled); } } if (!success) { return(SyncResult.Fail); } if (Settings.Instance.SyncDirection != Direction.OutlookToGoogle) { if (bubbleText != "") { bubbleText += "\r\n"; } success = googleToOutlook(googleEntries, outlookEntries, ref bubbleText); if (CancellationPending) { return(SyncResult.UserCancelled); } } if (bubbleText != "") { Forms.Main.Instance.NotificationTray.ShowBubbleInfo(bubbleText); } return(SyncResult.OK); } finally { for (int o = outlookEntries.Count() - 1; o >= 0; o--) { outlookEntries[o] = (AppointmentItem)OutlookOgcs.Calendar.ReleaseObject(outlookEntries[o]); outlookEntries.RemoveAt(o); } } }
public void Start(Boolean updateSyncSchedule = true) { Forms.Main mainFrm = Forms.Main.Instance; try { DateTime syncStarted = DateTime.Now; String cacheNextSync = mainFrm.lNextSyncVal.Text; mainFrm.Console.Clear(); if (Settings.Instance.UseGoogleCalendar == null || Settings.Instance.UseGoogleCalendar.Id == null || Settings.Instance.UseGoogleCalendar.Id == "") { MessageBox.Show("You need to select a Google Calendar first on the 'Settings' tab."); return; } if (Settings.Instance.MuteClickSounds) { Console.MuteClicks(true); } //Check network availability if (!System.Net.NetworkInformation.NetworkInterface.GetIsNetworkAvailable()) { mainFrm.Console.Update("There does not appear to be any network available! Sync aborted.", Console.Markup.error, notifyBubble: true); setNextSync(syncStarted, false, updateSyncSchedule, cacheNextSync); return; } //Check if Outlook is Online try { if (OutlookOgcs.Calendar.Instance.IOutlook.Offline() && Settings.Instance.AddAttendees) { mainFrm.Console.Update("<p>You have selected to sync attendees but Outlook is currently offline.</p>" + "<p>Either put Outlook online or do not sync attendees.</p>", Console.Markup.error, notifyBubble: true); setNextSync(syncStarted, false, updateSyncSchedule, cacheNextSync); return; } } catch (System.Exception ex) { mainFrm.Console.UpdateWithError(null, ex, notifyBubble: true); OGCSexception.Analyse(ex, true); return; } GoogleOgcs.Calendar.APIlimitReached_attendee = false; Forms.Main.Instance.SyncNote(Forms.Main.SyncNotes.QuotaExhaustedInfo, null, false); Forms.Main.Instance.bSyncNow.Text = "Stop Sync"; Forms.Main.Instance.NotificationTray.UpdateItem("sync", "&Stop Sync"); Forms.Main.Instance.lNextSyncVal.Text = "In progress..."; StringBuilder sb = new StringBuilder(); Forms.Main.Instance.Console.BuildOutput("Sync version: " + System.Windows.Forms.Application.ProductVersion, ref sb); Forms.Main.Instance.Console.BuildOutput((ManualForceCompare ? "Full s" : "S") + "ync started at " + syncStarted.ToString(), ref sb); Forms.Main.Instance.Console.BuildOutput("Syncing from " + Settings.Instance.SyncStart.ToShortDateString() + " to " + Settings.Instance.SyncEnd.ToShortDateString(), ref sb); mainFrm.Console.BuildOutput(Settings.Instance.SyncDirection.Name, ref sb); //Make the clock emoji show the right time int minsPastHour = DateTime.Now.Minute; minsPastHour = (int)minsPastHour - (minsPastHour % 30); sb.Insert(0, ":clock" + DateTime.Now.ToString("hh").TrimStart('0') + (minsPastHour == 00 ? "" : "30") + ":"); mainFrm.Console.Update(sb); if (Settings.Instance.OutlookPush) { OutlookOgcs.Calendar.Instance.DeregisterForPushSync(); } SyncResult syncResult = SyncResult.Fail; int failedAttempts = 0; Social.TrackSync(); try { GoogleOgcs.Calendar.Instance.GetCalendarSettings(); } catch (System.AggregateException ae) { OGCSexception.AnalyseAggregate(ae); syncResult = SyncResult.AutoRetry; } catch (System.Exception ex) { log.Warn(ex.Message); syncResult = SyncResult.AutoRetry; } while (syncResult == SyncResult.Fail) { if (failedAttempts > 0) { if (MessageBox.Show("The synchronisation failed - check the Sync tab for further details.\r\nDo you want to try again?", "Sync Failed", MessageBoxButtons.YesNo, MessageBoxIcon.Exclamation) == System.Windows.Forms.DialogResult.No) { syncResult = SyncResult.Abandon; break; } else { log.Info("User opted to retry sync straight away."); } } //Set up a separate thread for the sync to operate in. Keeps the UI responsive. bwSync = new AbortableBackgroundWorker(); //Don't need thread to report back. The logbox is updated from the thread anyway. bwSync.WorkerReportsProgress = false; bwSync.WorkerSupportsCancellation = true; //Kick off the sync in the background thread bwSync.DoWork += new DoWorkEventHandler( delegate(object o, DoWorkEventArgs args) { BackgroundWorker b = o as BackgroundWorker; try { syncResult = synchronize(); } catch (System.Exception ex) { sb = new StringBuilder(); mainFrm.Console.BuildOutput("The following error was encountered during sync:-", ref sb); if (ex.Data.Count > 0 && ex.Data.Contains("OGCS")) { mainFrm.Console.BuildOutput(ex.Data["OGCS"].ToString(), ref sb); mainFrm.Console.Update(sb, Console.Markup.error, notifyBubble: true); if (ex.Data["OGCS"].ToString().Contains("Please try again")) { syncResult = SyncResult.AutoRetry; } } else { OGCSexception.Analyse(ex, true); mainFrm.Console.UpdateWithError(null, ex, notifyBubble: true); syncResult = SyncResult.Fail; } } } ); bwSync.RunWorkerAsync(); while (bwSync != null && (bwSync.IsBusy || bwSync.CancellationPending)) { System.Windows.Forms.Application.DoEvents(); System.Threading.Thread.Sleep(100); } try { //Get Logbox text - this is a little bit dirty! if (syncResult != SyncResult.OK && mainFrm.Console.DocumentText.Contains("The RPC server is unavailable.")) { mainFrm.Console.Update("Attempting to reconnect to Outlook..."); try { OutlookOgcs.Calendar.Instance.Reset(); } catch { } } } finally { failedAttempts += (syncResult != SyncResult.OK) ? 1 : 0; } } if (syncResult == SyncResult.OK) { Settings.Instance.CompletedSyncs++; consecutiveSyncFails = 0; mainFrm.Console.Update("Sync finished with success!", Console.Markup.checkered_flag); } else if (syncResult == SyncResult.AutoRetry) { consecutiveSyncFails++; mainFrm.Console.Update("Sync encountered a problem and did not complete successfully.<br/>" + consecutiveSyncFails + " consecutive syncs failed.", Console.Markup.error, notifyBubble: true); } else { consecutiveSyncFails += failedAttempts; mainFrm.Console.Update("Operation aborted after " + failedAttempts + " failed attempts!", Console.Markup.error); } setNextSync(syncStarted, syncResult == SyncResult.OK, updateSyncSchedule, cacheNextSync); mainFrm.CheckSyncMilestone(); } finally { mainFrm.bSyncNow.Text = "Start Sync"; mainFrm.NotificationTray.UpdateItem("sync", "&Sync Now"); if (Settings.Instance.MuteClickSounds) { Console.MuteClicks(false); } if (Settings.Instance.OutlookPush) { OutlookOgcs.Calendar.Instance.RegisterForPushSync(); } //Release Outlook reference if GUI not available. //Otherwise, tasktray shows "another program is using outlook" and it doesn't send and receive emails OutlookOgcs.Calendar.Instance.Disconnect(onlyWhenNoGUI: true); } }