Initial work on backups plugin

pull/14/head
Tim Su 16 years ago
parent 1bd704b5d4
commit b6ec30285b

@ -4,7 +4,7 @@
<classpathentry kind="src" path="src-legacy"/>
<classpathentry kind="src" path="api-src"/>
<classpathentry kind="src" path="common-src"/>
<classpathentry excluding="com/todoroo/astrid/rmilk/EditOperationExposer.java|com/todoroo/astrid/rmilk/MilkEditActivity.java|com/todoroo/astrid/backup/TasksXmlExporter.java|com/todoroo/astrid/backup/TasksXmlImporter.java" kind="src" path="plugin-src"/>
<classpathentry excluding="com/todoroo/astrid/rmilk/EditOperationExposer.java|com/todoroo/astrid/rmilk/MilkEditActivity.java" kind="src" path="plugin-src"/>
<classpathentry kind="src" path="gen"/>
<classpathentry kind="con" path="com.android.ide.eclipse.adt.ANDROID_FRAMEWORK"/>
<classpathentry exported="true" kind="lib" path="lib/commons-codec-1.3.jar"/>

@ -189,6 +189,18 @@
<!-- backup -->
<service android:name="com.todoroo.astrid.backup.BackupService"/>
<activity android:name="com.todoroo.astrid.backup.BackupActivity"
android:theme="@android:style/Theme.Dialog"
android:icon="@drawable/ic_menu_archive"
android:label="@string/backup_BAc_label">
<intent-filter>
<action android:name="com.todoroo.astrid.TASK_LIST_MENU" />
<category android:name="android.intent.category.DEFAULT" />
</intent-filter>
<intent-filter>
<action android:name="android.intent.action.MAIN" />
</intent-filter>
</activity>
<!-- notes -->

@ -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);
}
}

@ -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<TagIdentifier, TagModelForView> 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<TagIdentifier> 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<SyncMapping> 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<Date> 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<String, String> 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));
}
}

@ -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$
}
}

@ -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$
}
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

@ -0,0 +1,41 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
**
** Copyright 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.
*/
-->
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:minWidth="300dip"
android:layout_width="fill_parent"
android:layout_height="wrap_content">
<View
android:layout_width="fill_parent"
android:layout_height="1dip"
android:padding="5dip"
android:background="@android:drawable/divider_horizontal_dark" />
<Button android:id="@+id/importButton"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:text="@string/backup_BAc_import" />
<Button android:id="@+id/exportButton"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:text="@string/backup_BAc_export" />
</LinearLayout>

@ -2,21 +2,37 @@
<!-- See the file "LICENSE" for the full license governing this code. -->
<resources xmlns:android="http://schemas.android.com/apk/res/android">
<!-- Resources for built-in backup plug-in -->
<!-- Resources for built-in backup plug-in -->
<!-- ============================================== BackupPreferences == -->
<string name="BPr_backup_title">Automatic Backups</string>
<string name="backup_BPr_backup_title">Automatic Backups</string>
<!-- backup summary when there is no backup message -->
<string name="BPr_backup_desc">Perform daily backups to sdcard.</string>
<!-- backup summary when there is no backup message -->
<string name="backup_BPr_backup_desc">Perform daily backups to sdcard.</string>
<!-- backup failure message (%s -> error message) -->
<string name="BPr_backup_desc_failure">Last backup failed: %s</string>
<!-- backup failure message (%s -> error message) -->
<string name="backup_BPr_backup_desc_failure">Last backup failed: %s</string>
<!-- backup failure error when error message is null -->
<string name="BPr_backup_desc_failure_null">Last backup failed, could not read SD card</string>
<!-- backup failure error when error message is null -->
<string name="backup_BPr_backup_desc_failure_null">Last backup failed, could not read SD card</string>
<!-- backup success message (%s -> date) -->
<string name="BPr_backup_desc_success">Latest backup was on %s</string>
<!-- backup success message (%s -> date) -->
<string name="backup_BPr_backup_desc_success">Latest backup was on %s</string>
<!-- ================================================= BackupActivity == -->
<!-- backup activity label -->
<string name="backup_BAc_label">Backups</string>
<!-- backup activity title -->
<string name="backup_BAc_title">Manage Your Backups</string>
<!-- backup activity import button -->
<string name="backup_BAc_import">Import Tasks</string>
<!-- backup activity export button -->
<string name="backup_BAc_export">Export Tasks</string>
<string name="export_toast">Backed Up %s to %s.</string>
<string name="import_summary_title">Restore Summary</string>

@ -27,8 +27,9 @@ import com.todoroo.astrid.model.Task;
public class MetadataDao extends GenericDao<Metadata> {
@Autowired
Database database;
private Database database;
@edu.umd.cs.findbugs.annotations.SuppressWarnings(value="UR_UNINIT_READ")
public MetadataDao() {
super(Metadata.class);
DependencyInjectionService.getInstance().inject(this);

@ -14,7 +14,6 @@ import com.todoroo.andlib.data.GenericDao;
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.Functions;
import com.todoroo.andlib.utility.DateUtilities;
@ -33,14 +32,12 @@ import com.todoroo.astrid.utility.Preferences;
public class TaskDao extends GenericDao<Task> {
@Autowired
MetadataDao metadataDao;
private MetadataDao metadataDao;
@Autowired
Database database;
@Autowired
ExceptionService exceptionService;
private Database database;
@edu.umd.cs.findbugs.annotations.SuppressWarnings(value="UR_UNINIT_READ")
public TaskDao() {
super(Task.class);
DependencyInjectionService.getInstance().inject(this);

Loading…
Cancel
Save