public ConflictResolution Resolve(ContactMatch match, bool isNewMatch) { string name = match.ToString(); if (isNewMatch) { _form.messageLabel.Text = "This is the first time these Outlook and Google Contacts \"" + name + "\" are synced. Choose which you would like to keep."; _form.skip.Text = "Keep both"; } else { _form.messageLabel.Text = "Both the Outlook Contact and the Google Contact \"" + name + "\" have been changed. Choose which you would like to keep."; } _form.OutlookItemTextBox.Text = string.Empty; _form.GoogleItemTextBox.Text = string.Empty; if (match.OutlookContact != null) { Microsoft.Office.Interop.Outlook.ContactItem item = match.OutlookContact.GetOriginalItemFromOutlook(); try { _form.OutlookItemTextBox.Text = ContactMatch.GetSummary(item); } finally { if (item != null) { System.Runtime.InteropServices.Marshal.ReleaseComObject(item); item = null; } } } if (match.GoogleContact != null) _form.GoogleItemTextBox.Text = ContactMatch.GetSummary(match.GoogleContact); return Resolve(); }
public static void SyncContact(ContactMatch match, Synchronizer sync) { Outlook.ContactItem outlookContactItem = match.OutlookContact != null ? match.OutlookContact.GetOriginalItemFromOutlook() : null; try { if (match.GoogleContact == null && match.OutlookContact != null) { //no google contact string googleContactId = match.OutlookContact.UserProperties.GoogleContactId; if (!string.IsNullOrEmpty(googleContactId)) { //Redundant check if exist, but in case an error occurred in MatchContacts Contact matchingGoogleContact = sync.GetGoogleContactById(googleContactId); if (matchingGoogleContact == null) { if (sync.SyncOption == SyncOption.OutlookToGoogleOnly || !sync.SyncDelete) return; else if (!sync.PromptDelete) sync.DeleteOutlookResolution = DeleteResolution.DeleteOutlookAlways; else if (sync.DeleteOutlookResolution != DeleteResolution.DeleteOutlookAlways && sync.DeleteOutlookResolution != DeleteResolution.KeepOutlookAlways) { var r = new ConflictResolver(); sync.DeleteOutlookResolution = r.ResolveDelete(match.OutlookContact); } switch (sync.DeleteOutlookResolution) { case DeleteResolution.KeepOutlook: case DeleteResolution.KeepOutlookAlways: ContactPropertiesUtils.ResetOutlookGoogleContactId(sync, outlookContactItem); break; case DeleteResolution.DeleteOutlook: case DeleteResolution.DeleteOutlookAlways: //Avoid recreating a GoogleContact already existing //==> Delete this outlookContact instead if previous match existed but no match exists anymore return; default: throw new ApplicationException("Cancelled"); } } } if (sync.SyncOption == SyncOption.GoogleToOutlookOnly) { sync.SkippedCount++; Logger.Log(string.Format("Outlook Contact not added to Google, because of SyncOption " + sync.SyncOption.ToString() + ": {0}", match.OutlookContact.FileAs), EventType.Information); return; } //create a Google contact from Outlook contact match.GoogleContact = new Contact(); sync.UpdateContact(outlookContactItem, match.GoogleContact); } else if (match.OutlookContact == null && match.GoogleContact != null) { // no outlook contact string outlookId = ContactPropertiesUtils.GetGoogleOutlookContactId(sync.SyncProfile, match.GoogleContact); if (outlookId != null) { if (sync.SyncOption == SyncOption.GoogleToOutlookOnly || !sync.SyncDelete) return; else if (!sync.PromptDelete) sync.DeleteGoogleResolution = DeleteResolution.DeleteGoogleAlways; else if (sync.DeleteGoogleResolution != DeleteResolution.DeleteGoogleAlways && sync.DeleteGoogleResolution != DeleteResolution.KeepGoogleAlways) { var r = new ConflictResolver(); sync.DeleteGoogleResolution = r.ResolveDelete(match.GoogleContact); } switch (sync.DeleteGoogleResolution) { case DeleteResolution.KeepGoogle: case DeleteResolution.KeepGoogleAlways: ContactPropertiesUtils.ResetGoogleOutlookContactId(sync.SyncProfile, match.GoogleContact); break; case DeleteResolution.DeleteGoogle: case DeleteResolution.DeleteGoogleAlways: //Avoid recreating a OutlookContact already existing //==> Delete this googleContact instead if previous match existed but no match exists anymore return; default: throw new ApplicationException("Cancelled"); } } if (sync.SyncOption == SyncOption.OutlookToGoogleOnly) { sync.SkippedCount++; Logger.Log(string.Format("Google Contact not added to Outlook, because of SyncOption " + sync.SyncOption.ToString() + ": {0}", match.GoogleContact.Title), EventType.Information); return; } //create a Outlook contact from Google contact outlookContactItem = Synchronizer.CreateOutlookContactItem(Synchronizer.SyncContactsFolder); sync.UpdateContact(match.GoogleContact, outlookContactItem); match.OutlookContact = new OutlookContactInfo(outlookContactItem, sync); } else if (match.OutlookContact != null && match.GoogleContact != null) { //merge contact details //determine if this contact pair were synchronized //DateTime? lastUpdated = GetOutlookPropertyValueDateTime(match.OutlookContact, sync.OutlookPropertyNameUpdated); DateTime? lastSynced = match.OutlookContact.UserProperties.LastSync; if (lastSynced.HasValue) { //contact pair was syncronysed before. //determine if google contact was updated since last sync //lastSynced is stored without seconds. take that into account. DateTime lastUpdatedOutlook = match.OutlookContact.LastModificationTime.AddSeconds(-match.OutlookContact.LastModificationTime.Second); DateTime lastUpdatedGoogle = match.GoogleContact.Updated.AddSeconds(-match.GoogleContact.Updated.Second); //check if both outlok and google contacts where updated sync last sync if (lastUpdatedOutlook.Subtract(lastSynced.Value).TotalSeconds > TimeTolerance && lastUpdatedGoogle.Subtract(lastSynced.Value).TotalSeconds > TimeTolerance) { //both contacts were updated. //options: 1) ignore 2) loose one based on SyncOption //throw new Exception("Both contacts were updated!"); switch (sync.SyncOption) { case SyncOption.MergeOutlookWins: case SyncOption.OutlookToGoogleOnly: //overwrite google contact Logger.Log("Outlook and Google contact have been updated, Outlook contact is overwriting Google because of SyncOption " + sync.SyncOption + ": " + match.OutlookContact.FileAs + ".", EventType.Information); sync.UpdateContact(outlookContactItem, match.GoogleContact); break; case SyncOption.MergeGoogleWins: case SyncOption.GoogleToOutlookOnly: //overwrite outlook contact Logger.Log("Outlook and Google contact have been updated, Google contact is overwriting Outlook because of SyncOption " + sync.SyncOption + ": " + match.OutlookContact.FileAs + ".", EventType.Information); sync.UpdateContact(match.GoogleContact, outlookContactItem); break; case SyncOption.MergePrompt: //promp for sync option if (sync.ConflictResolution != ConflictResolution.GoogleWinsAlways && sync.ConflictResolution != ConflictResolution.OutlookWinsAlways && sync.ConflictResolution != ConflictResolution.SkipAlways) { var r = new ConflictResolver(); sync.ConflictResolution = r.Resolve(match, false); } switch (sync.ConflictResolution) { case ConflictResolution.Skip: case ConflictResolution.SkipAlways: Logger.Log(string.Format("User skipped contact ({0}).", match.ToString()), EventType.Information); sync.SkippedCount++; break; case ConflictResolution.OutlookWins: case ConflictResolution.OutlookWinsAlways: sync.UpdateContact(outlookContactItem, match.GoogleContact); break; case ConflictResolution.GoogleWins: case ConflictResolution.GoogleWinsAlways: sync.UpdateContact(match.GoogleContact, outlookContactItem); break; default: throw new ApplicationException("Cancelled"); } break; } return; } //check if outlook contact was updated (with X second tolerance) if (sync.SyncOption != SyncOption.GoogleToOutlookOnly && (lastUpdatedOutlook.Subtract(lastSynced.Value).TotalSeconds > TimeTolerance || lastUpdatedGoogle.Subtract(lastSynced.Value).TotalSeconds > TimeTolerance && sync.SyncOption == SyncOption.OutlookToGoogleOnly ) ) { //outlook contact was changed or changed Google contact will be overwritten if (lastUpdatedGoogle.Subtract(lastSynced.Value).TotalSeconds > TimeTolerance && sync.SyncOption == SyncOption.OutlookToGoogleOnly) Logger.Log("Google contact has been updated since last sync, but Outlook contact is overwriting Google because of SyncOption " + sync.SyncOption + ": " + match.OutlookContact.FileAs + ".", EventType.Information); sync.UpdateContact(outlookContactItem, match.GoogleContact); //at the moment use outlook as "master" source of contacts - in the event of a conflict google contact will be overwritten. //TODO: control conflict resolution by SyncOption return; } //check if google contact was updated (with X second tolerance) if (sync.SyncOption != SyncOption.OutlookToGoogleOnly && (lastUpdatedGoogle.Subtract(lastSynced.Value).TotalSeconds > TimeTolerance || lastUpdatedOutlook.Subtract(lastSynced.Value).TotalSeconds > TimeTolerance && sync.SyncOption == SyncOption.GoogleToOutlookOnly ) ) { //google contact was changed or changed Outlook contact will be overwritten if (lastUpdatedOutlook.Subtract(lastSynced.Value).TotalSeconds > TimeTolerance && sync.SyncOption == SyncOption.GoogleToOutlookOnly) Logger.Log("Outlook contact has been updated since last sync, but Google contact is overwriting Outlook because of SyncOption " + sync.SyncOption + ": " + match.OutlookContact.FileAs + ".", EventType.Information); sync.UpdateContact(match.GoogleContact, outlookContactItem); } } else { //contacts were never synced. //merge contacts. switch (sync.SyncOption) { case SyncOption.MergeOutlookWins: case SyncOption.OutlookToGoogleOnly: //overwrite google contact sync.UpdateContact(outlookContactItem, match.GoogleContact); break; case SyncOption.MergeGoogleWins: case SyncOption.GoogleToOutlookOnly: //overwrite outlook contact sync.UpdateContact(match.GoogleContact, outlookContactItem); break; case SyncOption.MergePrompt: //promp for sync option if (sync.ConflictResolution != ConflictResolution.GoogleWinsAlways && sync.ConflictResolution != ConflictResolution.OutlookWinsAlways && sync.ConflictResolution != ConflictResolution.SkipAlways) { var r = new ConflictResolver(); sync.ConflictResolution = r.Resolve(match, true); } switch (sync.ConflictResolution) { case ConflictResolution.Skip: case ConflictResolution.SkipAlways: //Keep both, Google AND Outlook sync.Contacts.Add(new ContactMatch(match.OutlookContact, null)); sync.Contacts.Add(new ContactMatch(null, match.GoogleContact)); break; case ConflictResolution.OutlookWins: case ConflictResolution.OutlookWinsAlways: sync.UpdateContact(outlookContactItem, match.GoogleContact); break; case ConflictResolution.GoogleWins: case ConflictResolution.GoogleWinsAlways: sync.UpdateContact(match.GoogleContact, outlookContactItem); break; default: throw new ApplicationException("Cancelled"); } break; } } } else throw new ArgumentNullException("ContactMatch has all peers null."); } catch (ArgumentNullException e) { throw e; } catch (Exception e) { throw new Exception("Error syncing contact " + (match.OutlookContact != null ? match.OutlookContact.FileAs : match.GoogleContact.Title) + ": " + e.Message, e); } finally { if (outlookContactItem != null && match.OutlookContact != null) { match.OutlookContact.Update(outlookContactItem, sync); Marshal.ReleaseComObject(outlookContactItem); outlookContactItem = null; } } }
/// <summary> /// Matches outlook and google contact by a) google id b) properties. /// </summary> /// <param name="sync">Syncronizer instance</param> /// <param name="duplicatesFound">Exception returned, if duplicates have been found (null else)</param> /// <returns>Returns a list of match pairs (outlook contact + google contact) for all contact. Those that weren't matche will have it's peer set to null</returns> public static List<ContactMatch> MatchContacts(Synchronizer sync, out DuplicateDataException duplicatesFound) { Logger.Log("Matching Outlook and Google contacts...", EventType.Information); var result = new List<ContactMatch>(); var duplicateGoogleMatches = string.Empty; var duplicateOutlookContacts = string.Empty; sync.GoogleContactDuplicates = new Collection<ContactMatch>(); sync.OutlookContactDuplicates = new Collection<ContactMatch>(); var skippedOutlookIds = new List<string>(); //for each outlook contact try to get google contact id from user properties //if no match - try to match by properties //if no match - create a new match pair without google contact. //foreach (Outlook._ContactItem olc in outlookContacts) var outlookContactsWithoutOutlookGoogleId = new Collection<OutlookContactInfo>(); #region Match first all outlookContacts by sync id for (int i = 1; i <= sync.OutlookContacts.Count; i++) { Outlook.ContactItem olc = null; try { olc = sync.OutlookContacts[i] as Outlook.ContactItem; if (olc == null) { Logger.Log("Empty Outlook contact found (maybe distribution list). Skipping", EventType.Warning); sync.SkippedCount++; sync.SkippedCountNotMatches++; continue; } } catch (Exception ex) { //this is needed because some contacts throw exceptions Logger.Log("Accessing Outlook contact threw and exception. Skipping: " + ex.Message, EventType.Warning); sync.SkippedCount++; sync.SkippedCountNotMatches++; continue; } try { // sometimes contacts throw Exception when accessing their properties, so we give it a controlled try first. try { string email1Address = olc.Email1Address; } catch (Exception ex) { string message = string.Format("Can't access contact details for outlook contact, got {0} - '{1}'. Skipping", ex.GetType().ToString(), ex.Message); try { message = string.Format("{0} {1}.", message, olc.FileAs); //remember skippedOutlookIds to later not delete them if found on Google side skippedOutlookIds.Add(string.Copy(olc.EntryID)); } catch { //e.g. if olc.FileAs also fails, ignore, because messge already set //message = null; } //if (olc != null && message != null) // it's useless to say "we couldn't access some contacts properties //{ Logger.Log(message, EventType.Warning); //} sync.SkippedCount++; sync.SkippedCountNotMatches++; continue; } if (!IsContactValid(olc)) { Logger.Log(string.Format("Invalid outlook contact ({0}). Skipping", olc.FileAs), EventType.Warning); skippedOutlookIds.Add(string.Copy(olc.EntryID)); sync.SkippedCount++; sync.SkippedCountNotMatches++; continue; } if (olc.Body != null && olc.Body.Length > 62000) { // notes field too large Logger.Log(string.Format("Skipping outlook contact ({0}). Reduce the notes field to a maximum of 62.000 characters.", olc.FileAs), EventType.Warning); skippedOutlookIds.Add(string.Copy(olc.EntryID)); sync.SkippedCount++; sync.SkippedCountNotMatches++; continue; } if (NotificationReceived != null) NotificationReceived(String.Format("Matching contact {0} of {1} by id: {2} ...", i, sync.OutlookContacts.Count, olc.FileAs)); // Create our own info object to go into collections/lists, so we can free the Outlook objects and not run out of resources / exceed policy limits. var olci = new OutlookContactInfo(olc, sync); //try to match this contact to one of google contacts Outlook.UserProperties userProperties = olc.UserProperties; Outlook.UserProperty idProp = userProperties[sync.OutlookPropertyNameId]; try { if (idProp != null) { string googleContactId = string.Copy((string)idProp.Value); Contact foundContact = sync.GetGoogleContactById(googleContactId); var match = new ContactMatch(olci, null); //Check first, that this is not a duplicate //e.g. by copying an existing Outlook contact //or by Outlook checked this as duplicate, but the user selected "Add new" Collection<OutlookContactInfo> duplicates = sync.OutlookContactByProperty(sync.OutlookPropertyNameId, googleContactId); if (duplicates.Count > 1) { foreach (OutlookContactInfo duplicate in duplicates) { if (!string.IsNullOrEmpty(googleContactId)) { Logger.Log("Duplicate Outlook contact found, resetting match and trying to match again: " + duplicate.FileAs, EventType.Warning); Outlook.ContactItem item = duplicate.GetOriginalItemFromOutlook(); try { ContactPropertiesUtils.ResetOutlookGoogleContactId(sync, item); item.Save(); } finally { if (item != null) { Marshal.ReleaseComObject(item); item = null; } } } } if (foundContact != null && !foundContact.Deleted) { ContactPropertiesUtils.ResetGoogleOutlookContactId(sync.SyncProfile, foundContact); } outlookContactsWithoutOutlookGoogleId.Add(olci); } else { if (foundContact != null && !foundContact.Deleted) { //we found a match by google id, that is not deleted yet match.AddGoogleContact(foundContact); result.Add(match); //Remove the contact from the list to not sync it twice sync.GoogleContacts.Remove(foundContact); } else { outlookContactsWithoutOutlookGoogleId.Add(olci); } } } else outlookContactsWithoutOutlookGoogleId.Add(olci); } finally { if (idProp != null) Marshal.ReleaseComObject(idProp); Marshal.ReleaseComObject(userProperties); } } finally { Marshal.ReleaseComObject(olc); olc = null; } } #endregion #region Match the remaining contacts by properties for (int i = 0; i < outlookContactsWithoutOutlookGoogleId.Count; i++) { OutlookContactInfo olci = outlookContactsWithoutOutlookGoogleId[i]; if (NotificationReceived != null) NotificationReceived(String.Format("Matching contact {0} of {1} by unique properties: {2} ...", i + 1, outlookContactsWithoutOutlookGoogleId.Count, olci.FileAs)); //no match found by id => match by common properties //create a default match pair with just outlook contact. var match = new ContactMatch(olci, null); //foreach google contact try to match and create a match pair if found some match(es) for (int j=sync.GoogleContacts.Count-1;j>=0;j--) { Contact entry = sync.GoogleContacts[j]; if (entry.Deleted) continue; // only match if there is either an email or telephone or else // a matching google contact will be created at each sync //1. try to match by FileAs //1.1 try to match by FullName //2. try to match by primary email //3. try to match by mobile phone number, don't match by home or business bumbers, because several people may share the same home or business number //4. try to math Company, if Google Title is null, i.e. the contact doesn't have a name and title, only a company string entryTitleFirstLastAndSuffix = OutlookContactInfo.GetTitleFirstLastAndSuffix(entry); if (!string.IsNullOrEmpty(olci.FileAs) && !string.IsNullOrEmpty(entry.Title) && olci.FileAs.Equals(entry.Title.Replace("\r\n", "\n").Replace("\n", "\r\n"), StringComparison.InvariantCultureIgnoreCase) || //Replace twice to not replace a \r\n by \r\r\n. This is necessary because \r\n are saved as \n only to google !string.IsNullOrEmpty(olci.FileAs) && !string.IsNullOrEmpty(entry.Name.FullName) && olci.FileAs.Equals(entry.Name.FullName.Replace("\r\n", "\n").Replace("\n", "\r\n"), StringComparison.InvariantCultureIgnoreCase) || !string.IsNullOrEmpty(olci.FullName) && !string.IsNullOrEmpty(entry.Name.FullName) && olci.FullName.Equals(entry.Name.FullName.Replace("\r\n", "\n").Replace("\n", "\r\n"), StringComparison.InvariantCultureIgnoreCase) || !string.IsNullOrEmpty(olci.TitleFirstLastAndSuffix) && !string.IsNullOrEmpty(entryTitleFirstLastAndSuffix) && olci.TitleFirstLastAndSuffix.Equals(entryTitleFirstLastAndSuffix.Replace("\r\n", "\n").Replace("\n", "\r\n"), StringComparison.InvariantCultureIgnoreCase) || !string.IsNullOrEmpty(olci.Email1Address) && entry.Emails.Count > 0 && olci.Email1Address.Equals(entry.Emails[0].Address, StringComparison.InvariantCultureIgnoreCase) || //!string.IsNullOrEmpty(olci.Email2Address) && FindEmail(olci.Email2Address, entry.Emails) != null || //!string.IsNullOrEmpty(olci.Email3Address) && FindEmail(olci.Email3Address, entry.Emails) != null || olci.MobileTelephoneNumber != null && FindPhone(olci.MobileTelephoneNumber, entry.Phonenumbers) != null || !string.IsNullOrEmpty(olci.FileAs) && string.IsNullOrEmpty(entry.Title) && entry.Organizations.Count > 0 && olci.FileAs.Equals(entry.Organizations[0].Name, StringComparison.InvariantCultureIgnoreCase) ) { match.AddGoogleContact(entry); sync.GoogleContacts.Remove(entry); } } #region find duplicates not needed now //if (match.GoogleContact == null && match.OutlookContact != null) //{//If GoogleContact, we have to expect a conflict because of Google insert of duplicates // foreach (Contact entry in sync.GoogleContacts) // { // if (!string.IsNullOrEmpty(olc.FullName) && olc.FullName.Equals(entry.Title, StringComparison.InvariantCultureIgnoreCase) || // !string.IsNullOrEmpty(olc.FileAs) && olc.FileAs.Equals(entry.Title, StringComparison.InvariantCultureIgnoreCase) || // !string.IsNullOrEmpty(olc.Email1Address) && FindEmail(olc.Email1Address, entry.Emails) != null || // !string.IsNullOrEmpty(olc.Email2Address) && FindEmail(olc.Email1Address, entry.Emails) != null || // !string.IsNullOrEmpty(olc.Email3Address) && FindEmail(olc.Email1Address, entry.Emails) != null || // olc.MobileTelephoneNumber != null && FindPhone(olc.MobileTelephoneNumber, entry.Phonenumbers) != null // ) // } //// check for each email 1,2 and 3 if a duplicate exists with same email, because Google doesn't like inserting new contacts with same email //Collection<Outlook.ContactItem> duplicates1 = new Collection<Outlook.ContactItem>(); //Collection<Outlook.ContactItem> duplicates2 = new Collection<Outlook.ContactItem>(); //Collection<Outlook.ContactItem> duplicates3 = new Collection<Outlook.ContactItem>(); //if (!string.IsNullOrEmpty(olc.Email1Address)) // duplicates1 = sync.OutlookContactByEmail(olc.Email1Address); //if (!string.IsNullOrEmpty(olc.Email2Address)) // duplicates2 = sync.OutlookContactByEmail(olc.Email2Address); //if (!string.IsNullOrEmpty(olc.Email3Address)) // duplicates3 = sync.OutlookContactByEmail(olc.Email3Address); //if (duplicates1.Count > 1 || duplicates2.Count > 1 || duplicates3.Count > 1) //{ // if (string.IsNullOrEmpty(duplicatesEmailList)) // duplicatesEmailList = "Outlook contacts with the same email have been found and cannot be synchronized. Please delete duplicates of:"; // if (duplicates1.Count > 1) // foreach (Outlook.ContactItem duplicate in duplicates1) // { // string str = olc.FileAs + " (" + olc.Email1Address + ")"; // if (!duplicatesEmailList.Contains(str)) // duplicatesEmailList += Environment.NewLine + str; // } // if (duplicates2.Count > 1) // foreach (Outlook.ContactItem duplicate in duplicates2) // { // string str = olc.FileAs + " (" + olc.Email2Address + ")"; // if (!duplicatesEmailList.Contains(str)) // duplicatesEmailList += Environment.NewLine + str; // } // if (duplicates3.Count > 1) // foreach (Outlook.ContactItem duplicate in duplicates3) // { // string str = olc.FileAs + " (" + olc.Email3Address + ")"; // if (!duplicatesEmailList.Contains(str)) // duplicatesEmailList += Environment.NewLine + str; // } // continue; //} //else if (!string.IsNullOrEmpty(olc.Email1Address)) //{ // ContactMatch dup = result.Find(delegate(ContactMatch match) // { // return match.OutlookContact != null && match.OutlookContact.Email1Address == olc.Email1Address; // }); // if (dup != null) // { // Logger.Log(string.Format("Duplicate contact found by Email1Address ({0}). Skipping", olc.FileAs), EventType.Information); // continue; // } //} //// check for unique mobile phone, because this sync tool uses the also the mobile phone to identify matches between Google and Outlook //Collection<Outlook.ContactItem> duplicatesMobile = new Collection<Outlook.ContactItem>(); //if (!string.IsNullOrEmpty(olc.MobileTelephoneNumber)) // duplicatesMobile = sync.OutlookContactByProperty("MobileTelephoneNumber", olc.MobileTelephoneNumber); //if (duplicatesMobile.Count > 1) //{ // if (string.IsNullOrEmpty(duplicatesMobileList)) // duplicatesMobileList = "Outlook contacts with the same mobile phone have been found and cannot be synchronized. Please delete duplicates of:"; // foreach (Outlook.ContactItem duplicate in duplicatesMobile) // { // sync.OutlookContactDuplicates.Add(olc); // string str = olc.FileAs + " (" + olc.MobileTelephoneNumber + ")"; // if (!duplicatesMobileList.Contains(str)) // duplicatesMobileList += Environment.NewLine + str; // } // continue; //} //else if (!string.IsNullOrEmpty(olc.MobileTelephoneNumber)) //{ // ContactMatch dup = result.Find(delegate(ContactMatch match) // { // return match.OutlookContact != null && match.OutlookContact.MobileTelephoneNumber == olc.MobileTelephoneNumber; // }); // if (dup != null) // { // Logger.Log(string.Format("Duplicate contact found by MobileTelephoneNumber ({0}). Skipping", olc.FileAs), EventType.Information); // continue; // } //} #endregion if (match.AllGoogleContactMatches == null || match.AllGoogleContactMatches.Count == 0) { //Check, if this Outlook contact has a match in the google duplicates bool duplicateFound = false; foreach (ContactMatch duplicate in sync.GoogleContactDuplicates) { string entryTitleFirstLastAndSuffix = OutlookContactInfo.GetTitleFirstLastAndSuffix(duplicate.AllGoogleContactMatches[0]); if (duplicate.AllGoogleContactMatches.Count > 0 && (!string.IsNullOrEmpty(olci.FileAs) && !string.IsNullOrEmpty(duplicate.AllGoogleContactMatches[0].Title) && olci.FileAs.Equals(duplicate.AllGoogleContactMatches[0].Title.Replace("\r\n", "\n").Replace("\n","\r\n"), StringComparison.InvariantCultureIgnoreCase) || //Replace twice to not replace a \r\n by \r\r\n. This is necessary because \r\n are saved as \n only to google !string.IsNullOrEmpty(olci.FileAs) && !string.IsNullOrEmpty(duplicate.AllGoogleContactMatches[0].Name.FullName) && olci.FileAs.Equals(duplicate.AllGoogleContactMatches[0].Name.FullName.Replace("\r\n", "\n").Replace("\n", "\r\n"), StringComparison.InvariantCultureIgnoreCase) || !string.IsNullOrEmpty(olci.FullName) && !string.IsNullOrEmpty(duplicate.AllGoogleContactMatches[0].Name.FullName) && olci.FullName.Equals(duplicate.AllGoogleContactMatches[0].Name.FullName.Replace("\r\n", "\n").Replace("\n","\r\n"), StringComparison.InvariantCultureIgnoreCase) || !string.IsNullOrEmpty(olci.TitleFirstLastAndSuffix) && !string.IsNullOrEmpty(entryTitleFirstLastAndSuffix) && olci.TitleFirstLastAndSuffix.Equals(entryTitleFirstLastAndSuffix.Replace("\r\n", "\n").Replace("\n", "\r\n"), StringComparison.InvariantCultureIgnoreCase) || !string.IsNullOrEmpty(olci.Email1Address) && duplicate.AllGoogleContactMatches[0].Emails.Count > 0 && olci.Email1Address.Equals(duplicate.AllGoogleContactMatches[0].Emails[0].Address, StringComparison.InvariantCultureIgnoreCase) || //!string.IsNullOrEmpty(olci.Email2Address) && FindEmail(olci.Email2Address, duplicate.AllGoogleContactMatches[0].Emails) != null || //!string.IsNullOrEmpty(olci.Email3Address) && FindEmail(olci.Email3Address, duplicate.AllGoogleContactMatches[0].Emails) != null || olci.MobileTelephoneNumber != null && FindPhone(olci.MobileTelephoneNumber, duplicate.AllGoogleContactMatches[0].Phonenumbers) != null || !string.IsNullOrEmpty(olci.FileAs) && string.IsNullOrEmpty(duplicate.AllGoogleContactMatches[0].Title) && duplicate.AllGoogleContactMatches[0].Organizations.Count > 0 && olci.FileAs.Equals(duplicate.AllGoogleContactMatches[0].Organizations[0].Name, StringComparison.InvariantCultureIgnoreCase) ) || !string.IsNullOrEmpty(olci.FileAs) && olci.FileAs.Equals(duplicate.OutlookContact.FileAs, StringComparison.InvariantCultureIgnoreCase) || !string.IsNullOrEmpty(olci.FullName) && olci.FullName.Equals(duplicate.OutlookContact.FullName, StringComparison.InvariantCultureIgnoreCase) || !string.IsNullOrEmpty(olci.TitleFirstLastAndSuffix) && olci.TitleFirstLastAndSuffix.Equals(duplicate.OutlookContact.TitleFirstLastAndSuffix, StringComparison.InvariantCultureIgnoreCase) || !string.IsNullOrEmpty(olci.Email1Address) && olci.Email1Address.Equals(duplicate.OutlookContact.Email1Address, StringComparison.InvariantCultureIgnoreCase) || // olci.Email1Address.Equals(duplicate.OutlookContact.Email2Address, StringComparison.InvariantCultureIgnoreCase) || // olci.Email1Address.Equals(duplicate.OutlookContact.Email3Address, StringComparison.InvariantCultureIgnoreCase) // ) || //!string.IsNullOrEmpty(olci.Email2Address) && (olci.Email2Address.Equals(duplicate.OutlookContact.Email1Address, StringComparison.InvariantCultureIgnoreCase) || // olci.Email2Address.Equals(duplicate.OutlookContact.Email2Address, StringComparison.InvariantCultureIgnoreCase) || // olci.Email2Address.Equals(duplicate.OutlookContact.Email3Address, StringComparison.InvariantCultureIgnoreCase) // ) || //!string.IsNullOrEmpty(olci.Email3Address) && (olci.Email3Address.Equals(duplicate.OutlookContact.Email1Address, StringComparison.InvariantCultureIgnoreCase) || // olci.Email3Address.Equals(duplicate.OutlookContact.Email2Address, StringComparison.InvariantCultureIgnoreCase) || // olci.Email3Address.Equals(duplicate.OutlookContact.Email3Address, StringComparison.InvariantCultureIgnoreCase) // ) || olci.MobileTelephoneNumber != null && olci.MobileTelephoneNumber.Equals(duplicate.OutlookContact.MobileTelephoneNumber) || !string.IsNullOrEmpty(olci.FileAs) && string.IsNullOrEmpty(duplicate.GoogleContact.Title) && duplicate.GoogleContact.Organizations.Count > 0 && olci.FileAs.Equals(duplicate.GoogleContact.Organizations[0].Name, StringComparison.InvariantCultureIgnoreCase) ) { duplicateFound = true; duplicate.AddOutlookContact(olci); sync.OutlookContactDuplicates.Add(match); if (string.IsNullOrEmpty(duplicateOutlookContacts)) duplicateOutlookContacts = "Outlook contact found that has been already identified as duplicate Google contact (either same email, Mobile or FullName) and cannot be synchronized. Please delete or resolve duplicates of:"; string str = olci.FileAs + " (" + olci.Email1Address + ", " + olci.MobileTelephoneNumber + ")"; if (!duplicateOutlookContacts.Contains(str)) duplicateOutlookContacts += Environment.NewLine + str; break; } } if (!duplicateFound) Logger.Log(string.Format("No match found for outlook contact ({0}) => {1}", olci.FileAs, (olci.UserProperties.GoogleContactId != null?"Delete from Outlook":"Add to Google")), EventType.Information); } else { //Remember Google duplicates to later react to it when resetting matches or syncing //ResetMatches: Also reset the duplicates //Sync: Skip duplicates (don't sync duplicates to be fail safe) if (match.AllGoogleContactMatches.Count > 1) { sync.GoogleContactDuplicates.Add(match); foreach (Contact entry in match.AllGoogleContactMatches) { //Create message for duplicatesFound exception if (string.IsNullOrEmpty(duplicateGoogleMatches)) duplicateGoogleMatches = "Outlook contacts matching with multiple Google contacts have been found (either same email, Mobile, FullName or company) and cannot be synchronized. Please delete or resolve duplicates of:"; string str = olci.FileAs + " (" + olci.Email1Address + ", " + olci.MobileTelephoneNumber + ")"; if (!duplicateGoogleMatches.Contains(str)) duplicateGoogleMatches += Environment.NewLine + str; } } } result.Add(match); } #endregion if (!string.IsNullOrEmpty(duplicateGoogleMatches) || !string.IsNullOrEmpty(duplicateOutlookContacts)) duplicatesFound = new DuplicateDataException(duplicateGoogleMatches + Environment.NewLine + Environment.NewLine + duplicateOutlookContacts); else duplicatesFound = null; //return result; //for each google contact that's left (they will be nonmatched) create a new match pair without outlook contact. for (int i=0; i< sync.GoogleContacts.Count;i++) { Contact entry = sync.GoogleContacts[i]; if (NotificationReceived != null) NotificationReceived(String.Format("Adding new Google contact {0} of {1} by unique properties: {2} ...", i+1, sync.GoogleContacts.Count, entry.Title)); // only match if there is either an email or mobile phone or a name or a company // otherwise a matching google contact will be created at each sync bool mobileExists = false; foreach (PhoneNumber phone in entry.Phonenumbers) { if (phone.Rel == ContactsRelationships.IsMobile) { mobileExists = true; break; } } string googleOutlookId = ContactPropertiesUtils.GetGoogleOutlookContactId(sync.SyncProfile, entry); if (!String.IsNullOrEmpty(googleOutlookId) && skippedOutlookIds.Contains(googleOutlookId)) { Logger.Log("Skipped GoogleContact because Outlook contact couldn't be matched because of previous problem (see log): " + entry.Title, EventType.Warning); } else if (entry.Emails.Count == 0 && !mobileExists && string.IsNullOrEmpty(entry.Title) && (entry.Organizations.Count == 0 || string.IsNullOrEmpty(entry.Organizations[0].Name))) { // no telephone and email //ToDo: For now I use the ResolveDelete function, because it is almost the same, maybe we introduce a separate function for this ans also include DeleteGoogleAlways checkbox var r = new ConflictResolver(); DeleteResolution res = r.ResolveDelete(entry); if (res == DeleteResolution.DeleteGoogle || res == DeleteResolution.DeleteGoogleAlways) { ContactPropertiesUtils.SetGoogleOutlookContactId(sync.SyncProfile, entry, "-1"); //just set a dummy Id to delete this entry later on sync.SaveContact(new ContactMatch(null, entry)); } else { sync.SkippedCount++; sync.SkippedCountNotMatches++; Logger.Log("Skipped GoogleContact because no unique property found (Email1 or mobile or name or company):" + ContactMatch.GetSummary(entry), EventType.Warning); } } else { Logger.Log(string.Format("No match found for Google contact ({0}) => {1}", entry.Title, (!string.IsNullOrEmpty(googleOutlookId) ? "Delete from Google" : "Add to Outlook")), EventType.Information); var match = new ContactMatch(null, entry); result.Add(match); } } return result; }
private void ResolveDuplicateContact(ContactMatch match) { if (Contacts.Contains(match)) { if (_syncOption == SyncOption.MergePrompt) { //For each OutlookDuplicate: Ask user for the GoogleContact to be synced with for (int j = match.AllOutlookContactMatches.Count - 1; j >= 0 && match.AllGoogleContactMatches.Count > 0; j--) { OutlookContactInfo olci = match.AllOutlookContactMatches[j]; Outlook.ContactItem outlookContactItem = olci.GetOriginalItemFromOutlook(); try { Contact googleContact; ConflictResolver r = new ConflictResolver(); switch (r.ResolveDuplicate(olci, match.AllGoogleContactMatches, out googleContact)) { case ConflictResolution.Skip: case ConflictResolution.SkipAlways: //Keep both entries and sync it to both sides match.AllGoogleContactMatches.Remove(googleContact); match.AllOutlookContactMatches.Remove(olci); Contacts.Add(new ContactMatch(null, googleContact)); Contacts.Add(new ContactMatch(olci, null)); break; case ConflictResolution.OutlookWins: case ConflictResolution.OutlookWinsAlways: //Keep Outlook and overwrite Google match.AllGoogleContactMatches.Remove(googleContact); match.AllOutlookContactMatches.Remove(olci); UpdateContact(outlookContactItem, googleContact); SaveContact(new ContactMatch(olci, googleContact)); break; case ConflictResolution.GoogleWins: case ConflictResolution.GoogleWinsAlways: //Keep Google and overwrite Outlook match.AllGoogleContactMatches.Remove(googleContact); match.AllOutlookContactMatches.Remove(olci); UpdateContact(googleContact, outlookContactItem); SaveContact(new ContactMatch(olci, googleContact)); break; default: throw new ApplicationException("Cancelled"); } } finally { if (outlookContactItem != null) { Marshal.ReleaseComObject(outlookContactItem); outlookContactItem = null; } } //Cleanup the match, i.e. assign a proper OutlookContact and GoogleContact, because can be deleted before if (match.AllOutlookContactMatches.Count == 0) match.OutlookContact = null; else match.OutlookContact = match.AllOutlookContactMatches[0]; } } //Cleanup the match, i.e. assign a proper OutlookContact and GoogleContact, because can be deleted before if (match.AllGoogleContactMatches.Count == 0) match.GoogleContact = null; else match.GoogleContact = match.AllGoogleContactMatches[0]; if (match.AllOutlookContactMatches.Count == 0) { //If all OutlookContacts have been assigned by the users ==> Create one match for each remaining Google Contact to sync them to Outlook Contacts.Remove(match); foreach (Contact googleContact in match.AllGoogleContactMatches) Contacts.Add(new ContactMatch(null, googleContact)); } else if (match.AllGoogleContactMatches.Count == 0) { //If all GoogleContacts have been assigned by the users ==> Create one match for each remaining Outlook Contact to sync them to Google Contacts.Remove(match); foreach (OutlookContactInfo outlookContact in match.AllOutlookContactMatches) Contacts.Add(new ContactMatch(outlookContact, null)); } else // if (match.AllGoogleContactMatches.Count > 1 || // match.AllOutlookContactMatches.Count > 1) { SkippedCount++; Contacts.Remove(match); } //else //{ // //If there remains a modified ContactMatch with only a single OutlookContact and GoogleContact // //==>Remove all outlookContactDuplicates for this Outlook Contact to not remove it later from the Contacts to sync // foreach (ContactMatch duplicate in OutlookContactDuplicates) // { // if (duplicate.OutlookContact.EntryID == match.OutlookContact.EntryID) // { // OutlookContactDuplicates.Remove(duplicate); // break; // } // } //} } }
//public void SaveContactPhotos(ContactMatch match) //{ // bool hasGooglePhoto = Utilities.HasPhoto(match.GoogleContact); // bool hasOutlookPhoto = Utilities.HasPhoto(match.OutlookContact); // if (!hasGooglePhoto && !hasOutlookPhoto) // return; // else if (hasGooglePhoto && _syncOption != SyncOption.OutlookToGoogleOnly) // { // // add google photo to outlook // Image googlePhoto = Utilities.GetGooglePhoto(this, match.GoogleContact); // Utilities.SetOutlookPhoto(match.OutlookContact, googlePhoto); // match.OutlookContact.Save(); // googlePhoto.Dispose(); // } // else if (hasOutlookPhoto && _syncOption != SyncOption.GoogleToOutlookOnly) // { // // add outlook photo to google // Image outlookPhoto = Utilities.GetOutlookPhoto(match.OutlookContact); // if (outlookPhoto != null) // { // outlookPhoto = Utilities.CropImageGoogleFormat(outlookPhoto); // bool saved = Utilities.SaveGooglePhoto(this, match.GoogleContact, outlookPhoto); // if (!saved) // throw new Exception("Could not save"); // outlookPhoto.Dispose(); // } // } // else // { // // TODO: if both contacts have photos and one is updated, the // // other will not be updated. // } // //Utilities.DeleteTempPhoto(); //} public void SaveGooglePhoto(ContactMatch match, Outlook.ContactItem outlookContactitem) { bool hasGooglePhoto = Utilities.HasPhoto(match.GoogleContact); bool hasOutlookPhoto = Utilities.HasPhoto(outlookContactitem); if (hasOutlookPhoto) { // add outlook photo to google Image outlookPhoto = Utilities.GetOutlookPhoto(outlookContactitem); if (outlookPhoto != null) { //Try up to 5 times to overcome Google issue for (int retry = 0; retry < 5; retry++) { try { using (MemoryStream stream = new MemoryStream(Utilities.BitmapToBytes(new Bitmap(outlookPhoto)))) { // Save image to stream. //outlookPhoto.Save(stream, System.Drawing.Imaging.ImageFormat.Bmp); //Don'T crop, because maybe someone wants to keep his photo like it is on Outlook //outlookPhoto = Utilities.CropImageGoogleFormat(outlookPhoto); ContactsRequest.SetPhoto(match.GoogleContact, stream); //Just save the Outlook Contact to have the same lastUpdate date as Google ContactPropertiesUtils.SetOutlookGoogleContactId(this, outlookContactitem, match.GoogleContact); outlookContactitem.Save(); outlookPhoto.Dispose(); } break; //Exit because photo save succeeded } catch (GDataRequestException ex) { //If Google found a picture for a new Google account, it sets it automatically and throws an error, if updating it with the Outlook photo. //Therefore save it again and try again to save the photo if (retry == 4) ErrorHandler.Handle(new Exception("Photo of contact " + match.GoogleContact.Title + "couldn't be saved after 5 tries, maybe Google found its own photo and doesn't allow updating it", ex)); else { System.Threading.Thread.Sleep(1000); //LoadGoogleContact again to get latest ETag //match.GoogleContact = LoadGoogleContacts(match.GoogleContact.AtomEntry.Id); match.GoogleContact = SaveGoogleContact(match.GoogleContact); } } } } } else if (hasGooglePhoto) { //Delete Photo on Google side, if no Outlook photo exists ContactsRequest.Delete(match.GoogleContact.PhotoUri, match.GoogleContact.PhotoEtag); } Utilities.DeleteTempPhoto(); }
public void SaveGoogleContact(ContactMatch match) { Outlook.ContactItem outlookContactItem = match.OutlookContact.GetOriginalItemFromOutlook(); try { ContactPropertiesUtils.SetGoogleOutlookContactId(SyncProfile, match.GoogleContact, outlookContactItem); match.GoogleContact = SaveGoogleContact(match.GoogleContact); ContactPropertiesUtils.SetOutlookGoogleContactId(this, outlookContactItem, match.GoogleContact); outlookContactItem.Save(); //Now save the Photo SaveGooglePhoto(match, outlookContactItem); } finally { Marshal.ReleaseComObject(outlookContactItem); outlookContactItem = null; } }
public void SaveContact(ContactMatch match) { if (match.GoogleContact != null && match.OutlookContact != null) { //bool googleChanged, outlookChanged; //SaveContactGroups(match, out googleChanged, out outlookChanged); if (match.GoogleContact.ContactEntry.Dirty || match.GoogleContact.ContactEntry.IsDirty()) { //google contact was modified. save. SyncedCount++; SaveGoogleContact(match); Logger.Log("Updated Google contact from Outlook: \"" + match.OutlookContact.FileAs + "\".", EventType.Information); } } else if (match.GoogleContact == null && match.OutlookContact != null) { if (match.OutlookContact.UserProperties.GoogleContactId != null) { string name = match.OutlookContact.FileAs; if (_syncOption == SyncOption.OutlookToGoogleOnly) { SkippedCount++; Logger.Log("Skipped Deletion of Outlook contact because of SyncOption " + _syncOption + ":" + name + ".", EventType.Information); } else if (!SyncDelete) { SkippedCount++; Logger.Log("Skipped Deletion of Outlook contact because SyncDeletion is switched off: " + name + ".", EventType.Information); } else { // peer google contact was deleted, delete outlook contact Outlook.ContactItem item = match.OutlookContact.GetOriginalItemFromOutlook(); try { try { //First reset OutlookGoogleContactId to restore it later from trash ContactPropertiesUtils.ResetOutlookGoogleContactId(this, item); item.Save(); } catch (Exception) { Logger.Log("Error resetting match for Outlook contact: \"" + name + "\".", EventType.Warning); } item.Delete(); DeletedCount++; Logger.Log("Deleted Outlook contact: \"" + name + "\".", EventType.Information); } finally { Marshal.ReleaseComObject(item); item = null; } } } } else if (match.GoogleContact != null && match.OutlookContact == null) { if (ContactPropertiesUtils.GetGoogleOutlookContactId(SyncProfile, match.GoogleContact) != null) { if (_syncOption == SyncOption.GoogleToOutlookOnly) { SkippedCount++; Logger.Log("Skipped Deletion of Google contact because of SyncOption " + _syncOption + ":" + ContactMatch.GetName(match.GoogleContact) + ".", EventType.Information); } else if (!SyncDelete) { SkippedCount++; Logger.Log("Skipped Deletion of Google contact because SyncDeletion is switched off :" + ContactMatch.GetName(match.GoogleContact) + ".", EventType.Information); } else { //commented oud, because it causes precondition failed error, if the ResetMatch is short before the Delete //// peer outlook contact was deleted, delete google contact //try //{ // //First reset GoogleOutlookContactId to restore it later from trash // match.GoogleContact = ResetMatch(match.GoogleContact); //} //catch (Exception) //{ // Logger.Log("Error resetting match for Google contact: \"" + ContactMatch.GetName(match.GoogleContact) + "\".", EventType.Warning); //} ContactsRequest.Delete(match.GoogleContact); DeletedCount++; Logger.Log("Deleted Google contact: \"" + ContactMatch.GetName(match.GoogleContact) + "\".", EventType.Information); } } } else { //TODO: ignore for now: throw new ArgumentNullException("To save contacts, at least a GoogleContacat or OutlookContact must be present."); //Logger.Log("Both Google and Outlook contact: \"" + match.OutlookContact.FileAs + "\" have been changed! Not implemented yet.", EventType.Warning); } }