From b6ec30285b9f0fcf56ed696be8eb8c2caedea581 Mon Sep 17 00:00:00 2001 From: Tim Su Date: Mon, 26 Jul 2010 22:37:03 -0700 Subject: [PATCH] Initial work on backups plugin --- astrid/.classpath | 2 +- astrid/AndroidManifest.xml | 12 + .../todoroo/astrid/backup/BackupActivity.java | 19 ++ .../astrid/backup/BackupConstants.java | 277 ++++++++++++++++++ .../todoroo/astrid/backup/BackupService.java | 4 +- .../astrid/gcal/GCalTaskCompleteListener.java | 3 +- astrid/res/drawable/ic_menu_archive.png | Bin 0 -> 1475 bytes astrid/res/layout/backup_activity.xml | 41 +++ astrid/res/values/strings-backup.xml | 36 ++- .../res/values/{styles-3.0.xml => styles.xml} | 0 .../com/todoroo/astrid/dao/MetadataDao.java | 3 +- .../src/com/todoroo/astrid/dao/TaskDao.java | 9 +- 12 files changed, 386 insertions(+), 20 deletions(-) create mode 100644 astrid/plugin-src/com/todoroo/astrid/backup/BackupActivity.java create mode 100644 astrid/plugin-src/com/todoroo/astrid/backup/BackupConstants.java create mode 100644 astrid/res/drawable/ic_menu_archive.png create mode 100644 astrid/res/layout/backup_activity.xml rename astrid/res/values/{styles-3.0.xml => styles.xml} (100%) diff --git a/astrid/.classpath b/astrid/.classpath index 900abe288..0377f788f 100644 --- a/astrid/.classpath +++ b/astrid/.classpath @@ -4,7 +4,7 @@ - + diff --git a/astrid/AndroidManifest.xml b/astrid/AndroidManifest.xml index b88ec4cf6..bdb8866f7 100644 --- a/astrid/AndroidManifest.xml +++ b/astrid/AndroidManifest.xml @@ -189,6 +189,18 @@ + + + + + + + + + diff --git a/astrid/plugin-src/com/todoroo/astrid/backup/BackupActivity.java b/astrid/plugin-src/com/todoroo/astrid/backup/BackupActivity.java new file mode 100644 index 000000000..b2d2fea0b --- /dev/null +++ b/astrid/plugin-src/com/todoroo/astrid/backup/BackupActivity.java @@ -0,0 +1,19 @@ +package com.todoroo.astrid.backup; + +import android.app.Activity; +import android.os.Bundle; + +import com.timsu.astrid.R; + +public class BackupActivity extends Activity { + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(R.layout.backup_activity); + setTitle(R.string.backup_BAc_title); + + + } + +} diff --git a/astrid/plugin-src/com/todoroo/astrid/backup/BackupConstants.java b/astrid/plugin-src/com/todoroo/astrid/backup/BackupConstants.java new file mode 100644 index 000000000..bed168a57 --- /dev/null +++ b/astrid/plugin-src/com/todoroo/astrid/backup/BackupConstants.java @@ -0,0 +1,277 @@ +package com.todoroo.astrid.backup; + +import java.io.File; +import java.io.FileOutputStream; +import java.io.IOException; +import java.util.Date; +import java.util.HashMap; +import java.util.HashSet; +import java.util.LinkedList; +import java.util.List; + +import org.xmlpull.v1.XmlSerializer; + +import android.content.Context; +import android.database.Cursor; +import android.os.Environment; +import android.util.Log; +import android.util.Xml; +import android.widget.Toast; + +import com.timsu.astrid.R; +import com.timsu.astrid.data.TaskController; +import com.timsu.astrid.data.TaskIdentifier; +import com.timsu.astrid.data.TaskModelForXml; +import com.timsu.astrid.data.alerts.AlertController; +import com.timsu.astrid.data.sync.SyncDataController; +import com.timsu.astrid.data.sync.SyncMapping; +import com.timsu.astrid.data.tag.TagController; +import com.timsu.astrid.data.tag.TagIdentifier; +import com.timsu.astrid.data.tag.TagModelForView; + +public class BackupConstants { + + private TaskController taskController; + private TagController tagController; + private AlertController alertController; + private SyncDataController syncDataController; + private Context ctx; + private String output; + private final boolean isService; + private int exportCount; + private XmlSerializer xml; + private HashMap tagMap; + + public static final String ASTRID_TAG = "astrid"; + public static final String ASTRID_ATTR_VERSION = "version"; + public static final String TASK_TAG = "task"; + public static final String TAG_TAG = "tag"; + public static final String TAG_ATTR_NAME = "name"; + public static final String ALERT_TAG = "alert"; + public static final String ALERT_ATTR_DATE = "date"; + public static final String SYNC_TAG = "sync"; + public static final String SYNC_ATTR_SERVICE = "service"; + public static final String SYNC_ATTR_REMOTE_ID = "remote_id"; + public static final String XML_ENCODING = "utf-8"; + public static final String ASTRID_DIR = "/astrid"; + private static final String EXPORT_FILE_NAME = "user.%s.xml"; + private static final String BACKUP_FILE_NAME = "auto.%s.xml"; + public static final int FILENAME_DATE_BEGIN_INDEX = 5; + public static final int FILENAME_DATE_END_INDEX = 11; + + public BackupConstants(boolean isService) { + this.isService = isService; + this.exportCount = 0; + } + + private void initTagMap() { + tagMap = tagController.getAllTagsAsMap(); + } + + private void serializeTags(TaskIdentifier task) + throws IOException { + LinkedList tags = tagController.getTaskTags(task); + for (TagIdentifier tag : tags) { + if(!tagMap.containsKey(tag) || tagMap.get(tag) == null) + continue; + xml.startTag(null, TAG_TAG); + xml.attribute(null, TAG_ATTR_NAME, tagMap.get(tag).toString()); + xml.endTag(null, TAG_TAG); + } + } + + private void serializeSyncMappings(TaskIdentifier task) + throws IOException { + HashSet syncMappings = syncDataController.getSyncMappings(task); + for (SyncMapping sync : syncMappings) { + xml.startTag(null, SYNC_TAG); + xml.attribute(null, SYNC_ATTR_SERVICE, + Integer.toString(sync.getSyncServiceId())); + xml.attribute(null, SYNC_ATTR_REMOTE_ID, sync.getRemoteId()); + xml.endTag(null, SYNC_TAG); + } + } + + private void serializeAlerts(TaskIdentifier task) + throws IOException { + List alerts = alertController.getTaskAlerts(task); + for (Date alert : alerts) { + xml.startTag(null, ALERT_TAG); + xml.attribute(null, ALERT_ATTR_DATE, BackupDateUtilities.getIso8601String(alert)); + xml.endTag(null, ALERT_TAG); + } + } + + private void serializeTasks() + throws IOException { + Cursor c = taskController.getBackupTaskListCursor(); + if (! c.moveToFirst()) { + return; // No tasks. + } + do { + TaskModelForXml task = new TaskModelForXml(c); + TaskIdentifier taskId = task.getTaskIdentifier(); + xml.startTag(null, TASK_TAG); + HashMap taskAttributes = task.getTaskAttributes(); + for (String key : taskAttributes.keySet()) { + String value = taskAttributes.get(key); + xml.attribute(null, key, value); + } + serializeTags(taskId); + serializeAlerts(taskId); + serializeSyncMappings(taskId); + xml.endTag(null, TASK_TAG); + this.exportCount++; + } while (c.moveToNext()); + c.close(); + } + + private void doTasksExport() throws IOException { + File xmlFile = new File(this.output); + xmlFile.createNewFile(); + FileOutputStream fos = new FileOutputStream(xmlFile); + xml = Xml.newSerializer(); + xml.setOutput(fos, XML_ENCODING); + + xml.startDocument(null, null); + xml.setFeature("http://xmlpull.org/v1/doc/features.html#indent-output", true); + + xml.startTag(null, ASTRID_TAG); + xml.attribute(null, ASTRID_ATTR_VERSION, + Integer.toString(Preferences.getCurrentVersion(ctx))); + + openControllers(); + initTagMap(); + serializeTasks(); + closeControllers(); + + xml.endTag(null, ASTRID_TAG); + xml.endDocument(); + xml.flush(); + fos.close(); + + if (!isService) { + displayToast(); + } + } + + private void displayToast() { + CharSequence text = String.format(ctx.getString(R.string.export_toast), + ctx.getResources().getQuantityString(R.plurals.Ntasks, exportCount, + exportCount), output); + Toast.makeText(ctx, text, Toast.LENGTH_LONG).show(); + } + + private void displayErrorToast(String error) { + Toast.makeText(ctx, error, Toast.LENGTH_LONG).show(); + } + + private void closeControllers() { + tagController.close(); + taskController.close(); + alertController.close(); + syncDataController.close(); + } + + private void openControllers() { + taskController.open(); + tagController.open(); + alertController.open(); + syncDataController.open(); + } + + public void exportTasks(File directory) { + if (isService && !Preferences.isBackupEnabled(ctx)) { + // Automatic backups are disabled. + return; + } + if (setupFile(directory)) { + Thread thread = new Thread(doBackgroundExport); + thread.start(); + } + } + + public static File getExportDirectory() { + String storageState = Environment.getExternalStorageState(); + if (storageState.equals(Environment.MEDIA_MOUNTED)) { + String path = Environment.getExternalStorageDirectory().getAbsolutePath(); + path = path + ASTRID_DIR; + return new File(path); + } + return null; + } + + private boolean setupFile(File directory) { + File astridDir = directory; + if (astridDir != null) { + // Check for /sdcard/astrid directory. If it doesn't exist, make it. + if (astridDir.exists() || astridDir.mkdir()) { + String fileName; + if (isService) { + fileName = BACKUP_FILE_NAME; + } else { + fileName = EXPORT_FILE_NAME; + } + fileName = String.format(fileName, BackupDateUtilities.getDateForExport()); + setOutput(astridDir.getAbsolutePath() + "/" + fileName); + return true; + } else { + // Unable to make the /sdcard/astrid directory. + String error = ctx.getString(R.string.error_sdcard, astridDir.getAbsolutePath()); + Log.e("TasksXmlExporter", error); + if (!isService) { + displayErrorToast(error); + } + return false; + } + } else { + // Unable to access the sdcard because it's not in the mounted state. + String error = ctx.getString(R.string.error_sdcard_general); + Log.e("TasksXmlExporter", error); + if (!isService) { + displayErrorToast(error); + } + return false; + } + } + + private void setOutput(String file) { + this.output = file; + } + + private final Runnable doBackgroundExport = new Runnable() { + public void run() { + /*Looper.prepare(); + try { + doTasksExport(); + } catch (IOException e) { + Log.e("TasksXmlExporter", "IOException in doTasksExport " + e.getMessage()); + } + Looper.loop();*/ + } + }; + + public void setTaskController(TaskController taskController) { + this.taskController = taskController; + } + + public void setTagController(TagController tagController) { + this.tagController = tagController; + } + + public void setAlertController(AlertController alertController) { + this.alertController = alertController; + } + + public void setSyncDataController(SyncDataController syncDataController) { + this.syncDataController = syncDataController; + } + + public void setContext(Context ctx) { + this.ctx = ctx; + setTaskController(new TaskController(ctx)); + setTagController(new TagController(ctx)); + setAlertController(new AlertController(ctx)); + setSyncDataController(new SyncDataController(ctx)); + } +} diff --git a/astrid/plugin-src/com/todoroo/astrid/backup/BackupService.java b/astrid/plugin-src/com/todoroo/astrid/backup/BackupService.java index 36d48f794..17a04eacf 100644 --- a/astrid/plugin-src/com/todoroo/astrid/backup/BackupService.java +++ b/astrid/plugin-src/com/todoroo/astrid/backup/BackupService.java @@ -11,6 +11,7 @@ import android.app.Service; import android.content.Context; import android.content.Intent; import android.os.IBinder; +import android.util.Log; /** * Inspired heavily by SynchronizationService @@ -129,7 +130,8 @@ public class BackupService extends Service { } }); for(int i = DAYS_TO_KEEP_BACKUP; i < files.length; i++) { - files[i].delete(); + if(!files[i].delete()) + Log.i("astrid-backups", "Unable to delete: " + files[i]); //$NON-NLS-1$ //$NON-NLS-2$ } } diff --git a/astrid/plugin-src/com/todoroo/astrid/gcal/GCalTaskCompleteListener.java b/astrid/plugin-src/com/todoroo/astrid/gcal/GCalTaskCompleteListener.java index a66d938f5..d7e6843f3 100644 --- a/astrid/plugin-src/com/todoroo/astrid/gcal/GCalTaskCompleteListener.java +++ b/astrid/plugin-src/com/todoroo/astrid/gcal/GCalTaskCompleteListener.java @@ -7,6 +7,7 @@ import android.content.Context; import android.content.Intent; import android.net.Uri; import android.text.TextUtils; +import android.util.Log; import com.timsu.astrid.R; import com.todoroo.astrid.api.AstridApiConstants; @@ -36,7 +37,7 @@ public class GCalTaskCompleteListener extends BroadcastReceiver { task.getValue(Task.TITLE))); cr.update(Uri.parse(calendarUri), values, null, null); } catch (Exception e) { - // do nothing. + Log.d("astrid-gcal", "Error updating calendar entry", e); //$NON-NLS-1$ //$NON-NLS-2$ } } } diff --git a/astrid/res/drawable/ic_menu_archive.png b/astrid/res/drawable/ic_menu_archive.png new file mode 100644 index 0000000000000000000000000000000000000000..6c1250b455fbb8fce78bb3e87f16e7bdc4262949 GIT binary patch literal 1475 zcmV;!1w8tRP)Px#24YJ`L;(K){{a7>y{D4^000SaNLh0L01FcU01FcV0GgZ_00007bV*G`2igZ4 z1rP)QEsewg00l!yL_t(&-tAeQbsNiwz*Lh7^;k=T!oD1cBR8bB`mGyv29*pJd#^kwk=YA@e%e7jqCR%ISBj#mmYl|9<2BxQ{ z-(Fl?{1!lqi0psKqom$5J>L}obLrBh531E_tyZfE5s^}APa}+vk0YPYqtR%52H-mp zaYe)r!z;1g@jOZ$LNSjR*L9Cqt5p<>MN&!)v<5T77$ctN6#&$Us0pBxLU{&Y0R#Yi z5wZW)0{}p->!MgJ!gXCF1)#O2AP8{g%$e_lAh66VX+p+voX6+SpMR%buQv*Xf(O7R z38j7jpp=3!23l(j2%nwj0pRfA!wl5kV;hr4+Q*Fvg@=RIAm*b=~7<&z^lZqV6PzqS-4fBIV+iQc985`X8

}RH;;c zKX~xqmj@0UxZmr+Xs{-SmY0`b_kG_oGY3K7A3JvJ{`~y>6A|fzY&94FGlQ9* zK4c6TW9~03EPQhH>ebm>w{CqtIXPKz9B1DF_`VO{_tEWk0RZiG8+Y#9c``pg|0)sb zL;xb?BLLP~IF9p`<2dVg@812eUawcQ*85%-L?mF0*~!6DsU*c>aq9Z@>#xks%`GQN z;wUb|9FTUqO(##D{272%O1<36RQnAgGEO`a=H})a{aABP4}>ou-}iCi#EF$=vsoh| zIF2L6nE$=hBqCUADF}it08jcP-M|nEDXs~?-n@D9NNAx%CB}X=SjLz~VU5J80WdR^ zQY!%H;lqbV;_l6&`wHu<$FX8BIgLgDa=F|J0NUK#tVb`+%KG8{k77k%G7ng5K|~0G z03#zKzX4F5=N;BsM`4vo%NXweV7JnI;6cXB7#|=16##WQofk``QWRF1d7x6Mti*bT zWICjCI@N9*V_R=KlE21<--B5-y dl%YI-@)uiZn~YCK5;y<=002ovPDHLkV1nNtrK + + + + + +