/*
* PinDroid - http://code.google.com/p/PinDroid/
*
* Copyright (C) 2010 Matt Schmidt
*
* PinDroid is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published
* by the Free Software Foundation; either version 3 of the License,
* or (at your option) any later version.
*
* PinDroid is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with PinDroid; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
* USA
*/
package com.pindroid.syncadapter;
import java.io.IOException;
import java.text.ParseException;
import java.util.ArrayList;
import java.util.List;
import org.apache.http.auth.AuthenticationException;
import android.accounts.Account;
import android.accounts.AccountManager;
import android.content.AbstractThreadedSyncAdapter;
import android.content.ContentProviderClient;
import android.content.ContentResolver;
import android.content.Context;
import android.content.Intent;
import android.content.SyncResult;
import android.os.Bundle;
import android.support.v4.content.LocalBroadcastManager;
import android.text.TextUtils;
import android.util.Log;
import com.pindroid.Constants;
import com.pindroid.client.PinboardApi;
import com.pindroid.client.PinboardException;
import com.pindroid.client.TooManyRequestsException;
import com.pindroid.client.Update;
import com.pindroid.platform.BookmarkManager;
import com.pindroid.platform.NoteManager;
import com.pindroid.platform.TagManager;
import com.pindroid.providers.BookmarkContent.Bookmark;
import com.pindroid.providers.NoteContent.Note;
import com.pindroid.providers.TagContent.Tag;
/**
* SyncAdapter implementation for syncing bookmarks.
*/
public class BookmarkSyncAdapter extends AbstractThreadedSyncAdapter {
public static final String SYNC_FINISHED_ACTION = "sync_finished";
private static final String TAG = "BookmarkSyncAdapter";
private final Context mContext;
private Account mAccount;
private final AccountManager mAccountManager;
public BookmarkSyncAdapter(Context context, boolean autoInitialize) {
super(context, autoInitialize);
mContext = context;
mAccountManager = AccountManager.get(context);
}
@Override
public void onPerformSync(Account account, Bundle extras, String authority, ContentProviderClient provider, SyncResult syncResult) {
boolean upload = extras.containsKey(ContentResolver.SYNC_EXTRAS_UPLOAD);
boolean manual = extras.containsKey(ContentResolver.SYNC_EXTRAS_IGNORE_BACKOFF) && extras.containsKey(ContentResolver.SYNC_EXTRAS_IGNORE_SETTINGS);
try {
if(upload){
Log.d(TAG, "Beginning Upload Sync");
DeleteBookmarks(account, syncResult);
UploadBookmarks(account, syncResult);
} else {
if(manual)
Log.d(TAG, "Beginning Manual Download Sync");
else Log.d(TAG, "Beginning Download Sync");
DeleteBookmarks(account, syncResult);
UploadBookmarks(account, syncResult);
InsertBookmarks(account, syncResult);
}
checkSecretToken(account);
} catch (final ParseException e) {
syncResult.stats.numParseExceptions++;
Log.e(TAG, "ParseException", e);
} catch (final AuthenticationException e) {
syncResult.stats.numAuthExceptions++;
Log.e(TAG, "AuthException", e);
} catch (final IOException e) {
syncResult.stats.numIoExceptions++;
Log.e(TAG, "IOException", e);
} catch (final TooManyRequestsException e) {
syncResult.delayUntil = e.getBackoff();
Log.d(TAG, "Too Many Requests. Backing off for " + e.getBackoff() + " seconds.");
} catch (PinboardException e) {
syncResult.stats.numSkippedEntries++;
Log.e(TAG, "PinboardException", e);
} finally {
Log.d(TAG, "Finished Sync");
LocalBroadcastManager.getInstance(getContext()).sendBroadcast(new Intent(SYNC_FINISHED_ACTION));
}
}
private void InsertBookmarks(Account account, SyncResult syncResult)
throws AuthenticationException, IOException, TooManyRequestsException, ParseException, PinboardException{
long lastUpdate = getServerSyncMarker(account);
final String username = account.name;
mAccount = account;
final Update update = PinboardApi.lastUpdate(account, mContext);
if(update.getLastUpdate() > lastUpdate) {
Log.d(TAG, "In Bookmark Load");
final ArrayList<String> accounts = new ArrayList<String>();
accounts.add(account.name);
final ArrayList<Bookmark> addBookmarkList = getBookmarkList();
BookmarkManager.TruncateBookmarks(accounts, mContext, false);
if(!addBookmarkList.isEmpty()){
List<Bookmark> unsyncedBookmarks = BookmarkManager.GetLocalBookmarks(username, mContext);
addBookmarkList.removeAll(unsyncedBookmarks);
BookmarkManager.BulkInsert(addBookmarkList, username, mContext);
}
final ArrayList<Tag> tagList = PinboardApi.getTags(account, mContext);
TagManager.TruncateTags(username, mContext);
if(!tagList.isEmpty()){
TagManager.BulkInsert(tagList, username, mContext);
}
SyncNotes();
setServerSyncMarker(account, update.getLastUpdate());
syncResult.stats.numEntries += addBookmarkList.size();
} else {
Log.d(TAG, "No update needed. Last update time before last sync.");
}
}
private void SyncNotes() throws AuthenticationException, IOException, TooManyRequestsException, PinboardException{
final ArrayList<Note> noteList = PinboardApi.getNoteList(mAccount, mContext);
NoteManager.TruncateNotes(mAccount.name, mContext);
for(Note n : noteList){
//NoteManager.UpsertNote(n, mAccount.name, mContext);
Note t = PinboardApi.getNote(n.getPid(), mAccount, mContext);
n.setText(t.getText());
}
if(!noteList.isEmpty()){
NoteManager.BulkInsert(noteList, mAccount.name, mContext);
}
}
private void UploadBookmarks(Account account, SyncResult syncResult)
throws AuthenticationException, IOException, TooManyRequestsException, ParseException{
final ArrayList<Bookmark> bookmarks = BookmarkManager.GetLocalBookmarks(account.name, mContext);
for(Bookmark b : bookmarks)
{
try{
PinboardApi.addBookmark(b, account, mContext);
Log.d(TAG, "Bookmark edited: " + (b.getHash() == null ? "" : b.getHash()));
b.setSynced(1);
BookmarkManager.SetSynced(b, 1, account.name, mContext);
syncResult.stats.numEntries++;
}
catch(PinboardException e){
Log.d(TAG, "Error editing bookmark: " + (b.getHash() == null ? "" : b.getHash()));
b.setSynced(-1);
BookmarkManager.SetSynced(b, -1, account.name, mContext);
}
}
}
private void DeleteBookmarks(Account account, SyncResult syncResult)
throws AuthenticationException, IOException, TooManyRequestsException, ParseException, PinboardException{
final ArrayList<Bookmark> bookmarks = BookmarkManager.GetDeletedBookmarks(account.name, mContext);
for(Bookmark b : bookmarks)
{
PinboardApi.deleteBookmark(b, account, mContext);
Log.d(TAG, "Bookmark deleted: " + (b.getHash() == null ? "" : b.getHash()));
BookmarkManager.DeleteBookmark(b, mContext);
}
syncResult.stats.numEntries += bookmarks.size();
}
private ArrayList<Bookmark> getBookmarkList()
throws AuthenticationException, IOException, TooManyRequestsException, PinboardException {
int pageSize = Constants.BOOKMARK_PAGE_SIZE;
ArrayList<Bookmark> results = new ArrayList<Bookmark>();
int page = 0;
boolean morePages = true;
do{
morePages = results.addAll(PinboardApi.getAllBookmarks(null, page++ * pageSize, pageSize, mAccount, mContext));
} while(morePages);
return results;
}
private void checkSecretToken(Account account) throws AuthenticationException, IOException, TooManyRequestsException, ParseException, PinboardException{
String token = mAccountManager.getUserData(account, Constants.PREFS_SECRET_TOKEN);
if(token == null){
token = PinboardApi.getSecretToken(account, mContext);
mAccountManager.setUserData(account, Constants.PREFS_SECRET_TOKEN, token);
}
}
/**
* This helper function fetches the last known high-water-mark
* we received from the server - or 0 if we've never synced.
* @param account the account we're syncing
* @return the change high-water-mark
*/
private long getServerSyncMarker(Account account) {
String markerString = mAccountManager.getUserData(account, Constants.SYNC_MARKER_KEY);
if (!TextUtils.isEmpty(markerString)) {
return Long.parseLong(markerString);
}
return 0;
}
/**
* Save off the high-water-mark we receive back from the server.
* @param account The account we're syncing
* @param marker The high-water-mark we want to save.
*/
private void setServerSyncMarker(Account account, long marker) {
mAccountManager.setUserData(account, Constants.SYNC_MARKER_KEY, Long.toString(marker));
}
}