/**
*
*/
package fm.last.android.sync;
import java.io.IOException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import fm.last.android.AndroidLastFmServerFactory;
import fm.last.android.LastFMApplication;
import fm.last.api.Event;
import fm.last.api.LastFmServer;
import fm.last.api.WSError;
import android.accounts.Account;
import android.accounts.OperationCanceledException;
import android.app.Service;
import android.content.AbstractThreadedSyncAdapter;
import android.content.ContentProviderClient;
import android.content.ContentProviderOperation;
import android.content.ContentResolver;
import android.content.ContentUris;
import android.content.Context;
import android.content.Intent;
import android.content.SyncResult;
import android.content.SharedPreferences.Editor;
import android.database.Cursor;
import android.net.Uri;
import android.os.Bundle;
import android.os.IBinder;
import android.os.RemoteException;
import android.preference.PreferenceManager;
import android.provider.BaseColumns;
import android.provider.CalendarContract;
import android.provider.CalendarContract.Calendars;
import android.provider.CalendarContract.Events;
import android.util.Log;
/**
* @author sam
*
*/
public class CalendarSyncAdapterService extends Service {
private static SyncAdapterImpl sSyncAdapter = null;
private static ContentResolver mContentResolver = null;
private static Integer syncSchema = 1;
public CalendarSyncAdapterService() {
super();
}
private static class SyncAdapterImpl extends AbstractThreadedSyncAdapter {
private Context mContext;
public SyncAdapterImpl(Context context) {
super(context, true);
mContext = context;
}
@Override
public void onPerformSync(Account account, Bundle extras, String authority, ContentProviderClient provider, SyncResult syncResult) {
try {
CalendarSyncAdapterService.performSync(mContext, account, extras, authority, provider, syncResult);
} catch (OperationCanceledException e) {
}
}
}
@Override
public IBinder onBind(Intent intent) {
IBinder ret = null;
ret = getSyncAdapter().getSyncAdapterBinder();
return ret;
}
private SyncAdapterImpl getSyncAdapter() {
if (sSyncAdapter == null)
sSyncAdapter = new SyncAdapterImpl(this);
return sSyncAdapter;
}
private static long getCalendar(Account account) {
// Find the Last.fm calendar if we've got one
Uri calenderUri = Calendars.CONTENT_URI.buildUpon().appendQueryParameter(Calendars.ACCOUNT_NAME, account.name).appendQueryParameter(
Calendars.ACCOUNT_TYPE, account.type).build();
Cursor c1 = mContentResolver.query(calenderUri, new String[] { BaseColumns._ID }, null, null, null);
if (c1.moveToNext()) {
return c1.getLong(0);
} else {
ArrayList<ContentProviderOperation> operationList = new ArrayList<ContentProviderOperation>();
ContentProviderOperation.Builder builder = ContentProviderOperation.newInsert(Calendars.CONTENT_URI.buildUpon()
.appendQueryParameter(CalendarContract.CALLER_IS_SYNCADAPTER, "true")
.appendQueryParameter(Calendars.ACCOUNT_NAME, account.name)
.appendQueryParameter(Calendars.ACCOUNT_TYPE, account.type)
.build()
);
builder.withValue(Calendars.ACCOUNT_NAME, account.name);
builder.withValue(Calendars.ACCOUNT_TYPE, account.type);
builder.withValue(Calendars.NAME, "Last.fm Events");
builder.withValue(Calendars.CALENDAR_DISPLAY_NAME, "Last.fm Events");
builder.withValue(Calendars.CALENDAR_COLOR, 0xD51007);
builder.withValue(Calendars.CALENDAR_ACCESS_LEVEL, Calendars.CAL_ACCESS_RESPOND);
builder.withValue(Calendars.OWNER_ACCOUNT, account.name);
builder.withValue(Calendars.SYNC_EVENTS, 1);
operationList.add(builder.build());
try {
mContentResolver.applyBatch(CalendarContract.AUTHORITY, operationList);
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
return -1;
}
return getCalendar(account);
}
}
private static void deleteEvent(Context context, Account account, long rawId) {
Uri uri = ContentUris.withAppendedId(Events.CONTENT_URI, rawId).buildUpon()
.appendQueryParameter(CalendarContract.CALLER_IS_SYNCADAPTER, "true")
.appendQueryParameter(Calendars.ACCOUNT_NAME, account.name)
.appendQueryParameter(Calendars.ACCOUNT_TYPE, account.type)
.build();
ContentProviderClient client = context.getContentResolver().acquireContentProviderClient(CalendarContract.AUTHORITY);
try {
client.delete(uri, null, null);
} catch (RemoteException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
client.release();
}
private static ContentProviderOperation updateEvent(long calendar_id, Account account, Event event, long raw_id) {
ContentProviderOperation.Builder builder;
if(raw_id != -1) {
builder = ContentProviderOperation.newUpdate(Events.CONTENT_URI.buildUpon()
.appendQueryParameter(CalendarContract.CALLER_IS_SYNCADAPTER, "true")
.appendQueryParameter(Calendars.ACCOUNT_NAME, account.name)
.appendQueryParameter(Calendars.ACCOUNT_TYPE, account.type)
.build()
);
builder.withSelection(Events._ID + " = '" + raw_id + "'", null);
} else {
builder = ContentProviderOperation.newInsert(Events.CONTENT_URI.buildUpon()
.appendQueryParameter(CalendarContract.CALLER_IS_SYNCADAPTER, "true")
.appendQueryParameter(Calendars.ACCOUNT_NAME, account.name)
.appendQueryParameter(Calendars.ACCOUNT_TYPE, account.type)
.build()
);
}
long dtstart = event.getStartDate().getTime();
long dtend = dtstart + (1000*60*60);
if(event.getEndDate() != null)
dtend = event.getEndDate().getTime();
builder.withValue(Events.CALENDAR_ID, calendar_id);
builder.withValue(Events.DTSTART, dtstart);
builder.withValue(Events.DTEND, dtend);
builder.withValue(Events.TITLE, event.getTitle());
builder.withValue(Events.EVENT_LOCATION, event.getVenue().getName() + "\n" + event.getVenue().getLocation().getCity() + "\n" + event.getVenue().getLocation().getCountry());
if(Integer.valueOf(event.getStatus()) == 1)
builder.withValue(Events.STATUS, Events.STATUS_TENTATIVE);
else
builder.withValue(Events.STATUS, Events.STATUS_CONFIRMED);
builder.withValue(Events._SYNC_ID, Long.valueOf(event.getId()));
return builder.build();
}
private static class SyncEntry {
public Long raw_id = 0L;
}
private static void performSync(Context context, Account account, Bundle extras, String authority, ContentProviderClient provider, SyncResult syncResult)
throws OperationCanceledException {
HashMap<Long, SyncEntry> localEvents = new HashMap<Long, SyncEntry>();
ArrayList<Long> lastfmEvents = new ArrayList<Long>();
mContentResolver = context.getContentResolver();
//If our app has requested a full sync, we're going to delete all our local contacts and start over
boolean is_full_sync = PreferenceManager.getDefaultSharedPreferences(LastFMApplication.getInstance()).getBoolean("do_full_sync", false);
//If our schema is out-of-date, do a fresh sync
if(PreferenceManager.getDefaultSharedPreferences(LastFMApplication.getInstance()).getInt("sync_schema", 0) < syncSchema)
is_full_sync = true;
long calendar_id = getCalendar(account);
if(calendar_id == -1) {
Log.e("CalendarSyncAdapter", "Unable to create Last.fm event calendar");
return;
}
Log.i("CalendarSyncAdapter", "Last.fm events calendar: " + calendar_id);
// Load the local Last.fm events
Uri eventsUri = Events.CONTENT_URI.buildUpon().appendQueryParameter(Events.CALENDAR_ID, String.valueOf(calendar_id)).build();
Cursor c1 = mContentResolver.query(eventsUri, new String[] { Events._ID, Events._SYNC_ID }, null, null, null);
while (c1 != null && c1.moveToNext()) {
if(is_full_sync) {
deleteEvent(context, account, c1.getLong(0));
} else {
SyncEntry entry = new SyncEntry();
entry.raw_id = c1.getLong(0);
localEvents.put(c1.getLong(1), entry);
}
}
c1.close();
Editor editor = PreferenceManager.getDefaultSharedPreferences(LastFMApplication.getInstance()).edit();
editor.remove("do_full_sync");
editor.putInt("sync_schema", syncSchema);
editor.commit();
LastFmServer server = AndroidLastFmServerFactory.getServer();
try {
Event[] events = server.getUserEvents(account.name);
ArrayList<ContentProviderOperation> operationList = new ArrayList<ContentProviderOperation>();
for (Event event : events) {
lastfmEvents.add(Long.valueOf(event.getId()));
if (localEvents.containsKey(Long.valueOf(event.getId()))) {
SyncEntry entry = localEvents.get(Long.valueOf(event.getId()));
operationList.add(updateEvent(calendar_id, account, event, entry.raw_id));
} else {
operationList.add(updateEvent(calendar_id, account, event, -1));
}
if(operationList.size() >= 50) {
try {
mContentResolver.applyBatch(CalendarContract.AUTHORITY, operationList);
} catch (Exception e) {
e.printStackTrace();
}
operationList.clear();
}
}
events = server.getPastUserEvents(account.name);
for (Event event : events) {
lastfmEvents.add(Long.valueOf(event.getId()));
if (localEvents.containsKey(Long.valueOf(event.getId()))) {
SyncEntry entry = localEvents.get(Long.valueOf(event.getId()));
operationList.add(updateEvent(calendar_id, account, event, entry.raw_id));
} else {
operationList.add(updateEvent(calendar_id, account, event, -1));
}
if(operationList.size() >= 50) {
try {
mContentResolver.applyBatch(CalendarContract.AUTHORITY, operationList);
} catch (Exception e) {
e.printStackTrace();
}
operationList.clear();
}
}
if(operationList.size() > 0) {
try {
mContentResolver.applyBatch(CalendarContract.AUTHORITY, operationList);
} catch (Exception e) {
e.printStackTrace();
}
}
} catch (IOException e1) {
// TODO Auto-generated catch block
e1.printStackTrace();
} catch (WSError e1) {
// TODO Auto-generated catch block
e1.printStackTrace();
}
Iterator<Long> i = localEvents.keySet().iterator();
while(i.hasNext()) {
Long event = i.next();
if(!lastfmEvents.contains(event))
deleteEvent(context, account, localEvents.get(event).raw_id);
}
}
}