diff --git a/app/src/main/java/org/tasks/drive/DriveInvoker.java b/app/src/main/java/org/tasks/drive/DriveInvoker.java deleted file mode 100644 index 95ffc775a..000000000 --- a/app/src/main/java/org/tasks/drive/DriveInvoker.java +++ /dev/null @@ -1,146 +0,0 @@ -package org.tasks.drive; - -import static com.todoroo.andlib.utility.DateUtilities.now; - -import android.content.Context; -import android.net.Uri; -import com.google.api.client.http.HttpResponse; -import com.google.api.client.http.HttpResponseException; -import com.google.api.client.http.InputStreamContent; -import com.google.api.client.http.javanet.NetHttpTransport; -import com.google.api.client.json.GenericJson; -import com.google.api.client.json.jackson2.JacksonFactory; -import com.google.api.services.drive.Drive; -import com.google.api.services.drive.DriveRequest; -import com.google.api.services.drive.DriveScopes; -import com.google.api.services.drive.model.File; -import com.todoroo.astrid.gtasks.api.HttpCredentialsAdapter; -import dagger.hilt.android.qualifiers.ApplicationContext; -import java.io.IOException; -import java.util.Collections; -import java.util.List; -import javax.inject.Inject; -import org.tasks.BuildConfig; -import org.tasks.DebugNetworkInterceptor; -import org.tasks.R; -import org.tasks.files.FileHelper; -import org.tasks.preferences.Preferences; -import timber.log.Timber; - -public class DriveInvoker { - - private static final String MIME_FOLDER = "application/vnd.google-apps.folder"; - - private final Context context; - private final Preferences preferences; - private final DebugNetworkInterceptor interceptor; - private final Drive service; - private final HttpCredentialsAdapter credentialsAdapter; - - @Inject - public DriveInvoker( - @ApplicationContext Context context, - Preferences preferences, - HttpCredentialsAdapter credentialsAdapter, - DebugNetworkInterceptor interceptor) { - this.context = context; - this.preferences = preferences; - this.credentialsAdapter = credentialsAdapter; - this.interceptor = interceptor; - - service = - new Drive.Builder(new NetHttpTransport(), new JacksonFactory(), credentialsAdapter) - .setApplicationName(String.format("Tasks/%s", BuildConfig.VERSION_NAME)) - .build(); - } - - 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 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") - .setFields("files(id, modifiedTime)")) - .getFiles(); - } - - public File createFolder(String name) throws IOException { - File folder = new File().setName(name).setMimeType("application/vnd.google-apps.folder"); - - return execute(service.files().create(folder).setFields("id")); - } - - public void createFile(String folderId, Uri uri) throws IOException { - String mime = FileHelper.getMimeType(context, uri); - File metadata = - new File() - .setParents(Collections.singletonList(folderId)) - .setMimeType(mime) - .setName(FileHelper.getFilename(context, uri)); - InputStreamContent content = - new InputStreamContent(mime, context.getContentResolver().openInputStream(uri)); - execute(service.files().create(metadata, content)); - } - - private synchronized T execute(DriveRequest request) throws IOException { - return execute(request, false); - } - - private synchronized T execute(DriveRequest request, boolean retry) throws IOException { - String account = preferences.getStringValue(R.string.p_google_drive_backup_account); - credentialsAdapter.checkToken(account, DriveScopes.DRIVE_FILE); - Timber.d("%s request: %s", getCaller(), request); - T response; - try { - if (preferences.isFlipperEnabled()) { - long start = now(); - HttpResponse httpResponse = request.executeUnparsed(); - response = interceptor.report(httpResponse, request.getResponseClass(), start, now()); - } else { - response = request.execute(); - } - } catch (HttpResponseException e) { - if (e.getStatusCode() == 401 && !retry) { - credentialsAdapter.invalidateToken(); - return execute(request, true); - } else { - throw e; - } - } - Timber.d("%s response: %s", getCaller(), prettyPrint(response)); - return response; - } - - private Object prettyPrint(T object) throws IOException { - if (BuildConfig.DEBUG) { - if (object instanceof GenericJson) { - return ((GenericJson) object).toPrettyString(); - } - } - return object; - } - - private String getCaller() { - if (BuildConfig.DEBUG) { - try { - return Thread.currentThread().getStackTrace()[4].getMethodName(); - } catch (Exception e) { - Timber.e(e); - } - } - return ""; - } -} diff --git a/app/src/main/java/org/tasks/drive/DriveInvoker.kt b/app/src/main/java/org/tasks/drive/DriveInvoker.kt new file mode 100644 index 000000000..0e5ddc825 --- /dev/null +++ b/app/src/main/java/org/tasks/drive/DriveInvoker.kt @@ -0,0 +1,143 @@ +package org.tasks.drive + +import android.content.Context +import android.net.Uri +import com.google.api.client.http.HttpResponseException +import com.google.api.client.http.InputStreamContent +import com.google.api.client.http.javanet.NetHttpTransport +import com.google.api.client.json.GenericJson +import com.google.api.client.json.jackson2.JacksonFactory +import com.google.api.services.drive.Drive +import com.google.api.services.drive.DriveRequest +import com.google.api.services.drive.DriveScopes +import com.google.api.services.drive.model.File +import com.todoroo.andlib.utility.DateUtilities +import com.todoroo.astrid.gtasks.api.HttpCredentialsAdapter +import dagger.hilt.android.qualifiers.ApplicationContext +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.withContext +import org.tasks.BuildConfig +import org.tasks.DebugNetworkInterceptor +import org.tasks.R +import org.tasks.files.FileHelper +import org.tasks.preferences.Preferences +import timber.log.Timber +import java.io.IOException +import javax.inject.Inject + +class DriveInvoker @Inject constructor( + @param:ApplicationContext private val context: Context, + private val preferences: Preferences, + private val credentialsAdapter: HttpCredentialsAdapter, + private val interceptor: DebugNetworkInterceptor) { + private val service = + Drive + .Builder(NetHttpTransport(), JacksonFactory(), credentialsAdapter) + .setApplicationName(String.format("Tasks/%s", BuildConfig.VERSION_NAME)) + .build() + + @Throws(IOException::class) + suspend fun getFile(folderId: String?): File? { + return execute(service.files()[folderId].setFields("id, trashed")) + } + + @Throws(IOException::class) + suspend fun delete(file: File) { + execute(service.files().delete(file.id)) + } + + @Throws(IOException::class) + suspend fun getFilesByPrefix(folderId: String?, prefix: String?): List { + val 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") + .setFields("files(id, modifiedTime)")) + ?.files + ?: emptyList() + } + + @Throws(IOException::class) + suspend fun createFolder(name: String?): File? { + val folder = File().setName(name).setMimeType("application/vnd.google-apps.folder") + return execute(service.files().create(folder).setFields("id")) + } + + @Throws(IOException::class) + suspend fun createFile(folderId: String, uri: Uri?) { + val mime = FileHelper.getMimeType(context, uri) + val metadata = File() + .setParents(listOf(folderId)) + .setMimeType(mime) + .setName(FileHelper.getFilename(context, uri)) + val content = InputStreamContent(mime, context.contentResolver.openInputStream(uri!!)) + execute(service.files().create(metadata, content)) + } + + @Synchronized + @Throws(IOException::class) + private suspend fun execute(request: DriveRequest): T? { + return execute(request, false) + } + + @Synchronized + @Throws(IOException::class) + private suspend fun execute( + request: DriveRequest, + retry: Boolean + ): T? = withContext(Dispatchers.IO) { + val account = preferences.getStringValue(R.string.p_google_drive_backup_account) + credentialsAdapter.checkToken(account, DriveScopes.DRIVE_FILE) + Timber.d("%s request: %s", caller, request) + val response: T? + response = try { + if (preferences.isFlipperEnabled) { + val start = DateUtilities.now() + val httpResponse = request.executeUnparsed() + interceptor.report(httpResponse, request.responseClass, start, DateUtilities.now()) + } else { + request.execute() + } + } catch (e: HttpResponseException) { + return@withContext if (e.statusCode == 401 && !retry) { + credentialsAdapter.invalidateToken() + execute(request, true) + } else { + throw e + } + } + Timber.d("%s response: %s", caller, prettyPrint(response)) + return@withContext response + } + + @Throws(IOException::class) + private fun prettyPrint(`object`: T?): Any? { + if (BuildConfig.DEBUG) { + if (`object` is GenericJson) { + return (`object` as GenericJson).toPrettyString() + } + } + return `object` + } + + private val caller: String + get() { + if (BuildConfig.DEBUG) { + try { + return Thread.currentThread().stackTrace[4].methodName + } catch (e: Exception) { + Timber.e(e) + } + } + return "" + } + + companion object { + private const val MIME_FOLDER = "application/vnd.google-apps.folder" + } +} \ No newline at end of file diff --git a/app/src/main/java/org/tasks/jobs/DriveUploader.kt b/app/src/main/java/org/tasks/jobs/DriveUploader.kt index 8f39034f7..11095df00 100644 --- a/app/src/main/java/org/tasks/jobs/DriveUploader.kt +++ b/app/src/main/java/org/tasks/jobs/DriveUploader.kt @@ -33,7 +33,7 @@ class DriveUploader @WorkerInject constructor( val inputData = inputData val uri = Uri.parse(inputData.getString(EXTRA_URI)) return try { - val folder = folder + val folder = getFolder() ?: return Result.failure() preferences.setString(R.string.p_google_drive_backup_folder, folder.id) drive.createFile(folder.id, uri) if (inputData.getBoolean(EXTRA_PURGE, false)) { @@ -69,22 +69,21 @@ class DriveUploader @WorkerInject constructor( } } - @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 - } + @Throws(IOException::class) + private suspend fun getFolder(): File? { + 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 } + return if (file == null || file.trashed) drive.createFolder(FOLDER_NAME) else file + } companion object { private const val FOLDER_NAME = "Tasks Backups"