/* * Copyright © 2014-2020 The Android Password Store Authors. All Rights Reserved. * SPDX-License-Identifier: GPL-3.0-only */ package com.zeapo.pwdstore.git import android.app.Activity import android.app.ProgressDialog import android.content.Context import android.content.Intent import android.os.AsyncTask import com.github.ajalt.timberkt.e import com.zeapo.pwdstore.PasswordStore import com.zeapo.pwdstore.R import net.schmizz.sshj.userauth.UserAuthException import org.eclipse.jgit.api.CommitCommand import org.eclipse.jgit.api.GitCommand import org.eclipse.jgit.api.PullCommand import org.eclipse.jgit.api.PushCommand import org.eclipse.jgit.api.RebaseResult import org.eclipse.jgit.api.StatusCommand import org.eclipse.jgit.transport.RemoteRefUpdate import java.io.IOException import java.lang.ref.WeakReference class GitAsyncTask( activity: Activity, private val refreshListOnEnd: Boolean, private val operation: GitOperation, private val finishWithResultOnEnd: Intent?) : AsyncTask, Int, GitAsyncTask.Result>() { private val activityWeakReference: WeakReference = WeakReference(activity) private val activity: Activity? get() = activityWeakReference.get() private val context: Context = activity.applicationContext private val dialog = ProgressDialog(activity) sealed class Result { object Ok : Result() data class Err(val err: Exception) : Result() } override fun onPreExecute() { dialog.run { setMessage(activity!!.resources.getString(R.string.running_dialog_text)) setCancelable(false) show() } } override fun doInBackground(vararg commands: GitCommand<*>): Result? { var nbChanges: Int? = null for (command in commands) { try { when (command) { is StatusCommand -> { // in case we have changes, we want to keep track of it val status = command.call() nbChanges = status.changed.size + status.missing.size } is CommitCommand -> { // the previous status will eventually be used to avoid a commit if (nbChanges == null || nbChanges > 0) command.call() } is PullCommand -> { val result = command.call() val rr = result.rebaseResult if (rr.status === RebaseResult.Status.STOPPED) { return Result.Err(IOException(context.getString(R.string .git_pull_fail_error))) } } is PushCommand -> { for (result in command.call()) { // Code imported (modified) from Gerrit PushOp, license Apache v2 for (rru in result.remoteUpdates) { val error = when (rru.status) { RemoteRefUpdate.Status.REJECTED_NONFASTFORWARD -> context.getString(R.string.git_push_nff_error) RemoteRefUpdate.Status.REJECTED_NODELETE, RemoteRefUpdate.Status.REJECTED_REMOTE_CHANGED, RemoteRefUpdate.Status.NON_EXISTING, RemoteRefUpdate.Status.NOT_ATTEMPTED -> (activity!!.getString(R.string.git_push_generic_error) + rru.status.name) RemoteRefUpdate.Status.REJECTED_OTHER_REASON -> { if ("non-fast-forward" == rru.message) { context.getString(R.string.git_push_other_error) } else { (context.getString(R.string.git_push_generic_error) + rru.message) } } else -> null } if (error != null) Result.Err(IOException(error)) } } } else -> { command.call() } } } catch (e: Exception) { return Result.Err(e) } } return Result.Ok } private fun rootCauseException(e: Exception): Exception { var rootCause = e // JGit's TransportException hides the more helpful SSHJ exceptions. // Also, SSHJ's UserAuthException about exhausting available authentication methods hides // more useful exceptions. while ((rootCause is org.eclipse.jgit.errors.TransportException || rootCause is org.eclipse.jgit.api.errors.TransportException || (rootCause is UserAuthException && rootCause.message == "Exhausted available authentication methods"))) { rootCause = rootCause.cause as? Exception ?: break } return rootCause } override fun onPostExecute(maybeResult: Result?) { dialog.dismiss() when (val result = maybeResult ?: Result.Err(IOException("Unexpected error"))) { is Result.Err -> { e(result.err) operation.onError(rootCauseException(result.err)) if (finishWithResultOnEnd != null) { activity?.setResult(Activity.RESULT_CANCELED) activity?.finish() } } is Result.Ok -> { operation.onSuccess() if (finishWithResultOnEnd != null) { activity?.setResult(Activity.RESULT_OK, finishWithResultOnEnd) activity?.finish() } } } if (refreshListOnEnd) { (activity as? PasswordStore)?.resetPasswordList() } } }