Fixed sync issues:

- no more double-synchronizing, which gives "Service invocation failed" error
  - better logging
  - notes are now pushed to RTM if they don't already exist. this is a small hack
  - auto synchronization option
  - hacky screen gesture animations. they're really terrible, but kinda cool
  - completed tasks are marked as done on RTM, not deleted
  - tag-to-list conversion is now case insensitive

  Also, restored full tabs, and added animation xml's.
pull/14/head
Tim Su 17 years ago
parent f649568c14
commit 01af108e96

@ -1,13 +1,13 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android" <manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.timsu.astrid" package="com.timsu.astrid"
android:versionCode="57" android:versionCode="58"
android:versionName="2.0-beta"> android:versionName="2.0.1-beta">
<meta-data android:name="com.a0soft.gphone.aTrackDog.webURL" <meta-data android:name="com.a0soft.gphone.aTrackDog.webURL"
android:value="http://www.weloveastrid.com" /> android:value="http://www.weloveastrid.com" />
<meta-data android:name="com.a0soft.gphone.aTrackDog.testVersion" <meta-data android:name="com.a0soft.gphone.aTrackDog.testVersion"
android:value="57" /> android:value="58" />
<uses-permission android:name="android.permission.VIBRATE"/> <uses-permission android:name="android.permission.VIBRATE"/>
<uses-permission android:name="android.permission.INTERNET"/> <uses-permission android:name="android.permission.INTERNET"/>

@ -0,0 +1,19 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Copyright (C) 2008 The Android Open Source Project
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->
<set xmlns:android="http://schemas.android.com/apk/res/android">
<translate android:fromXDelta="100%p" android:toXDelta="0" android:duration="400"/>
</set>

@ -0,0 +1,19 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Copyright (C) 2008 The Android Open Source Project
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->
<set xmlns:android="http://schemas.android.com/apk/res/android">
<translate android:fromXDelta="0" android:toXDelta="-100%p" android:duration="400"/>
</set>

@ -0,0 +1,19 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Copyright (C) 2008 The Android Open Source Project
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->
<set xmlns:android="http://schemas.android.com/apk/res/android">
<translate android:fromXDelta="-100%p" android:toXDelta="0" android:duration="400"/>
</set>

@ -0,0 +1,19 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Copyright (C) 2008 The Android Open Source Project
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->
<set xmlns:android="http://schemas.android.com/apk/res/android">
<translate android:fromXDelta="0" android:toXDelta="100%p" android:duration="400"/>
</set>

@ -58,16 +58,6 @@
android:layout_height="wrap_content"> android:layout_height="wrap_content">
</LinearLayout> </LinearLayout>
<TextView android:id="@+id/estimatedDuration_label"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/estimatedDuration_label"
style="@style/TextAppearance.EditEvent_Label"/>
<Button android:id="@+id/estimatedDuration"
android:layout_width="fill_parent"
android:layout_height="wrap_content"/>
<TextView android:id="@+id/tags_label" <TextView android:id="@+id/tags_label"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
@ -268,6 +258,17 @@
android:background="@android:drawable/divider_horizontal_dark" android:background="@android:drawable/divider_horizontal_dark"
/> />
<TextView android:id="@+id/estimatedDuration_label"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/estimatedDuration_label"
style="@style/TextAppearance.EditEvent_Label"/>
<Button android:id="@+id/estimatedDuration"
android:layout_width="fill_parent"
android:layout_height="wrap_content"/>
<TextView android:id="@+id/elapsedDuration_label" <TextView android:id="@+id/elapsedDuration_label"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"

@ -199,10 +199,14 @@ If you don\'t want to see the new task right after you complete the old one, you
<!-- Synchronization --> <!-- Synchronization -->
<skip /> <skip />
<string name="p_sync_rtm">sync_rtm</string> <string name="p_sync_rtm">sync_rtm</string>
<string name="p_sync_every">sync_every</string>
<string name="sync_pref_group">Synchronization Services</string> <string name="sync_pref_group">Synchronization Services</string>
<string name="sync_pref_group_actions">Actions</string> <string name="sync_pref_group_actions">Actions</string>
<string name="sync_pref_group_options">Options</string>
<string name="sync_rtm_title">Remember The Milk</string> <string name="sync_rtm_title">Remember The Milk</string>
<string name="sync_rtm_desc">http://www.rememberthemilk.com</string> <string name="sync_rtm_desc">http://www.rememberthemilk.com</string>
<string name="sync_every_title">Synchronize Frequency</string>
<string name="sync_every_desc">If set, sync every # hours when Astrid starts</string>
<string name="sync_error">Sync Error! Sorry for the inconvenience! Error:</string> <string name="sync_error">Sync Error! Sorry for the inconvenience! Error:</string>
<string name="sync_auth_request"> <string name="sync_auth_request">
In order to synchronize, please log in to your %s account and authorize Astrid to read your data. In order to synchronize, please log in to your %s account and authorize Astrid to read your data.
@ -210,17 +214,17 @@ In order to synchronize, please log in to your %s account and authorize Astrid t
When finished, restart Astrid and come back here. When finished, restart Astrid and come back here.
</string> </string>
<string name="sync_rtm_notes"> <string name="sync_rtm_notes">
Astrid's RTM sync is not complete! Welcome to Astrid\'s RTM sync!
\n\n
- RTM tags are not read. Instead, RTM's lists are mapped to Astrid\'s tags.\n - RTM tags are not read. Instead, RTM's lists are mapped to Astrid\'s tags.\n
- Task notes are read from RTM, but changes are not written back.\n - Task notes are read from RTM, but edits are not written back.\n
- Notifications and repeats are not synchronized.\n - Notifications and repeats are not synchronized.\n
- Moving tasks in RTM to another list and then editing the task name causes duplication.\n - Moving tasks in RTM to another list and then renaming them causes dupes.\n
- Synchronization is SLOW! Be patient.\n - Deleting tasks in RTM is not detected.\n
\n\n - Synchronization is SLOW!\n
Wish me luck!\n Wish me luck!\n
</string> </string>
<string name="sync_now">Synchronize!</string> <string name="sync_now">Synchronize Now!</string>
<string name="sync_forget">Clear Personal Data</string> <string name="sync_forget">Clear Personal Data</string>
<string name="sync_forget_confirm">Clear data for selected services?</string> <string name="sync_forget_confirm">Clear data for selected services?</string>

@ -12,4 +12,14 @@
</PreferenceCategory> </PreferenceCategory>
<PreferenceCategory
android:title="@string/sync_pref_group_options">
<EditTextPreference
android:key="@string/p_sync_every"
android:title="@string/sync_every_title"
android:summary="@string/sync_every_desc" />
</PreferenceCategory>
</PreferenceScreen> </PreferenceScreen>

@ -9,6 +9,7 @@ import android.widget.Button;
import com.timsu.astrid.R; import com.timsu.astrid.R;
import com.timsu.astrid.sync.Synchronizer; import com.timsu.astrid.sync.Synchronizer;
import com.timsu.astrid.sync.Synchronizer.SynchronizerListener;
import com.timsu.astrid.utilities.DialogUtilities; import com.timsu.astrid.utilities.DialogUtilities;
public class SyncPreferences extends PreferenceActivity { public class SyncPreferences extends PreferenceActivity {
@ -21,10 +22,23 @@ public class SyncPreferences extends PreferenceActivity {
getListView().addFooterView(getLayoutInflater().inflate( getListView().addFooterView(getLayoutInflater().inflate(
R.layout.sync_footer, getListView(), false)); R.layout.sync_footer, getListView(), false));
((Button)findViewById(R.id.sync)).setOnClickListener(new View.OnClickListener() { final Button syncButton = ((Button)findViewById(R.id.sync));
syncButton.setOnClickListener(new View.OnClickListener() {
@Override @Override
public void onClick(View v) { public void onClick(View v) {
Synchronizer.synchronize(SyncPreferences.this); syncButton.setEnabled(false);
Synchronizer.synchronize(SyncPreferences.this, new SynchronizerListener() {
@Override
public void onSynchronizerFinished(int numServicesSynced) {
syncButton.setEnabled(true);
if(numServicesSynced == 0) {
DialogUtilities.okDialog(SyncPreferences.this,
"Nothing to do!", null);
} else {
finish();
}
}
});
} }
}); });

@ -100,6 +100,11 @@ public class TagList extends Activity {
gestureDetector = new GestureDetector(new TagListGestureDetector()); gestureDetector = new GestureDetector(new TagListGestureDetector());
} }
@Override
public void finish() {
super.finish();
}
class TagListGestureDetector extends GestureDetector.SimpleOnGestureListener { class TagListGestureDetector extends GestureDetector.SimpleOnGestureListener {
@Override @Override
public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) { public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) {

@ -48,11 +48,9 @@ import android.widget.CompoundButton;
import android.widget.EditText; import android.widget.EditText;
import android.widget.ImageButton; import android.widget.ImageButton;
import android.widget.LinearLayout; import android.widget.LinearLayout;
import android.widget.RelativeLayout;
import android.widget.SimpleCursorAdapter; import android.widget.SimpleCursorAdapter;
import android.widget.Spinner; import android.widget.Spinner;
import android.widget.TabHost; import android.widget.TabHost;
import android.widget.TabWidget;
import android.widget.TextView; import android.widget.TextView;
import android.widget.ToggleButton; import android.widget.ToggleButton;
import android.widget.CompoundButton.OnCheckedChangeListener; import android.widget.CompoundButton.OnCheckedChangeListener;
@ -137,22 +135,22 @@ public class TaskEdit extends TaskModificationTabbedActivity<TaskModelForEdit> {
tabHost.getTabContentView(), true); tabHost.getTabContentView(), true);
tabHost.addTab(tabHost.newTabSpec(TAB_BASIC) tabHost.addTab(tabHost.newTabSpec(TAB_BASIC)
.setIndicator("", .setIndicator("Basic",
r.getDrawable(R.drawable.ic_dialog_info_c)) r.getDrawable(R.drawable.ic_dialog_info_c))
.setContent(R.id.tab_basic)); .setContent(R.id.tab_basic));
tabHost.addTab(tabHost.newTabSpec(TAB_DATES) tabHost.addTab(tabHost.newTabSpec(TAB_DATES)
.setIndicator("", .setIndicator("Dates",
r.getDrawable(R.drawable.ic_dialog_time_c)) r.getDrawable(R.drawable.ic_dialog_time_c))
.setContent(R.id.tab_dates)); .setContent(R.id.tab_dates));
tabHost.addTab(tabHost.newTabSpec(TAB_ALERTS) tabHost.addTab(tabHost.newTabSpec(TAB_ALERTS)
.setIndicator("", .setIndicator("Alerts",
r.getDrawable(R.drawable.ic_dialog_alert_c)) r.getDrawable(R.drawable.ic_dialog_alert_c))
.setContent(R.id.tab_notification)); .setContent(R.id.tab_notification));
TabWidget tabWidget = tabHost.getTabWidget(); // TabWidget tabWidget = tabHost.getTabWidget();
for(int i = 0; i < tabWidget.getChildCount(); i++) { // for(int i = 0; i < tabWidget.getChildCount(); i++) {
RelativeLayout tab = (RelativeLayout)tabWidget.getChildAt(i); // RelativeLayout tab = (RelativeLayout)tabWidget.getChildAt(i);
tab.getLayoutParams().height = 46; // tab.getLayoutParams().height = 46;
} // }
setUpUIComponents(); setUpUIComponents();
setUpListeners(); setUpListeners();

@ -58,7 +58,9 @@ import com.timsu.astrid.data.tag.TagModelForView;
import com.timsu.astrid.data.task.TaskController; import com.timsu.astrid.data.task.TaskController;
import com.timsu.astrid.data.task.TaskIdentifier; import com.timsu.astrid.data.task.TaskIdentifier;
import com.timsu.astrid.data.task.TaskModelForList; import com.timsu.astrid.data.task.TaskModelForList;
import com.timsu.astrid.sync.Synchronizer;
import com.timsu.astrid.utilities.Constants; import com.timsu.astrid.utilities.Constants;
import com.timsu.astrid.utilities.Preferences;
import com.timsu.astrid.utilities.StartupReceiver; import com.timsu.astrid.utilities.StartupReceiver;
@ -143,6 +145,22 @@ public class TaskList extends Activity {
tagController = new TagController(this); tagController = new TagController(this);
tagController.open(); tagController.open();
setupUIComponents();
fillData();
// auto sync
Integer autoSyncHours = Preferences.autoSyncFrequency(this);
if(autoSyncHours != null) {
Date lastSync = Preferences.getSyncLastSync(this);
if(lastSync == null || lastSync.getTime() +
1000L*3600*autoSyncHours < System.currentTimeMillis()) {
Synchronizer.synchronize(this, null);
}
}
}
public void setupUIComponents() {
listView = (ListView)findViewById(R.id.tasklist); listView = (ListView)findViewById(R.id.tasklist);
addButton = (Button)findViewById(R.id.addtask); addButton = (Button)findViewById(R.id.addtask);
addButton.setOnClickListener(new addButton.setOnClickListener(new
@ -165,8 +183,6 @@ public class TaskList extends Activity {
} }
}); });
fillData();
gestureDetector = new GestureDetector(new TaskListGestureDetector()); gestureDetector = new GestureDetector(new TaskListGestureDetector());
gestureTouchListener = new View.OnTouchListener() { gestureTouchListener = new View.OnTouchListener() {
public boolean onTouch(View v, MotionEvent event) { public boolean onTouch(View v, MotionEvent event) {

@ -144,13 +144,18 @@ public class TagController extends AbstractController {
TagModelForView.FIELD_LIST, TagModelForView.FIELD_LIST,
KEY_ROWID + "=" + id, null, null, null, null, null); KEY_ROWID + "=" + id, null, null, null, null, null);
if (cursor != null) { try {
cursor.moveToFirst(); if (cursor != null) {
TagModelForView model = new TagModelForView(cursor); cursor.moveToFirst();
return model; TagModelForView model = new TagModelForView(cursor);
return model;
}
throw new SQLException("Returned empty set!");
} finally {
if(cursor != null)
cursor.close();
} }
throw new SQLException("Returned empty set!");
} }
/** Deletes the tag and removes tag/task mappings */ /** Deletes the tag and removes tag/task mappings */

@ -55,16 +55,18 @@ public class TaskController extends AbstractController {
AbstractTaskModel.NOTIFICATIONS, AbstractTaskModel.NOTIFICATIONS,
AbstractTaskModel.NOTIFICATION_FLAGS), null, null, null, null, null); AbstractTaskModel.NOTIFICATION_FLAGS), null, null, null, null, null);
if(cursor.getCount() == 0) try {
return list; if(cursor.getCount() == 0)
return list;
do { do {
cursor.moveToNext(); cursor.moveToNext();
list.add(new TaskModelForNotify(cursor)); list.add(new TaskModelForNotify(cursor));
} while(!cursor.isLast()); } while(!cursor.isLast());
cursor.close(); return list;
return list; } finally {
cursor.close();
}
} }
/** Return a list of all active tasks with deadlines */ /** Return a list of all active tasks with deadlines */
@ -77,16 +79,19 @@ public class TaskController extends AbstractController {
AbstractTaskModel.DEFINITE_DUE_DATE, AbstractTaskModel.DEFINITE_DUE_DATE,
AbstractTaskModel.PREFERRED_DUE_DATE), null, null, null, null, null); AbstractTaskModel.PREFERRED_DUE_DATE), null, null, null, null, null);
if(cursor.getCount() == 0) try {
return list; if(cursor.getCount() == 0)
return list;
do { do {
cursor.moveToNext(); cursor.moveToNext();
list.add(new TaskModelForNotify(cursor)); list.add(new TaskModelForNotify(cursor));
} while(!cursor.isLast()); } while(!cursor.isLast());
cursor.close(); return list;
return list; } finally {
cursor.close();
}
} }
/** Return a list of all of the tasks with progress < COMPLETE_PERCENTAGE */ /** Return a list of all of the tasks with progress < COMPLETE_PERCENTAGE */
@ -119,21 +124,48 @@ public class TaskController extends AbstractController {
} }
/** Get identifiers for all tasks */ /** Get identifiers for all tasks */
public HashSet<TaskIdentifier> getAllTaskIdentifiers() {
HashSet<TaskIdentifier> list = new HashSet<TaskIdentifier>();
Cursor cursor = database.query(TASK_TABLE_NAME, new String[] { KEY_ROWID },
null, null, null, null, null, null);
try {
if(cursor.getCount() == 0)
return list;
do {
cursor.moveToNext();
list.add(new TaskIdentifier(cursor.getInt(
cursor.getColumnIndexOrThrow(KEY_ROWID))));
} while(!cursor.isLast());
return list;
} finally {
cursor.close();
}
}
/** Get identifiers for all non-completed tasks */
public HashSet<TaskIdentifier> getActiveTaskIdentifiers() { public HashSet<TaskIdentifier> getActiveTaskIdentifiers() {
HashSet<TaskIdentifier> list = new HashSet<TaskIdentifier>(); HashSet<TaskIdentifier> list = new HashSet<TaskIdentifier>();
Cursor cursor = database.query(TASK_TABLE_NAME, new String[] { KEY_ROWID }, Cursor cursor = database.query(TASK_TABLE_NAME, new String[] { KEY_ROWID },
AbstractTaskModel.PROGRESS_PERCENTAGE + " < " + AbstractTaskModel.PROGRESS_PERCENTAGE + " < " +
AbstractTaskModel.COMPLETE_PERCENTAGE, null, null, null, null, null); AbstractTaskModel.COMPLETE_PERCENTAGE, null, null, null, null, null);
if(cursor.getCount() == 0)
return list;
do { try {
cursor.moveToNext(); if(cursor.getCount() == 0)
list.add(new TaskIdentifier(cursor.getInt( return list;
cursor.getColumnIndexOrThrow(KEY_ROWID))));
} while(!cursor.isLast());
return list; do {
cursor.moveToNext();
list.add(new TaskIdentifier(cursor.getInt(
cursor.getColumnIndexOrThrow(KEY_ROWID))));
} while(!cursor.isLast());
return list;
} finally {
cursor.close();
}
} }
/** Create a weighted list of tasks from the db cursor given */ /** Create a weighted list of tasks from the db cursor given */
@ -280,7 +312,10 @@ public class TaskController extends AbstractController {
/** Returns a TaskModelForView by name */ /** Returns a TaskModelForView by name */
public TaskModelForSync searchForTaskForSync(String name) throws SQLException { public TaskModelForSync searchForTaskForSync(String name) throws SQLException {
Cursor cursor = database.query(true, TASK_TABLE_NAME, TaskModelForSync.FIELD_LIST, Cursor cursor = database.query(true, TASK_TABLE_NAME, TaskModelForSync.FIELD_LIST,
AbstractTaskModel.NAME + " = ?", new String[] { name }, null, null, null, null); AbstractTaskModel.NAME + " = ? AND " +
AbstractTaskModel.PROGRESS_PERCENTAGE + " < "+
AbstractTaskModel.COMPLETE_PERCENTAGE,
new String[] { name }, null, null, null, null);
if (cursor == null || cursor.getCount() == 0) if (cursor == null || cursor.getCount() == 0)
return null; return null;
cursor.moveToFirst(); cursor.moveToFirst();

@ -25,11 +25,12 @@ import android.database.Cursor;
import com.timsu.astrid.data.AbstractController; import com.timsu.astrid.data.AbstractController;
import com.timsu.astrid.data.enums.Importance; import com.timsu.astrid.data.enums.Importance;
import com.timsu.astrid.utilities.Notifications.Notifiable;
/** Fields that you would want to synchronize in the TaskModel */ /** Fields that you would want to synchronize in the TaskModel */
public class TaskModelForSync extends AbstractTaskModel { public class TaskModelForSync extends AbstractTaskModel implements Notifiable {
static String[] FIELD_LIST = new String[] { static String[] FIELD_LIST = new String[] {
AbstractController.KEY_ROWID, AbstractController.KEY_ROWID,
@ -46,6 +47,9 @@ public class TaskModelForSync extends AbstractTaskModel {
COMPLETION_DATE, COMPLETION_DATE,
NOTES, NOTES,
REPEAT, REPEAT,
LAST_NOTIFIED,
NOTIFICATIONS,
NOTIFICATION_FLAGS,
}; };
// --- constructors // --- constructors
@ -132,6 +136,23 @@ public class TaskModelForSync extends AbstractTaskModel {
return super.getRepeat(); return super.getRepeat();
} }
@Override
public Integer getNotificationIntervalSeconds() {
return super.getNotificationIntervalSeconds();
}
@Override
public int getNotificationFlags() {
return super.getNotificationFlags();
}
@Override
public Date getLastNotificationDate() {
return super.getLastNotificationDate();
}
// --- setters
@Override @Override
public void setDefiniteDueDate(Date definiteDueDate) { public void setDefiniteDueDate(Date definiteDueDate) {
super.setDefiniteDueDate(definiteDueDate); super.setDefiniteDueDate(definiteDueDate);
@ -197,6 +218,9 @@ public class TaskModelForSync extends AbstractTaskModel {
super.setProgressPercentage(progressPercentage); super.setProgressPercentage(progressPercentage);
} }
@Override
public void setNotificationIntervalSeconds(Integer intervalInSeconds) {
super.setNotificationIntervalSeconds(intervalInSeconds);
}
} }

@ -134,7 +134,6 @@ public class RTMSyncService extends SynchronizationService {
@Override @Override
public void run() { public void run() {
performSyncInNewThread(activity); performSyncInNewThread(activity);
Synchronizer.closeControllers();
} }
}).start(); }).start();
} }
@ -157,7 +156,7 @@ public class RTMSyncService extends SynchronizationService {
// load RTM lists // load RTM lists
RtmLists lists = rtmService.lists_getList(); RtmLists lists = rtmService.lists_getList();
for(RtmList list : lists.getLists().values()) { for(RtmList list : lists.getLists().values()) {
listNameToIdMap.put(list.getName(), list.getId()); listNameToIdMap.put(list.getName().toLowerCase(), list.getId());
listIdToNameMap.put(list.getId(), list.getName()); listIdToNameMap.put(list.getId(), list.getName());
// read the name of the inbox with the correct case // read the name of the inbox with the correct case
@ -184,16 +183,16 @@ public class RTMSyncService extends SynchronizationService {
public String createTask(String listName) throws IOException { public String createTask(String listName) throws IOException {
if(listName == null) if(listName == null)
listName = INBOX_LIST_NAME; listName = INBOX_LIST_NAME;
if(!listNameToIdMap.containsKey(listName)) { if(!listNameToIdMap.containsKey(listName.toLowerCase())) {
try { try {
String listId = String listId =
rtmService.lists_add(timeline, listName).getId(); rtmService.lists_add(timeline, listName).getId();
listNameToIdMap.put(listName, listId); listNameToIdMap.put(listName.toLowerCase(), listId);
} catch (Exception e) { } catch (Exception e) {
listName = INBOX_LIST_NAME; listName = INBOX_LIST_NAME;
} }
} }
String listId = listNameToIdMap.get(listName); String listId = listNameToIdMap.get(listName.toLowerCase());
RtmTaskSeries s = rtmService.tasks_add(timeline, RtmTaskSeries s = rtmService.tasks_add(timeline,
listId, "tmp"); listId, "tmp");
return new RtmId(listId, s).toString(); return new RtmId(listId, s).toString();
@ -219,7 +218,9 @@ public class RTMSyncService extends SynchronizationService {
} }
}); });
Preferences.setSyncRTMLastSync(activity, new Date()); // add a bit of fudge time so we don't load tasks we just edited
Date syncTime = new Date(System.currentTimeMillis() + 1000);
Preferences.setSyncRTMLastSync(activity, syncTime);
} catch (Exception e) { } catch (Exception e) {
showError(activity, e); showError(activity, e);

@ -7,13 +7,16 @@ import java.util.LinkedList;
import java.util.List; import java.util.List;
import android.app.Activity; import android.app.Activity;
import android.app.Dialog;
import android.app.ProgressDialog; import android.app.ProgressDialog;
import android.content.Context; import android.content.Context;
import android.content.DialogInterface;
import android.content.res.Resources; import android.content.res.Resources;
import android.os.Handler; import android.os.Handler;
import android.util.Log; import android.util.Log;
import com.timsu.astrid.R; import com.timsu.astrid.R;
import com.timsu.astrid.data.alerts.AlertController;
import com.timsu.astrid.data.sync.SyncDataController; import com.timsu.astrid.data.sync.SyncDataController;
import com.timsu.astrid.data.sync.SyncMapping; import com.timsu.astrid.data.sync.SyncMapping;
import com.timsu.astrid.data.tag.TagController; import com.timsu.astrid.data.tag.TagController;
@ -23,6 +26,8 @@ import com.timsu.astrid.data.task.TaskController;
import com.timsu.astrid.data.task.TaskIdentifier; import com.timsu.astrid.data.task.TaskIdentifier;
import com.timsu.astrid.data.task.TaskModelForSync; import com.timsu.astrid.data.task.TaskModelForSync;
import com.timsu.astrid.utilities.DialogUtilities; import com.timsu.astrid.utilities.DialogUtilities;
import com.timsu.astrid.utilities.Notifications;
import com.timsu.astrid.utilities.Preferences;
/** A service that synchronizes with Astrid /** A service that synchronizes with Astrid
* *
@ -32,35 +37,26 @@ import com.timsu.astrid.utilities.DialogUtilities;
public abstract class SynchronizationService { public abstract class SynchronizationService {
private int id; private int id;
protected ProgressDialog progressDialog; static ProgressDialog progressDialog;
protected Handler syncHandler = new Handler(); protected Handler syncHandler;
public SynchronizationService(int id) { public SynchronizationService(int id) {
this.id = id; this.id = id;
} }
// called off the UI thread. does some setup // called off the UI thread. does some setup
void synchronizeService(final Activity activity) { void synchronizeService(final Activity activity) {
syncHandler.post(new Runnable() { syncHandler = new Handler();
@Override progressDialog = new ProgressDialog(activity);
public void run() { progressDialog.setIcon(android.R.drawable.ic_dialog_alert);
progressDialog = new ProgressDialog(activity); progressDialog.setTitle("Synchronization");
progressDialog.setIcon(android.R.drawable.ic_dialog_alert); progressDialog.setProgressStyle(ProgressDialog.STYLE_HORIZONTAL);
progressDialog.setTitle("Synchronization"); progressDialog.setMax(100);
progressDialog.setProgressStyle(ProgressDialog.STYLE_HORIZONTAL); progressDialog.setMessage("Checking Authorization...");
progressDialog.setMax(100); progressDialog.setProgress(0);
progressDialog.setMessage("Checking Authorization..."); progressDialog.setCancelable(false);
progressDialog.setProgress(0); progressDialog.show();
progressDialog.show();
}
});
synchronize(activity); synchronize(activity);
syncHandler.post(new Runnable() {
@Override
public void run() {
progressDialog.dismiss();
}
});
} }
/** Synchronize with the service */ /** Synchronize with the service */
@ -142,14 +138,27 @@ public abstract class SynchronizationService {
protected void synchronizeTasks(final Activity activity, List<TaskProxy> remoteTasks, protected void synchronizeTasks(final Activity activity, List<TaskProxy> remoteTasks,
SynchronizeHelper helper) throws IOException { SynchronizeHelper helper) throws IOException {
final SyncStats stats = new SyncStats(); final SyncStats stats = new SyncStats();
final StringBuilder log = new StringBuilder();
syncHandler.post(new Runnable() {
@Override
public void run() {
if(!progressDialog.isShowing())
progressDialog.show();
}
});
SyncDataController syncController = Synchronizer.getSyncController(activity); SyncDataController syncController = Synchronizer.getSyncController(activity);
TaskController taskController = Synchronizer.getTaskController(activity); TaskController taskController = Synchronizer.getTaskController(activity);
TagController tagController = Synchronizer.getTagController(activity); TagController tagController = Synchronizer.getTagController(activity);
AlertController alertController = Synchronizer.getAlertController(activity);
// get data out of the database (note we get non-completed tasks only) // get data out of the database
HashSet<SyncMapping> mappings = syncController.getSyncMapping(getId()); HashSet<SyncMapping> mappings = syncController.getSyncMapping(getId());
HashSet<TaskIdentifier> localTasks = taskController.getActiveTaskIdentifiers(); HashSet<TaskIdentifier> activeTasks = taskController.
getActiveTaskIdentifiers();
HashSet<TaskIdentifier> allTasks = taskController.
getAllTaskIdentifiers();
HashMap<TagIdentifier, TagModelForView> tags = HashMap<TagIdentifier, TagModelForView> tags =
tagController.getAllTagsAsMap(activity); tagController.getAllTagsAsMap(activity);
@ -179,6 +188,7 @@ public abstract class SynchronizationService {
} }
// grab tasks without a sync mapping and create them remotely // grab tasks without a sync mapping and create them remotely
log.append(">> on remote server:\n");
syncHandler.post(new Runnable() { syncHandler.post(new Runnable() {
@Override @Override
public void run() { public void run() {
@ -186,8 +196,7 @@ public abstract class SynchronizationService {
progressDialog.setProgress(0); progressDialog.setProgress(0);
} }
}); });
HashSet<TaskIdentifier> newlyCreatedTasks = new HashSet<TaskIdentifier>( HashSet<TaskIdentifier> newlyCreatedTasks = new HashSet<TaskIdentifier>(activeTasks);
localTasks);
newlyCreatedTasks.removeAll(mappedTasks); newlyCreatedTasks.removeAll(mappedTasks);
for(TaskIdentifier taskId : newlyCreatedTasks) { for(TaskIdentifier taskId : newlyCreatedTasks) {
LinkedList<TagIdentifier> taskTags = LinkedList<TagIdentifier> taskTags =
@ -210,6 +219,7 @@ public abstract class SynchronizationService {
helper.pushTask(localTask, mapping); helper.pushTask(localTask, mapping);
// update stats // update stats
log.append("add " + task.getName() + "\n");
stats.remoteCreatedTasks++; stats.remoteCreatedTasks++;
syncHandler.post(new ProgressUpdater(stats.remoteCreatedTasks, syncHandler.post(new ProgressUpdater(stats.remoteCreatedTasks,
newlyCreatedTasks.size())); newlyCreatedTasks.size()));
@ -223,8 +233,9 @@ public abstract class SynchronizationService {
progressDialog.setProgress(0); progressDialog.setProgress(0);
} }
}); });
HashSet<TaskIdentifier> deletedTasks = new HashSet<TaskIdentifier>(mappedTasks); HashSet<TaskIdentifier> deletedTasks = new HashSet<TaskIdentifier>(
deletedTasks.removeAll(localTasks); mappedTasks);
deletedTasks.removeAll(allTasks);
for(TaskIdentifier taskId : deletedTasks) { for(TaskIdentifier taskId : deletedTasks) {
SyncMapping mapping = localIdToSyncMapping.get(taskId); SyncMapping mapping = localIdToSyncMapping.get(taskId);
syncController.deleteSyncMapping(mapping); syncController.deleteSyncMapping(mapping);
@ -236,6 +247,7 @@ public abstract class SynchronizationService {
remoteChangeMap.remove(taskId); remoteChangeMap.remove(taskId);
// update stats // update stats
log.append("del #" + taskId.getId() + "\n");
stats.remoteDeletedTasks++; stats.remoteDeletedTasks++;
syncHandler.post(new ProgressUpdater(stats.remoteDeletedTasks, syncHandler.post(new ProgressUpdater(stats.remoteDeletedTasks,
deletedTasks.size())); deletedTasks.size()));
@ -262,6 +274,9 @@ public abstract class SynchronizationService {
remoteConflict = remoteChangeMap.get(mapping.getTask()); remoteConflict = remoteChangeMap.get(mapping.getTask());
localTask.mergeWithOther(remoteConflict); localTask.mergeWithOther(remoteConflict);
stats.mergedTasks++; stats.mergedTasks++;
log.append("mrg " + task.getName() + "\n");
} else {
log.append("upd " + task.getName() + "\n");
} }
try { try {
@ -276,13 +291,21 @@ public abstract class SynchronizationService {
TaskProxy newTask = helper.refetchTask(remoteConflict); TaskProxy newTask = helper.refetchTask(remoteConflict);
remoteTasks.remove(remoteConflict); remoteTasks.remove(remoteConflict);
remoteTasks.add(newTask); remoteTasks.add(newTask);
} } else
stats.remoteUpdatedTasks++; stats.remoteUpdatedTasks++;
syncHandler.post(new ProgressUpdater(stats.remoteUpdatedTasks, syncHandler.post(new ProgressUpdater(stats.remoteUpdatedTasks,
localChanges.size())); localChanges.size()));
} }
// load remote information // load remote information
log.append(">> on astrid:\n");
syncHandler.post(new Runnable() {
@Override
public void run() {
progressDialog.setMessage("Updating local tasks");
progressDialog.setProgress(0);
}
});
for(TaskProxy remoteTask : remoteTasks) { for(TaskProxy remoteTask : remoteTasks) {
SyncMapping mapping = null; SyncMapping mapping = null;
TaskModelForSync task = null; TaskModelForSync task = null;
@ -297,18 +320,23 @@ public abstract class SynchronizationService {
task = taskController.searchForTaskForSync(remoteTask.name); task = taskController.searchForTaskForSync(remoteTask.name);
if(task == null) { if(task == null) {
task = new TaskModelForSync(); task = new TaskModelForSync();
setupTaskDefaults(activity, task);
log.append("add " + remoteTask.name + "\n");
} else { } else {
mapping = localIdToSyncMapping.get(task.getTaskIdentifier()); mapping = localIdToSyncMapping.get(task.getTaskIdentifier());
log.append("mov " + remoteTask.name + "\n");
} }
} else { } else {
mapping = remoteIdToSyncMapping.get(remoteTask.getRemoteId()); mapping = remoteIdToSyncMapping.get(remoteTask.getRemoteId());
if(remoteTask.isDeleted()) { if(remoteTask.isDeleted()) {
taskController.deleteTask(mapping.getTask()); taskController.deleteTask(mapping.getTask());
syncController.deleteSyncMapping(mapping); syncController.deleteSyncMapping(mapping);
log.append("del " + remoteTask.name + "\n");
stats.localDeletedTasks++; stats.localDeletedTasks++;
continue; continue;
} }
log.append("upd " + remoteTask.name + "\n");
task = taskController.fetchTaskForSync( task = taskController.fetchTaskForSync(
mapping.getTask()); mapping.getTask());
} }
@ -346,6 +374,9 @@ public abstract class SynchronizationService {
syncController.saveSyncMapping(mapping); syncController.saveSyncMapping(mapping);
stats.localCreatedTasks++; stats.localCreatedTasks++;
} }
Notifications.updateAlarm(activity, taskController, alertController,
task);
} }
stats.localUpdatedTasks -= stats.localCreatedTasks; stats.localUpdatedTasks -= stats.localCreatedTasks;
@ -353,11 +384,18 @@ public abstract class SynchronizationService {
syncHandler.post(new Runnable() { syncHandler.post(new Runnable() {
@Override @Override
public void run() { public void run() {
stats.showDialog(activity); stats.showDialog(activity, log.toString());
} }
}); });
} }
/** Set up defaults from preferences for this task */
private void setupTaskDefaults(Activity activity, TaskModelForSync task) {
Integer reminder = Preferences.getDefaultReminder(activity);
if(reminder != null)
task.setNotificationIntervalSeconds(24*3600*reminder);
}
// --- helper classes // --- helper classes
protected class SyncStats { protected class SyncStats {
@ -372,17 +410,29 @@ public abstract class SynchronizationService {
int remoteDeletedTasks = 0; int remoteDeletedTasks = 0;
/** Display a dialog with statistics */ /** Display a dialog with statistics */
public void showDialog(Context context) { public void showDialog(final Activity activity, String log) {
progressDialog.dismiss(); progressDialog.hide();
Dialog.OnClickListener finishListener = new Dialog.OnClickListener() {
@Override
public void onClick(DialogInterface dialog,
int which) {
Synchronizer.continueSynchronization(activity);
}
};
if(equals(new SyncStats())) { // i.e. no change // nothing updated
DialogUtilities.okDialog(context, "Nothing to do!", null); if(localCreatedTasks + localUpdatedTasks + localDeletedTasks +
mergedTasks + remoteCreatedTasks + remoteDeletedTasks +
remoteUpdatedTasks == 0) {
DialogUtilities.okDialog(activity, "Sync: Up to date!", finishListener);
return; return;
} }
StringBuilder sb = new StringBuilder(); StringBuilder sb = new StringBuilder();
sb.append(getName()).append(" Sync Results:"); // TODO i18n sb.append(getName()).append(" Sync Results:"); // TODO i18n
sb.append("\n\n--- Astrid Tasks ---"); sb.append("\n\n");
sb.append(log);
sb.append("\n--- Summary: Astrid Tasks ---");
if(localCreatedTasks > 0) if(localCreatedTasks > 0)
sb.append("\nCreated: " + localCreatedTasks); sb.append("\nCreated: " + localCreatedTasks);
if(localUpdatedTasks > 0) if(localUpdatedTasks > 0)
@ -393,7 +443,7 @@ public abstract class SynchronizationService {
if(mergedTasks > 0) if(mergedTasks > 0)
sb.append("\n\nMerged: " + localCreatedTasks); sb.append("\n\nMerged: " + localCreatedTasks);
sb.append("\n\n--- Remote Tasks ---"); sb.append("\n\n--- Summary: Remote Server ---");
if(remoteCreatedTasks > 0) if(remoteCreatedTasks > 0)
sb.append("\nCreated: " + remoteCreatedTasks); sb.append("\nCreated: " + remoteCreatedTasks);
if(remoteUpdatedTasks > 0) if(remoteUpdatedTasks > 0)
@ -403,7 +453,7 @@ public abstract class SynchronizationService {
sb.append("\n"); sb.append("\n");
DialogUtilities.okDialog(context, sb.toString(), null); DialogUtilities.okDialog(activity, sb.toString(), finishListener);
} }
} }

@ -1,10 +1,11 @@
package com.timsu.astrid.sync; package com.timsu.astrid.sync;
import java.util.HashMap; import java.util.Date;
import java.util.Map;
import android.app.Activity; import android.app.Activity;
import android.content.Context;
import com.timsu.astrid.data.alerts.AlertController;
import com.timsu.astrid.data.sync.SyncDataController; import com.timsu.astrid.data.sync.SyncDataController;
import com.timsu.astrid.data.tag.TagController; import com.timsu.astrid.data.tag.TagController;
import com.timsu.astrid.data.task.TaskController; import com.timsu.astrid.data.task.TaskController;
@ -12,37 +13,114 @@ import com.timsu.astrid.utilities.Preferences;
public class Synchronizer { public class Synchronizer {
// Synchronization Service ID's
private static final int SYNC_ID_RTM = 1; private static final int SYNC_ID_RTM = 1;
// --- public interface // --- public interface
/** Synchronize all activated sync services */ public interface SynchronizerListener {
public static void synchronize(final Activity activity) { void onSynchronizerFinished(int numServicesSynced);
}
// RTM sync
if(Preferences.shouldSyncRTM(activity)) {
services.get(SYNC_ID_RTM).synchronizeService(activity);
}
/** Synchronize all activated sync services */
public static void synchronize(Activity activity, SynchronizerListener listener) {
currentStep = ServiceWrapper._FIRST_SERVICE.ordinal();
servicesSynced = 0;
callback = listener;
continueSynchronization(activity);
} }
/** Clears tokens if services are disabled */ /** Clears tokens if services are disabled */
public static void clearUserData(Activity activity) { public static void clearUserData(Activity activity) {
if(Preferences.shouldSyncRTM(activity)) { for(ServiceWrapper serviceWrapper : ServiceWrapper.values()) {
services.get(SYNC_ID_RTM).clearPersonalData(activity); if(serviceWrapper.isActivated(activity)) {
serviceWrapper.service.clearPersonalData(activity);
}
} }
} }
// --- package helpers // --- internal synchronization logic
/** Synchronization Services enumeration
* note that id must be kept constant!
* @author timsu
*
*/
private enum ServiceWrapper {
_FIRST_SERVICE(null) { // must be first entry
@Override
boolean isActivated(Context arg0) {
return false;
}
},
RTM(new RTMSyncService(SYNC_ID_RTM)) {
@Override
boolean isActivated(Context context) {
return Preferences.shouldSyncRTM(context);
}
},
_LAST_SERVICE(null) { // must be last entry
@Override
boolean isActivated(Context arg0) {
return false;
}
};
private SynchronizationService service;
private ServiceWrapper(SynchronizationService service) {
this.service = service;
}
abstract boolean isActivated(Context context);
}
/** Service map */ // Internal state for the synchronization process
private static Map<Integer, SynchronizationService> services =
new HashMap<Integer, SynchronizationService>(); /** Current step in the sync process */
static { private static int currentStep;
services.put(SYNC_ID_RTM, new RTMSyncService(SYNC_ID_RTM));
/** # of services synchronized */
private static int servicesSynced;
/** On finished callback */
private static SynchronizerListener callback;
/** Called to do the next step of synchronization. Run me on the UI thread! */
static void continueSynchronization(Activity activity) {
ServiceWrapper serviceWrapper =
ServiceWrapper.values()[currentStep];
currentStep++;
switch(serviceWrapper) {
case _FIRST_SERVICE:
continueSynchronization(activity);
break;
case RTM:
if(Preferences.shouldSyncRTM(activity)) {
servicesSynced++;
serviceWrapper.service.synchronizeService(activity);
} else {
continueSynchronization(activity);
}
break;
case _LAST_SERVICE:
finishSynchronization(activity);
}
}
/** Called at the end of sync. */
private static void finishSynchronization(final Activity activity) {
closeControllers();
Preferences.setSyncLastSync(activity, new Date());
if(callback != null)
callback.onSynchronizerFinished(servicesSynced);
} }
// --- package helpers
static SyncDataController getSyncController(Activity activity) { static SyncDataController getSyncController(Activity activity) {
if(syncController == null) { if(syncController == null) {
syncController = new SyncDataController(activity); syncController = new SyncDataController(activity);
@ -67,12 +145,21 @@ public class Synchronizer {
return tagController; return tagController;
} }
static AlertController getAlertController(Activity activity) {
if(alertController == null) {
alertController = new AlertController(activity);
alertController.open();
}
return alertController;
}
// --- controller stuff // --- controller stuff
private static SyncDataController syncController = null; private static SyncDataController syncController = null;
private static TaskController taskController = null; private static TaskController taskController = null;
private static TagController tagController = null; private static TagController tagController = null;
private static AlertController alertController = null;
static void closeControllers() { private static void closeControllers() {
if(syncController != null) { if(syncController != null) {
syncController.close(); syncController.close();
syncController = null; syncController = null;
@ -87,5 +174,10 @@ public class Synchronizer {
tagController.close(); tagController.close();
tagController = null; tagController = null;
} }
if(alertController != null) {
alertController.close();
alertController = null;
}
} }
} }

@ -18,6 +18,7 @@ public class Preferences {
private static final String P_SHOW_REPEAT_HELP = "repeathelp"; private static final String P_SHOW_REPEAT_HELP = "repeathelp";
private static final String P_SYNC_RTM_TOKEN = "rtmtoken"; private static final String P_SYNC_RTM_TOKEN = "rtmtoken";
private static final String P_SYNC_RTM_LAST_SYNC = "rtmlastsync"; private static final String P_SYNC_RTM_LAST_SYNC = "rtmlastsync";
private static final String P_SYNC_LAST_SYNC = "lastsync";
// default values // default values
private static final boolean DEFAULT_PERSISTENCE_MODE = true; private static final boolean DEFAULT_PERSISTENCE_MODE = true;
@ -162,6 +163,31 @@ public class Preferences {
R.string.p_sync_rtm), false); R.string.p_sync_rtm), false);
} }
/** returns the font size user wants on the front page */
public static Integer autoSyncFrequency(Context context) {
return getIntegerValue(context, R.string.p_sync_every);
}
/** Last Auto-Sync Date, or null */
public static Date getSyncLastSync(Context context) {
Long value = getPrefs(context).getLong(P_SYNC_LAST_SYNC, 0);
if(value == 0)
return null;
return new Date(value);
}
/** Set Last Auto-Sync Date */
public static void setSyncLastSync(Context context, Date date) {
if(date == null) {
clearPref(context, P_SYNC_LAST_SYNC);
return;
}
Editor editor = getPrefs(context).edit();
editor.putLong(P_SYNC_LAST_SYNC, date.getTime());
editor.commit();
}
// --- helper methods // --- helper methods
@SuppressWarnings("unused") @SuppressWarnings("unused")

Loading…
Cancel
Save