| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381 |
- package info.knacki.pass.ui;
- import android.os.Bundle;
- import android.support.v7.app.AppCompatActivity;
- import android.widget.ProgressBar;
- import android.widget.TextView;
- import java.io.File;
- import java.io.FileOutputStream;
- import java.io.IOException;
- import java.io.OutputStream;
- import java.util.HashMap;
- import java.util.HashSet;
- import java.util.Map;
- import java.util.Set;
- import java.util.Stack;
- import java.util.logging.Level;
- import java.util.logging.Logger;
- import info.knacki.pass.R;
- import info.knacki.pass.git.GitInterface;
- import info.knacki.pass.git.GitInterfaceFactory;
- import info.knacki.pass.git.GitLocal;
- import info.knacki.pass.git.GitSha1;
- import info.knacki.pass.git.entities.GitCommit;
- import info.knacki.pass.git.entities.GitObject;
- import info.knacki.pass.io.FileUtils;
- import info.knacki.pass.io.OnResponseListener;
- import info.knacki.pass.io.OnStreamResponseListener;
- import info.knacki.pass.io.PathUtils;
- import info.knacki.pass.settings.SettingsManager;
- import info.knacki.pass.ui.alertPrompt.AlertPrompt;
- import info.knacki.pass.ui.alertPrompt.AlertPromptGenerator;
- import info.knacki.pass.ui.alertPrompt.views.ConflictView;
- public class GitPullActivity extends AppCompatActivity {
- private final static Logger log = Logger.getLogger(GitPullActivity.class.getName());
- public final static String COMMIT_MSG = "Android pass sync";
- public static String LOCAL_GIT_HASH_VERSION_FILE;
- private GitInterface fGitInterface;
- private GitCommit fHeadCommit;
- private void OnMsg(final String msg) {
- log.info(msg);
- GitPullActivity.this.runOnUiThread(() -> {
- TextView logView = findViewById(R.id.logView);
- logView.append(msg +"\n");
- });
- }
- @Override
- protected void onCreate(Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
- final SettingsManager.VCS versioning = SettingsManager.GetVCS(this);
- setContentView(R.layout.activity_git_pull);
- setTitle(R.string.pref_vcs_git_pull);
- if (!(versioning instanceof SettingsManager.Git)) {
- finish();
- return;
- }
- LOCAL_GIT_HASH_VERSION_FILE = PathUtils.GetGitFile(this);
- try {
- fGitInterface = GitInterfaceFactory.factory((SettingsManager.Git) versioning);
- fGitInterface.FetchHead(new OnStreamResponseListener<GitCommit>() {
- @Override
- public void OnMsg(final String msg) {
- GitPullActivity.this.OnMsg(msg);
- }
- @Override
- public void OnResponse(GitCommit result) {
- fHeadCommit = result;
- GitPullActivity.this.runOnUiThread(GitPullActivity.this::OnTreeStructureFetched);
- }
- @Override
- public void OnError(final String msg, Throwable e) {
- GitPullActivity.this.runOnUiThread(() -> {
- OnMsg(msg);
- FinishLoading();
- });
- }
- });
- } catch (GitInterfaceFactory.GitInterfaceException e) {
- GitPullActivity.this.runOnUiThread(() -> {
- OnMsg(e.getMessage());
- FinishLoading();
- });
- }
- findViewById(R.id.close_bt).setOnClickListener(v -> GitPullActivity.this.finish());
- }
- protected void OnTreeStructureFetched() {
- final GitLocal localVersion = new GitLocal(new File(LOCAL_GIT_HASH_VERSION_FILE));
- HashMap<String, GitObject.GitBlob> filesToPull = new HashMap<>();
- Set<String> filesToPush = new HashSet<>();
- HashMap<String, GitObject.GitBlob> conflictingFiles = new HashMap<>();
- OnMsg("Done reading remote tree");
- OnMsg("Building change list");
- for (Map.Entry<String, GitObject.GitBlob>i: fHeadCommit.GetTree().FindAllBlobs().entrySet()) {
- final String remoteKnownHash = localVersion.GetHash(i.getKey(), "");
- final String remoteHash = GitSha1.BytesToString(i.getValue().GetHash());
- final String currentHash = GitSha1.BytesToString(GitSha1.getRawSha1OfFile(new File(PathUtils.GetPassDir(this) +i.getKey())));
- final boolean remoteChanged = !remoteKnownHash.equals(remoteHash);
- final boolean localChanged = !remoteKnownHash.equals(currentHash);
- if (remoteChanged && localChanged) {
- // Conflict (but file can still has the same content)
- if (currentHash.equals(remoteHash))
- filesToPull.put(i.getKey(), i.getValue());
- else
- conflictingFiles.put(i.getKey(), i.getValue());
- } else if (remoteChanged) {
- // remote changed
- filesToPull.put(i.getKey(), i.getValue());
- } else if (localChanged) {
- // local changed
- filesToPush.add(i.getKey());
- }
- }
- for (String i: localVersion.FileNames()) {
- if (fHeadCommit.GetTree().GetObjectFullPath(i) == null) {
- log.finer("removed from remote " +i);
- final String currentHash = GitSha1.BytesToString(GitSha1.getRawSha1OfFile(new File(PathUtils.GetPassDir(this) +i)));
- final boolean localChanged = !currentHash.equals(localVersion.GetHash(i, ""));
- if (!localChanged || "".equals(currentHash))
- filesToPull.put(i, null);
- else
- conflictingFiles.put(i, null);
- }
- }
- CheckNewFiles(localVersion, new File(PathUtils.GetPassDir(this)), "", filesToPull.keySet(), filesToPush, conflictingFiles.keySet());
- if (conflictingFiles.isEmpty())
- SyncFiles(localVersion, filesToPull, filesToPush);
- else
- AskForConflicts(localVersion, conflictingFiles, filesToPull, filesToPush);
- }
- int CheckNewFiles(GitLocal localVersion, File root, String rootPath, Set<String> filesToPull, Set<String> filesToPush, Set<String> conflicts) {
- int newFiles = 0;
- for (final File i: root.listFiles()) {
- if (!PathUtils.IsHidden(i.getAbsolutePath())) {
- if (i.isDirectory()) {
- newFiles += CheckNewFiles(localVersion, i, rootPath + "/" + i.getName(), filesToPull, filesToPush, conflicts);
- } else if (i.isFile()) {
- String path = rootPath + "/" + i.getName();
- if (!localVersion.HasHash(path) && !filesToPull.contains(path) && !conflicts.contains(path)) {
- // New file
- filesToPush.add(path);
- newFiles++;
- }
- }
- }
- }
- return newFiles;
- }
- void AskForConflicts(final GitLocal localVersion, final HashMap<String, GitObject.GitBlob> conflicts, final HashMap<String, GitObject.GitBlob> filesToPull, final Set<String> filesToPush) {
- runOnUiThread(() -> {
- AlertPrompt pt = AlertPromptGenerator.StaticMake(GitPullActivity.this)
- .setCancelable(true)
- .setNegativeButton(R.string.cancel, (dialogInterface, view) -> GitPullActivity.this.finish())
- .setPositiveButton(R.string.ok, (dialogInterface, v) -> {
- ConflictView.ConflictViewResult viewResult = ((ConflictView) v).GetResult();
- for (String s: viewResult.fUseTheir) {
- filesToPull.put(s, conflicts.get(s));
- }
- filesToPush.addAll(viewResult.fUseMine);
- SyncFiles(localVersion, filesToPull, filesToPush);
- })
- .setTitle(R.string.conflictingFiles);
- ConflictView view = new ConflictView(GitPullActivity.this, pt, conflicts.keySet());
- pt.setView(view).show();
- view.UpdateButtonState();
- });
- }
- void RmEmptyDirs(File dir, boolean isRoot) {
- File[] content = dir.listFiles();
- if (null == content || content.length == 0) {
- if (!isRoot)
- FileUtils.DeleteFile(dir);
- return;
- }
- for (File i: content)
- if (i.isDirectory())
- RmEmptyDirs(i, false);
- }
- void FinishLoading() {
- ProgressBar pg = findViewById(R.id.progressBar);
- pg.setIndeterminate(false);
- pg.setMax(1);
- pg.setProgress(1);
- RmEmptyDirs(new File(PathUtils.GetPassDir(GitPullActivity.this)), true);
- findViewById(R.id.close_bt).setEnabled(true);
- }
- void SyncFiles(final GitLocal localVersion, final HashMap<String, GitObject.GitBlob> filesToPull, final Set<String> filesToPush) {
- final OnStreamResponseListener<Void> allDone = new OnStreamResponseListener<Void>() {
- @Override
- public void OnResponse(Void result) {
- GitPullActivity.this.runOnUiThread(() -> {
- localVersion.Write(new File(LOCAL_GIT_HASH_VERSION_FILE));
- FinishLoading();
- });
- }
- @Override
- public void OnError(String msg, Throwable e) {
- GitPullActivity.this.runOnUiThread(() -> FinishLoading());
- }
- @Override
- public void OnMsg(String message) {
- GitPullActivity.this.OnMsg(message);
- }
- };
- final Runnable afterFetching = () -> {
- if (filesToPush.size() > 0) {
- GitPullActivity.this.OnMsg("Updating remote repository");
- PushBlobs(filesToPush, localVersion, allDone);
- } else {
- GitPullActivity.this.OnMsg("Nothing to push");
- allDone.OnResponse(null);
- }
- };
- if (filesToPull.isEmpty()) {
- OnMsg("Nothing to pull");
- afterFetching.run();
- } else {
- OnMsg("Updating local repository");
- DownloadBlobs(filesToPull, localVersion, new OnStreamResponseListener<Void>() {
- @Override
- public void OnResponse(Void result) {
- afterFetching.run();
- }
- @Override
- public void OnError(String msg, Throwable e) {
- allDone.OnError(msg, e);
- }
- @Override
- public void OnMsg(String message) {
- GitPullActivity.this.OnMsg(message);
- }
- });
- }
- }
- void DownloadBlobs(Map<String, GitObject.GitBlob> blobs, final GitLocal localVersion, final OnStreamResponseListener<Void> resp) {
- if (blobs.size() == 0) {
- resp.OnResponse(null);
- return;
- }
- final TextView logView = findViewById(R.id.logView);
- logView.append(blobs.size() +" files to update locally\n");
- for (String i: blobs.keySet())
- logView.append(" > " +i +"\n");
- final Stack<Map.Entry<String, GitObject.GitBlob>> files = new Stack<>();
- files.addAll(blobs.entrySet());
- final OnStreamResponseListener<byte[]> downloader = new OnStreamResponseListener<byte[]>() {
- @Override
- public void OnResponse(final byte[] result) {
- final String filename = files.peek().getKey();
- final GitObject.GitBlob blob = files.peek().getValue();
- final OnStreamResponseListener<byte[]> _this = this;
- GitPullActivity.this.runOnUiThread(() -> {
- WriteFile(filename, localVersion, blob, result);
- logView.append("Done fetching " +files.peek().getValue().GetFilename() +"\n");
- files.pop();
- DownloadNext(files, localVersion, _this, resp);
- });
- }
- @Override
- public void OnError(final String msg, Throwable e) {
- log.log(Level.SEVERE, msg, e);
- GitPullActivity.this.runOnUiThread(() -> logView.append("Error while fetching " +files.peek().getValue().GetFilename() +": " +msg));
- files.pop();
- if (!files.empty()) {
- fGitInterface.FetchBlob(files.peek().getValue(), this);
- } else {
- resp.OnResponse(null);
- }
- }
- @Override
- public void OnMsg(String message) {
- resp.OnMsg(message);
- }
- };
- DownloadNext(files, localVersion, downloader, resp);
- }
- void DownloadNext(Stack<Map.Entry<String, GitObject.GitBlob>> files, GitLocal localCache, OnStreamResponseListener<byte[]> downloader, OnResponseListener<Void> resp) {
- if (!files.empty()) {
- if (files.peek().getValue() == null) {
- // remove file
- String filename = files.pop().getKey();
- FileUtils.TrashFile(new File(PathUtils.GetPassDir(this) +filename));
- localCache.remove(filename);
- DownloadNext(files, localCache, downloader, resp);
- log.info("Removed file " +filename);
- } else {
- fGitInterface.FetchBlob(files.peek().getValue(), downloader);
- }
- } else {
- resp.OnResponse(null);
- }
- }
- void WriteFile(String filename, final GitLocal localVersion, GitObject.GitBlob blob, byte[] result) {
- File f = new File(PathUtils.GetPassDir(this) +filename);
- FileUtils.MkDir(f.getParentFile(), true);
- final int chunkSize = 1024;
- final int nbChunk = (int) Math.ceil((double) result.length / chunkSize);
- try {
- OutputStream writer = new FileOutputStream(f);
- for (int i = 0; i < nbChunk; ++i)
- writer.write(result, i * chunkSize, Math.min(chunkSize, result.length -Math.max(0, ((i -1) * chunkSize))));
- writer.close();
- }
- catch (IOException e) {
- log.log(Level.SEVERE, e.getMessage(), e);
- return;
- }
- localVersion.SetHash(filename, GitSha1.BytesToString(blob.GetHash()));
- }
- void PushBlobs(final Set<String> files, final GitLocal localVersion, final OnStreamResponseListener<Void> resp) {
- if (files.isEmpty()) {
- resp.OnMsg("Nothing to commit");
- resp.OnResponse(null);
- } else {
- SettingsManager.Git config = (SettingsManager.Git) SettingsManager.GetVCS(this);
- final GitCommit.Builder commit = new GitCommit.Builder(fHeadCommit, config.GetUsername(), config.GetUserEmail(), COMMIT_MSG);
- for (String i : files)
- commit.AddFile(i, new File(PathUtils.GetPassDir(this) + i));
- fGitInterface.PushCommitBuilder(commit, new OnStreamResponseListener<Void>() {
- @Override
- public void OnMsg(String message) {
- resp.OnMsg(message);
- }
- @Override
- public void OnResponse(Void result) {
- final GitObject.GitTree tree = commit.Build().GetTree();
- for (String i: files) {
- if (tree.GetObjectFullPath(i) == null)
- localVersion.remove(i);
- else
- localVersion.SetHash(i, GitSha1.BytesToString(tree.GetObjectFullPath(i).GetHash()));
- }
- OnMsg("Done");
- resp.OnResponse(result);
- }
- @Override
- public void OnError(String msg, Throwable e) {
- log.log(Level.SEVERE, msg, e);
- }
- });
- }
- }
- }
|