added a monthly update force for Astrid, also fixed up task importing. but it doesn't work yet?

pull/14/head
Tim Su 14 years ago
parent b30e9a8e56
commit 5d43da7b68

@ -17,7 +17,7 @@ public class BackupConstants {
// Do NOT edit the constants in this file! You will break compatibility with old backups // Do NOT edit the constants in this file! You will break compatibility with old backups
// --- format 2 // --- general xml
/** Tag containing Astrid backup data */ /** Tag containing Astrid backup data */
public static final String ASTRID_TAG = "astrid"; public static final String ASTRID_TAG = "astrid";
@ -28,9 +28,14 @@ public class BackupConstants {
/** Attribute indicating backup file format */ /** Attribute indicating backup file format */
public static final String ASTRID_ATTR_FORMAT = "format"; public static final String ASTRID_ATTR_FORMAT = "format";
// --- format 2
/** Tag containing a task */ /** Tag containing a task */
public static final String TASK_TAG = "task"; public static final String TASK_TAG = "task";
/** Tag containing a metadata item */
public static final String METADATA_TAG = "metadata";
// --- format 1 // --- format 1
public static final String TAG_TAG = "tag"; public static final String TAG_TAG = "tag";

@ -46,7 +46,7 @@ public class FilePickerBuilder extends AlertDialog.Builder implements DialogInte
private void setPath(File path) { private void setPath(File path) {
if (path != null && path.exists()) { if (path != null && path.exists()) {
this.path = path.getAbsolutePath(); this.path = path.getAbsolutePath();
// Reverse the order of the file list so newest timestamped file is first. // Reverse the order of the file list so newest time-stamped file is first.
List<String> fileList = Arrays.asList(path.list(filter)); List<String> fileList = Arrays.asList(path.list(filter));
Collections.sort(fileList); Collections.sort(fileList);
Collections.reverse(fileList); Collections.reverse(fileList);

@ -9,7 +9,6 @@ import org.xmlpull.v1.XmlSerializer;
import android.app.ProgressDialog; import android.app.ProgressDialog;
import android.content.Context; import android.content.Context;
import android.os.Handler; import android.os.Handler;
import android.os.Looper;
import android.util.Log; import android.util.Log;
import android.util.Xml; import android.util.Xml;
import android.widget.Toast; import android.widget.Toast;
@ -57,10 +56,10 @@ public class TasksXmlExporter {
private final ExceptionService exceptionService = PluginServices.getExceptionService(); private final ExceptionService exceptionService = PluginServices.getExceptionService();
private final ProgressDialog progressDialog; private final ProgressDialog progressDialog;
private final Handler importHandler; private final Handler handler;
private void setProgress(final int taskNumber, final int total, final String title) { private void setProgress(final int taskNumber, final int total, final String title) {
importHandler.post(new Runnable() { handler.post(new Runnable() {
public void run() { public void run() {
progressDialog.setProgress(taskNumber * 10000 / total); progressDialog.setProgress(taskNumber * 10000 / total);
progressDialog.setMessage(context.getString(R.string.export_progress_read, title)); progressDialog.setMessage(context.getString(R.string.export_progress_read, title));
@ -73,23 +72,24 @@ public class TasksXmlExporter {
this.exportCount = 0; this.exportCount = 0;
progressDialog = new ProgressDialog(context); progressDialog = new ProgressDialog(context);
importHandler = new Handler(); handler = new Handler();
importHandler.post(new Runnable() { if(!isService) {
@Override handler.post(new Runnable() {
public void run() { @Override
progressDialog.setIcon(android.R.drawable.ic_dialog_info); public void run() {
progressDialog.setTitle(R.string.export_progress_title); progressDialog.setIcon(android.R.drawable.ic_dialog_info);
progressDialog.setProgressStyle(ProgressDialog.STYLE_HORIZONTAL); progressDialog.setTitle(R.string.export_progress_title);
progressDialog.setCancelable(false); progressDialog.setProgressStyle(ProgressDialog.STYLE_HORIZONTAL);
progressDialog.setIndeterminate(false); progressDialog.setCancelable(false);
progressDialog.show(); progressDialog.setIndeterminate(false);
} progressDialog.show();
}); }
});
}
new Thread(new Runnable() { new Thread(new Runnable() {
@Override @Override
public void run() { public void run() {
Looper.prepare();
try { try {
String output = setupFile(BackupConstants.getExportDirectory(), String output = setupFile(BackupConstants.getExportDirectory(),
isService); isService);
@ -108,7 +108,6 @@ public class TasksXmlExporter {
Preferences.setString(BackupService.PREF_BACKUP_LAST_ERROR, e.toString()); Preferences.setString(BackupService.PREF_BACKUP_LAST_ERROR, e.toString());
} }
} }
Looper.loop();
} }
}).start(); }).start();
} }
@ -152,7 +151,7 @@ public class TasksXmlExporter {
setProgress(i, length, task.getValue(Task.TITLE)); setProgress(i, length, task.getValue(Task.TITLE));
xml.startTag(null, BackupConstants.TASK_TAG); xml.startTag(null, BackupConstants.TASK_TAG);
serializeModel(task, Task.PROPERTIES); serializeModel(task, Task.PROPERTIES, Task.ID);
serializeMetadata(task); serializeMetadata(task);
xml.endTag(null, BackupConstants.TASK_TAG); xml.endTag(null, BackupConstants.TASK_TAG);
this.exportCount++; this.exportCount++;
@ -170,9 +169,9 @@ public class TasksXmlExporter {
for(cursor.moveToFirst(); !cursor.isAfterLast(); cursor.moveToNext()) { for(cursor.moveToFirst(); !cursor.isAfterLast(); cursor.moveToNext()) {
metadata.readFromCursor(cursor); metadata.readFromCursor(cursor);
xml.startTag(null, BackupConstants.TAG_TAG); xml.startTag(null, BackupConstants.METADATA_TAG);
serializeModel(metadata, Metadata.PROPERTIES); serializeModel(metadata, Metadata.PROPERTIES, Metadata.ID, Metadata.TASK);
xml.endTag(null, BackupConstants.TAG_TAG); xml.endTag(null, BackupConstants.METADATA_TAG);
} }
} finally { } finally {
cursor.close(); cursor.close();
@ -183,8 +182,12 @@ public class TasksXmlExporter {
* Turn a model into xml attributes * Turn a model into xml attributes
* @param model * @param model
*/ */
private void serializeModel(AbstractModel model, Property<?>[] properties) { private void serializeModel(AbstractModel model, Property<?>[] properties, Property<?>... excludes) {
for(Property<?> property : properties) { outer: for(Property<?> property : properties) {
for(Property<?> exclude : excludes)
if(property == exclude)
continue outer;
try { try {
property.accept(xmlWritingVisitor, model); property.accept(xmlWritingVisitor, model);
} catch (Exception e) { } catch (Exception e) {
@ -259,11 +262,16 @@ public class TasksXmlExporter {
} }
private void displayToast(String output) { private void displayToast(final String output) {
CharSequence text = String.format(context.getString(R.string.export_toast), handler.post(new Runnable() {
context.getResources().getQuantityString(R.plurals.Ntasks, exportCount, @Override
exportCount), output); public void run() {
Toast.makeText(context, text, Toast.LENGTH_LONG).show(); CharSequence text = String.format(context.getString(R.string.export_toast),
context.getResources().getQuantityString(R.plurals.Ntasks, exportCount,
exportCount), output);
Toast.makeText(context, text, Toast.LENGTH_LONG).show();
}
});
} }
/** /**

@ -16,11 +16,14 @@ import android.content.Context;
import android.content.DialogInterface; import android.content.DialogInterface;
import android.content.res.Resources; import android.content.res.Resources;
import android.os.Handler; import android.os.Handler;
import android.os.Looper; import android.text.TextUtils;
import android.util.Log; import android.util.Log;
import com.google.ical.values.RRule; import com.google.ical.values.RRule;
import com.timsu.astrid.R; import com.timsu.astrid.R;
import com.todoroo.andlib.data.AbstractModel;
import com.todoroo.andlib.data.Property;
import com.todoroo.andlib.data.Property.PropertyVisitor;
import com.todoroo.andlib.data.TodorooCursor; import com.todoroo.andlib.data.TodorooCursor;
import com.todoroo.andlib.service.ContextManager; import com.todoroo.andlib.service.ContextManager;
import com.todoroo.andlib.service.ExceptionService; import com.todoroo.andlib.service.ExceptionService;
@ -102,7 +105,6 @@ public class TasksXmlImporter {
new Thread(new Runnable() { new Thread(new Runnable() {
@Override @Override
public void run() { public void run() {
Looper.prepare();
try { try {
performImport(); performImport();
} catch (IOException e) { } catch (IOException e) {
@ -112,7 +114,6 @@ public class TasksXmlImporter {
exceptionService.displayAndReportError(context, exceptionService.displayAndReportError(context,
context.getString(R.string.backup_TXI_error), e); context.getString(R.string.backup_TXI_error), e);
} }
Looper.loop();
} }
}).start(); }).start();
} }
@ -136,27 +137,15 @@ public class TasksXmlImporter {
if (tag != null) { if (tag != null) {
// Process <astrid ... > // Process <astrid ... >
if (tag.equals(BackupConstants.ASTRID_TAG)) { if (tag.equals(BackupConstants.ASTRID_TAG)) {
String version = xpp.getAttributeValue(null, BackupConstants.ASTRID_ATTR_FORMAT); String format = xpp.getAttributeValue(null, BackupConstants.ASTRID_ATTR_FORMAT);
int intVersion; if(TextUtils.equals(format, FORMAT1))
try { new Format1TaskImporter(xpp);
intVersion = version == null ? 1 : Integer.parseInt(version); else if(TextUtils.equals(format, FORMAT2))
} catch (Exception e) { new Format2TaskImporter(xpp);
else
throw new UnsupportedOperationException( throw new UnsupportedOperationException(
"Did not know how to import tasks with xml format '" + "Did not know how to import tasks with xml format '" +
version + "'"); format + "'");
}
switch(intVersion) {
case 1:
new Astrid2TaskImporter(xpp);
break;
case 2:
new Astrid3TaskImporter(xpp);
break;
default:
throw new UnsupportedOperationException(
"Did not know how to import tasks with xml format number '" +
version + "'");
}
} }
} }
} }
@ -167,7 +156,7 @@ public class TasksXmlImporter {
} }
private void showSummary() { private void showSummary() {
AlertDialog.Builder builder = new AlertDialog.Builder(context); final AlertDialog.Builder builder = new AlertDialog.Builder(context);
builder.setTitle(R.string.import_summary_title); builder.setTitle(R.string.import_summary_title);
Resources r = context.getResources(); Resources r = context.getResources();
String message = context.getString(R.string.import_summary_message, String message = context.getString(R.string.import_summary_message,
@ -185,22 +174,149 @@ public class TasksXmlImporter {
} }
} }
}); });
builder.show();
importHandler.post(new Runnable() {
@Override
public void run() {
builder.show();
}
});
} }
// --- importers // --- importers
private class Astrid3TaskImporter { // =============================================================== FORMAT2
@SuppressWarnings("unused")
private static final String FORMAT2 = "2"; //$NON-NLS-1$
private class Format2TaskImporter {
private final XmlPullParser xpp; private final XmlPullParser xpp;
private final Task currentTask = new Task();
private final Metadata metadata = new Metadata();
public Astrid3TaskImporter(XmlPullParser xpp) { public Format2TaskImporter(XmlPullParser xpp) throws XmlPullParserException, IOException {
this.xpp = xpp; this.xpp = xpp;
// TODO while (xpp.next() != XmlPullParser.END_DOCUMENT) {
String tag = xpp.getName();
if (tag == null || xpp.getEventType() == XmlPullParser.END_TAG)
continue;
else if (tag.equals(BackupConstants.TASK_TAG)) {
// Parse <task ... >
parseTask();
} else if (tag.equals(BackupConstants.METADATA_TAG)) {
// Process <metadata ... >
parseMetadata();
}
}
}
private void parseTask() {
taskCount++;
setProgressMessage(context.getString(R.string.import_progress_read,
taskCount));
currentTask.clear();
String title = xpp.getAttributeValue(null, Task.TITLE.name);
String created = xpp.getAttributeValue(null, Task.CREATION_DATE.name);
// if we don't have task name or creation date, skip
if (created == null || title == null) {
skipCount++;
return;
}
// if the task's name and creation date match an existing task, skip
TodorooCursor<Task> cursor = taskService.query(Query.select(Task.ID).
where(Criterion.and(Task.TITLE.eq(title),
Task.CREATION_DATE.eq(created))));
try {
if(cursor.getCount() > 0) {
skipCount++;
return;
}
} finally {
cursor.close();
}
// else, make a new task model and add away.
deserializeModel(currentTask, Task.PROPERTIES);
// Save the task to the database.
taskService.save(currentTask, false);
importCount++;
}
private void parseMetadata() {
if(!currentTask.isSaved())
return;
metadata.clear();
deserializeModel(metadata, Metadata.PROPERTIES);
metadata.setValue(Metadata.TASK, currentTask.getId());
metadataService.save(metadata);
}
/**
* Turn a model into xml attributes
* @param model
*/
private void deserializeModel(AbstractModel model, Property<?>[] properties) {
for(Property<?> property : properties) {
try {
property.accept(xmlReadingVisitor, model);
} catch (Exception e) {
Log.e("astrid-importer", //$NON-NLS-1$
"Caught exception while writing " + property.name + //$NON-NLS-1$
" from " + xpp.getText(), e); //$NON-NLS-1$
}
}
}
private final XmlReadingPropertyVisitor xmlReadingVisitor = new XmlReadingPropertyVisitor();
private class XmlReadingPropertyVisitor implements PropertyVisitor<Void, AbstractModel> {
@Override
public Void visitInteger(Property<Integer> property,
AbstractModel data) {
String value = xpp.getAttributeValue(null, property.name);
if(value != null)
data.setValue(property, Integer.parseInt(value));
return null;
}
@Override
public Void visitLong(Property<Long> property, AbstractModel data) {
String value = xpp.getAttributeValue(null, property.name);
if(value != null)
data.setValue(property, Long.parseLong(value));
return null;
}
@Override
public Void visitDouble(Property<Double> property,
AbstractModel data) {
String value = xpp.getAttributeValue(null, property.name);
if(value != null)
data.setValue(property, Double.parseDouble(value));
return null;
}
@Override
public Void visitString(Property<String> property,
AbstractModel data) {
String value = xpp.getAttributeValue(null, property.name);
if(value != null)
data.setValue(property, value);
return null;
}
} }
} }
private class Astrid2TaskImporter { // =============================================================== FORMAT1
private static final String FORMAT1 = null;
private class Format1TaskImporter {
private final XmlPullParser xpp; private final XmlPullParser xpp;
private Task currentTask = null; private Task currentTask = null;
private String upgradeNotes = null; private String upgradeNotes = null;
@ -208,7 +324,7 @@ public class TasksXmlImporter {
private final ArrayList<String> tags = new ArrayList<String>(); private final ArrayList<String> tags = new ArrayList<String>();
public Astrid2TaskImporter(XmlPullParser xpp) throws XmlPullParserException, IOException { public Format1TaskImporter(XmlPullParser xpp) throws XmlPullParserException, IOException {
this.xpp = xpp; this.xpp = xpp;
while (xpp.next() != XmlPullParser.END_DOCUMENT) { while (xpp.next() != XmlPullParser.END_DOCUMENT) {

@ -41,6 +41,12 @@
<string name="export_toast">Backed Up %s to %s.</string> <string name="export_toast">Backed Up %s to %s.</string>
<!-- Progress Dialog Title for exporting -->
<string name="export_progress_title">Exporting...</string>
<!-- Progress Dialog text for export reading task (%s -> task title) -->
<string name="export_progress_read">Reading task %s...</string>
<!-- Summary title for import --> <!-- Summary title for import -->
<string name="import_summary_title">Restore Summary</string> <string name="import_summary_title">Restore Summary</string>
@ -50,22 +56,25 @@ File %s contained %s.\n\n
%s imported,\n %s imported,\n
%s already exist\n %s already exist\n
</string> </string>
<!-- Progress Dialog Title for importing --> <!-- Progress Dialog Title for importing -->
<string name="import_progress_title">Importing...</string> <string name="import_progress_title">Importing...</string>
<!-- Progress Dialog Title for exporting -->
<string name="export_progress_title">Exporting...</string>
<!-- Progress Dialog text for import opening file --> <!-- Progress Dialog text for import opening file -->
<string name="import_progress_open">Opening file...</string> <string name="import_progress_open">Opening file...</string>
<!-- Progress Dialog text for import reading task (%d -> task number)--> <!-- Progress Dialog text for import reading task (%d -> task number)-->
<string name="import_progress_read">Reading task %d...</string> <string name="import_progress_read">Reading task %d...</string>
<!-- Progress Dialog text for export reading task (%s -> task title) --> <!-- Dialog when unable to open a file -->
<string name="export_progress_read">Reading task %s...</string> <string name="DLG_error_opening">Could not find this item: </string>
<!-- Dialog when unable to open SD card folder -->
<string name="DLG_error_sdcard">Cannot access folder: %s</string>
<!-- Dialog when unable to open SD card in general -->
<string name="DLG_error_sdcard_general">Cannot access your SD card!</string>
<!-- File Selector dialog for import --> <!-- File Selector dialog for import -->
<string name="import_file_prompt">Select a File to Restore</string> <string name="import_file_prompt">Select a File to Restore</string>

@ -122,15 +122,14 @@
<!-- Title for dialog selecting a time (hours and minutes) --> <!-- Title for dialog selecting a time (hours and minutes) -->
<string name="DLG_hour_minutes">Time (hours : minutes)</string> <string name="DLG_hour_minutes">Time (hours : minutes)</string>
<!-- Dialog when unable to open a file -->
<string name="DLG_error_opening">Could not find this item: </string>
<!-- Dialog when unable to open SD card folder --> <!-- Dialog when Astrid needs to be updated -->
<string name="DLG_error_sdcard">Cannot access folder: %s</string> <string name="DLG_please_update">Astrid should to be updated to the latest
version in the Android market! Please do that before continuing, or wait a
few seconds.</string>
<!-- Dialog when unable to open SD card in general --> <!-- Button for going to Market -->
<string name="DLG_error_sdcard_general">Cannot access your SD card!</string> <string name="DLG_to_market">Go To Market</string>
<!-- =============================================================== UI == --> <!-- =============================================================== UI == -->

@ -3,6 +3,9 @@ package com.todoroo.astrid.activity;
import java.util.Date; import java.util.Date;
import java.util.List; import java.util.List;
import java.util.Map.Entry; import java.util.Map.Entry;
import java.util.Timer;
import java.util.TimerTask;
import java.util.concurrent.atomic.AtomicInteger;
import android.app.AlertDialog; import android.app.AlertDialog;
import android.app.ListActivity; import android.app.ListActivity;
@ -15,6 +18,7 @@ import android.content.IntentFilter;
import android.content.pm.PackageManager; import android.content.pm.PackageManager;
import android.content.pm.ResolveInfo; import android.content.pm.ResolveInfo;
import android.database.Cursor; import android.database.Cursor;
import android.net.Uri;
import android.os.Bundle; import android.os.Bundle;
import android.text.Editable; import android.text.Editable;
import android.text.TextUtils; import android.text.TextUtils;
@ -157,6 +161,8 @@ public class TaskListActivity extends ListActivity implements OnScrollListener {
if(database == null) if(database == null)
return; return;
checkForUpgrades();
database.openForWriting(); database.openForWriting();
setUpUiComponents(); setUpUiComponents();
setUpTaskList(); setUpTaskList();
@ -171,6 +177,55 @@ public class TaskListActivity extends ListActivity implements OnScrollListener {
}).start(); }).start();
} }
private void checkForUpgrades() {
final AtomicInteger countdown = new AtomicInteger(10);
if(DateUtilities.now() > Constants.UPGRADE.getTime()) {
DialogInterface.OnClickListener okListener = new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface arg0, int arg1) {
startActivity(new Intent(Intent.ACTION_VIEW,
Uri.parse("market://search?q=pname:" + //$NON-NLS-1$
getPackageName())));
finish();
}
};
final AlertDialog dialog = new AlertDialog.Builder(this)
.setTitle(R.string.DLG_information_title)
.setMessage(R.string.DLG_please_update)
.setIcon(android.R.drawable.ic_dialog_alert)
.setPositiveButton(R.string.DLG_to_market, okListener)
.setNegativeButton(countdown.toString(), null)
.setCancelable(false)
.show();
dialog.getButton(AlertDialog.BUTTON_NEGATIVE).setEnabled(false);
final Timer timer = new Timer();
timer.scheduleAtFixedRate(new TimerTask() {
@Override
public void run() {
final int number = countdown.addAndGet(-1);
if(number == 0)
timer.cancel();
runOnUiThread(new Runnable() {
public void run() {
if(number == 0) {
dialog.setCancelable(true);
dialog.getButton(AlertDialog.BUTTON_NEGATIVE).setText(
android.R.string.ok);
dialog.getButton(AlertDialog.BUTTON_NEGATIVE).setEnabled(true);
} else {
dialog.getButton(AlertDialog.BUTTON_NEGATIVE).setText(
Integer.toString(number));
}
}
});
}
}, 0L, 1000L);
}
}
/** /**
* Create options menu (displayed when user presses menu key) * Create options menu (displayed when user presses menu key)
* *

@ -1,5 +1,7 @@
package com.todoroo.astrid.utility; package com.todoroo.astrid.utility;
import java.util.Date;
public final class Constants { public final class Constants {
// --- general application constants // --- general application constants
@ -30,6 +32,11 @@ public final class Constants {
*/ */
public static final boolean DEBUG = false; public static final boolean DEBUG = false;
/**
* Upgrade time
*/
public static final Date UPGRADE = new Date(110, 8, 1);
// --- notification id's // --- notification id's
/** Notification Manager id for sync notifications */ /** Notification Manager id for sync notifications */

Loading…
Cancel
Save