using System; using System.Collections.Generic; using System.Text; using GZTW.AyaNova.BLL; using Outlook = Microsoft.Office.Interop.Outlook; using Office = Microsoft.Office.Core; using System.Reflection; using System.ComponentModel; using System.Security.Cryptography; using System.Runtime.InteropServices; using System.Windows.Forms; namespace AyaNovaOL { class ImportSchedule { #region Globals private Outlook.Application moutApp = null; //Holds the current logged in user's localized text //lookup object private LocalizedTextTable mLocaleText = null; public ImportSchedule(Outlook.Application outapp, LocalizedTextTable localeText) { moutApp = outapp; mLocaleText = localeText; } public LocalizedTextTable LocaleText { get { return mLocaleText; } } #endregion globals #region Outlook api globals and util methods #region Outlook application object public Outlook.Application outApp { get { return moutApp; } } #endregion outlook app object #region AyaNova calendar folder Outlook.MAPIFolder mAyaCalendar = null; /// /// Get the AyaNova calendar /// create it if required /// public Outlook.MAPIFolder AyaCalendar { get { if (mAyaCalendar == null) { if (bdiag) d("Check for existing AyaNovaSchedule calendar..."); //see if there is one already, if not create it mAyaCalendar = GetFolder(Outlook.OlDefaultFolders.olFolderCalendar, "AyaNovaSchedule"); if (mAyaCalendar == null) { if (bdiag) d("Calendar AyaNovaSchedule not found in Outlook. Creating it..."); //get required objects to do the op Outlook.NameSpace ns = outApp.GetNamespace("MAPI"); Outlook.MAPIFolder fldDefault = ns.GetDefaultFolder(Outlook.OlDefaultFolders.olFolderCalendar); Outlook.Folders fldDefaultFolders = fldDefault.Folders; //create it mAyaCalendar = fldDefaultFolders.Add("AyaNovaSchedule", Outlook.OlDefaultFolders.olFolderCalendar); mAyaCalendar.Description = "AyaNova OLI exported items"; //release required objects Marshal.ReleaseComObject(fldDefaultFolders); fldDefaultFolders = null; Marshal.ReleaseComObject(fldDefault); fldDefault = null; Marshal.ReleaseComObject(ns); ns = null; } } return mAyaCalendar; } } #endregion ayanova calendar folder #region Remove AyaNova generated appointment from Outlook public void RemoveOutlookAppointment(TypeAndID tid) { // d("Remove outlook appointment top..."); Outlook.AppointmentItem oaiToDelete = null; Outlook.Items ayCalItems = AyaCalendar.Items; bool bFoundIt = false; for (int x = 0; x < ayCalItems.Count; x++) { //d("fetching outlook appointment for comparison..."); Outlook.AppointmentItem oai = (Outlook.AppointmentItem)ayCalItems[x + 1]; //d("examining outlook appointment user properties..."); Outlook.UserProperties props = null; if (oai.UserProperties != null) props = oai.UserProperties; if (props != null && props.Count > 0) { Outlook.UserProperty prop = props["AyaNovaTypeAndID"]; if (prop != null) { //d("prop.value to string:" + prop.Value.ToString()); //d("Tid to string:" + tid.ToString());//bugbug tid is null here if (prop.Value.ToString() == tid.ToString()) { //d("Have a match!"); oaiToDelete = oai; bFoundIt = true; } Marshal.ReleaseComObject(prop); prop = null; } Marshal.ReleaseComObject(props); props = null; } //d("Found? - " + bFoundIt.ToString()); if (bFoundIt) break; else Marshal.ReleaseComObject(oai); oai = null; } Marshal.ReleaseComObject(ayCalItems); ayCalItems = null; //d("Finished in loop"); //Remove from Outlook if (oaiToDelete != null) { //d("Deleteing outlook appointment " + oaiToDelete.Subject + " ..."); oaiToDelete.Delete(); if (oaiToDelete != null) { Marshal.ReleaseComObject(oaiToDelete); oaiToDelete = null; } } else throw new System.ApplicationException("Outlook schedule plugin: Error RemoveOutlookAppointment: appointment matching [" + tid.ToString() + "] not found in Outlook"); //d("Remove outlook appointment bottom."); } #endregion #region Insert Shadow appointment into Outlook public void InsertShadowAppointment(ShadowAppointment sa) { Outlook.Items ayCalItems = AyaCalendar.Items; Outlook.AppointmentItem oa = (Outlook.AppointmentItem)ayCalItems.Add(Outlook.OlItemType.olAppointmentItem); Outlook.UserProperties props = oa.UserProperties; Outlook.UserProperty prop = props.Add("AyaNovaTypeAndID", Microsoft.Office.Interop.Outlook.OlUserPropertyType.olText, false, null); prop.Value = sa.AyaNovaTypeAndID.ToString(); oa.Subject = sa.Subject; oa.Location = sa.Location; oa.Start = sa.Start; oa.End = sa.End; oa.Body = sa.BodyText; if (sa.ShowTimeAsBusy) oa.BusyStatus = Microsoft.Office.Interop.Outlook.OlBusyStatus.olBusy; else oa.BusyStatus = Microsoft.Office.Interop.Outlook.OlBusyStatus.olTentative; oa.ReminderSet = false; oa.Save(); Marshal.ReleaseComObject(prop); prop = null; Marshal.ReleaseComObject(props); props = null; Marshal.ReleaseComObject(oa); oa = null; Marshal.ReleaseComObject(ayCalItems); ayCalItems = null; } #endregion insert shadow appt. #region Localized text stuff string mSchedUserLocalizedText = null; string mSchedMarkerLocalizedText = null; public string SchedMarkerLocalizedText { get { if (mSchedMarkerLocalizedText == null) mSchedMarkerLocalizedText = LocaleText.GetLocalizedText("O.ScheduleMarker"); return mSchedMarkerLocalizedText; } } public string SchedUserLocalizedText { get { if (mSchedUserLocalizedText == null) mSchedUserLocalizedText = LocaleText.GetLocalizedText("O.WorkorderItemScheduledUser"); return mSchedUserLocalizedText; } } #endregion LocalizedTextstuff #region Client contact cache Dictionary clientInfoCache = null; /// /// Get client info /// /// /// public string GetClientInfoDisplay(Guid id) { //check in cache first if (clientInfoCache == null) clientInfoCache = new Dictionary(); if (!clientInfoCache.ContainsKey(id)) { //retrieve the client record Client c = Client.GetItemNoMRU(id); StringBuilder b = new StringBuilder(); b.Append(c.Name); b.Append("\r\n"); b.Append(c.GoToAddress.FullAddress); b.Append("\r\n"); b.Append(c.GetPrimaryContactDefaultContactInfo()); //if (c.TechNotes != "") //{ // b.Append(LocaleText.GetLocalizedText("Client.Label.TechNotes")); // b.Append(":\r\n"); // b.Append(c.TechNotes); //} clientInfoCache.Add(id, b.ToString()); } return clientInfoCache[id]; } #endregion client contact cache #region utils #region ShadowAppointmentStuff /// /// /// /// /// public ShadowAppointment GetShadowAppointment(AppointmentList.AppointmentListInfo i) { ShadowAppointment a = new ShadowAppointment(); if (i.SourceObjectType == RootObjectTypes.ScheduleMarker) { ScheduleMarker sm = ScheduleMarker.GetItem(i.SourceObjectID); a.Subject = sm.Name; a.Location = SchedMarkerLocalizedText; a.Start = i.StartDateTime; a.End = i.EndDateTime; a.ShowTimeAsBusy = false; string sFollowUpText = ""; if (sm.FollowID != Guid.Empty) sFollowUpText = NameFetcher.GetItem(new TypeAndID(sm.FollowType, sm.FollowID)).RecordName + "\r\n"; a.BodyText = sFollowUpText + sm.Notes; a.AyaNovaTypeAndID = new TypeAndID(RootObjectTypes.ScheduleMarker, i.SourceObjectID); } else//workorderitemscheduleuser { WorkorderServiceList.WorkorderServiceListInfo woinfo = WorkorderServiceList.GetListForSingleItem(i.WorkorderID)[0]; a.Subject = i.ServiceNumber.ToString() + " " + woinfo.LT_O_Client.Display; a.Location = SchedUserLocalizedText; a.Start = i.StartDateTime; a.End = i.EndDateTime; a.ShowTimeAsBusy = true; a.BodyText = i.Subject + "\r\n------------------------------------\r\n" + GetClientInfoDisplay(woinfo.LT_O_Client.Value); a.AyaNovaTypeAndID = new TypeAndID(i.SourceObjectType, i.SourceObjectID); } System.Text.StringBuilder sb = new StringBuilder(); sb.Append(a.Subject); sb.Append(a.Location); sb.Append(a.Start.ToString("yyyyMMddHHmm")); sb.Append(a.End.ToString("yyyyMMddHHmm")); sb.Append(a.BodyText); a.CheckSum = GetHash(sb.ToString()); //if (i.SourceObjectType == RootObjectTypes.ScheduleMarker) // d("Shadow appt. checksum:" + a.CheckSum + "\r\nData was:\r\n" + sb.ToString()); return a; } #endregion shadowappointmentstuff #region Outlook folder stuff /// /// Iterates all folders of type specified and returns the one with the name specified /// Caller MUST release folder returned /// /// /// public Outlook.MAPIFolder GetFolder(Outlook.OlDefaultFolders folderType, string sNameContains) { //d("Top of GetFolder..."); //get required objects to do the op Outlook.NameSpace ns = outApp.GetNamespace("MAPI"); Outlook.MAPIFolder f = GetCalendar(ns.GetDefaultFolder(folderType), sNameContains); // d("Got folder, releasing objects..."); if (ns != null) { Marshal.ReleaseComObject(ns); ns = null; } return f; } /// /// Recursive method to iterate folders and return one that contains the name specified /// /// /// /// public Outlook.MAPIFolder GetCalendar(Outlook.MAPIFolder mf, string sNameContains) { //d("Top of GetCalendar " + mf.FolderPath + ", " + sNameContains); if (mf.FolderPath.Contains(sNameContains)) { //d("GetCalendar: Found it, returning."); return mf; } Outlook.Folders fldrs = mf.Folders; for (int x = 0; x < fldrs.Count; x++) { //d("GetCalendar: Checking calendar " + x.ToString() + " of " + mf.Folders.Count.ToString()); Outlook.MAPIFolder f = mf.Folders[x + 1]; if (GetCalendar(f, sNameContains) != null) { Marshal.ReleaseComObject(fldrs); fldrs = null; return f; } else { Marshal.ReleaseComObject(f); f = null; } } //d("GetCalendar: Not here, returning."); Marshal.ReleaseComObject(fldrs); fldrs = null; return null; } #endregion Outlook folder stuff #region Checksum stuff private aptOut GetOutlookAppointmentChecksum(Outlook.AppointmentItem oai) { //d("top of GetOutlookAppointmentChecksum(" + oai.Subject + ")"); aptOut a; a.CheckSum = ""; a.AyaNovaTypeAndID = new TypeAndID(RootObjectTypes.Nothing, Guid.Empty); if (oai == null) return a; if (string.IsNullOrEmpty(oai.EntryID)) return a; Outlook.UserProperties props = oai.UserProperties; Outlook.UserProperty prop = props["AyaNovaTypeAndID"]; a.AyaNovaTypeAndID = TypeAndID.Parse(prop.Value.ToString()); Marshal.ReleaseComObject(prop); prop = null; Marshal.ReleaseComObject(props); props = null; System.Text.StringBuilder sb = new StringBuilder(); sb.Append(oai.Subject); sb.Append(oai.Location); sb.Append(oai.Start.ToString("yyyyMMddHHmm")); sb.Append(oai.End.ToString("yyyyMMddHHmm")); sb.Append(oai.Body); a.CheckSum = GetHash(sb.ToString()); //if(a.CheckSum.StartsWith("92C")) // d("Outlook appt. checksum:"+a.CheckSum+"\r\nData was:\r\n" + sb.ToString()); return a; } /// /// Get a reliable hash value /// /// /// private string GetHash(string s) { //TODO: FIPS SHA256 shaM = new SHA256Managed(); UTF8Encoding enc = new UTF8Encoding(); return BitConverter.ToString(shaM.ComputeHash(enc.GetBytes(s))).Replace("-", ""); } #endregion checksum stuff #endregion utils #endregion outlook api globals and util methods #region Diagnostic messages /// /// Diagnostics messages /// /// private void d(string s) { if (bdiag) MessageBox.Show(s); } private bool bdiag = false; #endregion diags #region DoImport /// /// Do the import /// public void Import(bool DiagnosticMode) { bdiag = DiagnosticMode; DateTime dtStarted = DateTime.Now.AddMinutes(-1); if (bdiag) d("Starting export of items between " + dtStarted.ToString() + " and " + dtStarted.AddYears(1) + "..."); List lsaptOut = new List(); List lsShadowApt = new List(); AppointmentList apl = null; try { #region Build checksum list of appointments in Outlook if (bdiag) d("Inspecting existing AyaNovaSchedule items in Outlook ..."); Outlook.Items ayCalItems = AyaCalendar.Items; // d("Raw AyaCalendar.Items.Count=" + ayCalItems.Count); for (int x = 0; x < ayCalItems.Count; x++) { //d("Fetching existing item " + x.ToString()+" ..."); Outlook.AppointmentItem oai = (Outlook.AppointmentItem)ayCalItems[x + 1]; if (oai.Start > dtStarted || oai.End > dtStarted)//appointments that start after now or end after now { Outlook.UserProperties props = oai.UserProperties; if (props != null && props.Count > 0) { Outlook.UserProperty prop = props["AyaNovaTypeAndID"]; if (prop != null) { lsaptOut.Add(GetOutlookAppointmentChecksum(oai)); Marshal.ReleaseComObject(prop); prop = null; } Marshal.ReleaseComObject(props); props = null; } } Marshal.ReleaseComObject(oai); oai = null; } Marshal.ReleaseComObject(ayCalItems); ayCalItems = null; if (bdiag) d(lsaptOut.Count.ToString() + " existing relevant AyaNovaSchedule Outlook calendar items found in Outlook."); #endregion build checksum list #region Build shadow appointment list if (bdiag) d("Fetching any existing open AyaNova schedule items in the next year for current logged in user..."); apl = AppointmentList.GetList(dtStarted, dtStarted.AddYears(1), User.CurrentThreadUserID); UserListScheduleable uls = UserListScheduleable.GetList(); foreach (AppointmentList.AppointmentListInfo i in apl) { switch (i.SourceObjectType) { case RootObjectTypes.WorkorderItemScheduledUser: { lsShadowApt.Add(GetShadowAppointment(i)); } break; case RootObjectTypes.ScheduleMarker: { #region schedmarkers ////Could be a bunch by region , global , dispatchzone, schedusergroup ////or could be a single by one user ID switch (i.AppliesToObjectType) { case RootObjectTypes.User: if (i.AppliesToObjectID == User.CurrentThreadUserID) lsShadowApt.Add(GetShadowAppointment(i)); break; case RootObjectTypes.Region: { //Loop through all active scheduleable users //if the region ID matches the marker region ID then //add a marker on the calendar for it foreach (UserListScheduleable.UserListScheduleableInfo ui in uls) { if (ui.ID == User.CurrentThreadUserID) { //case 58 if (i.AppliesToObjectID == GZTW.AyaNova.BLL.Region.DefaultRegionID)//SM is for default region? Then applies to everyone lsShadowApt.Add(GetShadowAppointment(i)); else if (ui.RegionID == GZTW.AyaNova.BLL.Region.DefaultRegionID)//User is in default region? Then applies to them lsShadowApt.Add(GetShadowAppointment(i)); else if (ui.RegionID == i.AppliesToObjectID)//SM and User in same non default region lsShadowApt.Add(GetShadowAppointment(i)); break; } } } break; case RootObjectTypes.DispatchZone: { //Loop through all active scheduleable users //if the Dispatch zone ID matches the marker zone ID then //add a marker on the calendar for it foreach (UserListScheduleable.UserListScheduleableInfo ui in uls) if (ui.ID == User.CurrentThreadUserID) { if (ui.DispatchZoneID == i.AppliesToObjectID) lsShadowApt.Add(GetShadowAppointment(i)); break; } } break; case RootObjectTypes.ScheduleableUserGroup: { ScheduleableUserGroupUsersList ScheduleMarkerGroup = ScheduleableUserGroupUsersList.GetList(i.AppliesToObjectID); //Loop through all active scheduleable users //if they are in the ScheduleableUserGroup then //add a marker on the calendar for it foreach (UserListScheduleable.UserListScheduleableInfo ui in uls) if (ui.ID == User.CurrentThreadUserID) { if (ScheduleMarkerGroup.Contains(ui.ID)) lsShadowApt.Add(GetShadowAppointment(i)); break; } } break; case RootObjectTypes.Global: { lsShadowApt.Add(GetShadowAppointment(i)); } break; } #endregion schedmarkers } break; } } if (bdiag) d(lsShadowApt.Count.ToString() + " existing AyaNova schedule items found in AyaNova."); #endregion build shadow appointment list //Do Export: #region step 1 remove stale outlook appointments if (bdiag) d("Removing existing AyaNovaSchedule Outlook calendar items that are changed since last export ..."); //iterate outlook appointments list, for each AyaNova generated one //that has no matching checksum in shadow, delete it from outlook then from outlist int nlsaptOutCount = lsaptOut.Count; int nTotalStaleCount = 0; #region mismatch Diagnostics stuff if (bdiag) { System.Text.StringBuilder sbdiag = new StringBuilder(); sbdiag.Append("Shadowlist:\r\n"); foreach (ShadowAppointment sa in lsShadowApt) { sbdiag.Append(sa.Subject); sbdiag.Append(" ck:"); sbdiag.Append(sa.CheckSum); sbdiag.Append("\r\n"); } d(sbdiag.ToString()); sbdiag.Length = 0; sbdiag.Append("Outlist:\r\n"); foreach (aptOut sa in lsaptOut) { sbdiag.Append(sa.AyaNovaTypeAndID.ToString()); sbdiag.Append(" ck:"); sbdiag.Append(sa.CheckSum); sbdiag.Append("\r\n"); } d(sbdiag.ToString()); } #endregion mismatch diags //iterate the outlook appointments list for (int x = 0; x < nlsaptOutCount; x++) { //d("x=" + x.ToString() + ", nlsaptOutCount=" + nlsaptOutCount.ToString() + ", lsaptOut.Count=" + lsaptOut.Count.ToString() + ", lsShadowApt.count=" + lsShadowApt.Count.ToString()); bool bExists = false; //if outlook appointment checksum not in shadow list delete it aptOut ThisapOut = lsaptOut[x]; foreach (ShadowAppointment sa in lsShadowApt) { //System.Diagnostics.Debug.Assert(!string.IsNullOrEmpty(sa.CheckSum)); //d("Comparing outlook checksum:\r\n " + ThisapOut.CheckSum + "\r\nTo shadow checksum:\r\n" + sa.CheckSum); if (sa.CheckSum == ThisapOut.CheckSum) { //d("Identical match: " + sa.CheckSum); bExists = true; break; } } if (!bExists) { //d("Unmatched outlook checksum:\r\n " + ThisapOut.CheckSum ); //remove from outlook RemoveOutlookAppointment(ThisapOut.AyaNovaTypeAndID); //remove from the working list lsaptOut.RemoveAt(x); x--; nlsaptOutCount--; nTotalStaleCount++; } } if (bdiag) d(nTotalStaleCount.ToString() + " stale items removed from Outlook."); #endregion step 1 #region Step 2 Remove unchanged appointments from shadow appointment list if (bdiag) d("Identifying AyaNova items that already exist identically in Outlook and do not need to be exported..."); //Iterate through shadow appointments, for each one that has matching checksum in outlook apts remove it from shadow list int nShadowListCount = lsShadowApt.Count; int nTotalUnchangedShadowItems = 0; for (int x = 0; x < nShadowListCount; x++) { bool bExists = false; ShadowAppointment sa = lsShadowApt[x]; foreach (aptOut ao in lsaptOut) { if (ao.CheckSum == sa.CheckSum) { bExists = true; break; } } if (bExists) { //remove from shadowlist lsShadowApt.RemoveAt(x); x--; nShadowListCount--; nTotalUnchangedShadowItems++; } } if (bdiag) d(nTotalUnchangedShadowItems.ToString() + " items in AyaNova already exist unchanged in Outlook and won't be exported."); #endregion Step 2 #region Step 3 Insert remaining shadow appointments in Outlook if (bdiag) { if (lsShadowApt.Count > 0) d("Exporting " + lsShadowApt.Count.ToString() + " new / updated AyaNova schedule items to Outlook..."); else d("No items found to export to Outlook."); } foreach (ShadowAppointment sa in lsShadowApt) { InsertShadowAppointment(sa); } #endregion step 3 } catch (Exception ex) { throw (ex); } finally { #region Step 4 release objects if (bdiag) d("Releasing calendar object..."); if (mAyaCalendar != null) { Marshal.ReleaseComObject(mAyaCalendar); mAyaCalendar = null; } //if (bdiag) d("Releasing app object..."); //if (moutApp != null) //{ // Marshal.ReleaseComObject(moutApp); moutApp = null; //} //clear the client cache if (clientInfoCache != null) { if (bdiag) d("Clearing client cache..."); clientInfoCache.Clear(); clientInfoCache = null; } if (bdiag) d("All done!"); #endregion step 4 } } #endregion doimport #region Appointment and comparison structs public struct aptOut { public TypeAndID AyaNovaTypeAndID; public string CheckSum; } /// /// contains all the data that will go into an Outlook appointment /// public struct ShadowAppointment { public string Subject; public string Location; public DateTime Start; public DateTime End; public bool ShowTimeAsBusy; public string BodyText; public TypeAndID AyaNovaTypeAndID; public string CheckSum; } #endregion appt comparison structs }//end of class }