@Override public void onReceive(Context context, Intent intent) { ContextManager.setContext(context); DependencyInjectionService.getInstance().inject(this); long taskId = intent.getLongExtra(AstridApiConstants.EXTRAS_TASK_ID, -1); if (taskId == -1) return; Task task = PluginServices.getTaskService().fetchById(taskId, Task.PROPERTIES); if (task == null || !task.isCompleted()) return; String recurrence = task.getValue(Task.RECURRENCE); if (recurrence != null && recurrence.length() > 0) { long newDueDate; try { newDueDate = computeNextDueDate(task, recurrence); if (newDueDate == -1) return; } catch (ParseException e) { PluginServices.getExceptionService().reportError("repeat-parse", e); // $NON-NLS-1$ return; } StatisticsService.reportEvent(StatisticsConstants.V2_TASK_REPEAT); long oldDueDate = task.getValue(Task.DUE_DATE); long repeatUntil = task.getValue(Task.REPEAT_UNTIL); boolean repeatFinished = repeatUntil > 0 && newDueDate >= repeatUntil; if (repeatFinished) { Intent repeatFinishedIntent = new Intent(AstridApiConstants.BROADCAST_EVENT_TASK_REPEAT_FINISHED); repeatFinishedIntent.putExtra(AstridApiConstants.EXTRAS_TASK_ID, task.getId()); repeatFinishedIntent.putExtra(AstridApiConstants.EXTRAS_OLD_DUE_DATE, oldDueDate); repeatFinishedIntent.putExtra(AstridApiConstants.EXTRAS_NEW_DUE_DATE, newDueDate); context.sendOrderedBroadcast(repeatFinishedIntent, null); return; } rescheduleTask(task, newDueDate); // send a broadcast Intent broadcastIntent = new Intent(AstridApiConstants.BROADCAST_EVENT_TASK_REPEATED); broadcastIntent.putExtra(AstridApiConstants.EXTRAS_TASK_ID, task.getId()); broadcastIntent.putExtra(AstridApiConstants.EXTRAS_OLD_DUE_DATE, oldDueDate); broadcastIntent.putExtra(AstridApiConstants.EXTRAS_NEW_DUE_DATE, newDueDate); context.sendOrderedBroadcast(broadcastIntent, null); Flags.set(Flags.REFRESH); return; } }
public class TasksXmlExporter { // --- public interface /** * Import tasks from the given file * * @param context context * @param isService if false, displays ui dialogs * @param runAfterExport runnable to run after exporting * @param backupDirectoryOverride new backupdirectory, or null to use default */ public static void exportTasks( Context context, boolean isService, Runnable runAfterExport, File backupDirectoryOverride) { new TasksXmlExporter(context, isService, runAfterExport, backupDirectoryOverride); } // --- implementation private static final int FORMAT = 2; private final Context context; private int exportCount = 0; private XmlSerializer xml; private final TaskService taskService = PluginServices.getTaskService(); private final MetadataService metadataService = PluginServices.getMetadataService(); private final ExceptionService exceptionService = PluginServices.getExceptionService(); private final ProgressDialog progressDialog; private final Handler handler; private final File backupDirectory; private void setProgress(final int taskNumber, final int total) { handler.post( new Runnable() { public void run() { progressDialog.setMax(total); progressDialog.setProgress(taskNumber); } }); } private TasksXmlExporter( final Context context, final boolean isService, final Runnable runAfterExport, File backupDirectoryOverride) { this.context = context; this.exportCount = 0; this.backupDirectory = backupDirectoryOverride == null ? BackupConstants.defaultExportDirectory() : backupDirectoryOverride; handler = new Handler(); progressDialog = new ProgressDialog(context); if (!isService) { progressDialog.setIcon(android.R.drawable.ic_dialog_info); progressDialog.setTitle(R.string.export_progress_title); progressDialog.setProgressStyle(ProgressDialog.STYLE_HORIZONTAL); progressDialog.setProgress(0); progressDialog.setCancelable(false); progressDialog.setIndeterminate(false); progressDialog.show(); if (context instanceof Activity) progressDialog.setOwnerActivity((Activity) context); } new Thread( new Runnable() { @Override public void run() { try { String output = setupFile(backupDirectory, isService); int tasks = taskService.countTasks(); if (tasks > 0) doTasksExport(output); Preferences.setLong(BackupPreferences.PREF_BACKUP_LAST_DATE, DateUtilities.now()); Preferences.setString(BackupPreferences.PREF_BACKUP_LAST_ERROR, null); if (!isService) onFinishExport(output); } catch (IOException e) { if (!isService) exceptionService.displayAndReportError( context, context.getString(R.string.backup_TXI_error), e); else { exceptionService.reportError("background-backup", e); // $NON-NLS-1$ Preferences.setString(BackupPreferences.PREF_BACKUP_LAST_ERROR, e.toString()); } } finally { if (runAfterExport != null) runAfterExport.run(); } } }) .start(); } @SuppressWarnings("nls") private void doTasksExport(String output) throws IOException { File xmlFile = new File(output); xmlFile.createNewFile(); FileOutputStream fos = new FileOutputStream(xmlFile); xml = Xml.newSerializer(); xml.setOutput(fos, BackupConstants.XML_ENCODING); xml.startDocument(null, null); xml.setFeature("http://xmlpull.org/v1/doc/features.html#indent-output", true); xml.startTag(null, BackupConstants.ASTRID_TAG); xml.attribute( null, BackupConstants.ASTRID_ATTR_VERSION, Integer.toString(AstridPreferences.getCurrentVersion())); xml.attribute(null, BackupConstants.ASTRID_ATTR_FORMAT, Integer.toString(FORMAT)); serializeTasks(); xml.endTag(null, BackupConstants.ASTRID_TAG); xml.endDocument(); xml.flush(); fos.close(); } private void serializeTasks() throws IOException { TodorooCursor<Task> cursor = taskService.query(Query.select(Task.PROPERTIES).orderBy(Order.asc(Task.ID))); try { Task task = new Task(); int length = cursor.getCount(); for (int i = 0; i < length; i++) { cursor.moveToNext(); task.readFromCursor(cursor); setProgress(i, length); xml.startTag(null, BackupConstants.TASK_TAG); serializeModel(task, Task.PROPERTIES, Task.ID); serializeMetadata(task); xml.endTag(null, BackupConstants.TASK_TAG); this.exportCount++; } } finally { cursor.close(); } } private synchronized void serializeMetadata(Task task) throws IOException { TodorooCursor<Metadata> cursor = metadataService.query( Query.select(Metadata.PROPERTIES).where(MetadataCriteria.byTask(task.getId()))); try { Metadata metadata = new Metadata(); for (cursor.moveToFirst(); !cursor.isAfterLast(); cursor.moveToNext()) { metadata.readFromCursor(cursor); xml.startTag(null, BackupConstants.METADATA_TAG); serializeModel(metadata, Metadata.PROPERTIES, Metadata.ID, Metadata.TASK); xml.endTag(null, BackupConstants.METADATA_TAG); } } finally { cursor.close(); } } /** * Turn a model into xml attributes * * @param model */ private void serializeModel( AbstractModel model, Property<?>[] properties, Property<?>... excludes) { outer: for (Property<?> property : properties) { for (Property<?> exclude : excludes) if (property.name.equals(exclude.name)) continue outer; try { property.accept(xmlWritingVisitor, model); } catch (Exception e) { Log.e( "astrid-exporter", //$NON-NLS-1$ "Caught exception while reading " + property.name + //$NON-NLS-1$ " from " + model.getDatabaseValues(), e); //$NON-NLS-1$ } } } private final XmlWritingPropertyVisitor xmlWritingVisitor = new XmlWritingPropertyVisitor(); private class XmlWritingPropertyVisitor implements PropertyVisitor<Void, AbstractModel> { @Override public Void visitInteger(Property<Integer> property, AbstractModel data) { try { xml.attribute(null, property.name, data.getValue(property).toString()); } catch (UnsupportedOperationException e) { // didn't read this value, do nothing } catch (IllegalArgumentException e) { throw new RuntimeException(e); } catch (IllegalStateException e) { throw new RuntimeException(e); } catch (IOException e) { throw new RuntimeException(e); } return null; } @Override public Void visitLong(Property<Long> property, AbstractModel data) { try { xml.attribute(null, property.name, data.getValue(property).toString()); } catch (UnsupportedOperationException e) { // didn't read this value, do nothing } catch (IllegalArgumentException e) { throw new RuntimeException(e); } catch (IllegalStateException e) { throw new RuntimeException(e); } catch (IOException e) { throw new RuntimeException(e); } return null; } @Override public Void visitDouble(Property<Double> property, AbstractModel data) { try { xml.attribute(null, property.name, data.getValue(property).toString()); } catch (UnsupportedOperationException e) { // didn't read this value, do nothing } catch (IllegalArgumentException e) { throw new RuntimeException(e); } catch (IllegalStateException e) { throw new RuntimeException(e); } catch (IOException e) { throw new RuntimeException(e); } return null; } @Override public Void visitString(Property<String> property, AbstractModel data) { try { String value = data.getValue(property); if (value == null) return null; xml.attribute(null, property.name, value); } catch (UnsupportedOperationException e) { // didn't read this value, do nothing } catch (IllegalArgumentException e) { throw new RuntimeException(e); } catch (IllegalStateException e) { throw new RuntimeException(e); } catch (IOException e) { throw new RuntimeException(e); } return null; } } private void onFinishExport(final String outputFile) { handler.post( new Runnable() { @Override public void run() { if (exportCount == 0) Toast.makeText( context, context.getString(R.string.export_toast_no_tasks), Toast.LENGTH_LONG) .show(); else { CharSequence text = String.format( context.getString(R.string.export_toast), context .getResources() .getQuantityString(R.plurals.Ntasks, exportCount, exportCount), outputFile); Toast.makeText(context, text, Toast.LENGTH_LONG).show(); if (progressDialog.isShowing() && context instanceof Activity) DialogUtilities.dismissDialog((Activity) context, progressDialog); } } }); } /** * Creates directories if necessary and returns fully qualified file * * @param directory * @return output file name * @throws IOException */ private String setupFile(File directory, boolean isService) throws IOException { 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 = BackupConstants.BACKUP_FILE_NAME; } else { fileName = BackupConstants.EXPORT_FILE_NAME; } fileName = String.format(fileName, BackupDateUtilities.getDateForExport()); return astridDir.getAbsolutePath() + File.separator + fileName; } else { // Unable to make the /sdcard/astrid directory. throw new IOException( context.getString(R.string.DLG_error_sdcard, astridDir.getAbsolutePath())); } } else { // Unable to access the sdcard because it's not in the mounted state. throw new IOException(context.getString(R.string.DLG_error_sdcard_general)); } } }