package com.nuscomputing.ivle;
import java.io.IOException;
import java.net.MalformedURLException;
import java.util.HashSet;
import java.util.Set;
import org.joda.time.Instant;
import org.joda.time.Interval;
import android.accounts.Account;
import android.accounts.AccountManager;
import android.accounts.AuthenticatorException;
import android.accounts.OperationCanceledException;
import android.content.AbstractThreadedSyncAdapter;
import android.content.ContentProviderClient;
import android.content.ContentUris;
import android.content.ContentValues;
import android.content.Context;
import android.content.SharedPreferences;
import android.content.SharedPreferences.Editor;
import android.content.SyncResult;
import android.database.Cursor;
import android.database.sqlite.SQLiteException;
import android.net.Uri;
import android.os.Bundle;
import android.os.RemoteException;
import android.preference.PreferenceManager;
import android.text.Html;
import android.util.Log;
import com.nuscomputing.ivle.providers.AnnouncementsContract;
import com.nuscomputing.ivle.providers.GradebookItemsContract;
import com.nuscomputing.ivle.providers.GradebooksContract;
import com.nuscomputing.ivle.providers.IVLEContract;
import com.nuscomputing.ivle.providers.ModulesContract;
import com.nuscomputing.ivle.providers.TimetableSlotsContract;
import com.nuscomputing.ivle.providers.UsersContract;
import com.nuscomputing.ivle.providers.WebcastFilesContract;
import com.nuscomputing.ivle.providers.WebcastItemGroupsContract;
import com.nuscomputing.ivle.providers.WebcastsContract;
import com.nuscomputing.ivle.providers.WeblinksContract;
import com.nuscomputing.ivle.providers.WorkbinFilesContract;
import com.nuscomputing.ivle.providers.WorkbinFoldersContract;
import com.nuscomputing.ivle.providers.WorkbinsContract;
import com.nuscomputing.ivlelapi.Announcement;
import com.nuscomputing.ivlelapi.FailedLoginException;
import com.nuscomputing.ivlelapi.Gradebook;
import com.nuscomputing.ivlelapi.IVLE;
import com.nuscomputing.ivlelapi.IVLEObject;
import com.nuscomputing.ivlelapi.JSONParserException;
import com.nuscomputing.ivlelapi.Module;
import com.nuscomputing.ivlelapi.NetworkErrorException;
import com.nuscomputing.ivlelapi.Timetable;
import com.nuscomputing.ivlelapi.User;
import com.nuscomputing.ivlelapi.Webcast;
import com.nuscomputing.ivlelapi.Weblink;
import com.nuscomputing.ivlelapi.Workbin;
/**
* The actual sync adapter implementation for announcements.
* @author yjwong
*/
public class IVLESyncAdapter extends AbstractThreadedSyncAdapter {
// {{{ properties
/** TAG for logging */
public static final String TAG = "IVLESyncAdapter";
/** The key for last sync time in SharedPreferences */
private static final String KEY_LAST_SYNC_TIME = "last_sync_time";
/** The account manager */
private static AccountManager mAccountManager = null;
/** The context of the sync service */
private Context mContext;
/** Account to be synced */
private Account mAccount;
/** Content provider client */
private ContentProviderClient mProvider;
/** Sync result */
private SyncResult mSyncResult;
/** The last sync time */
private Instant mLastSyncTime;
// }}}
// {{{ methods
public IVLESyncAdapter(Context context, boolean autoInitialize) {
super(context, autoInitialize);
Log.v(TAG, "IVLESyncAdapter started");
mContext = context;
mAccountManager = AccountManager.get(mContext);
}
/**
* Method: getLastSyncTimeKey
* <p>
* Gets the SharedPreferences key that denotes the last time this account
* was synced.
*/
private String getLastSyncTimeKey() {
return KEY_LAST_SYNC_TIME.concat("_").concat(mAccount.name);
}
/**
* Method: onPerformSync
* <p>
* Performs the actual synchronisation operation.
* <p>
* This is a giant method, and each time we sync, we remove all existing
* content. Not the best of choice on a mobile platform, but this is to
* maintain data consistency at its best possible state.
*/
@Override
public void onPerformSync(Account account, Bundle extras, String authority,
ContentProviderClient provider, SyncResult syncResult) {
// Set class members so that helper functions can use them.
this.mAccount = account;
this.mProvider = provider;
this.mSyncResult = syncResult;
// Get the duration since the last sync.
SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(mContext);
long lastSyncTimeLong = prefs.getLong(getLastSyncTimeKey(), -1);
if (lastSyncTimeLong != -1) {
mLastSyncTime = new Instant(lastSyncTimeLong);
} else {
mLastSyncTime = null;
}
// Tell interested listeners that sync has started.
this.setSyncInProgress(account, true);
IVLESyncService.broadcastSyncStarted(mContext, account);
// Obtain an IVLE object.
Log.d(TAG, "Performing sync of IVLE data, seconds since last sync = " + getSecondsSinceLastSync());
String authToken = null;
// Set the last sync time.
Editor prefsEditor = prefs.edit();
prefsEditor.putLong(getLastSyncTimeKey(), new Instant().getMillis());
prefsEditor.commit();
try {
// Obtain the authentication, and get the list of modules.
authToken = mAccountManager.blockingGetAuthToken(account, Constants.AUTHTOKEN_TYPE, true);
IVLE ivle = new IVLE(Constants.API_KEY, authToken);
Module[] modules = ivle.getModules();
this.purgeDeletedModulesFromLocal(modules);
Log.v(TAG, modules.length + " modules found: ");
// Put those modules into the provider.
for (Module module : modules) {
// Insert the creator into the user's table.
Integer moduleCreatorId = null;
if (module.creator.ID != null) {
Uri moduleCreatorUri = this.insertUserIfNotExists(module.creator);
moduleCreatorId = Integer.parseInt(moduleCreatorUri.getLastPathSegment());
}
// Insert modules.
long moduleId = this.insertModule(module, moduleCreatorId);
// Fetch announcements.
Log.v(TAG, "Fetching announcements");
Announcement[] announcements = module.getAnnouncements();
this.purgeDeletedItemsFromLocal(AnnouncementsContract.class, announcements, moduleId);
for (Announcement announcement : announcements) {
// Insert the creator into the user's table.
Integer announcementCreatorId = null;
if (announcement.creator.ID != null) {
Uri announcementCreatorUri = this.insertUserIfNotExists(announcement.creator);
announcementCreatorId = Integer.parseInt(announcementCreatorUri.getLastPathSegment());
}
// Insert announcements.
this.insertAnnouncement(announcement, moduleId, announcementCreatorId);
}
// Fetch gradebooks.
Log.v(TAG, "Fetching gradebooks");
Gradebook[] gradebooks = module.getGradebooks();
this.purgeDeletedItemsFromLocal(GradebooksContract.class, gradebooks, moduleId);
for (Gradebook gradebook : gradebooks) {
// Insert gradebooks.
long gradebookId = this.insertGradebook(gradebook, moduleId);
// Fetch gradebook items.
Log.v(TAG, "Fetching gradebook items");
Gradebook.Item[] gradebookItems = gradebook.getItems();
for (Gradebook.Item gradebookItem : gradebookItems) {
this.insertGradebookItem(gradebookItem, moduleId, gradebookId);
}
}
// Fetch webcasts.
Log.v(TAG, "Fetching webcasts");
Webcast[] webcasts = module.getWebcasts();
this.purgeDeletedItemsFromLocal(WebcastsContract.class, webcasts, moduleId);
for (Webcast webcast : webcasts) {
// Insert the creator into the user's table.
Integer webcastCreatorId = null;
if (webcast.creator.ID != null) {
Uri webcastCreatorUri = this.insertUserIfNotExists(webcast.creator);
webcastCreatorId = Integer.parseInt(webcastCreatorUri.getLastPathSegment());
}
// Insert webcasts.
long webcastId = this.insertWebcast(webcast, moduleId, webcastCreatorId);
// Fetch webcast item groups.
Log.v(TAG, "Fetching webcast item groups");
Webcast.ItemGroup[] webcastItemGroups = webcast.getItemGroups();
for (Webcast.ItemGroup webcastItemGroup : webcastItemGroups) {
long webcastItemGroupId = this.insertWebcastItemGroup(webcastItemGroup, moduleId, webcastId);
// Fetch webcast files.
Log.v(TAG, "Fetching webcast files");
Webcast.File[] webcastFiles = webcastItemGroup.getFiles();
for (Webcast.File webcastFile : webcastFiles) {
// Insert the creator of the file into the user's table.
Integer webcastFileCreatorId = null;
if (webcastFile.creator.ID != null) {
Uri webcastFileCreatorUri = this.insertUserIfNotExists(webcastFile.creator);
webcastFileCreatorId = Integer.parseInt(webcastFileCreatorUri.getLastPathSegment());
}
// Insert webcast files.
this.insertWebcastFile(webcastFile, moduleId, webcastItemGroupId, webcastFileCreatorId);
}
}
}
// Fetch weblinks.
Log.v(TAG, "Fetching weblinks");
Weblink[] weblinks = module.getWeblinks();
this.purgeDeletedItemsFromLocal(WeblinksContract.class, weblinks, moduleId);
for (Weblink weblink : weblinks) {
// Insert weblinks.
this.insertWeblink(weblink, moduleId);
}
// Fetch workbins.
Log.v(TAG, "Fetching workbins");
Workbin[] workbins = module.getWorkbins();
this.purgeDeletedItemsFromLocal(WorkbinsContract.class, workbins, moduleId);
for (Workbin workbin : workbins) {
long workbinId = this.insertWorkbin(workbin, moduleId);
// Fetch workbin folders.
Log.v(TAG, "Fetching workbin folders");
Workbin.Folder[] workbinFolders = workbin.getFolders();
for (Workbin.Folder workbinFolder : workbinFolders) {
// Insert workbin folders.
this.insertWorkbinFolder(workbinFolder, moduleId, workbinId, null);
}
}
}
// Fetch the user's timetable.
Log.v(TAG, "Fetching timetable slots");
mProvider.delete(
TimetableSlotsContract.CONTENT_URI,
DatabaseHelper.TIMETABLE_SLOTS_TABLE_NAME.concat(".").concat(TimetableSlotsContract.ACCOUNT).concat(" = ?"),
new String[] { mAccount.name }
);
Timetable timetable = ivle.getTimetableStudent("2012/2013", 1);
for (Timetable.Slot timetableSlot : timetable.slots) {
this.insertTimetableSlot(timetableSlot);
}
// Verbose: print sync statistics.
Log.d(TAG, "Sync is complete");
Log.v(TAG, "Sync statistics:");
Log.v(TAG, "Deleted " + mSyncResult.stats.numDeletes + " record(s)");
Log.v(TAG, "Inserted " + mSyncResult.stats.numInserts + " record(s)");
Log.v(TAG, "Updated " + mSyncResult.stats.numUpdates + " record(s)");
// Send a broadcast.
IVLESyncService.broadcastSyncSuccess(mContext, account);
} catch (Exception e) {
// Handle any sync exceptions.
this.handleSyncExceptions(authToken, e);
} finally {
this.setSyncInProgress(account, false);
IVLESyncService.broadcastSyncComplete(mContext, account);
}
}
@Override
public void onSyncCanceled(Thread thread) {
this.setSyncInProgress(mAccount, false);
IVLESyncService.broadcastSyncCanceled(mContext, mAccount);
}
/**
* Method: getSecondsSinceLastSync
* <p>
* Returns the number of seconds since the last sync was completed.
* <p>
* This method should be called for every invocation of an IVLE API call
* since a call can possibly take tens of seconds to complete. If not, we
* might miss out some important data that has been changed.
*
* @return The duration since the last sync.
*/
private int getSecondsSinceLastSync() {
// The default is 0, i.e. fetch all data.
long changeDuration = 0;
Instant currentTime = new Instant();
if (mLastSyncTime != null) {
Interval changeInterval = new Interval(mLastSyncTime, currentTime);
changeDuration = changeInterval.toDuration().getStandardSeconds();
}
return Long.valueOf(changeDuration).intValue();
}
/**
* Method: isSyncInProgress
* <p>
* Returns true if a sync is in progress, false otherwise.
*/
public static boolean isSyncInProgress(Context context, Account account) {
SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context);
return prefs.getBoolean(IVLESyncService.KEY_SYNC_IN_PROGRESS + "_" + account.name, false);
}
/**
* Method: setSyncInProgress
* <p>
* Sets whether the sync is in progress or not.
*/
private void setSyncInProgress(Account account, boolean inProgress) {
// Abuse of shared preferences to set sync status ):
SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(mContext.getApplicationContext());
SharedPreferences.Editor prefsEditor = prefs.edit();
prefsEditor.putBoolean(IVLESyncService.KEY_SYNC_IN_PROGRESS + "_" + account.name, inProgress);
prefsEditor.commit();
}
/**
* Method: handleSyncExceptions
* <p>
* Handles synchronization exceptions.
*/
private void handleSyncExceptions(String authToken, Exception e) throws
IllegalStateException {
if (e instanceof OperationCanceledException) {
Log.d(TAG, "Sync canceled");
IVLESyncService.broadcastSyncCanceled(mContext, mAccount);
} else if (e instanceof AuthenticatorException || e instanceof FailedLoginException) {
Log.d(TAG, "AuthenticatorException or FailedLoginException, refreshing authToken");
mSyncResult.stats.numAuthExceptions++;
mAccountManager.invalidateAuthToken(Constants.AUTHTOKEN_TYPE, authToken);
return;
} else if (e instanceof RemoteException) {
Log.d(TAG, "RemoteException");
mSyncResult.databaseError = true;
e.printStackTrace();
} else if (e instanceof SQLiteException) {
Log.d(TAG, "SQLiteException");
mSyncResult.databaseError = true;
e.printStackTrace();
} else if (e instanceof IOException || e instanceof NetworkErrorException) {
Log.d(TAG, "IOException or NetworkErrorException");
mSyncResult.stats.numIoExceptions++;
e.printStackTrace();
} else if (e instanceof JSONParserException) {
Log.d(TAG, "JSONParserException");
mSyncResult.stats.numParseExceptions++;
e.printStackTrace();
} else {
// Tell the system that we have a sync failure.
Log.d(TAG, "Unknown exception encountered");
mSyncResult.stats.numParseExceptions++;
e.printStackTrace();
}
// The sync failed, so broadcast our failure.
this.setSyncInProgress(mAccount, false);
IVLESyncService.broadcastSyncFailed(mContext, mAccount);
}
/**
* Method: findDeletedModules
* <p>
* Find modules deleted between the current sync and the time of the
* last sync.
* <p>
* Note: we cannot use the generic implementation because things work
* too differently for modules.
*
* @param modules An array containing modules from the current sync
*/
private Set<String> findDeletedModules(Module[] modules) throws
RemoteException {
// Get the column names.
Uri fieldContentUri = ModulesContract.CONTENT_URI;
String fieldTable = ModulesContract.TABLE;
String fieldIvleId = ModulesContract.IVLE_ID;
String fieldAccount = ModulesContract.ACCOUNT;
// Get the set of old items.
Cursor c = mProvider.query(
fieldContentUri,
new String[] { fieldIvleId },
fieldTable.concat(".").concat(fieldAccount).concat(" = ?"),
new String [] { mAccount.name },
null);
Set<String> oldSet = new HashSet<String>();
c.moveToFirst();
while (!c.isAfterLast()) {
oldSet.add(c.getString(c.getColumnIndex(fieldIvleId)));
c.moveToNext();
}
// Get the set of new items.
Set<String> newSet = new HashSet<String>();
for (Module module : modules) {
newSet.add(module.ID);
}
oldSet.removeAll(newSet);
return oldSet;
}
/**
* Method: purgeDeletedModulesFromLocal
* <p>
* Removes deleted modules from the local cache.
*/
private void purgeDeletedModulesFromLocal(Module[] modules) throws
RemoteException {
Set<String> removeSet = this.findDeletedModules(modules);
Uri fieldContentUri = ModulesContract.CONTENT_URI;
String fieldIvleId = ModulesContract.IVLE_ID;
String fieldAccount = ModulesContract.ACCOUNT;
for (String toRemove : removeSet) {
mSyncResult.stats.numDeletes++;
Log.v(TAG, "purging non-existent module with ID = " + toRemove);
mProvider.delete(
fieldContentUri,
fieldIvleId.concat(" = ?").concat(" AND ")
.concat(fieldAccount).concat(" = ?"),
new String[] { toRemove, mAccount.name }
);
}
}
/**
* Method: findDeletedItemsByType
* <p>
* Generic method to find IVLE items deleted between the current sync
* and the time of the last sync.
*
* @param contract The contract for the item
* @param objects An array containing items from the current sync
* @param moduleId The ID of the module the items belong to
*/
private <T extends IVLEObject> Set<String> findDeletedItemsByType(
IVLEContract contract, T[] objects,
long moduleId) throws RemoteException {
// Get the column names.
Uri fieldContentUri = contract.getContentUri();
String fieldTable = contract.getTableName();
String fieldIvleId = contract.getColumnNameIvleId();
String fieldModuleId = contract.getColumnNameModuleId();
String fieldAccount = contract.getColumnNameAccount();
// Get the set of old items.
Cursor c = mProvider.query(
fieldContentUri,
new String[] { fieldIvleId },
fieldTable.concat(".").concat(fieldAccount).concat(" = ? AND ") +
fieldTable.concat(".").concat(fieldModuleId).concat(" = ?"),
new String[] { mAccount.name, Long.toString(moduleId) },
null);
Set<String> oldSet = new HashSet<String>();
c.moveToFirst();
while (!c.isAfterLast()) {
oldSet.add(c.getString(c.getColumnIndex(fieldIvleId)));
c.moveToNext();
}
// Get the new set of items.
Set<String> newSet = new HashSet<String>();
for (T object : objects) {
newSet.add(object.ID);
}
oldSet.removeAll(newSet);
return oldSet;
}
/**
* Method: purgeDeletedItemsFromLocal
* <p>
* Removes deleted items from the local cache.
*/
private <T extends IVLEObject> void purgeDeletedItemsFromLocal(
Class<? extends IVLEContract> contractClass, T[] objects,
long moduleId) throws RemoteException {
// Create an instance of the contract.
try {
IVLEContract contract = contractClass.newInstance();
// Find out what has been deleted.
Set<String> removeSet = this.findDeletedItemsByType(contract, objects, moduleId);
Uri fieldContentUri = contract.getContentUri();
String fieldIvleId = contract.getColumnNameIvleId();
String fieldModuleId = contract.getColumnNameModuleId();
String fieldAccount = contract.getColumnNameAccount();
for (String toRemove : removeSet) {
mSyncResult.stats.numDeletes++;
Log.v(TAG, "purging non-existent item of type " + contract.getClass().getName() + " with ID = " + toRemove);
mProvider.delete(
fieldContentUri,
fieldIvleId.concat(" = ?").concat(" AND ")
.concat(fieldAccount).concat(" = ?").concat(" AND ")
.concat(fieldModuleId).concat(" = ?"),
new String[] { toRemove, mAccount.name, Long.toString(moduleId) }
);
}
} catch (InstantiationException e) {
Log.e(TAG, "InstantiationException encountered purging deleted items of type " + contractClass.getName());
} catch (IllegalAccessException e) {
Log.e(TAG, "IllegalAccessException encountered purging deleted items of type " + contractClass.getName());
} catch (IllegalArgumentException e) {
// Do nothing.
}
}
/**
* Method: itemExists
* <p>
* Determines if an IVLE item exists. If it exists, this method returns
* the item ID. Otherwise, it returns -1.
*
* @return
* @throws RemoteException
*/
private long itemExists(Class<? extends IVLEContract> contractClass,
String ivleId) throws RemoteException {
// Check if the item exists.
try {
IVLEContract contract = contractClass.newInstance();
Cursor c = mProvider.query(
contract.getContentUri(),
new String[] { contract.getColumnNameId() },
contract.getTableName().concat(".").concat(contract.getColumnNameIvleId()).concat(" = ? AND ") +
contract.getTableName().concat(".").concat(contract.getColumnNameAccount()).concat(" = ?"),
new String[] { ivleId, mAccount.name }, null);
if (c.getCount() > 0) {
c.moveToFirst();
return c.getLong(c.getColumnIndex(contract.getColumnNameId()));
} else {
return -1;
}
} catch (InstantiationException e) {
Log.e(TAG, "InstantiationException encountered checking if item of type " + contractClass.getName() + " exists");
return -1;
} catch (IllegalAccessException e) {
Log.e(TAG, "IllegalAccessException encountered checking if item of type " + contractClass.getName() + " exists");
return -1;
}
}
/**
* Method: insertAnnouncement
* <p>
* Inserts an announcement into the announcement table.
*/
private long insertAnnouncement(Announcement announcement, long moduleId,
int creatorId) throws RemoteException {
// Prepare the content values.
ContentValues v = new ContentValues();
v.put(AnnouncementsContract.IVLE_ID, announcement.ID);
v.put(AnnouncementsContract.MODULE_ID, moduleId);
v.put(AnnouncementsContract.ACCOUNT, mAccount.name);
v.put(AnnouncementsContract.CREATOR_ID, creatorId);
v.put(AnnouncementsContract.TITLE, announcement.title);
v.put(AnnouncementsContract.DESCRIPTION, announcement.description);
v.put(AnnouncementsContract.CREATED_DATE, announcement.createdDate.toString());
v.put(AnnouncementsContract.EXPIRY_DATE, announcement.expiryDate.toString());
v.put(AnnouncementsContract.URL, announcement.url);
v.put(AnnouncementsContract.IS_READ, announcement.isRead ? 1 : 0);
// Cache column for description.
String description = Html.fromHtml(announcement.description).toString();
description = description.replace('\r', ' ').replace('\n', ' ').trim();
v.put(AnnouncementsContract._DESCRIPTION_NOHTML, description);
// Insert or update announcements.
long id = this.itemExists(AnnouncementsContract.class, announcement.ID);
if (id > -1) {
Log.v(TAG, "insertAnnouncement: title = " + announcement.title + ", ID = " + announcement.ID + " (update)");
mSyncResult.stats.numUpdates++;
mProvider.update(
AnnouncementsContract.CONTENT_URI, v,
AnnouncementsContract.IVLE_ID.concat(" = ? AND ") +
AnnouncementsContract.ACCOUNT.concat(" = ?"),
new String[] { announcement.ID, mAccount.name }
);
return id;
} else {
Log.v(TAG, "insertAnnouncement: title = " + announcement.title + ", ID = " + announcement.ID);
mSyncResult.stats.numInserts++;
Uri uri = mProvider.insert(AnnouncementsContract.CONTENT_URI, v);
return ContentUris.parseId(uri);
}
}
/**
* Method: insertGradebook
* <p>
* Inserts a gradebook into the gradebook table.
*/
private long insertGradebook(Gradebook gradebook, long moduleId) throws
RemoteException {
// Prepare the content values.
ContentValues v = new ContentValues();
v.put(GradebooksContract.IVLE_ID, gradebook.ID);
v.put(GradebooksContract.MODULE_ID, moduleId);
v.put(GradebooksContract.ACCOUNT, mAccount.name);
v.put(GradebooksContract.CATEGORY_TITLE, gradebook.categoryTitle);
// Insert or update gradebooks.
long id = this.itemExists(GradebooksContract.class, gradebook.ID);
if (id > -1) {
Log.v(TAG, "insertGradebook: categoryTitle = " + gradebook.categoryTitle + ", ID = " + gradebook.ID + " (update)");
mSyncResult.stats.numUpdates++;
mProvider.update(
GradebooksContract.CONTENT_URI, v,
GradebooksContract.IVLE_ID.concat(" = ? AND ") +
GradebooksContract.ACCOUNT.concat(" = ?"),
new String[] { gradebook.ID, mAccount.name }
);
return id;
} else {
Log.v(TAG, "insertGradebook: categoryTitle = " + gradebook.categoryTitle + ", ID = " + gradebook.ID);
mSyncResult.stats.numInserts++;
Uri uri = mProvider.insert(GradebooksContract.CONTENT_URI, v);
return ContentUris.parseId(uri);
}
}
/**
* Method: insertGradebookItem
* <p>
* Inserts a gradebook item into the gradebook item table.
*/
private long insertGradebookItem(Gradebook.Item item, long moduleId,
long gradebookId) throws RemoteException {
// Prepare the content values.
ContentValues v = new ContentValues();
v.put(GradebookItemsContract.IVLE_ID, item.ID);
v.put(GradebookItemsContract.MODULE_ID, moduleId);
v.put(GradebookItemsContract.GRADEBOOK_ID, gradebookId);
v.put(GradebookItemsContract.ACCOUNT, mAccount.name);
v.put(GradebookItemsContract.AVERAGE_MEDIAN_MARKS, item.averageMedianMarks);
v.put(GradebookItemsContract.DATE_ENTERED, item.dateEntered);
v.put(GradebookItemsContract.HIGHEST_LOWEST_MARKS, item.highestLowestMarks);
v.put(GradebookItemsContract.ITEM_DESCRIPTION, item.itemDescription);
v.put(GradebookItemsContract.ITEM_NAME, item.itemName);
v.put(GradebookItemsContract.MARKS_OBTAINED, item.marksObtained);
v.put(GradebookItemsContract.MAX_MARKS, item.maxMarks);
v.put(GradebookItemsContract.PERCENTILE, item.percentile);
v.put(GradebookItemsContract.REMARK, item.remark);
// Insert or update gradebook items.
long id = this.itemExists(GradebookItemsContract.class, item.ID);
if (id > -1) {
Log.v(TAG, "insertGradebookItem: itemName = " + item.itemName + ", ID = " + item.ID + " (update)");
mSyncResult.stats.numUpdates++;
mProvider.update(
GradebookItemsContract.CONTENT_URI, v,
GradebookItemsContract.IVLE_ID.concat(" = ? AND ") +
GradebookItemsContract.ACCOUNT.concat(" = ? AND ") +
GradebookItemsContract.GRADEBOOK_ID.concat(" = ?"),
new String[] { item.ID, mAccount.name, Long.toString(gradebookId) }
);
return id;
} else {
Log.v(TAG, "insertGradebookItem: itemName = " + item.itemName + ", ID = " + item.ID);
mSyncResult.stats.numInserts++;
Uri uri = mProvider.insert(GradebookItemsContract.CONTENT_URI, v);
return ContentUris.parseId(uri);
}
}
/**
* Method: insertModule
* <p>
* Inserts a module into the module table.
*/
private long insertModule(Module module, Integer creatorId) throws
RemoteException {
// Prepare the content values.
ContentValues v = new ContentValues();
v.put(ModulesContract.IVLE_ID, module.ID);
v.put(ModulesContract.ACCOUNT, mAccount.name);
v.put(ModulesContract.BADGE, module.badge);
v.put(ModulesContract.BADGE_ANNOUNCEMENT, module.badgeAnnouncement);
v.put(ModulesContract.COURSE_ACAD_YEAR, module.courseAcadYear);
v.put(ModulesContract.COURSE_CLOSE_DATE, module.courseCloseDate.toString());
v.put(ModulesContract.COURSE_CODE, module.courseCode);
v.put(ModulesContract.COURSE_DEPARTMENT, module.courseDepartment);
v.put(ModulesContract.COURSE_LEVEL, module.courseLevel);
v.put(ModulesContract.COURSE_MC, module.courseMC);
v.put(ModulesContract.COURSE_NAME, module.courseName);
v.put(ModulesContract.COURSE_OPEN_DATE, module.courseOpenDate.toString());
v.put(ModulesContract.COURSE_SEMESTER, module.courseSemester);
v.put(ModulesContract.CREATOR_ID, creatorId);
v.put(ModulesContract.HAS_ANNOUNCEMENT_ITEMS, module.hasAnnouncementItems ? 1 : 0);
v.put(ModulesContract.HAS_CLASS_GROUPS_FOR_SIGN_UP, module.hasClassGroupsForSignUp ? 1 : 0);
v.put(ModulesContract.HAS_CLASS_ROSTER_ITEMS, module.hasClassRosterItems ? 1 : 0);
v.put(ModulesContract.HAS_CONSULTATION_ITEMS, module.hasConsultationItems ? 1 : 0);
v.put(ModulesContract.HAS_CONSULTATION_SLOTS_FOR_SIGN_UP, module.hasConsultationSlotsForSignUp ? 1 : 0);
v.put(ModulesContract.HAS_DESCRIPTION_ITEMS, module.hasDescriptionItems ? 1 : 0);
v.put(ModulesContract.HAS_GRADEBOOK_ITEMS, module.hasGradebookItems ? 1 : 0);
v.put(ModulesContract.HAS_GROUPS_ITEMS, module.hasGroupsItems ? 1 : 0);
v.put(ModulesContract.HAS_GUEST_ROSTER_ITEMS, module.hasGuestRosterItems ? 1 : 0);
v.put(ModulesContract.HAS_LECTURER_ITEMS, module.hasLecturerItems ? 1 : 0);
v.put(ModulesContract.HAS_PROJECT_GROUP_ITEMS, module.hasProjectGroupItems ? 1 : 0);
v.put(ModulesContract.HAS_PROJECT_GROUPS_FOR_SIGN_UP, module.hasProjectGroupsForSignUp ? 1 : 0);
v.put(ModulesContract.HAS_READING_ITEMS, module.hasReadingItems ? 1 : 0);
v.put(ModulesContract.HAS_TIMETABLE_ITEMS, module.hasTimetableItems ? 1 : 0);
v.put(ModulesContract.HAS_WEBLINK_ITEMS, module.hasWeblinkItems ? 1 : 0);
v.put(ModulesContract.PERMISSION, module.permission);
// Insert or update gradebook items.
long id = this.itemExists(ModulesContract.class, module.ID);
if (id > -1) {
Log.v(TAG, "insertModule: courseName = " + module.courseName + ", ID = " + module.ID + " (update)");
mSyncResult.stats.numUpdates++;
mProvider.update(
ModulesContract.CONTENT_URI, v,
ModulesContract.IVLE_ID.concat(" = ? AND ") +
ModulesContract.ACCOUNT.concat(" = ?"),
new String[] { module.ID, mAccount.name }
);
return id;
} else {
Log.v(TAG, "insertModule: courseName = " + module.courseName + ", ID = " + module.ID);
mSyncResult.stats.numInserts++;
Uri uri = mProvider.insert(ModulesContract.CONTENT_URI, v);
return ContentUris.parseId(uri);
}
}
/**
* Method: insertTimetableSlot
* <p>
* Inserts a timetable slot into the timetable slot table.
*/
private int insertTimetableSlot(Timetable.Slot slot) throws
RemoteException {
// Prepare the content values.
ContentValues v = new ContentValues();
v.put(TimetableSlotsContract.ACCOUNT, mAccount.name);
v.put(TimetableSlotsContract.ACAD_YEAR, slot.acadYear);
v.put(TimetableSlotsContract.SEMESTER, slot.semester);
v.put(TimetableSlotsContract.START_TIME, slot.startTime);
v.put(TimetableSlotsContract.END_TIME, slot.endTime);
v.put(TimetableSlotsContract.MODULE_CODE, slot.moduleCode);
v.put(TimetableSlotsContract.CLASS_NO, slot.classNo);
v.put(TimetableSlotsContract.LESSON_TYPE, slot.lessonType);
v.put(TimetableSlotsContract.VENUE, slot.venue);
v.put(TimetableSlotsContract.DAY_CODE, slot.dayCode);
v.put(TimetableSlotsContract.DAY_TEXT, slot.dayText);
v.put(TimetableSlotsContract.WEEK_CODE, slot.weekCode);
v.put(TimetableSlotsContract.WEEK_TEXT, slot.weekText);
// Insert timetable slot.
Uri uri = mProvider.insert(TimetableSlotsContract.CONTENT_URI, v);
return Integer.parseInt(uri.getLastPathSegment());
}
/**
* Method: insertWebcast
* <p>
* Inserts a webcast into the webcast table.
*/
private long insertWebcast(Webcast webcast, long moduleId, int creatorId) throws
RemoteException {
// Prepare the content values.
ContentValues v = new ContentValues();
v.put(WebcastsContract.IVLE_ID, webcast.ID);
v.put(WebcastsContract.MODULE_ID, moduleId);
v.put(WebcastsContract.ACCOUNT, mAccount.name);
v.put(WebcastsContract.CREATOR_ID, creatorId);
v.put(WebcastsContract.BADGE_TOOL, webcast.badgeTool);
v.put(WebcastsContract.PUBLISHED, webcast.published);
v.put(WebcastsContract.TITLE, webcast.title);
// Insert or update webcast items.
long id = this.itemExists(WebcastsContract.class, webcast.ID);
if (id > -1) {
Log.v(TAG, "insertWebcast: title = " + webcast.title + ", ID = " + webcast.ID + " (update)");
mSyncResult.stats.numUpdates++;
mProvider.update(
WebcastsContract.CONTENT_URI, v,
WebcastsContract.IVLE_ID.concat(" = ? AND ") +
WebcastsContract.ACCOUNT.concat(" = ?"),
new String[] { webcast.ID, mAccount.name }
);
return id;
} else {
Log.v(TAG, "insertWebcast: title = " + webcast.title + ", ID = " + webcast.ID);
mSyncResult.stats.numInserts++;
Uri uri = mProvider.insert(WebcastsContract.CONTENT_URI, v);
return ContentUris.parseId(uri);
}
}
/**
* Method: insertWebcastFile
* <p>
* Inserts a webcast file into the webcast file table.
*/
private long insertWebcastFile(Webcast.File file, long moduleId,
long webcastItemGroupId, Integer creatorId) throws
RemoteException {
// Prepare the content values.
ContentValues v = new ContentValues();
v.put(WebcastFilesContract.IVLE_ID, file.ID);
v.put(WebcastFilesContract.MODULE_ID, moduleId);
v.put(WebcastFilesContract.WEBCAST_ITEM_GROUP_ID, webcastItemGroupId);
v.put(WebcastFilesContract.ACCOUNT, mAccount.name);
v.put(WebcastFilesContract.CREATOR_ID, creatorId);
v.put(WebcastFilesContract.BANK_ITEM_ID, file.bankItemID);
v.put(WebcastFilesContract.CREATE_DATE, file.createDate.toString());
v.put(WebcastFilesContract.FILE_DESCRIPTION, file.fileDescription);
v.put(WebcastFilesContract.FILE_NAME, file.fileName);
v.put(WebcastFilesContract.FILE_TITLE, file.fileTitle);
v.put(WebcastFilesContract.MP3, file.MP3);
v.put(WebcastFilesContract.MP4, file.MP4);
v.put(WebcastFilesContract.MEDIA_FORMAT, file.mediaFormat);
v.put(WebcastFilesContract.IS_READ, file.isRead);
// Insert or update webcast file items.
long id = this.itemExists(WebcastFilesContract.class, file.ID);
if (id > -1) {
Log.v(TAG, "insertWebcastFile: fileTitle = " + file.fileTitle + ", ID = " + file.ID + " (update)");
mSyncResult.stats.numUpdates++;
mProvider.update(
WebcastFilesContract.CONTENT_URI, v,
WebcastFilesContract.IVLE_ID.concat(" = ? AND ") +
WebcastFilesContract.ACCOUNT.concat(" = ? AND ") +
WebcastFilesContract.WEBCAST_ITEM_GROUP_ID.concat(" = ?"),
new String[] { file.ID, mAccount.name, Long.toString(webcastItemGroupId) }
);
return id;
} else {
Log.v(TAG, "insertWebcastFile: fileTitle = " + file.fileTitle + ", ID = " + file.ID);
mSyncResult.stats.numInserts++;
Uri uri = mProvider.insert(WebcastFilesContract.CONTENT_URI, v);
return ContentUris.parseId(uri);
}
}
/**
* Method: insertWebcastItemGroup
* <p>
* Inserts a webcast item group into the webcast item group table.
*/
private long insertWebcastItemGroup(Webcast.ItemGroup group, long moduleId,
long webcastId) throws RemoteException {
// Prepare the content values.
ContentValues v = new ContentValues();
v.put(WebcastItemGroupsContract.IVLE_ID, group.ID);
v.put(WebcastItemGroupsContract.MODULE_ID, moduleId);
v.put(WebcastItemGroupsContract.WEBCAST_ID, webcastId);
v.put(WebcastItemGroupsContract.ACCOUNT, mAccount.name);
v.put(WebcastItemGroupsContract.ITEM_GROUP_TITLE, group.itemGroupTitle);
// Webcast item groups don't have IDs, so we cannot identify whether
// to update or insert.
mProvider.delete(
WebcastItemGroupsContract.CONTENT_URI,
WebcastItemGroupsContract.ACCOUNT.concat(" = ? AND ") +
WebcastItemGroupsContract.WEBCAST_ID.concat(" = ?"),
new String[] { mAccount.name, Long.toString(webcastId) });
Uri uri = mProvider.insert(WebcastItemGroupsContract.CONTENT_URI, v);
return ContentUris.parseId(uri);
}
/**
* Method: insertWeblink
* <p>
* Inserts a weblink into the user table.
*/
private long insertWeblink(Weblink weblink, long moduleId) throws
RemoteException {
// Prepare the content values.
ContentValues v = new ContentValues();
v.put(WeblinksContract.IVLE_ID, weblink.ID);
v.put(WeblinksContract.MODULE_ID, moduleId);
v.put(WeblinksContract.ACCOUNT, mAccount.name);
v.put(WeblinksContract.DESCRIPTION, weblink.description);
v.put(WeblinksContract.ORDER, weblink.order);
v.put(WeblinksContract.RATING, weblink.rating);
v.put(WeblinksContract.SITE_TYPE, weblink.siteType);
v.put(WeblinksContract.URL, weblink.url.toString());
// Insert or update weblink items.
long id = this.itemExists(WeblinksContract.class, weblink.ID);
if (id > -1) {
Log.v(TAG, "insertWeblink: description = " + weblink.description + ", ID = " + weblink.ID + " (update)");
mSyncResult.stats.numUpdates++;
mProvider.update(
WeblinksContract.CONTENT_URI, v,
WeblinksContract.IVLE_ID.concat(" = ? AND ") +
WeblinksContract.ACCOUNT.concat(" = ?"),
new String[] { weblink.ID, mAccount.name }
);
return id;
} else {
Log.v(TAG, "insertWeblink: description = " + weblink.description + ", ID = " + weblink.ID);
mSyncResult.stats.numInserts++;
Uri uri = mProvider.insert(WeblinksContract.CONTENT_URI, v);
return ContentUris.parseId(uri);
}
}
/**
* Method: insertWorkbin
* <p>
* Inserts a workbin into the workbin table.
*/
private long insertWorkbin(Workbin workbin, long moduleId) throws
RemoteException {
// Prepare the content values.
ContentValues v = new ContentValues();
v.put(WorkbinsContract.IVLE_ID, workbin.ID);
v.put(WorkbinsContract.MODULE_ID, moduleId);
v.put(WorkbinsContract.ACCOUNT, mAccount.name);
v.put(WorkbinsContract.CREATOR_ID, workbin.creator.ID);
v.put(WorkbinsContract.BADGE_TOOL, workbin.badgeTool);
v.put(WorkbinsContract.PUBLISHED, workbin.published);
v.put(WorkbinsContract.TITLE, workbin.title);
// Insert or update workbin items.
long id = this.itemExists(WorkbinsContract.class, workbin.ID);
if (id > -1) {
Log.v(TAG, "insertWorkbin: title = " + workbin.title + ", ID = " + workbin.ID + " (update)");
mSyncResult.stats.numUpdates++;
mProvider.update(
WorkbinsContract.CONTENT_URI, v,
WorkbinsContract.IVLE_ID.concat(" = ? AND ") +
WorkbinsContract.ACCOUNT.concat(" = ?"),
new String[] { workbin.ID, mAccount.name }
);
return id;
} else {
Log.v(TAG, "insertWorkbin: title = " + workbin.title + ", ID = " + workbin.ID);
mSyncResult.stats.numInserts++;
Uri uri = mProvider.insert(WorkbinsContract.CONTENT_URI, v);
return ContentUris.parseId(uri);
}
}
/**
* Method: insertWorkbinFolder
* <p>
* Inserts a workbin folder into the workbin folders table.
*/
private long insertWorkbinFolder(Workbin.Folder folder, long moduleId,
long workbinId, Long workbinFolderId) throws RemoteException {
// Prepare the content values.
ContentValues v = new ContentValues();
v.put(WorkbinFoldersContract.IVLE_ID, folder.ID);
v.put(WorkbinFoldersContract.MODULE_ID, moduleId);
v.put(WorkbinFoldersContract.ACCOUNT, mAccount.name);
v.put(WorkbinFoldersContract.WORKBIN_ID, workbinId);
v.put(WorkbinFoldersContract.WORKBIN_FOLDER_ID, workbinFolderId);
v.put(WorkbinFoldersContract.ALLOW_UPLOAD, folder.allowUpload);
v.put(WorkbinFoldersContract.ALLOW_VIEW, folder.allowView);
v.put(WorkbinFoldersContract.CLOSE_DATE, folder.closeDate.toString());
v.put(WorkbinFoldersContract.COMMENT_OPTION, folder.commentOption.toString());
v.put(WorkbinFoldersContract.FILE_COUNT, folder.fileCount);
v.put(WorkbinFoldersContract.FOLDER_NAME, folder.folderName);
v.put(WorkbinFoldersContract.ORDER, folder.order);
v.put(WorkbinFoldersContract.OPEN_DATE, folder.openDate.toString());
v.put(WorkbinFoldersContract.SORT_FILES_BY, folder.sortFilesBy);
v.put(WorkbinFoldersContract.UPLOAD_DISPLAY_OPTION, folder.uploadDisplayOption);
// Insert or update workbin folders.
long id = this.itemExists(WorkbinFoldersContract.class, folder.ID);
long insertedId;
if (id > -1) {
Log.v(TAG, "insertWorkbinFolder: folderName = " + folder.folderName + ", ID = " + folder.ID + " (update)");
mSyncResult.stats.numUpdates++;
// The workbin folder ID can be null.
if (workbinFolderId == null) {
mProvider.update(
WorkbinFoldersContract.CONTENT_URI, v,
WorkbinFoldersContract.IVLE_ID.concat(" = ? AND ") +
WorkbinFoldersContract.ACCOUNT.concat(" = ? AND ") +
WorkbinFoldersContract.WORKBIN_ID.concat(" = ?"),
new String[] { folder.ID, mAccount.name, Long.toString(workbinId) }
);
} else {
mProvider.update(
WorkbinFoldersContract.CONTENT_URI, v,
WorkbinFoldersContract.IVLE_ID.concat(" = ? AND ") +
WorkbinFoldersContract.ACCOUNT.concat(" = ? AND ") +
WorkbinFoldersContract.WORKBIN_ID.concat(" = ? AND ") +
WorkbinFoldersContract.WORKBIN_FOLDER_ID.concat(" = ?"),
new String[] { folder.ID, mAccount.name, Long.toString(workbinId), Long.toString(workbinFolderId) }
);
}
insertedId = id;
} else {
Log.v(TAG, "insertWorkbinFolder: folderName = " + folder.folderName + ", ID = " + folder.ID);
mSyncResult.stats.numInserts++;
Uri uri = mProvider.insert(WorkbinFoldersContract.CONTENT_URI, v);
insertedId = ContentUris.parseId(uri);
}
// Insert the files inside this folder.
Workbin.File[] files = folder.getFiles();
for (Workbin.File file : files) {
Integer creatorId = null;
if (file.creator.ID != null) {
Uri creatorUri = this.insertUserIfNotExists(file.creator);
creatorId = Integer.parseInt(creatorUri.getLastPathSegment());
}
Integer commenterId = null;
if (file.commenter.ID != null) {
Uri commenterUri = this.insertUserIfNotExists(file.commenter);
commenterId = Integer.parseInt(commenterUri.getLastPathSegment());
}
this.insertWorkbinFile(file, moduleId, insertedId, creatorId, commenterId);
}
// Insert the subfolders.
Workbin.Folder[] subfolders = folder.getFolders();
for (Workbin.Folder subfolder : subfolders) {
this.insertWorkbinFolder(subfolder, moduleId, workbinId, insertedId);
}
return insertedId;
}
/**
* Method: insertWorkbinFile
* <p>
* Inserts a workbin file into the workbin files table.
*/
private long insertWorkbinFile(Workbin.File file, long moduleId,
long workbinFolderId, int creatorId, int commenterId) throws
RemoteException {
// Prepare the content values.
ContentValues v = new ContentValues();
v.put(WorkbinFilesContract.IVLE_ID, file.ID);
v.put(WorkbinFilesContract.MODULE_ID, moduleId);
v.put(WorkbinFilesContract.ACCOUNT, mAccount.name);
v.put(WorkbinFilesContract.WORKBIN_FOLDER_ID, workbinFolderId);
v.put(WorkbinFilesContract.CREATOR_ID, creatorId);
v.put(WorkbinFilesContract.COMMENTER_ID, commenterId);
v.put(WorkbinFilesContract.FILE_DESCRIPTION, file.fileDescription);
v.put(WorkbinFilesContract.FILE_NAME, file.fileName);
v.put(WorkbinFilesContract.FILE_REMARKS, file.fileRemarks);
v.put(WorkbinFilesContract.FILE_REMARKS_ATTACHMENT, file.fileRemarksAttachment);
v.put(WorkbinFilesContract.FILE_SIZE, file.fileSize);
v.put(WorkbinFilesContract.FILE_TYPE, file.fileType);
v.put(WorkbinFilesContract.IS_DOWNLOADED, file.isDownloaded);
try {
v.put(WorkbinFilesContract.DOWNLOAD_URL, file.getDownloadURL().toString());
} catch (MalformedURLException e) {
// Ignore the exception.
Log.w(TAG, "MalformedURLException inserting download URL for workbin file " + file.ID);
}
// Insert or update workbin file items.
long id = this.itemExists(WorkbinFilesContract.class, file.ID);
if (id > -1) {
Log.v(TAG, "insertWorkbinFile: fileName = " + file.fileName + ", ID = " + file.ID + " (update)");
mSyncResult.stats.numUpdates++;
mProvider.update(
WorkbinFilesContract.CONTENT_URI, v,
WorkbinFilesContract.IVLE_ID.concat(" = ? AND ") +
WorkbinFilesContract.ACCOUNT.concat(" = ?"),
new String[] { file.ID, mAccount.name }
);
return id;
} else {
Log.v(TAG, "insertWorkbinFile: fileName = " + file.fileName + ", ID = " + file.ID);
mSyncResult.stats.numInserts++;
Uri uri = mProvider.insert(WorkbinFilesContract.CONTENT_URI, v);
return ContentUris.parseId(uri);
}
}
/**
* Method: insertUserIfNotExists
* <p>
* Inserts a user into the user table if the user doesn't already
* exist. If the user object is null, then this method returns null.
*/
private Uri insertUserIfNotExists(User user) throws
RemoteException {
// If user ID is null, then the user is probably null, and we should
// ignore it.
if (user.ID == null) {
Log.d(TAG, "User was null in insertUserIfNotExists!");
return null;
}
// Query for the user first.
String[] projection = { UsersContract.ID };
String selection = UsersContract.IVLE_ID + " = ?";
String[] selectionArgs = { user.ID };
Cursor c = mProvider.query(UsersContract.CONTENT_URI, projection,
selection, selectionArgs, null);
// Prepare values to be inserted.
ContentValues v = new ContentValues();
v.put(UsersContract.IVLE_ID, user.ID);
v.put(UsersContract.ACCOUNT, mAccount.name);
v.put(UsersContract.ACCOUNT_TYPE, user.accountType);
v.put(UsersContract.EMAIL, user.email);
v.put(UsersContract.NAME, user.name);
v.put(UsersContract.TITLE, user.title);
v.put(UsersContract.USER_ID, user.userID);
// Check number of users that matched.
if (c.getCount() < 1) {
mSyncResult.stats.numInserts++;
return mProvider.insert(UsersContract.CONTENT_URI, v);
} else {
// Obtain the ID for the user we want to update.
c.moveToFirst();
int userId = c.getInt(0);
mSyncResult.stats.numUpdates++;
return mProvider.insert(
Uri.withAppendedPath(UsersContract.CONTENT_URI, "/" + userId),
v
);
}
}
// }}}
}