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.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.gson.Gson;
import com.google.gson.GsonBuilder;
@ -31,7 +29,7 @@ import org.tasks.data.TaskAttachmentDao;
import org.tasks.data.UserActivityDao;
import org.tasks.drive.DriveInvoker;
import org.tasks.files.FileHelper;
import org.tasks.gtasks.PlayServices;
import org.tasks.jobs.WorkManager;
import org.tasks.preferences.Preferences;
import java.io.IOException;
@ -66,7 +64,7 @@ public class TasksJsonExporter {
private final GoogleTaskListDao googleTaskListDao;
private final TaskAttachmentDao taskAttachmentDao;
private final CaldavDao caldavDao;
private final DriveInvoker driveInvoker;
private final WorkManager workManager;
private final TaskDao taskDao;
private final UserActivityDao userActivityDao;
private final Preferences preferences;
@ -90,6 +88,7 @@ public class TasksJsonExporter {
GoogleTaskListDao googleTaskListDao,
TaskAttachmentDao taskAttachmentDao,
CaldavDao caldavDao,
WorkManager workManager,
DriveInvoker driveInvoker) {
this.tagDataDao = tagDataDao;
this.taskDao = taskDao;
@ -103,7 +102,7 @@ public class TasksJsonExporter {
this.googleTaskListDao = googleTaskListDao;
this.taskAttachmentDao = taskAttachmentDao;
this.caldavDao = caldavDao;
this.driveInvoker = driveInvoker;
this.workManager = workManager;
}
private static String getDateForExport() {
@ -154,13 +153,7 @@ public class TasksJsonExporter {
OutputStream os = context.getContentResolver().openOutputStream(uri);
doTasksExport(os, tasks);
os.close();
if (preferences.getBoolean(R.string.p_google_drive_backup, false)) {
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);
}
workManager.scheduleDriveUpload(uri);
}
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.R;
import org.tasks.files.FileHelper;
import org.tasks.injection.ForApplication;
import org.tasks.preferences.Preferences;
@ -29,6 +30,8 @@ import timber.log.Timber;
public class DriveInvoker {
private static final String MIME_FOLDER = "application/vnd.google-apps.folder";
private final Drive service;
private final Context context;
@ -51,8 +54,19 @@ public class DriveInvoker {
}
}
public List<File> findFolder(String name) throws IOException {
String query = String.format("name='%s'", name);
public File getFile(String folderId) throws IOException {
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();
}
@ -64,11 +78,12 @@ public class DriveInvoker {
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()
.setParents(Collections.singletonList(parent))
.setParents(Collections.singletonList(folderId))
.setMimeType(mime)
.setName(name);
.setName(FileHelper.getFilename(context, uri));
InputStreamContent content =
new InputStreamContent(mime, context.getContentResolver().openInputStream(uri));
execute(service.files().create(metadata, content));

@ -134,15 +134,18 @@ public class FileHelper {
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) {
if (uri == null) {
return;
}
MimeTypeMap mimeTypeMap = MimeTypeMap.getSingleton();
String filename = getFilename(context, uri);
String extension = Files.getFileExtension(filename);
String mimeType = mimeTypeMap.getMimeTypeFromExtension(extension);
String mimeType = getMimeType(context, uri);
Intent intent = new Intent(Intent.ACTION_VIEW);
if (uri.getScheme().equals(ContentResolver.SCHEME_CONTENT)) {
uri = copyToUri(context, Uri.fromFile(context.getCacheDir()), uri);

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

@ -4,15 +4,18 @@ import android.content.Context;
import android.net.Uri;
import com.google.common.base.Predicate;
import com.google.common.base.Strings;
import org.tasks.R;
import org.tasks.backup.TasksJsonExporter;
import org.tasks.drive.DriveInvoker;
import org.tasks.injection.ForApplication;
import org.tasks.injection.JobComponent;
import org.tasks.preferences.Preferences;
import java.io.File;
import java.io.FileFilter;
import java.io.IOException;
import java.util.Arrays;
import java.util.Collections;
import java.util.Comparator;
@ -40,12 +43,15 @@ public class BackupWork extends RepeatingWorker {
(f1, f2) -> Long.compare(f2.lastModified(), f1.lastModified());
private static final Comparator<DocumentFile> DOCUMENT_FILE_COMPARATOR =
(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;
@Inject @ForApplication Context context;
@Inject TasksJsonExporter tasksJsonExporter;
@Inject Preferences preferences;
@Inject WorkManager workManager;
@Inject DriveInvoker drive;
public BackupWork(@NonNull Context context, @NonNull WorkerParameters workerParams) {
super(context, workerParams);
@ -84,6 +90,11 @@ public class BackupWork extends RepeatingWorker {
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
protected void inject(JobComponent component) {
component.inject(this);
@ -91,7 +102,13 @@ public class BackupWork extends RepeatingWorker {
void startBackup(Context context) {
try {
deleteOldBackups();
deleteOldLocalBackups();
} catch (Exception e) {
Timber.e(e);
}
try {
deleteOldDriveBackups();
} catch (Exception e) {
Timber.e(e);
}
@ -104,7 +121,7 @@ public class BackupWork extends RepeatingWorker {
}
}
private void deleteOldBackups() {
private void deleteOldLocalBackups() {
Uri uri = preferences.getBackupDirectory();
switch (uri.getScheme()) {
case "content":
@ -126,4 +143,21 @@ public class BackupWork extends RepeatingWorker {
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;
import android.net.Uri;
import static com.todoroo.andlib.utility.DateUtilities.now;
import static org.tasks.date.DateTimeUtils.midnight;
import static org.tasks.date.DateTimeUtils.newDateTime;
@ -119,11 +121,7 @@ public class WorkManager {
ExistingPeriodicWorkPolicy.KEEP,
new PeriodicWorkRequest.Builder(SyncWork.class, 1, TimeUnit.HOURS)
.setBackoffCriteria(BackoffPolicy.EXPONENTIAL, 1, TimeUnit.MINUTES)
.setConstraints(
new Constraints.Builder()
.setRequiredNetworkType(
onlyOnUnmetered ? NetworkType.UNMETERED : NetworkType.CONNECTED)
.build())
.setConstraints(getNetworkConstraints(onlyOnUnmetered))
.build());
} else {
workManager.cancelUniqueWork(TAG_BACKGROUND_SYNC);
@ -151,6 +149,28 @@ public class WorkManager {
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) {
long delay = time - now();
OneTimeWorkRequest.Builder builder = new Builder(c);

@ -21,6 +21,7 @@
<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_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>
<!-- boolean : whether to enable quiet hours or not -->
<string name="p_rmd_enable_quiet">enable_qhours</string>

Loading…
Cancel
Save