Google Drive updates

* Use work manager for uploads
* Clean up auto backups
pull/795/head
Alex Baker 6 years ago
parent e3ed0934ca
commit faff1dcc08

@ -7,8 +7,6 @@ import android.net.Uri;
import android.os.Handler; import android.os.Handler;
import android.widget.Toast; import android.widget.Toast;
import com.google.api.services.drive.model.File;
import com.google.api.services.drive.model.FileList;
import com.google.common.io.Files; import com.google.common.io.Files;
import com.google.gson.Gson; import com.google.gson.Gson;
import com.google.gson.GsonBuilder; import com.google.gson.GsonBuilder;
@ -31,7 +29,7 @@ import org.tasks.data.TaskAttachmentDao;
import org.tasks.data.UserActivityDao; import org.tasks.data.UserActivityDao;
import org.tasks.drive.DriveInvoker; import org.tasks.drive.DriveInvoker;
import org.tasks.files.FileHelper; import org.tasks.files.FileHelper;
import org.tasks.gtasks.PlayServices; import org.tasks.jobs.WorkManager;
import org.tasks.preferences.Preferences; import org.tasks.preferences.Preferences;
import java.io.IOException; import java.io.IOException;
@ -66,7 +64,7 @@ public class TasksJsonExporter {
private final GoogleTaskListDao googleTaskListDao; private final GoogleTaskListDao googleTaskListDao;
private final TaskAttachmentDao taskAttachmentDao; private final TaskAttachmentDao taskAttachmentDao;
private final CaldavDao caldavDao; private final CaldavDao caldavDao;
private final DriveInvoker driveInvoker; private final WorkManager workManager;
private final TaskDao taskDao; private final TaskDao taskDao;
private final UserActivityDao userActivityDao; private final UserActivityDao userActivityDao;
private final Preferences preferences; private final Preferences preferences;
@ -90,6 +88,7 @@ public class TasksJsonExporter {
GoogleTaskListDao googleTaskListDao, GoogleTaskListDao googleTaskListDao,
TaskAttachmentDao taskAttachmentDao, TaskAttachmentDao taskAttachmentDao,
CaldavDao caldavDao, CaldavDao caldavDao,
WorkManager workManager,
DriveInvoker driveInvoker) { DriveInvoker driveInvoker) {
this.tagDataDao = tagDataDao; this.tagDataDao = tagDataDao;
this.taskDao = taskDao; this.taskDao = taskDao;
@ -103,7 +102,7 @@ public class TasksJsonExporter {
this.googleTaskListDao = googleTaskListDao; this.googleTaskListDao = googleTaskListDao;
this.taskAttachmentDao = taskAttachmentDao; this.taskAttachmentDao = taskAttachmentDao;
this.caldavDao = caldavDao; this.caldavDao = caldavDao;
this.driveInvoker = driveInvoker; this.workManager = workManager;
} }
private static String getDateForExport() { private static String getDateForExport() {
@ -154,13 +153,7 @@ public class TasksJsonExporter {
OutputStream os = context.getContentResolver().openOutputStream(uri); OutputStream os = context.getContentResolver().openOutputStream(uri);
doTasksExport(os, tasks); doTasksExport(os, tasks);
os.close(); os.close();
if (preferences.getBoolean(R.string.p_google_drive_backup, false)) { workManager.scheduleDriveUpload(uri);
List<File> files = driveInvoker.findFolder("org.tasks");
File folder = files.isEmpty()
? driveInvoker.createFolder("org.tasks")
: files.get(0);
driveInvoker.createFile(MIME, folder.getId(), FileHelper.getFilename(context, uri), uri);
}
} }
if (exportType == ExportType.EXPORT_TYPE_MANUAL) { if (exportType == ExportType.EXPORT_TYPE_MANUAL) {

@ -16,6 +16,7 @@ import com.google.api.services.drive.model.File;
import org.tasks.BuildConfig; import org.tasks.BuildConfig;
import org.tasks.R; import org.tasks.R;
import org.tasks.files.FileHelper;
import org.tasks.injection.ForApplication; import org.tasks.injection.ForApplication;
import org.tasks.preferences.Preferences; import org.tasks.preferences.Preferences;
@ -29,6 +30,8 @@ import timber.log.Timber;
public class DriveInvoker { public class DriveInvoker {
private static final String MIME_FOLDER = "application/vnd.google-apps.folder";
private final Drive service; private final Drive service;
private final Context context; private final Context context;
@ -51,8 +54,19 @@ public class DriveInvoker {
} }
} }
public List<File> findFolder(String name) throws IOException { public File getFile(String folderId) throws IOException {
String query = String.format("name='%s'", name); return execute(service.files().get(folderId).setFields("id, trashed"));
}
public void delete(File file) throws IOException {
execute(service.files().delete(file.getId()));
}
public List<File> getFilesByPrefix(String folderId, String prefix) throws IOException {
String query =
String.format(
"'%s' in parents and name contains '%s' and trashed = false and mimeType != '%s'",
folderId, prefix, MIME_FOLDER);
return execute(service.files().list().setQ(query).setSpaces("drive")).getFiles(); return execute(service.files().list().setQ(query).setSpaces("drive")).getFiles();
} }
@ -64,11 +78,12 @@ public class DriveInvoker {
return execute(service.files().create(folder).setFields("id")); return execute(service.files().create(folder).setFields("id"));
} }
public void createFile(String mime, String parent, String name, Uri uri) throws IOException { public void createFile(String folderId, Uri uri) throws IOException {
String mime = FileHelper.getMimeType(context, uri);
File metadata = new File() File metadata = new File()
.setParents(Collections.singletonList(parent)) .setParents(Collections.singletonList(folderId))
.setMimeType(mime) .setMimeType(mime)
.setName(name); .setName(FileHelper.getFilename(context, uri));
InputStreamContent content = InputStreamContent content =
new InputStreamContent(mime, context.getContentResolver().openInputStream(uri)); new InputStreamContent(mime, context.getContentResolver().openInputStream(uri));
execute(service.files().create(metadata, content)); execute(service.files().create(metadata, content));

@ -134,15 +134,18 @@ public class FileHelper {
return null; return null;
} }
public static String getMimeType(Context context, Uri uri) {
String filename = getFilename(context, uri);
String extension = Files.getFileExtension(filename);
return MimeTypeMap.getSingleton().getMimeTypeFromExtension(extension);
}
public static void startActionView(Activity context, Uri uri) { public static void startActionView(Activity context, Uri uri) {
if (uri == null) { if (uri == null) {
return; return;
} }
MimeTypeMap mimeTypeMap = MimeTypeMap.getSingleton(); String mimeType = getMimeType(context, uri);
String filename = getFilename(context, uri);
String extension = Files.getFileExtension(filename);
String mimeType = mimeTypeMap.getMimeTypeFromExtension(extension);
Intent intent = new Intent(Intent.ACTION_VIEW); Intent intent = new Intent(Intent.ACTION_VIEW);
if (uri.getScheme().equals(ContentResolver.SCHEME_CONTENT)) { if (uri.getScheme().equals(ContentResolver.SCHEME_CONTENT)) {
uri = copyToUri(context, Uri.fromFile(context.getCacheDir()), uri); uri = copyToUri(context, Uri.fromFile(context.getCacheDir()), uri);

@ -4,6 +4,7 @@ import dagger.Subcomponent;
import org.tasks.jobs.AfterSaveWork; import org.tasks.jobs.AfterSaveWork;
import org.tasks.jobs.BackupWork; import org.tasks.jobs.BackupWork;
import org.tasks.jobs.CleanupWork; import org.tasks.jobs.CleanupWork;
import org.tasks.jobs.DriveUploader;
import org.tasks.jobs.MidnightRefreshWork; import org.tasks.jobs.MidnightRefreshWork;
import org.tasks.jobs.NotificationWork; import org.tasks.jobs.NotificationWork;
import org.tasks.jobs.RefreshWork; import org.tasks.jobs.RefreshWork;
@ -25,4 +26,6 @@ public interface JobComponent {
void inject(MidnightRefreshWork midnightRefreshWork); void inject(MidnightRefreshWork midnightRefreshWork);
void inject(AfterSaveWork afterSaveWork); void inject(AfterSaveWork afterSaveWork);
void inject(DriveUploader driveUploader);
} }

@ -4,15 +4,18 @@ import android.content.Context;
import android.net.Uri; import android.net.Uri;
import com.google.common.base.Predicate; import com.google.common.base.Predicate;
import com.google.common.base.Strings;
import org.tasks.R; import org.tasks.R;
import org.tasks.backup.TasksJsonExporter; import org.tasks.backup.TasksJsonExporter;
import org.tasks.drive.DriveInvoker;
import org.tasks.injection.ForApplication; import org.tasks.injection.ForApplication;
import org.tasks.injection.JobComponent; import org.tasks.injection.JobComponent;
import org.tasks.preferences.Preferences; import org.tasks.preferences.Preferences;
import java.io.File; import java.io.File;
import java.io.FileFilter; import java.io.FileFilter;
import java.io.IOException;
import java.util.Arrays; import java.util.Arrays;
import java.util.Collections; import java.util.Collections;
import java.util.Comparator; import java.util.Comparator;
@ -40,12 +43,15 @@ public class BackupWork extends RepeatingWorker {
(f1, f2) -> Long.compare(f2.lastModified(), f1.lastModified()); (f1, f2) -> Long.compare(f2.lastModified(), f1.lastModified());
private static final Comparator<DocumentFile> DOCUMENT_FILE_COMPARATOR = private static final Comparator<DocumentFile> DOCUMENT_FILE_COMPARATOR =
(d1, d2) -> Long.compare(d2.lastModified(), d1.lastModified()); (d1, d2) -> Long.compare(d2.lastModified(), d1.lastModified());
private static final Comparator<com.google.api.services.drive.model.File> DRIVE_FILE_COMPARATOR =
(f1, f2) -> Long.compare(f2.getModifiedTime().getValue(), f1.getModifiedTime().getValue());
private static final int DAYS_TO_KEEP_BACKUP = 7; private static final int DAYS_TO_KEEP_BACKUP = 7;
@Inject @ForApplication Context context; @Inject @ForApplication Context context;
@Inject TasksJsonExporter tasksJsonExporter; @Inject TasksJsonExporter tasksJsonExporter;
@Inject Preferences preferences; @Inject Preferences preferences;
@Inject WorkManager workManager; @Inject WorkManager workManager;
@Inject DriveInvoker drive;
public BackupWork(@NonNull Context context, @NonNull WorkerParameters workerParams) { public BackupWork(@NonNull Context context, @NonNull WorkerParameters workerParams) {
super(context, workerParams); super(context, workerParams);
@ -84,6 +90,11 @@ public class BackupWork extends RepeatingWorker {
return newArrayList(skip(files, DAYS_TO_KEEP_BACKUP)); return newArrayList(skip(files, DAYS_TO_KEEP_BACKUP));
} }
private static List<com.google.api.services.drive.model.File> getDeleteList(List<com.google.api.services.drive.model.File> files) {
Collections.sort(files, DRIVE_FILE_COMPARATOR);
return newArrayList(skip(files, DAYS_TO_KEEP_BACKUP));
}
@Override @Override
protected void inject(JobComponent component) { protected void inject(JobComponent component) {
component.inject(this); component.inject(this);
@ -91,7 +102,13 @@ public class BackupWork extends RepeatingWorker {
void startBackup(Context context) { void startBackup(Context context) {
try { try {
deleteOldBackups(); deleteOldLocalBackups();
} catch (Exception e) {
Timber.e(e);
}
try {
deleteOldDriveBackups();
} catch (Exception e) { } catch (Exception e) {
Timber.e(e); Timber.e(e);
} }
@ -104,7 +121,7 @@ public class BackupWork extends RepeatingWorker {
} }
} }
private void deleteOldBackups() { private void deleteOldLocalBackups() {
Uri uri = preferences.getBackupDirectory(); Uri uri = preferences.getBackupDirectory();
switch (uri.getScheme()) { switch (uri.getScheme()) {
case "content": case "content":
@ -126,4 +143,21 @@ public class BackupWork extends RepeatingWorker {
break; break;
} }
} }
private void deleteOldDriveBackups() throws IOException {
if (!preferences.getBoolean(R.string.p_google_drive_backup, false)) {
return;
}
String folderId = preferences.getStringValue(R.string.p_google_drive_backup_folder);
if (Strings.isNullOrEmpty(folderId)) {
return;
}
List<com.google.api.services.drive.model.File> files = drive.getFilesByPrefix(folderId, "auto.");
for (com.google.api.services.drive.model.File file : getDeleteList(files)) {
drive.delete(file);
}
}
} }

@ -0,0 +1,68 @@
package org.tasks.jobs;
import android.content.Context;
import android.net.Uri;
import com.google.api.services.drive.model.File;
import com.google.common.base.Strings;
import org.tasks.R;
import org.tasks.analytics.Tracker;
import org.tasks.drive.DriveInvoker;
import org.tasks.injection.ForApplication;
import org.tasks.injection.InjectingWorker;
import org.tasks.injection.JobComponent;
import org.tasks.preferences.Preferences;
import java.io.IOException;
import javax.inject.Inject;
import androidx.annotation.NonNull;
import androidx.work.Data;
import androidx.work.WorkerParameters;
public class DriveUploader extends InjectingWorker {
private static final String FOLDER_NAME = "Tasks Backups";
private static final String EXTRA_URI = "extra_uri";
@Inject @ForApplication Context context;
@Inject DriveInvoker drive;
@Inject Preferences preferences;
@Inject Tracker tracker;
static Data getInputData(Uri uri) {
return new Data.Builder().putString(EXTRA_URI, uri.toString()).build();
}
public DriveUploader(@NonNull Context context, @NonNull WorkerParameters workerParams) {
super(context, workerParams);
}
@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);
return Result.SUCCESS;
} catch (IOException e) {
tracker.reportException(e);
return Result.FAILURE;
}
}
private File getFolder() throws IOException {
String folderId = preferences.getStringValue(R.string.p_google_drive_backup_folder);
File file = Strings.isNullOrEmpty(folderId) ? null : drive.getFile(folderId);
return file == null || file.getTrashed() ? drive.createFolder(FOLDER_NAME) : file;
}
@Override
protected void inject(JobComponent component) {
component.inject(this);
}
}

@ -1,5 +1,7 @@
package org.tasks.jobs; package org.tasks.jobs;
import android.net.Uri;
import static com.todoroo.andlib.utility.DateUtilities.now; import static com.todoroo.andlib.utility.DateUtilities.now;
import static org.tasks.date.DateTimeUtils.midnight; import static org.tasks.date.DateTimeUtils.midnight;
import static org.tasks.date.DateTimeUtils.newDateTime; import static org.tasks.date.DateTimeUtils.newDateTime;
@ -119,11 +121,7 @@ public class WorkManager {
ExistingPeriodicWorkPolicy.KEEP, ExistingPeriodicWorkPolicy.KEEP,
new PeriodicWorkRequest.Builder(SyncWork.class, 1, TimeUnit.HOURS) new PeriodicWorkRequest.Builder(SyncWork.class, 1, TimeUnit.HOURS)
.setBackoffCriteria(BackoffPolicy.EXPONENTIAL, 1, TimeUnit.MINUTES) .setBackoffCriteria(BackoffPolicy.EXPONENTIAL, 1, TimeUnit.MINUTES)
.setConstraints( .setConstraints(getNetworkConstraints(onlyOnUnmetered))
new Constraints.Builder()
.setRequiredNetworkType(
onlyOnUnmetered ? NetworkType.UNMETERED : NetworkType.CONNECTED)
.build())
.build()); .build());
} else { } else {
workManager.cancelUniqueWork(TAG_BACKGROUND_SYNC); workManager.cancelUniqueWork(TAG_BACKGROUND_SYNC);
@ -151,6 +149,28 @@ public class WorkManager {
Math.min(newDateTime(lastBackup).plusDays(1).getMillis(), midnight())); Math.min(newDateTime(lastBackup).plusDays(1).getMillis(), midnight()));
} }
public void scheduleDriveUpload(Uri uri) {
if (!preferences.getBoolean(R.string.p_google_drive_backup, false)) {
return;
}
workManager.enqueue(
new Builder(DriveUploader.class)
.setInputData(DriveUploader.getInputData(uri))
.setConstraints(getNetworkConstraints())
.build());
}
private Constraints getNetworkConstraints() {
return getNetworkConstraints(preferences.getBoolean(R.string.p_background_sync_unmetered_only, false));
}
private Constraints getNetworkConstraints(boolean unmeteredOnly) {
return new Constraints.Builder()
.setRequiredNetworkType(unmeteredOnly ? NetworkType.UNMETERED : NetworkType.CONNECTED)
.build();
}
private void enqueueUnique(String key, Class<? extends Worker> c, long time) { private void enqueueUnique(String key, Class<? extends Worker> c, long time) {
long delay = time - now(); long delay = time - now();
OneTimeWorkRequest.Builder builder = new Builder(c); OneTimeWorkRequest.Builder builder = new Builder(c);

@ -21,6 +21,7 @@
<string name="p_backup_dir">p_backup_dir</string> <string name="p_backup_dir">p_backup_dir</string>
<string name="p_google_drive_backup">p_google_drive_backup</string> <string name="p_google_drive_backup">p_google_drive_backup</string>
<string name="p_google_drive_backup_account">p_google_drive_backup_account</string> <string name="p_google_drive_backup_account">p_google_drive_backup_account</string>
<string name="p_google_drive_backup_folder">p_google_drive_backup_folder</string>
<string name="p_rmd_enabled">notif_enabled</string> <string name="p_rmd_enabled">notif_enabled</string>
<!-- boolean : whether to enable quiet hours or not --> <!-- boolean : whether to enable quiet hours or not -->
<string name="p_rmd_enable_quiet">enable_qhours</string> <string name="p_rmd_enable_quiet">enable_qhours</string>

Loading…
Cancel
Save