The application simply copies all contacts from one account (which might be designated the "master") to another, flagging them as it does so in order that they can be deleted/updated when the program is scheduled to run regularly. As such, the destination account's existing contacts are never modified. This works within Google Apps, and also as a means of syncing contacts between regular Gmail accounts.
To obtain the reference assemblies, you'll need to get the GData .NET Client Library from http://code.google.com/p/google-gdata/, or I've included the relevant DLLs in the source download at www.cslacey.co.uk/blog/downloads/CSL.GoogleContactsSync.zip.
The latter link also contains the compiled application - to start using it straight away, simply enter the relevant credentials into the .config file within /bin/Release, and run CSL.GoogleContactsSync.ConsoleApp.exe.
(Assembly references: System.Configuration, Google.GData.Client, Google.GData.Contacts, Google.GData.Extensions)
using System;
using System.Collections.Generic;
using System.Configuration;
using System.Linq;
using Google.Contacts;
using Google.GData.Client;
using Google.GData.Contacts;
namespace CSL.GoogleContactsSync.ConsoleApp
{
class Program
{
const int MAX_BATCH_SIZE = 100;
const string GOOGLE_APP_NAME = "CSL.GoogleContactsSync.ConsoleApp";
const string GAL_FIELD_KEY = "Origin";
const string GAL_FIELD_VALUE = "Global Address List";
static void Main(string[] args)
{
List<Contact> sourceContacts = GetSourceContacts(
ConfigurationManager.AppSettings["SourceGoogleUsername"],
ConfigurationManager.AppSettings["SourceGooglePassword"]);
DeleteExistingGlobalContacts(
ConfigurationManager.AppSettings["DestinationGoogleUsername"],
ConfigurationManager.AppSettings["DestinationGooglePassword"]);
InsertContacts(sourceContacts,
ConfigurationManager.AppSettings["DestinationGoogleUsername"],
ConfigurationManager.AppSettings["DestinationGooglePassword"]);
}
/// <summary>
/// Retrieve all contacts from the specified account in a form
/// suitable for insertion into other destination accounts
/// </summary>
private static List<Contact> GetSourceContacts(
string username, string password)
{
List<Contact> result = new List<Contact>();
RequestSettings requestSettings = new RequestSettings(
GOOGLE_APP_NAME, username, password);
ContactsRequest contactsRequest =
new ContactsRequest(requestSettings);
Feed<Contact> feed = contactsRequest.GetContacts();
feed.AutoPaging = true;
foreach (Contact contact in feed.Entries)
{
// Remove all group membership, as groups may not exist in
// destination account
while (contact.GroupMembership.Count > 0)
{
contact.GroupMembership.RemoveAt(0);
}
// Remove all user defined fields, as they may not exist in
// destination account
while (contact.ContactEntry.UserDefinedFields.Count > 0)
{
contact.ContactEntry.UserDefinedFields.RemoveAt(0);
}
// Flag retrieved contact as originating from the GAL
contact.ContactEntry.UserDefinedFields.Add(
new UserDefinedField(GAL_FIELD_VALUE, GAL_FIELD_KEY));
Console.WriteLine(String.Format("RETRIEVING CONTACT: {0}",
contact.Name.FullName));
result.Add(contact);
}
return result;
}
/// <summary>
/// Delete all contacts which have been flagged
/// as originating from the GAL
/// </summary>
private static void DeleteExistingGlobalContacts(
string username, string password)
{
RequestSettings requestSettings = new RequestSettings(
GOOGLE_APP_NAME, username, password);
ContactsRequest contactsRequest =
new ContactsRequest(requestSettings);
Feed<Contact> feed = contactsRequest.GetContacts();
feed.AutoPaging = false;
List<Contact> contactsToDelete;
do
{
contactsToDelete = new List<Contact>();
foreach (Contact contact in feed.Entries)
{
for (int i = 0;
i < contact.ContactEntry.UserDefinedFields.Count; i++)
{
if (contact.ContactEntry.UserDefinedFields[i].Key
== GAL_FIELD_KEY
&& contact.ContactEntry.UserDefinedFields[i].Value
== GAL_FIELD_VALUE)
{
// Contact originated from GAL, so add it to the
// delete batch
Console.WriteLine(String.Format("PENDING DELETE: {0}",
contact.Name.FullName));
contactsToDelete.Add(contact);
}
}
}
Console.WriteLine("BATCH DELETING {0} Contacts",
contactsToDelete.Count);
contactsRequest.Batch(contactsToDelete, feed,
GDataBatchOperationType.delete);
// Re-intialise batch and feed for next execution
contactsToDelete = new List<Contact>();
feed = contactsRequest.GetContacts();
}
while (feed.Entries.Count() > 0 && contactsToDelete.Count > 0);
}
/// <summary>
/// Insert the specified contacts
/// </summary>
private static void InsertContacts(
List<Contact> contacts, string username, string password)
{
RequestSettings requestSettings = new RequestSettings(
GOOGLE_APP_NAME, username, password);
ContactsRequest contactsRequest =
new ContactsRequest(requestSettings);
Feed<Contact> feed = contactsRequest.GetContacts();
List<Contact> contactsToInsert = new List<Contact>();
foreach (Contact contact in contacts)
{
Console.WriteLine(String.Format("PENDING INSERT: {0}",
contact.Name.FullName));
contactsToInsert.Add(contact);
if (contactsToInsert.Count == MAX_BATCH_SIZE)
{
Console.WriteLine("BATCH INSERTING {0} Contacts",
contactsToInsert.Count);
contactsRequest.Batch(contactsToInsert, feed,
GDataBatchOperationType.insert);
// Re-initialise batch for next execution
contactsToInsert = new List<Contact>();
}
}
Console.WriteLine("BATCH INSERTING {0} Contacts",
contactsToInsert.Count);
contactsRequest.Batch(contactsToInsert, feed,
GDataBatchOperationType.insert);
}
}
}