Wednesday, June 29, 2011

Google Contacts Synchronisation

Having recently migrated to Google Apps at the organisation in which I work, one of the glaring omissions we discovered was the concept of a Global Address List to which I'd personally become accustomed in MS Exchange. Despite this persistently being at the top of the feature request list with Google, there still seems to be no easy way of sharing contacts between users, and so I decided to write some code to implement it programmatically.

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);
}
}
}

No comments:

Post a Comment