Use android-job for daily backup

pull/699/head
Alex Baker 7 years ago
parent 9c5bb51de6
commit ce89959c07

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

@ -8,11 +8,7 @@ package org.tasks.jobs;
import static android.support.test.InstrumentationRegistry.getTargetContext; import static android.support.test.InstrumentationRegistry.getTargetContext;
import static junit.framework.Assert.assertEquals; import static junit.framework.Assert.assertEquals;
import static junit.framework.Assert.assertFalse;
import static junit.framework.Assert.assertTrue; 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 android.support.test.runner.AndroidJUnit4;
import com.todoroo.andlib.utility.AndroidUtilities; import com.todoroo.andlib.utility.AndroidUtilities;
@ -30,7 +26,6 @@ import org.tasks.backup.TasksJsonExporter;
import org.tasks.injection.InjectingTestCase; import org.tasks.injection.InjectingTestCase;
import org.tasks.injection.TestComponent; import org.tasks.injection.TestComponent;
import org.tasks.preferences.Preferences; import org.tasks.preferences.Preferences;
import org.tasks.scheduling.AlarmManager;
@RunWith(AndroidJUnit4.class) @RunWith(AndroidJUnit4.class)
public class BackupServiceTests extends InjectingTestCase { public class BackupServiceTests extends InjectingTestCase {
@ -39,7 +34,7 @@ public class BackupServiceTests extends InjectingTestCase {
private File temporaryDirectory = null; private File temporaryDirectory = null;
@Inject TasksJsonExporter xmlExporter; @Inject TasksJsonExporter jsonExporter;
@Inject TaskDao taskDao; @Inject TaskDao taskDao;
@Inject Preferences preferences; @Inject Preferences preferences;
@ -90,11 +85,8 @@ public class BackupServiceTests extends InjectingTestCase {
public void testBackup() { public void testBackup() {
assertEquals(0, temporaryDirectory.list().length); assertEquals(0, temporaryDirectory.list().length);
preferences.setLong(TasksJsonExporter.PREF_BACKUP_LAST_DATE, 0);
// create a backup // create a backup
BackupJob service = new BackupJob(getTargetContext(), BackupJob service = new BackupJob(getTargetContext(), jsonExporter, preferences);
new JobManager(getTargetContext(), mock(AlarmManager.class), null), xmlExporter, preferences);
service.startBackup(getTargetContext()); service.startBackup(getTargetContext());
AndroidUtilities.sleepDeep(BACKUP_WAIT_TIME); AndroidUtilities.sleepDeep(BACKUP_WAIT_TIME);
@ -103,47 +95,5 @@ public class BackupServiceTests extends InjectingTestCase {
File[] files = temporaryDirectory.listFiles(); File[] files = temporaryDirectory.listFiles();
assertEquals(1, files.length); assertEquals(1, files.length);
assertTrue(files[0].getName().matches(BackupJob.BACKUP_FILE_NAME_REGEX)); 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());
} }
} }

@ -412,12 +412,6 @@
android:permission="android.permission.BIND_JOB_SERVICE" android:permission="android.permission.BIND_JOB_SERVICE"
android:exported="false" /> android:exported="false" />
<receiver android:name=".jobs.BackupJob$Broadcast" />
<service
android:name=".jobs.BackupJob"
android:permission="android.permission.BIND_JOB_SERVICE"
android:exported="false" />
<receiver android:name=".jobs.MidnightRefreshJob$Broadcast" /> <receiver android:name=".jobs.MidnightRefreshJob$Broadcast" />
<service <service
android:name=".jobs.MidnightRefreshJob" android:name=".jobs.MidnightRefreshJob"

@ -41,8 +41,6 @@ import timber.log.Timber;
public class TasksJsonExporter { public class TasksJsonExporter {
public static final String PREF_BACKUP_LAST_DATE = "backupDate"; //$NON-NLS-1$
// --- public interface // --- public interface
private final TagDataDao tagDataDao; private final TagDataDao tagDataDao;
@ -123,8 +121,6 @@ public class TasksJsonExporter {
doTasksExport(output, tasks); doTasksExport(output, tasks);
} }
preferences.setLong(PREF_BACKUP_LAST_DATE, DateUtilities.now());
if (exportType == ExportType.EXPORT_TYPE_MANUAL) { if (exportType == ExportType.EXPORT_TYPE_MANUAL) {
onFinishExport(output); onFinishExport(output);
} }

@ -2,7 +2,6 @@ package org.tasks.injection;
import dagger.Subcomponent; import dagger.Subcomponent;
import org.tasks.jobs.AfterSaveIntentService; import org.tasks.jobs.AfterSaveIntentService;
import org.tasks.jobs.BackupJob;
import org.tasks.jobs.MidnightRefreshJob; import org.tasks.jobs.MidnightRefreshJob;
import org.tasks.jobs.RefreshJob; import org.tasks.jobs.RefreshJob;
import org.tasks.locale.receiver.TaskerIntentService; import org.tasks.locale.receiver.TaskerIntentService;
@ -23,8 +22,6 @@ public interface IntentServiceComponent {
void inject(NotificationSchedulerIntentService notificationSchedulerIntentService); void inject(NotificationSchedulerIntentService notificationSchedulerIntentService);
void inject(BackupJob backupJob);
void inject(MidnightRefreshJob midnightRefreshJob); void inject(MidnightRefreshJob midnightRefreshJob);
void inject(RefreshJob refreshJob); void inject(RefreshJob refreshJob);

@ -1,50 +1,46 @@
package org.tasks.jobs; package org.tasks.jobs;
import android.content.BroadcastReceiver; import static com.google.common.collect.Iterables.skip;
import static com.google.common.collect.Lists.newArrayList;
import static java.util.Collections.emptyList;
import android.content.Context; import android.content.Context;
import android.content.Intent; import android.support.annotation.NonNull;
import android.support.v4.app.JobIntentService; import com.evernote.android.job.Job;
import java.io.File; import java.io.File;
import java.io.FileFilter; import java.io.FileFilter;
import java.util.Arrays; import java.util.Arrays;
import javax.inject.Inject; import java.util.Collections;
import java.util.Comparator;
import java.util.List;
import org.tasks.backup.TasksJsonExporter; import org.tasks.backup.TasksJsonExporter;
import org.tasks.injection.ForApplication;
import org.tasks.injection.IntentServiceComponent;
import org.tasks.preferences.Preferences; import org.tasks.preferences.Preferences;
import timber.log.Timber; import timber.log.Timber;
public class BackupJob extends MidnightJob { public class BackupJob extends Job {
public static final String TAG = "job_backup"; public static final String TAG = "job_backup";
public static final String BACKUP_FILE_NAME_REGEX = "auto\\.[-\\d]+\\.xml"; //$NON-NLS-1$ static final String BACKUP_FILE_NAME_REGEX = "auto\\.[-\\d]+\\.json";
private static final int DAYS_TO_KEEP_BACKUP = 7; static final FileFilter FILE_FILTER = f -> f.getName().matches(BACKUP_FILE_NAME_REGEX);
@Inject @ForApplication Context context; private static final Comparator<File> BY_LAST_MODIFIED = (f1, f2) ->
@Inject JobManager jobManager; Long.compare(f2.lastModified(), f1.lastModified());
@Inject TasksJsonExporter tasksJsonExporter;
@Inject Preferences preferences;
@SuppressWarnings("unused") private static final int DAYS_TO_KEEP_BACKUP = 7;
public BackupJob() { private final Context context;
private final TasksJsonExporter tasksJsonExporter;
} private final Preferences preferences;
BackupJob(Context context, JobManager jobManager, TasksJsonExporter tasksJsonExporter, BackupJob(Context context, TasksJsonExporter tasksJsonExporter, Preferences preferences) {
Preferences preferences) {
this.context = context; this.context = context;
this.jobManager = jobManager;
this.tasksJsonExporter = tasksJsonExporter; this.tasksJsonExporter = tasksJsonExporter;
this.preferences = preferences; this.preferences = preferences;
} }
@NonNull
@Override @Override
protected void run() { protected Result onRunJob(@NonNull Params params) {
startBackup(context); startBackup(context);
} return Result.SUCCESS;
@Override
protected void scheduleNext() {
jobManager.scheduleMidnightBackup();
} }
void startBackup(Context context) { void startBackup(Context context) {
@ -63,42 +59,27 @@ public class BackupJob extends MidnightJob {
} }
private void deleteOldBackups() { private void deleteOldBackups() {
FileFilter backupFileFilter = file -> {
if (file.getName().matches(BACKUP_FILE_NAME_REGEX)) {
return true;
}
return false;
};
File astridDir = preferences.getBackupDirectory(); File astridDir = preferences.getBackupDirectory();
if (astridDir == null) { if (astridDir == null) {
return; return;
} }
// grab all backup files, sort by modified date, delete old ones // grab all backup files, sort by modified date, delete old ones
File[] files = astridDir.listFiles(backupFileFilter); File[] fileArray = astridDir.listFiles(FILE_FILTER);
if (files == null) { for (File file : getDeleteList(fileArray, DAYS_TO_KEEP_BACKUP)) {
return; if (!file.delete()) {
} Timber.e("Unable to delete: %s", file);
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]);
} }
} }
} }
@Override static List<File> getDeleteList(File[] fileArray, int keepNewest) {
protected void inject(IntentServiceComponent component) { if (fileArray == null) {
component.inject(this); return emptyList();
} }
public static class Broadcast extends BroadcastReceiver { List<File> files = Arrays.asList(fileArray);
Collections.sort(files, BY_LAST_MODIFIED);
@Override return newArrayList(skip(files, keepNewest));
public void onReceive(Context context, Intent intent) {
JobIntentService.enqueueWork(context, BackupJob.class, JobManager.JOB_ID_BACKUP, intent);
}
} }
} }

@ -1,25 +1,33 @@
package org.tasks.jobs; package org.tasks.jobs;
import android.content.Context;
import android.support.annotation.NonNull; import android.support.annotation.NonNull;
import android.support.annotation.Nullable; import android.support.annotation.Nullable;
import com.evernote.android.job.Job; import com.evernote.android.job.Job;
import javax.inject.Inject; import javax.inject.Inject;
import org.tasks.Notifier; import org.tasks.Notifier;
import org.tasks.backup.TasksJsonExporter;
import org.tasks.injection.ApplicationScope; import org.tasks.injection.ApplicationScope;
import org.tasks.injection.ForApplication;
import org.tasks.preferences.Preferences; import org.tasks.preferences.Preferences;
@ApplicationScope @ApplicationScope
public class JobCreator implements com.evernote.android.job.JobCreator { public class JobCreator implements com.evernote.android.job.JobCreator {
private final Context context;
private final Preferences preferences; private final Preferences preferences;
private final Notifier notifier; private final Notifier notifier;
private final NotificationQueue notificationQueue; private final NotificationQueue notificationQueue;
private final TasksJsonExporter tasksJsonExporter;
@Inject @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.preferences = preferences;
this.notifier = notifier; this.notifier = notifier;
this.notificationQueue = notificationQueue; this.notificationQueue = notificationQueue;
this.tasksJsonExporter = tasksJsonExporter;
} }
@Nullable @Nullable
@ -28,6 +36,8 @@ public class JobCreator implements com.evernote.android.job.JobCreator {
switch (tag) { switch (tag) {
case NotificationJob.TAG: case NotificationJob.TAG:
return new NotificationJob(preferences, notifier, notificationQueue); return new NotificationJob(preferences, notifier, notificationQueue);
case BackupJob.TAG:
return new BackupJob(context, tasksJsonExporter, preferences);
default: default:
throw new IllegalArgumentException("Unhandled tag: " + tag); throw new IllegalArgumentException("Unhandled tag: " + tag);
} }

@ -7,7 +7,10 @@ import static org.tasks.time.DateTimeUtils.printTimestamp;
import android.app.PendingIntent; import android.app.PendingIntent;
import android.content.Context; import android.content.Context;
import android.content.Intent; import android.content.Intent;
import com.evernote.android.job.DailyJob;
import com.evernote.android.job.JobRequest; import com.evernote.android.job.JobRequest;
import com.evernote.android.job.JobRequest.Builder;
import java.util.concurrent.TimeUnit;
import javax.inject.Inject; import javax.inject.Inject;
import org.tasks.injection.ApplicationScope; import org.tasks.injection.ApplicationScope;
import org.tasks.injection.ForApplication; import org.tasks.injection.ForApplication;
@ -26,7 +29,6 @@ public class JobManager {
public static final int JOB_ID_TASKER = 11; public static final int JOB_ID_TASKER = 11;
static final int JOB_ID_REFRESH = 1; static final int JOB_ID_REFRESH = 1;
static final int JOB_ID_MIDNIGHT_REFRESH = 6; static final int JOB_ID_MIDNIGHT_REFRESH = 6;
static final int JOB_ID_BACKUP = 7;
private final Context context; private final Context context;
private final AlarmManager alarmManager; private final AlarmManager alarmManager;
private final com.evernote.android.job.JobManager jobManager; private final com.evernote.android.job.JobManager jobManager;
@ -58,10 +60,8 @@ public class JobManager {
alarmManager.noWakeup(adjust(time), getPendingBroadcast(MidnightRefreshJob.Broadcast.class)); alarmManager.noWakeup(adjust(time), getPendingBroadcast(MidnightRefreshJob.Broadcast.class));
} }
public void scheduleMidnightBackup() { public void scheduleBackup() {
long time = nextMidnight(); DailyJob.schedule(new Builder(BackupJob.TAG), 0, TimeUnit.HOURS.toMillis(24) - 1);
Timber.d("%s: %s", BackupJob.TAG, printTimestamp(time));
alarmManager.wakeup(adjust(time), getPendingBroadcast(BackupJob.Broadcast.class));
} }
public void cancelNotifications() { public void cancelNotifications() {

@ -35,7 +35,7 @@ public class BackgroundScheduler extends InjectingJobIntentService {
CalendarNotificationIntentService.enqueueWork(context); CalendarNotificationIntentService.enqueueWork(context);
GeofenceSchedulingIntentService.enqueueWork(context); GeofenceSchedulingIntentService.enqueueWork(context);
jobManager.scheduleMidnightBackup(); jobManager.scheduleBackup();
jobManager.scheduleMidnightRefresh(); jobManager.scheduleMidnightRefresh();
refreshScheduler.clear(); refreshScheduler.clear();

Loading…
Cancel
Save