mirror of https://github.com/tasks/tasks
Merge with Corey Downing's backup branch
------------------------------------------------------------ Use --include-merges or -n0 to see merged revisions.pull/14/head
parent
1cfab5670e
commit
74ab94f6fb
@ -1,10 +1,13 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
<classpath>
|
<classpath>
|
||||||
<classpathentry excluding="javax/xml/validation/|javax/xml/transform/dom/|javax/xml/parsers/|org/jaxp/transform/dom/|org/jaxp/transform/sax/|org/jaxp/transform/stax/|org/jaxp/transform/stream/|org/jaxp/stream/events/|org/jaxp/stream/util/|org/jaxp/parsers/|org/jaxp/stream/|org/jaxp/validation/" kind="src" path="src"/>
|
<classpathentry excluding="javax/xml/validation/|javax/xml/transform/dom/|javax/xml/parsers/|org/jaxp/transform/dom/|org/jaxp/transform/sax/|org/jaxp/transform/stax/|org/jaxp/transform/stream/|org/jaxp/stream/events/|org/jaxp/stream/util/|org/jaxp/parsers/|org/jaxp/stream/|org/jaxp/validation/" kind="src" path="src"/>
|
||||||
<classpathentry kind="lib" path="lib/commons-codec-1.3.jar"/>
|
|
||||||
<classpathentry kind="con" path="com.android.ide.eclipse.adt.ANDROID_FRAMEWORK"/>
|
|
||||||
<classpathentry kind="src" path="gen"/>
|
<classpathentry kind="src" path="gen"/>
|
||||||
|
<classpathentry kind="lib" path="lib/commons-codec-1.3.jar"/>
|
||||||
<classpathentry kind="lib" path="lib/locale_platform.jar"/>
|
<classpathentry kind="lib" path="lib/locale_platform.jar"/>
|
||||||
<classpathentry kind="lib" path="lib/FlurryAgent.jar"/>
|
<classpathentry kind="lib" path="lib/FlurryAgent.jar"/>
|
||||||
<classpathentry kind="output" path="ecbuild"/>
|
<classpathentry kind="con" path="org.eclipse.jdt.USER_LIBRARY/Android 1.6"/>
|
||||||
|
<classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER"/>
|
||||||
|
<classpathentry kind="con" path="org.eclipse.jdt.USER_LIBRARY/Python 2.6.4 interpreter library"/>
|
||||||
|
<classpathentry kind="con" path="org.eclipse.jdt.USER_LIBRARY/Android 1.6"/>
|
||||||
|
<classpathentry kind="output" path="out/production/astrid-2.x"/>
|
||||||
</classpath>
|
</classpath>
|
||||||
|
|||||||
@ -0,0 +1,192 @@
|
|||||||
|
package com.timsu.astrid.data.task;
|
||||||
|
|
||||||
|
import android.database.Cursor;
|
||||||
|
import com.timsu.astrid.data.AbstractController;
|
||||||
|
import com.timsu.astrid.data.enums.Importance;
|
||||||
|
import com.timsu.astrid.data.enums.RepeatInterval;
|
||||||
|
import com.timsu.astrid.utilities.DateUtilities;
|
||||||
|
|
||||||
|
import java.util.Date;
|
||||||
|
import java.util.HashMap;
|
||||||
|
|
||||||
|
public class TaskModelForXml extends AbstractTaskModel {
|
||||||
|
|
||||||
|
static String[] FIELD_LIST = new String[] {
|
||||||
|
AbstractController.KEY_ROWID,
|
||||||
|
NAME,
|
||||||
|
IMPORTANCE,
|
||||||
|
ELAPSED_SECONDS,
|
||||||
|
ESTIMATED_SECONDS,
|
||||||
|
TIMER_START,
|
||||||
|
DEFINITE_DUE_DATE,
|
||||||
|
PREFERRED_DUE_DATE,
|
||||||
|
NOTIFICATIONS,
|
||||||
|
PROGRESS_PERCENTAGE,
|
||||||
|
COMPLETION_DATE,
|
||||||
|
CREATION_DATE,
|
||||||
|
HIDDEN_UNTIL,
|
||||||
|
NOTES,
|
||||||
|
REPEAT,
|
||||||
|
FLAGS,
|
||||||
|
POSTPONE_COUNT,
|
||||||
|
BLOCKING_ON,
|
||||||
|
LAST_NOTIFIED,
|
||||||
|
NOTIFICATION_FLAGS,
|
||||||
|
CALENDAR_URI,
|
||||||
|
};
|
||||||
|
private HashMap<String, String> taskAttributesMap;
|
||||||
|
public static final String REPEAT_VALUE = "repeat_value";
|
||||||
|
public static final String REPEAT_INTERVAL = "repeat_interval";
|
||||||
|
|
||||||
|
|
||||||
|
private RepeatInterval repeatInterval = null;
|
||||||
|
private Integer repeatValue = null;
|
||||||
|
|
||||||
|
// --- constructors
|
||||||
|
|
||||||
|
public TaskModelForXml() {
|
||||||
|
super();
|
||||||
|
setCreationDate(new Date());
|
||||||
|
taskAttributesMap = new HashMap<String, String>(FIELD_LIST.length);
|
||||||
|
}
|
||||||
|
|
||||||
|
public TaskModelForXml(Cursor cursor) {
|
||||||
|
super(cursor);
|
||||||
|
prefetchData(FIELD_LIST);
|
||||||
|
taskAttributesMap = new HashMap<String, String>(FIELD_LIST.length);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Safely add a value from a date field (in case of null values) to the
|
||||||
|
taskAttributesMap.
|
||||||
|
*/
|
||||||
|
private void safePutDate(String field, Date value) {
|
||||||
|
if (value != null) {
|
||||||
|
taskAttributesMap.put(field, DateUtilities.getIso8601String(value));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- getters and setters
|
||||||
|
|
||||||
|
public Date getCreationDate() {
|
||||||
|
return super.getCreationDate();
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Build a HashMap of task fields and associated values.
|
||||||
|
*/
|
||||||
|
public HashMap<String, String> getTaskAttributes() {
|
||||||
|
taskAttributesMap.put(AbstractController.KEY_ROWID, getTaskIdentifier().idAsString());
|
||||||
|
taskAttributesMap.put(NAME, getName());
|
||||||
|
taskAttributesMap.put(IMPORTANCE, getImportance().toString());
|
||||||
|
taskAttributesMap.put(ELAPSED_SECONDS, getElapsedSeconds().toString());
|
||||||
|
taskAttributesMap.put(ESTIMATED_SECONDS, getEstimatedSeconds().toString());
|
||||||
|
safePutDate(TIMER_START, getTimerStart());
|
||||||
|
safePutDate(DEFINITE_DUE_DATE, getDefiniteDueDate());
|
||||||
|
safePutDate(PREFERRED_DUE_DATE, getPreferredDueDate());
|
||||||
|
taskAttributesMap.put(NOTIFICATIONS, getNotificationIntervalSeconds().toString());
|
||||||
|
taskAttributesMap.put(PROGRESS_PERCENTAGE, Integer.toString(getProgressPercentage()));
|
||||||
|
safePutDate(COMPLETION_DATE, getCompletionDate());
|
||||||
|
safePutDate(CREATION_DATE, getCreationDate());
|
||||||
|
safePutDate(HIDDEN_UNTIL, getHiddenUntil());
|
||||||
|
taskAttributesMap.put(NOTES, getNotes());
|
||||||
|
RepeatInfo repeat = getRepeat();
|
||||||
|
if (repeat != null) {
|
||||||
|
taskAttributesMap.put(REPEAT_VALUE, Integer.toString(repeat.getValue()));
|
||||||
|
taskAttributesMap.put(REPEAT_INTERVAL,
|
||||||
|
Integer.toString(repeat.getInterval().getLabelResource()));
|
||||||
|
}
|
||||||
|
taskAttributesMap.put(FLAGS, Integer.toString(getFlags()));
|
||||||
|
taskAttributesMap.put(POSTPONE_COUNT, getPostponeCount().toString());
|
||||||
|
taskAttributesMap.put(BLOCKING_ON, Long.toString(getBlockingOn().getId()));
|
||||||
|
safePutDate(LAST_NOTIFIED, getLastNotificationDate());
|
||||||
|
taskAttributesMap.put(NOTIFICATION_FLAGS, Integer.toString(getNotificationFlags()));
|
||||||
|
String calendarUri = getCalendarUri();
|
||||||
|
if (calendarUri != null) {
|
||||||
|
taskAttributesMap.put(CALENDAR_URI, calendarUri);
|
||||||
|
}
|
||||||
|
return taskAttributesMap;
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- setters
|
||||||
|
|
||||||
|
public boolean setField(String field, String value) {
|
||||||
|
boolean success = true;
|
||||||
|
if(field.equals(NAME)) {
|
||||||
|
setName(value);
|
||||||
|
}
|
||||||
|
else if(field.equals(NOTES)) {
|
||||||
|
setNotes(value);
|
||||||
|
}
|
||||||
|
else if(field.equals(PROGRESS_PERCENTAGE)) {
|
||||||
|
setProgressPercentage(Integer.parseInt(value));
|
||||||
|
}
|
||||||
|
else if(field.equals(IMPORTANCE)) {
|
||||||
|
setImportance(Importance.valueOf(value));
|
||||||
|
}
|
||||||
|
else if(field.equals(ESTIMATED_SECONDS)) {
|
||||||
|
setEstimatedSeconds(Integer.parseInt(value));
|
||||||
|
}
|
||||||
|
else if(field.equals(ELAPSED_SECONDS)) {
|
||||||
|
setElapsedSeconds(Integer.parseInt(value));
|
||||||
|
}
|
||||||
|
else if(field.equals(TIMER_START)) {
|
||||||
|
setTimerStart(DateUtilities.getDateFromIso8601String(value));
|
||||||
|
}
|
||||||
|
else if(field.equals(DEFINITE_DUE_DATE)) {
|
||||||
|
setDefiniteDueDate(DateUtilities.getDateFromIso8601String(value));
|
||||||
|
}
|
||||||
|
else if(field.equals(PREFERRED_DUE_DATE)) {
|
||||||
|
setPreferredDueDate(DateUtilities.getDateFromIso8601String(value));
|
||||||
|
}
|
||||||
|
else if(field.equals(HIDDEN_UNTIL)) {
|
||||||
|
setHiddenUntil(DateUtilities.getDateFromIso8601String(value));
|
||||||
|
}
|
||||||
|
else if(field.equals(BLOCKING_ON)) {
|
||||||
|
setBlockingOn(new TaskIdentifier(Long.parseLong(value)));
|
||||||
|
}
|
||||||
|
else if(field.equals(POSTPONE_COUNT)) {
|
||||||
|
setPostponeCount(Integer.parseInt(value));
|
||||||
|
}
|
||||||
|
else if(field.equals(NOTIFICATIONS)) {
|
||||||
|
setNotificationIntervalSeconds(Integer.parseInt(value));
|
||||||
|
}
|
||||||
|
else if(field.equals(CREATION_DATE)) {
|
||||||
|
setCreationDate(DateUtilities.getDateFromIso8601String(value));
|
||||||
|
}
|
||||||
|
else if(field.equals(COMPLETION_DATE)) {
|
||||||
|
setCompletionDate(DateUtilities.getDateFromIso8601String(value));
|
||||||
|
}
|
||||||
|
else if(field.equals(NOTIFICATION_FLAGS)) {
|
||||||
|
setNotificationFlags(Integer.parseInt(value));
|
||||||
|
}
|
||||||
|
else if(field.equals(LAST_NOTIFIED)) {
|
||||||
|
setLastNotificationTime(DateUtilities.getDateFromIso8601String(value));
|
||||||
|
}
|
||||||
|
else if(field.equals(REPEAT_INTERVAL)) {
|
||||||
|
setRepeatInterval(RepeatInterval.values()[Integer.parseInt(value)]);
|
||||||
|
}
|
||||||
|
else if(field.equals(REPEAT_VALUE)) {
|
||||||
|
setRepeatValue(Integer.parseInt(value));
|
||||||
|
}
|
||||||
|
else if(field.equals(FLAGS)) {
|
||||||
|
setFlags(Integer.parseInt(value));
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
success = false;
|
||||||
|
}
|
||||||
|
return success;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setRepeatInterval(RepeatInterval repeatInterval) {
|
||||||
|
this.repeatInterval = repeatInterval;
|
||||||
|
if (repeatValue != null) {
|
||||||
|
setRepeat(new RepeatInfo(repeatInterval, repeatValue));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setRepeatValue(Integer repeatValue) {
|
||||||
|
this.repeatValue = repeatValue;
|
||||||
|
if (repeatInterval != null) {
|
||||||
|
setRepeat(new RepeatInfo(repeatInterval, repeatValue));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,91 @@
|
|||||||
|
package com.timsu.astrid.utilities;
|
||||||
|
|
||||||
|
import android.app.AlarmManager;
|
||||||
|
import android.app.PendingIntent;
|
||||||
|
import android.app.Service;
|
||||||
|
import android.content.Context;
|
||||||
|
import android.content.Intent;
|
||||||
|
import android.os.IBinder;
|
||||||
|
|
||||||
|
import java.io.File;
|
||||||
|
import java.io.FilenameFilter;
|
||||||
|
|
||||||
|
public class BackupService extends Service {
|
||||||
|
/* Inspired heavily by SynchronizationService
|
||||||
|
|
||||||
|
*/
|
||||||
|
|
||||||
|
private static final long BACKUP_OFFSET = 5*60*1000L;
|
||||||
|
private static final String BACKUP_ACTION = "backup";
|
||||||
|
private static final String BACKUP_FILE_NAME_REGEX = "auto\\.\\d{6}\\-\\d{4}\\.xml";
|
||||||
|
private static final int DAYS_TO_KEEP_BACKUP = 7;
|
||||||
|
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public IBinder onBind(Intent intent) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onStart(Intent intent, int startId) {
|
||||||
|
if (intent.getAction().equals(BACKUP_ACTION)) {
|
||||||
|
startBackup(this);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void startBackup(Context ctx) {
|
||||||
|
if (ctx == null || ctx.getResources() == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (!Preferences.isBackupEnabled(ctx)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
deleteOldBackups();
|
||||||
|
TasksXmlExporter exporter = new TasksXmlExporter(true);
|
||||||
|
exporter.setContext(ctx);
|
||||||
|
exporter.exportTasks();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void scheduleService(Context ctx) {
|
||||||
|
AlarmManager am = (AlarmManager) ctx.getSystemService(Context.ALARM_SERVICE);
|
||||||
|
PendingIntent pendingIntent = PendingIntent.getService(ctx, 0,
|
||||||
|
createAlarmIntent(ctx), PendingIntent.FLAG_UPDATE_CURRENT);
|
||||||
|
am.cancel(pendingIntent);
|
||||||
|
if (!Preferences.isBackupEnabled(ctx)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
am.setInexactRepeating(AlarmManager.RTC, System.currentTimeMillis() + BACKUP_OFFSET,
|
||||||
|
AlarmManager.INTERVAL_DAY, pendingIntent);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void unscheduleService(Context ctx) {
|
||||||
|
AlarmManager am = (AlarmManager) ctx.getSystemService(Context.ALARM_SERVICE);
|
||||||
|
PendingIntent pendingIntent = PendingIntent.getService(ctx, 0,
|
||||||
|
createAlarmIntent(ctx), PendingIntent.FLAG_UPDATE_CURRENT);
|
||||||
|
am.cancel(pendingIntent);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static Intent createAlarmIntent(Context ctx) {
|
||||||
|
Intent intent = new Intent(ctx, BackupService.class);
|
||||||
|
intent.setAction(BACKUP_ACTION);
|
||||||
|
return intent;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void deleteOldBackups() {
|
||||||
|
FilenameFilter filter = new FilenameFilter() {
|
||||||
|
@Override
|
||||||
|
public boolean accept(File file, String s) {
|
||||||
|
if (s.matches(BACKUP_FILE_NAME_REGEX)) {
|
||||||
|
String dateString = s.substring(12, 18);
|
||||||
|
return DateUtilities.wasCreatedBefore(dateString, DAYS_TO_KEEP_BACKUP);
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
File astridDir = TasksXmlExporter.getExportDirectory();
|
||||||
|
String[] files = astridDir.list(filter);
|
||||||
|
for (String file : files) {
|
||||||
|
new File(astridDir, file).delete();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,268 @@
|
|||||||
|
package com.timsu.astrid.utilities;
|
||||||
|
|
||||||
|
import android.content.Context;
|
||||||
|
import android.database.Cursor;
|
||||||
|
import android.os.Environment;
|
||||||
|
import android.os.Looper;
|
||||||
|
import android.util.Log;
|
||||||
|
import android.util.Xml;
|
||||||
|
import android.widget.Toast;
|
||||||
|
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.SyncMapping;
|
||||||
|
import com.timsu.astrid.data.tag.TagController;
|
||||||
|
import com.timsu.astrid.data.tag.TagIdentifier;
|
||||||
|
import com.timsu.astrid.data.tag.TagModelForView;
|
||||||
|
import com.timsu.astrid.data.task.TaskController;
|
||||||
|
import com.timsu.astrid.data.task.TaskIdentifier;
|
||||||
|
import com.timsu.astrid.data.task.TaskModelForXml;
|
||||||
|
import org.xmlpull.v1.XmlSerializer;
|
||||||
|
|
||||||
|
import java.io.File;
|
||||||
|
import java.io.FileOutputStream;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.util.*;
|
||||||
|
|
||||||
|
public class TasksXmlExporter {
|
||||||
|
|
||||||
|
private TaskController taskController;
|
||||||
|
private TagController tagController;
|
||||||
|
private AlertController alertController;
|
||||||
|
private SyncDataController syncDataController;
|
||||||
|
private Context ctx;
|
||||||
|
private String output;
|
||||||
|
private 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 TasksXmlExporter(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) {
|
||||||
|
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, DateUtilities.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() {
|
||||||
|
if (isService && !Preferences.isBackupEnabled(ctx)) {
|
||||||
|
// Automatic backups are disabled.
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (setupFile()) {
|
||||||
|
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 astridDir = getExportDirectory();
|
||||||
|
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, DateUtilities.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);
|
||||||
|
Log.e("TasksXmlExporter", error);
|
||||||
|
if (!isService) {
|
||||||
|
displayErrorToast(error);
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void setOutput(String file) {
|
||||||
|
this.output = file;
|
||||||
|
}
|
||||||
|
|
||||||
|
private 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));
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,298 @@
|
|||||||
|
package com.timsu.astrid.utilities;
|
||||||
|
|
||||||
|
import android.app.AlertDialog;
|
||||||
|
import android.app.ProgressDialog;
|
||||||
|
import android.content.Context;
|
||||||
|
import android.content.DialogInterface;
|
||||||
|
import android.os.Handler;
|
||||||
|
import android.os.Looper;
|
||||||
|
import android.util.Log;
|
||||||
|
import com.timsu.astrid.R;
|
||||||
|
import com.timsu.astrid.data.AbstractController;
|
||||||
|
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;
|
||||||
|
import com.timsu.astrid.data.task.TaskController;
|
||||||
|
import com.timsu.astrid.data.task.TaskIdentifier;
|
||||||
|
import com.timsu.astrid.data.task.TaskModelForXml;
|
||||||
|
import org.xmlpull.v1.XmlPullParser;
|
||||||
|
import org.xmlpull.v1.XmlPullParserException;
|
||||||
|
import org.xmlpull.v1.XmlPullParserFactory;
|
||||||
|
|
||||||
|
import java.io.FileNotFoundException;
|
||||||
|
import java.io.FileReader;
|
||||||
|
import java.util.Date;
|
||||||
|
|
||||||
|
public class TasksXmlImporter {
|
||||||
|
public static final String TAG = "TasksXmlImporter";
|
||||||
|
public static final String ASTRID_TAG = TasksXmlExporter.ASTRID_TAG;
|
||||||
|
public static final String ASTRID_ATTR_VERSION = TasksXmlExporter.ASTRID_ATTR_VERSION;
|
||||||
|
public static final String TASK_TAG = TasksXmlExporter.TASK_TAG;
|
||||||
|
public static final String TAG_TAG = TasksXmlExporter.TAG_TAG;
|
||||||
|
public static final String ALERT_TAG = TasksXmlExporter.ALERT_TAG;
|
||||||
|
public static final String SYNC_TAG = TasksXmlExporter.SYNC_TAG;
|
||||||
|
public static final String TASK_ID = AbstractController.KEY_ROWID;
|
||||||
|
public static final String TASK_NAME = TaskModelForXml.NAME;
|
||||||
|
public static final String TASK_CREATION_DATE = TaskModelForXml.CREATION_DATE;
|
||||||
|
public static final String TAG_ATTR_NAME = TasksXmlExporter.TAG_ATTR_NAME;
|
||||||
|
public static final String ALERT_ATTR_DATE = TasksXmlExporter.ALERT_ATTR_DATE;
|
||||||
|
public static final String SYNC_ATTR_SERVICE = TasksXmlExporter.SYNC_ATTR_SERVICE;
|
||||||
|
public static final String SYNC_ATTR_REMOTE_ID = TasksXmlExporter.SYNC_ATTR_REMOTE_ID;
|
||||||
|
|
||||||
|
private TaskController taskController;
|
||||||
|
private TagController tagController;
|
||||||
|
private AlertController alertController;
|
||||||
|
private SyncDataController syncDataController;
|
||||||
|
private XmlPullParser xpp;
|
||||||
|
private String input;
|
||||||
|
private Handler importHandler;
|
||||||
|
private final Context context;
|
||||||
|
private int taskCount;
|
||||||
|
private int importCount;
|
||||||
|
private int skipCount;
|
||||||
|
|
||||||
|
static ProgressDialog progressDialog;
|
||||||
|
|
||||||
|
public TasksXmlImporter(Context context) {
|
||||||
|
this.context = context;
|
||||||
|
setContext(context);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void setProgressMessage(final String message) {
|
||||||
|
importHandler.post(new Runnable() {
|
||||||
|
public void run() {
|
||||||
|
progressDialog.setMessage(message);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public void importTasks(final Runnable runAfterImport) {
|
||||||
|
importHandler = new Handler();
|
||||||
|
importHandler.post(new Runnable() {
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
TasksXmlImporter.progressDialog = new ProgressDialog(context);
|
||||||
|
progressDialog.setIcon(android.R.drawable.ic_dialog_info);
|
||||||
|
progressDialog.setTitle(R.string.import_progress_title);
|
||||||
|
progressDialog.setProgressStyle(ProgressDialog.STYLE_SPINNER);
|
||||||
|
progressDialog.setMessage(context.getString(R.string.import_progress_open));
|
||||||
|
progressDialog.setCancelable(false);
|
||||||
|
progressDialog.setIndeterminate(true);
|
||||||
|
progressDialog.show();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
new Thread(new Runnable() {
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
Looper.prepare();
|
||||||
|
try {
|
||||||
|
performImport();
|
||||||
|
if (runAfterImport != null) {
|
||||||
|
importHandler.post(runAfterImport);
|
||||||
|
}
|
||||||
|
} catch (FileNotFoundException e) {
|
||||||
|
Log.e("TasksXmlImporter", e.getMessage());
|
||||||
|
} catch (XmlPullParserException e) {
|
||||||
|
Log.e("TasksXmlImporter", e.getMessage());
|
||||||
|
}
|
||||||
|
Looper.loop();
|
||||||
|
}
|
||||||
|
}).start();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void performImport() throws FileNotFoundException, XmlPullParserException {
|
||||||
|
taskCount = 0;
|
||||||
|
importCount = 0;
|
||||||
|
skipCount = 0;
|
||||||
|
XmlPullParserFactory factory = XmlPullParserFactory.newInstance();
|
||||||
|
xpp = factory.newPullParser();
|
||||||
|
xpp.setInput(new FileReader(input));
|
||||||
|
setProgressMessage(context.getString(R.string.import_progress_opened));
|
||||||
|
|
||||||
|
openControllers();
|
||||||
|
try {
|
||||||
|
TaskModelForXml currentTask = null;
|
||||||
|
while (xpp.next() != XmlPullParser.END_DOCUMENT) {
|
||||||
|
String tag = xpp.getName();
|
||||||
|
if (xpp.getEventType() == XmlPullParser.END_TAG) {
|
||||||
|
// Ignore end tag.
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (tag != null) {
|
||||||
|
if (tag.equals(ASTRID_TAG)) {
|
||||||
|
// Process <astrid ... >
|
||||||
|
// Perform version compatibility check?
|
||||||
|
}
|
||||||
|
else if (tag.equals(TASK_TAG)) {
|
||||||
|
// Parse <task ... >
|
||||||
|
currentTask = parseTask();
|
||||||
|
} else if (currentTask != null) {
|
||||||
|
// These tags all require that we have a task to associate them with.
|
||||||
|
if (tag.equals(TAG_TAG)) {
|
||||||
|
// Process <tag ... >
|
||||||
|
parseTag(currentTask.getTaskIdentifier());
|
||||||
|
} else if (tag.equals(ALERT_TAG)) {
|
||||||
|
// Process <alert ... >
|
||||||
|
parseAlert(currentTask.getTaskIdentifier());
|
||||||
|
} else if (tag.equals(SYNC_TAG)) {
|
||||||
|
// Process <sync ... >
|
||||||
|
parseSync(currentTask.getTaskIdentifier());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (Exception e) {
|
||||||
|
Log.e(TAG, "import error " + e.getMessage());
|
||||||
|
} finally {
|
||||||
|
closeControllers();
|
||||||
|
progressDialog.dismiss();
|
||||||
|
showSummary();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean parseSync(TaskIdentifier taskId) {
|
||||||
|
String service = xpp.getAttributeValue(null, SYNC_ATTR_SERVICE);
|
||||||
|
String remoteId = xpp.getAttributeValue(null, SYNC_ATTR_REMOTE_ID);
|
||||||
|
if (service != null && remoteId != null) {
|
||||||
|
int serviceInt = Integer.parseInt(service);
|
||||||
|
SyncMapping sm = new SyncMapping(taskId, serviceInt, remoteId);
|
||||||
|
syncDataController.saveSyncMapping(sm);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean parseAlert(TaskIdentifier taskId) {
|
||||||
|
String alert = xpp.getAttributeValue(null, ALERT_ATTR_DATE);
|
||||||
|
if (alert != null) {
|
||||||
|
Date alertDate = DateUtilities.getDateFromIso8601String(alert);
|
||||||
|
if (alertDate != null) {
|
||||||
|
if (! alertController.addAlert(taskId, alertDate)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean parseTag(TaskIdentifier taskId) {
|
||||||
|
String tagName = xpp.getAttributeValue(null, TAG_ATTR_NAME);
|
||||||
|
if (tagName != null) {
|
||||||
|
TagIdentifier tagId;
|
||||||
|
TagModelForView tagModel;
|
||||||
|
tagModel = tagController.fetchTagFromName(tagName);
|
||||||
|
if (tagModel == null) {
|
||||||
|
// Tag not found, create a new one.
|
||||||
|
tagId = tagController.createTag(tagName);
|
||||||
|
} else {
|
||||||
|
tagId = tagModel.getTagIdentifier();
|
||||||
|
}
|
||||||
|
if (! tagController.addTag(taskId, tagId)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
private TaskModelForXml parseTask() {
|
||||||
|
taskCount++;
|
||||||
|
setProgressMessage(context.getString(R.string.import_progress_read, taskCount));
|
||||||
|
TaskModelForXml task = null;
|
||||||
|
String taskName = xpp.getAttributeValue(null, TASK_NAME);
|
||||||
|
|
||||||
|
Date creationDate = null;
|
||||||
|
String createdString = xpp.getAttributeValue(null, TASK_CREATION_DATE);
|
||||||
|
if (createdString != null) {
|
||||||
|
creationDate = DateUtilities.getDateFromIso8601String(createdString);
|
||||||
|
}
|
||||||
|
// If the task's name and creation date match an existing task, skip it.
|
||||||
|
if (creationDate != null && taskName != null) {
|
||||||
|
task = taskController.fetchTaskForXml(taskName, creationDate);
|
||||||
|
}
|
||||||
|
if (task != null) {
|
||||||
|
// Skip this task.
|
||||||
|
skipCount++;
|
||||||
|
setProgressMessage(context.getString(R.string.import_progress_skip, taskCount));
|
||||||
|
// Set currentTask to null so we skip its alerts/syncs/tags, too.
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
// Else, make a new task model and add away.
|
||||||
|
task = new TaskModelForXml();
|
||||||
|
int numAttributes = xpp.getAttributeCount();
|
||||||
|
for (int i = 0; i < numAttributes; i++) {
|
||||||
|
String fieldName = xpp.getAttributeName(i);
|
||||||
|
String fieldValue = xpp.getAttributeValue(i);
|
||||||
|
task.setField(fieldName, fieldValue);
|
||||||
|
}
|
||||||
|
// Save the task to the database.
|
||||||
|
taskController.saveTask(task, false);
|
||||||
|
importCount++;
|
||||||
|
setProgressMessage(context.getString(R.string.import_progress_add, taskCount));
|
||||||
|
return task;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void showSummary() {
|
||||||
|
AlertDialog.Builder builder = new AlertDialog.Builder(context);
|
||||||
|
builder.setTitle(R.string.import_summary_title);
|
||||||
|
String message = context.getString(R.string.import_summary_message,
|
||||||
|
input, taskCount, importCount, skipCount);
|
||||||
|
builder.setMessage(message);
|
||||||
|
builder.setPositiveButton(context.getString(android.R.string.ok),
|
||||||
|
new DialogInterface.OnClickListener() {
|
||||||
|
public void onClick(DialogInterface dialog, int id) {
|
||||||
|
dialog.dismiss();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
builder.show();
|
||||||
|
}
|
||||||
|
|
||||||
|
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) {
|
||||||
|
setTaskController(new TaskController(ctx));
|
||||||
|
setTagController(new TagController(ctx));
|
||||||
|
setAlertController(new AlertController(ctx));
|
||||||
|
setSyncDataController(new SyncDataController(ctx));
|
||||||
|
}
|
||||||
|
|
||||||
|
private void closeControllers() {
|
||||||
|
taskController.close();
|
||||||
|
tagController.close();
|
||||||
|
alertController.close();
|
||||||
|
syncDataController.close();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void openControllers() {
|
||||||
|
taskController.open();
|
||||||
|
tagController.open();
|
||||||
|
alertController.open();
|
||||||
|
syncDataController.open();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setInput(String input) {
|
||||||
|
this.input = input;
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,58 @@
|
|||||||
|
package com.timsu.astrid.widget;
|
||||||
|
|
||||||
|
import android.app.AlertDialog;
|
||||||
|
import android.content.Context;
|
||||||
|
import android.content.DialogInterface;
|
||||||
|
|
||||||
|
import java.io.File;
|
||||||
|
import java.io.FilenameFilter;
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
public class FilePickerBuilder extends AlertDialog.Builder implements DialogInterface.OnClickListener {
|
||||||
|
|
||||||
|
public interface OnFilePickedListener {
|
||||||
|
void onFilePicked(String filePath);
|
||||||
|
}
|
||||||
|
|
||||||
|
private OnFilePickedListener callback;
|
||||||
|
private String[] files;
|
||||||
|
private String path;
|
||||||
|
private FilenameFilter filter;
|
||||||
|
|
||||||
|
public FilePickerBuilder(Context ctx, String title, File path, OnFilePickedListener callback) {
|
||||||
|
super(ctx);
|
||||||
|
filter = new FilenameFilter() {
|
||||||
|
@Override
|
||||||
|
public boolean accept(File dir, String s) {
|
||||||
|
File file = new File(dir, s);
|
||||||
|
return file.isFile();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
setTitle(title);
|
||||||
|
setPath(path);
|
||||||
|
this.callback = callback;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setFilter(FilenameFilter filter) {
|
||||||
|
this.filter = filter;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void setPath(File path) {
|
||||||
|
this.path = path.getAbsolutePath();
|
||||||
|
// Reverse the order of the file list so newest timestamped file is first.
|
||||||
|
List<String> fileList = Arrays.asList(path.list(filter));
|
||||||
|
Collections.sort(fileList);
|
||||||
|
Collections.reverse(fileList);
|
||||||
|
files = (String[])fileList.toArray();
|
||||||
|
setItems(files, this);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onClick(DialogInterface dialogInterface, int i) {
|
||||||
|
if (callback != null) {
|
||||||
|
callback.onFilePicked(path + "/" + files[i]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
Loading…
Reference in New Issue