From ce89959c071064f96a9e2923da9af5b36baf46a2 Mon Sep 17 00:00:00 2001 From: Alex Baker Date: Thu, 29 Mar 2018 15:35:22 -0500 Subject: [PATCH] Use android-job for daily backup --- .../java/org/tasks/jobs/BackupJobTest.java | 62 ++++++++++++++ .../org/tasks/jobs/BackupServiceTests.java | 54 +----------- app/src/main/AndroidManifest.xml | 6 -- .../org/tasks/backup/TasksJsonExporter.java | 4 - .../injection/IntentServiceComponent.java | 3 - .../main/java/org/tasks/jobs/BackupJob.java | 85 +++++++------------ .../main/java/org/tasks/jobs/JobCreator.java | 12 ++- .../main/java/org/tasks/jobs/JobManager.java | 10 +-- .../tasks/scheduling/BackgroundScheduler.java | 2 +- 9 files changed, 114 insertions(+), 124 deletions(-) create mode 100644 app/src/androidTest/java/org/tasks/jobs/BackupJobTest.java diff --git a/app/src/androidTest/java/org/tasks/jobs/BackupJobTest.java b/app/src/androidTest/java/org/tasks/jobs/BackupJobTest.java new file mode 100644 index 000000000..0070ddd91 --- /dev/null +++ b/app/src/androidTest/java/org/tasks/jobs/BackupJobTest.java @@ -0,0 +1,62 @@ +package org.tasks.jobs; + +import static java.util.Collections.emptyList; +import static java.util.Collections.singletonList; +import static junit.framework.Assert.assertEquals; +import static junit.framework.Assert.assertFalse; +import static junit.framework.Assert.assertTrue; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.stub; +import static org.tasks.date.DateTimeUtils.newDate; + +import android.support.test.runner.AndroidJUnit4; +import java.io.File; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.tasks.time.DateTime; + +@RunWith(AndroidJUnit4.class) +public class BackupJobTest { + @Test + public void filterExcludesXmlFiles() { + assertFalse(BackupJob.FILE_FILTER.accept(new File("/a/b/c/d/auto.180329-0001.xml"))); + } + + @Test + public void filterIncludesJsonFiles() { + assertTrue(BackupJob.FILE_FILTER.accept(new File("/a/b/c/d/auto.180329-0001.json"))); + } + + @Test + public void getDeleteKeepAllFiles() { + File file1 = newFile(newDate(2018, 3, 27)); + File file2 = newFile(newDate(2018, 3, 28)); + File file3 = newFile(newDate(2018, 3, 29)); + + assertEquals( + emptyList(), + BackupJob.getDeleteList(new File[] {file2, file1, file3}, 7)); + } + + @Test + public void getDeleteFromNullFileList() { + assertEquals(emptyList(), BackupJob.getDeleteList(null, 2)); + } + + @Test + public void sortFiles() { + File file1 = newFile(newDate(2018, 3, 27)); + File file2 = newFile(newDate(2018, 3, 28)); + File file3 = newFile(newDate(2018, 3, 29)); + + assertEquals( + singletonList(file1), + BackupJob.getDeleteList(new File[] {file2, file1, file3}, 2)); + } + + private static File newFile(DateTime lastModified) { + File result = mock(File.class); + stub(result.lastModified()).toReturn(lastModified.getMillis()); + return result; + } +} diff --git a/app/src/androidTest/java/org/tasks/jobs/BackupServiceTests.java b/app/src/androidTest/java/org/tasks/jobs/BackupServiceTests.java index 4464deeaa..55f461ecf 100644 --- a/app/src/androidTest/java/org/tasks/jobs/BackupServiceTests.java +++ b/app/src/androidTest/java/org/tasks/jobs/BackupServiceTests.java @@ -8,11 +8,7 @@ package org.tasks.jobs; import static android.support.test.InstrumentationRegistry.getTargetContext; import static junit.framework.Assert.assertEquals; -import static junit.framework.Assert.assertFalse; import static junit.framework.Assert.assertTrue; -import static org.mockito.Mockito.mock; -import static org.tasks.date.DateTimeUtils.newDateTime; -import static org.tasks.time.DateTimeUtils.currentTimeMillis; import android.support.test.runner.AndroidJUnit4; import com.todoroo.andlib.utility.AndroidUtilities; @@ -30,7 +26,6 @@ import org.tasks.backup.TasksJsonExporter; import org.tasks.injection.InjectingTestCase; import org.tasks.injection.TestComponent; import org.tasks.preferences.Preferences; -import org.tasks.scheduling.AlarmManager; @RunWith(AndroidJUnit4.class) public class BackupServiceTests extends InjectingTestCase { @@ -39,7 +34,7 @@ public class BackupServiceTests extends InjectingTestCase { private File temporaryDirectory = null; - @Inject TasksJsonExporter xmlExporter; + @Inject TasksJsonExporter jsonExporter; @Inject TaskDao taskDao; @Inject Preferences preferences; @@ -90,11 +85,8 @@ public class BackupServiceTests extends InjectingTestCase { public void testBackup() { assertEquals(0, temporaryDirectory.list().length); - preferences.setLong(TasksJsonExporter.PREF_BACKUP_LAST_DATE, 0); - // create a backup - BackupJob service = new BackupJob(getTargetContext(), - new JobManager(getTargetContext(), mock(AlarmManager.class), null), xmlExporter, preferences); + BackupJob service = new BackupJob(getTargetContext(), jsonExporter, preferences); service.startBackup(getTargetContext()); AndroidUtilities.sleepDeep(BACKUP_WAIT_TIME); @@ -103,47 +95,5 @@ public class BackupServiceTests extends InjectingTestCase { File[] files = temporaryDirectory.listFiles(); assertEquals(1, files.length); assertTrue(files[0].getName().matches(BackupJob.BACKUP_FILE_NAME_REGEX)); - - // assert summary updated - assertTrue(preferences.getLong(TasksJsonExporter.PREF_BACKUP_LAST_DATE, 0) > 0); - } - - @Test - public void testDeletion() throws IOException { - // create a bunch of backups - assertEquals(0, temporaryDirectory.list().length); - - // create some user files - File myFile = new File(temporaryDirectory, "beans"); - myFile.createNewFile(); - - // create some backup files - for (int i = 0; i < 10; i++) { - String name = String.format("auto.%02d%s.xml", i, newDateTime().toString("MMdd-HHmm")); - File tempFile = new File(temporaryDirectory, name); - tempFile.createNewFile(); - } - - // make one really old - File[] files = temporaryDirectory.listFiles(); - files[4].setLastModified(currentTimeMillis() - 20000); - - // assert files created - assertEquals(11, files.length); - - // backup - BackupJob service = new BackupJob(getTargetContext(), - new JobManager(getTargetContext(), mock(AlarmManager.class), null), xmlExporter, preferences); - service.startBackup(getTargetContext()); - - AndroidUtilities.sleepDeep(BACKUP_WAIT_TIME); - - // assert the oldest file was deleted - assertTrue(temporaryDirectory.listFiles().length < 11); - assertFalse(files[4].exists()); - - // assert user file still exists - service.startBackup(getTargetContext()); - assertTrue(myFile.exists()); } } diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 4d62626c3..ef6b8c9d4 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -412,12 +412,6 @@ android:permission="android.permission.BIND_JOB_SERVICE" android:exported="false" /> - - - f.getName().matches(BACKUP_FILE_NAME_REGEX); + private static final Comparator BY_LAST_MODIFIED = (f1, f2) -> + Long.compare(f2.lastModified(), f1.lastModified()); - } + private static final int DAYS_TO_KEEP_BACKUP = 7; + private final Context context; + private final TasksJsonExporter tasksJsonExporter; + private final Preferences preferences; - BackupJob(Context context, JobManager jobManager, TasksJsonExporter tasksJsonExporter, - Preferences preferences) { + BackupJob(Context context, TasksJsonExporter tasksJsonExporter, Preferences preferences) { this.context = context; - this.jobManager = jobManager; this.tasksJsonExporter = tasksJsonExporter; this.preferences = preferences; } + @NonNull @Override - protected void run() { + protected Result onRunJob(@NonNull Params params) { startBackup(context); - } - - @Override - protected void scheduleNext() { - jobManager.scheduleMidnightBackup(); + return Result.SUCCESS; } void startBackup(Context context) { @@ -63,42 +59,27 @@ public class BackupJob extends MidnightJob { } private void deleteOldBackups() { - FileFilter backupFileFilter = file -> { - if (file.getName().matches(BACKUP_FILE_NAME_REGEX)) { - return true; - } - return false; - }; File astridDir = preferences.getBackupDirectory(); if (astridDir == null) { return; } // grab all backup files, sort by modified date, delete old ones - File[] files = astridDir.listFiles(backupFileFilter); - if (files == null) { - return; - } - - Arrays.sort(files, - (file1, file2) -> -Long.valueOf(file1.lastModified()).compareTo(file2.lastModified())); - for (int i = DAYS_TO_KEEP_BACKUP; i < files.length; i++) { - if (!files[i].delete()) { - Timber.i("Unable to delete: %s", files[i]); + File[] fileArray = astridDir.listFiles(FILE_FILTER); + for (File file : getDeleteList(fileArray, DAYS_TO_KEEP_BACKUP)) { + if (!file.delete()) { + Timber.e("Unable to delete: %s", file); } } } - @Override - protected void inject(IntentServiceComponent component) { - component.inject(this); - } - - public static class Broadcast extends BroadcastReceiver { - - @Override - public void onReceive(Context context, Intent intent) { - JobIntentService.enqueueWork(context, BackupJob.class, JobManager.JOB_ID_BACKUP, intent); + static List getDeleteList(File[] fileArray, int keepNewest) { + if (fileArray == null) { + return emptyList(); } + + List files = Arrays.asList(fileArray); + Collections.sort(files, BY_LAST_MODIFIED); + return newArrayList(skip(files, keepNewest)); } } diff --git a/app/src/main/java/org/tasks/jobs/JobCreator.java b/app/src/main/java/org/tasks/jobs/JobCreator.java index 473d0d5c4..e660f2d2c 100644 --- a/app/src/main/java/org/tasks/jobs/JobCreator.java +++ b/app/src/main/java/org/tasks/jobs/JobCreator.java @@ -1,25 +1,33 @@ package org.tasks.jobs; +import android.content.Context; import android.support.annotation.NonNull; import android.support.annotation.Nullable; import com.evernote.android.job.Job; import javax.inject.Inject; import org.tasks.Notifier; +import org.tasks.backup.TasksJsonExporter; import org.tasks.injection.ApplicationScope; +import org.tasks.injection.ForApplication; import org.tasks.preferences.Preferences; @ApplicationScope public class JobCreator implements com.evernote.android.job.JobCreator { + private final Context context; private final Preferences preferences; private final Notifier notifier; private final NotificationQueue notificationQueue; + private final TasksJsonExporter tasksJsonExporter; @Inject - public JobCreator(Preferences preferences, Notifier notifier, NotificationQueue notificationQueue) { + public JobCreator(@ForApplication Context context, Preferences preferences, Notifier notifier, + NotificationQueue notificationQueue, TasksJsonExporter tasksJsonExporter) { + this.context = context; this.preferences = preferences; this.notifier = notifier; this.notificationQueue = notificationQueue; + this.tasksJsonExporter = tasksJsonExporter; } @Nullable @@ -28,6 +36,8 @@ public class JobCreator implements com.evernote.android.job.JobCreator { switch (tag) { case NotificationJob.TAG: return new NotificationJob(preferences, notifier, notificationQueue); + case BackupJob.TAG: + return new BackupJob(context, tasksJsonExporter, preferences); default: throw new IllegalArgumentException("Unhandled tag: " + tag); } diff --git a/app/src/main/java/org/tasks/jobs/JobManager.java b/app/src/main/java/org/tasks/jobs/JobManager.java index 02bfd21b8..803bb9c96 100644 --- a/app/src/main/java/org/tasks/jobs/JobManager.java +++ b/app/src/main/java/org/tasks/jobs/JobManager.java @@ -7,7 +7,10 @@ import static org.tasks.time.DateTimeUtils.printTimestamp; import android.app.PendingIntent; import android.content.Context; import android.content.Intent; +import com.evernote.android.job.DailyJob; import com.evernote.android.job.JobRequest; +import com.evernote.android.job.JobRequest.Builder; +import java.util.concurrent.TimeUnit; import javax.inject.Inject; import org.tasks.injection.ApplicationScope; import org.tasks.injection.ForApplication; @@ -26,7 +29,6 @@ public class JobManager { public static final int JOB_ID_TASKER = 11; static final int JOB_ID_REFRESH = 1; static final int JOB_ID_MIDNIGHT_REFRESH = 6; - static final int JOB_ID_BACKUP = 7; private final Context context; private final AlarmManager alarmManager; private final com.evernote.android.job.JobManager jobManager; @@ -58,10 +60,8 @@ public class JobManager { alarmManager.noWakeup(adjust(time), getPendingBroadcast(MidnightRefreshJob.Broadcast.class)); } - public void scheduleMidnightBackup() { - long time = nextMidnight(); - Timber.d("%s: %s", BackupJob.TAG, printTimestamp(time)); - alarmManager.wakeup(adjust(time), getPendingBroadcast(BackupJob.Broadcast.class)); + public void scheduleBackup() { + DailyJob.schedule(new Builder(BackupJob.TAG), 0, TimeUnit.HOURS.toMillis(24) - 1); } public void cancelNotifications() { diff --git a/app/src/main/java/org/tasks/scheduling/BackgroundScheduler.java b/app/src/main/java/org/tasks/scheduling/BackgroundScheduler.java index 79483230d..3d31ad2fe 100644 --- a/app/src/main/java/org/tasks/scheduling/BackgroundScheduler.java +++ b/app/src/main/java/org/tasks/scheduling/BackgroundScheduler.java @@ -35,7 +35,7 @@ public class BackgroundScheduler extends InjectingJobIntentService { CalendarNotificationIntentService.enqueueWork(context); GeofenceSchedulingIntentService.enqueueWork(context); - jobManager.scheduleMidnightBackup(); + jobManager.scheduleBackup(); jobManager.scheduleMidnightRefresh(); refreshScheduler.clear();