using Google.Apis.Calendar.v3.Data;
using Serilog;
using System;
using System.Collections.Generic;
using System.Runtime.InteropServices;
using Outlook = Microsoft.Office.Interop.Outlook;
namespace GoContactSyncMod
{
internal static class AppointmentPropertiesUtils
{
internal static string GetOutlookId(Outlook.AppointmentItem oa)
{
return oa.EntryID;
}
internal static string GetGoogleId(Event ga)
{
var id = ga.Id.ToString();
if (id == null)
{
throw new Exception();
}
return id;
}
internal static bool SetGoogleOutlookId(Event ga, Outlook.AppointmentItem oa)
{
var oid = GetOutlookId(oa);
if (oid == null)
{
throw new Exception("Must save Outlook appointment before getting id");
}
return SetGoogleOutlookId(ga, oid);
}
internal static bool SetGoogleOutlookId(Event ga, string oid)
{
var key = OutlookPropertiesUtils.GetKey();
if (ga.ExtendedProperties == null)
{
ga.ExtendedProperties = new Event.ExtendedPropertiesData();
}
if (ga.ExtendedProperties.Shared == null)
{
ga.ExtendedProperties.Shared = new Dictionary<string, string>();
}
// check if exists
foreach (var p in ga.ExtendedProperties.Shared)
{
if (p.Key == key)
{
if (ga.ExtendedProperties.Shared[p.Key] != oid)
{
ga.ExtendedProperties.Shared[p.Key] = oid;
return true;
}
return false;
}
}
//not found
var prop = new KeyValuePair<string, string>(key, oid);
ga.ExtendedProperties.Shared.Add(prop);
return true;
}
internal static string GetGoogleOutlookId(Event ga)
{
var key = OutlookPropertiesUtils.GetKey();
// get extended prop
if (ga.ExtendedProperties != null && ga.ExtendedProperties.Shared != null)
{
foreach (var p in ga.ExtendedProperties.Shared)
{
if (p.Key == key)
{
return p.Value;
}
}
}
return null;
}
internal static bool ResetGoogleOutlookId(Event ga)
{
var key = OutlookPropertiesUtils.GetKey();
if (ga.ExtendedProperties != null && ga.ExtendedProperties.Shared != null)
{
// get extended prop
foreach (var p in ga.ExtendedProperties.Shared)
{
if (p.Key == key)
{
// remove
ga.ExtendedProperties.Shared.Remove(p);
return true;
}
}
}
return false;
}
/// <summary>
/// Sets the syncId of the Outlook Appointment and the last sync date.
/// Please assure to always call this function when saving OutlookItem
/// </summary>
/// <param name="sync"></param>
/// <param name="oa"></param>
/// <param name="e"></param>
internal static bool SetOutlookGoogleId(Outlook.AppointmentItem oa, Event e)
{
if (e.Id == null)
{
throw new NullReferenceException("GoogleAppointment must have a valid Id");
}
Outlook.UserProperties userProps = null;
try
{
userProps = oa.UserProperties;
return OutlookPropertiesUtils.SetOutlookGoogleId(userProps, e.Id, e.ETag);
}
finally
{
if (userProps != null)
{
Marshal.ReleaseComObject(userProps);
}
}
}
internal static DateTime? GetOutlookLastSync(Outlook.AppointmentItem oa)
{
Outlook.UserProperties up = null;
try
{
up = oa.UserProperties;
return OutlookPropertiesUtils.GetOutlookLastSync(up);
}
finally
{
if (up != null)
{
Marshal.ReleaseComObject(up);
}
}
}
internal static string GetOutlookLastEtag(Outlook.AppointmentItem oa)
{
Outlook.UserProperties up = null;
try
{
up = oa.UserProperties;
return OutlookPropertiesUtils.GetOutlookLastEtag(up);
}
finally
{
if (up != null)
{
Marshal.ReleaseComObject(up);
}
}
}
internal static string GetOutlookGoogleId(Outlook.AppointmentItem oa)
{
Outlook.UserProperties up = null;
try
{
up = oa.UserProperties;
return OutlookPropertiesUtils.GetOutlookPropertyValue<string>(Synchronizer.OutlookPropertyNameId, up, Outlook.OlFormatText.olFormatTextText);
}
finally
{
if (up != null)
{
Marshal.ReleaseComObject(up);
}
}
}
internal static void ResetOutlookGoogleId(Outlook.AppointmentItem oa)
{
Outlook.UserProperties up = null;
try
{
up = oa.UserProperties;
OutlookPropertiesUtils.ResetOutlookGoogleId(up);
}
finally
{
if (up != null)
{
Marshal.ReleaseComObject(up);
}
}
}
internal static DateTime GetOutlookLastUpdated(AppointmentMatch match)
{
var lastUpdated = match.OutlookAppointment.LastModificationTime;
if (match.OutlookAppointment.IsRecurring)
{
//adding Outlook exception is not changing modification date of the parent
//first scan exceptions
Outlook.RecurrencePattern rp = null;
Outlook.Exceptions exceptions = null;
try
{
rp = match.OutlookAppointment.GetRecurrence();
exceptions = rp.Exceptions;
if (exceptions != null)
{
for (var i = exceptions.Count; i > 0; i--)
{
Outlook.Exception e = null;
Outlook.AppointmentItem ola = null;
try
{
e = exceptions[i];
if (e.Deleted &&
//ToDo: Check, if we Don't sync deleted instances, if they are in the past, otherwise they will be synchronized again and again and again
AppointmentSync.InSyncPeriod(e.OriginalDate)) //In Sync Range?
//e.OriginalDate >= DateTime.Now) //Or only in future?
{
//for deleted exception it is not possible to get
//their modification date
//==> Update it always, to not overlook a cancelled/deleted recurrence exception
//ToDo: Check, but only if the recurrence exception is within sync range, to not update it again and again (especially for old exceptions in the past)
//ToDo: Or only the ones in the future?
lastUpdated = DateTime.Now;
Log.Debug($"Deleted Outlook recurrence exception found in the future (original date {e.OriginalDate.ToString()}, trying to delete it again (if not yet done): {match.OutlookAppointment.ToLogString()}.");
break;
}
else
{ //Maybe it is possible to get the deletion date via the e.AppointmentItem.LastModificationTime???
try
{
ola = e.AppointmentItem;
}
catch
{
}
if (ola != null)
{
if (ola.LastModificationTime > lastUpdated)
{
lastUpdated = ola.LastModificationTime;
}
}
}
}
finally
{
if (ola != null)
{
Marshal.ReleaseComObject(ola);
}
if (e != null)
{
Marshal.ReleaseComObject(e);
}
}
}
}
}
finally
{
if (exceptions != null)
{
Marshal.ReleaseComObject(exceptions);
}
if (rp != null)
{
Marshal.ReleaseComObject(rp);
}
}
}
if (lastUpdated == null)
lastUpdated = DateTime.MinValue;
if (lastUpdated.Kind == DateTimeKind.Utc)
lastUpdated = TimeZoneInfo.ConvertTimeFromUtc(lastUpdated, TimeZoneInfo.Local);
//lastUpdated = lastUpdated.AddSeconds(-lastUpdated.Second); //Not needed for Outlook Appointments, because appointments can hold seconds (contacts cannot)
return lastUpdated;
}
internal static DateTime GetGoogleLastUpdated(AppointmentMatch match, Synchronizer sync)
{
DateTime lastUpdated = DateTime.MinValue;
var now = DateTime.Now;
if (match.GoogleAppointment.Updated.HasValue)
{
lastUpdated = match.GoogleAppointment.Updated.Value;
//consider GoogleAppointmentExceptions, because if they are updated, the master appointment doesn't have a new Saved TimeStamp
foreach (var ga in match.GoogleAppointmentExceptions)
{
if (ga.Updated != null)//happens for cancelled events
{
var lastUpdatedGoogleException = ga.Updated.Value;
if (lastUpdatedGoogleException > lastUpdated)
{
lastUpdated = lastUpdatedGoogleException;
}
}
else
{
var f_ga = sync.GetGoogleAppointment(ga.Id);
if (f_ga.Updated != null)
{
var lastUpdatedGoogleException = f_ga.Updated.Value;
if (lastUpdatedGoogleException > lastUpdated)
{
lastUpdated = lastUpdatedGoogleException;
}
}
else if (match.OutlookAppointment.IsRecurring && match.OutlookAppointment.RecurrenceState == Outlook.OlRecurrenceState.olApptMaster)
{
Outlook.AppointmentItem oa = null;
Outlook.RecurrencePattern rp = null;
try
{
try
{
rp = match.OutlookAppointment.GetRecurrence();
if (ga.OriginalStartTime != null)
{
if (!string.IsNullOrEmpty(ga.OriginalStartTime.Date))
{
var date = DateTime.Parse(ga.OriginalStartTime.Date);
//ToDo: Check, if we Don't sync deleted instances, if they are in the past, otherwise they will be synchronized again and again and again
if (AppointmentSync.InSyncPeriod(date)) //In sync range?
//if (date >= now.Date) //or only in the future?
oa = rp.GetOccurrence(date);
}
else if (ga.OriginalStartTime.DateTime != null)
{
//ToDo: Check, if we Don't sync deleted instances, if they are in the past, otherwise they will be synchronized again and again and again
if (AppointmentSync.InSyncPeriod(ga.OriginalStartTime.DateTime.Value)) //In sync range?
//if (ga.OriginalStartTime.DateTime.Value >= now) //or only in the future?
oa = rp.GetOccurrence(ga.OriginalStartTime.DateTime.Value);
}
}
}
catch (Exception ex) when ((uint)ex.HResult == 0x80004005)
{
//most likely there is deleted Outlook exception
lastUpdated = now;
break; //no need to search further, already newest date set
}
finally
{
if (rp != null)
{
Marshal.ReleaseComObject(rp);
rp = null;
}
}
if (oa != null && oa.MeetingStatus != Outlook.OlMeetingStatus.olMeetingCanceled)
{
lastUpdated = now;
break; //no need to search further, already newest date set
}
}
finally
{
if (oa != null)
{
Marshal.ReleaseComObject(oa);
oa = null;
}
}
}
}
}
}
else
{
//Maybe complete new Google Appointment, therefore now LastUpdate???
lastUpdated = now;
}
if (lastUpdated == null)
lastUpdated = DateTime.MinValue;
if (lastUpdated.Kind == DateTimeKind.Utc)
lastUpdated = TimeZoneInfo.ConvertTimeFromUtc(lastUpdated, TimeZoneInfo.Local);
//lastUpdated = lastUpdated.AddSeconds(-lastUpdated.Second); //Not needed for Outlook Appointments, because appointments can hold seconds (contacts cannot)
return lastUpdated;
}
}
}