|
|
@@ -5,16 +5,31 @@ import android.support.v7.app.AppCompatActivity;
|
|
|
import android.widget.ProgressBar;
|
|
|
import android.widget.TextView;
|
|
|
|
|
|
+import java.io.File;
|
|
|
+import java.io.FileInputStream;
|
|
|
+import java.io.FileOutputStream;
|
|
|
+import java.io.FileWriter;
|
|
|
+import java.io.IOException;
|
|
|
+import java.io.InputStream;
|
|
|
+import java.io.OutputStream;
|
|
|
import java.nio.charset.Charset;
|
|
|
+import java.security.MessageDigest;
|
|
|
+import java.util.ArrayDeque;
|
|
|
+import java.util.HashMap;
|
|
|
+import java.util.Map;
|
|
|
+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.entities.GitObject;
|
|
|
import info.knacki.pass.settings.SettingsManager;
|
|
|
|
|
|
public class GitPullActivity extends AppCompatActivity {
|
|
|
+ private final static Logger log = Logger.getLogger(GitPullActivity.class.getName());
|
|
|
|
|
|
private GitInterface fGitInterfage;
|
|
|
private GitObject.GitTree fTree;
|
|
|
@@ -31,6 +46,7 @@ public class GitPullActivity extends AppCompatActivity {
|
|
|
finish();
|
|
|
return;
|
|
|
}
|
|
|
+ LOCALGIT_HASH_VERSION_FILE = getApplicationContext().getFilesDir().getAbsolutePath() +"/" +getResources().getString(R.string.data_git_local);
|
|
|
fGitInterfage = GitInterfaceFactory.factory(this, (SettingsManager.Git) versioning);
|
|
|
if (fGitInterfage != null) {
|
|
|
fGitInterfage.FetchTree(new GitInterface.OnStreamResponseListener<GitObject.GitTree>() {
|
|
|
@@ -74,7 +90,223 @@ public class GitPullActivity extends AppCompatActivity {
|
|
|
}
|
|
|
}
|
|
|
|
|
|
+ public String getMd5OfFile(String filePath)
|
|
|
+ {
|
|
|
+ return getMd5OfFile(new File(getApplicationContext().getFilesDir().getAbsolutePath() +"/" +getResources().getString(R.string.data_dir_name) +filePath));
|
|
|
+ }
|
|
|
+
|
|
|
+ public String getMd5OfFile(File f)
|
|
|
+ {
|
|
|
+ try
|
|
|
+ {
|
|
|
+ InputStream input = new FileInputStream(f);
|
|
|
+ byte[] buffer = new byte[1024];
|
|
|
+ MessageDigest md5Hash = MessageDigest.getInstance("MD5");
|
|
|
+ int numRead = 0;
|
|
|
+ while (numRead != -1)
|
|
|
+ {
|
|
|
+ numRead = input.read(buffer);
|
|
|
+ if (numRead > 0)
|
|
|
+ md5Hash.update(buffer, 0, numRead);
|
|
|
+ }
|
|
|
+ input.close();
|
|
|
+
|
|
|
+ byte [] md5Bytes = md5Hash.digest();
|
|
|
+ StringBuilder sb = new StringBuilder();
|
|
|
+ for (int i=0; i < md5Bytes.length; i++)
|
|
|
+ sb.append(Integer.toString((md5Bytes[i] & 0xff) + 0x100, 16).substring(1));
|
|
|
+ return sb.toString();
|
|
|
+ }
|
|
|
+ catch(Throwable t) {
|
|
|
+ t.printStackTrace();
|
|
|
+ }
|
|
|
+ return "";
|
|
|
+ }
|
|
|
+
|
|
|
+ public static String LOCALGIT_HASH_VERSION_FILE;
|
|
|
+
|
|
|
protected void OnTreeStructureFetched() {
|
|
|
+ final GitLocal localVersion = new GitLocal(new File(LOCALGIT_HASH_VERSION_FILE));
|
|
|
+ HashMap<String, GitObject.GitBlob> filesToDownload = new HashMap<>();
|
|
|
+ HashMap<String, GitObject.GitBlob> filesToPush = new HashMap<>();
|
|
|
+ HashMap<String, GitObject.GitBlob> conflictingFiles = new HashMap<>();
|
|
|
+
|
|
|
+ for (Map.Entry<String, GitObject.GitBlob>i: fTree.FindAllBlobs().entrySet()) {
|
|
|
+ String localHash = localVersion.GetLocalHash(i.getKey());
|
|
|
+ String remoteHash = localVersion.GetRemoteHash(i.getKey());
|
|
|
+ String currentHash = getMd5OfFile(i.getKey());
|
|
|
+
|
|
|
+ if (localHash == null) localHash = "";
|
|
|
+ if (remoteHash == null) remoteHash = "";
|
|
|
+
|
|
|
+ final boolean remoteChanged = !remoteHash.equals(i.getValue().fSha1);
|
|
|
+ final boolean localChanged = !localHash.equals(currentHash);
|
|
|
+
|
|
|
+ if (remoteChanged && localChanged) {
|
|
|
+ // Conflict
|
|
|
+ conflictingFiles.put(i.getKey(), i.getValue());
|
|
|
+ } else if (remoteChanged) {
|
|
|
+ // remote changed
|
|
|
+ filesToDownload.put(i.getKey(), i.getValue());
|
|
|
+ } else if (localChanged) {
|
|
|
+ // local changed
|
|
|
+ filesToPush.put(i.getKey(), i.getValue());
|
|
|
+ }
|
|
|
+ }
|
|
|
+ for (String i: localVersion.FileNames()) {
|
|
|
+ if (fTree.GetObjectFullPath(i) == null) {
|
|
|
+ final boolean localChanged = !getMd5OfFile(i).equals(localVersion.GetLocalHash(i));
|
|
|
+ if (localChanged)
|
|
|
+ conflictingFiles.put(i, null);
|
|
|
+ else
|
|
|
+ filesToDownload.put(i, null);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ // FIXME new file on local
|
|
|
+
|
|
|
+ AskForConflicts(localVersion, conflictingFiles, filesToDownload, filesToPush);
|
|
|
+ }
|
|
|
+
|
|
|
+ void AskForConflicts(GitLocal localVersion, HashMap<String, GitObject.GitBlob> conflicts, HashMap<String, GitObject.GitBlob> filesToDownload, HashMap<String, GitObject.GitBlob> filesToPush) {
|
|
|
+ // FIXME
|
|
|
+ filesToDownload.putAll(conflicts);
|
|
|
+ SyncFiles(localVersion, filesToDownload, filesToPush);
|
|
|
+ }
|
|
|
+
|
|
|
+ void SyncFiles(final GitLocal localVersion, final HashMap<String, GitObject.GitBlob> filesToDownload, final HashMap<String, GitObject.GitBlob> filesToPush) {
|
|
|
+ final GitInterface.OnResponseListener<Void> allDone = new GitInterface.OnResponseListener<Void>() {
|
|
|
+ @Override
|
|
|
+ public void onResponse(Void result) {
|
|
|
+ GitPullActivity.this.runOnUiThread(new Runnable() {
|
|
|
+ @Override
|
|
|
+ public void run() {
|
|
|
+ ProgressBar pg = findViewById(R.id.progressBar);
|
|
|
+ pg.setIndeterminate(false);
|
|
|
+ pg.setMax(1);
|
|
|
+ pg.setProgress(1);
|
|
|
+ localVersion.Write(new File(LOCALGIT_HASH_VERSION_FILE));
|
|
|
+ }
|
|
|
+ });
|
|
|
+ }
|
|
|
+
|
|
|
+ @Override
|
|
|
+ public void onError(String msg, Throwable e) {
|
|
|
+ GitPullActivity.this.runOnUiThread(new Runnable() {
|
|
|
+ @Override
|
|
|
+ public void run() {
|
|
|
+ ProgressBar pg = findViewById(R.id.progressBar);
|
|
|
+ pg.setIndeterminate(false);
|
|
|
+ pg.setMax(1);
|
|
|
+ pg.setProgress(1);
|
|
|
+ }
|
|
|
+ });
|
|
|
+ }
|
|
|
+ };
|
|
|
+
|
|
|
+ DownloadBlobs(filesToDownload, localVersion, new GitInterface.OnResponseListener<Void>(){
|
|
|
+ @Override
|
|
|
+ public void onResponse(Void result) {
|
|
|
+ if (filesToPush.size() > 0)
|
|
|
+ PushBlobs(filesToPush, allDone);
|
|
|
+ else
|
|
|
+ allDone.onResponse(result);
|
|
|
+ }
|
|
|
+
|
|
|
+ @Override
|
|
|
+ public void onError(String msg, Throwable e) {
|
|
|
+ allDone.onError(msg, e);
|
|
|
+ }
|
|
|
+ });
|
|
|
+ }
|
|
|
+
|
|
|
+ void DownloadBlobs(Map<String, GitObject.GitBlob> blobs, final GitLocal localVersion, final GitInterface.OnResponseListener<Void> resp) {
|
|
|
+ if (blobs.size() == 0) {
|
|
|
+ resp.onResponse(null);
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ final TextView logView = findViewById(R.id.logView);
|
|
|
+ logView.append(blobs.size() +" files to Download\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 GitInterface.OnResponseListener<byte[]> downloader = new GitInterface.OnResponseListener<byte[]>() {
|
|
|
+ @Override
|
|
|
+ public void onResponse(final byte[] result) {
|
|
|
+ final String filename = files.peek().getKey();
|
|
|
+ final GitObject.GitBlob blob = files.peek().getValue();
|
|
|
+ final GitInterface.OnResponseListener<byte[]> _this = this;
|
|
|
+
|
|
|
+
|
|
|
+ GitPullActivity.this.runOnUiThread(new Runnable() {
|
|
|
+ @Override
|
|
|
+ public void run() {
|
|
|
+ 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(new Runnable() {
|
|
|
+ @Override
|
|
|
+ public void run() {
|
|
|
+ logView.append("Error while fetching " +files.peek().getValue().GetFilename() +": " +msg);
|
|
|
+ }
|
|
|
+ });
|
|
|
+
|
|
|
+ files.pop();
|
|
|
+ if (!files.empty()) {
|
|
|
+ fGitInterfage.FetchBlob(files.peek().getValue(), this);
|
|
|
+ } else {
|
|
|
+ resp.onResponse(null);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ };
|
|
|
+
|
|
|
+ DownloadNext(files, localVersion, downloader, resp);
|
|
|
+ }
|
|
|
+
|
|
|
+ void DownloadNext(Stack<Map.Entry<String, GitObject.GitBlob>> files, GitLocal localCache, GitInterface.OnResponseListener<byte[]> downloader, GitInterface.OnResponseListener<Void> resp) {
|
|
|
+ if (!files.empty()) {
|
|
|
+ if (files.peek().getValue() == null) {
|
|
|
+ // remove file
|
|
|
+ String filename = files.pop().getKey();
|
|
|
+ new File(getApplicationContext().getFilesDir().getAbsolutePath() +"/" +getResources().getString(R.string.data_dir_name) +filename).delete();
|
|
|
+ localCache.remove(filename);
|
|
|
+ DownloadNext(files, localCache, downloader, resp);
|
|
|
+ } else {
|
|
|
+ fGitInterfage.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(getApplicationContext().getFilesDir().getAbsolutePath() +"/" +getResources().getString(R.string.data_dir_name) +filename);
|
|
|
+ f.getParentFile().mkdirs();
|
|
|
+ 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.SetHashes(filename, blob.GetHash(), getMd5OfFile(f));
|
|
|
+ }
|
|
|
|
|
|
+ void PushBlobs(Map<String, GitObject.GitBlob> blobs, GitInterface.OnResponseListener<Void> resp) {
|
|
|
}
|
|
|
}
|