diff --git a/app/src/main/java/com/todoroo/astrid/gtasks/api/GtasksInvoker.java b/app/src/main/java/com/todoroo/astrid/gtasks/api/GtasksInvoker.java deleted file mode 100644 index 9c1f0e295..000000000 --- a/app/src/main/java/com/todoroo/astrid/gtasks/api/GtasksInvoker.java +++ /dev/null @@ -1,198 +0,0 @@ -package com.todoroo.astrid.gtasks.api; - -import android.content.Context; -import androidx.annotation.Nullable; -import com.google.api.client.http.HttpRequest; -import com.google.api.client.http.HttpResponseException; -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.tasks.Tasks; -import com.google.api.services.tasks.TasksRequest; -import com.google.api.services.tasks.TasksScopes; -import com.google.api.services.tasks.model.Task; -import com.google.api.services.tasks.model.TaskList; -import com.google.api.services.tasks.model.TaskLists; -import dagger.hilt.android.qualifiers.ApplicationContext; -import java.io.IOException; -import javax.inject.Inject; -import org.tasks.BuildConfig; -import org.tasks.DebugNetworkInterceptor; -import org.tasks.gtasks.GoogleAccountManager; -import org.tasks.preferences.Preferences; -import timber.log.Timber; - -/** - * Wrapper around the official Google Tasks API to simplify common operations. In the case of an - * exception, each request is tried twice in case of a timeout. - * - * @author Sam Bosley - */ -public class GtasksInvoker { - - private final Context context; - private final GoogleAccountManager googleAccountManager; - private final Preferences preferences; - private final DebugNetworkInterceptor interceptor; - private final String account; - private final Tasks service; - private final HttpCredentialsAdapter credentialsAdapter; - - @Inject - public GtasksInvoker( - @ApplicationContext Context context, - GoogleAccountManager googleAccountManager, - Preferences preferences, - DebugNetworkInterceptor interceptor) { - this.context = context; - this.googleAccountManager = googleAccountManager; - this.preferences = preferences; - this.interceptor = interceptor; - account = null; - service = null; - credentialsAdapter = null; - } - - private GtasksInvoker( - Context context, - GoogleAccountManager googleAccountManager, - Preferences preferences, - DebugNetworkInterceptor interceptor, - String account) { - this.context = context; - this.googleAccountManager = googleAccountManager; - this.preferences = preferences; - this.interceptor = interceptor; - this.account = account; - - credentialsAdapter = new HttpCredentialsAdapter(googleAccountManager); - service = - new Tasks.Builder(new NetHttpTransport(), new JacksonFactory(), credentialsAdapter) - .setApplicationName(String.format("Tasks/%s", BuildConfig.VERSION_NAME)) - .build(); - } - - public GtasksInvoker forAccount(String account) { - return new GtasksInvoker(context, googleAccountManager, preferences, interceptor, account); - } - - public @Nullable TaskLists allGtaskLists(@Nullable String pageToken) throws IOException { - return execute(service.tasklists().list().setMaxResults(100L).setPageToken(pageToken)); - } - - public @Nullable com.google.api.services.tasks.model.Tasks getAllGtasksFromListId( - String listId, long lastSyncDate, @Nullable String pageToken) throws IOException { - return execute( - service - .tasks() - .list(listId) - .setMaxResults(100L) - .setShowDeleted(true) - .setShowHidden(true) - .setPageToken(pageToken) - .setUpdatedMin( - GtasksApiUtilities.unixTimeToGtasksCompletionTime(lastSyncDate).toStringRfc3339())); - } - - public com.google.api.services.tasks.model.Tasks getAllPositions( - String listId, @Nullable String pageToken) throws IOException { - return execute( - service - .tasks() - .list(listId) - .setMaxResults(100L) - .setShowDeleted(false) - .setShowHidden(false) - .setPageToken(pageToken) - .setFields("items(id,parent,position),nextPageToken")); - } - - public @Nullable Task createGtask( - String listId, Task task, @Nullable String parent, @Nullable String previous) - throws IOException { - return execute(service.tasks().insert(listId, task).setParent(parent).setPrevious(previous)); - } - - public void updateGtask(String listId, Task task) throws IOException { - execute(service.tasks().update(listId, task.getId(), task)); - } - - @Nullable - public Task moveGtask(String listId, String taskId, String parentId, String previousId) - throws IOException { - return execute( - service.tasks().move(listId, taskId).setParent(parentId).setPrevious(previousId)); - } - - public void deleteGtaskList(String listId) throws IOException { - try { - execute(service.tasklists().delete(listId)); - } catch (HttpNotFoundException ignored) { - } - } - - public @Nullable TaskList renameGtaskList(String listId, String title) throws IOException { - return execute(service.tasklists().patch(listId, new TaskList().setTitle(title))); - } - - public @Nullable TaskList createGtaskList(String title) throws IOException { - return execute(service.tasklists().insert(new TaskList().setTitle(title))); - } - - public void deleteGtask(String listId, String taskId) throws IOException { - try { - execute(service.tasks().delete(listId, taskId)); - } catch (HttpNotFoundException ignored) { - } - } - - private synchronized @Nullable T execute(TasksRequest request) throws IOException { - return execute(request, false); - } - - private synchronized @Nullable T execute(TasksRequest request, boolean retry) - throws IOException { - credentialsAdapter.checkToken(account, TasksScopes.TASKS); - T response; - try { - HttpRequest httpRequest = request.buildHttpRequest(); - Timber.d("%s", httpRequest.getUrl()); - if (preferences.isFlipperEnabled()) { - response = interceptor.execute(httpRequest, request.getResponseClass()); - } else { - response = httpRequest.execute().parseAs(request.getResponseClass()); - } - } catch (HttpResponseException e) { - if (e.getStatusCode() == 401 && !retry) { - credentialsAdapter.invalidateToken(); - return execute(request, true); - } else if (e.getStatusCode() == 404) { - throw new HttpNotFoundException(e); - } else { - throw e; - } - } - Timber.d("%s response: %s", getCaller(retry), 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(boolean retry) { - if (BuildConfig.DEBUG) { - try { - return Thread.currentThread().getStackTrace()[retry ? 6 : 5].getMethodName(); - } catch (Exception e) { - Timber.e(e); - } - } - return ""; - } -} diff --git a/app/src/main/java/com/todoroo/astrid/gtasks/api/GtasksInvoker.kt b/app/src/main/java/com/todoroo/astrid/gtasks/api/GtasksInvoker.kt new file mode 100644 index 000000000..727d17b07 --- /dev/null +++ b/app/src/main/java/com/todoroo/astrid/gtasks/api/GtasksInvoker.kt @@ -0,0 +1,204 @@ +package com.todoroo.astrid.gtasks.api + +import android.content.Context +import com.google.api.client.http.HttpResponseException +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.tasks.Tasks +import com.google.api.services.tasks.TasksRequest +import com.google.api.services.tasks.TasksScopes +import com.google.api.services.tasks.model.Task +import com.google.api.services.tasks.model.TaskList +import com.google.api.services.tasks.model.TaskLists +import dagger.hilt.android.qualifiers.ApplicationContext +import org.tasks.BuildConfig +import org.tasks.DebugNetworkInterceptor +import org.tasks.gtasks.GoogleAccountManager +import org.tasks.preferences.Preferences +import timber.log.Timber +import java.io.IOException +import javax.inject.Inject + +/** + * Wrapper around the official Google Tasks API to simplify common operations. In the case of an + * exception, each request is tried twice in case of a timeout. + * + * @author Sam Bosley + */ +class GtasksInvoker { + private val context: Context + private val googleAccountManager: GoogleAccountManager + private val preferences: Preferences + private val interceptor: DebugNetworkInterceptor + private val account: String? + private val service: Tasks? + private val credentialsAdapter: HttpCredentialsAdapter? + + @Inject + constructor( + @ApplicationContext context: Context, + googleAccountManager: GoogleAccountManager, + preferences: Preferences, + interceptor: DebugNetworkInterceptor) { + this.context = context + this.googleAccountManager = googleAccountManager + this.preferences = preferences + this.interceptor = interceptor + account = null + service = null + credentialsAdapter = null + } + + private constructor( + context: Context, + googleAccountManager: GoogleAccountManager, + preferences: Preferences, + interceptor: DebugNetworkInterceptor, + account: String) { + this.context = context + this.googleAccountManager = googleAccountManager + this.preferences = preferences + this.interceptor = interceptor + this.account = account + credentialsAdapter = HttpCredentialsAdapter(googleAccountManager) + service = Tasks.Builder(NetHttpTransport(), JacksonFactory(), credentialsAdapter) + .setApplicationName(String.format("Tasks/%s", BuildConfig.VERSION_NAME)) + .build() + } + + fun forAccount(account: String): GtasksInvoker { + return GtasksInvoker(context, googleAccountManager, preferences, interceptor, account) + } + + @Throws(IOException::class) + fun allGtaskLists(pageToken: String?): TaskLists? { + return execute(service!!.tasklists().list().setMaxResults(100L).setPageToken(pageToken)) + } + + @Throws(IOException::class) + fun getAllGtasksFromListId( + listId: String?, lastSyncDate: Long, pageToken: String?): com.google.api.services.tasks.model.Tasks? { + return execute( + service!! + .tasks() + .list(listId) + .setMaxResults(100L) + .setShowDeleted(true) + .setShowHidden(true) + .setPageToken(pageToken) + .setUpdatedMin( + GtasksApiUtilities.unixTimeToGtasksCompletionTime(lastSyncDate).toStringRfc3339())) + } + + @Throws(IOException::class) + fun getAllPositions( + listId: String?, pageToken: String?): com.google.api.services.tasks.model.Tasks { + return execute( + service!! + .tasks() + .list(listId) + .setMaxResults(100L) + .setShowDeleted(false) + .setShowHidden(false) + .setPageToken(pageToken) + .setFields("items(id,parent,position),nextPageToken"))!! + } + + @Throws(IOException::class) + fun createGtask( + listId: String?, task: Task?, parent: String?, previous: String?): Task? { + return execute(service!!.tasks().insert(listId, task).setParent(parent).setPrevious(previous)) + } + + @Throws(IOException::class) + fun updateGtask(listId: String?, task: Task) { + execute(service!!.tasks().update(listId, task.id, task)) + } + + @Throws(IOException::class) + fun moveGtask(listId: String?, taskId: String?, parentId: String?, previousId: String?): Task? { + return execute( + service!!.tasks().move(listId, taskId).setParent(parentId).setPrevious(previousId)) + } + + @Throws(IOException::class) + fun deleteGtaskList(listId: String?) { + try { + execute(service!!.tasklists().delete(listId)) + } catch (ignored: HttpNotFoundException) { + } + } + + @Throws(IOException::class) + fun renameGtaskList(listId: String?, title: String?): TaskList? { + return execute(service!!.tasklists().patch(listId, TaskList().setTitle(title))) + } + + @Throws(IOException::class) + fun createGtaskList(title: String?): TaskList? { + return execute(service!!.tasklists().insert(TaskList().setTitle(title))) + } + + @Throws(IOException::class) + fun deleteGtask(listId: String?, taskId: String?) { + try { + execute(service!!.tasks().delete(listId, taskId)) + } catch (ignored: HttpNotFoundException) { + } + } + + @Synchronized + @Throws(IOException::class) + private fun execute(request: TasksRequest): T? { + return execute(request, false) + } + + @Synchronized + @Throws(IOException::class) + private fun execute(request: TasksRequest, retry: Boolean): T? { + credentialsAdapter!!.checkToken(account, TasksScopes.TASKS) + val response: T? + response = try { + val httpRequest = request.buildHttpRequest() + Timber.d("%s", httpRequest.url) + if (preferences.isFlipperEnabled) { + interceptor.execute(httpRequest, request.responseClass) + } else { + httpRequest.execute().parseAs(request.responseClass) + } + } catch (e: HttpResponseException) { + return if (e.statusCode == 401 && !retry) { + credentialsAdapter.invalidateToken() + execute(request, true) + } else if (e.statusCode == 404) { + throw HttpNotFoundException(e) + } else { + throw e + } + } + Timber.d("%s response: %s", getCaller(retry), prettyPrint(response)) + return 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 fun getCaller(retry: Boolean): String { + if (BuildConfig.DEBUG) { + try { + return Thread.currentThread().stackTrace[if (retry) 6 else 5].methodName + } catch (e: Exception) { + Timber.e(e) + } + } + return "" + } +} \ No newline at end of file