From 641b60be9b27930454e964e01c589ebd0dc46fe2 Mon Sep 17 00:00:00 2001 From: Alex Baker Date: Mon, 15 Jun 2020 12:03:17 -0500 Subject: [PATCH] Convert workers to Kotlin --- .../java/org/tasks/jobs/AfterSaveWork.java | 152 ------------------ .../main/java/org/tasks/jobs/AfterSaveWork.kt | 128 +++++++++++++++ .../main/java/org/tasks/jobs/BackupWork.java | 127 --------------- .../main/java/org/tasks/jobs/BackupWork.kt | 90 +++++++++++ .../main/java/org/tasks/jobs/CleanupWork.java | 77 --------- .../main/java/org/tasks/jobs/CleanupWork.kt | 65 ++++++++ .../java/org/tasks/jobs/DriveUploader.java | 113 ------------- .../main/java/org/tasks/jobs/DriveUploader.kt | 106 ++++++++++++ .../org/tasks/jobs/MidnightRefreshWork.java | 34 ---- .../org/tasks/jobs/MidnightRefreshWork.kt | 21 +++ .../main/java/org/tasks/jobs/RefreshWork.java | 36 ----- .../main/java/org/tasks/jobs/RefreshWork.kt | 22 +++ .../java/org/tasks/jobs/RepeatingWorker.java | 23 --- .../java/org/tasks/jobs/RepeatingWorker.kt | 15 ++ .../main/java/org/tasks/jobs/SyncWork.java | 94 ----------- app/src/main/java/org/tasks/jobs/SyncWork.kt | 77 +++++++++ .../main/java/org/tasks/jobs/WorkManager.kt | 2 +- 17 files changed, 525 insertions(+), 657 deletions(-) delete mode 100644 app/src/main/java/org/tasks/jobs/AfterSaveWork.java create mode 100644 app/src/main/java/org/tasks/jobs/AfterSaveWork.kt delete mode 100644 app/src/main/java/org/tasks/jobs/BackupWork.java create mode 100644 app/src/main/java/org/tasks/jobs/BackupWork.kt delete mode 100644 app/src/main/java/org/tasks/jobs/CleanupWork.java create mode 100644 app/src/main/java/org/tasks/jobs/CleanupWork.kt delete mode 100644 app/src/main/java/org/tasks/jobs/DriveUploader.java create mode 100644 app/src/main/java/org/tasks/jobs/DriveUploader.kt delete mode 100644 app/src/main/java/org/tasks/jobs/MidnightRefreshWork.java create mode 100644 app/src/main/java/org/tasks/jobs/MidnightRefreshWork.kt delete mode 100644 app/src/main/java/org/tasks/jobs/RefreshWork.java create mode 100644 app/src/main/java/org/tasks/jobs/RefreshWork.kt delete mode 100644 app/src/main/java/org/tasks/jobs/RepeatingWorker.java create mode 100644 app/src/main/java/org/tasks/jobs/RepeatingWorker.kt delete mode 100644 app/src/main/java/org/tasks/jobs/SyncWork.java create mode 100644 app/src/main/java/org/tasks/jobs/SyncWork.kt diff --git a/app/src/main/java/org/tasks/jobs/AfterSaveWork.java b/app/src/main/java/org/tasks/jobs/AfterSaveWork.java deleted file mode 100644 index e85574c92..000000000 --- a/app/src/main/java/org/tasks/jobs/AfterSaveWork.java +++ /dev/null @@ -1,152 +0,0 @@ -package org.tasks.jobs; - -import static com.todoroo.astrid.dao.TaskDao.TRANS_SUPPRESS_REFRESH; -import static org.tasks.Strings.isNullOrEmpty; - -import android.content.ContentResolver; -import android.content.ContentValues; -import android.content.Context; -import android.net.Uri; -import android.provider.CalendarContract; -import androidx.annotation.NonNull; -import androidx.work.Data; -import androidx.work.Data.Builder; -import androidx.work.WorkerParameters; -import com.todoroo.astrid.dao.TaskDao; -import com.todoroo.astrid.data.SyncFlags; -import com.todoroo.astrid.data.Task; -import com.todoroo.astrid.reminders.ReminderService; -import com.todoroo.astrid.repeats.RepeatTaskHelper; -import com.todoroo.astrid.timers.TimerPlugin; -import java.util.Objects; -import javax.inject.Inject; -import org.tasks.LocalBroadcastManager; -import org.tasks.R; -import org.tasks.data.CaldavAccount; -import org.tasks.data.CaldavDao; -import org.tasks.injection.ApplicationComponent; -import org.tasks.injection.ApplicationContext; -import org.tasks.injection.InjectingWorker; -import org.tasks.location.GeofenceApi; -import org.tasks.notifications.NotificationManager; -import org.tasks.scheduling.RefreshScheduler; -import org.tasks.sync.SyncAdapters; -import timber.log.Timber; - -public class AfterSaveWork extends InjectingWorker { - - private static final String EXTRA_ID = "extra_id"; - private static final String EXTRA_ORIG_COMPLETED = "extra_was_completed"; - private static final String EXTRA_ORIG_DELETED = "extra_was_deleted"; - private static final String EXTRA_PUSH_GTASKS = "extra_push_gtasks"; - private static final String EXTRA_PUSH_CALDAV = "extra_push_caldav"; - private static final String EXTRA_SUPPRESS_REFRESH = "extra_suppress_refresh"; - - @Inject RepeatTaskHelper repeatTaskHelper; - @Inject @ApplicationContext Context context; - @Inject NotificationManager notificationManager; - @Inject GeofenceApi geofenceApi; - @Inject TimerPlugin timerPlugin; - @Inject ReminderService reminderService; - @Inject RefreshScheduler refreshScheduler; - @Inject LocalBroadcastManager localBroadcastManager; - @Inject TaskDao taskDao; - @Inject SyncAdapters syncAdapters; - @Inject WorkManager workManager; - @Inject CaldavDao caldavDao; - - public AfterSaveWork(@NonNull Context context, @NonNull WorkerParameters workerParams) { - super(context, workerParams); - } - - static Data getInputData(Task current, Task original) { - boolean suppress = current.checkTransitory(SyncFlags.SUPPRESS_SYNC); - boolean forceCaldav = current.checkTransitory(SyncFlags.FORCE_CALDAV_SYNC); - Builder builder = - new Builder() - .putLong(EXTRA_ID, current.getId()) - .putBoolean(EXTRA_PUSH_GTASKS, !suppress && !current.googleTaskUpToDate(original)) - .putBoolean( - EXTRA_PUSH_CALDAV, !suppress && (!current.caldavUpToDate(original) || forceCaldav)) - .putBoolean(EXTRA_SUPPRESS_REFRESH, current.checkTransitory(TRANS_SUPPRESS_REFRESH)); - if (original != null) { - builder - .putLong(EXTRA_ORIG_COMPLETED, original.getCompletionDate()) - .putLong(EXTRA_ORIG_DELETED, original.getDeletionDate()); - } - return builder.build(); - } - - @Override - protected Result run() { - Data data = getInputData(); - long taskId = data.getLong(EXTRA_ID, -1); - Task task = taskDao.fetch(taskId); - if (task == null) { - Timber.e("Missing saved task"); - return Result.failure(); - } - - reminderService.scheduleAlarm(task); - - boolean completionDateModified = - !Objects.equals(task.getCompletionDate(), data.getLong(EXTRA_ORIG_COMPLETED, 0)); - boolean deletionDateModified = - !Objects.equals(task.getDeletionDate(), data.getLong(EXTRA_ORIG_DELETED, 0)); - - boolean justCompleted = completionDateModified && task.isCompleted(); - boolean justDeleted = deletionDateModified && task.isDeleted(); - - if (justCompleted || justDeleted) { - notificationManager.cancel(taskId); - } - if (completionDateModified || deletionDateModified) { - geofenceApi.update(taskId); - } - - if (justCompleted) { - updateCalendarTitle(task); - CaldavAccount account = caldavDao.getAccountForTask(taskId); - if (account == null || !account.isSuppressRepeatingTasks()) { - repeatTaskHelper.handleRepeat(task); - } - if (task.getTimerStart() > 0) { - timerPlugin.stopTimer(task); - } - } - - if ((data.getBoolean(EXTRA_PUSH_GTASKS, false) && syncAdapters.isGoogleTaskSyncEnabled()) - || (data.getBoolean(EXTRA_PUSH_CALDAV, false) && syncAdapters.isCaldavSyncEnabled())) { - workManager.sync(false); - } - - refreshScheduler.scheduleRefresh(task); - if (!data.getBoolean(EXTRA_SUPPRESS_REFRESH, false)) { - localBroadcastManager.broadcastRefresh(); - } - - return Result.success(); - } - - private void updateCalendarTitle(Task task) { - String calendarUri = task.getCalendarURI(); - if (!isNullOrEmpty(calendarUri)) { - try { - // change title of calendar event - ContentResolver cr = context.getContentResolver(); - ContentValues values = new ContentValues(); - values.put( - CalendarContract.Events.TITLE, - context.getString(R.string.gcal_completed_title, task.getTitle())); - cr.update(Uri.parse(calendarUri), values, null, null); - } catch (Exception e) { - Timber.e(e); - } - } - } - - @Override - protected void inject(ApplicationComponent component) { - component.inject(this); - } -} diff --git a/app/src/main/java/org/tasks/jobs/AfterSaveWork.kt b/app/src/main/java/org/tasks/jobs/AfterSaveWork.kt new file mode 100644 index 000000000..9f228314a --- /dev/null +++ b/app/src/main/java/org/tasks/jobs/AfterSaveWork.kt @@ -0,0 +1,128 @@ +package org.tasks.jobs + +import android.content.ContentValues +import android.content.Context +import android.net.Uri +import android.provider.CalendarContract +import androidx.work.Data +import androidx.work.WorkerParameters +import com.todoroo.astrid.dao.TaskDao +import com.todoroo.astrid.data.SyncFlags +import com.todoroo.astrid.data.Task +import com.todoroo.astrid.reminders.ReminderService +import com.todoroo.astrid.repeats.RepeatTaskHelper +import com.todoroo.astrid.timers.TimerPlugin +import org.tasks.LocalBroadcastManager +import org.tasks.R +import org.tasks.Strings.isNullOrEmpty +import org.tasks.data.CaldavDao +import org.tasks.injection.ApplicationComponent +import org.tasks.injection.ApplicationContext +import org.tasks.injection.InjectingWorker +import org.tasks.location.GeofenceApi +import org.tasks.notifications.NotificationManager +import org.tasks.scheduling.RefreshScheduler +import org.tasks.sync.SyncAdapters +import timber.log.Timber +import javax.inject.Inject + +class AfterSaveWork(context: Context, workerParams: WorkerParameters) : InjectingWorker(context, workerParams) { + @Inject lateinit var repeatTaskHelper: RepeatTaskHelper + @Inject @ApplicationContext lateinit var context: Context + @Inject lateinit var notificationManager: NotificationManager + @Inject lateinit var geofenceApi: GeofenceApi + @Inject lateinit var timerPlugin: TimerPlugin + @Inject lateinit var reminderService: ReminderService + @Inject lateinit var refreshScheduler: RefreshScheduler + @Inject lateinit var localBroadcastManager: LocalBroadcastManager + @Inject lateinit var taskDao: TaskDao + @Inject lateinit var syncAdapters: SyncAdapters + @Inject lateinit var workManager: WorkManager + @Inject lateinit var caldavDao: CaldavDao + + override fun run(): Result { + val data = inputData + val taskId = data.getLong(EXTRA_ID, -1) + val task = taskDao.fetch(taskId) + if (task == null) { + Timber.e("Missing saved task") + return Result.failure() + } + reminderService.scheduleAlarm(task) + val completionDateModified = task.completionDate != data.getLong(EXTRA_ORIG_COMPLETED, 0) + val deletionDateModified = task.deletionDate != data.getLong(EXTRA_ORIG_DELETED, 0) + val justCompleted = completionDateModified && task.isCompleted + val justDeleted = deletionDateModified && task.isDeleted + if (justCompleted || justDeleted) { + notificationManager.cancel(taskId) + } + if (completionDateModified || deletionDateModified) { + geofenceApi.update(taskId) + } + if (justCompleted) { + updateCalendarTitle(task) + val account = caldavDao.getAccountForTask(taskId) + if (account == null || !account.isSuppressRepeatingTasks) { + repeatTaskHelper.handleRepeat(task) + } + if (task.timerStart > 0) { + timerPlugin.stopTimer(task) + } + } + if (data.getBoolean(EXTRA_PUSH_GTASKS, false) && syncAdapters.isGoogleTaskSyncEnabled + || data.getBoolean(EXTRA_PUSH_CALDAV, false) && syncAdapters.isCaldavSyncEnabled) { + workManager.sync(false) + } + refreshScheduler.scheduleRefresh(task) + if (!data.getBoolean(EXTRA_SUPPRESS_REFRESH, false)) { + localBroadcastManager.broadcastRefresh() + } + return Result.success() + } + + private fun updateCalendarTitle(task: Task) { + val calendarUri = task.calendarURI + if (!isNullOrEmpty(calendarUri)) { + try { + // change title of calendar event + val cr = context.contentResolver + val values = ContentValues() + values.put( + CalendarContract.Events.TITLE, + context.getString(R.string.gcal_completed_title, task.title)) + cr.update(Uri.parse(calendarUri), values, null, null) + } catch (e: Exception) { + Timber.e(e) + } + } + } + + override fun inject(component: ApplicationComponent) { + component.inject(this) + } + + companion object { + private const val EXTRA_ID = "extra_id" + private const val EXTRA_ORIG_COMPLETED = "extra_was_completed" + private const val EXTRA_ORIG_DELETED = "extra_was_deleted" + private const val EXTRA_PUSH_GTASKS = "extra_push_gtasks" + private const val EXTRA_PUSH_CALDAV = "extra_push_caldav" + private const val EXTRA_SUPPRESS_REFRESH = "extra_suppress_refresh" + fun getInputData(current: Task, original: Task?): Data { + val suppress = current.checkTransitory(SyncFlags.SUPPRESS_SYNC) + val forceCaldav = current.checkTransitory(SyncFlags.FORCE_CALDAV_SYNC) + val builder = Data.Builder() + .putLong(EXTRA_ID, current.id) + .putBoolean(EXTRA_PUSH_GTASKS, !suppress && !current.googleTaskUpToDate(original)) + .putBoolean( + EXTRA_PUSH_CALDAV, !suppress && (!current.caldavUpToDate(original) || forceCaldav)) + .putBoolean(EXTRA_SUPPRESS_REFRESH, current.checkTransitory(TaskDao.TRANS_SUPPRESS_REFRESH)) + if (original != null) { + builder + .putLong(EXTRA_ORIG_COMPLETED, original.completionDate) + .putLong(EXTRA_ORIG_DELETED, original.deletionDate) + } + return builder.build() + } + } +} \ No newline at end of file diff --git a/app/src/main/java/org/tasks/jobs/BackupWork.java b/app/src/main/java/org/tasks/jobs/BackupWork.java deleted file mode 100644 index 93e66ab11..000000000 --- a/app/src/main/java/org/tasks/jobs/BackupWork.java +++ /dev/null @@ -1,127 +0,0 @@ -package org.tasks.jobs; - -import static com.google.common.collect.Iterables.filter; -import static com.google.common.collect.Iterables.skip; -import static com.google.common.collect.Lists.newArrayList; -import static com.todoroo.andlib.utility.DateUtilities.now; -import static java.util.Collections.emptyList; - -import android.content.ContentResolver; -import android.content.Context; -import android.net.Uri; -import androidx.annotation.NonNull; -import androidx.documentfile.provider.DocumentFile; -import androidx.work.WorkerParameters; -import com.google.common.base.Predicate; -import java.io.File; -import java.io.FileFilter; -import java.util.Arrays; -import java.util.Collections; -import java.util.Comparator; -import java.util.List; -import javax.inject.Inject; -import org.tasks.R; -import org.tasks.backup.TasksJsonExporter; -import org.tasks.injection.ApplicationComponent; -import org.tasks.injection.ApplicationContext; -import org.tasks.preferences.Preferences; -import timber.log.Timber; - -public class BackupWork extends RepeatingWorker { - - static final int DAYS_TO_KEEP_BACKUP = 7; - static final String BACKUP_FILE_NAME_REGEX = "auto\\.[-\\d]+\\.json"; - private static final Predicate FILENAME_FILTER = f -> f.matches(BACKUP_FILE_NAME_REGEX); - static final FileFilter FILE_FILTER = f -> FILENAME_FILTER.apply(f.getName()); - private static final Comparator BY_LAST_MODIFIED = - (f1, f2) -> Long.compare(f2.lastModified(), f1.lastModified()); - private static final Comparator DOCUMENT_FILE_COMPARATOR = - (d1, d2) -> Long.compare(d2.lastModified(), d1.lastModified()); - @Inject @ApplicationContext Context context; - @Inject TasksJsonExporter tasksJsonExporter; - @Inject Preferences preferences; - @Inject WorkManager workManager; - - public BackupWork(@NonNull Context context, @NonNull WorkerParameters workerParams) { - super(context, workerParams); - } - - 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)); - } - - private static List getDeleteList(DocumentFile[] fileArray) { - if (fileArray == null) { - return emptyList(); - } - - List files = Arrays.asList(fileArray); - files = newArrayList(filter(files, file -> FILENAME_FILTER.apply(file.getName()))); - Collections.sort(files, DOCUMENT_FILE_COMPARATOR); - return newArrayList(skip(files, DAYS_TO_KEEP_BACKUP)); - } - - @Override - protected Result run() { - preferences.setLong(R.string.p_last_backup, now()); - startBackup(context); - return Result.success(); - } - - @Override - protected void scheduleNext() { - workManager.scheduleBackup(); - } - - @Override - protected void inject(ApplicationComponent component) { - component.inject(this); - } - - private void startBackup(Context context) { - try { - deleteOldLocalBackups(); - } catch (Exception e) { - Timber.e(e); - } - - try { - tasksJsonExporter.exportTasks( - context, TasksJsonExporter.ExportType.EXPORT_TYPE_SERVICE, null); - } catch (Exception e) { - Timber.e(e); - } - } - - private void deleteOldLocalBackups() { - Uri uri = preferences.getBackupDirectory(); - if (uri == null) { - return; - } - switch (uri.getScheme()) { - case ContentResolver.SCHEME_CONTENT: - DocumentFile dir = DocumentFile.fromTreeUri(context, uri); - for (DocumentFile file : getDeleteList(dir.listFiles())) { - if (!file.delete()) { - Timber.e("Unable to delete: %s", file); - } - } - break; - case ContentResolver.SCHEME_FILE: - File astridDir = new File(uri.getPath()); - 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); - } - } - break; - } - } -} diff --git a/app/src/main/java/org/tasks/jobs/BackupWork.kt b/app/src/main/java/org/tasks/jobs/BackupWork.kt new file mode 100644 index 000000000..11be73e82 --- /dev/null +++ b/app/src/main/java/org/tasks/jobs/BackupWork.kt @@ -0,0 +1,90 @@ +package org.tasks.jobs + +import android.content.ContentResolver +import android.content.Context +import androidx.documentfile.provider.DocumentFile +import androidx.work.WorkerParameters +import com.todoroo.andlib.utility.DateUtilities +import org.tasks.R +import org.tasks.backup.TasksJsonExporter +import org.tasks.injection.ApplicationComponent +import org.tasks.injection.ApplicationContext +import org.tasks.preferences.Preferences +import timber.log.Timber +import java.io.File +import java.io.FileFilter +import java.util.* +import javax.inject.Inject + +class BackupWork(context: Context, workerParams: WorkerParameters) : RepeatingWorker(context, workerParams) { + @Inject @ApplicationContext lateinit var context: Context + @Inject lateinit var tasksJsonExporter: TasksJsonExporter + @Inject lateinit var preferences: Preferences + @Inject lateinit var workManager: WorkManager + + override fun run(): Result { + preferences.setLong(R.string.p_last_backup, DateUtilities.now()) + startBackup(context) + return Result.success() + } + + override fun scheduleNext() = workManager.scheduleBackup() + + override fun inject(component: ApplicationComponent) = component.inject(this) + + private fun startBackup(context: Context?) { + try { + deleteOldLocalBackups() + } catch (e: Exception) { + Timber.e(e) + } + try { + tasksJsonExporter.exportTasks( + context, TasksJsonExporter.ExportType.EXPORT_TYPE_SERVICE, null) + } catch (e: Exception) { + Timber.e(e) + } + } + + private fun deleteOldLocalBackups() { + val uri = preferences.backupDirectory + when (uri?.scheme) { + ContentResolver.SCHEME_CONTENT -> { + val dir = DocumentFile.fromTreeUri(context, uri) + for (file in getDeleteList(dir?.listFiles())) { + if (!file.delete()) { + Timber.e("Unable to delete: %s", file) + } + } + } + ContentResolver.SCHEME_FILE -> { + val astridDir = File(uri.path) + val fileArray = astridDir.listFiles(FILE_FILTER) + for (file in getDeleteList(fileArray, DAYS_TO_KEEP_BACKUP)) { + if (!file.delete()) { + Timber.e("Unable to delete: %s", file) + } + } + } + } + } + + companion object { + const val DAYS_TO_KEEP_BACKUP = 7 + val BACKUP_FILE_NAME_REGEX = Regex("auto\\.[-\\d]+\\.json") + private val FILENAME_FILTER = { f: String -> f.matches(BACKUP_FILE_NAME_REGEX) } + val FILE_FILTER = FileFilter { f: File -> FILENAME_FILTER.invoke(f.name) } + private val BY_LAST_MODIFIED = Comparator { f1: File, f2: File -> f2.lastModified().compareTo(f1.lastModified()) } + private val DOCUMENT_FILE_COMPARATOR = Comparator { d1: DocumentFile, d2: DocumentFile -> d2.lastModified().compareTo(d1.lastModified()) } + + fun getDeleteList(fileArray: Array?, keepNewest: Int) = + fileArray?.sortedWith(BY_LAST_MODIFIED)?.drop(keepNewest) ?: emptyList() + + private fun getDeleteList(fileArray: Array?) = + fileArray + ?.filter { FILENAME_FILTER.invoke(it.name!!) } + ?.sortedWith(DOCUMENT_FILE_COMPARATOR) + ?.drop(DAYS_TO_KEEP_BACKUP) + ?: emptyList() + } +} \ No newline at end of file diff --git a/app/src/main/java/org/tasks/jobs/CleanupWork.java b/app/src/main/java/org/tasks/jobs/CleanupWork.java deleted file mode 100644 index db8e21ce5..000000000 --- a/app/src/main/java/org/tasks/jobs/CleanupWork.java +++ /dev/null @@ -1,77 +0,0 @@ -package org.tasks.jobs; - -import android.content.Context; -import androidx.annotation.NonNull; -import androidx.work.WorkerParameters; -import com.todoroo.astrid.alarms.AlarmService; -import com.todoroo.astrid.reminders.ReminderService; -import com.todoroo.astrid.timers.TimerPlugin; -import javax.inject.Inject; -import org.tasks.data.DeletionDao; -import org.tasks.data.Geofence; -import org.tasks.data.LocationDao; -import org.tasks.data.TaskAttachment; -import org.tasks.data.TaskAttachmentDao; -import org.tasks.data.UserActivity; -import org.tasks.data.UserActivityDao; -import org.tasks.files.FileHelper; -import org.tasks.injection.ApplicationComponent; -import org.tasks.injection.InjectingWorker; -import org.tasks.location.GeofenceApi; -import org.tasks.notifications.NotificationManager; -import timber.log.Timber; - -public class CleanupWork extends InjectingWorker { - - static final String EXTRA_TASK_IDS = "extra_task_ids"; - private final Context context; - @Inject NotificationManager notificationManager; - @Inject GeofenceApi geofenceApi; - @Inject TimerPlugin timerPlugin; - @Inject ReminderService reminderService; - @Inject AlarmService alarmService; - @Inject TaskAttachmentDao taskAttachmentDao; - @Inject UserActivityDao userActivityDao; - @Inject LocationDao locationDao; - @Inject DeletionDao deletionDao; - - public CleanupWork(@NonNull Context context, @NonNull WorkerParameters workerParams) { - super(context, workerParams); - this.context = context; - } - - @NonNull - @Override - public Result run() { - long[] tasks = getInputData().getLongArray(EXTRA_TASK_IDS); - if (tasks == null) { - Timber.e("No task ids provided"); - return Result.failure(); - } - for (long task : tasks) { - alarmService.cancelAlarms(task); - reminderService.cancelReminder(task); - notificationManager.cancel(task); - for (Geofence geofence : locationDao.getGeofencesForTask(task)) { - locationDao.delete(geofence); - geofenceApi.update(geofence.getPlace()); - } - for (TaskAttachment attachment : taskAttachmentDao.getAttachments(task)) { - FileHelper.delete(context, attachment.parseUri()); - taskAttachmentDao.delete(attachment); - } - for (UserActivity comment : userActivityDao.getComments(task)) { - FileHelper.delete(context, comment.getPictureUri()); - userActivityDao.delete(comment); - } - } - timerPlugin.updateNotifications(); - deletionDao.purgeDeleted(); - return Result.success(); - } - - @Override - protected void inject(ApplicationComponent component) { - component.inject(this); - } -} diff --git a/app/src/main/java/org/tasks/jobs/CleanupWork.kt b/app/src/main/java/org/tasks/jobs/CleanupWork.kt new file mode 100644 index 000000000..9ed2a8aa3 --- /dev/null +++ b/app/src/main/java/org/tasks/jobs/CleanupWork.kt @@ -0,0 +1,65 @@ +package org.tasks.jobs + +import android.content.Context +import androidx.work.WorkerParameters +import com.todoroo.astrid.alarms.AlarmService +import com.todoroo.astrid.reminders.ReminderService +import com.todoroo.astrid.timers.TimerPlugin +import org.tasks.data.DeletionDao +import org.tasks.data.LocationDao +import org.tasks.data.TaskAttachmentDao +import org.tasks.data.UserActivityDao +import org.tasks.files.FileHelper +import org.tasks.injection.ApplicationComponent +import org.tasks.injection.InjectingWorker +import org.tasks.location.GeofenceApi +import org.tasks.notifications.NotificationManager +import timber.log.Timber +import javax.inject.Inject + +class CleanupWork(private val context: Context, workerParams: WorkerParameters) : InjectingWorker(context, workerParams) { + + @Inject lateinit var notificationManager: NotificationManager + @Inject lateinit var geofenceApi: GeofenceApi + @Inject lateinit var timerPlugin: TimerPlugin + @Inject lateinit var reminderService: ReminderService + @Inject lateinit var alarmService: AlarmService + @Inject lateinit var taskAttachmentDao: TaskAttachmentDao + @Inject lateinit var userActivityDao: UserActivityDao + @Inject lateinit var locationDao: LocationDao + @Inject lateinit var deletionDao: DeletionDao + + public override fun run(): Result { + val tasks = inputData.getLongArray(EXTRA_TASK_IDS) + if (tasks == null) { + Timber.e("No task ids provided") + return Result.failure() + } + tasks.forEach { task -> + alarmService.cancelAlarms(task) + reminderService.cancelReminder(task) + notificationManager.cancel(task) + locationDao.getGeofencesForTask(task).forEach { + locationDao.delete(it) + geofenceApi.update(it.place!!) + } + taskAttachmentDao.getAttachments(task).forEach { + FileHelper.delete(context, it.parseUri()) + taskAttachmentDao.delete(it) + } + userActivityDao.getComments(task).forEach { + FileHelper.delete(context, it.pictureUri) + userActivityDao.delete(it) + } + } + timerPlugin.updateNotifications() + deletionDao.purgeDeleted() + return Result.success() + } + + override fun inject(component: ApplicationComponent) = component.inject(this) + + companion object { + const val EXTRA_TASK_IDS = "extra_task_ids" + } +} \ No newline at end of file diff --git a/app/src/main/java/org/tasks/jobs/DriveUploader.java b/app/src/main/java/org/tasks/jobs/DriveUploader.java deleted file mode 100644 index 917a79c57..000000000 --- a/app/src/main/java/org/tasks/jobs/DriveUploader.java +++ /dev/null @@ -1,113 +0,0 @@ -package org.tasks.jobs; - -import static com.google.common.collect.Iterables.skip; -import static com.google.common.collect.Lists.newArrayList; -import static org.tasks.Strings.isNullOrEmpty; - -import android.content.Context; -import android.net.Uri; -import androidx.annotation.NonNull; -import androidx.work.Data; -import androidx.work.WorkerParameters; -import com.google.api.client.googleapis.json.GoogleJsonResponseException; -import com.google.api.services.drive.model.File; -import java.io.IOException; -import java.net.ConnectException; -import java.net.SocketTimeoutException; -import java.net.UnknownHostException; -import java.util.Collections; -import java.util.Comparator; -import java.util.List; -import javax.inject.Inject; -import javax.net.ssl.SSLException; -import org.tasks.R; -import org.tasks.drive.DriveInvoker; -import org.tasks.injection.ApplicationComponent; -import org.tasks.injection.ApplicationContext; -import org.tasks.injection.InjectingWorker; -import org.tasks.preferences.Preferences; -import timber.log.Timber; - -public class DriveUploader extends InjectingWorker { - - private static final String FOLDER_NAME = "Tasks Backups"; - private static final String EXTRA_URI = "extra_uri"; - private static final String EXTRA_PURGE = "extra_purge"; - private static final Comparator DRIVE_FILE_COMPARATOR = - (f1, f2) -> Long.compare(f2.getModifiedTime().getValue(), f1.getModifiedTime().getValue()); - - @Inject @ApplicationContext Context context; - @Inject DriveInvoker drive; - @Inject Preferences preferences; - - public DriveUploader(@NonNull Context context, @NonNull WorkerParameters workerParams) { - super(context, workerParams); - } - - static Data getInputData(Uri uri, boolean purge) { - return new Data.Builder() - .putString(EXTRA_URI, uri.toString()) - .putBoolean(EXTRA_PURGE, purge) - .build(); - } - - private static List getDeleteList(List files) { - Collections.sort(files, DRIVE_FILE_COMPARATOR); - return newArrayList(skip(files, BackupWork.DAYS_TO_KEEP_BACKUP)); - } - - @Override - protected Result run() { - Data inputData = getInputData(); - Uri uri = Uri.parse(inputData.getString(EXTRA_URI)); - try { - File folder = getFolder(); - preferences.setString(R.string.p_google_drive_backup_folder, folder.getId()); - drive.createFile(folder.getId(), uri); - - if (inputData.getBoolean(EXTRA_PURGE, false)) { - List files = drive.getFilesByPrefix(folder.getId(), "auto."); - for (File file : getDeleteList(files)) { - try { - drive.delete(file); - } catch (GoogleJsonResponseException e) { - if (e.getStatusCode() == 404) { - Timber.e(e); - } else { - throw e; - } - } - } - } - - return Result.success(); - } catch (SocketTimeoutException | SSLException | ConnectException | UnknownHostException e) { - Timber.e(e); - return Result.retry(); - } catch (IOException e) { - firebase.reportException(e); - return Result.failure(); - } - } - - private File getFolder() throws IOException { - String folderId = preferences.getStringValue(R.string.p_google_drive_backup_folder); - File file = null; - if (!isNullOrEmpty(folderId)) { - try { - file = drive.getFile(folderId); - } catch (GoogleJsonResponseException e) { - if (e.getStatusCode() != 404) { - throw e; - } - } - } - - return file == null || file.getTrashed() ? drive.createFolder(FOLDER_NAME) : file; - } - - @Override - protected void inject(ApplicationComponent component) { - component.inject(this); - } -} diff --git a/app/src/main/java/org/tasks/jobs/DriveUploader.kt b/app/src/main/java/org/tasks/jobs/DriveUploader.kt new file mode 100644 index 000000000..3e132254e --- /dev/null +++ b/app/src/main/java/org/tasks/jobs/DriveUploader.kt @@ -0,0 +1,106 @@ +package org.tasks.jobs + +import android.content.Context +import android.net.Uri +import androidx.work.Data +import androidx.work.WorkerParameters +import com.google.api.client.googleapis.json.GoogleJsonResponseException +import com.google.api.services.drive.model.File +import org.tasks.R +import org.tasks.Strings.isNullOrEmpty +import org.tasks.drive.DriveInvoker +import org.tasks.injection.ApplicationComponent +import org.tasks.injection.ApplicationContext +import org.tasks.injection.InjectingWorker +import org.tasks.preferences.Preferences +import timber.log.Timber +import java.io.IOException +import java.net.ConnectException +import java.net.SocketTimeoutException +import java.net.UnknownHostException +import java.util.* +import javax.inject.Inject +import javax.net.ssl.SSLException + +class DriveUploader(context: Context, workerParams: WorkerParameters) : InjectingWorker(context, workerParams) { + @Inject @ApplicationContext lateinit var context: Context + @Inject lateinit var drive: DriveInvoker + @Inject lateinit var preferences: Preferences + + override fun run(): Result { + val inputData = inputData + val uri = Uri.parse(inputData.getString(EXTRA_URI)) + return try { + val folder = folder + preferences.setString(R.string.p_google_drive_backup_folder, folder.id) + drive.createFile(folder.id, uri) + if (inputData.getBoolean(EXTRA_PURGE, false)) { + val files = drive.getFilesByPrefix(folder.id, "auto.") + for (file in getDeleteList(files)) { + try { + drive.delete(file) + } catch (e: GoogleJsonResponseException) { + if (e.statusCode == 404) { + Timber.e(e) + } else { + throw e + } + } + } + } + Result.success() + } catch (e: SocketTimeoutException) { + Timber.e(e) + Result.retry() + } catch (e: SSLException) { + Timber.e(e) + Result.retry() + } catch (e: ConnectException) { + Timber.e(e) + Result.retry() + } catch (e: UnknownHostException) { + Timber.e(e) + Result.retry() + } catch (e: IOException) { + firebase.reportException(e) + Result.failure() + } + } + + @get:Throws(IOException::class) + private val folder: File + get() { + val folderId = preferences.getStringValue(R.string.p_google_drive_backup_folder) + var file: File? = null + if (!isNullOrEmpty(folderId)) { + try { + file = drive.getFile(folderId) + } catch (e: GoogleJsonResponseException) { + if (e.statusCode != 404) { + throw e + } + } + } + return if (file == null || file.trashed) drive.createFolder(FOLDER_NAME) else file + } + + override fun inject(component: ApplicationComponent) = component.inject(this) + + companion object { + private const val FOLDER_NAME = "Tasks Backups" + private const val EXTRA_URI = "extra_uri" + private const val EXTRA_PURGE = "extra_purge" + private val DRIVE_FILE_COMPARATOR = Comparator { f1, f2 -> + f2.modifiedTime.value.compareTo(f1.modifiedTime.value) + } + + fun getInputData(uri: Uri, purge: Boolean) = + Data.Builder() + .putString(EXTRA_URI, uri.toString()) + .putBoolean(EXTRA_PURGE, purge) + .build() + + private fun getDeleteList(files: List) = + files.sortedWith(DRIVE_FILE_COMPARATOR).drop(BackupWork.DAYS_TO_KEEP_BACKUP) + } +} \ No newline at end of file diff --git a/app/src/main/java/org/tasks/jobs/MidnightRefreshWork.java b/app/src/main/java/org/tasks/jobs/MidnightRefreshWork.java deleted file mode 100644 index b77fd597e..000000000 --- a/app/src/main/java/org/tasks/jobs/MidnightRefreshWork.java +++ /dev/null @@ -1,34 +0,0 @@ -package org.tasks.jobs; - -import android.content.Context; -import androidx.annotation.NonNull; -import androidx.work.WorkerParameters; -import javax.inject.Inject; -import org.tasks.LocalBroadcastManager; -import org.tasks.injection.ApplicationComponent; - -public class MidnightRefreshWork extends RepeatingWorker { - - @Inject WorkManager workManager; - @Inject LocalBroadcastManager localBroadcastManager; - - public MidnightRefreshWork(@NonNull Context context, @NonNull WorkerParameters workerParams) { - super(context, workerParams); - } - - @Override - protected Result run() { - localBroadcastManager.broadcastRefresh(); - return Result.success(); - } - - @Override - protected void scheduleNext() { - workManager.scheduleMidnightRefresh(); - } - - @Override - protected void inject(ApplicationComponent component) { - component.inject(this); - } -} diff --git a/app/src/main/java/org/tasks/jobs/MidnightRefreshWork.kt b/app/src/main/java/org/tasks/jobs/MidnightRefreshWork.kt new file mode 100644 index 000000000..f21954135 --- /dev/null +++ b/app/src/main/java/org/tasks/jobs/MidnightRefreshWork.kt @@ -0,0 +1,21 @@ +package org.tasks.jobs + +import android.content.Context +import androidx.work.WorkerParameters +import org.tasks.LocalBroadcastManager +import org.tasks.injection.ApplicationComponent +import javax.inject.Inject + +class MidnightRefreshWork(context: Context, workerParams: WorkerParameters) : RepeatingWorker(context, workerParams) { + @Inject lateinit var workManager: WorkManager + @Inject lateinit var localBroadcastManager: LocalBroadcastManager + + override fun run(): Result { + localBroadcastManager.broadcastRefresh() + return Result.success() + } + + override fun scheduleNext() = workManager.scheduleMidnightRefresh() + + override fun inject(component: ApplicationComponent) = component.inject(this) +} \ No newline at end of file diff --git a/app/src/main/java/org/tasks/jobs/RefreshWork.java b/app/src/main/java/org/tasks/jobs/RefreshWork.java deleted file mode 100644 index 80e5233dd..000000000 --- a/app/src/main/java/org/tasks/jobs/RefreshWork.java +++ /dev/null @@ -1,36 +0,0 @@ -package org.tasks.jobs; - -import android.content.Context; -import androidx.annotation.NonNull; -import androidx.work.WorkerParameters; -import javax.inject.Inject; -import org.tasks.LocalBroadcastManager; -import org.tasks.injection.ApplicationComponent; -import org.tasks.scheduling.RefreshScheduler; - -public class RefreshWork extends RepeatingWorker { - - @Inject RefreshScheduler refreshScheduler; - @Inject LocalBroadcastManager localBroadcastManager; - - public RefreshWork(@NonNull Context context, @NonNull WorkerParameters workerParams) { - super(context, workerParams); - } - - @NonNull - @Override - public Result run() { - localBroadcastManager.broadcastRefresh(); - return Result.success(); - } - - @Override - protected void inject(ApplicationComponent component) { - component.inject(this); - } - - @Override - protected void scheduleNext() { - refreshScheduler.scheduleNext(); - } -} diff --git a/app/src/main/java/org/tasks/jobs/RefreshWork.kt b/app/src/main/java/org/tasks/jobs/RefreshWork.kt new file mode 100644 index 000000000..9ae153805 --- /dev/null +++ b/app/src/main/java/org/tasks/jobs/RefreshWork.kt @@ -0,0 +1,22 @@ +package org.tasks.jobs + +import android.content.Context +import androidx.work.WorkerParameters +import org.tasks.LocalBroadcastManager +import org.tasks.injection.ApplicationComponent +import org.tasks.scheduling.RefreshScheduler +import javax.inject.Inject + +class RefreshWork(context: Context, workerParams: WorkerParameters) : RepeatingWorker(context, workerParams) { + @Inject lateinit var refreshScheduler: RefreshScheduler + @Inject lateinit var localBroadcastManager: LocalBroadcastManager + + public override fun run(): Result { + localBroadcastManager.broadcastRefresh() + return Result.success() + } + + override fun inject(component: ApplicationComponent) = component.inject(this) + + override fun scheduleNext() = refreshScheduler.scheduleNext() +} \ No newline at end of file diff --git a/app/src/main/java/org/tasks/jobs/RepeatingWorker.java b/app/src/main/java/org/tasks/jobs/RepeatingWorker.java deleted file mode 100644 index 285d94c23..000000000 --- a/app/src/main/java/org/tasks/jobs/RepeatingWorker.java +++ /dev/null @@ -1,23 +0,0 @@ -package org.tasks.jobs; - -import android.content.Context; -import androidx.annotation.NonNull; -import androidx.work.WorkerParameters; -import org.tasks.injection.InjectingWorker; - -public abstract class RepeatingWorker extends InjectingWorker { - - RepeatingWorker(@NonNull Context context, @NonNull WorkerParameters workerParams) { - super(context, workerParams); - } - - @NonNull - @Override - public final Result doWork() { - Result result = super.doWork(); - scheduleNext(); - return result; - } - - protected abstract void scheduleNext(); -} diff --git a/app/src/main/java/org/tasks/jobs/RepeatingWorker.kt b/app/src/main/java/org/tasks/jobs/RepeatingWorker.kt new file mode 100644 index 000000000..1c5f6e1bc --- /dev/null +++ b/app/src/main/java/org/tasks/jobs/RepeatingWorker.kt @@ -0,0 +1,15 @@ +package org.tasks.jobs + +import android.content.Context +import androidx.work.WorkerParameters +import org.tasks.injection.InjectingWorker + +abstract class RepeatingWorker internal constructor(context: Context, workerParams: WorkerParameters) : InjectingWorker(context, workerParams) { + override fun doWork(): Result { + val result = super.doWork() + scheduleNext() + return result + } + + protected abstract fun scheduleNext() +} \ No newline at end of file diff --git a/app/src/main/java/org/tasks/jobs/SyncWork.java b/app/src/main/java/org/tasks/jobs/SyncWork.java deleted file mode 100644 index 60ad06a41..000000000 --- a/app/src/main/java/org/tasks/jobs/SyncWork.java +++ /dev/null @@ -1,94 +0,0 @@ -package org.tasks.jobs; - -import static java.util.concurrent.Executors.newFixedThreadPool; - -import android.content.Context; -import androidx.annotation.NonNull; -import androidx.work.WorkerParameters; -import java.util.List; -import java.util.concurrent.ExecutorService; -import java.util.concurrent.TimeUnit; -import javax.inject.Inject; -import org.tasks.LocalBroadcastManager; -import org.tasks.caldav.CaldavSynchronizer; -import org.tasks.data.CaldavAccount; -import org.tasks.data.CaldavDao; -import org.tasks.data.GoogleTaskAccount; -import org.tasks.data.GoogleTaskListDao; -import org.tasks.etesync.EteSynchronizer; -import org.tasks.gtasks.GoogleTaskSynchronizer; -import org.tasks.injection.ApplicationComponent; -import org.tasks.injection.InjectingWorker; -import org.tasks.preferences.Preferences; -import org.tasks.sync.SyncAdapters; - -public class SyncWork extends InjectingWorker { - - private static final Object LOCK = new Object(); - - @Inject CaldavSynchronizer caldavSynchronizer; - @Inject EteSynchronizer eteSynchronizer; - @Inject GoogleTaskSynchronizer googleTaskSynchronizer; - @Inject LocalBroadcastManager localBroadcastManager; - @Inject Preferences preferences; - @Inject CaldavDao caldavDao; - @Inject GoogleTaskListDao googleTaskListDao; - @Inject SyncAdapters syncAdapters; - - public SyncWork(@NonNull Context context, @NonNull WorkerParameters workerParams) { - super(context, workerParams); - } - - @NonNull - @Override - public Result run() { - if (!syncAdapters.isSyncEnabled()) { - return Result.success(); - } - synchronized (LOCK) { - if (preferences.isSyncOngoing()) { - return Result.retry(); - } - } - preferences.setSyncOngoing(true); - localBroadcastManager.broadcastRefresh(); - try { - sync(); - } catch (Exception e) { - firebase.reportException(e); - } finally { - preferences.setSyncOngoing(false); - localBroadcastManager.broadcastRefresh(); - } - return Result.success(); - } - - private void sync() throws InterruptedException { - int numThreads = Runtime.getRuntime().availableProcessors(); - ExecutorService executor = newFixedThreadPool(numThreads); - - for (CaldavAccount account : caldavDao.getAccounts()) { - executor.execute( - () -> { - if (account.isCaldavAccount()) { - caldavSynchronizer.sync(account); - } else if (account.isEteSyncAccount()) { - eteSynchronizer.sync(account); - } - }); - } - List accounts = googleTaskListDao.getAccounts(); - for (int i = 0; i < accounts.size(); i++) { - int count = i; - executor.execute(() -> googleTaskSynchronizer.sync(accounts.get(count), count)); - } - - executor.shutdown(); - executor.awaitTermination(15, TimeUnit.MINUTES); - } - - @Override - protected void inject(ApplicationComponent component) { - component.inject(this); - } -} diff --git a/app/src/main/java/org/tasks/jobs/SyncWork.kt b/app/src/main/java/org/tasks/jobs/SyncWork.kt new file mode 100644 index 000000000..bc4077199 --- /dev/null +++ b/app/src/main/java/org/tasks/jobs/SyncWork.kt @@ -0,0 +1,77 @@ +package org.tasks.jobs + +import android.content.Context +import androidx.work.WorkerParameters +import org.tasks.LocalBroadcastManager +import org.tasks.caldav.CaldavSynchronizer +import org.tasks.data.CaldavDao +import org.tasks.data.GoogleTaskListDao +import org.tasks.etesync.EteSynchronizer +import org.tasks.gtasks.GoogleTaskSynchronizer +import org.tasks.injection.ApplicationComponent +import org.tasks.injection.InjectingWorker +import org.tasks.preferences.Preferences +import org.tasks.sync.SyncAdapters +import java.util.concurrent.Executors +import java.util.concurrent.TimeUnit +import javax.inject.Inject + +class SyncWork(context: Context, workerParams: WorkerParameters) : InjectingWorker(context, workerParams) { + @Inject lateinit var caldavSynchronizer: CaldavSynchronizer + @Inject lateinit var eteSynchronizer: EteSynchronizer + @Inject lateinit var googleTaskSynchronizer: GoogleTaskSynchronizer + @Inject lateinit var localBroadcastManager: LocalBroadcastManager + @Inject lateinit var preferences: Preferences + @Inject lateinit var caldavDao: CaldavDao + @Inject lateinit var googleTaskListDao: GoogleTaskListDao + @Inject lateinit var syncAdapters: SyncAdapters + + public override fun run(): Result { + if (!syncAdapters.isSyncEnabled) { + return Result.success() + } + synchronized(LOCK) { + if (preferences.isSyncOngoing) { + return Result.retry() + } + } + preferences.isSyncOngoing = true + localBroadcastManager.broadcastRefresh() + try { + sync() + } catch (e: Exception) { + firebase.reportException(e) + } finally { + preferences.isSyncOngoing = false + localBroadcastManager.broadcastRefresh() + } + return Result.success() + } + + @Throws(InterruptedException::class) + private fun sync() { + val numThreads = Runtime.getRuntime().availableProcessors() + val executor = Executors.newFixedThreadPool(numThreads) + for (account in caldavDao.getAccounts()) { + executor.execute { + if (account.isCaldavAccount) { + caldavSynchronizer.sync(account) + } else if (account.isEteSyncAccount) { + eteSynchronizer.sync(account) + } + } + } + val accounts = googleTaskListDao.getAccounts() + for (i in accounts.indices) { + executor.execute { googleTaskSynchronizer.sync(accounts[i], i) } + } + executor.shutdown() + executor.awaitTermination(15, TimeUnit.MINUTES) + } + + override fun inject(component: ApplicationComponent) = component.inject(this) + + companion object { + private val LOCK = Any() + } +} \ No newline at end of file diff --git a/app/src/main/java/org/tasks/jobs/WorkManager.kt b/app/src/main/java/org/tasks/jobs/WorkManager.kt index 254582fd4..9e95435da 100644 --- a/app/src/main/java/org/tasks/jobs/WorkManager.kt +++ b/app/src/main/java/org/tasks/jobs/WorkManager.kt @@ -166,7 +166,7 @@ class WorkManager @Inject constructor( Constraints.Builder().setRequiredNetworkType(NetworkType.CONNECTED).build()) .build()) - fun scheduleDriveUpload(uri: Uri?, purge: Boolean) { + fun scheduleDriveUpload(uri: Uri, purge: Boolean) { if (!preferences.getBoolean(R.string.p_google_drive_backup, false)) { return }