package com.door43.translationstudio.tasks;
import android.os.Process;
import com.door43.tools.reporting.FileUtils;
import com.door43.tools.reporting.Logger;
import com.door43.translationstudio.AppContext;
import com.door43.translationstudio.R;
import com.door43.translationstudio.SettingsActivity;
import com.door43.translationstudio.core.Profile;
import com.door43.translationstudio.core.TargetTranslation;
import com.door43.translationstudio.git.Repo;
import com.door43.translationstudio.git.TransportCallback;
import com.door43.util.Manifest;
import com.door43.util.tasks.ManagedTask;
import org.eclipse.jgit.api.CheckoutCommand;
import org.eclipse.jgit.api.CreateBranchCommand;
import org.eclipse.jgit.api.DeleteBranchCommand;
import org.eclipse.jgit.api.Git;
import org.eclipse.jgit.api.MergeResult;
import org.eclipse.jgit.api.PullCommand;
import org.eclipse.jgit.api.PullResult;
import org.eclipse.jgit.api.ResetCommand;
import org.eclipse.jgit.api.errors.CheckoutConflictException;
import org.eclipse.jgit.api.errors.GitAPIException;
import org.eclipse.jgit.api.errors.TransportException;
import org.eclipse.jgit.errors.NoRemoteRepositoryException;
import org.eclipse.jgit.lib.ProgressMonitor;
import org.eclipse.jgit.merge.MergeStrategy;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;
/**
* Pulls down changes from a remote target translation repository
*/
public class PullTargetTranslationTask extends ManagedTask {
public static final String TASK_ID = "pull_target_translation_task";
private final TargetTranslation targetTranslation;
private final MergeStrategy mergeStrategy;
private String message = "";
private Status status = Status.UNKNOWN;
private Map<String, int[][]> conflicts = new HashMap<>();
/**
* todo: eventually we need to support pulling from a particular user.
* We may need to create a different task or just pass in the target translation id
* @param targetTranslation
*/
public PullTargetTranslationTask(TargetTranslation targetTranslation) {
setThreadPriority(Process.THREAD_PRIORITY_DEFAULT);
this.targetTranslation = targetTranslation;
this.mergeStrategy = MergeStrategy.RECURSIVE;
}
/**
* todo: eventually we need to support pulling from a particular user.
* We may need to create a different task or just pass in the target translation id
* @param targetTranslation
*/
public PullTargetTranslationTask(TargetTranslation targetTranslation, MergeStrategy mergeStrategy) {
setThreadPriority(Process.THREAD_PRIORITY_DEFAULT);
this.targetTranslation = targetTranslation;
this.mergeStrategy = mergeStrategy;
}
@Override
public void start() {
Profile profile = AppContext.getProfile();
if(targetTranslation != null && AppContext.context().isNetworkAvailable() && profile != null && profile.gogsUser != null) {
publishProgress(-1, "Downloading updates");
String server = AppContext.context().getUserPreferences().getString(SettingsActivity.KEY_PREF_GIT_SERVER, AppContext.context().getResources().getString(R.string.pref_default_git_server));
String remote = server + ":" + profile.gogsUser.getUsername() + "/" + this.targetTranslation.getId() + ".git";
try {
this.targetTranslation.commitSync();
} catch (Exception e) {
Logger.w(this.getClass().getName(), "Failed to commit the target translation " + targetTranslation.getId(), e);
}
Repo repo = this.targetTranslation.getRepo();
createBackupBranch(repo);
this.message = pull(repo, remote);
}
}
private void createBackupBranch(Repo repo) {
try {
Git git = repo.getGit();
DeleteBranchCommand deleteBranchCommand = git.branchDelete();
deleteBranchCommand.setBranchNames("backup-master")
.setForce(true)
.call();
CreateBranchCommand createBranchCommand = git.branchCreate();
createBranchCommand.setName("backup-master")
.setForce(true)
.call();
} catch (Exception e) {
e.printStackTrace();
}
}
private String pull(Repo repo, String remote) {
Git git;
try {
repo.deleteRemote("origin");
repo.setRemote("origin", remote);
git = repo.getGit();
} catch (IOException e) {
return null;
}
Manifest localManifest = Manifest.generate(this.targetTranslation.getPath());
// TODO: we might want to get some progress feedback for the user
PullCommand pullCommand = git.pull()
.setTransportConfigCallback(new TransportCallback())
.setRemote("origin")
.setStrategy(mergeStrategy)
.setRemoteBranchName("master")
.setProgressMonitor(new ProgressMonitor() {
@Override
public void start(int totalTasks) {
}
@Override
public void beginTask(String title, int totalWork) {
}
@Override
public void update(int completed) {
}
@Override
public void endTask() {
}
@Override
public boolean isCancelled() {
return false;
}
});
try {
PullResult result = pullCommand.call();
MergeResult mergeResult = result.getMergeResult();
if(mergeResult != null && mergeResult.getConflicts() != null && mergeResult.getConflicts().size() > 0) {
this.status = Status.MERGE_CONFLICTS;
this.conflicts = mergeResult.getConflicts();
// revert manifest merge conflict to avoid corruption
if(this.conflicts.containsKey("manifest.json")) {
try {
git.checkout()
.setStage(CheckoutCommand.Stage.THEIRS)
.addPath("manifest.json")
.call();
Manifest remoteManifest = Manifest.generate(this.targetTranslation.getPath());
localManifest = TargetTranslation.mergeManifests(localManifest, remoteManifest);
} catch (CheckoutConflictException e) {
// failed to reset manifest.json
Logger.e(this.getClass().getName(), e.getMessage(), e);
} finally {
localManifest.save();
}
}
} else {
this.status = Status.UP_TO_DATE;
}
return "message";
} catch (TransportException e) {
Logger.e(this.getClass().getName(), e.getMessage(), e);
Throwable cause = e.getCause();
if(cause != null) {
Throwable subException = cause.getCause();
if(subException != null) {
String detail = subException.getMessage();
if ("Auth fail".equals(detail)) {
this.status = Status.AUTH_FAILURE; // we do special handling for auth failure
}
} else if(cause instanceof NoRemoteRepositoryException) {
this.status = Status.NO_REMOTE_REPO;
}
}
return null;
} catch (Exception e) {
Throwable cause = e.getCause();
if(cause instanceof NoRemoteRepositoryException) {
this.status = Status.NO_REMOTE_REPO;
}
Logger.e(this.getClass().getName(), e.getMessage(), e);
return null;
} catch (OutOfMemoryError e) {
Logger.e(this.getClass().getName(), e.getMessage(), e);
this.status = Status.OUT_OF_MEMORY;
return null;
} catch (Throwable e) {
Logger.e(this.getClass().getName(), e.getMessage(), e);
return null;
}
}
public String getMessage() {
return message;
}
public Status getStatus() {
return status;
}
public Map<String, int[][]> getConflicts() {
return conflicts;
}
public enum Status {
UP_TO_DATE,
MERGE_CONFLICTS,
OUT_OF_MEMORY,
AUTH_FAILURE,
NO_REMOTE_REPO,
UNKNOWN
}
}