Convert workers to Kotlin

pull/1020/head
Alex Baker 4 years ago
parent cde5bcfb87
commit 641b60be9b

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

@ -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()
}
}
}

@ -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<String> FILENAME_FILTER = f -> f.matches(BACKUP_FILE_NAME_REGEX);
static final FileFilter FILE_FILTER = f -> FILENAME_FILTER.apply(f.getName());
private static final Comparator<File> BY_LAST_MODIFIED =
(f1, f2) -> Long.compare(f2.lastModified(), f1.lastModified());
private static final Comparator<DocumentFile> 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<File> getDeleteList(File[] fileArray, int keepNewest) {
if (fileArray == null) {
return emptyList();
}
List<File> files = Arrays.asList(fileArray);
Collections.sort(files, BY_LAST_MODIFIED);
return newArrayList(skip(files, keepNewest));
}
private static List<DocumentFile> getDeleteList(DocumentFile[] fileArray) {
if (fileArray == null) {
return emptyList();
}
List<DocumentFile> 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;
}
}
}

@ -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<File>?, keepNewest: Int) =
fileArray?.sortedWith(BY_LAST_MODIFIED)?.drop(keepNewest) ?: emptyList()
private fun getDeleteList(fileArray: Array<DocumentFile>?) =
fileArray
?.filter { FILENAME_FILTER.invoke(it.name!!) }
?.sortedWith(DOCUMENT_FILE_COMPARATOR)
?.drop(DAYS_TO_KEEP_BACKUP)
?: emptyList()
}
}

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

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

@ -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<File> 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<File> getDeleteList(List<File> 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<File> 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);
}
}

@ -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<File> { 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<File>) =
files.sortedWith(DRIVE_FILE_COMPARATOR).drop(BackupWork.DAYS_TO_KEEP_BACKUP)
}
}

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

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

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

@ -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()
}

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

@ -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()
}

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

@ -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()
}
}

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

Loading…
Cancel
Save