diff --git a/api/src/com/todoroo/andlib/data/Property.java b/api/src/com/todoroo/andlib/data/Property.java index ae255c796..e481eb662 100644 --- a/api/src/com/todoroo/andlib/data/Property.java +++ b/api/src/com/todoroo/andlib/data/Property.java @@ -49,6 +49,8 @@ public abstract class Property extends Field implements Cloneable { public static final int PROP_FLAG_BOOLEAN = 1 << 3; /** Is this field a serialized JSON object? */ public static final int PROP_FLAG_JSON = 1 << 4; + /** Is this field for pictures? (usually as a json object containing "path" key or urls) */ + public static final int PROP_FLAG_PICTURE = 1 << 5; public int flags = 0; diff --git a/api/src/com/todoroo/andlib/utility/DialogUtilities.java b/api/src/com/todoroo/andlib/utility/DialogUtilities.java index fd7303884..1d9d30da4 100644 --- a/api/src/com/todoroo/andlib/utility/DialogUtilities.java +++ b/api/src/com/todoroo/andlib/utility/DialogUtilities.java @@ -200,13 +200,13 @@ public class DialogUtilities { * @param text * @return */ - public static ProgressDialog progressDialog(Context context, String text) { + public static ProgressDialog progressDialog(Activity context, String text) { ProgressDialog dialog = new ProgressDialog(context); dialog.setIndeterminate(true); dialog.setProgressStyle(ProgressDialog.STYLE_SPINNER); dialog.setMessage(text); dialog.show(); - dialog.setOwnerActivity((Activity)context); + dialog.setOwnerActivity(context); return dialog; } diff --git a/api/src/com/todoroo/astrid/data/MetadataApiDao.java b/api/src/com/todoroo/astrid/data/MetadataApiDao.java index 6296c4ade..1a92f0b6d 100644 --- a/api/src/com/todoroo/astrid/data/MetadataApiDao.java +++ b/api/src/com/todoroo/astrid/data/MetadataApiDao.java @@ -5,16 +5,10 @@ */ package com.todoroo.astrid.data; -import java.util.ArrayList; -import java.util.HashSet; - -import android.content.ContentValues; import android.content.Context; import com.todoroo.andlib.data.ContentResolverDao; -import com.todoroo.andlib.data.TodorooCursor; import com.todoroo.andlib.sql.Criterion; -import com.todoroo.andlib.sql.Query; /** * Data access object for accessing Astrid's {@link Metadata} table. A @@ -52,55 +46,4 @@ public class MetadataApiDao extends ContentResolverDao { } - /** - * Synchronize metadata for given task id. Deletes rows in database that - * are not identical to those in the metadata list, creates rows that - * have no match. - * - * @param taskId id of task to perform synchronization on - * @param metadata list of new metadata items to save - * @param metadataCriteria criteria to load data for comparison from metadata - */ - public void synchronizeMetadata(long taskId, ArrayList metadata, - Criterion metadataCriteria) { - HashSet newMetadataValues = new HashSet(); - for(Metadata metadatum : metadata) { - metadatum.setValue(Metadata.TASK, taskId); - metadatum.clearValue(Metadata.ID); - newMetadataValues.add(metadatum.getMergedValues()); - } - - Metadata item = new Metadata(); - TodorooCursor cursor = query(Query.select(Metadata.PROPERTIES).where(Criterion.and(MetadataCriteria.byTask(taskId), - metadataCriteria))); - try { - // try to find matches within our metadata list - for(cursor.moveToFirst(); !cursor.isAfterLast(); cursor.moveToNext()) { - item.readFromCursor(cursor); - long id = item.getId(); - - // clear item id when matching with incoming values - item.clearValue(Metadata.ID); - ContentValues itemMergedValues = item.getMergedValues(); - if(newMetadataValues.contains(itemMergedValues)) { - newMetadataValues.remove(itemMergedValues); - continue; - } - - // not matched. cut it - delete(id); - } - } finally { - cursor.close(); - } - - // everything that remains shall be written - for(ContentValues values : newMetadataValues) { - item.clear(); - item.mergeWith(values); - save(item); - } - } - - } diff --git a/api/src/com/todoroo/astrid/data/RemoteModel.java b/api/src/com/todoroo/astrid/data/RemoteModel.java index 2290da095..1fe72eea7 100644 --- a/api/src/com/todoroo/astrid/data/RemoteModel.java +++ b/api/src/com/todoroo/astrid/data/RemoteModel.java @@ -5,17 +5,25 @@ */ package com.todoroo.astrid.data; +import java.io.File; +import java.io.FileNotFoundException; +import java.io.FileOutputStream; +import java.io.IOException; + import org.json.JSONException; import org.json.JSONObject; +import android.content.ContentValues; +import android.content.Context; import android.graphics.Bitmap; +import android.graphics.BitmapFactory; import android.text.TextUtils; import com.todoroo.andlib.data.AbstractModel; import com.todoroo.andlib.data.Property.LongProperty; import com.todoroo.andlib.data.Property.StringProperty; import com.todoroo.andlib.data.TodorooCursor; -import com.todoroo.andlib.utility.AndroidUtilities; +import com.todoroo.andlib.utility.DateUtilities; /** * A model that is synchronized to a remote server and has a remote id @@ -77,6 +85,16 @@ abstract public class RemoteModel extends AbstractModel { return NO_UUID; } + public void setUuid(String uuid) { + if (setValues == null) + setValues = new ContentValues(); + + if(NO_UUID.equals(uuid)) + clearValue(UUID_PROPERTY); + else + setValues.put(UUID_PROPERTY_NAME, uuid); + } + public static boolean isUuidEmpty(String uuid) { return NO_UUID.equals(uuid) || TextUtils.isEmpty(uuid); } @@ -97,12 +115,49 @@ abstract public class RemoteModel extends AbstractModel { public static class PictureHelper { + public static final String PICTURES_DIRECTORY = "pictures"; //$NON-NLS-1$ + + @SuppressWarnings("nls") + public static JSONObject savePictureJson(Context context, Bitmap bitmap) { + try { + String name = DateUtilities.now() + ".jpg"; + JSONObject jsonObject = new JSONObject(); + jsonObject.put("name", name); + jsonObject.put("type", "image/jpeg"); + + File dir = context.getExternalFilesDir(PICTURES_DIRECTORY); + if (dir != null) { + File file = new File(dir + File.separator + DateUtilities.now() + ".jpg"); + if (file.exists()) + return null; + + try { + FileOutputStream fos = new FileOutputStream(file); + bitmap.compress(Bitmap.CompressFormat.JPEG, 90, fos); + fos.flush(); + fos.close(); + jsonObject.put("path", file.getAbsolutePath()); + } catch (FileNotFoundException e) { + // + } catch (IOException e) { + // + } + return jsonObject; + } else { + return null; + } + } catch (JSONException e) { + // + } + return null; + } + public static String getPictureUrl(String value, String size) { try { if (value == null) return null; JSONObject pictureJson = new JSONObject(value); - if (pictureJson.has("data")) // Unpushed encoded bitmap //$NON-NLS-1$ + if (pictureJson.has("path")) // Unpushed encoded bitmap //$NON-NLS-1$ return null; return pictureJson.optString(size); } catch (JSONException e) { @@ -115,11 +170,11 @@ abstract public class RemoteModel extends AbstractModel { try { if (value == null) return null; - if (value.contains("data")) { + if (value.contains("path")) { JSONObject pictureJson = new JSONObject(value); - if (pictureJson.has("data")) { - String data = pictureJson.getString("data"); - return AndroidUtilities.decodeBase64Bitmap(data); + if (pictureJson.has("path")) { + String path = pictureJson.getString("path"); + return BitmapFactory.decodeFile(path); } } return null; @@ -133,18 +188,5 @@ abstract public class RemoteModel extends AbstractModel { String value = cursor.get(pictureProperty); return getPictureUrl(value, size); } - - @SuppressWarnings("nls") - public static JSONObject uploadPictureJson(Bitmap bitmap) { - try { - JSONObject pictureJson = new JSONObject(); - pictureJson.put("name", "photo.jpg"); - pictureJson.put("type", "image/jpeg"); - pictureJson.put("data", AndroidUtilities.encodeBase64Bitmap(bitmap)); - return pictureJson; - } catch (JSONException e) { - return null; - } - } } } diff --git a/api/src/com/todoroo/astrid/data/TagData.java b/api/src/com/todoroo/astrid/data/TagData.java index 797f8b2d6..8002f79cc 100644 --- a/api/src/com/todoroo/astrid/data/TagData.java +++ b/api/src/com/todoroo/astrid/data/TagData.java @@ -63,7 +63,7 @@ public final class TagData extends RemoteModel { /** Project picture */ public static final StringProperty PICTURE = new StringProperty( - TABLE, "picture", Property.PROP_FLAG_JSON); + TABLE, "picture", Property.PROP_FLAG_JSON | Property.PROP_FLAG_PICTURE); /** Tag team array (JSON) */ @Deprecated public static final StringProperty MEMBERS = new StringProperty( diff --git a/api/src/com/todoroo/astrid/data/UserActivity.java b/api/src/com/todoroo/astrid/data/UserActivity.java index 87b9712b5..590c13e39 100644 --- a/api/src/com/todoroo/astrid/data/UserActivity.java +++ b/api/src/com/todoroo/astrid/data/UserActivity.java @@ -54,7 +54,7 @@ public class UserActivity extends RemoteModel { /** Picture */ public static final StringProperty PICTURE = new StringProperty( - TABLE, "picture", Property.PROP_FLAG_JSON); + TABLE, "picture", Property.PROP_FLAG_JSON | Property.PROP_FLAG_PICTURE); /** Target id */ public static final StringProperty TARGET_ID = new StringProperty( diff --git a/api/src/com/todoroo/astrid/sync/SyncV2BackgroundService.java b/api/src/com/todoroo/astrid/sync/SyncV2BackgroundService.java index 2c5b6ce50..9e6dded89 100644 --- a/api/src/com/todoroo/astrid/sync/SyncV2BackgroundService.java +++ b/api/src/com/todoroo/astrid/sync/SyncV2BackgroundService.java @@ -77,13 +77,15 @@ abstract public class SyncV2BackgroundService extends Service { if(!getSyncUtilities().isLoggedIn()) return; - getSyncProvider().synchronizeActiveTasks(false, new SyncResultCallbackAdapter() { - @Override - public void finished() { - getSyncUtilities().recordSuccessfulSync(); - context.sendBroadcast(new Intent(AstridApiConstants.BROADCAST_EVENT_REFRESH)); - } - }); + SyncV2Provider provider = getSyncProvider(); + if (provider.isActive()) + provider.synchronizeActiveTasks(false, new SyncResultCallbackAdapter() { + @Override + public void finished() { + getSyncUtilities().recordSuccessfulSync(); + context.sendBroadcast(new Intent(AstridApiConstants.BROADCAST_EVENT_REFRESH)); + } + }); } @Override diff --git a/api/src/com/todoroo/astrid/sync/SyncV2Provider.java b/api/src/com/todoroo/astrid/sync/SyncV2Provider.java index d51c5335d..7372beb4a 100644 --- a/api/src/com/todoroo/astrid/sync/SyncV2Provider.java +++ b/api/src/com/todoroo/astrid/sync/SyncV2Provider.java @@ -86,7 +86,8 @@ abstract public class SyncV2Provider { utilities.recordSuccessfulSync(); utilities.reportLastError(); utilities.stopOngoing(); - callback.finished(); + if (callback != null) + callback.finished(); } @Override diff --git a/astrid/.classpath b/astrid/.classpath index 20e839511..4c3509217 100644 --- a/astrid/.classpath +++ b/astrid/.classpath @@ -5,7 +5,6 @@ - @@ -28,9 +27,9 @@ - + diff --git a/astrid/AndroidManifest.xml b/astrid/AndroidManifest.xml index a304714dc..6e4c1401c 100644 --- a/astrid/AndroidManifest.xml +++ b/astrid/AndroidManifest.xml @@ -189,11 +189,6 @@ android:theme="@android:style/Theme" /> - - - - - + android:label="@string/gtasks_GPr_header" + android:screenOrientation="portrait"> - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/astrid/ant.properties b/astrid/ant.properties index f2bab94ce..eb9e6bcae 100644 --- a/astrid/ant.properties +++ b/astrid/ant.properties @@ -8,7 +8,7 @@ out.dir=antbuild source.dir=src-combined # Astrid source folders -astrid.sources=src,common-src,plugin-src,src-legacy,rmilk-src +astrid.sources=src,common-src,plugin-src,src-legacy # Keystore signjar.keystore=/etc/todoroo/keystore diff --git a/astrid/libs/crittercism_v2_1_2.jar b/astrid/libs/crittercism_v2_1_2.jar deleted file mode 100644 index cdc546a71..000000000 Binary files a/astrid/libs/crittercism_v2_1_2.jar and /dev/null differ diff --git a/astrid/libs/crittercism_v3_0_7_sdkonly.jar b/astrid/libs/crittercism_v3_0_7_sdkonly.jar new file mode 100644 index 000000000..e5d4c0831 Binary files /dev/null and b/astrid/libs/crittercism_v3_0_7_sdkonly.jar differ diff --git a/astrid/plugin-src/com/todoroo/astrid/actfm/ActFmLoginActivity.java b/astrid/plugin-src/com/todoroo/astrid/actfm/ActFmLoginActivity.java index 0dd90c5d5..9e2c8259f 100644 --- a/astrid/plugin-src/com/todoroo/astrid/actfm/ActFmLoginActivity.java +++ b/astrid/plugin-src/com/todoroo/astrid/actfm/ActFmLoginActivity.java @@ -7,7 +7,10 @@ package com.todoroo.astrid.actfm; import java.io.IOException; import java.util.Arrays; +import java.util.HashMap; +import java.util.Map.Entry; import java.util.Random; +import java.util.Set; import org.json.JSONObject; @@ -52,10 +55,13 @@ import com.google.android.googlelogin.GoogleLoginServiceConstants; import com.google.android.googlelogin.GoogleLoginServiceHelper; import com.timsu.astrid.GCMIntentService; import com.timsu.astrid.R; +import com.todoroo.andlib.data.TodorooCursor; import com.todoroo.andlib.service.Autowired; import com.todoroo.andlib.service.ContextManager; import com.todoroo.andlib.service.DependencyInjectionService; import com.todoroo.andlib.service.ExceptionService; +import com.todoroo.andlib.sql.Criterion; +import com.todoroo.andlib.sql.Query; import com.todoroo.andlib.utility.AndroidUtilities; import com.todoroo.andlib.utility.DateUtilities; import com.todoroo.andlib.utility.DialogUtilities; @@ -65,31 +71,47 @@ import com.todoroo.astrid.actfm.sync.ActFmPreferenceService; import com.todoroo.astrid.actfm.sync.ActFmServiceException; import com.todoroo.astrid.actfm.sync.ActFmSyncMonitor; import com.todoroo.astrid.actfm.sync.messages.ConstructOutstandingTableFromMasterTable; +import com.todoroo.astrid.actfm.sync.messages.ConstructTaskOutstandingTableFromMasterTable; import com.todoroo.astrid.actfm.sync.messages.NameMaps; import com.todoroo.astrid.activity.Eula; +import com.todoroo.astrid.dao.Database; +import com.todoroo.astrid.dao.MetadataDao; +import com.todoroo.astrid.dao.MetadataDao.MetadataCriteria; +import com.todoroo.astrid.dao.RemoteModelDao; import com.todoroo.astrid.dao.TagDataDao; +import com.todoroo.astrid.dao.TagMetadataDao; import com.todoroo.astrid.dao.TagOutstandingDao; +import com.todoroo.astrid.dao.TaskAttachmentDao; +import com.todoroo.astrid.dao.TaskAttachmentOutstandingDao; import com.todoroo.astrid.dao.TaskDao; import com.todoroo.astrid.dao.TaskListMetadataDao; import com.todoroo.astrid.dao.TaskListMetadataOutstandingDao; import com.todoroo.astrid.dao.TaskOutstandingDao; import com.todoroo.astrid.dao.UserActivityDao; import com.todoroo.astrid.dao.UserActivityOutstandingDao; +import com.todoroo.astrid.dao.UserDao; +import com.todoroo.astrid.data.Metadata; +import com.todoroo.astrid.data.RemoteModel; import com.todoroo.astrid.data.TagData; import com.todoroo.astrid.data.TagOutstanding; import com.todoroo.astrid.data.Task; import com.todoroo.astrid.data.TaskListMetadata; import com.todoroo.astrid.data.TaskListMetadataOutstanding; -import com.todoroo.astrid.data.TaskOutstanding; import com.todoroo.astrid.data.UserActivity; import com.todoroo.astrid.data.UserActivityOutstanding; import com.todoroo.astrid.gtasks.auth.ModernAuthManager; +import com.todoroo.astrid.helper.UUIDHelper; import com.todoroo.astrid.service.AstridDependencyInjector; import com.todoroo.astrid.service.MarketStrategy.AmazonMarketStrategy; import com.todoroo.astrid.service.StatisticsConstants; import com.todoroo.astrid.service.StatisticsService; import com.todoroo.astrid.service.SyncV2Service; import com.todoroo.astrid.service.TaskService; +import com.todoroo.astrid.subtasks.AstridOrderedListUpdater; +import com.todoroo.astrid.subtasks.AstridOrderedListUpdater.Node; +import com.todoroo.astrid.subtasks.SubtasksHelper; +import com.todoroo.astrid.subtasks.SubtasksHelper.TreeRemapHelper; +import com.todoroo.astrid.tags.TaskToTagMetadata; /** * This activity allows users to sign in or log in to Astrid.com @@ -99,6 +121,8 @@ import com.todoroo.astrid.service.TaskService; */ public class ActFmLoginActivity extends SherlockFragmentActivity { + @Autowired + protected Database database; @Autowired protected ExceptionService exceptionService; @Autowired @@ -111,10 +135,16 @@ public class ActFmLoginActivity extends SherlockFragmentActivity { @Autowired private TaskOutstandingDao taskOutstandingDao; @Autowired + private TaskAttachmentDao taskAttachmentDao; + @Autowired + private TaskAttachmentOutstandingDao taskAttachmentOutstandingDao; + @Autowired private TagDataDao tagDataDao; @Autowired private TagOutstandingDao tagOutstandingDao; @Autowired + private UserDao userDao; + @Autowired private UserActivityDao userActivityDao; @Autowired private UserActivityOutstandingDao userActivityOutstandingDao; @@ -122,7 +152,10 @@ public class ActFmLoginActivity extends SherlockFragmentActivity { private TaskListMetadataDao taskListMetadataDao; @Autowired private TaskListMetadataOutstandingDao taskListMetadataOutstandingDao; - + @Autowired + private MetadataDao metadataDao; + @Autowired + private TagMetadataDao tagMetadataDao; @Autowired protected SyncV2Service syncService; @@ -618,11 +651,11 @@ public class ActFmLoginActivity extends SherlockFragmentActivity { StatisticsService.reportEvent(StatisticsConstants.ACTFM_NEW_USER, "provider", provider); } // Successful login, create outstanding entries - if (!TextUtils.isEmpty(token)) { - new ConstructOutstandingTableFromMasterTable(NameMaps.TABLE_ID_TASKS, taskDao, taskOutstandingDao, Task.CREATION_DATE).execute(); - new ConstructOutstandingTableFromMasterTable(NameMaps.TABLE_ID_TAGS, tagDataDao, tagOutstandingDao, TagData.CREATION_DATE).execute(); - new ConstructOutstandingTableFromMasterTable(NameMaps.TABLE_ID_USER_ACTIVITY, userActivityDao, userActivityOutstandingDao, UserActivity.CREATED_AT).execute(); - new ConstructOutstandingTableFromMasterTable(NameMaps.TABLE_ID_TASK_LIST_METADATA, taskListMetadataDao, taskListMetadataOutstandingDao, null).execute(); + long lastId = Preferences.getLong(ActFmPreferenceService.PREF_USER_ID, 0); + long newUser = result.optLong("id"); + + if (!TextUtils.isEmpty(token) && (lastId == 0 || lastId == newUser)) { + constructOutstandingTables(); } runOnUiThread(new Runnable() { public void run() { @@ -646,8 +679,192 @@ public class ActFmLoginActivity extends SherlockFragmentActivity { }.start(); } + private void constructOutstandingTables() { + new ConstructTaskOutstandingTableFromMasterTable(NameMaps.TABLE_ID_TASKS, taskDao, taskOutstandingDao, metadataDao, Task.CREATION_DATE).execute(); + new ConstructOutstandingTableFromMasterTable(NameMaps.TABLE_ID_TAGS, tagDataDao, tagOutstandingDao, TagData.CREATION_DATE).execute(); + new ConstructOutstandingTableFromMasterTable(NameMaps.TABLE_ID_USER_ACTIVITY, userActivityDao, userActivityOutstandingDao, UserActivity.CREATED_AT).execute(); + new ConstructOutstandingTableFromMasterTable(NameMaps.TABLE_ID_TASK_LIST_METADATA, taskListMetadataDao, taskListMetadataOutstandingDao, null).execute(); + } + @SuppressWarnings("nls") - protected void postAuthenticate(JSONObject result, String token) { + private void postAuthenticate(final JSONObject result, final String token) { + long lastLoggedInUser = Preferences.getLong(ActFmPreferenceService.PREF_USER_ID, 0); + if (lastLoggedInUser > 0) { + long newUserId = result.optLong("id"); + if (lastLoggedInUser != newUserId) { + // In this case, we need to either make all data private or clear all data + // Prompt for choice + DialogUtilities.okCancelCustomDialog(this, + getString(R.string.actfm_logged_in_different_user_title), + getString(R.string.actfm_logged_in_different_user_body), + R.string.actfm_logged_in_different_user_keep_data, + R.string.actfm_logged_in_different_user_clear_data, + android.R.drawable.ic_dialog_alert, + new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialog, int which) { + // TODO: Make all data private + final ProgressDialog pd = DialogUtilities.progressDialog(ActFmLoginActivity.this, getString(R.string.actfm_logged_in_different_user_processing)); + + new Thread(new Runnable() { + @Override + public void run() { + // Delete all tasks not assigned to self + taskService.deleteWhere(Criterion.or(Task.USER_ID.neq(0), Task.DELETION_DATE.gt(0))); + // Delete user table + userDao.deleteWhere(Criterion.all); + // Delete attachments table + taskAttachmentDao.deleteWhere(Criterion.all); + // Delete deleted tags + tagDataDao.deleteWhere(TagData.DELETION_DATE.gt(0)); + // Delete deleted metadata + metadataDao.deleteWhere(Metadata.DELETION_DATE.gt(0)); + + // Clear all outstanding tables + taskOutstandingDao.deleteWhere(Criterion.all); + tagOutstandingDao.deleteWhere(Criterion.all); + userActivityOutstandingDao.deleteWhere(Criterion.all); + taskListMetadataOutstandingDao.deleteWhere(Criterion.all); + taskAttachmentOutstandingDao.deleteWhere(Criterion.all); + + // Make all tags private + tagMetadataDao.deleteWhere(Criterion.all); + + // Generate new uuids for all tasks/tags/user activity/task list metadata and update links + generateNewUuids(); + + constructOutstandingTables(); + runOnUiThread(new Runnable() { + @Override + public void run() { + finishSignIn(result, token, true); + } + }); + pd.dismiss(); + } + }).start(); + } + }, + new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialog, int which) { + deleteDatabase(database.getName()); + finishSignIn(result, token, true); + } + }); + } else { + finishSignIn(result, token, false); + } + } else { + finishSignIn(result, token, false); + } + } + + private void generateNewUuids() { + final HashMap uuidTaskMap = new HashMap(); + HashMap uuidTagMap = new HashMap(); + HashMap uuidUserActivityMap = new HashMap(); + HashMap uuidTaskListMetadataMap = new HashMap(); + + mapUuids(taskDao, uuidTaskMap); + mapUuids(tagDataDao, uuidTagMap); + mapUuids(userActivityDao, uuidUserActivityMap); + mapUuids(taskListMetadataDao, uuidTaskListMetadataMap); + + Task t = new Task(); + TagData td = new TagData(); + Metadata m = new Metadata(); + UserActivity ua = new UserActivity(); + TaskListMetadata tlm = new TaskListMetadata(); + + Set> entries = uuidTaskMap.entrySet(); + for (Entry e : entries) { + t.clear(); + m.clear(); + ua.clear(); + + String oldUuid = e.getKey(); + String newUuid = e.getValue(); + + t.setValue(Task.UUID, newUuid); + ua.setValue(UserActivity.TARGET_ID, newUuid); + m.setValue(TaskToTagMetadata.TASK_UUID, newUuid); + + taskDao.update(Task.UUID.eq(oldUuid), t); + metadataDao.update(Criterion.and(MetadataCriteria.withKey(TaskToTagMetadata.KEY), TaskToTagMetadata.TASK_UUID.eq(oldUuid)), m); + userActivityDao.update(UserActivity.TARGET_ID.eq(oldUuid), ua); + } + + entries = uuidTagMap.entrySet(); + for (Entry e : entries) { + td.clear(); + ua.clear(); + m.clear(); + tlm.clear(); + + String oldUuid = e.getKey(); + String newUuid = e.getValue(); + + td.setValue(TagData.UUID, newUuid); + ua.setValue(UserActivity.TARGET_ID, newUuid); + m.setValue(TaskToTagMetadata.TAG_UUID, newUuid); + tlm.setValue(TaskListMetadata.TAG_UUID, newUuid); + + tagDataDao.update(TagData.UUID.eq(oldUuid), td); + userActivityDao.update(UserActivity.TARGET_ID.eq(oldUuid), ua); + metadataDao.update(Criterion.and(MetadataCriteria.withKey(TaskToTagMetadata.KEY), TaskToTagMetadata.TAG_UUID.eq(oldUuid)), m); + taskListMetadataDao.update(TaskListMetadata.TAG_UUID.eq(oldUuid), tlm); + } + + entries = uuidUserActivityMap.entrySet(); + for (Entry e : entries) { + ua.clear(); + + String oldUuid = e.getKey(); + String newUuid = e.getValue(); + + ua.setValue(UserActivity.UUID, newUuid); + userActivityDao.update(UserActivity.UUID.eq(oldUuid), ua); + } + + TodorooCursor tlmCursor = taskListMetadataDao.query(Query.select(TaskListMetadata.ID, TaskListMetadata.UUID, TaskListMetadata.TASK_IDS, TaskListMetadata.CHILD_TAG_IDS)); + try { + for (tlmCursor.moveToFirst(); !tlmCursor.isAfterLast(); tlmCursor.moveToNext()) { + tlm.clear(); + tlm.readFromCursor(tlmCursor); + tlm.setValue(TaskListMetadata.UUID, uuidTaskListMetadataMap.get(tlm.getUuid())); + String taskIds = tlm.getValue(TaskListMetadata.TASK_IDS); + if (!TaskListMetadata.taskIdsIsEmpty(taskIds)) { + Node root = AstridOrderedListUpdater.buildTreeModel(taskIds, null); + SubtasksHelper.remapTree(root, uuidTaskMap, new TreeRemapHelper() { + public String getKeyFromOldUuid(String uuid) { + return uuid; // Old uuids are the keys + } + }); + taskIds = AstridOrderedListUpdater.serializeTree(root); + tlm.setValue(TaskListMetadata.TASK_IDS, taskIds); + } + taskListMetadataDao.saveExisting(tlm); + } + } finally { + tlmCursor.close(); + } + + } + + private void mapUuids(RemoteModelDao dao, HashMap map) { + TodorooCursor items = dao.query(Query.select(RemoteModel.UUID_PROPERTY)); + try { + for (items.moveToFirst(); !items.isAfterLast(); items.moveToNext()) { + map.put(items.get(RemoteModel.UUID_PROPERTY), UUIDHelper.newUUID()); + } + } finally { + items.close(); + } + } + + @SuppressWarnings("nls") + private void finishSignIn(JSONObject result, String token, boolean restart) { actFmPreferenceService.setToken(token); Preferences.setLong(ActFmPreferenceService.PREF_USER_ID, @@ -667,15 +884,21 @@ public class ActFmLoginActivity extends SherlockFragmentActivity { ActFmPreferenceService.reloadThisUser(); + GCMIntentService.register(this); + + + if (restart) { + System.exit(0); + return; + } else { + setResult(RESULT_OK); + finish(); + } + ActFmSyncMonitor monitor = ActFmSyncMonitor.getInstance(); synchronized (monitor) { monitor.notifyAll(); } - - setResult(RESULT_OK); - finish(); - - GCMIntentService.register(this); } @SuppressWarnings("nls") diff --git a/astrid/plugin-src/com/todoroo/astrid/actfm/CommentsFragment.java b/astrid/plugin-src/com/todoroo/astrid/actfm/CommentsFragment.java index 91d112ec5..4a9ea4d6c 100644 --- a/astrid/plugin-src/com/todoroo/astrid/actfm/CommentsFragment.java +++ b/astrid/plugin-src/com/todoroo/astrid/actfm/CommentsFragment.java @@ -325,7 +325,7 @@ public abstract class CommentsFragment extends SherlockListFragment { protected void addComment() { UserActivity update = createUpdate(); if (picture != null) { - JSONObject pictureJson = RemoteModel.PictureHelper.uploadPictureJson(picture); + JSONObject pictureJson = RemoteModel.PictureHelper.savePictureJson(getActivity(), picture); if (pictureJson != null) { update.setValue(UserActivity.PICTURE, pictureJson.toString()); } diff --git a/astrid/plugin-src/com/todoroo/astrid/actfm/TagSettingsActivity.java b/astrid/plugin-src/com/todoroo/astrid/actfm/TagSettingsActivity.java index 81300e59e..adac6a4a4 100644 --- a/astrid/plugin-src/com/todoroo/astrid/actfm/TagSettingsActivity.java +++ b/astrid/plugin-src/com/todoroo/astrid/actfm/TagSettingsActivity.java @@ -293,7 +293,7 @@ public class TagSettingsActivity extends SherlockFragmentActivity { tagData.setValue(TagData.TAG_DESCRIPTION, newDesc); if (setBitmap != null) { - JSONObject pictureJson = RemoteModel.PictureHelper.uploadPictureJson(setBitmap); + JSONObject pictureJson = RemoteModel.PictureHelper.savePictureJson(this, setBitmap); if (pictureJson != null) tagData.setValue(TagData.PICTURE, pictureJson.toString()); } diff --git a/astrid/plugin-src/com/todoroo/astrid/actfm/sync/ActFmInvoker.java b/astrid/plugin-src/com/todoroo/astrid/actfm/sync/ActFmInvoker.java index 72d20a464..826376659 100644 --- a/astrid/plugin-src/com/todoroo/astrid/actfm/sync/ActFmInvoker.java +++ b/astrid/plugin-src/com/todoroo/astrid/actfm/sync/ActFmInvoker.java @@ -12,14 +12,12 @@ import java.security.NoSuchAlgorithmException; import java.util.ArrayList; import java.util.Collections; import java.util.Comparator; -import java.util.List; import java.util.TimeZone; import org.apache.commons.codec.digest.DigestUtils; import org.apache.http.HttpEntity; -import org.apache.http.client.entity.UrlEncodedFormEntity; -import org.apache.http.message.BasicNameValuePair; -import org.apache.http.protocol.HTTP; +import org.apache.http.entity.mime.MultipartEntity; +import org.apache.http.entity.mime.content.StringBody; import org.json.JSONArray; import org.json.JSONException; import org.json.JSONObject; @@ -185,7 +183,7 @@ public class ActFmInvoker { } } - public JSONObject postSync(JSONArray data, String token) throws IOException, + public JSONObject postSync(JSONArray data, MultipartEntity entity, String token) throws IOException, ActFmServiceException { try { String dataString = data.toString(); @@ -194,11 +192,9 @@ public class ActFmInvoker { String request = createFetchUrl("api/" + API_VERSION, "synchronize", "token", token, "data", dataString, "time", timeString); if (SYNC_DEBUG) Log.e("act-fm-post", request); - List pairs = new ArrayList(); - pairs.add(new BasicNameValuePair("token", token)); - pairs.add(new BasicNameValuePair("data", data.toString())); - pairs.add(new BasicNameValuePair("time", timeString)); - UrlEncodedFormEntity entity = new UrlEncodedFormEntity(pairs, HTTP.UTF_8); + entity.addPart("token", new StringBody(token)); + entity.addPart("data", new StringBody(data.toString())); + entity.addPart("time", new StringBody(timeString)); String response = restClient.post(request, entity); JSONObject object = new JSONObject(response); diff --git a/astrid/plugin-src/com/todoroo/astrid/actfm/sync/ActFmSyncThread.java b/astrid/plugin-src/com/todoroo/astrid/actfm/sync/ActFmSyncThread.java index 8fc756ad2..09c869a2c 100644 --- a/astrid/plugin-src/com/todoroo/astrid/actfm/sync/ActFmSyncThread.java +++ b/astrid/plugin-src/com/todoroo/astrid/actfm/sync/ActFmSyncThread.java @@ -3,22 +3,31 @@ package com.todoroo.astrid.actfm.sync; import java.io.IOException; import java.util.Collections; import java.util.HashMap; +import java.util.HashSet; import java.util.LinkedList; import java.util.List; import java.util.Map; +import java.util.Set; +import org.apache.http.entity.mime.MultipartEntity; import org.json.JSONArray; import org.json.JSONObject; +import android.app.Activity; +import android.content.Context; import android.content.Intent; import android.util.Log; +import android.view.View; +import android.widget.ProgressBar; +import com.crittercism.app.Crittercism; import com.todoroo.andlib.data.TodorooCursor; import com.todoroo.andlib.service.Autowired; import com.todoroo.andlib.service.ContextManager; import com.todoroo.andlib.service.DependencyInjectionService; import com.todoroo.andlib.sql.Query; import com.todoroo.andlib.utility.AndroidUtilities; +import com.todoroo.andlib.utility.DateUtilities; import com.todoroo.andlib.utility.Preferences; import com.todoroo.astrid.actfm.sync.messages.BriefMe; import com.todoroo.astrid.actfm.sync.messages.ChangesHappened; @@ -52,6 +61,7 @@ import com.todoroo.astrid.data.TaskListMetadataOutstanding; import com.todoroo.astrid.data.TaskOutstanding; import com.todoroo.astrid.data.User; import com.todoroo.astrid.data.UserActivity; +import com.todoroo.astrid.widget.TasksWidget; public class ActFmSyncThread { @@ -98,6 +108,12 @@ public class ActFmSyncThread { private boolean isTimeForBackgroundSync = false; + private final Object progressBarLock = new Object(); + + private Activity activity = null; + + private ProgressBar progressBar = null; + public static enum ModelType { TYPE_TASK, TYPE_TAG, @@ -164,6 +180,18 @@ public class ActFmSyncThread { } } + private final Runnable enqueueMessageProgressRunnable = new Runnable() { + @Override + public void run() { + synchronized (progressBarLock) { + if (progressBar != null) { + progressBar.setMax(progressBar.getMax() + 2); + progressBar.setVisibility(View.VISIBLE); + } + } + } + }; + public synchronized void enqueueMessage(ClientToServerMessage message, Runnable callback) { if (!pendingMessages.contains(message)) { pendingMessages.add(message); @@ -172,6 +200,51 @@ public class ActFmSyncThread { synchronized(monitor) { monitor.notifyAll(); } + + if (activity != null) { + activity.runOnUiThread(enqueueMessageProgressRunnable); + } + } + } + + public void setProgressBar(Activity activity, ProgressBar progressBar) { + synchronized(progressBarLock) { + int oldProgress = progressBar.getProgress(); + int oldMax = progressBar.getMax(); + this.activity = activity; + this.progressBar = progressBar; + if (this.activity != null && this.progressBar != null) { + this.progressBar.setMax(oldMax); + this.progressBar.setProgress(oldProgress); + if (oldProgress < oldMax && oldMax != 0) { + this.progressBar.setVisibility(View.VISIBLE); + } else { + this.progressBar.setVisibility(View.GONE); + } + } + } + } + + private final Runnable incrementProgressRunnable = new Runnable() { + @Override + public void run() { + synchronized(progressBarLock) { + if (progressBar != null) { + progressBar.incrementProgressBy(1); + if (progressBar.getProgress() == progressBar.getMax()) { + progressBar.setMax(0); + progressBar.setVisibility(View.GONE); + } + } + } + } + }; + + private void incrementProgress() { + synchronized (progressBarLock) { + if (activity != null) { + activity.runOnUiThread(incrementProgressRunnable); + } } } @@ -224,33 +297,41 @@ public class ActFmSyncThread { ClientToServerMessage message = pendingMessages.remove(0); if (message != null) messageBatch.add(message); + incrementProgress(); } if (!messageBatch.isEmpty() && checkForToken()) { JSONArray payload = new JSONArray(); + MultipartEntity entity = new MultipartEntity(); for (ClientToServerMessage message : messageBatch) { - JSONObject serialized = message.serializeToJSON(); + JSONObject serialized = message.serializeToJSON(entity); if (serialized != null) { payload.put(serialized); syncLog("Sending: " + serialized); + } else { + incrementProgress(); // Don't let failed serialization mess up progress bar } } - if (payload.length() == 0) + if (payload.length() == 0) { + messageBatch.clear(); continue; + } try { - JSONObject response = actFmInvoker.postSync(payload, token); + JSONObject response = actFmInvoker.postSync(payload, entity, token); // process responses + String time = response.optString("time"); JSONArray serverMessagesJson = response.optJSONArray("messages"); if (serverMessagesJson != null) { + setWidgetSuppression(true); for (int i = 0; i < serverMessagesJson.length(); i++) { JSONObject serverMessageJson = serverMessagesJson.optJSONObject(i); if (serverMessageJson != null) { ServerToClientMessage serverMessage = ServerToClientMessage.instantiateMessage(serverMessageJson); if (serverMessage != null) { syncLog("Processing server message of type " + serverMessage.getClass().getSimpleName()); - serverMessage.processMessage(); + serverMessage.processMessage(time); } else { syncLog("Unable to instantiate message " + serverMessageJson.toString()); } @@ -259,6 +340,7 @@ public class ActFmSyncThread { JSONArray errors = response.optJSONArray("errors"); boolean errorsExist = (errors != null && errors.length() > 0); replayOutstandingChanges(errorsExist); + setWidgetSuppression(false); } batchSize = Math.min(batchSize, messageBatch.size()) * 2; @@ -267,29 +349,27 @@ public class ActFmSyncThread { batchSize = Math.max(batchSize / 2, 1); } - boolean didDefaultRefreshThisLoop = false; + Set callbacksExecutedThisLoop = new HashSet(); for (ClientToServerMessage message : messageBatch) { + incrementProgress(); try { Runnable r = pendingCallbacks.remove(message); - if (r != null) { - if (r == DEFAULT_REFRESH_RUNNABLE) { - if (didDefaultRefreshThisLoop) - continue; - didDefaultRefreshThisLoop = true; - } + if (r != null && !callbacksExecutedThisLoop.contains(r)) { r.run(); + callbacksExecutedThisLoop.add(r); } } catch (Exception e) { Log.e(ERROR_TAG, "Unexpected exception executing sync callback", e); } } - messageBatch = new LinkedList>(); + messageBatch.clear(); } } } catch (Exception e) { // In the worst case, restart thread if something goes wrong Log.e(ERROR_TAG, "Unexpected sync thread exception", e); + Crittercism.logHandledException(e); thread = null; startSyncThread(); } @@ -309,6 +389,18 @@ public class ActFmSyncThread { return isTimeForBackgroundSync; } + private void setWidgetSuppression(boolean suppress) { + long date = suppress ? DateUtilities.now() : 0; + TasksWidget.suppressUpdateFlag = date; + + if (date == 0) { + Context context = ContextManager.getContext(); + if (context != null) { + TasksWidget.updateWidgets(context); + } + } + } + public void repopulateQueueFromOutstandingTables() { syncLog("Constructing queue from outstanding tables"); //$NON-NLS-1$ constructChangesHappenedFromOutstandingTable(Task.class, taskDao, taskOutstandingDao); diff --git a/astrid/plugin-src/com/todoroo/astrid/actfm/sync/ActFmSyncV2Provider.java b/astrid/plugin-src/com/todoroo/astrid/actfm/sync/ActFmSyncV2Provider.java index 1bf3e07e6..8f38be6ac 100644 --- a/astrid/plugin-src/com/todoroo/astrid/actfm/sync/ActFmSyncV2Provider.java +++ b/astrid/plugin-src/com/todoroo/astrid/actfm/sync/ActFmSyncV2Provider.java @@ -6,9 +6,7 @@ package com.todoroo.astrid.actfm.sync; import java.io.IOException; -import java.util.concurrent.atomic.AtomicInteger; -import org.json.JSONException; import org.json.JSONObject; import com.timsu.astrid.GCMIntentService; @@ -73,8 +71,6 @@ public class ActFmSyncV2Provider extends SyncV2Provider { return actFmPreferenceService.isLoggedIn(); } - private static final String LAST_FEATURED_TAG_FETCH_TIME = "actfm_last_featuredTag"; //$NON-NLS-1$ - // --- synchronize active tasks @Override @@ -83,18 +79,10 @@ public class ActFmSyncV2Provider extends SyncV2Provider { new Thread(new Runnable() { public void run() { - callback.started(); - callback.incrementMax(160); - - final AtomicInteger finisher = new AtomicInteger(1); updateUserStatus(); ActFmSyncThread.getInstance().setTimeForBackgroundSync(true); - - startFeaturedListFetcher(callback, finisher); - - callback.incrementProgress(50); } }).start(); } @@ -133,32 +121,6 @@ public class ActFmSyncV2Provider extends SyncV2Provider { } } - /** fetch changes to tags */ - private void startFeaturedListFetcher(final SyncResultCallback callback, - final AtomicInteger finisher) { - new Thread(new Runnable() { - @Override - public void run() { - int time = Preferences.getInt(LAST_FEATURED_TAG_FETCH_TIME, 0); - try { - if (Preferences.getBoolean(R.string.p_show_featured_lists, false)) { - time = actFmSyncService.fetchFeaturedLists(time); - Preferences.setInt(LAST_FEATURED_TAG_FETCH_TIME, time); - } - } catch (JSONException e) { - handler.handleException("actfm-sync", e, e.toString()); //$NON-NLS-1$ - } catch (IOException e) { - handler.handleException("actfm-sync", e, e.toString()); //$NON-NLS-1$ - } finally { - callback.incrementProgress(20); - if(finisher.decrementAndGet() == 0) { - finishSync(callback); - } - } - } - }).start(); - } - // --- synchronize list @Override public void synchronizeList(Object list, final boolean manual, diff --git a/astrid/plugin-src/com/todoroo/astrid/actfm/sync/AstridNewSyncMigrator.java b/astrid/plugin-src/com/todoroo/astrid/actfm/sync/AstridNewSyncMigrator.java index aa8dc1c65..8fe916556 100644 --- a/astrid/plugin-src/com/todoroo/astrid/actfm/sync/AstridNewSyncMigrator.java +++ b/astrid/plugin-src/com/todoroo/astrid/actfm/sync/AstridNewSyncMigrator.java @@ -292,6 +292,8 @@ public class AstridNewSyncMigrator { attachment.putTransitory(SyncFlags.ACTFM_SUPPRESS_OUTSTANDING_ENTRIES, true); } + if (!ActFmPreferenceService.isPremiumUser()) + attachment.putTransitory(SyncFlags.ACTFM_SUPPRESS_OUTSTANDING_ENTRIES, true); taskAttachmentDao.createNew(attachment); } @@ -315,9 +317,7 @@ public class AstridNewSyncMigrator { TaskListMetadata tlm = new TaskListMetadata(); tlm.setValue(TaskListMetadata.FILTER, TaskListMetadata.FILTER_ID_ALL); tlm.setValue(TaskListMetadata.TASK_IDS, activeTasksOrder); - tlm.putTransitory(SyncFlags.ACTFM_SUPPRESS_OUTSTANDING_ENTRIES, true); if (taskListMetadataDao.update(TaskListMetadata.FILTER.eq(TaskListMetadata.FILTER_ID_ALL), tlm) <= 0) { - tlm.putTransitory(SyncFlags.ACTFM_SUPPRESS_OUTSTANDING_ENTRIES, true); taskListMetadataDao.createNew(tlm); } @@ -330,9 +330,7 @@ public class AstridNewSyncMigrator { tlm.setValue(TaskListMetadata.FILTER, TaskListMetadata.FILTER_ID_TODAY); tlm.setValue(TaskListMetadata.TASK_IDS, todayTasksOrder); - tlm.putTransitory(SyncFlags.ACTFM_SUPPRESS_OUTSTANDING_ENTRIES, true); if (taskListMetadataDao.update(TaskListMetadata.FILTER.eq(TaskListMetadata.FILTER_ID_TODAY), tlm) <= 0) { - tlm.putTransitory(SyncFlags.ACTFM_SUPPRESS_OUTSTANDING_ENTRIES, true); taskListMetadataDao.createNew(tlm); } @@ -404,6 +402,16 @@ public class AstridNewSyncMigrator { Log.e(LOG_TAG, "Error validating task to tag metadata", e); } + // -------------- + // Delete all featured list data + // -------------- + try { + tagDataDao.deleteWhere(Functions.bitwiseAnd(TagData.FLAGS, TagData.FLAG_FEATURED).gt(0)); + } catch (Exception e) { + Log.e(LOG_TAG, "Error deleting featured list data", e); + } + + // -------------- // Finally, create oustanding entries for tags on unsynced tasks // -------------- diff --git a/astrid/plugin-src/com/todoroo/astrid/actfm/sync/SyncDatabaseListener.java b/astrid/plugin-src/com/todoroo/astrid/actfm/sync/SyncDatabaseListener.java index 8b38b16b1..cd3339f5f 100644 --- a/astrid/plugin-src/com/todoroo/astrid/actfm/sync/SyncDatabaseListener.java +++ b/astrid/plugin-src/com/todoroo/astrid/actfm/sync/SyncDatabaseListener.java @@ -9,7 +9,7 @@ import com.todoroo.astrid.actfm.sync.messages.ClientToServerMessage; public class SyncDatabaseListener implements ModelUpdateListener { private final ModelType modelType; - private final ActFmSyncThread actFmSyncThread; + protected final ActFmSyncThread actFmSyncThread; public SyncDatabaseListener(ActFmSyncThread actFmSyncThread, ModelType modelType) { this.actFmSyncThread = actFmSyncThread; this.modelType = modelType; diff --git a/astrid/plugin-src/com/todoroo/astrid/actfm/sync/SyncUpgradePrompt.java b/astrid/plugin-src/com/todoroo/astrid/actfm/sync/SyncUpgradePrompt.java new file mode 100644 index 000000000..dd431d807 --- /dev/null +++ b/astrid/plugin-src/com/todoroo/astrid/actfm/sync/SyncUpgradePrompt.java @@ -0,0 +1,133 @@ +package com.todoroo.astrid.actfm.sync; + +import android.app.Activity; +import android.app.Dialog; +import android.content.Intent; +import android.util.TypedValue; +import android.view.View; +import android.view.View.OnClickListener; +import android.widget.Button; +import android.widget.TextView; +import android.widget.Toast; + +import com.timsu.astrid.R; +import com.todoroo.andlib.utility.DateUtilities; +import com.todoroo.andlib.utility.Preferences; +import com.todoroo.astrid.actfm.ActFmLoginActivity; +import com.todoroo.astrid.core.PluginServices; + +public class SyncUpgradePrompt { + + private static final String P_SYNC_UPGRADE_PROMPT = "p_sync_upgr_prompt"; //$NON-NLS-1$ + private static long lastPromptDate = -1; + + public static void showSyncUpgradePrompt(final Activity activity) { + if (lastPromptDate == -1) + lastPromptDate = Preferences.getLong(P_SYNC_UPGRADE_PROMPT, 0L); + + Dialog d = null; + if (DateUtilities.now() - lastPromptDate > DateUtilities.ONE_WEEK * 3) { + if (!PluginServices.getActFmPreferenceService().isLoggedIn()) { + if (PluginServices.getGtasksPreferenceService().isLoggedIn()) { + // Logged into google but not astrid + d = getDialog(activity, R.string.sync_upgr_gtasks_only_title, R.string.sync_upgr_gtasks_only_body, + R.string.sync_upgr_gtasks_only_btn1, new Runnable() { + @Override + public void run() { + activity.startActivity(new Intent(activity, ActFmLoginActivity.class)); + } + }, + R.string.sync_upgr_gtasks_only_btn2, + null); + } else { + // Logged into neither + d = getDialog(activity, R.string.sync_upgr_neither_title, R.string.sync_upgr_neither_body, + R.string.sync_upgr_neither_btn1, new Runnable() { + @Override + public void run() { + activity.startActivity(new Intent(activity, ActFmLoginActivity.class)); + } + }); + } + setLastPromptDate(DateUtilities.now()); + } else if (PluginServices.getGtasksPreferenceService().isLoggedIn()) { + // Logged into both + d = getDialog(activity, R.string.sync_upgr_both_title, R.string.sync_upgr_both_body, + R.string.sync_upgr_both_btn1, + null, + R.string.sync_upgr_both_btn2, new Runnable() { + @Override + public void run() { + new ActFmSyncV2Provider().signOut(); + Toast.makeText(activity, R.string.sync_upgr_logged_out, Toast.LENGTH_LONG).show(); + } + }); + setLastPromptDate(Long.MAX_VALUE); + } else { + // Logged into just astrid--don't need to show prompts anymore + setLastPromptDate(Long.MAX_VALUE); + } + } + + if (d != null) + d.show(); + } + + private static void setLastPromptDate(long date) { + lastPromptDate = date; + Preferences.setLong(P_SYNC_UPGRADE_PROMPT, lastPromptDate); + } + + private static Dialog getDialog(Activity activity, int title, int body, Object... buttonsAndListeners) { + final Dialog d = new Dialog(activity, R.style.ReminderDialog); + d.setContentView(R.layout.astrid_reminder_view_portrait); + ((TextView) d.findViewById(R.id.reminder_title)).setText(title); + ((TextView) d.findViewById(R.id.reminder_message)).setText(body); + + d.findViewById(R.id.dismiss).setOnClickListener(new OnClickListener() { + @Override + public void onClick(View v) { + d.dismiss(); + } + }); + + d.findViewById(R.id.reminder_complete).setVisibility(View.GONE); + TypedValue tv = new TypedValue(); + activity.getTheme().resolveAttribute(R.attr.asThemeTextColor, tv, false); + + int button1 = (Integer) buttonsAndListeners[0]; + final Runnable listener1 = (Runnable) buttonsAndListeners[1]; + Button b1 = (Button) d.findViewById(R.id.reminder_edit); + b1.setText(button1); + b1.setOnClickListener(new OnClickListener() { + @Override + public void onClick(View v) { + d.dismiss(); + if (listener1 != null) + listener1.run(); + } + }); + b1.setBackgroundColor(activity.getResources().getColor(tv.data)); + + if (buttonsAndListeners.length < 3) { + d.findViewById(R.id.reminder_snooze).setVisibility(View.GONE); + } else { + int button2 = (Integer) buttonsAndListeners[2]; + final Runnable listener2 = (Runnable) buttonsAndListeners[3]; + Button b2 = (Button) d.findViewById(R.id.reminder_snooze); + b2.setText(button2); + b2.setOnClickListener(new OnClickListener() { + @Override + public void onClick(View v) { + d.dismiss(); + if (listener2 != null) + listener2.run(); + } + }); + b2.setBackgroundColor(activity.getResources().getColor(tv.data)); + } + + return d; + } + +} diff --git a/astrid/plugin-src/com/todoroo/astrid/actfm/sync/TaskListMetadataSyncDatabaseListener.java b/astrid/plugin-src/com/todoroo/astrid/actfm/sync/TaskListMetadataSyncDatabaseListener.java index 151835580..ae7015fb8 100644 --- a/astrid/plugin-src/com/todoroo/astrid/actfm/sync/TaskListMetadataSyncDatabaseListener.java +++ b/astrid/plugin-src/com/todoroo/astrid/actfm/sync/TaskListMetadataSyncDatabaseListener.java @@ -18,7 +18,7 @@ public class TaskListMetadataSyncDatabaseListener extends SyncDatabaseListener idsList = new ArrayList(); diff --git a/astrid/plugin-src/com/todoroo/astrid/actfm/sync/messages/BriefMe.java b/astrid/plugin-src/com/todoroo/astrid/actfm/sync/messages/BriefMe.java index 4cfb89933..7c74ee98f 100644 --- a/astrid/plugin-src/com/todoroo/astrid/actfm/sync/messages/BriefMe.java +++ b/astrid/plugin-src/com/todoroo/astrid/actfm/sync/messages/BriefMe.java @@ -1,5 +1,6 @@ package com.todoroo.astrid.actfm.sync.messages; +import org.apache.http.entity.mime.MultipartEntity; import org.json.JSONException; import org.json.JSONObject; @@ -34,7 +35,7 @@ public class BriefMe extends ClientToServerMessage 0) { for (int i = 0; i < extraParameters.length - 1; i++) { try { diff --git a/astrid/plugin-src/com/todoroo/astrid/actfm/sync/messages/ChangesHappened.java b/astrid/plugin-src/com/todoroo/astrid/actfm/sync/messages/ChangesHappened.java index e0c969862..12cd11537 100644 --- a/astrid/plugin-src/com/todoroo/astrid/actfm/sync/messages/ChangesHappened.java +++ b/astrid/plugin-src/com/todoroo/astrid/actfm/sync/messages/ChangesHappened.java @@ -3,7 +3,10 @@ package com.todoroo.astrid.actfm.sync.messages; import java.io.File; import java.util.ArrayList; import java.util.List; +import java.util.concurrent.atomic.AtomicInteger; +import org.apache.http.entity.mime.MultipartEntity; +import org.apache.http.entity.mime.content.FileBody; import org.json.JSONArray; import org.json.JSONException; import org.json.JSONObject; @@ -11,12 +14,12 @@ import org.json.JSONObject; import android.text.TextUtils; import android.util.Log; +import com.crittercism.app.Crittercism; import com.todoroo.andlib.data.Property; import com.todoroo.andlib.data.Property.PropertyVisitor; import com.todoroo.andlib.data.TodorooCursor; import com.todoroo.andlib.sql.Order; import com.todoroo.andlib.sql.Query; -import com.todoroo.andlib.utility.AndroidUtilities; import com.todoroo.andlib.utility.DateUtilities; import com.todoroo.astrid.actfm.sync.ActFmPreferenceService; import com.todoroo.astrid.actfm.sync.ActFmSyncThread.ModelType; @@ -77,12 +80,14 @@ public class ChangesHappened(); + if (!foundEntity) // Stop sending changes for entities that don't exist anymore + outstandingDao.deleteWhere(OutstandingEntry.ENTITY_ID_PROPERTY.eq(id)); } @Override - protected boolean serializeExtrasToJSON(JSONObject serializeTo) throws JSONException { + protected boolean serializeExtrasToJSON(JSONObject serializeTo, MultipartEntity entity) throws JSONException { // Process changes list and serialize to JSON - JSONArray changesJson = changesToJSON(); + JSONArray changesJson = changesToJSON(entity); if (changesJson == null || changesJson.length() == 0) return false; serializeTo.put(CHANGES_KEY, changesJson); @@ -98,11 +103,12 @@ public class ChangesHappened localProperty = NameMaps.localColumnNameToProperty(table, localColumn); @@ -140,7 +149,22 @@ public class ChangesHappened cursor = outstandingDao.query(Query.select(DaoReflectionHelpers.getModelProperties(outstandingClass)) .where(OutstandingEntry.ENTITY_ID_PROPERTY.eq(id)).orderBy(Order.asc(OutstandingEntry.CREATED_AT_PROPERTY))); @@ -176,6 +216,16 @@ public class ChangesHappened property, Object value) { + if (Task.TITLE.equals(property)) { + if (!(value instanceof String) || TextUtils.isEmpty((String) value)) + return false; + } + return true; + } + private JSONObject getFileJson(String value) { try { JSONObject obj = new JSONObject(value); @@ -186,12 +236,6 @@ public class ChangesHappened { protected final long id; protected final String uuid; protected final long pushedAt; + protected final boolean foundEntity; public static final String TYPE_KEY = "type"; public static final String TABLE_KEY = "table"; @@ -30,6 +33,7 @@ public abstract class ClientToServerMessage { this.table = NameMaps.getServerNameForTable(tableClass); this.uuid = uuid; this.pushedAt = pushedAt; + this.foundEntity = true; this.id = AbstractModel.NO_ID; } @@ -40,6 +44,7 @@ public abstract class ClientToServerMessage { this.table = NameMaps.getServerNameForTable(tableClass); TYPE entity = getEntity(id, modelDao); + this.foundEntity = entity != null; if (entity == null) { this.uuid = RemoteModel.NO_UUID; this.pushedAt = 0; @@ -61,7 +66,7 @@ public abstract class ClientToServerMessage { return pushedAt; } - public final JSONObject serializeToJSON() { + public final JSONObject serializeToJSON(MultipartEntity entity) { JSONObject json = new JSONObject(); try { json.put(TYPE_KEY, getTypeString()); @@ -69,11 +74,12 @@ public abstract class ClientToServerMessage { json.put(UUID_KEY, uuid); String dateValue = DateUtilities.timeToIso8601(pushedAt, true); json.put(PUSHED_AT_KEY, dateValue != null ? dateValue : 0); - if (serializeExtrasToJSON(json)) + if (serializeExtrasToJSON(json, entity)) return json; else return null; } catch (JSONException e) { + Crittercism.logHandledException(e); return null; } } @@ -109,6 +115,6 @@ public abstract class ClientToServerMessage { return true; } - protected abstract boolean serializeExtrasToJSON(JSONObject serializeTo) throws JSONException; + protected abstract boolean serializeExtrasToJSON(JSONObject serializeTo, MultipartEntity entity) throws JSONException; protected abstract String getTypeString(); } diff --git a/astrid/plugin-src/com/todoroo/astrid/actfm/sync/messages/ConstructOutstandingTableFromMasterTable.java b/astrid/plugin-src/com/todoroo/astrid/actfm/sync/messages/ConstructOutstandingTableFromMasterTable.java index fcdf9f2ea..e6567a466 100644 --- a/astrid/plugin-src/com/todoroo/astrid/actfm/sync/messages/ConstructOutstandingTableFromMasterTable.java +++ b/astrid/plugin-src/com/todoroo/astrid/actfm/sync/messages/ConstructOutstandingTableFromMasterTable.java @@ -17,10 +17,10 @@ import com.todoroo.astrid.data.RemoteModel; @SuppressWarnings("nls") public class ConstructOutstandingTableFromMasterTable> { - private final String table; - private final RemoteModelDao dao; - private final OutstandingEntryDao outstandingDao; - private final LongProperty createdAtProperty; + protected final String table; + protected final RemoteModelDao dao; + protected final OutstandingEntryDao outstandingDao; + protected final LongProperty createdAtProperty; public ConstructOutstandingTableFromMasterTable(String table, RemoteModelDao dao, OutstandingEntryDao outstandingDao, LongProperty createdAtProperty) { @@ -30,24 +30,31 @@ public class ConstructOutstandingTableFromMasterTable[] syncableProperties = NameMaps.syncableProperties(table); TodorooCursor items = dao.query(Query.select(AndroidUtilities.addToArray(syncableProperties, AbstractModel.ID_PROPERTY, RemoteModel.UUID_PROPERTY))); try { OE oe = outstandingDao.getModelClass().newInstance(); for (items.moveToFirst(); !items.isAfterLast(); items.moveToNext()) { + long createdAt; + if (createdAtProperty != null) + createdAt = items.get(createdAtProperty); + else + createdAt = DateUtilities.now(); long itemId = items.get(AbstractModel.ID_PROPERTY); for (Property p : syncableProperties) { oe.clear(); oe.setValue(OutstandingEntry.ENTITY_ID_PROPERTY, itemId); oe.setValue(OutstandingEntry.COLUMN_STRING_PROPERTY, p.name); oe.setValue(OutstandingEntry.VALUE_STRING_PROPERTY, items.get(p).toString()); - if (createdAtProperty != null) - oe.setValue(OutstandingEntry.CREATED_AT_PROPERTY, items.get(createdAtProperty)); - else - oe.setValue(OutstandingEntry.CREATED_AT_PROPERTY, DateUtilities.now()); + oe.setValue(OutstandingEntry.CREATED_AT_PROPERTY, createdAt); outstandingDao.createNew(oe); } + extras(itemId, createdAt); } } catch (IllegalAccessException e) { Log.e("ConstructOutstanding", "Error instantiating outstanding model class", e); diff --git a/astrid/plugin-src/com/todoroo/astrid/actfm/sync/messages/ConstructTaskOutstandingTableFromMasterTable.java b/astrid/plugin-src/com/todoroo/astrid/actfm/sync/messages/ConstructTaskOutstandingTableFromMasterTable.java new file mode 100644 index 000000000..9bf3db85b --- /dev/null +++ b/astrid/plugin-src/com/todoroo/astrid/actfm/sync/messages/ConstructTaskOutstandingTableFromMasterTable.java @@ -0,0 +1,50 @@ +package com.todoroo.astrid.actfm.sync.messages; + +import com.todoroo.andlib.data.Property.LongProperty; +import com.todoroo.andlib.data.TodorooCursor; +import com.todoroo.andlib.sql.Criterion; +import com.todoroo.andlib.sql.Query; +import com.todoroo.astrid.dao.MetadataDao; +import com.todoroo.astrid.dao.MetadataDao.MetadataCriteria; +import com.todoroo.astrid.dao.OutstandingEntryDao; +import com.todoroo.astrid.dao.RemoteModelDao; +import com.todoroo.astrid.data.Metadata; +import com.todoroo.astrid.data.Task; +import com.todoroo.astrid.data.TaskOutstanding; +import com.todoroo.astrid.tags.TaskToTagMetadata; + +public class ConstructTaskOutstandingTableFromMasterTable extends ConstructOutstandingTableFromMasterTable { + + private final MetadataDao metadataDao; + + public ConstructTaskOutstandingTableFromMasterTable(String table, RemoteModelDao dao, OutstandingEntryDao outstandingDao, MetadataDao metadataDao, LongProperty createdAtProperty) { + super(table, dao, outstandingDao, createdAtProperty); + this.metadataDao = metadataDao; + } + + @Override + protected void extras(long itemId, long createdAt) { + super.extras(itemId, createdAt); + TodorooCursor tagMetadata = metadataDao.query(Query.select(Metadata.PROPERTIES) + .where(Criterion.and(MetadataCriteria.byTaskAndwithKey(itemId, TaskToTagMetadata.KEY), Metadata.DELETION_DATE.eq(0)))); + Metadata m = new Metadata(); + try { + for (tagMetadata.moveToFirst(); !tagMetadata.isAfterLast(); tagMetadata.moveToNext()) { + m.clear(); + m.readFromCursor(tagMetadata); + + if (m.containsNonNullValue(TaskToTagMetadata.TAG_UUID)) { + TaskOutstanding oe = new TaskOutstanding(); + oe.setValue(TaskOutstanding.ENTITY_ID_PROPERTY, itemId); + oe.setValue(TaskOutstanding.COLUMN_STRING, NameMaps.TAG_ADDED_COLUMN); + oe.setValue(TaskOutstanding.VALUE_STRING, m.getValue(TaskToTagMetadata.TAG_UUID)); + oe.setValue(TaskOutstanding.CREATED_AT, createdAt); + outstandingDao.createNew(oe); + } + } + } finally { + tagMetadata.close(); + } + } + +} diff --git a/astrid/plugin-src/com/todoroo/astrid/actfm/sync/messages/Debug.java b/astrid/plugin-src/com/todoroo/astrid/actfm/sync/messages/Debug.java index bb8e20c00..f8bbc667f 100644 --- a/astrid/plugin-src/com/todoroo/astrid/actfm/sync/messages/Debug.java +++ b/astrid/plugin-src/com/todoroo/astrid/actfm/sync/messages/Debug.java @@ -13,7 +13,7 @@ public class Debug extends ServerToClientMessage { @Override @SuppressWarnings("nls") - public void processMessage() { + public void processMessage(String serverTime) { String message = json.optString("message"); if (!TextUtils.isEmpty(message)) Log.w("actfm-debug-message", message); diff --git a/astrid/plugin-src/com/todoroo/astrid/actfm/sync/messages/DoubleCheck.java b/astrid/plugin-src/com/todoroo/astrid/actfm/sync/messages/DoubleCheck.java index e5b0d7fdf..345c4a88e 100644 --- a/astrid/plugin-src/com/todoroo/astrid/actfm/sync/messages/DoubleCheck.java +++ b/astrid/plugin-src/com/todoroo/astrid/actfm/sync/messages/DoubleCheck.java @@ -9,7 +9,7 @@ public class DoubleCheck extends ServerToClientMessage { } @Override - public void processMessage() { + public void processMessage(String serverTime) { // TODO Auto-generated method stub } diff --git a/astrid/plugin-src/com/todoroo/astrid/actfm/sync/messages/MakeChanges.java b/astrid/plugin-src/com/todoroo/astrid/actfm/sync/messages/MakeChanges.java index 5b918af2c..a5648612f 100644 --- a/astrid/plugin-src/com/todoroo/astrid/actfm/sync/messages/MakeChanges.java +++ b/astrid/plugin-src/com/todoroo/astrid/actfm/sync/messages/MakeChanges.java @@ -1,5 +1,6 @@ package com.todoroo.astrid.actfm.sync.messages; +import java.text.ParseException; import java.util.ArrayList; import java.util.Iterator; @@ -20,13 +21,13 @@ import com.todoroo.andlib.utility.Preferences; import com.todoroo.astrid.actfm.sync.ActFmInvoker; import com.todoroo.astrid.core.PluginServices; import com.todoroo.astrid.dao.HistoryDao; +import com.todoroo.astrid.dao.MetadataDao.MetadataCriteria; import com.todoroo.astrid.dao.RemoteModelDao; import com.todoroo.astrid.dao.TagMetadataDao; import com.todoroo.astrid.dao.TaskListMetadataDao; import com.todoroo.astrid.dao.UserActivityDao; import com.todoroo.astrid.data.History; import com.todoroo.astrid.data.Metadata; -import com.todoroo.astrid.data.MetadataApiDao.MetadataCriteria; import com.todoroo.astrid.data.RemoteModel; import com.todoroo.astrid.data.SyncFlags; import com.todoroo.astrid.data.TagData; @@ -65,7 +66,7 @@ public class MakeChanges extends ServerToClientMessage return model; } - private static void saveOrUpdateModelAfterChanges(RemoteModelDao dao, T model, String oldUuid, String uuid, Criterion orCriterion) { + private static void saveOrUpdateModelAfterChanges(RemoteModelDao dao, T model, String oldUuid, String uuid, String serverTime, Criterion orCriterion) { Criterion uuidCriterion; if (oldUuid == null) uuidCriterion = RemoteModel.UUID_PROPERTY.eq(uuid); @@ -76,6 +77,16 @@ public class MakeChanges extends ServerToClientMessage uuidCriterion = Criterion.or(uuidCriterion, orCriterion); if (model.getSetValues() != null && model.getSetValues().size() > 0) { + long pushedAt; + try { + pushedAt = DateUtilities.parseIso8601(serverTime); + } catch (ParseException e) { + pushedAt = 0; + } + + if (pushedAt > 0) + model.setValue(RemoteModel.PUSHED_AT_PROPERTY, pushedAt); + model.putTransitory(SyncFlags.ACTFM_SUPPRESS_OUTSTANDING_ENTRIES, true); if (dao.update(uuidCriterion, model) <= 0) { // If update doesn't update rows. create a new model model.putTransitory(SyncFlags.ACTFM_SUPPRESS_OUTSTANDING_ENTRIES, true); @@ -85,7 +96,7 @@ public class MakeChanges extends ServerToClientMessage } @Override - public void processMessage() { + public void processMessage(String serverTime) { JSONObject changes = json.optJSONObject("changes"); String uuid = json.optString("uuid"); if (changes != null && !TextUtils.isEmpty(uuid)) { @@ -105,7 +116,7 @@ public class MakeChanges extends ServerToClientMessage if (model.getSetValues() != null && !model.getSetValues().containsKey(uuidProperty.name)) model.setValue(uuidProperty, uuid); - saveOrUpdateModelAfterChanges(dao, model, oldUuid, uuid, getMatchCriterion(model)); + saveOrUpdateModelAfterChanges(dao, model, oldUuid, uuid, serverTime, getMatchCriterion(model)); afterSaveChanges(changes, model, uuid, oldUuid); } catch (IllegalAccessException e) { diff --git a/astrid/plugin-src/com/todoroo/astrid/actfm/sync/messages/NameMaps.java b/astrid/plugin-src/com/todoroo/astrid/actfm/sync/messages/NameMaps.java index 37db77f67..fc0219665 100644 --- a/astrid/plugin-src/com/todoroo/astrid/actfm/sync/messages/NameMaps.java +++ b/astrid/plugin-src/com/todoroo/astrid/actfm/sync/messages/NameMaps.java @@ -127,7 +127,7 @@ public class NameMaps { putTaskPropertyToServerName(Task.TITLE, "title", true); putTaskPropertyToServerName(Task.IMPORTANCE, "importance", true); putTaskPropertyToServerName(Task.DUE_DATE, "due", true); - putTaskPropertyToServerName(Task.HIDE_UNTIL, "hide_until", false); + putTaskPropertyToServerName(Task.HIDE_UNTIL, "hide_until", true); putTaskPropertyToServerName(Task.CREATION_DATE, "created_at", true); putTaskPropertyToServerName(Task.COMPLETION_DATE, "completed_at", true); putTaskPropertyToServerName(Task.RECURRENCE, "repeat", true); diff --git a/astrid/plugin-src/com/todoroo/astrid/actfm/sync/messages/NowBriefed.java b/astrid/plugin-src/com/todoroo/astrid/actfm/sync/messages/NowBriefed.java index 81a827694..f06bd16c9 100644 --- a/astrid/plugin-src/com/todoroo/astrid/actfm/sync/messages/NowBriefed.java +++ b/astrid/plugin-src/com/todoroo/astrid/actfm/sync/messages/NowBriefed.java @@ -42,7 +42,7 @@ public class NowBriefed extends ServerToClientMessage } @Override - public void processMessage() { + public void processMessage(String serverTime) { if (pushedAt > 0) { if (TextUtils.isEmpty(uuid)) { if (!TextUtils.isEmpty(taskId)) { diff --git a/astrid/plugin-src/com/todoroo/astrid/actfm/sync/messages/RequestDoubleCheck.java b/astrid/plugin-src/com/todoroo/astrid/actfm/sync/messages/RequestDoubleCheck.java index 0f70b0da8..5f405f783 100644 --- a/astrid/plugin-src/com/todoroo/astrid/actfm/sync/messages/RequestDoubleCheck.java +++ b/astrid/plugin-src/com/todoroo/astrid/actfm/sync/messages/RequestDoubleCheck.java @@ -1,5 +1,6 @@ package com.todoroo.astrid.actfm.sync.messages; +import org.apache.http.entity.mime.MultipartEntity; import org.json.JSONException; import org.json.JSONObject; @@ -13,7 +14,7 @@ public class RequestDoubleCheck extends ClientToServer } @Override - protected boolean serializeExtrasToJSON(JSONObject serializeTo) throws JSONException { + protected boolean serializeExtrasToJSON(JSONObject serializeTo, MultipartEntity entity) throws JSONException { // No extras return true; } diff --git a/astrid/plugin-src/com/todoroo/astrid/actfm/sync/messages/ServerToClientMessage.java b/astrid/plugin-src/com/todoroo/astrid/actfm/sync/messages/ServerToClientMessage.java index eeaac82d0..d4a51febb 100644 --- a/astrid/plugin-src/com/todoroo/astrid/actfm/sync/messages/ServerToClientMessage.java +++ b/astrid/plugin-src/com/todoroo/astrid/actfm/sync/messages/ServerToClientMessage.java @@ -13,7 +13,7 @@ import com.todoroo.astrid.data.UserActivity; @SuppressWarnings("nls") public abstract class ServerToClientMessage { - public abstract void processMessage(); + public abstract void processMessage(String serverTime); public static final String TYPE_MAKE_CHANGES = "MakeChanges"; public static final String TYPE_NOW_BRIEFED = "NowBriefed"; diff --git a/astrid/plugin-src/com/todoroo/astrid/actfm/sync/messages/TaskListMetadataChangesHappened.java b/astrid/plugin-src/com/todoroo/astrid/actfm/sync/messages/TaskListMetadataChangesHappened.java index cfe419484..78c47fb31 100644 --- a/astrid/plugin-src/com/todoroo/astrid/actfm/sync/messages/TaskListMetadataChangesHappened.java +++ b/astrid/plugin-src/com/todoroo/astrid/actfm/sync/messages/TaskListMetadataChangesHappened.java @@ -11,11 +11,8 @@ import com.todoroo.astrid.data.TaskListMetadataOutstanding; public class TaskListMetadataChangesHappened extends ChangesHappened { - private final Throwable throwable; - public TaskListMetadataChangesHappened(long id, Class modelClass, TaskListMetadataDao modelDao, TaskListMetadataOutstandingDao outstandingDao) { super(id, modelClass, modelDao, outstandingDao); - throwable = new Throwable(); } @Override diff --git a/astrid/plugin-src/com/todoroo/astrid/actfm/sync/messages/UserData.java b/astrid/plugin-src/com/todoroo/astrid/actfm/sync/messages/UserData.java index f2798fe5e..2f6387830 100644 --- a/astrid/plugin-src/com/todoroo/astrid/actfm/sync/messages/UserData.java +++ b/astrid/plugin-src/com/todoroo/astrid/actfm/sync/messages/UserData.java @@ -20,7 +20,7 @@ public class UserData extends ServerToClientMessage { @Override @SuppressWarnings("nls") - public void processMessage() { + public void processMessage(String serverTime) { String uuid = json.optString("uuid"); String email = json.optString("email"); diff --git a/astrid/plugin-src/com/todoroo/astrid/core/CoreFilterExposer.java b/astrid/plugin-src/com/todoroo/astrid/core/CoreFilterExposer.java index 3daec6238..bc15f2349 100644 --- a/astrid/plugin-src/com/todoroo/astrid/core/CoreFilterExposer.java +++ b/astrid/plugin-src/com/todoroo/astrid/core/CoreFilterExposer.java @@ -22,9 +22,9 @@ import com.todoroo.astrid.api.AstridFilterExposer; import com.todoroo.astrid.api.Filter; import com.todoroo.astrid.api.FilterListItem; import com.todoroo.astrid.dao.MetadataDao.MetadataCriteria; +import com.todoroo.astrid.dao.TaskDao.TaskCriteria; import com.todoroo.astrid.data.Metadata; import com.todoroo.astrid.data.Task; -import com.todoroo.astrid.data.TaskApiDao.TaskCriteria; import com.todoroo.astrid.service.ThemeService; import com.todoroo.astrid.tags.TaskToTagMetadata; diff --git a/astrid/plugin-src/com/todoroo/astrid/core/CustomFilterActivity.java b/astrid/plugin-src/com/todoroo/astrid/core/CustomFilterActivity.java index 7b37dad4a..42bbecc17 100644 --- a/astrid/plugin-src/com/todoroo/astrid/core/CustomFilterActivity.java +++ b/astrid/plugin-src/com/todoroo/astrid/core/CustomFilterActivity.java @@ -54,8 +54,8 @@ import com.todoroo.astrid.api.MultipleSelectCriterion; import com.todoroo.astrid.api.PermaSql; import com.todoroo.astrid.api.TextInputCriterion; import com.todoroo.astrid.dao.Database; +import com.todoroo.astrid.dao.TaskDao.TaskCriteria; import com.todoroo.astrid.data.Task; -import com.todoroo.astrid.data.TaskApiDao.TaskCriteria; import com.todoroo.astrid.service.StatisticsService; import com.todoroo.astrid.service.ThemeService; import com.todoroo.astrid.utility.AstridPreferences; diff --git a/astrid/plugin-src/com/todoroo/astrid/core/CustomFilterExposer.java b/astrid/plugin-src/com/todoroo/astrid/core/CustomFilterExposer.java index 1743059bf..3d41678d0 100644 --- a/astrid/plugin-src/com/todoroo/astrid/core/CustomFilterExposer.java +++ b/astrid/plugin-src/com/todoroo/astrid/core/CustomFilterExposer.java @@ -38,10 +38,10 @@ import com.todoroo.astrid.api.Filter; import com.todoroo.astrid.api.FilterListItem; import com.todoroo.astrid.api.PermaSql; import com.todoroo.astrid.dao.StoreObjectDao; +import com.todoroo.astrid.dao.TaskDao.TaskCriteria; import com.todoroo.astrid.data.Metadata; import com.todoroo.astrid.data.StoreObject; import com.todoroo.astrid.data.Task; -import com.todoroo.astrid.data.TaskApiDao.TaskCriteria; import com.todoroo.astrid.gtasks.GtasksPreferenceService; import com.todoroo.astrid.service.TagDataService; import com.todoroo.astrid.service.ThemeService; diff --git a/astrid/plugin-src/com/todoroo/astrid/core/PluginServices.java b/astrid/plugin-src/com/todoroo/astrid/core/PluginServices.java index 075786c6f..23896cdb6 100644 --- a/astrid/plugin-src/com/todoroo/astrid/core/PluginServices.java +++ b/astrid/plugin-src/com/todoroo/astrid/core/PluginServices.java @@ -28,6 +28,7 @@ import com.todoroo.astrid.dao.UserActivityDao; import com.todoroo.astrid.dao.UserActivityOutstandingDao; import com.todoroo.astrid.dao.UserDao; import com.todoroo.astrid.data.Metadata; +import com.todoroo.astrid.gtasks.GtasksPreferenceService; import com.todoroo.astrid.service.AddOnService; import com.todoroo.astrid.service.AstridDependencyInjector; import com.todoroo.astrid.service.MetadataService; @@ -105,6 +106,9 @@ public final class PluginServices { @Autowired ActFmPreferenceService actFmPreferenceService; + @Autowired + GtasksPreferenceService gtasksPreferenceService; + private static volatile PluginServices instance; static { @@ -212,6 +216,10 @@ public final class PluginServices { return getInstance().actFmPreferenceService; } + public static GtasksPreferenceService getGtasksPreferenceService() { + return getInstance().gtasksPreferenceService; + } + // -- helpers /** diff --git a/astrid/plugin-src/com/todoroo/astrid/gtasks/GtasksCustomFilterCriteriaExposer.java b/astrid/plugin-src/com/todoroo/astrid/gtasks/GtasksCustomFilterCriteriaExposer.java index 5bb628304..5247e49cf 100644 --- a/astrid/plugin-src/com/todoroo/astrid/gtasks/GtasksCustomFilterCriteriaExposer.java +++ b/astrid/plugin-src/com/todoroo/astrid/gtasks/GtasksCustomFilterCriteriaExposer.java @@ -23,9 +23,9 @@ import com.todoroo.andlib.sql.Query; import com.todoroo.astrid.api.AstridApiConstants; import com.todoroo.astrid.api.CustomFilterCriterion; import com.todoroo.astrid.api.MultipleSelectCriterion; +import com.todoroo.astrid.dao.MetadataDao.MetadataCriteria; import com.todoroo.astrid.dao.TaskDao; import com.todoroo.astrid.data.Metadata; -import com.todoroo.astrid.data.MetadataApiDao.MetadataCriteria; import com.todoroo.astrid.data.StoreObject; import com.todoroo.astrid.data.Task; import com.todoroo.astrid.service.AstridDependencyInjector; diff --git a/astrid/plugin-src/com/todoroo/astrid/gtasks/GtasksFilterExposer.java b/astrid/plugin-src/com/todoroo/astrid/gtasks/GtasksFilterExposer.java index 358a4ad0f..6b58105c2 100644 --- a/astrid/plugin-src/com/todoroo/astrid/gtasks/GtasksFilterExposer.java +++ b/astrid/plugin-src/com/todoroo/astrid/gtasks/GtasksFilterExposer.java @@ -24,6 +24,7 @@ import com.todoroo.andlib.sql.Functions; import com.todoroo.andlib.sql.Join; import com.todoroo.andlib.sql.Order; import com.todoroo.andlib.sql.QueryTemplate; +import com.todoroo.astrid.actfm.sync.ActFmPreferenceService; import com.todoroo.astrid.api.AstridApiConstants; import com.todoroo.astrid.api.AstridFilterExposer; import com.todoroo.astrid.api.Filter; @@ -31,9 +32,9 @@ import com.todoroo.astrid.api.FilterCategoryWithNewButton; import com.todoroo.astrid.api.FilterListItem; import com.todoroo.astrid.api.FilterWithCustomIntent; import com.todoroo.astrid.api.PermaSql; +import com.todoroo.astrid.dao.MetadataDao.MetadataCriteria; import com.todoroo.astrid.dao.TaskDao.TaskCriteria; import com.todoroo.astrid.data.Metadata; -import com.todoroo.astrid.data.MetadataApiDao.MetadataCriteria; import com.todoroo.astrid.data.StoreObject; import com.todoroo.astrid.data.Task; import com.todoroo.astrid.service.AstridDependencyInjector; @@ -48,6 +49,7 @@ public class GtasksFilterExposer extends BroadcastReceiver implements AstridFilt @Autowired private GtasksListService gtasksListService; @Autowired private GtasksPreferenceService gtasksPreferenceService; + @Autowired private ActFmPreferenceService actFmPreferenceService; static { AstridDependencyInjector.initialize(); @@ -92,8 +94,8 @@ public class GtasksFilterExposer extends BroadcastReceiver implements AstridFilt private FilterListItem[] prepareFilters(Context context) { DependencyInjectionService.getInstance().inject(this); - // if we aren't logged in, don't expose features - if(!gtasksPreferenceService.isLoggedIn()) + // if we aren't logged in (or we are logged in to astrid.com), don't expose features + if(!gtasksPreferenceService.isLoggedIn() || actFmPreferenceService.isLoggedIn()) return null; lists = gtasksListService.getLists(); diff --git a/astrid/plugin-src/com/todoroo/astrid/gtasks/GtasksMetadataService.java b/astrid/plugin-src/com/todoroo/astrid/gtasks/GtasksMetadataService.java index 7511a33d1..9e9d0ca5d 100644 --- a/astrid/plugin-src/com/todoroo/astrid/gtasks/GtasksMetadataService.java +++ b/astrid/plugin-src/com/todoroo/astrid/gtasks/GtasksMetadataService.java @@ -30,8 +30,8 @@ import com.todoroo.astrid.data.StoreObject; import com.todoroo.astrid.data.Task; import com.todoroo.astrid.gtasks.sync.GtasksTaskContainer; import com.todoroo.astrid.subtasks.OrderedMetadataListUpdater.OrderedListIterator; -import com.todoroo.astrid.sync.SyncMetadataService; import com.todoroo.astrid.sync.SyncProviderUtilities; +import com.todoroo.astrid.utility.SyncMetadataService; /** * Service for working with GTasks metadata @@ -91,6 +91,7 @@ public final class GtasksMetadataService extends SyncMetadataService importConflicts; + + public GtasksImportCallback(SyncResultCallback wrap) { + super(wrap); + importConflicts = new ArrayList(); + } + + public void addImportConflict(GtasksImportTuple tuple) { + importConflicts.add(tuple); + } } @Override public void synchronizeActiveTasks(final boolean manual, final SyncResultCallback callback) { + // TODO: Improve this logic. Should only be able to import from settings or something. + final boolean isImport = actFmPreferenceService.isLoggedIn(); + if (isImport && !manual) + return; + callback.started(); callback.incrementMax(100); + gtasksPreferenceService.recordSyncStart(); new Thread(new Runnable() { @@ -137,10 +176,13 @@ public class GtasksSyncV2Provider extends SyncV2Provider { new Thread(new Runnable() { @Override public void run() { - synchronizeListHelper(list, invoker, manual, handler, callback); + synchronizeListHelper(list, invoker, manual, handler, callback, isImport); callback.incrementProgress(25); if (finisher.decrementAndGet() == 0) { - pushUpdated(invoker, callback); + if (!isImport) + pushUpdated(invoker, callback); + else + finishImport(callback); finishSync(callback); } } @@ -186,6 +228,8 @@ public class GtasksSyncV2Provider extends SyncV2Provider { if (!GtasksList.TYPE.equals(gtasksList.getValue(StoreObject.TYPE))) return; + final boolean isImport = actFmPreferenceService.isLoggedIn(); + callback.started(); callback.incrementMax(100); @@ -198,7 +242,7 @@ public class GtasksSyncV2Provider extends SyncV2Provider { gtasksSyncService.waitUntilEmpty(); callback.incrementProgress(13); final GtasksInvoker service = new GtasksInvoker(authToken); - synchronizeListHelper(gtasksList, service, manual, null, callback); + synchronizeListHelper(gtasksList, service, manual, null, callback, isImport); } finally { callback.incrementProgress(25); callback.finished(); @@ -221,7 +265,7 @@ public class GtasksSyncV2Provider extends SyncV2Provider { private synchronized void synchronizeListHelper(StoreObject list, GtasksInvoker invoker, - boolean manual, SyncExceptionHandler errorHandler, SyncResultCallback callback) { + boolean manual, SyncExceptionHandler errorHandler, SyncResultCallback callback, boolean isImport) { String listId = list.getValue(GtasksList.REMOTE_ID); long lastSyncDate; if (!manual && list.containsNonNullValue(GtasksList.LAST_SYNC)) { @@ -253,7 +297,7 @@ public class GtasksSyncV2Provider extends SyncV2Provider { list.setValue(GtasksList.LAST_SYNC, DateUtilities.now()); storeObjectDao.persist(list); - if(lastSyncDate == 0) { + if(lastSyncDate == 0 && !isImport) { Long[] localIdArray = localIds.toArray(new Long[localIds.size()]); Criterion delete = Criterion.and(Metadata.KEY.eq(GtasksMetadata.METADATA_KEY), GtasksMetadata.LIST_ID.eq(listId), @@ -315,6 +359,7 @@ public class GtasksSyncV2Provider extends SyncV2Provider { Task local = PluginServices.getTaskService().fetchById(task.task.getId(), Task.DUE_DATE, Task.COMPLETION_DATE); if (local == null) { task.task.clearValue(Task.ID); + task.task.clearValue(Task.UUID); } else { mergeDates(task.task, local); if(task.task.isCompleted() && !local.isCompleted()) @@ -333,13 +378,14 @@ public class GtasksSyncV2Provider extends SyncV2Provider { private void titleMatchWithActFm(Task task) { String title = task.getValue(Task.TITLE); - TodorooCursor match = taskService.query(Query.select(Task.ID) + TodorooCursor match = taskService.query(Query.select(Task.ID, Task.UUID) .join(Join.left(Metadata.TABLE, Criterion.and(Metadata.KEY.eq(GtasksMetadata.METADATA_KEY), Metadata.TASK.eq(Task.ID)))) .where(Criterion.and(Task.TITLE.eq(title), GtasksMetadata.ID.isNull()))); try { if (match.getCount() > 0) { match.moveToFirst(); task.setId(match.get(Task.ID)); + task.setUuid(match.get(Task.UUID)); } } finally { match.close(); @@ -358,4 +404,65 @@ public class GtasksSyncV2Provider extends SyncV2Provider { remote.setValue(Task.DUE_DATE, setDate); } } + + private void finishImport(SyncResultCallback callback) { + TodorooCursor tasks = taskService.query(Query.select(Task.ID, Task.UUID, Task.TITLE, GtasksList.NAME) + .join(Join.inner(Metadata.TABLE, Task.ID.eq(Metadata.TASK))) + .join(Join.left(StoreObject.TABLE, GtasksMetadata.LIST_ID.eq(GtasksList.REMOTE_ID))) + .where(MetadataCriteria.withKey(GtasksMetadata.METADATA_KEY))); + + GtasksImportCallback gtCallback = null; + if (callback instanceof GtasksImportCallback) + gtCallback = (GtasksImportCallback) callback; + + try { + for (tasks.moveToFirst(); !tasks.isAfterLast(); tasks.moveToNext()) { + String listName = tasks.get(GtasksList.NAME); + String tagUuid = RemoteModel.NO_UUID; + if (!TextUtils.isEmpty(listName)) { + TodorooCursor existingTag = tagDataDao.query(Query.select(TagData.UUID).where(TagData.NAME.eq(listName))); + try { + if (existingTag.getCount() > 0) { + existingTag.moveToFirst(); + tagUuid = existingTag.get(TagData.UUID); + + boolean taskIsInTag = metadataDao.taskIsInTag(tasks.get(Task.UUID), tagUuid); + + if (tagMetadataDao.tagHasMembers(tagUuid) && !taskIsInTag) { + GtasksImportTuple tuple = new GtasksImportTuple(); + tuple.taskId = tasks.get(Task.ID); + tuple.taskName = tasks.get(Task.TITLE); + tuple.taskUuid = tasks.get(Task.UUID); + tuple.tagUuid = tagUuid; + tuple.tagName = listName; + + if (gtCallback != null) + gtCallback.addImportConflict(tuple); + + continue; + } else if (taskIsInTag) { + continue; + } + } else { + TagData td = new TagData(); + td.setValue(TagData.NAME, listName); + tagDataDao.createNew(td); + tagUuid = td.getUuid(); + } + } finally { + existingTag.close(); + } + + if (!RemoteModel.isUuidEmpty(tagUuid)) { + Task task = new Task(); + task.setId(tasks.get(Task.ID)); + task.setUuid(tasks.get(Task.UUID)); + tagService.createLink(task, listName, tagUuid); + } + } + } + } finally { + tasks.close(); + } + } } diff --git a/astrid/plugin-src/com/todoroo/astrid/notes/EditNoteActivity.java b/astrid/plugin-src/com/todoroo/astrid/notes/EditNoteActivity.java index f0aa259ff..b87c017c5 100644 --- a/astrid/plugin-src/com/todoroo/astrid/notes/EditNoteActivity.java +++ b/astrid/plugin-src/com/todoroo/astrid/notes/EditNoteActivity.java @@ -12,6 +12,8 @@ import java.util.Date; import java.util.LinkedList; import java.util.List; +import org.json.JSONObject; + import android.app.Activity; import android.content.Intent; import android.content.res.Resources; @@ -448,7 +450,9 @@ public class EditNoteActivity extends LinearLayout implements TimerActionListene userActivity.setValue(UserActivity.TARGET_NAME, task.getValue(Task.TITLE)); userActivity.setValue(UserActivity.CREATED_AT, DateUtilities.now()); if (usePicture && pendingCommentPicture != null) { - userActivity.setValue(UserActivity.PICTURE, RemoteModel.PictureHelper.uploadPictureJson(pendingCommentPicture).toString()); + JSONObject pictureJson = RemoteModel.PictureHelper.savePictureJson(activity, pendingCommentPicture); + if (pictureJson != null) + userActivity.setValue(UserActivity.PICTURE, pictureJson.toString()); } userActivityDao.createNew(userActivity); diff --git a/astrid/plugin-src/com/todoroo/astrid/opencrx/OpencrxControlSet.java b/astrid/plugin-src/com/todoroo/astrid/opencrx/OpencrxControlSet.java index d90c7b8b6..975ee6f9a 100644 --- a/astrid/plugin-src/com/todoroo/astrid/opencrx/OpencrxControlSet.java +++ b/astrid/plugin-src/com/todoroo/astrid/opencrx/OpencrxControlSet.java @@ -25,10 +25,10 @@ import com.todoroo.andlib.service.Autowired; import com.todoroo.andlib.service.DependencyInjectionService; import com.todoroo.andlib.sql.Query; import com.todoroo.andlib.utility.DateUtilities; +import com.todoroo.astrid.dao.MetadataDao.MetadataCriteria; import com.todoroo.astrid.dao.StoreObjectDao; import com.todoroo.astrid.dao.StoreObjectDao.StoreObjectCriteria; import com.todoroo.astrid.data.Metadata; -import com.todoroo.astrid.data.MetadataApiDao.MetadataCriteria; import com.todoroo.astrid.data.StoreObject; import com.todoroo.astrid.data.Task; import com.todoroo.astrid.service.MetadataService; diff --git a/astrid/plugin-src/com/todoroo/astrid/producteev/ProducteevBackgroundService.java b/astrid/plugin-src/com/todoroo/astrid/producteev/ProducteevBackgroundService.java deleted file mode 100644 index d4ae57ce7..000000000 --- a/astrid/plugin-src/com/todoroo/astrid/producteev/ProducteevBackgroundService.java +++ /dev/null @@ -1,47 +0,0 @@ -/** - * Copyright (c) 2012 Todoroo Inc - * - * See the file "LICENSE" for the full license governing this code. - */ -package com.todoroo.astrid.producteev; - -import com.todoroo.astrid.producteev.sync.ProducteevSyncProvider; -import com.todoroo.astrid.service.StatisticsService; -import com.todoroo.astrid.sync.SyncBackgroundService; -import com.todoroo.astrid.sync.SyncProvider; -import com.todoroo.astrid.sync.SyncProviderUtilities; - - -/** - * SynchronizationService is the service that performs Astrid's background - * synchronization with online task managers. Starting this service - * schedules a repeating alarm which handles the synchronization - * - * @author Tim Su - * - */ -public class ProducteevBackgroundService extends SyncBackgroundService { - - @Override - protected SyncProvider getSyncProvider() { - return new ProducteevSyncProvider(); - } - - @Override - protected SyncProviderUtilities getSyncUtilities() { - return ProducteevUtilities.INSTANCE; - } - - @Override - public void onCreate() { - super.onCreate(); - StatisticsService.sessionStart(this); - } - - @Override - public void onDestroy() { - StatisticsService.sessionStop(this); - super.onDestroy(); - } - -} diff --git a/astrid/plugin-src/com/todoroo/astrid/producteev/ProducteevControlSet.java b/astrid/plugin-src/com/todoroo/astrid/producteev/ProducteevControlSet.java deleted file mode 100644 index 811c48ccd..000000000 --- a/astrid/plugin-src/com/todoroo/astrid/producteev/ProducteevControlSet.java +++ /dev/null @@ -1,285 +0,0 @@ -/** - * Copyright (c) 2012 Todoroo Inc - * - * See the file "LICENSE" for the full license governing this code. - */ -package com.todoroo.astrid.producteev; - -import java.util.ArrayList; - -import org.json.JSONObject; - -import android.app.Activity; -import android.app.ProgressDialog; -import android.content.DialogInterface; -import android.content.DialogInterface.OnClickListener; -import android.util.Log; -import android.view.View; -import android.widget.AdapterView; -import android.widget.AdapterView.OnItemSelectedListener; -import android.widget.ArrayAdapter; -import android.widget.EditText; -import android.widget.Spinner; -import android.widget.TextView; - -import com.timsu.astrid.R; -import com.todoroo.andlib.service.Autowired; -import com.todoroo.andlib.service.DependencyInjectionService; -import com.todoroo.andlib.service.ExceptionService; -import com.todoroo.andlib.utility.DateUtilities; -import com.todoroo.andlib.utility.DialogUtilities; -import com.todoroo.andlib.utility.Preferences; -import com.todoroo.astrid.data.Metadata; -import com.todoroo.astrid.data.StoreObject; -import com.todoroo.astrid.data.Task; -import com.todoroo.astrid.producteev.sync.ProducteevDashboard; -import com.todoroo.astrid.producteev.sync.ProducteevDataService; -import com.todoroo.astrid.producteev.sync.ProducteevSyncProvider; -import com.todoroo.astrid.producteev.sync.ProducteevTask; -import com.todoroo.astrid.producteev.sync.ProducteevUser; -import com.todoroo.astrid.service.MetadataService; -import com.todoroo.astrid.ui.PopupControlSet; - -/** - * Control Set for managing task/dashboard assignments in Producteev - * - * @author Arne Jans - * - */ -public class ProducteevControlSet extends PopupControlSet { - - private Spinner responsibleSelector; - private Spinner dashboardSelector; - - private ArrayList users = null; - private ArrayList dashboards = null; - - @Autowired MetadataService metadataService; - @Autowired ExceptionService exceptionService; - - private int lastDashboardSelection = 0; - - public ProducteevControlSet(final Activity activity, int layout, int displayViewLayout, int title) { - super(activity, layout, displayViewLayout, title); - DependencyInjectionService.getInstance().inject(this); - } - - /** - * Refresh the content of the responsibleSelector with the given userlist. - * - * @param newUsers the new userlist to show in the responsibleSelector - */ - private void refreshResponsibleSpinner(ArrayList newUsers) { - Metadata metadata = ProducteevDataService.getInstance().getTaskMetadata(model.getId()); - long responsibleId = -1; - if(metadata != null && metadata.containsNonNullValue(ProducteevTask.RESPONSIBLE_ID)) - responsibleId = metadata.getValue(ProducteevTask.RESPONSIBLE_ID); - refreshResponsibleSpinner(newUsers, responsibleId); - } - - /** - * Refresh the content of the responsibleSelector with the given userlist. - * - * @param newUsers the new userlist to show in the responsibleSelector - * @param responsibleId the id of the responsible user to set in the spinner - */ - private void refreshResponsibleSpinner(ArrayList newUsers, long responsibleId) { - // Fill the responsible-spinner and set the current responsible - this.users = (newUsers == null ? new ArrayList() : newUsers); - - ArrayAdapter usersAdapter = new ArrayAdapter(activity, - android.R.layout.simple_spinner_item, this.users); - usersAdapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item); - responsibleSelector.setAdapter(usersAdapter); - - int visibility = newUsers == null ? View.GONE : View.VISIBLE; - - getView().findViewById(R.id.producteev_TEA_task_assign_label).setVisibility(visibility); - responsibleSelector.setVisibility(visibility); - - int responsibleSpinnerIndex = 0; - - for (int i = 0; i < this.users.size() ; i++) { - if (this.users.get(i).getId() == responsibleId || - (responsibleId == -1 && this.users.get(i).getId() == Preferences.getLong(ProducteevUtilities.PREF_USER_ID, -1))) { - responsibleSpinnerIndex = i; - break; - } - } - responsibleSelector.setSelection(responsibleSpinnerIndex); - } - - @Override - protected void readFromTaskOnInitialize() { - Metadata metadata = ProducteevDataService.getInstance().getTaskMetadata(model.getId()); - if(metadata == null) - metadata = ProducteevTask.newMetadata(); - - // Fill the dashboard-spinner and set the current dashboard - long dashboardId = ProducteevUtilities.INSTANCE.getDefaultDashboard(); - if(metadata.containsNonNullValue(ProducteevTask.DASHBOARD_ID)) - dashboardId = metadata.getValue(ProducteevTask.DASHBOARD_ID); - - StoreObject[] dashboardsData = ProducteevDataService.getInstance().getDashboards(); - dashboards = new ArrayList(dashboardsData.length); - ProducteevDashboard ownerDashboard = null; - int dashboardSpinnerIndex = -1; - - int i = 0; - for (i=0;i dashAdapter = new ArrayAdapter(activity, - android.R.layout.simple_spinner_item, dashboards); - dashAdapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item); - dashboardSelector.setAdapter(dashAdapter); - dashboardSelector.setSelection(dashboardSpinnerIndex+1); - - if (ownerDashboard == null || ownerDashboard.getId() == ProducteevUtilities.DASHBOARD_NO_SYNC - || ownerDashboard.getId() == ProducteevUtilities.DASHBOARD_CREATE) { - responsibleSelector.setEnabled(false); - responsibleSelector.setAdapter(null); - getView().findViewById(R.id.producteev_TEA_task_assign_label).setVisibility(View.GONE); - return; - } - - refreshResponsibleSpinner(ownerDashboard.getUsers()); - } - - @Override - protected void afterInflate() { - - this.displayText.setText(activity.getString(R.string.producteev_TEA_control_set_display)); - - //view = LayoutInflater.from(activity).inflate(R.layout.producteev_control, parent, true); - - this.responsibleSelector = (Spinner) getView().findViewById(R.id.producteev_TEA_task_assign); - TextView emptyView = new TextView(activity); - emptyView.setText(activity.getText(R.string.producteev_no_dashboard)); - responsibleSelector.setEmptyView(emptyView); - - this.dashboardSelector = (Spinner) getView().findViewById(R.id.producteev_TEA_dashboard_assign); - this.dashboardSelector.setOnItemSelectedListener(new OnItemSelectedListener() { - - @Override - public void onItemSelected(AdapterView spinnerParent, View spinnerView, - int position, long id) { - final Spinner dashSelector = (Spinner) spinnerParent; - ProducteevDashboard dashboard = (ProducteevDashboard) dashSelector.getSelectedItem(); - if (dashboard.getId() == ProducteevUtilities.DASHBOARD_CREATE) { - // let the user create a new dashboard - final EditText editor = new EditText(ProducteevControlSet.this.activity); - OnClickListener okListener = new OnClickListener() { - @Override - public void onClick(DialogInterface dlg, int which) { - Activity context = ProducteevControlSet.this.activity; - String newDashboardName = editor.getText().toString(); - if (newDashboardName == null || newDashboardName.length() == 0) { - dlg.cancel(); - } else { - // create the real dashboard, select it in the spinner and refresh responsiblespinner - ProgressDialog progressDialog = com.todoroo.andlib.utility.DialogUtilities.progressDialog(context, - context.getString(R.string.DLG_wait)); - try { - progressDialog.show(); - JSONObject newDashJSON = ProducteevSyncProvider.getInvoker().dashboardsCreate( - newDashboardName).getJSONObject("dashboard"); //$NON-NLS-1$ - StoreObject local = ProducteevDataService.getInstance().updateDashboards(newDashJSON, true); - if (local != null) { - ProducteevDashboard newDashboard = new ProducteevDashboard(local); - ArrayAdapter adapter = (ArrayAdapter) dashSelector.getAdapter(); - adapter.insert(newDashboard, adapter.getCount()-1); - dashSelector.setSelection(adapter.getCount()-2); - refreshResponsibleSpinner(newDashboard.getUsers()); - DialogUtilities.dismissDialog(context, progressDialog); - } - } catch (Exception e) { - DialogUtilities.dismissDialog(context, progressDialog); - DialogUtilities.okDialog(context, - context.getString(R.string.DLG_error, e.getMessage()), - null); - exceptionService.reportError("pdv-create-dashboard", e); //$NON-NLS-1$ - dashSelector.setSelection(0); - } - } - - } - }; - OnClickListener cancelListener = new OnClickListener() { - @Override - public void onClick(DialogInterface dlg, int which) { - dlg.cancel(); - dashboardSelector.setSelection(lastDashboardSelection); - } - }; - DialogUtilities.viewDialog(ProducteevControlSet.this.activity, - ProducteevControlSet.this.activity.getString(R.string.producteev_create_dashboard_name), - editor, - okListener, - cancelListener); - } else { - refreshResponsibleSpinner(dashboard.getUsers()); - lastDashboardSelection = position; - } - } - - @Override - public void onNothingSelected(AdapterView spinnerParent) { - // - } - }); - } - - @Override - protected String writeToModelAfterInitialized(Task task) { - Metadata metadata = ProducteevDataService.getInstance().getTaskMetadata(task.getId()); - try { - if (metadata == null) { - metadata = new Metadata(); - metadata.setValue(Metadata.KEY, ProducteevTask.METADATA_KEY); - metadata.setValue(Metadata.TASK, task.getId()); - metadata.setValue(ProducteevTask.ID, 0L); - } - - ProducteevDashboard dashboard = (ProducteevDashboard) dashboardSelector.getSelectedItem(); - metadata.setValue(ProducteevTask.DASHBOARD_ID, dashboard.getId()); - - ProducteevUser responsibleUser = (ProducteevUser) responsibleSelector.getSelectedItem(); - - if(responsibleUser == null) - metadata.setValue(ProducteevTask.RESPONSIBLE_ID, 0L); - else - metadata.setValue(ProducteevTask.RESPONSIBLE_ID, responsibleUser.getId()); - - // Erase PDTV-repeating-info if task itself is repeating with Astrid-repeat - if (task.containsNonNullValue(Task.RECURRENCE) && task.getValue(Task.RECURRENCE).length()>0) { - metadata.setValue(ProducteevTask.REPEATING_SETTING, ""); //$NON-NLS-1$ - } - - if(metadata.getSetValues().size() > 0) { - metadataService.save(metadata); - task.setValue(Task.MODIFICATION_DATE, DateUtilities.now()); - } - } catch (Exception e) { - Log.e("error-saving-pdv", "Error Saving Metadata", e); //$NON-NLS-1$ //$NON-NLS-2$ - } - return null; - } - - @Override - protected void refreshDisplayView() { - // TODO Auto-generated method stub - - } -} diff --git a/astrid/plugin-src/com/todoroo/astrid/producteev/ProducteevCustomFilterCriteriaExposer.java b/astrid/plugin-src/com/todoroo/astrid/producteev/ProducteevCustomFilterCriteriaExposer.java deleted file mode 100644 index 257448089..000000000 --- a/astrid/plugin-src/com/todoroo/astrid/producteev/ProducteevCustomFilterCriteriaExposer.java +++ /dev/null @@ -1,124 +0,0 @@ -/** - * Copyright (c) 2012 Todoroo Inc - * - * See the file "LICENSE" for the full license governing this code. - */ -package com.todoroo.astrid.producteev; - -import java.util.Set; -import java.util.TreeSet; - -import android.content.BroadcastReceiver; -import android.content.ContentValues; -import android.content.Context; -import android.content.Intent; -import android.content.res.Resources; -import android.graphics.drawable.BitmapDrawable; - -import com.timsu.astrid.R; -import com.todoroo.andlib.sql.Criterion; -import com.todoroo.andlib.sql.Join; -import com.todoroo.andlib.sql.Query; -import com.todoroo.astrid.api.AstridApiConstants; -import com.todoroo.astrid.api.CustomFilterCriterion; -import com.todoroo.astrid.api.MultipleSelectCriterion; -import com.todoroo.astrid.dao.MetadataDao; -import com.todoroo.astrid.dao.TaskDao; -import com.todoroo.astrid.data.Metadata; -import com.todoroo.astrid.data.StoreObject; -import com.todoroo.astrid.data.Task; -import com.todoroo.astrid.producteev.sync.ProducteevDashboard; -import com.todoroo.astrid.producteev.sync.ProducteevDataService; -import com.todoroo.astrid.producteev.sync.ProducteevTask; -import com.todoroo.astrid.producteev.sync.ProducteevUser; - -public class ProducteevCustomFilterCriteriaExposer extends BroadcastReceiver { - private static final String IDENTIFIER_PRODUCTEEV_WORKSPACE = "producteev_workspace"; //$NON-NLS-1$ - private static final String IDENTIFIER_PRODUCTEEV_ASSIGNEE = "producteev_assignee"; //$NON-NLS-1$ - - @SuppressWarnings("nls") - @Override - public void onReceive(Context context, Intent intent) { - // if we aren't logged in, don't expose features - if(!ProducteevUtilities.INSTANCE.isLoggedIn()) - return; - - Resources r = context.getResources(); - - StoreObject[] objects = ProducteevDataService.getInstance().getDashboards(); - ProducteevDashboard[] dashboards = new ProducteevDashboard[objects.length]; - for (int i = 0; i < objects.length; i++) { - dashboards[i] = new ProducteevDashboard(objects[i]); - } - - CustomFilterCriterion[] ret = new CustomFilterCriterion[2]; - int j = 0; - - { - String[] workspaceNames = new String[objects.length]; - String[] workspaceIds = new String[objects.length]; - for (int i = 0; i < dashboards.length; i++) { - workspaceNames[i] = dashboards[i].getName(); - workspaceIds[i] = String.valueOf(dashboards[i].getId()); - } - ContentValues values = new ContentValues(); - values.put(Metadata.KEY.name, ProducteevTask.METADATA_KEY); - values.put(ProducteevTask.DASHBOARD_ID.name, "?"); - CustomFilterCriterion criterion = new MultipleSelectCriterion( - IDENTIFIER_PRODUCTEEV_WORKSPACE, - context.getString(R.string.CFC_producteev_in_workspace_text), - // Todo: abstract these metadata queries - Query.select(Metadata.TASK).from(Metadata.TABLE).join(Join.inner( - Task.TABLE, Metadata.TASK.eq(Task.ID))).where(Criterion.and( - TaskDao.TaskCriteria.activeAndVisible(), - MetadataDao.MetadataCriteria.withKey(ProducteevTask.METADATA_KEY), - ProducteevTask.DASHBOARD_ID.eq("?"))).toString(), - values, - workspaceNames, - workspaceIds, - ((BitmapDrawable)r.getDrawable(R.drawable.silk_folder)).getBitmap(), - context.getString(R.string.CFC_producteev_in_workspace_name)); - ret[j++] = criterion; - } - - { - Set users = new TreeSet(); - for (ProducteevDashboard dashboard : dashboards) { - users.addAll(dashboard.getUsers()); - } - int numUsers = users.size(); - String[] userNames = new String[numUsers]; - String[] userIds = new String[numUsers]; - int i = 0; - for (ProducteevUser user : users) { - userNames[i] = user.toString(); - userIds[i] = String.valueOf(user.getId()); - i++; - } - ContentValues values = new ContentValues(2); - values.put(Metadata.KEY.name, ProducteevTask.METADATA_KEY); - values.put(ProducteevTask.RESPONSIBLE_ID.name, "?"); - CustomFilterCriterion criterion = new MultipleSelectCriterion( - IDENTIFIER_PRODUCTEEV_ASSIGNEE, - context.getString(R.string.CFC_producteev_assigned_to_text), - // Todo: abstract these metadata queries, and unify this code with the CustomFilterExposers. - Query.select(Metadata.TASK).from(Metadata.TABLE).join(Join.inner( - Task.TABLE, Metadata.TASK.eq(Task.ID))).where(Criterion.and( - TaskDao.TaskCriteria.activeAndVisible(), - MetadataDao.MetadataCriteria.withKey(ProducteevTask.METADATA_KEY), - ProducteevTask.RESPONSIBLE_ID.eq("?"))).toString(), - values, - userNames, - userIds, - ((BitmapDrawable)r.getDrawable(R.drawable.silk_user_gray)).getBitmap(), - context.getString(R.string.CFC_producteev_assigned_to_name)); - ret[j++] = criterion; - } - - // transmit filter list - Intent broadcastIntent = new Intent(AstridApiConstants.BROADCAST_SEND_CUSTOM_FILTER_CRITERIA); - broadcastIntent.putExtra(AstridApiConstants.EXTRAS_ADDON, ProducteevUtilities.IDENTIFIER); - broadcastIntent.putExtra(AstridApiConstants.EXTRAS_RESPONSE, ret); - context.sendBroadcast(broadcastIntent, AstridApiConstants.PERMISSION_READ); - } -} diff --git a/astrid/plugin-src/com/todoroo/astrid/producteev/ProducteevDetailExposer.java b/astrid/plugin-src/com/todoroo/astrid/producteev/ProducteevDetailExposer.java deleted file mode 100644 index 4f0e201bc..000000000 --- a/astrid/plugin-src/com/todoroo/astrid/producteev/ProducteevDetailExposer.java +++ /dev/null @@ -1,180 +0,0 @@ -/** - * Copyright (c) 2012 Todoroo Inc - * - * See the file "LICENSE" for the full license governing this code. - */ -package com.todoroo.astrid.producteev; - -import java.text.DateFormatSymbols; -import java.util.Calendar; - -import android.content.BroadcastReceiver; -import android.content.Context; -import android.content.Intent; - -import com.timsu.astrid.R; -import com.todoroo.andlib.service.ContextManager; -import com.todoroo.andlib.utility.Preferences; -import com.todoroo.astrid.adapter.TaskAdapter; -import com.todoroo.astrid.api.AstridApiConstants; -import com.todoroo.astrid.data.Metadata; -import com.todoroo.astrid.data.StoreObject; -import com.todoroo.astrid.producteev.sync.ProducteevDashboard; -import com.todoroo.astrid.producteev.sync.ProducteevDataService; -import com.todoroo.astrid.producteev.sync.ProducteevTask; - -/** - * Exposes Task Details for Producteev: - * - notes - * - * @author Tim Su - * - */ -public class ProducteevDetailExposer extends BroadcastReceiver { - - @Override - public void onReceive(Context context, Intent intent) { - ContextManager.setContext(context); - long taskId = intent.getLongExtra(AstridApiConstants.EXTRAS_TASK_ID, -1); - if(taskId == -1) - return; - - String taskDetail; - try { - taskDetail = getTaskDetails(context, taskId); - } catch (Exception e) { - return; - } - if(taskDetail == null) - return; - - Intent broadcastIntent = new Intent(AstridApiConstants.BROADCAST_SEND_DETAILS); - broadcastIntent.putExtra(AstridApiConstants.EXTRAS_ADDON, ProducteevUtilities.IDENTIFIER); - broadcastIntent.putExtra(AstridApiConstants.EXTRAS_TASK_ID, taskId); - broadcastIntent.putExtra(AstridApiConstants.EXTRAS_RESPONSE, taskDetail); - context.sendBroadcast(broadcastIntent, AstridApiConstants.PERMISSION_READ); - } - - @SuppressWarnings("nls") - public String getTaskDetails(Context context, long id) { - Metadata metadata = ProducteevDataService.getInstance().getTaskMetadata(id); - if(metadata == null) - return null; - - StringBuilder builder = new StringBuilder(); - - if(!ProducteevUtilities.INSTANCE.isLoggedIn()) - return null; - - long dashboardId = -1; - if(metadata.containsNonNullValue(ProducteevTask.DASHBOARD_ID)) - dashboardId = metadata.getValue(ProducteevTask.DASHBOARD_ID); - long responsibleId = -1; - if(metadata.containsNonNullValue(ProducteevTask.RESPONSIBLE_ID)) - responsibleId = metadata.getValue(ProducteevTask.RESPONSIBLE_ID); - long creatorId = -1; - if(metadata.containsNonNullValue(ProducteevTask.CREATOR_ID)) - creatorId = metadata.getValue(ProducteevTask.CREATOR_ID); - String repeatSetting = null; - if(metadata.containsNonNullValue(ProducteevTask.REPEATING_SETTING)) - repeatSetting = metadata.getValue(ProducteevTask.REPEATING_SETTING); - - // display dashboard if not "no sync" or "default" - StoreObject ownerDashboard = null; - for(StoreObject dashboard : ProducteevDataService.getInstance().getDashboards()) { - if(dashboard == null || !dashboard.containsNonNullValue(ProducteevDashboard.REMOTE_ID)) - continue; - - if(dashboard.getValue(ProducteevDashboard.REMOTE_ID) == dashboardId) { - ownerDashboard = dashboard; - break; - } - } - if(dashboardId != ProducteevUtilities.DASHBOARD_NO_SYNC && dashboardId - != Preferences.getLong(ProducteevUtilities.PREF_DEFAULT_DASHBOARD, 0L) && - ownerDashboard != null) { - String dashboardName = ownerDashboard.getValue(ProducteevDashboard.NAME); - builder.append(" ").append(dashboardName).append(TaskAdapter.DETAIL_SEPARATOR); //$NON-NLS-1$ - } - - // display responsible user if not current one - if(responsibleId > 0 && ownerDashboard != null && responsibleId != - Preferences.getLong(ProducteevUtilities.PREF_USER_ID, 0L)) { - String user = ProducteevDashboard.getUserFromDashboard(ownerDashboard, responsibleId); - if(user != null) - builder.append(" ").append(user).append(TaskAdapter.DETAIL_SEPARATOR); //$NON-NLS-1$ - } else { - // display creator user if not responsible user - if(creatorId > 0 && ownerDashboard != null && creatorId != responsibleId) { - String user = ProducteevDashboard.getUserFromDashboard(ownerDashboard, creatorId); - if(user != null) - builder.append(" ").append( //$NON-NLS-1$ - context.getString(R.string.producteev_PDE_task_from, user)). - append(TaskAdapter.DETAIL_SEPARATOR); - } - } - - // display repeating task information - if (repeatSetting != null && repeatSetting.length() > 0) { - String interval = null; - String[] pdvRepeating = repeatSetting.split(","); - int pdvRepeatingValue = 0; - String pdvRepeatingDay = null; - try { - pdvRepeatingValue = Integer.parseInt(pdvRepeating[0]); - } catch (Exception e) { - pdvRepeatingDay = pdvRepeating[0]; - pdvRepeatingValue = 1; - } - String pdvRepeatingInterval = pdvRepeating[1]; - - if (pdvRepeatingInterval.startsWith("day")) { - interval = context.getResources().getQuantityString(R.plurals.DUt_days, pdvRepeatingValue, - pdvRepeatingValue); - } else if (pdvRepeatingInterval.startsWith("weekday")) { - interval = context.getResources().getQuantityString(R.plurals.DUt_weekdays, pdvRepeatingValue, - pdvRepeatingValue); - } else if (pdvRepeatingInterval.startsWith("week")) { - interval = context.getResources().getQuantityString(R.plurals.DUt_weeks, pdvRepeatingValue, - pdvRepeatingValue); - } else if (pdvRepeatingInterval.startsWith("month")) { - interval = context.getResources().getQuantityString(R.plurals.DUt_months, pdvRepeatingValue, - pdvRepeatingValue); - } else if (pdvRepeatingInterval.startsWith("year")) { - interval = context.getResources().getQuantityString(R.plurals.DUt_years, pdvRepeatingValue, - pdvRepeatingValue); - } - interval = "" + interval + ""; //$NON-NLS-1$//$NON-NLS-2$ - if (pdvRepeatingDay != null) { - DateFormatSymbols dfs = new DateFormatSymbols(); - String[] weekdays = dfs.getShortWeekdays(); - if (pdvRepeatingDay.equals("monday")) { - pdvRepeatingDay = weekdays[Calendar.MONDAY]; - } else if (pdvRepeatingDay.equals("tuesday")) { - pdvRepeatingDay = weekdays[Calendar.TUESDAY]; - } else if (pdvRepeatingDay.equals("wednesday")) { - pdvRepeatingDay = weekdays[Calendar.WEDNESDAY]; - } else if (pdvRepeatingDay.equals("thursday")) { - pdvRepeatingDay = weekdays[Calendar.THURSDAY]; - } else if (pdvRepeatingDay.equals("friday")) { - pdvRepeatingDay = weekdays[Calendar.FRIDAY]; - } else if (pdvRepeatingDay.equals("saturday")) { - pdvRepeatingDay = weekdays[Calendar.SATURDAY]; - } else if (pdvRepeatingDay.equals("sunday")) { - pdvRepeatingDay = weekdays[Calendar.SUNDAY]; - } - interval = context.getResources().getString(R.string.repeat_detail_byday).replace("$I", //$NON-NLS-1$ - interval).replace("$D", pdvRepeatingDay); //$NON-NLS-1$ - } - String detail = context.getString(R.string.repeat_detail_duedate, interval); - builder.append(" ").append(detail). //$NON-NLS-1$ - append(TaskAdapter.DETAIL_SEPARATOR); - } - - if(builder.length() == 0) - return null; - String result = builder.toString(); - return result.substring(0, result.length() - TaskAdapter.DETAIL_SEPARATOR.length()); - } - -} diff --git a/astrid/plugin-src/com/todoroo/astrid/producteev/ProducteevFilterExposer.java b/astrid/plugin-src/com/todoroo/astrid/producteev/ProducteevFilterExposer.java deleted file mode 100644 index 206775ddf..000000000 --- a/astrid/plugin-src/com/todoroo/astrid/producteev/ProducteevFilterExposer.java +++ /dev/null @@ -1,197 +0,0 @@ -/** - * Copyright (c) 2012 Todoroo Inc - * - * See the file "LICENSE" for the full license governing this code. - */ -package com.todoroo.astrid.producteev; - -import java.util.TreeSet; - -import android.content.BroadcastReceiver; -import android.content.ContentValues; -import android.content.Context; -import android.content.Intent; - -import com.timsu.astrid.R; -import com.todoroo.andlib.service.ContextManager; -import com.todoroo.andlib.sql.Criterion; -import com.todoroo.andlib.sql.QueryTemplate; -import com.todoroo.andlib.utility.Preferences; -import com.todoroo.astrid.api.AstridApiConstants; -import com.todoroo.astrid.api.AstridFilterExposer; -import com.todoroo.astrid.api.Filter; -import com.todoroo.astrid.api.FilterCategory; -import com.todoroo.astrid.api.FilterListHeader; -import com.todoroo.astrid.api.FilterListItem; -import com.todoroo.astrid.dao.MetadataDao.MetadataCriteria; -import com.todoroo.astrid.dao.TaskDao.TaskCriteria; -import com.todoroo.astrid.data.Metadata; -import com.todoroo.astrid.data.StoreObject; -import com.todoroo.astrid.producteev.sync.ProducteevDashboard; -import com.todoroo.astrid.producteev.sync.ProducteevDataService; -import com.todoroo.astrid.producteev.sync.ProducteevTask; -import com.todoroo.astrid.producteev.sync.ProducteevUser; - -/** - * Exposes filters based on Producteev Dashboards - * - * @author Arne Jans - * - */ -public class ProducteevFilterExposer extends BroadcastReceiver implements AstridFilterExposer { - - /** - * @param context - */ - public static Filter filterFromList(Context context, ProducteevDashboard dashboard, long currentUserId) { - String dashboardTitle = dashboard.getName(); - String title = dashboard.getName(); - ContentValues values = new ContentValues(); - values.put(Metadata.KEY.name, ProducteevTask.METADATA_KEY); - values.put(ProducteevTask.DASHBOARD_ID.name, dashboard.getId()); - values.put(ProducteevTask.ID.name, 0); - values.put(ProducteevTask.CREATOR_ID.name, currentUserId); - values.put(ProducteevTask.RESPONSIBLE_ID.name, currentUserId); - Filter filter; - if (currentUserId != -1) - filter = new Filter(dashboardTitle, title, new QueryTemplate().join( - ProducteevDataService.METADATA_JOIN).where(Criterion.and( - MetadataCriteria.withKey(ProducteevTask.METADATA_KEY), - TaskCriteria.isActive(), - TaskCriteria.isVisible(), - Criterion.or(ProducteevTask.CREATOR_ID.eq(currentUserId), - ProducteevTask.RESPONSIBLE_ID.eq(currentUserId)), - ProducteevTask.DASHBOARD_ID.eq(dashboard.getId()))), - values); - else - filter = new Filter(dashboardTitle, title, new QueryTemplate().join( - ProducteevDataService.METADATA_JOIN).where(Criterion.and( - MetadataCriteria.withKey(ProducteevTask.METADATA_KEY), - TaskCriteria.isActive(), - TaskCriteria.isVisible(), - ProducteevTask.DASHBOARD_ID.eq(dashboard.getId()))), - values); - - return filter; - } - - private Filter filterUserAssignedByMe(Context context, ProducteevUser user, long currentUserId) { - String title = context.getString(R.string.producteev_FEx_responsible_title, user.toString()); - ContentValues values = new ContentValues(); - values.put(Metadata.KEY.name, ProducteevTask.METADATA_KEY); - values.put(ProducteevTask.ID.name, 0); - values.put(ProducteevTask.CREATOR_ID.name, currentUserId); - values.put(ProducteevTask.RESPONSIBLE_ID.name, user.getId()); - Filter filter = new Filter(user.toString(), title, new QueryTemplate().join( - ProducteevDataService.METADATA_JOIN).where(Criterion.and( - MetadataCriteria.withKey(ProducteevTask.METADATA_KEY), - TaskCriteria.isActive(), - TaskCriteria.isVisible(), - ProducteevTask.CREATOR_ID.eq(currentUserId), - ProducteevTask.RESPONSIBLE_ID.eq(user.getId()))), - values); - - return filter; - } - - private Filter filterUserAssignedByOthers(Context context, ProducteevUser user, long currentUserId) { - String title = context.getString(R.string.producteev_FEx_responsible_title, user.toString()); - ContentValues values = new ContentValues(); - values.put(Metadata.KEY.name, ProducteevTask.METADATA_KEY); - values.put(ProducteevTask.ID.name, 0); - values.put(ProducteevTask.CREATOR_ID.name, 0); - values.put(ProducteevTask.RESPONSIBLE_ID.name, currentUserId); - Filter filter = new Filter(user.toString(), title, new QueryTemplate().join( - ProducteevDataService.METADATA_JOIN).where(Criterion.and( - MetadataCriteria.withKey(ProducteevTask.METADATA_KEY), - TaskCriteria.isActive(), - TaskCriteria.isVisible(), - Criterion.not(ProducteevTask.CREATOR_ID.eq(currentUserId)), - ProducteevTask.RESPONSIBLE_ID.eq(user.getId()))), - values); - - return filter; - } - - @Override - public void onReceive(Context context, Intent intent) { - ContextManager.setContext(context); - FilterListItem[] list = prepareFilters(context); - - Intent broadcastIntent = new Intent(AstridApiConstants.BROADCAST_SEND_FILTERS); - broadcastIntent.putExtra(AstridApiConstants.EXTRAS_ADDON, ProducteevUtilities.IDENTIFIER); - broadcastIntent.putExtra(AstridApiConstants.EXTRAS_RESPONSE, list); - context.sendBroadcast(broadcastIntent, AstridApiConstants.PERMISSION_READ); - } - - private FilterListItem[] prepareFilters(Context context) { - // if we aren't logged in, don't expose features - if(!ProducteevUtilities.INSTANCE.isLoggedIn()) - return null; - - StoreObject[] dashboards = ProducteevDataService.getInstance().getDashboards(); - - // If user does not have any dashboards, don't show this section at all - if(dashboards.length == 0) - return null; - - FilterListHeader producteevHeader = new FilterListHeader(context.getString(R.string.producteev_FEx_header)); - - long currentUserId = Preferences.getLong(ProducteevUtilities.PREF_USER_ID, -1); - - // load dashboards - Filter[] dashboardFilters = new Filter[dashboards.length]; - for(int i = 0; i < dashboards.length; i++) - dashboardFilters[i] = filterFromList(context, new ProducteevDashboard(dashboards[i]), currentUserId); - FilterCategory producteevDashboards = new FilterCategory(context.getString(R.string.producteev_FEx_dashboard), - dashboardFilters); - - // load responsible people, assigned by me - TreeSet people = loadResponsiblePeople(dashboards); - Filter[] peopleByMeFilters = new Filter[people.size()]; - int index = 0; - for (ProducteevUser person : people) - peopleByMeFilters[index++] = filterUserAssignedByMe(context, person, currentUserId); - FilterCategory producteevUsersByMeCategory = new FilterCategory(context.getString(R.string.producteev_FEx_responsible_byme), - peopleByMeFilters); - - // load responsible people, assigned by others - Filter[] peopleByOthersFilters = new Filter[people.size()]; - index = 0; - for (ProducteevUser person : people) - peopleByOthersFilters[index++] = filterUserAssignedByOthers(context, person, currentUserId); - FilterCategory producteevUsersByOthersCategory = new FilterCategory(context.getString(R.string.producteev_FEx_responsible_byothers), - peopleByOthersFilters); - - // transmit filter list - FilterListItem[] list = new FilterListItem[4]; - list[0] = producteevHeader; - list[1] = producteevDashboards; - list[2] = producteevUsersByMeCategory; - list[3] = producteevUsersByOthersCategory; - return list; - } - - /** - * @param dashboards - * @return people in a map of name => pair(dashboard id, user id) - */ - private TreeSet loadResponsiblePeople(StoreObject[] dashboards) { - TreeSet users = new TreeSet(); - for(StoreObject dashboard : dashboards) { - ProducteevDashboard elDashboard = new ProducteevDashboard(dashboard); - users.addAll(elDashboard.getUsers()); - } - - return users; - } - - @Override - public FilterListItem[] getFilters() { - if (ContextManager.getContext() == null) - return null; - - return prepareFilters(ContextManager.getContext()); - } - -} diff --git a/astrid/plugin-src/com/todoroo/astrid/producteev/ProducteevLoginActivity.java b/astrid/plugin-src/com/todoroo/astrid/producteev/ProducteevLoginActivity.java deleted file mode 100644 index 393d10c58..000000000 --- a/astrid/plugin-src/com/todoroo/astrid/producteev/ProducteevLoginActivity.java +++ /dev/null @@ -1,240 +0,0 @@ -/** - * Copyright (c) 2012 Todoroo Inc - * - * See the file "LICENSE" for the full license governing this code. - */ -package com.todoroo.astrid.producteev; - -import java.util.TimeZone; - -import android.app.Activity; -import android.app.ProgressDialog; -import android.content.Intent; -import android.net.Uri; -import android.os.Bundle; -import android.text.Editable; -import android.view.View; -import android.view.View.OnClickListener; -import android.widget.Button; -import android.widget.EditText; -import android.widget.Spinner; -import android.widget.TextView; - -import com.timsu.astrid.R; -import com.todoroo.andlib.service.ContextManager; -import com.todoroo.andlib.service.DependencyInjectionService; -import com.todoroo.andlib.utility.DialogUtilities; -import com.todoroo.andlib.utility.Preferences; -import com.todoroo.astrid.producteev.api.ApiAuthenticationException; -import com.todoroo.astrid.producteev.api.ProducteevInvoker; -import com.todoroo.astrid.producteev.sync.ProducteevSyncProvider; -import com.todoroo.astrid.service.StatisticsConstants; -import com.todoroo.astrid.service.StatisticsService; - -/** - * This activity allows users to sign in or log in to Producteev - * - * @author arne.jans - * - */ -public class ProducteevLoginActivity extends Activity { - - // --- ui initialization - - public ProducteevLoginActivity() { - super(); - DependencyInjectionService.getInstance().inject(this); - } - - @Override - protected void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - ContextManager.setContext(this); - - setContentView(R.layout.producteev_login_activity); - setTitle(R.string.producteev_PLA_title); - - // terms clicking - findViewById(R.id.terms).setOnClickListener(new OnClickListener() { - @Override - public void onClick(View arg0) { - startActivity(new Intent(Intent.ACTION_VIEW, - Uri.parse("https://www.producteev.com/#terms"))); //$NON-NLS-1$ - } - }); - - final TextView errors = (TextView) findViewById(R.id.error); - final EditText emailEditText = (EditText) findViewById(R.id.email); - final EditText passwordEditText = (EditText) findViewById(R.id.password); - final View newUserLayout = findViewById(R.id.newUserLayout); - final Spinner timezoneList = (Spinner) findViewById(R.id.timezoneList); - - String[] timezoneEntries = getResources().getStringArray(R.array.PLA_timezones_list); - String defaultTimeZone = TimeZone.getDefault().getID(); - int selected = 0; - for(int i = 0; i < timezoneEntries.length; i++) { - if(timezoneEntries[i].equals(defaultTimeZone)) - selected = i; - } - timezoneList.setSelection(selected); - - Button signIn = (Button) findViewById(R.id.signIn); - signIn.setOnClickListener(new OnClickListener() { - public void onClick(View v) { - errors.setVisibility(View.GONE); - if(newUserLayout.getVisibility() == View.VISIBLE) - newUserLayout.setVisibility(View.GONE); - else { - Editable email = emailEditText.getText(); - Editable password = passwordEditText.getText(); - if(email.length() == 0 || password.length() == 0) { - errors.setVisibility(View.VISIBLE); - errors.setText(R.string.producteev_PLA_errorEmpty); - return; - } - - performLogin(email.toString(), password.toString()); - } - } - - }); - - Button createNew = (Button) findViewById(R.id.createNew); - createNew.setOnClickListener(new OnClickListener() { - public void onClick(View v) { - errors.setVisibility(View.GONE); - if(newUserLayout.getVisibility() != View.VISIBLE) - newUserLayout.setVisibility(View.VISIBLE); - else { - Editable email = emailEditText.getText(); - Editable password = passwordEditText.getText(); - Editable firstName = ((EditText)findViewById(R.id.firstName)).getText(); - Editable lastName = ((EditText)findViewById(R.id.lastName)).getText(); - String timezone = timezoneList.getSelectedItem().toString(); - if(email.length() == 0 || password.length() == 0 || - firstName.length() == 0 || - lastName.length() == 0) { - errors.setVisibility(View.VISIBLE); - errors.setText(R.string.producteev_PLA_errorEmpty); - return; - } - performSignup(email.toString(), password.toString(), - firstName.toString(), lastName.toString(), timezone); - } - } - }); - - } - - - private void performLogin(final String email, final String password) { - final ProgressDialog dialog = DialogUtilities.progressDialog(this, - getString(R.string.DLG_wait)); - final TextView errors = (TextView) findViewById(R.id.error); - dialog.show(); - new Thread() { - @Override - public void run() { - ProducteevInvoker invoker = ProducteevSyncProvider.getInvoker(); - final StringBuilder errorMessage = new StringBuilder(); - try { - invoker.authenticate(email, password); - - Preferences.setString(R.string.producteev_PPr_email, email); - Preferences.setString(R.string.producteev_PPr_password, password); - ProducteevUtilities.INSTANCE.setToken(invoker.getToken()); - - StatisticsService.reportEvent(StatisticsConstants.PRODUCTEEV_LOGIN); - - synchronize(); - } catch (ApiAuthenticationException e) { - errorMessage.append(getString(R.string.producteev_PLA_errorAuth)); - } catch (Exception e) { - errorMessage.append(e.getMessage()); - } finally { - runOnUiThread(new Runnable() { - public void run() { - DialogUtilities.dismissDialog(ProducteevLoginActivity.this, dialog); - if(errorMessage.length() > 0) { - errors.setVisibility(View.VISIBLE); - errors.setText(errorMessage); - } - } - }); - } - } - }.start(); - } - - private void performSignup(final String email, final String password, - final String firstName, final String lastName, final String timezone) { - final ProgressDialog dialog = DialogUtilities.progressDialog(this, - getString(R.string.DLG_wait)); - final TextView errors = (TextView) findViewById(R.id.error); - dialog.show(); - new Thread() { - @Override - public void run() { - ProducteevInvoker invoker = ProducteevSyncProvider.getInvoker(); - final StringBuilder errorMessage = new StringBuilder(); - try { - invoker.usersSignUp(email, firstName, lastName, password, timezone, null); - invoker.authenticate(email, password); - - Preferences.setString(R.string.producteev_PPr_email, email); - Preferences.setString(R.string.producteev_PPr_password, password); - ProducteevUtilities.INSTANCE.setToken(invoker.getToken()); - - StatisticsService.reportEvent(StatisticsConstants.PRODUCTEEV_SIGNUP); - - synchronize(); - } catch (Exception e) { - errorMessage.append(e.getMessage()); - } finally { - runOnUiThread(new Runnable() { - public void run() { - DialogUtilities.dismissDialog(ProducteevLoginActivity.this, dialog); - if(errorMessage.length() > 0) { - errors.setVisibility(View.VISIBLE); - errors.setText(errorMessage); - } - } - }); - } - } - }.start(); - } - - /** - * Perform synchronization - */ - protected void synchronize() { - startService(new Intent(null, null, - this, ProducteevBackgroundService.class)); - finish(); - } - - @Override - protected void onStart() { - super.onStart(); - } - - @Override - protected void onResume() { - super.onResume(); - StatisticsService.sessionStart(this); - } - - @Override - protected void onPause() { - super.onPause(); - StatisticsService.sessionPause(); - } - - @Override - protected void onStop() { - super.onStop(); - StatisticsService.sessionStop(this); - } - -} diff --git a/astrid/plugin-src/com/todoroo/astrid/producteev/ProducteevPreferences.java b/astrid/plugin-src/com/todoroo/astrid/producteev/ProducteevPreferences.java deleted file mode 100644 index 715278c89..000000000 --- a/astrid/plugin-src/com/todoroo/astrid/producteev/ProducteevPreferences.java +++ /dev/null @@ -1,102 +0,0 @@ -/** - * Copyright (c) 2012 Todoroo Inc - * - * See the file "LICENSE" for the full license governing this code. - */ -package com.todoroo.astrid.producteev; - -import android.content.res.Resources; -import android.os.Bundle; -import android.preference.ListPreference; -import android.preference.Preference; - -import com.timsu.astrid.R; -import com.todoroo.andlib.utility.AndroidUtilities; -import com.todoroo.astrid.data.StoreObject; -import com.todoroo.astrid.producteev.sync.ProducteevDashboard; -import com.todoroo.astrid.producteev.sync.ProducteevDataService; -import com.todoroo.astrid.producteev.sync.ProducteevSyncProvider; -import com.todoroo.astrid.sync.SyncProviderPreferences; -import com.todoroo.astrid.sync.SyncProviderUtilities; - -/** - * Displays synchronization preferences and an action panel so users can - * initiate actions from the menu. - * - * @author timsu - * - */ -public class ProducteevPreferences extends SyncProviderPreferences { - - @Override - public int getPreferenceResource() { - return R.xml.preferences_producteev; - } - - @Override - public void startSync() { - new ProducteevSyncProvider().synchronize(this); - finish(); - } - - @Override - public void logOut() { - new ProducteevSyncProvider().signOut(); - } - - @Override - public SyncProviderUtilities getUtilities() { - return ProducteevUtilities.INSTANCE; - } - - @Override - protected void onPause() { - super.onPause(); - new ProducteevBackgroundService().scheduleService(); - } - - @Override - public void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - - ListPreference defaultDash = (ListPreference)findPreference(getString(R.string.producteev_PPr_defaultdash_key)); - String[] entries, entryValues; - StoreObject[] dashboards = ProducteevDataService.getInstance().getDashboards(); - if(ProducteevUtilities.INSTANCE.isLoggedIn() && dashboards.length > 0) { - entries = new String[dashboards.length + 1]; - entryValues = new String[dashboards.length + 1]; - for(int i = 0; i < dashboards.length; i++) { - entries[i + 1] = dashboards[i].getValue(ProducteevDashboard.NAME); - entryValues[i + 1] = Long.toString(dashboards[i].getValue(ProducteevDashboard.REMOTE_ID)); - } - } else { - entries = new String[2]; - entries[1] = getString(R.string.producteev_default_dashboard); - entryValues = new String[2]; - entryValues[1] = Integer.toString(ProducteevUtilities.DASHBOARD_DEFAULT); - } - entries[0] = getString(R.string.producteev_no_dashboard); - entryValues[0] = Integer.toString(ProducteevUtilities.DASHBOARD_NO_SYNC); - defaultDash.setEntries(entries); - defaultDash.setEntryValues(entryValues); - } - - @Override - public void updatePreferences(Preference preference, Object value) { - super.updatePreferences(preference, value); - final Resources r = getResources(); - - if (r.getString(R.string.producteev_PPr_defaultdash_key).equals( - preference.getKey())) { - int index = AndroidUtilities.indexOf(((ListPreference)preference).getEntryValues(), (String)value); - if(index == -1) - index = 1; - if(index == 0) - preference.setSummary(R.string.producteev_PPr_defaultdash_summary_none); - else - preference.setSummary(r.getString( - R.string.producteev_PPr_defaultdash_summary, - ((ListPreference)preference).getEntries()[index])); - } - } -} diff --git a/astrid/plugin-src/com/todoroo/astrid/producteev/ProducteevStartupReceiver.java b/astrid/plugin-src/com/todoroo/astrid/producteev/ProducteevStartupReceiver.java deleted file mode 100644 index da2d81908..000000000 --- a/astrid/plugin-src/com/todoroo/astrid/producteev/ProducteevStartupReceiver.java +++ /dev/null @@ -1,28 +0,0 @@ -/** - * Copyright (c) 2012 Todoroo Inc - * - * See the file "LICENSE" for the full license governing this code. - */ -package com.todoroo.astrid.producteev; - -import android.content.BroadcastReceiver; -import android.content.Context; -import android.content.Intent; - -import com.todoroo.andlib.service.ContextManager; -import com.todoroo.astrid.service.AstridDependencyInjector; - -public class ProducteevStartupReceiver extends BroadcastReceiver { - - static { - AstridDependencyInjector.initialize(); - } - - @Override - /** Called when device is restarted */ - public void onReceive(final Context context, Intent intent) { - ContextManager.setContext(context); - new ProducteevBackgroundService().scheduleService(); - } - -} diff --git a/astrid/plugin-src/com/todoroo/astrid/producteev/ProducteevSyncActionExposer.java b/astrid/plugin-src/com/todoroo/astrid/producteev/ProducteevSyncActionExposer.java deleted file mode 100644 index 9562af7f4..000000000 --- a/astrid/plugin-src/com/todoroo/astrid/producteev/ProducteevSyncActionExposer.java +++ /dev/null @@ -1,43 +0,0 @@ -/** - * Copyright (c) 2012 Todoroo Inc - * - * See the file "LICENSE" for the full license governing this code. - */ -package com.todoroo.astrid.producteev; - -import android.app.PendingIntent; -import android.content.BroadcastReceiver; -import android.content.Context; -import android.content.Intent; - -import com.timsu.astrid.R; -import com.todoroo.andlib.service.ContextManager; -import com.todoroo.astrid.api.AstridApiConstants; -import com.todoroo.astrid.api.SyncAction; - -/** - * Exposes sync action - * - */ -public class ProducteevSyncActionExposer extends BroadcastReceiver { - - @Override - public void onReceive(Context context, Intent intent) { - ContextManager.setContext(context); - // if we aren't logged in, don't expose sync action - if(!ProducteevUtilities.INSTANCE.isLoggedIn()) - return; - - Intent syncIntent = new Intent(null, null, - context, ProducteevBackgroundService.class); - PendingIntent pendingIntent = PendingIntent.getService(context, 0, syncIntent, PendingIntent.FLAG_UPDATE_CURRENT); - SyncAction syncAction = new SyncAction(context.getString(R.string.producteev_PPr_header), - pendingIntent); - - Intent broadcastIntent = new Intent(AstridApiConstants.BROADCAST_SEND_SYNC_ACTIONS); - broadcastIntent.putExtra(AstridApiConstants.EXTRAS_ADDON, ProducteevUtilities.IDENTIFIER); - broadcastIntent.putExtra(AstridApiConstants.EXTRAS_RESPONSE, syncAction); - context.sendBroadcast(broadcastIntent, AstridApiConstants.PERMISSION_READ); - } - -} diff --git a/astrid/plugin-src/com/todoroo/astrid/producteev/ProducteevUtilities.java b/astrid/plugin-src/com/todoroo/astrid/producteev/ProducteevUtilities.java deleted file mode 100644 index 1afaae704..000000000 --- a/astrid/plugin-src/com/todoroo/astrid/producteev/ProducteevUtilities.java +++ /dev/null @@ -1,84 +0,0 @@ -/** - * Copyright (c) 2012 Todoroo Inc - * - * See the file "LICENSE" for the full license governing this code. - */ -package com.todoroo.astrid.producteev; - -import com.timsu.astrid.R; -import com.todoroo.andlib.utility.Preferences; -import com.todoroo.astrid.sync.SyncProviderUtilities; - -/** - * Displays synchronization preferences and an action panel so users can - * initiate actions from the menu. - * - * @author timsu - * - */ -public class ProducteevUtilities extends SyncProviderUtilities { - - /** add-on identifier */ - public static final String IDENTIFIER = "pdv"; //$NON-NLS-1$ - - public static final ProducteevUtilities INSTANCE = new ProducteevUtilities(); - - /** setting for dashboard to getting created */ - public static final int DASHBOARD_CREATE = -2; - - /** setting for dashboard to not synchronize */ - public static final int DASHBOARD_NO_SYNC = -1; - - /** setting for dashboard to use default one */ - public static final int DASHBOARD_DEFAULT = 0; - - @Override - public String getIdentifier() { - return IDENTIFIER; - } - - @Override - public int getSyncIntervalKey() { - return R.string.producteev_PPr_interval_key; - } - - // --- producteev-specific preferences - - public static final String PREF_SERVER_LAST_SYNC = IDENTIFIER + "_last_server"; //$NON-NLS-1$ - - public static final String PREF_SERVER_LAST_NOTIFICATION = IDENTIFIER + "_last_notification"; //$NON-NLS-1$ - - public static final String PREF_SERVER_LAST_ACTIVITY = IDENTIFIER + "_last_activity"; //$NON-NLS-1$ - - /** Producteev user's default dashboard. This is different from the - * preference key, which indicates where user wants to put new tasks */ - public static final String PREF_DEFAULT_DASHBOARD = IDENTIFIER + "_defaultdash"; //$NON-NLS-1$ - - /** Producteev user's id */ - public static final String PREF_USER_ID = IDENTIFIER + "_userid"; //$NON-NLS-1$ - - /** - * Gets default dashboard from setting - * @return DASHBOARD_NO_SYNC if should not sync, otherwise remote id - */ - public long getDefaultDashboard() { - int defaultDashboard = Preferences.getIntegerFromString(R.string.producteev_PPr_defaultdash_key, - DASHBOARD_DEFAULT); - if(defaultDashboard == DASHBOARD_NO_SYNC) - return DASHBOARD_NO_SYNC; - else if(defaultDashboard == DASHBOARD_DEFAULT) - return Preferences.getLong(PREF_DEFAULT_DASHBOARD, 0); - else - return (long) defaultDashboard; - } - - private ProducteevUtilities() { - // prevent instantiation - } - - @Override - public String getLoggedInUserName() { - return Preferences.getStringValue(R.string.producteev_PPr_email); - } - -} diff --git a/astrid/plugin-src/com/todoroo/astrid/producteev/api/ApiAuthenticationException.java b/astrid/plugin-src/com/todoroo/astrid/producteev/api/ApiAuthenticationException.java deleted file mode 100644 index afb8bdf79..000000000 --- a/astrid/plugin-src/com/todoroo/astrid/producteev/api/ApiAuthenticationException.java +++ /dev/null @@ -1,23 +0,0 @@ -/** - * Copyright (c) 2012 Todoroo Inc - * - * See the file "LICENSE" for the full license governing this code. - */ -package com.todoroo.astrid.producteev.api; - -/** - * Exception that is thrown when an authentication exception occurs and users - * need to sign in - * - * @author timsu - * - */ -public class ApiAuthenticationException extends ApiServiceException { - - private static final long serialVersionUID = 1696103465107607150L; - - public ApiAuthenticationException(String detailMessage) { - super(detailMessage); - } - -} diff --git a/astrid/plugin-src/com/todoroo/astrid/producteev/api/ApiResponseParseException.java b/astrid/plugin-src/com/todoroo/astrid/producteev/api/ApiResponseParseException.java deleted file mode 100644 index 990eba011..000000000 --- a/astrid/plugin-src/com/todoroo/astrid/producteev/api/ApiResponseParseException.java +++ /dev/null @@ -1,24 +0,0 @@ -/** - * Copyright (c) 2012 Todoroo Inc - * - * See the file "LICENSE" for the full license governing this code. - */ -package com.todoroo.astrid.producteev.api; - - -/** - * Exception that wraps an exception encountered during API invocation or - * processing. - * - * @author timsu - * - */ -public class ApiResponseParseException extends ApiServiceException { - - private static final long serialVersionUID = 5421855785088364483L; - - public ApiResponseParseException(Throwable throwable) { - super("Exception reading API response: " + throwable.getMessage()); //$NON-NLS-1$ - initCause(throwable); - } -} diff --git a/astrid/plugin-src/com/todoroo/astrid/producteev/api/ApiServiceException.java b/astrid/plugin-src/com/todoroo/astrid/producteev/api/ApiServiceException.java deleted file mode 100644 index 7c8ff5b70..000000000 --- a/astrid/plugin-src/com/todoroo/astrid/producteev/api/ApiServiceException.java +++ /dev/null @@ -1,40 +0,0 @@ -/** - * Copyright (c) 2012 Todoroo Inc - * - * See the file "LICENSE" for the full license governing this code. - */ -package com.todoroo.astrid.producteev.api; - -import java.io.IOException; - - -/** - * Exception that wraps an exception encountered during API invocation or - * processing. - * - * @author timsu - * - */ -public class ApiServiceException extends IOException { - - private static final long serialVersionUID = 8805573304840404684L; - - public ApiServiceException(String detailMessage) { - super(detailMessage); - } - - public ApiServiceException(Throwable throwable) { - super(throwable.getMessage()); - initCause(throwable); - } - - public ApiServiceException() { - super(); - } - - @Override - public String toString() { - return getClass().getSimpleName() + ": " + getMessage(); //$NON-NLS-1$ - } - -} diff --git a/astrid/plugin-src/com/todoroo/astrid/producteev/api/ApiSignatureException.java b/astrid/plugin-src/com/todoroo/astrid/producteev/api/ApiSignatureException.java deleted file mode 100644 index 357c37a82..000000000 --- a/astrid/plugin-src/com/todoroo/astrid/producteev/api/ApiSignatureException.java +++ /dev/null @@ -1,23 +0,0 @@ -/** - * Copyright (c) 2012 Todoroo Inc - * - * See the file "LICENSE" for the full license governing this code. - */ -package com.todoroo.astrid.producteev.api; - - -/** - * Exception that wraps a 403 exception - * - * @author timsu - * - */ -public class ApiSignatureException extends ApiServiceException { - - private static final long serialVersionUID = 4320984373933L; - - public ApiSignatureException(String detailMessage) { - super(detailMessage); - } - -} diff --git a/astrid/plugin-src/com/todoroo/astrid/producteev/api/ApiUtilities.java b/astrid/plugin-src/com/todoroo/astrid/producteev/api/ApiUtilities.java deleted file mode 100644 index 7f213bdcf..000000000 --- a/astrid/plugin-src/com/todoroo/astrid/producteev/api/ApiUtilities.java +++ /dev/null @@ -1,112 +0,0 @@ -/** - * Copyright (c) 2012 Todoroo Inc - * - * See the file "LICENSE" for the full license governing this code. - */ -package com.todoroo.astrid.producteev.api; - -import java.text.ParseException; -import java.text.SimpleDateFormat; -import java.util.Date; -import java.util.Locale; - -import org.json.JSONObject; - -import com.todoroo.andlib.service.ContextManager; -import com.todoroo.andlib.utility.DateUtilities; -import com.todoroo.astrid.data.Metadata; -import com.todoroo.astrid.notes.NoteMetadata; -import com.todoroo.astrid.producteev.sync.ProducteevDataService; - -/** - * Utilities for working with API responses and JSON objects - * - * @author timsu - * - */ -public final class ApiUtilities { - - private static final SimpleDateFormat timeParser = new SimpleDateFormat( - "EEE, dd MMM yyyy HH:mm:ss Z", Locale.US); //$NON-NLS-1$ - - private static final SimpleDateFormat timeWriter = new SimpleDateFormat( - "yyyy/MM/dd HH:mm:ss Z", Locale.US); //$NON-NLS-1$ - - private static final SimpleDateFormat dateWriter = new SimpleDateFormat( - "yyyy/MM/dd", Locale.US); //$NON-NLS-1$ - - /** - * Utility method to convert PDV time to unix time - * - * @param date - * @param defaultValue - * @return - */ - public static long producteevToUnixTime(String value, long defaultValue) { - synchronized(timeParser) { - try { - return DateUtilities.dateToUnixtime(timeParser.parse(value)); - } catch (ParseException e) { - return defaultValue; - } - } - } - - /** - * Utility method to convert unix time to PDV time - * @param time - * @return - */ - public static String unixTimeToProducteev(long time) { - synchronized(timeWriter) { - return timeWriter.format(DateUtilities.unixtimeToDate(time)); - } - } - - /** - * Utility method to convert unix date to PDV date - * @param time - * @return - */ - public static String unixDateToProducteev(long date) { - synchronized(dateWriter) { - return dateWriter.format(DateUtilities.unixtimeToDate(date)); - } - } - - /** - * Un-escape a Producteev string - * @param string - * @return - */ - public static String decode(String string) { - return StringEscapeUtils.unescapeHtml(string); - } - - - - /** - * Create metadata from json object - * @param note JSON object with params id_note and message - * @param creatorName - * @return - */ - @SuppressWarnings("nls") - public static Metadata createNoteMetadata(JSONObject note, String creatorName) { - Metadata metadata = new Metadata(); - metadata.setValue(Metadata.KEY, NoteMetadata.METADATA_KEY); - metadata.setValue(NoteMetadata.EXT_ID, note.optString("id_note")); - metadata.setValue(NoteMetadata.EXT_PROVIDER, ProducteevDataService.NOTE_PROVIDER); - metadata.setValue(NoteMetadata.BODY, ApiUtilities.decode(note.optString("message"))); - - long created = ApiUtilities.producteevToUnixTime(note.optString("time_create"), 0); - metadata.setValue(Metadata.CREATION_DATE, created); - - if(creatorName != null) - metadata.setValue(NoteMetadata.TITLE, creatorName); - else - metadata.setValue(NoteMetadata.TITLE, DateUtilities.getDateStringWithWeekday(ContextManager.getContext(), - new Date(created))); - return metadata; - } -} diff --git a/astrid/plugin-src/com/todoroo/astrid/producteev/api/ProducteevInvoker.java b/astrid/plugin-src/com/todoroo/astrid/producteev/api/ProducteevInvoker.java deleted file mode 100644 index 2bc2cc827..000000000 --- a/astrid/plugin-src/com/todoroo/astrid/producteev/api/ProducteevInvoker.java +++ /dev/null @@ -1,682 +0,0 @@ -/** - * Copyright (c) 2012 Todoroo Inc - * - * See the file "LICENSE" for the full license governing this code. - */ -package com.todoroo.astrid.producteev.api; - -import java.io.IOException; -import java.io.UnsupportedEncodingException; -import java.net.URLEncoder; -import java.security.NoSuchAlgorithmException; -import java.util.ArrayList; -import java.util.Collections; -import java.util.Comparator; - -import org.apache.commons.codec.digest.DigestUtils; -import org.json.JSONArray; -import org.json.JSONException; -import org.json.JSONObject; - -import android.text.TextUtils; - -import com.todoroo.andlib.utility.Pair; - -@SuppressWarnings("nls") -public class ProducteevInvoker { - - private final String URL = "https://api.producteev.com/"; - - private final String apiKey; - private final String apiSecret; - - /** saved credentials in case we need to re-log in */ - private String retryEmail; - private String retryPassword; - private String token = null; - - /** - * Create new producteev service - * @param apiKey - * @param apiSecret - */ - public ProducteevInvoker(String apiKey, String apiSecret) { - this.apiKey = apiKey; - this.apiSecret = apiSecret; - } - - // --- authentication and time - - /** - * Authenticate the given user - */ - public void authenticate(String email, String password) throws IOException, ApiServiceException { - retryEmail = email; - retryPassword = password; - JSONObject response = invokeGet("users/login.json", - "email", email, "password", password); - try { - token = response.getJSONObject("login").getString("token"); - } catch (JSONException e) { - throw new ApiServiceException(e); - } - - } - - public boolean hasToken() { - return token != null; - } - - public void setCredentials(String token, String email, String password) { - this.token = token; - retryEmail = email; - retryPassword = password; - } - - public String getToken() { - return token; - } - - /** - * Gets Server time - */ - public String time() throws IOException, ApiServiceException { - JSONObject response = invokeGet("time.json"); - try { - return response.getJSONObject("time").getString("value"); - } catch (JSONException e) { - throw new ApiServiceException(e); - } - - } - - // --- users - - /** - * Sign up as the given user - */ - public JSONObject usersSignUp(String email, String firstName, String lastName, String - password, String timezone, Long fbUid) throws IOException, ApiServiceException { - return invokeGet("users/signup.json", - "email", email, - "firstname", firstName, - "lastname", lastName, - "password", password, - "timezone", timezone, - "fbuid", fbUid); - } - - // --- dashboards - - /** - * show list - * - * @param idResponsible (optional) if null return every task for current user - * @param since (optional) if not null, the function only returns tasks modified or created since this date - * - * @return array tasks/view - */ - public JSONArray dashboardsShowList(String since) throws ApiServiceException, IOException { - return getResponse(callAuthenticated("dashboards/show_list.json", - "token", token, - "since", since), "dashboards"); - } - - /** - * create a dasbhoard - * - * @param name - * @return the new created dashboard as JSONObject - */ - public JSONObject dashboardsCreate(String name) throws ApiServiceException, IOException { - return callAuthenticated("dashboards/create.json", - "token", token, - "title", name); - } - - /** - * return the list of users who can access a specific dashboard - * - * @param idDashboard - * @param dashboard array-information about the dashboard, if this ... - */ - public JSONArray dashboardsAccess(long idDashboard, String dashboard) throws ApiServiceException, IOException { - return getResponse(callAuthenticated("dashboards/access.json", - "token", token, - "id_dashboard", idDashboard, - "dashboard", dashboard),"dashboard"); - } - - // --- tasks - - /** - * create a task - * - * @param title - * @param idResponsible (optional) - * @param idDashboard (optional); - * @param deadline (optional) - * @param reminder (optional) 0 = "None", 1 = "5 minutes before", 2 = "15 minutes before", 3 = "30 minutes before", 4 = "1 hour before", 5 = "2 hours before", 6 = "1 day before", 7 = "2 days before", 8 = "on date of event" - * @param status (optional) (1 = UNDONE, 2 = DONE) - * @param star (optional) (0 to 5 stars) - * - * @return task - */ - public JSONObject tasksCreate(String title, Long idResponsible, Long idDashboard, - String deadline, Integer reminder, Integer status, Integer star) throws ApiServiceException, IOException { - return callAuthenticated("tasks/create.json", - "token", token, - "title", title, - "id_responsible", idResponsible, - "id_dashboard", idDashboard, - "deadline", deadline, - "reminder", reminder, - "status", status, - "star", star); - } - - /** - * show list - * - * @param idResponsible (optional) if null return every task for current user - * @param since (optional) if not null, the function only returns tasks modified or created since this date - * - * @return array tasks/view - */ - public JSONArray tasksShowList(Long idDashboard, String since) throws ApiServiceException, IOException { - return getResponse(callAuthenticated("tasks/show_list.json", - "token", token, - "id_dashboard", idDashboard, - "since", since), "tasks"); - } - - /** - * get a task - * - * @param idTask - * - * @return array tasks/view - */ - public JSONObject tasksView(Long idTask) throws ApiServiceException, IOException { - return callAuthenticated("tasks/view.json", - "token", token, - "id_task", idTask); - } - - /** - * change title of a task - * - * @param idTask - * @param title - * - * @return array tasks/view - */ - public JSONObject tasksSetTitle(long idTask, String title) throws ApiServiceException, IOException { - return callAuthenticated("tasks/set_title.json", - "token", token, - "id_task", idTask, - "title", title); - } - - /** - * set status of a task - * - * @param idTask - * @param status (1 = UNDONE, 2 = DONE) - * - * @return array tasks/view - */ - public JSONObject tasksSetStatus(long idTask, int status) throws ApiServiceException, IOException { - return callAuthenticated("tasks/set_status.json", - "token", token, - "id_task", idTask, - "status", status); - } - - /** - * set star status of a task - * - * @param idTask - * @param star (0 to 5 stars) - * - * @return array tasks/view - */ - public JSONObject tasksSetStar(long idTask, int star) throws ApiServiceException, IOException { - return callAuthenticated("tasks/set_star.json", - "token", token, - "id_task", idTask, - "star", star); - } - - /** - * set a deadline - * - * @param idTask - * @param deadline - * @param allDay (optional), 1: all day task (time specified in deadline will be ignored), 0: deadline with time - * - * @return array tasks/view - */ - public JSONObject tasksSetDeadline(long idTask, String deadline, Integer allDay) throws ApiServiceException, IOException { - return callAuthenticated("tasks/set_deadline.json", - "token", token, - "id_task", idTask, - "deadline", deadline, - "all_day", allDay); - } - - /** - * unset a deadline - * - * @param idTask - * - * @return array tasks/view - */ - public JSONObject tasksUnsetDeadline(long idTask) throws ApiServiceException, IOException { - return callAuthenticated("tasks/unset_deadline.json", - "token", token, - "id_task", idTask); - } - - /** - * set repeating - * - * @param idTask - * @param repeatInterval - * @param repeatValue - * - * @return array tasks/view - * @throws IOException - * @throws ApiServiceException - */ - public JSONObject tasksSetRepeating(long idTask, String repeatInterval, Integer repeatValue) throws ApiServiceException, IOException { - return callAuthenticated("tasks/set_repeating.json", - "token", token, - "id_task", idTask, - "repeat_interval", repeatInterval, - "repeat_value", (repeatValue == null ? 1 : repeatValue)); - } - - /** - * unset repeating - * - * @param idTask - * - * @return array tasks/view - * @throws IOException - * @throws ApiServiceException - */ - public JSONObject tasksUnsetRepeating(long idTask) throws ApiServiceException, IOException { - return callAuthenticated("tasks/unset_repeating.json", - "token", token, - "id_task", idTask); - } - - /** - * set a workspace - * - * @param idTask - * @param id_dashboard - * - * @return array tasks/view - */ - public JSONObject tasksSetWorkspace(long idTask, long idDashboard) throws ApiServiceException, IOException { - return callAuthenticated("tasks/set_workspace.json", - "token", token, - "id_task", idTask, - "id_dashboard", idDashboard); - } - - /** - * delete a task - * - * @param idTask - * - * @return array with the result = (Array("stats" => Array("result" => "TRUE|FALSE")) - */ - public JSONObject tasksDelete(long idTask) throws ApiServiceException, IOException { - return callAuthenticated("tasks/delete.json", - "token", token, - "id_task", idTask); - } - - /** - * get labels assigned to a task - * - * @param idTask - * - * @return array: list of labels/view - */ - public JSONArray tasksLabels(long idTask) throws ApiServiceException, IOException { - return getResponse(callAuthenticated("tasks/labels.json", - "token", token, - "id_task", idTask), "labels"); - } - - /** - * change labels for a task - * - * @param idTask - * @param idLabels - * - * @return array: tasks/view - */ - public JSONObject tasksChangeLabel(long idTask, long... idLabels) throws ApiServiceException, IOException { - Object[] parameters = new Object[2 * (idLabels.length + 2)]; - parameters[0] = "token"; parameters[1] = token; - parameters[2] = "id_task"; parameters[3] = idTask; - for(int i = 0; i < idLabels.length; i++) { - parameters[i * 2 + 4] = "id_label[]"; - parameters[i * 2 + 5] = idLabels[i]; - } - return callAuthenticated("tasks/change_labels.json", parameters); - } - - /** - * set a labels to a task - * - * @param idTask - * @param idLabels - * - * @return array: tasks/view - */ - @Deprecated - public JSONObject tasksSetLabel(long idTask, long idLabel) throws ApiServiceException, IOException { - return callAuthenticated("tasks/set_label.json", - "token", token, - "id_task", idTask, - "id_label", idLabel); - } - - /** - * set a labels to a task - * - * @param idTask - * @param idLabel - * - * @return array: tasks/view - */ - @Deprecated - public JSONObject tasksUnsetLabel(long idTask, long idLabel) throws ApiServiceException, IOException { - return callAuthenticated("tasks/unset_label.json", - "token", token, - "id_task", idTask, - "id_label", idLabel); - } - - /** - * change responsible of a task - * - * @param idTask - * @param idResponsible - * - * @return array: tasks/view - */ - public JSONObject tasksSetResponsible(long idTask, long idResponsible) throws ApiServiceException, IOException { - return callAuthenticated("tasks/set_responsible.json", - "token", token, - "id_task", idTask, - "id_responsible", idResponsible); - } - - /** - * change responsible of a task - * - * @param idTask - * @param idResponsible - * - * @return array: tasks/view - */ - public JSONObject tasksUnsetResponsible(long idTask) throws ApiServiceException, IOException { - return callAuthenticated("tasks/unset_responsible.json", - "token", token, - "id_task", idTask); - } - - /** - * create a note attached to a task - * - * @param idTask - * @param message - * - * @return array tasks::note_view - */ - public JSONObject tasksNoteCreate(long idTask, String message) throws ApiServiceException, IOException { - return callAuthenticated("tasks/note_create.json", - "token", token, - "id_task", idTask, - "message", message); - } - - // --- labels - - /** - * get every label for a given dashboard - * - * @param idTask - * @param idLabel - * - * @return array: labels/view - */ - public JSONArray labelsShowList(long idDashboard, String since) throws ApiServiceException, IOException { - return getResponse(callAuthenticated("labels/show_list.json", - "token", token, - "id_dashboard", idDashboard, - "since", since), "labels"); - } - - /** - * create a label - * - * @param idDashboard - * @param title - * - * @return array: labels/view - */ - public JSONObject labelsCreate(long idDashboard, String title) throws ApiServiceException, IOException { - return callAuthenticated("labels/create.json", - "token", token, - "id_dashboard", idDashboard, - "title", title); - } - - public JSONObject labelsDelete(long idLabel) throws ApiServiceException, IOException { - return callAuthenticated("labels/delete.json", - "token", token, - "id_label", idLabel); - } - - // --- notifications/activities - - /** - * get every activities - * - * @param dashboardId (optional) if not null, this function only returns notifications for this specific dashboard - * @param lastId (optional) this function returns only activities later than this id - */ - public JSONArray activitiesShowActivities(Long dashboardId, Long lastId) throws ApiResponseParseException, ApiServiceException, IOException { - return getResponse(callAuthenticated("activities/show_activities.json", - "token", token, - "id_dashboard", dashboardId, - "last_id", lastId), "activities"); - } - - /** - * get every notification for the current user - * @param dashboardId - * @param lastId - * @return - * @throws ApiResponseParseException - * @throws ApiServiceException - * @throws IOException - */ - public JSONArray activitiesShowNotifications(Long dashboardId, Long lastId) throws ApiResponseParseException, ApiServiceException, IOException { - return getResponse(callAuthenticated("activities/show_notifications.json", - "token", token, - "id_dashboard", dashboardId, - "last_id", lastId), "activities"); - } - // --- users - - /** - * get a user - * - * @param idColleague - * - * @return array information about the user - */ - public JSONObject usersView(Long idColleague) throws ApiServiceException, IOException { - return callAuthenticated("users/view.json", - "token", token, - "id_colleague", idColleague); - } - - public JSONObject usersColleagues() throws ApiServiceException, IOException { - return callAuthenticated("users/colleagues.json", - "token", token); - } - - // --- invocation - - private final ProducteevRestClient restClient = new ProducteevRestClient(); - - /** - * Invokes authenticated method using HTTP GET. Will retry after re-authenticating if service exception encountered - * - * @param method - * API method to invoke - * @param getParameters - * Name/Value pairs. Values will be URL encoded. - * @return response object - */ - private JSONObject callAuthenticated(String method, Object... getParameters) - throws IOException, ApiServiceException { - try { - String request = createFetchUrl(method, getParameters); - String response = null; - try { - response = restClient.get(request); - } catch (ApiSignatureException e) { - // clear cookies, get new token, retry - String oldToken = token; - restClient.reset(); - authenticate(retryEmail, retryPassword); - for(int i = 0; i < getParameters.length; i++) - if(oldToken.equals(getParameters[i])) { - getParameters[i] = getToken(); - } - request = createFetchUrl(method, getParameters); - try { - response = restClient.get(request); - } catch (ApiSignatureException newException) { - // - } - - if(response == null) - throw e; - } - if(response.startsWith("DEBUG MESSAGE")) { - System.err.println(response); - return new JSONObject(); - } - try { - if(TextUtils.isEmpty(response)) - return new JSONObject(); - - return new JSONObject(response); - } catch (JSONException e) { - System.err.println(response); - throw new ApiResponseParseException(e); - } - } catch (NoSuchAlgorithmException e) { - throw new RuntimeException(e); - } - } - - /** - * Invokes API method using HTTP GET - * - * @param method - * API method to invoke - * @param getParameters - * Name/Value pairs. Values will be URL encoded. - * @return response object - */ - JSONObject invokeGet(String method, Object... getParameters) throws IOException, ApiServiceException { - try { - String request = createFetchUrl(method, getParameters); - String response = restClient.get(request); - if(response.startsWith("DEBUG MESSAGE")) { - System.err.println(response); - return new JSONObject(); - } - return new JSONObject(response); - } catch (JSONException e) { - throw new ApiResponseParseException(e); - } catch (NoSuchAlgorithmException e) { - throw new RuntimeException(e); - } - } - - /** - * Creates a URL for invoking an HTTP GET/POST on the given method - * @param method - * @param getParameters - * @return - * @throws UnsupportedEncodingException - * @throws NoSuchAlgorithmException - */ - String createFetchUrl(String method, Object... getParameters) throws UnsupportedEncodingException, NoSuchAlgorithmException { - ArrayList> params = new ArrayList>(); - for(int i = 0; i < getParameters.length; i += 2) - params.add(new Pair(getParameters[i].toString(), getParameters[i+1])); - params.add(new Pair("api_key", apiKey)); - - Collections.sort(params, new Comparator>() { - @Override - public int compare(Pair object1, - Pair object2) { - return object1.getLeft().compareTo(object2.getLeft()); - } - }); - - StringBuilder requestBuilder = new StringBuilder(URL).append(method).append('?'); - StringBuilder sigBuilder = new StringBuilder(); - for(Pair entry : params) { - if(entry.getRight() == null) - continue; - - String key = entry.getLeft(); - String value = entry.getRight().toString(); - String encoded = URLEncoder.encode(value, "UTF-8"); - - requestBuilder.append(key).append('=').append(encoded).append('&'); - - if(!key.endsWith("[]")) - sigBuilder.append(key).append(value); - } - - sigBuilder.append(apiSecret); - String signature = DigestUtils.md5Hex(sigBuilder.toString()); - requestBuilder.append("api_sig").append('=').append(signature); - return requestBuilder.toString(); - } - - /** - * Helper method to get a field out or throw an api exception - * @param response - * @param field - * @return - * @throws ApiResponseParseException - */ - private JSONArray getResponse(JSONObject response, String field) throws ApiResponseParseException { - try { - if(!response.has(field)) - return new JSONArray(); - return response.getJSONArray(field); - } catch (JSONException e) { - throw new ApiResponseParseException(e); - } - } - -} diff --git a/astrid/plugin-src/com/todoroo/astrid/producteev/api/ProducteevRestClient.java b/astrid/plugin-src/com/todoroo/astrid/producteev/api/ProducteevRestClient.java deleted file mode 100644 index 7179cb786..000000000 --- a/astrid/plugin-src/com/todoroo/astrid/producteev/api/ProducteevRestClient.java +++ /dev/null @@ -1,188 +0,0 @@ -/** - * Copyright (c) 2012 Todoroo Inc - * - * See the file "LICENSE" for the full license governing this code. - */ -package com.todoroo.astrid.producteev.api; - -import java.io.BufferedReader; -import java.io.IOException; -import java.io.InputStream; -import java.io.InputStreamReader; - -import org.apache.http.Header; -import org.apache.http.HttpEntity; -import org.apache.http.HttpResponse; -import org.apache.http.client.HttpClient; -import org.apache.http.client.methods.HttpGet; -import org.apache.http.client.methods.HttpPost; -import org.apache.http.impl.client.DefaultHttpClient; -import org.apache.http.params.BasicHttpParams; -import org.apache.http.params.HttpConnectionParams; -import org.apache.http.params.HttpParams; -import org.json.JSONObject; - -import com.todoroo.andlib.service.RestClient; -import com.todoroo.astrid.utility.Constants; - -/** - * RestClient allows Android to consume web requests. - *

- * Portions by Praeda: - * http://senior.ceng.metu.edu.tr/2009/praeda/2009/01/11/a-simple - * -restful-client-at-android/ - * - * @author Tim Su - * - */ -public class ProducteevRestClient implements RestClient { - - private static final int HTTP_OK = 200; - - private static final int TIMEOUT_MILLIS = 30000; - - private static HttpClient httpClient = null; - - private static String convertStreamToString(InputStream is) { - /* - * To convert the InputStream to String we use the - * BufferedReader.readLine() method. We iterate until the BufferedReader - * return null which means there's no more data to read. Each line will - * appended to a StringBuilder and returned as String. - */ - BufferedReader reader = new BufferedReader(new InputStreamReader(is), 16384); - StringBuilder sb = new StringBuilder(); - - String line = null; - try { - while ((line = reader.readLine()) != null) { - sb.append(line + "\n"); //$NON-NLS-1$ - } - } catch (IOException e) { - e.printStackTrace(); - } finally { - try { - is.close(); - } catch (IOException e) { - e.printStackTrace(); - } - } - - return sb.toString(); - } - - private synchronized static void initializeHttpClient() { - if (httpClient == null) { - HttpParams params = new BasicHttpParams(); - HttpConnectionParams.setConnectionTimeout(params, TIMEOUT_MILLIS); - HttpConnectionParams.setSoTimeout(params, TIMEOUT_MILLIS); - httpClient = new DefaultHttpClient(params); - } - } - - private String processHttpResponse(HttpResponse response) throws IOException, ApiServiceException { - HttpEntity entity = response.getEntity(); - String body = null; - if (entity != null) { - InputStream contentStream = entity.getContent(); - try { - body = convertStreamToString(contentStream); - } finally { - contentStream.close(); - } - } - if(Constants.DEBUG) - System.err.println(body); - - int statusCode = response.getStatusLine().getStatusCode(); - if(statusCode != HTTP_OK || (body != null && body.startsWith("{\"error\":"))) { //$NON-NLS-1$ - ApiServiceException error; - try { - JSONObject errorObject = new JSONObject(body).getJSONObject("error"); //$NON-NLS-1$ - String errorMessage = errorObject.getString("message"); //$NON-NLS-1$ - - if(statusCode == 403) - error = new ApiSignatureException(errorMessage); - else if(statusCode == 401) - error = new ApiAuthenticationException(errorMessage); - else - error = new ApiServiceException(errorMessage); - } catch (Exception e) { - if(statusCode == 401) - error = new ApiAuthenticationException(response.getStatusLine().getReasonPhrase()); - else - error = new ApiServiceException(response.getStatusLine() + - "\n" + body); //$NON-NLS-1$ - } - throw error; - } - - - return body; - } - - /** - * Issue an HTTP GET for the given URL, return the response - * - * @param url url with url-encoded params - * @return response, or null if there was no response - * @throws IOException - */ - public synchronized String get(String url) throws IOException { - initializeHttpClient(); - - if(Constants.DEBUG) - System.err.println("GET: " + url); //$NON-NLS-1$ // (debug) - - try { - HttpGet httpGet = new HttpGet(url); - HttpResponse response = httpClient.execute(httpGet); - - return processHttpResponse(response); - } catch (IOException e) { - throw e; - } catch (Exception e) { - IOException ioException = new IOException(e.getMessage()); - ioException.initCause(e); - throw ioException; - } - } - - /** - * Issue an HTTP POST for the given URL, return the response - * - * @param url - * @param data - * url-encoded data - * @param headers - * @throws IOException - */ - public synchronized String post(String url, HttpEntity data, Header... headers) throws IOException { - initializeHttpClient(); - - if(Constants.DEBUG) - System.err.println("POST: " + url); //$NON-NLS-1$ // (debug) - - try { - HttpPost httpPost = new HttpPost(url); - httpPost.setEntity(data); - HttpResponse response = httpClient.execute(httpPost); - - return processHttpResponse(response); - } catch (IOException e) { - throw e; - } catch (Exception e) { - IOException ioException = new IOException(e.getMessage()); - ioException.initCause(e); - throw ioException; - } - } - - /** - * Destroy and re-create http client - */ - public void reset() { - httpClient = null; - } - -} diff --git a/astrid/plugin-src/com/todoroo/astrid/producteev/sync/ProducteevDashboard.java b/astrid/plugin-src/com/todoroo/astrid/producteev/sync/ProducteevDashboard.java deleted file mode 100644 index 485ed33de..000000000 --- a/astrid/plugin-src/com/todoroo/astrid/producteev/sync/ProducteevDashboard.java +++ /dev/null @@ -1,123 +0,0 @@ -/** - * Copyright (c) 2012 Todoroo Inc - * - * See the file "LICENSE" for the full license governing this code. - */ -package com.todoroo.astrid.producteev.sync; - -import java.util.ArrayList; -import java.util.StringTokenizer; - -import com.todoroo.andlib.data.Property.LongProperty; -import com.todoroo.andlib.data.Property.StringProperty; -import com.todoroo.astrid.data.StoreObject; - -/** - * {@link StoreObject} entries for a Producteev Dashboard - * - * @author Tim Su - * - */ -public class ProducteevDashboard { - - /** type*/ - public static final String TYPE = "pdv-dash"; //$NON-NLS-1$ - - /** dashboard id in producteev */ - public static final LongProperty REMOTE_ID = new LongProperty(StoreObject.TABLE, - StoreObject.ITEM.name); - - /** dashboard name */ - public static final StringProperty NAME = new StringProperty(StoreObject.TABLE, - StoreObject.VALUE1.name); - - /** users (list in the format "id_user,name;id_user,name;") */ - public static final StringProperty USERS = new StringProperty(StoreObject.TABLE, - StoreObject.VALUE2.name); - - // data class-part - private final long id; - - private final String name; - - private ArrayList users = null; - - public ProducteevDashboard (StoreObject dashboardData) { - this(dashboardData.getValue(REMOTE_ID),dashboardData.getValue(NAME),(dashboardData.containsValue(USERS)?dashboardData.getValue(USERS):null)); - } - - /** - * Constructor for a dashboard. - * - * @param id id of the remote dashboard - * @param name name of the remote dashboard - * @param usercsv csv-userstring as returned by a StoreObject-dashboard with property ProducteevDashboard.USERS - */ - @SuppressWarnings("nls") - public ProducteevDashboard(long id, String name, String usercsv) { - this.id = id; - this.name = name; - - if (usercsv == null) - return; - - StringTokenizer tokenizer = new StringTokenizer(usercsv, ";"); - int usercount = tokenizer.countTokens(); - - while (tokenizer.hasMoreTokens()) { - String userdata = tokenizer.nextToken(); - int delim_index = userdata.indexOf(","); - String userid = userdata.substring(0, delim_index); - String username = userdata.substring(delim_index+1); - int name_gap = username.indexOf(" "); - String firstname = (name_gap == -1 ? username : username.substring(0,name_gap)); - String lastname = (name_gap == -1 ? null : username.substring(name_gap+1)); - if (users == null) { - users = new ArrayList(usercount); - } - users.add(new ProducteevUser(Long.parseLong(userid),null,firstname,lastname)); - } - } - - /** - * @return the id - */ - public long getId() { - return id; - } - - /** - * @return the name - */ - public String getName() { - return name; - } - - /** - * return the name of this dashboard - */ - @Override - public String toString() { - return name; - } - - /** - * @return the users - */ - public ArrayList getUsers() { - if (users == null) { - return new ArrayList(); // Don't return null - } - return users; - } - - /** Try and find user in the dashboard. return null if un-findable */ - public static String getUserFromDashboard(StoreObject dashboard, long userId) { - String users = ";" + dashboard.getValue(USERS); //$NON-NLS-1$ - int index = users.indexOf(";" + userId + ","); //$NON-NLS-1$ //$NON-NLS-2$ - if(index > -1) - return users.substring(users.indexOf(',', index) + 1, - users.indexOf(';', index + 1)); - return null; - } -} diff --git a/astrid/plugin-src/com/todoroo/astrid/producteev/sync/ProducteevDataService.java b/astrid/plugin-src/com/todoroo/astrid/producteev/sync/ProducteevDataService.java deleted file mode 100644 index 3393f22c6..000000000 --- a/astrid/plugin-src/com/todoroo/astrid/producteev/sync/ProducteevDataService.java +++ /dev/null @@ -1,310 +0,0 @@ -/** - * Copyright (c) 2012 Todoroo Inc - * - * See the file "LICENSE" for the full license governing this code. - */ -package com.todoroo.astrid.producteev.sync; - -import java.util.ArrayList; -import java.util.Random; - -import org.json.JSONArray; -import org.json.JSONException; -import org.json.JSONObject; - -import android.content.Context; - -import com.todoroo.andlib.data.Property; -import com.todoroo.andlib.data.TodorooCursor; -import com.todoroo.andlib.service.Autowired; -import com.todoroo.andlib.service.ContextManager; -import com.todoroo.andlib.service.DependencyInjectionService; -import com.todoroo.andlib.sql.Criterion; -import com.todoroo.andlib.sql.Join; -import com.todoroo.andlib.sql.Query; -import com.todoroo.astrid.core.PluginServices; -import com.todoroo.astrid.dao.MetadataDao.MetadataCriteria; -import com.todoroo.astrid.dao.StoreObjectDao; -import com.todoroo.astrid.dao.StoreObjectDao.StoreObjectCriteria; -import com.todoroo.astrid.dao.TaskDao; -import com.todoroo.astrid.dao.TaskDao.TaskCriteria; -import com.todoroo.astrid.data.Metadata; -import com.todoroo.astrid.data.StoreObject; -import com.todoroo.astrid.data.Task; -import com.todoroo.astrid.notes.NoteMetadata; -import com.todoroo.astrid.producteev.ProducteevUtilities; -import com.todoroo.astrid.producteev.api.ApiUtilities; -import com.todoroo.astrid.service.MetadataService; -import com.todoroo.astrid.tags.TaskToTagMetadata; - -public final class ProducteevDataService { - - // --- constants - - /** Utility for joining tasks with metadata */ - public static final Join METADATA_JOIN = Join.left(Metadata.TABLE, Task.ID.eq(Metadata.TASK)); - - /** NoteMetadata provider string */ - public static final String NOTE_PROVIDER = "producteev"; //$NON-NLS-1$ - - // --- singleton - - private static ProducteevDataService instance = null; - - public static synchronized ProducteevDataService getInstance() { - if(instance == null) - instance = new ProducteevDataService(ContextManager.getContext()); - return instance; - } - - // --- instance variables - - protected final Context context; - - @Autowired - private TaskDao taskDao; - - @Autowired - private MetadataService metadataService; - - @Autowired - private StoreObjectDao storeObjectDao; - - private final ProducteevUtilities preferences = ProducteevUtilities.INSTANCE; - - static final Random random = new Random(); - - private ProducteevDataService(Context context) { - this.context = context; - DependencyInjectionService.getInstance().inject(this); - } - - // --- task and metadata methods - - /** - * Clears metadata information. Used when user logs out of service - */ - public void clearMetadata() { - PluginServices.getTaskService().clearDetails(Task.ID.in(Query.select(Metadata.TASK).from(Metadata.TABLE). - where(MetadataCriteria.withKey(ProducteevTask.METADATA_KEY)))); - metadataService.deleteWhere(Metadata.KEY.eq(ProducteevTask.METADATA_KEY)); - storeObjectDao.deleteWhere(StoreObject.TYPE.eq(ProducteevDashboard.TYPE)); - } - - /** - * Gets tasks that were created since last sync - * @param properties - * @return - */ - public TodorooCursor getLocallyCreated(Property[] properties) { - return - taskDao.query(Query.select(properties).join(ProducteevDataService.METADATA_JOIN).where( - Criterion.and( - Criterion.not(Task.ID.in(Query.select(Metadata.TASK).from(Metadata.TABLE). - where(Criterion.and(MetadataCriteria.withKey(ProducteevTask.METADATA_KEY), ProducteevTask.ID.gt(0))))), - TaskCriteria.isActive())). - groupBy(Task.ID)); - } - - /** - * Gets tasks that were modified since last sync - * @param properties - * @return null if never sync'd - */ - public TodorooCursor getLocallyUpdated(Property[] properties) { - long lastSyncDate = preferences.getLastSyncDate(); - if(lastSyncDate == 0) - return taskDao.query(Query.select(Task.ID).where(Criterion.none)); - return - taskDao.query(Query.select(properties).join(ProducteevDataService.METADATA_JOIN).where( - Criterion.and( - MetadataCriteria.withKey(ProducteevTask.METADATA_KEY), - ProducteevTask.ID.gt(0), - Task.MODIFICATION_DATE.gt(lastSyncDate))). - groupBy(Task.ID)); - } - - /** - * Searches for a local task with same remote id, updates this task's id - * @param remoteTask - * @return true if found local match - */ - public boolean findLocalMatch(ProducteevTaskContainer remoteTask) { - if(remoteTask.task.getId() != Task.NO_ID) - return true; - TodorooCursor cursor = taskDao.query(Query.select(Task.ID). - join(ProducteevDataService.METADATA_JOIN).where(Criterion.and(MetadataCriteria.withKey(ProducteevTask.METADATA_KEY), - ProducteevTask.ID.eq(remoteTask.pdvTask.getValue(ProducteevTask.ID))))); - try { - if(cursor.getCount() == 0) - return false; - cursor.moveToFirst(); - remoteTask.task.setId(cursor.get(Task.ID)); - return true; - } finally { - cursor.close(); - } - } - - /** - * Saves a task and its metadata - * @param task - */ - public void saveTaskAndMetadata(ProducteevTaskContainer task) { - taskDao.save(task.task); - - task.metadata.add(task.pdvTask); - // note we don't include note metadata, since we only receive deltas - metadataService.synchronizeMetadata(task.task.getId(), task.metadata, - Criterion.or(MetadataCriteria.withKey(ProducteevTask.METADATA_KEY), - MetadataCriteria.withKey(TaskToTagMetadata.KEY)), true); - } - - /** - * Reads a task and its metadata - * @param task - * @return - */ - public ProducteevTaskContainer readTaskAndMetadata(TodorooCursor taskCursor) { - Task task = new Task(taskCursor); - - // read tags, notes, etc - ArrayList metadata = new ArrayList(); - TodorooCursor metadataCursor = metadataService.query(Query.select(Metadata.PROPERTIES). - where(Criterion.and(MetadataCriteria.byTask(task.getId()), - Criterion.or(MetadataCriteria.withKey(TaskToTagMetadata.KEY), - MetadataCriteria.withKey(ProducteevTask.METADATA_KEY), - MetadataCriteria.withKey(NoteMetadata.METADATA_KEY))))); - try { - for(metadataCursor.moveToFirst(); !metadataCursor.isAfterLast(); metadataCursor.moveToNext()) { - metadata.add(new Metadata(metadataCursor)); - } - } finally { - metadataCursor.close(); - } - - return new ProducteevTaskContainer(task, metadata); - } - - /** - * Reads metadata out of a task - * @return null if no metadata found - */ - public Metadata getTaskMetadata(long taskId) { - TodorooCursor cursor = metadataService.query(Query.select( - Metadata.PROPERTIES).where( - MetadataCriteria.byTaskAndwithKey(taskId, ProducteevTask.METADATA_KEY))); - try { - if(cursor.getCount() == 0) - return null; - cursor.moveToFirst(); - return new Metadata(cursor); - } finally { - cursor.close(); - } - } - - /** - * Reads task notes out of a task - */ - public TodorooCursor getTaskNotesCursor(long taskId) { - TodorooCursor cursor = metadataService.query(Query.select(Metadata.PROPERTIES). - where(MetadataCriteria.byTaskAndwithKey(taskId, NoteMetadata.METADATA_KEY))); - return cursor; - } - - private void readDashboards() { - if (dashboards == null) { - dashboards = readStoreObjects(ProducteevDashboard.TYPE); - } - } - - /** - * Reads store objects. - */ - public StoreObject[] readStoreObjects(String type) { - StoreObject[] ret; - TodorooCursor cursor = storeObjectDao.query(Query.select(StoreObject.PROPERTIES). - where(StoreObjectCriteria.byType(type))); - try { - ret = new StoreObject[cursor.getCount()]; - for(int i = 0; i < ret.length; i++) { - cursor.moveToNext(); - StoreObject dashboard = new StoreObject(cursor); - ret[i] = dashboard; - } - } finally { - cursor.close(); - } - - return ret; - } - - // --- dashboard methods - - private StoreObject[] dashboards = null; - - /** - * @return a list of dashboards - */ - public StoreObject[] getDashboards() { - readDashboards(); - return dashboards; - } - - /** - * Reads dashboards - * @throws JSONException - */ - @SuppressWarnings("nls") - public void updateDashboards(JSONArray changedDashboards) throws JSONException { - readDashboards(); - for(int i = 0; i < changedDashboards.length(); i++) { - JSONObject remote = changedDashboards.getJSONObject(i).getJSONObject("dashboard"); - updateDashboards(remote, false); - } - - // clear dashboard cache - dashboards = null; - } - - @SuppressWarnings("nls") - public StoreObject updateDashboards(JSONObject remote, boolean reinitCache) throws JSONException { - if (reinitCache) - readDashboards(); - long id = remote.getLong("id_dashboard"); - StoreObject local = null; - for(StoreObject dashboard : dashboards) { - if(dashboard.getValue(ProducteevDashboard.REMOTE_ID).equals(id)) { - local = dashboard; - break; - } - } - - if(remote.getInt("deleted") != 0) { - if(local != null) - storeObjectDao.delete(local.getId()); - } - - if(local == null) - local = new StoreObject(); - local.setValue(StoreObject.TYPE, ProducteevDashboard.TYPE); - local.setValue(ProducteevDashboard.REMOTE_ID, id); - local.setValue(ProducteevDashboard.NAME, ApiUtilities.decode(remote.getString("title"))); - - StringBuilder users = new StringBuilder(); - JSONArray accessList = remote.getJSONArray("accesslist"); - for(int j = 0; j < accessList.length(); j++) { - JSONObject user = accessList.getJSONObject(j).getJSONObject("user"); - users.append(user.getLong("id_user")).append(','); - String name = ApiUtilities.decode(user.optString("firstname", "") + ' ' + - user.optString("lastname", "")); - users.append(name.trim()).append(';'); - } - local.setValue(ProducteevDashboard.USERS, users.toString()); - storeObjectDao.persist(local); - if (reinitCache) - dashboards = null; - return local; - } -} diff --git a/astrid/plugin-src/com/todoroo/astrid/producteev/sync/ProducteevSyncProvider.java b/astrid/plugin-src/com/todoroo/astrid/producteev/sync/ProducteevSyncProvider.java deleted file mode 100644 index f9eadc683..000000000 --- a/astrid/plugin-src/com/todoroo/astrid/producteev/sync/ProducteevSyncProvider.java +++ /dev/null @@ -1,811 +0,0 @@ -/** - * Copyright (c) 2012 Todoroo Inc - * - * See the file "LICENSE" for the full license governing this code. - */ -package com.todoroo.astrid.producteev.sync; - -import java.io.IOException; -import java.util.ArrayList; -import java.util.HashMap; -import java.util.HashSet; - -import org.json.JSONArray; -import org.json.JSONException; -import org.json.JSONObject; - -import android.app.Activity; -import android.app.Notification; -import android.app.PendingIntent; -import android.content.Context; -import android.content.Intent; -import android.text.TextUtils; - -import com.timsu.astrid.R; -import com.todoroo.andlib.data.Property; -import com.todoroo.andlib.data.TodorooCursor; -import com.todoroo.andlib.service.Autowired; -import com.todoroo.andlib.service.ContextManager; -import com.todoroo.andlib.service.DependencyInjectionService; -import com.todoroo.andlib.service.ExceptionService; -import com.todoroo.andlib.service.NotificationManager; -import com.todoroo.andlib.sql.Criterion; -import com.todoroo.andlib.utility.AndroidUtilities; -import com.todoroo.andlib.utility.DateUtilities; -import com.todoroo.andlib.utility.Preferences; -import com.todoroo.astrid.activity.ShortcutActivity; -import com.todoroo.astrid.api.AstridApiConstants; -import com.todoroo.astrid.api.Filter; -import com.todoroo.astrid.core.PluginServices; -import com.todoroo.astrid.dao.TaskDao; -import com.todoroo.astrid.data.Metadata; -import com.todoroo.astrid.data.StoreObject; -import com.todoroo.astrid.data.Task; -import com.todoroo.astrid.notes.NoteMetadata; -import com.todoroo.astrid.producteev.ProducteevBackgroundService; -import com.todoroo.astrid.producteev.ProducteevFilterExposer; -import com.todoroo.astrid.producteev.ProducteevLoginActivity; -import com.todoroo.astrid.producteev.ProducteevPreferences; -import com.todoroo.astrid.producteev.ProducteevUtilities; -import com.todoroo.astrid.producteev.api.ApiResponseParseException; -import com.todoroo.astrid.producteev.api.ApiServiceException; -import com.todoroo.astrid.producteev.api.ApiUtilities; -import com.todoroo.astrid.producteev.api.ProducteevInvoker; -import com.todoroo.astrid.service.AstridDependencyInjector; -import com.todoroo.astrid.service.StatisticsConstants; -import com.todoroo.astrid.service.StatisticsService; -import com.todoroo.astrid.sync.SyncContainer; -import com.todoroo.astrid.sync.SyncProvider; -import com.todoroo.astrid.sync.SyncProviderUtilities; -import com.todoroo.astrid.tags.TaskToTagMetadata; -import com.todoroo.astrid.utility.Constants; - -@SuppressWarnings("nls") -public class ProducteevSyncProvider extends SyncProvider { - - private static final long TASK_ID_UNSYNCED = 1L; - private ProducteevDataService dataService = null; - private ProducteevInvoker invoker = null; - private final ProducteevUtilities preferences = ProducteevUtilities.INSTANCE; - - /** producteev user id. set during sync */ - private long userId; - - /** map of producteev dashboard id + label name to id's */ - private final HashMap labelMap = new HashMap(); - - static { - AstridDependencyInjector.initialize(); - } - - @Autowired - protected ExceptionService exceptionService; - - public ProducteevSyncProvider() { - super(); - DependencyInjectionService.getInstance().inject(this); - } - - // ---------------------------------------------------------------------- - // ------------------------------------------------------ utility methods - // ---------------------------------------------------------------------- - - /** - * Sign out of service, deleting all synchronization metadata - */ - public void signOut() { - preferences.setToken(null); - Preferences.setString(R.string.producteev_PPr_email, null); - Preferences.setString(R.string.producteev_PPr_password, null); - Preferences.setString(ProducteevUtilities.PREF_SERVER_LAST_SYNC, null); - Preferences.setStringFromInteger(R.string.producteev_PPr_defaultdash_key, - ProducteevUtilities.DASHBOARD_DEFAULT); - preferences.clearLastSyncDate(); - - dataService = ProducteevDataService.getInstance(); - dataService.clearMetadata(); - } - - @Override - protected SyncProviderUtilities getUtilities() { - return ProducteevUtilities.INSTANCE; - } - - // ---------------------------------------------------------------------- - // ------------------------------------------------------ initiating sync - // ---------------------------------------------------------------------- - - /** - * initiate sync in background - */ - @Override - protected void initiateBackground() { - dataService = ProducteevDataService.getInstance(); - - try { - String authToken = preferences.getToken(); - invoker = getInvoker(); - - String email = Preferences.getStringValue(R.string.producteev_PPr_email); - String password = Preferences.getStringValue(R.string.producteev_PPr_password); - - // check if we have a token & it works - if(authToken != null) { - invoker.setCredentials(authToken, email, password); - performSync(); - } else { - if (email == null && password == null) { - // we can't do anything, user is not logged in - } else { - invoker.authenticate(email, password); - preferences.setToken(invoker.getToken()); - performSync(); - } - } - } catch (IllegalStateException e) { - // occurs when application was closed - } catch (Exception e) { - handleException("pdv-authenticate", e, true); - } finally { - preferences.stopOngoing(); - } - } - - /** - * If user isn't already signed in, show sign in dialog. Else perform sync. - */ - @Override - protected void initiateManual(Activity activity) { - String authToken = preferences.getToken(); - ProducteevUtilities.INSTANCE.stopOngoing(); - - // check if we have a token & it works - if(authToken == null) { - // display login-activity - Intent intent = new Intent(activity, ProducteevLoginActivity.class); - activity.startActivityForResult(intent, 0); - } else { - activity.startService(new Intent(null, null, - activity, ProducteevBackgroundService.class)); - } - } - - public static ProducteevInvoker getInvoker() { - String z = stripslashes(0, "71o3346pr40o5o4nt4n7t6n287t4op28","2"); - String v = stripslashes(2, "9641n76n9s1736q1578q1o1337q19233","4ae"); - return new ProducteevInvoker(z, v); - } - - // ---------------------------------------------------------------------- - // ----------------------------------------------------- synchronization! - // ---------------------------------------------------------------------- - - protected void performSync() { - preferences.recordSyncStart(); - String syncSuccess = "failed"; - - try { - // load user information - JSONObject user = invoker.usersView(null).getJSONObject("user"); - saveUserData(user); - - String lastServerSync = Preferences.getStringValue(ProducteevUtilities.PREF_SERVER_LAST_SYNC); - - // read dashboards - JSONArray dashboards = invoker.dashboardsShowList(lastServerSync); - dataService.updateDashboards(dashboards); - - // read labels and tasks for each dashboard - ArrayList remoteTasks = new ArrayList(); - for(StoreObject dashboard : dataService.getDashboards()) { - long dashboardId = dashboard.getValue(ProducteevDashboard.REMOTE_ID); - JSONArray labels = invoker.labelsShowList(dashboardId, null); - readLabels(labels); - - try { - // This invocation throws ApiServiceException for workspaces that need to be upgraded - JSONArray tasks = invoker.tasksShowList(dashboardId, lastServerSync); - for(int i = 0; i < tasks.length(); i++) { - ProducteevTaskContainer remote = parseRemoteTask(tasks.getJSONObject(i)); - - if(remote.pdvTask.getValue(ProducteevTask.CREATOR_ID) != userId && - remote.pdvTask.getValue(ProducteevTask.RESPONSIBLE_ID) != userId) - remote.task.setValue(Task.IS_READONLY, 1); - else - remote.task.setValue(Task.IS_READONLY, 0); - - // update reminder flags for incoming remote tasks to prevent annoying - if(remote.task.hasDueDate() && remote.task.getValue(Task.DUE_DATE) < DateUtilities.now()) - remote.task.setFlag(Task.REMINDER_FLAGS, Task.NOTIFY_AFTER_DEADLINE, false); - - dataService.findLocalMatch(remote); - - remoteTasks.add(remote); - } - } catch (ApiServiceException ase) { - // catch it here, so that other dashboards can still be synchronized - handleException("pdv-sync", ase, true); //$NON-NLS-1$ - } - } - - SyncData syncData = populateSyncData(remoteTasks); - try { - synchronizeTasks(syncData); - AndroidUtilities.sleepDeep(3000L); - checkForCreatedDuringSync(); - } finally { - syncData.localCreated.close(); - syncData.localUpdated.close(); - } - - Preferences.setString(ProducteevUtilities.PREF_SERVER_LAST_SYNC, invoker.time()); - preferences.recordSuccessfulSync(); - - Intent broadcastIntent = new Intent(AstridApiConstants.BROADCAST_EVENT_REFRESH); - ContextManager.getContext().sendBroadcast(broadcastIntent, AstridApiConstants.PERMISSION_READ); - - // notification/activities stuff - processNotifications(); - - syncSuccess = getFinalSyncStatus(); - } catch (IllegalStateException e) { - // occurs when application was closed - } catch (Exception e) { - handleException("pdv-sync", e, true); //$NON-NLS-1$ - } finally { - StatisticsService.reportEvent(StatisticsConstants.PDV_SYNC_FINISHED, - "success", syncSuccess); //$NON-NLS-1$ - } - } - - private void checkForCreatedDuringSync() { - TodorooCursor localCreated = dataService.getLocallyCreated(PROPERTIES); - try { - SyncData localCreatedData = new SyncData(null, localCreated, null); - sendLocallyCreated(localCreatedData, new HashMap()); - } catch (IOException e) { - handleException("gtasks-sync", e, true); - } finally { - localCreated.close(); - } - } - - /** - * @param activities - * @return - * @throws JSONException - */ - private String[] parseActivities(JSONArray activities) throws JSONException { - int count = (activities == null ? 0 : activities.length()); - String[] activitiesList = new String[count]; - if(activities == null) - return activitiesList; - for(int i = 0; i < activities.length(); i++) { - String message = activities.getJSONObject(i).getJSONObject("activity").getString("message"); - activitiesList[i] = ApiUtilities.decode(message); - } - return activitiesList; - } - - - // ---------------------------------------------------------------------- - // ------------------------------------------------------------ sync data - // ---------------------------------------------------------------------- - - private void saveUserData(JSONObject user) throws JSONException { - long defaultDashboard = user.getLong("default_dashboard"); - userId = user.getLong("id_user"); - Preferences.setLong(ProducteevUtilities.PREF_DEFAULT_DASHBOARD, defaultDashboard); - Preferences.setLong(ProducteevUtilities.PREF_USER_ID, userId); - - // save the default dashboard preference if unset - int defaultDashSetting = Preferences.getIntegerFromString(R.string.producteev_PPr_defaultdash_key, - ProducteevUtilities.DASHBOARD_DEFAULT); - if(defaultDashSetting == ProducteevUtilities.DASHBOARD_DEFAULT) - Preferences.setStringFromInteger(R.string.producteev_PPr_defaultdash_key, (int) defaultDashboard); - } - - // all synchronized properties - private static final Property[] PROPERTIES = new Property[] { - Task.ID, - Task.TITLE, - Task.IMPORTANCE, - Task.DUE_DATE, - Task.CREATION_DATE, - Task.COMPLETION_DATE, - Task.DELETION_DATE, - Task.REMINDER_FLAGS, - Task.NOTES, - Task.RECURRENCE - }; - - /** - * Populate SyncData data structure - * @throws JSONException - */ - private SyncData populateSyncData(ArrayList remoteTasks) throws JSONException { - // fetch locally created tasks - TodorooCursor localCreated = dataService.getLocallyCreated(PROPERTIES); - - // fetch locally updated tasks - TodorooCursor localUpdated = dataService.getLocallyUpdated(PROPERTIES); - - return new SyncData(remoteTasks, localCreated, localUpdated); - } - - // ---------------------------------------------------------------------- - // ------------------------------------------------- create / push / pull - // ---------------------------------------------------------------------- - - @Override - protected ProducteevTaskContainer create(ProducteevTaskContainer local) throws IOException { - Task localTask = local.task; - long dashboard = ProducteevUtilities.INSTANCE.getDefaultDashboard(); - if(local.pdvTask.containsNonNullValue(ProducteevTask.DASHBOARD_ID)) - dashboard = local.pdvTask.getValue(ProducteevTask.DASHBOARD_ID); - long responsibleId = local.pdvTask.getValue(ProducteevTask.RESPONSIBLE_ID); - - if(dashboard == ProducteevUtilities.DASHBOARD_NO_SYNC) { - // set a bogus task id, then return without creating - local.pdvTask.setValue(ProducteevTask.ID, TASK_ID_UNSYNCED); - return local; - } - - JSONObject response = invoker.tasksCreate(localTask.getValue(Task.TITLE), - responsibleId, dashboard, createDeadline(localTask), createReminder(localTask), - localTask.isCompleted() ? 2 : 1, createStars(localTask)); - ProducteevTaskContainer newRemoteTask; - try { - newRemoteTask = parseRemoteTask(response); - } catch (JSONException e) { - throw new ApiResponseParseException(e); - } - transferIdentifiers(newRemoteTask, local); - push(local, newRemoteTask); - return newRemoteTask; - } - - /** Create a task container for the given ProducteevTask - * @throws JSONException */ - private ProducteevTaskContainer parseRemoteTask(JSONObject remoteTask) throws JSONException { - Task task = new Task(); - - ArrayList metadata = new ArrayList(); - - if(remoteTask.has("task")) - remoteTask = remoteTask.getJSONObject("task"); - - task.setValue(Task.TITLE, ApiUtilities.decode(remoteTask.getString("title"))); - task.setValue(Task.CREATION_DATE, ApiUtilities.producteevToUnixTime(remoteTask.getString("time_created"), 0)); - task.setValue(Task.COMPLETION_DATE, remoteTask.getInt("status") == 2 ? DateUtilities.now() : 0); - task.setValue(Task.DELETION_DATE, remoteTask.getInt("deleted") == 1 ? DateUtilities.now() : 0); - - long dueDate = ApiUtilities.producteevToUnixTime(remoteTask.getString("deadline"), 0); - if(remoteTask.optInt("all_day", 0) == 1) - task.setValue(Task.DUE_DATE, Task.createDueDate(Task.URGENCY_SPECIFIC_DAY, dueDate)); - else - task.setValue(Task.DUE_DATE, Task.createDueDate(Task.URGENCY_SPECIFIC_DAY_TIME, dueDate)); - task.setValue(Task.IMPORTANCE, 5 - remoteTask.getInt("star")); - - JSONArray labels = remoteTask.getJSONArray("labels"); - for(int i = 0; i < labels.length(); i++) { - JSONObject label = labels.getJSONObject(i).getJSONObject("label"); - if(label.getInt("deleted") != 0) - continue; - - Metadata tagData = new Metadata(); - tagData.setValue(Metadata.KEY, TaskToTagMetadata.KEY); - tagData.setValue(TaskToTagMetadata.TAG_NAME, ApiUtilities.decode(label.getString("title"))); - metadata.add(tagData); - } - - ProducteevTaskContainer container = new ProducteevTaskContainer(task, metadata, remoteTask); - - JSONArray notes = remoteTask.getJSONArray("notes"); - for(int i = notes.length() - 1; i >= 0; i--) { - JSONObject note = notes.getJSONObject(i).getJSONObject("note"); - PluginServices.getMetadataService().deleteWhere(Criterion.and(Metadata.KEY.eq(NoteMetadata.METADATA_KEY), - NoteMetadata.EXT_ID.eq(note.getString("id_note")))); - if(note.getLong("deleted") != 0) - continue; - - long creator = note.getLong("id_creator"); - metadata.add(ApiUtilities.createNoteMetadata(note, creatorName(container, creator))); - } - - return container; - } - - private String creatorName(ProducteevTaskContainer container, long creator) { - StoreObject[] dashboards = dataService.getDashboards(); - for(int i = 0; i < dashboards.length; i++) { - Long dashboard = container.pdvTask.getValue(ProducteevTask.DASHBOARD_ID); - if(dashboard.equals(dashboards[i].getValue(ProducteevDashboard.REMOTE_ID))) { - return ProducteevDashboard.getUserFromDashboard(dashboards[i], creator); - } - } - return null; - } - - @Override - protected ProducteevTaskContainer pull(ProducteevTaskContainer task) throws IOException { - if(!task.pdvTask.containsNonNullValue(ProducteevTask.ID)) - throw new ApiServiceException("Tried to read an invalid task"); //$NON-NLS-1$ - - JSONObject remote = invoker.tasksView(task.pdvTask.getValue(ProducteevTask.ID)); - try { - return parseRemoteTask(remote); - } catch (JSONException e) { - throw new ApiResponseParseException(e); - } - } - - /** - * Send changes for the given Task across the wire. If a remoteTask is - * supplied, we attempt to intelligently only transmit the values that - * have changed. - */ - @Override - protected ProducteevTaskContainer push(ProducteevTaskContainer local, ProducteevTaskContainer remote) throws IOException { - boolean remerge = false; - - long idTask = local.pdvTask.getValue(ProducteevTask.ID); - long idDashboard = local.pdvTask.getValue(ProducteevTask.DASHBOARD_ID); - long idResponsible = local.pdvTask.getValue(ProducteevTask.RESPONSIBLE_ID); - - // if local is marked do not sync, handle accordingly - if(idDashboard == ProducteevUtilities.DASHBOARD_NO_SYNC) { - return local; - } - - // fetch remote task for comparison - if(remote == null) - remote = pull(local); - - // either delete or re-create if necessary - if(shouldTransmit(local, Task.DELETION_DATE, remote)) { - if(local.task.getValue(Task.DELETION_DATE) > 0) - invoker.tasksDelete(idTask); - else { - // if we create, we transfer identifiers to old remote - // in case it is used by caller for other purposes - ProducteevTaskContainer newRemote = create(local); - transferIdentifiers(newRemote, remote); - remote = newRemote; - } - } - - // dashboard - if(remote != null && idDashboard != remote.pdvTask.getValue(ProducteevTask.DASHBOARD_ID)) { - invoker.tasksSetWorkspace(idTask, idDashboard); - remote = pull(local); - } else if(remote == null && idTask == TASK_ID_UNSYNCED) { - // was un-synced, create remote - remote = create(local); - } - - // responsible - if(remote != null && idResponsible != - remote.pdvTask.getValue(ProducteevTask.RESPONSIBLE_ID)) { - invoker.tasksSetResponsible(idTask, idResponsible); - } - - // core properties - if(shouldTransmit(local, Task.TITLE, remote)) - invoker.tasksSetTitle(idTask, local.task.getValue(Task.TITLE)); - if(shouldTransmit(local, Task.IMPORTANCE, remote)) - invoker.tasksSetStar(idTask, createStars(local.task)); - if(shouldTransmit(local, Task.DUE_DATE, remote)) { - if(local.task.hasDueDate()) - invoker.tasksSetDeadline(idTask, createDeadline(local.task), local.task.hasDueTime() ? 0 : 1); - else - invoker.tasksUnsetDeadline(idTask); - } - - boolean isPDVRepeating = ((local.pdvTask.containsNonNullValue(ProducteevTask.REPEATING_SETTING) && - local.pdvTask.getValue(ProducteevTask.REPEATING_SETTING).length()>0) || - (remote != null && remote.pdvTask.containsNonNullValue(ProducteevTask.REPEATING_SETTING) && - remote.pdvTask.getValue(ProducteevTask.REPEATING_SETTING).length()>0)); - - boolean isAstridRepeating = local.task.containsNonNullValue(Task.RECURRENCE) && - !TextUtils.isEmpty(local.task.getValue(Task.RECURRENCE)); - - if (isAstridRepeating && isPDVRepeating) { - // Astrid-repeat overrides PDV-repeat - invoker.tasksUnsetRepeating(idTask); - } - - if(shouldTransmit(local, Task.COMPLETION_DATE, remote)) { - invoker.tasksSetStatus(idTask, local.task.isCompleted() ? 2 : 1); - if (local.task.isCompleted() && !isAstridRepeating && - isPDVRepeating) { - local.task.setValue(Task.COMPLETION_DATE, 0L); - remerge = true; - } - } - - - try { - // tags - transmitTags(local, remote, idTask, idDashboard); - - // notes - if(!TextUtils.isEmpty(local.task.getValue(Task.NOTES))) { - String note = local.task.getValue(Task.NOTES); - JSONObject result = invoker.tasksNoteCreate(idTask, note); - local.metadata.add(ApiUtilities.createNoteMetadata(result.getJSONObject("note"), null)); - local.task.setValue(Task.NOTES, ""); - } - - remote = pull(local); - remote.task.setId(local.task.getId()); - if(remerge) { - // transform local into remote - local.task = remote.task; - local.pdvTask.setValue(ProducteevTask.ID, remote.pdvTask.getValue(ProducteevTask.ID)); - local.pdvTask.setValue(ProducteevTask.DASHBOARD_ID, remote.pdvTask.getValue(ProducteevTask.DASHBOARD_ID)); - local.pdvTask.setValue(ProducteevTask.CREATOR_ID, remote.pdvTask.getValue(ProducteevTask.CREATOR_ID)); - local.pdvTask.setValue(ProducteevTask.RESPONSIBLE_ID, remote.pdvTask.getValue(ProducteevTask.RESPONSIBLE_ID)); - if(remote.pdvTask.containsNonNullValue(ProducteevTask.REPEATING_SETTING)) - local.pdvTask.setValue(ProducteevTask.REPEATING_SETTING, remote.pdvTask.getValue(ProducteevTask.REPEATING_SETTING)); - } - - return remote; - } catch (JSONException e) { - throw new ApiResponseParseException(e); - } - } - - /** - * Transmit tags - * - * @param local - * @param remote - * @param idTask - * @param idDashboard - * @throws ApiServiceException - * @throws JSONException - * @throws IOException - */ - private void transmitTags(ProducteevTaskContainer local, - ProducteevTaskContainer remote, long idTask, long idDashboard) throws ApiServiceException, JSONException, IOException { - HashSet localTags = new HashSet(); - HashSet remoteTags = new HashSet(); - for(Metadata item : local.metadata) - if(TaskToTagMetadata.KEY.equals(item.getValue(Metadata.KEY))) - localTags.add(item.getValue(TaskToTagMetadata.TAG_NAME)); - if(remote != null && remote.metadata != null) { - for(Metadata item : remote.metadata) - if(TaskToTagMetadata.KEY.equals(item.getValue(Metadata.KEY))) - remoteTags.add(item.getValue(TaskToTagMetadata.TAG_NAME)); - } - - if(!localTags.equals(remoteTags)) { - long[] labels = new long[localTags.size()]; - int index = 0; - for(String label : localTags) { - String pdvLabel = idDashboard + label; - final long id; - if(!labelMap.containsKey(pdvLabel)) { - JSONObject result = invoker.labelsCreate(idDashboard, label).getJSONObject("label"); - id = putLabelIntoCache(result); - } else - id = labelMap.get(pdvLabel); - labels[index++] = id; - } - invoker.tasksChangeLabel(idTask, labels); - } - } - - // ---------------------------------------------------------------------- - // --------------------------------------------------------- read / write - // ---------------------------------------------------------------------- - - @Override - protected ProducteevTaskContainer read(TodorooCursor cursor) throws IOException { - return dataService.readTaskAndMetadata(cursor); - } - - @Override - protected void write(ProducteevTaskContainer task) throws IOException { - if(task.task.isSaved()) { - Task local = PluginServices.getTaskService().fetchById(task.task.getId(), Task.COMPLETION_DATE); - if(task.task.isCompleted() && !local.isCompleted()) - StatisticsService.reportEvent(StatisticsConstants.PDV_TASK_COMPLETED); - } else { // Set default reminders for remotely created tasks - TaskDao.setDefaultReminders(task.task); - } - dataService.saveTaskAndMetadata(task); - } - - // ---------------------------------------------------------------------- - // --------------------------------------------------------- misc helpers - // ---------------------------------------------------------------------- - - @Override - protected int matchTask(ArrayList tasks, ProducteevTaskContainer target) { - int length = tasks.size(); - for(int i = 0; i < length; i++) { - ProducteevTaskContainer task = tasks.get(i); - if (target.pdvTask.containsNonNullValue(ProducteevTask.ID) && - task.pdvTask.getValue(ProducteevTask.ID).equals(target.pdvTask.getValue(ProducteevTask.ID))) - return i; - } - return -1; - } - - /** - * get stars in producteev format - * @param local - * @return - */ - private Integer createStars(Task local) { - return 5 - local.getValue(Task.IMPORTANCE); - } - - /** - * get reminder in producteev format - * @param local - * @return - */ - private Integer createReminder(Task local) { - if(local.getFlag(Task.REMINDER_FLAGS, Task.NOTIFY_AT_DEADLINE)) - return 8; - return null; - } - - /** - * get deadline in producteev format - * @param task - * @return - */ - private String createDeadline(Task task) { - if(!task.hasDueDate()) - return ""; - return ApiUtilities.unixTimeToProducteev(task.getValue(Task.DUE_DATE)); - } - - /** - * Determine whether this task's property should be transmitted - * @param task task to consider - * @param property property to consider - * @param remoteTask remote task proxy - * @return - */ - private boolean shouldTransmit(SyncContainer task, Property property, SyncContainer remoteTask) { - if(!task.task.containsValue(property)) - return false; - - if(remoteTask == null) - return true; - if(!remoteTask.task.containsValue(property)) - return true; - - // special cases - match if they're zero or nonzero - if(property == Task.COMPLETION_DATE || - property == Task.DELETION_DATE) - return !AndroidUtilities.equals((Long)task.task.getValue(property) == 0, - (Long)remoteTask.task.getValue(property) == 0); - - return !AndroidUtilities.equals(task.task.getValue(property), - remoteTask.task.getValue(property)); - } - - @Override - protected int updateNotification(Context context, Notification notification) { - String notificationTitle = context.getString(R.string.producteev_notification_title); - Intent intent = new Intent(context, ProducteevPreferences.class); - PendingIntent notificationIntent = PendingIntent.getActivity(context, 0, - intent, 0); - notification.setLatestEventInfo(context, - notificationTitle, context.getString(R.string.SyP_progress), - notificationIntent); - return Constants.NOTIFICATION_SYNC; - } - - @Override - protected void transferIdentifiers(ProducteevTaskContainer source, - ProducteevTaskContainer destination) { - destination.pdvTask = source.pdvTask; - } - - /** - * Read labels into label map - * @param dashboardId - * @throws JSONException - * @throws ApiServiceException - * @throws IOException - */ - private void readLabels(JSONArray labels) throws JSONException, ApiServiceException, IOException { - for(int i = 0; i < labels.length(); i++) { - JSONObject label = labels.getJSONObject(i).getJSONObject("label"); - putLabelIntoCache(label); - } - } - - /** - * Puts a single label into the cache - * @param dashboardId - * @param label - * @throws JSONException - */ - private long putLabelIntoCache(JSONObject label) - throws JSONException { - String name = ApiUtilities.decode(label.getString("title")); - long dashboard = label.getLong("id_dashboard"); - labelMap.put(dashboard + name, label.getLong("id_label")); - return label.getLong("id_label"); - } - - /** - * Show workspace notifications - * - * @throws ApiResponseParseException - * @throws ApiServiceException - * @throws IOException - * @throws JSONException - */ - private void processNotifications() throws ApiResponseParseException, - ApiServiceException, IOException, JSONException { - String lastNotificationId = Preferences.getStringValue(ProducteevUtilities.PREF_SERVER_LAST_NOTIFICATION); - String lastActivityId = Preferences.getStringValue(ProducteevUtilities.PREF_SERVER_LAST_ACTIVITY); - JSONArray notifications = invoker.activitiesShowNotifications(null, (lastNotificationId == null ? null : new Long(lastNotificationId))); - String[] notificationsList = parseActivities(notifications); - // update lastIds - if (notifications.length() > 0) { - lastNotificationId = ""+notifications.getJSONObject(0).getJSONObject("activity").getLong("id_activity"); - } - - // display notifications from producteev-log - Context context = ContextManager.getContext(); - final NotificationManager nm = new NotificationManager.AndroidNotificationManager(context); - for (int i = 0; i< notificationsList.length; i++) { - long id_dashboard = notifications.getJSONObject(i).getJSONObject("activity").getLong("id_dashboard"); - String dashboardName = null; - StoreObject[] dashboardsData = ProducteevDataService.getInstance().getDashboards(); - ProducteevDashboard dashboard = null; - if (dashboardsData != null) { - for (int j=0; i]+>", ""); - PendingIntent contentIntent = PendingIntent.getActivity(context, 0, notificationIntent, 0); - notification.setLatestEventInfo(context, contentTitle, message, contentIntent); - - nm.notify(Constants.NOTIFICATION_PRODUCTEEV_NOTIFICATIONS-i, notification); - } - } - - // store lastIds in Preferences - Preferences.setString(ProducteevUtilities.PREF_SERVER_LAST_NOTIFICATION, lastNotificationId); - Preferences.setString(ProducteevUtilities.PREF_SERVER_LAST_ACTIVITY, lastActivityId); - } - - // ---------------------------------------------------------------------- - // ------------------------------------------------------- helper methods - // ---------------------------------------------------------------------- - - private static final String stripslashes(int ____,String __,String ___) { - int _=__.charAt(____/92);_=_==116?_-1:_;_=((_>=97)&&(_<=123)? - ((_-83)%27+97):_);return TextUtils.htmlEncode(____==31?___: - stripslashes(____+1,__.substring(1),___+((char)_))); - } -} diff --git a/astrid/plugin-src/com/todoroo/astrid/producteev/sync/ProducteevTask.java b/astrid/plugin-src/com/todoroo/astrid/producteev/sync/ProducteevTask.java deleted file mode 100644 index 05c69662b..000000000 --- a/astrid/plugin-src/com/todoroo/astrid/producteev/sync/ProducteevTask.java +++ /dev/null @@ -1,55 +0,0 @@ -/** - * Copyright (c) 2012 Todoroo Inc - * - * See the file "LICENSE" for the full license governing this code. - */ -package com.todoroo.astrid.producteev.sync; - -import com.todoroo.andlib.data.Property.LongProperty; -import com.todoroo.andlib.data.Property.StringProperty; -import com.todoroo.andlib.utility.Preferences; -import com.todoroo.astrid.data.Metadata; -import com.todoroo.astrid.producteev.ProducteevUtilities; - -/** - * Metadata entries for a Producteev Task - * @author Tim Su - * - */ -public class ProducteevTask { - - /** metadata key */ - public static final String METADATA_KEY = "producteev"; //$NON-NLS-1$ - - /** task id in producteev */ - public static final LongProperty ID = new LongProperty(Metadata.TABLE, - Metadata.VALUE1.name); - - /** dashboard id */ - public static final LongProperty DASHBOARD_ID = new LongProperty(Metadata.TABLE, - Metadata.VALUE2.name); - - /** creator id */ - public static final LongProperty CREATOR_ID = new LongProperty(Metadata.TABLE, - Metadata.VALUE3.name); - - /** responsible id */ - public static final LongProperty RESPONSIBLE_ID = new LongProperty(Metadata.TABLE, - Metadata.VALUE4.name); - - /** repeating settings */ - public static final StringProperty REPEATING_SETTING = new StringProperty(Metadata.TABLE, - Metadata.VALUE5.name); - - public static Metadata newMetadata() { - Metadata metadata = new Metadata(); - metadata.setValue(Metadata.KEY, ProducteevTask.METADATA_KEY); - metadata.setValue(ID, 0L); - metadata.setValue(DASHBOARD_ID, ProducteevUtilities.INSTANCE.getDefaultDashboard()); - metadata.setValue(CREATOR_ID, Preferences.getLong(ProducteevUtilities.PREF_USER_ID, 0L)); - metadata.setValue(RESPONSIBLE_ID, Preferences.getLong(ProducteevUtilities.PREF_USER_ID, 0L)); - metadata.setValue(REPEATING_SETTING, ""); //$NON-NLS-1$ - return metadata; - } - -} diff --git a/astrid/plugin-src/com/todoroo/astrid/producteev/sync/ProducteevTaskContainer.java b/astrid/plugin-src/com/todoroo/astrid/producteev/sync/ProducteevTaskContainer.java deleted file mode 100644 index 9581ef3d9..000000000 --- a/astrid/plugin-src/com/todoroo/astrid/producteev/sync/ProducteevTaskContainer.java +++ /dev/null @@ -1,70 +0,0 @@ -/** - * Copyright (c) 2012 Todoroo Inc - * - * See the file "LICENSE" for the full license governing this code. - */ -package com.todoroo.astrid.producteev.sync; - -import java.util.ArrayList; -import java.util.Iterator; - -import org.json.JSONObject; - -import com.todoroo.astrid.data.Metadata; -import com.todoroo.astrid.data.Task; -import com.todoroo.astrid.sync.SyncContainer; - -/** - * RTM Task Container - * - * @author Tim Su - * - */ -public class ProducteevTaskContainer extends SyncContainer { - - public Metadata pdvTask; - - public ProducteevTaskContainer(Task task, ArrayList metadata, Metadata pdvTask) { - this.task = task; - this.metadata = metadata; - this.pdvTask = pdvTask; - if(this.pdvTask == null) { - this.pdvTask = ProducteevTask.newMetadata(); - } - } - - @SuppressWarnings("nls") - public ProducteevTaskContainer(Task task, ArrayList metadata, JSONObject remoteTask) { - this(task, metadata, new Metadata()); - pdvTask.setValue(Metadata.KEY, ProducteevTask.METADATA_KEY); - pdvTask.setValue(ProducteevTask.ID, remoteTask.optLong("id_task")); - pdvTask.setValue(ProducteevTask.DASHBOARD_ID, remoteTask.optLong("id_dashboard")); - pdvTask.setValue(ProducteevTask.RESPONSIBLE_ID, remoteTask.optLong("id_responsible")); - pdvTask.setValue(ProducteevTask.CREATOR_ID, remoteTask.optLong("id_creator")); - String repeatingValue = remoteTask.optString("repeating_value"); - String repeatingInterval = remoteTask.optString("repeating_interval"); - if (!"0".equals(repeatingValue) && repeatingValue.length() > 0 && - repeatingInterval != null && repeatingInterval.length() > 0) { - pdvTask.setValue(ProducteevTask.REPEATING_SETTING, repeatingValue+","+repeatingInterval); - } - } - - public ProducteevTaskContainer(Task task, ArrayList metadata) { - this.task = task; - this.metadata = metadata; - - for(Iterator iterator = metadata.iterator(); iterator.hasNext(); ) { - Metadata item = iterator.next(); - if(ProducteevTask.METADATA_KEY.equals(item.getValue(Metadata.KEY))) { - pdvTask = item; - iterator.remove(); - // don't break, could be multiple - } - } - if(this.pdvTask == null) { - this.pdvTask = ProducteevTask.newMetadata(); - } - } - - -} diff --git a/astrid/plugin-src/com/todoroo/astrid/producteev/sync/ProducteevUser.java b/astrid/plugin-src/com/todoroo/astrid/producteev/sync/ProducteevUser.java deleted file mode 100644 index faf57f6c0..000000000 --- a/astrid/plugin-src/com/todoroo/astrid/producteev/sync/ProducteevUser.java +++ /dev/null @@ -1,120 +0,0 @@ -/** - * Copyright (c) 2012 Todoroo Inc - * - * See the file "LICENSE" for the full license governing this code. - */ -package com.todoroo.astrid.producteev.sync; - -import org.json.JSONException; -import org.json.JSONObject; - -/** - * - * @author Arne Jans - */ -@SuppressWarnings("nls") -public class ProducteevUser implements Comparable { - - private final long id; - - private final String email; - - private final String firstname; - - private final String lastname; - - public ProducteevUser(long id, String email, String firstname, - String lastname) { - this.id = id; - this.email = email; - this.firstname = firstname; - this.lastname = lastname; - } - - public ProducteevUser(JSONObject elt) throws JSONException { - this.id = elt.getLong("id"); - this.email = elt.getString("email"); - this.firstname = elt.getString("firstname"); - this.lastname = elt.getString("lastname"); - } - - /** - * @return the email - */ - public String getEmail() { - return email; - } - - /** - * @return the firstname - */ - public String getFirstname() { - return firstname; - } - - /** - * @return the lastname - */ - public String getLastname() { - return lastname; - } - - /** - * @return the id - */ - public long getId() { - return id; - } - - @Override - public String toString() { - String displayString = ""; - boolean hasFirstname = false; - boolean hasLastname = false; - if (firstname != null && firstname.length() > 0) { - displayString += firstname; - hasFirstname = true; - } - if (lastname != null && lastname.length() > 0) - hasLastname = true; - if (hasFirstname && hasLastname) - displayString += " "; - if (hasLastname) - displayString += lastname; - - if (!hasFirstname && !hasLastname && email != null - && email.length() > 0) - displayString += email; - return displayString; - } - - @Override - public boolean equals(Object o) { - if (this == o) return true; - if (o == null || getClass() != o.getClass()) return false; - - ProducteevUser that = (ProducteevUser) o; - - if (id != that.id) return false; - if (email != null ? !email.equals(that.email) : that.email != null) return false; - if (firstname != null ? !firstname.equals(that.firstname) : that.firstname != null) return false; - if (lastname != null ? !lastname.equals(that.lastname) : that.lastname != null) return false; - - return true; - } - - @Override - public int hashCode() { - int result = (int) (id ^ (id >>> 32)); - result = 31 * result + (email != null ? email.hashCode() : 0); - result = 31 * result + (firstname != null ? firstname.hashCode() : 0); - result = 31 * result + (lastname != null ? lastname.hashCode() : 0); - return result; - } - - @Override - public int compareTo(ProducteevUser o) { - int ret = toString().compareTo(o.toString()); - return ret == 0 ? (new Long(id).compareTo(o.id)) : ret; - } -} diff --git a/astrid/plugin-src/com/todoroo/astrid/reminders/Notifications.java b/astrid/plugin-src/com/todoroo/astrid/reminders/Notifications.java index 3831a8d6b..76972528c 100644 --- a/astrid/plugin-src/com/todoroo/astrid/reminders/Notifications.java +++ b/astrid/plugin-src/com/todoroo/astrid/reminders/Notifications.java @@ -6,6 +6,8 @@ package com.todoroo.astrid.reminders; import java.util.Date; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; import android.app.Notification; import android.app.PendingIntent; @@ -251,13 +253,27 @@ public class Notifications extends BroadcastReceiver { } } + private static long lastNotificationSound = 0L; + + /** + * @returns true if notification should sound + */ + private static boolean checkLastNotificationSound() { + long now = DateUtilities.now(); + if (now - lastNotificationSound > 10000) { + lastNotificationSound = now; + return true; + } + return false; + } + /** * Shows an Astrid notification. Pulls in ring tone and quiet hour settings * from preferences. You can make it say anything you like. * @param ringTimes number of times to ring (-1 = nonstop) */ public static void showNotification(int notificationId, Intent intent, int type, String title, - String text, int ringTimes) { + final String text, int ringTimes) { Context context = ContextManager.getContext(); if(notificationManager == null) @@ -305,7 +321,7 @@ public class Notifications extends BroadcastReceiver { else notification.defaults = Notification.DEFAULT_LIGHTS; - AudioManager audioManager = (AudioManager)context.getSystemService( + final AudioManager audioManager = (AudioManager)context.getSystemService( Context.AUDIO_SERVICE); // detect call state @@ -317,9 +333,9 @@ public class Notifications extends BroadcastReceiver { // if multi-ring is activated and the setting p_rmd_maxvolume allows it, set up the flags for insistent // notification, and increase the volume to full volume, so the user // will actually pay attention to the alarm - boolean maxOutVolumeForMultipleRingReminders = Preferences.getBoolean(R.string.p_rmd_maxvolume, true); + final boolean maxOutVolumeForMultipleRingReminders = Preferences.getBoolean(R.string.p_rmd_maxvolume, true); // remember it to set it to the old value after the alarm - int previousAlarmVolume = audioManager.getStreamVolume(AudioManager.STREAM_ALARM); + final int previousAlarmVolume = audioManager.getStreamVolume(AudioManager.STREAM_ALARM); if (ringTimes != 1 && (type != ReminderService.TYPE_RANDOM)) { notification.audioStreamType = AudioManager.STREAM_ALARM; if (maxOutVolumeForMultipleRingReminders) { @@ -337,6 +353,7 @@ public class Notifications extends BroadcastReceiver { notification.audioStreamType = AudioManager.STREAM_NOTIFICATION; } + boolean soundIntervalOk = checkLastNotificationSound(); // quiet hours = no sound if(quietHours || callState != TelephonyManager.CALL_STATE_IDLE) { @@ -348,7 +365,7 @@ public class Notifications extends BroadcastReceiver { notification.sound = null; voiceReminder = false; } else if(notificationPreference != null) { - if(notificationPreference.length() > 0) { + if(notificationPreference.length() > 0 && soundIntervalOk) { Uri notificationSound = Uri.parse(notificationPreference); notification.sound = notificationSound; } else { @@ -366,7 +383,7 @@ public class Notifications extends BroadcastReceiver { notification.vibrate = null; } else { if (Preferences.getBoolean(R.string.p_rmd_vibrate, true) - && audioManager.shouldVibrate(AudioManager.VIBRATE_TYPE_NOTIFICATION)) { + && audioManager.shouldVibrate(AudioManager.VIBRATE_TYPE_NOTIFICATION) && soundIntervalOk) { notification.vibrate = new long[] {0, 1000, 500, 1000, 500, 1000}; } else { notification.vibrate = null; @@ -378,29 +395,37 @@ public class Notifications extends BroadcastReceiver { for(int i = 0; i < Math.max(ringTimes, 1); i++) { notificationManager.notify(notificationId, notification); - AndroidUtilities.sleepDeep(500); } Flags.set(Flags.REFRESH); // Forces a reload when app launches - if (voiceReminder || maxOutVolumeForMultipleRingReminders) { - AndroidUtilities.sleepDeep(2000); - for(int i = 0; i < 50; i++) { - AndroidUtilities.sleepDeep(500); - if(audioManager.getMode() != AudioManager.MODE_RINGTONE) - break; - } - try { - // first reset the Alarm-volume to the value before it was eventually maxed out - if (maxOutVolumeForMultipleRingReminders) - audioManager.setStreamVolume(AudioManager.STREAM_ALARM, previousAlarmVolume, 0); - if (voiceReminder) - VoiceOutputService.getVoiceOutputInstance().queueSpeak(text); - } catch (VerifyError e) { - // unavailable - } + final boolean finalVoiceReminder = voiceReminder; + + if ((finalVoiceReminder || maxOutVolumeForMultipleRingReminders) && soundIntervalOk) { + singleThreadVoicePool.submit(new Runnable() { + @Override + public void run() { + AndroidUtilities.sleepDeep(2000); + for(int i = 0; i < 50; i++) { + AndroidUtilities.sleepDeep(500); + if(audioManager.getMode() != AudioManager.MODE_RINGTONE) + break; + } + try { + // first reset the Alarm-volume to the value before it was eventually maxed out + if (maxOutVolumeForMultipleRingReminders) + audioManager.setStreamVolume(AudioManager.STREAM_ALARM, previousAlarmVolume, 0); + if (finalVoiceReminder) + VoiceOutputService.getVoiceOutputInstance().queueSpeak(text); + } catch (VerifyError e) { + // unavailable + } + } + }); } } + private static ExecutorService singleThreadVoicePool = Executors.newSingleThreadExecutor(); + /** * @return whether we're in quiet hours */ diff --git a/astrid/plugin-src/com/todoroo/astrid/reminders/ReengagementReceiver.java b/astrid/plugin-src/com/todoroo/astrid/reminders/ReengagementReceiver.java index 5c05584c6..68ef8d49b 100644 --- a/astrid/plugin-src/com/todoroo/astrid/reminders/ReengagementReceiver.java +++ b/astrid/plugin-src/com/todoroo/astrid/reminders/ReengagementReceiver.java @@ -31,8 +31,8 @@ import com.todoroo.astrid.activity.TaskListActivity; import com.todoroo.astrid.activity.TaskListFragment; import com.todoroo.astrid.api.FilterWithCustomIntent; import com.todoroo.astrid.core.SortHelper; +import com.todoroo.astrid.dao.TaskDao.TaskCriteria; import com.todoroo.astrid.data.Task; -import com.todoroo.astrid.data.TaskApiDao.TaskCriteria; import com.todoroo.astrid.service.TaskService; import com.todoroo.astrid.utility.Constants; import com.todoroo.astrid.utility.Flags; diff --git a/astrid/plugin-src/com/todoroo/astrid/reminders/ReminderService.java b/astrid/plugin-src/com/todoroo/astrid/reminders/ReminderService.java index 1378e6bc4..b355e932d 100644 --- a/astrid/plugin-src/com/todoroo/astrid/reminders/ReminderService.java +++ b/astrid/plugin-src/com/todoroo/astrid/reminders/ReminderService.java @@ -30,7 +30,6 @@ import com.todoroo.andlib.utility.Preferences; import com.todoroo.astrid.dao.TaskDao; import com.todoroo.astrid.dao.TaskDao.TaskCriteria; import com.todoroo.astrid.data.Task; -import com.todoroo.astrid.data.TaskApiDao; import com.todoroo.astrid.utility.Constants; @@ -501,7 +500,7 @@ public final class ReminderService { private TodorooCursor getTasksWithReminders(Property... properties) { return taskDao.query(Query.select(properties).where(Criterion.and( TaskCriteria.isActive(), - TaskApiDao.TaskCriteria.ownedByMe(), + TaskCriteria.ownedByMe(), Criterion.or(Task.REMINDER_FLAGS.gt(0), Task.REMINDER_PERIOD.gt(0))))); } diff --git a/astrid/plugin-src/com/todoroo/astrid/repeats/RepeatControlSet.java b/astrid/plugin-src/com/todoroo/astrid/repeats/RepeatControlSet.java index eb6ee9b79..803f19949 100644 --- a/astrid/plugin-src/com/todoroo/astrid/repeats/RepeatControlSet.java +++ b/astrid/plugin-src/com/todoroo/astrid/repeats/RepeatControlSet.java @@ -367,7 +367,7 @@ public class RepeatControlSet extends PopupControlSet { result = rrule.toIcal(); } - if (type.getSelectedItemPosition() == TYPE_COMPLETION_DATE) { + if (type.getSelectedItemPosition() == TYPE_COMPLETION_DATE && !TextUtils.isEmpty(result)) { result = result + ";FROM=COMPLETION"; //$NON-NLS-1$ } diff --git a/astrid/plugin-src/com/todoroo/astrid/subtasks/SubtasksHelper.java b/astrid/plugin-src/com/todoroo/astrid/subtasks/SubtasksHelper.java index 681d18170..9e89f59bb 100644 --- a/astrid/plugin-src/com/todoroo/astrid/subtasks/SubtasksHelper.java +++ b/astrid/plugin-src/com/todoroo/astrid/subtasks/SubtasksHelper.java @@ -139,27 +139,39 @@ public class SubtasksHelper { return AstridOrderedListUpdater.serializeTree(tree); } - private static void remapLocalTreeToRemote(Node root, HashMap idMap) { + public static interface TreeRemapHelper { + public T getKeyFromOldUuid(String uuid); + } + + public static void remapTree(Node root, HashMap idMap, TreeRemapHelper helper) { ArrayList children = root.children; for (int i = 0; i < children.size(); i++) { Node child = children.get(i); - - long localId = -1L; - try { - localId = Long.parseLong(child.uuid); - } catch (NumberFormatException e) {/**/} - String uuid = idMap.get(localId); + T key = helper.getKeyFromOldUuid(child.uuid); + String uuid = idMap.get(key); if (!RemoteModel.isValidUuid(uuid)) { children.remove(i); children.addAll(i, child.children); i--; } else { child.uuid = uuid; - remapLocalTreeToRemote(child, idMap); + remapTree(child, idMap, helper); } } } + private static void remapLocalTreeToRemote(Node root, HashMap idMap) { + remapTree(root, idMap, new TreeRemapHelper() { + public Long getKeyFromOldUuid(String uuid) { + Long localId = -1L; + try { + localId = Long.parseLong(uuid); + } catch (NumberFormatException e) {/**/} + return localId; + } + }); + } + private static HashMap getIdMap(A[] keys, Property keyProperty, Property valueProperty) { HashMap map = new HashMap(); TodorooCursor tasks = PluginServices.getTaskService().query(Query.select(keyProperty, valueProperty).where(keyProperty.in(keys))); diff --git a/astrid/plugin-src/com/todoroo/astrid/subtasks/SubtasksTagListFragment.java b/astrid/plugin-src/com/todoroo/astrid/subtasks/SubtasksTagListFragment.java index 91f68910b..0eeafe22f 100644 --- a/astrid/plugin-src/com/todoroo/astrid/subtasks/SubtasksTagListFragment.java +++ b/astrid/plugin-src/com/todoroo/astrid/subtasks/SubtasksTagListFragment.java @@ -82,6 +82,7 @@ public class SubtasksTagListFragment extends TagViewFragment { @Override protected void refresh() { + initializeTaskListMetadata(); setUpTaskList(); } diff --git a/astrid/plugin-src/com/todoroo/astrid/tags/TagService.java b/astrid/plugin-src/com/todoroo/astrid/tags/TagService.java index c6de951ff..a12397f0d 100644 --- a/astrid/plugin-src/com/todoroo/astrid/tags/TagService.java +++ b/astrid/plugin-src/com/todoroo/astrid/tags/TagService.java @@ -44,7 +44,6 @@ import com.todoroo.astrid.data.RemoteModel; import com.todoroo.astrid.data.SyncFlags; import com.todoroo.astrid.data.TagData; import com.todoroo.astrid.data.Task; -import com.todoroo.astrid.data.TaskApiDao; import com.todoroo.astrid.service.MetadataService; import com.todoroo.astrid.service.TagDataService; import com.todoroo.astrid.service.TaskService; @@ -180,7 +179,7 @@ public final class TagService { Criterion.not(Task.UUID.in(Query.select(TaskToTagMetadata.TASK_UUID).from(Metadata.TABLE) .where(Criterion.and(MetadataCriteria.withKey(TaskToTagMetadata.KEY), Metadata.DELETION_DATE.eq(0))))), TaskCriteria.isActive(), - TaskApiDao.TaskCriteria.ownedByMe(), + TaskCriteria.ownedByMe(), TaskCriteria.isVisible())); } diff --git a/astrid/plugin-src/com/todoroo/astrid/tags/reusable/FeaturedListFilterExposer.java b/astrid/plugin-src/com/todoroo/astrid/tags/reusable/FeaturedListFilterExposer.java index cd6fcb0de..c745ef4fb 100644 --- a/astrid/plugin-src/com/todoroo/astrid/tags/reusable/FeaturedListFilterExposer.java +++ b/astrid/plugin-src/com/todoroo/astrid/tags/reusable/FeaturedListFilterExposer.java @@ -22,13 +22,13 @@ import com.todoroo.astrid.api.FilterListItem; import com.todoroo.astrid.api.FilterWithCustomIntent; import com.todoroo.astrid.api.FilterWithUpdate; import com.todoroo.astrid.core.PluginServices; +import com.todoroo.astrid.dao.TaskDao.TaskCriteria; import com.todoroo.astrid.data.Metadata; import com.todoroo.astrid.data.TagData; -import com.todoroo.astrid.data.TaskApiDao.TaskCriteria; import com.todoroo.astrid.tags.TagFilterExposer; -import com.todoroo.astrid.tags.TaskToTagMetadata; import com.todoroo.astrid.tags.TagService; import com.todoroo.astrid.tags.TagService.Tag; +import com.todoroo.astrid.tags.TaskToTagMetadata; public class FeaturedListFilterExposer extends TagFilterExposer { diff --git a/astrid/res/layout/control_set_producteev.xml b/astrid/res/layout/control_set_producteev.xml deleted file mode 100644 index cefea71c6..000000000 --- a/astrid/res/layout/control_set_producteev.xml +++ /dev/null @@ -1,56 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - diff --git a/astrid/res/layout/producteev_login_activity.xml b/astrid/res/layout/producteev_login_activity.xml deleted file mode 100644 index c633292a7..000000000 --- a/astrid/res/layout/producteev_login_activity.xml +++ /dev/null @@ -1,112 +0,0 @@ - - - - - - - - - - - - - - - -