Browse Source

[add] git fetch remove deleted files

isundil 7 years ago
parent
commit
92e9d11ed4

+ 1 - 1
app/src/main/java/info/knacki/pass/git/DumbGitInterface.java

@@ -237,7 +237,7 @@ class DumbGitInterface implements GitInterface {
                     byte []sha1 = new byte[21];
                     System.arraycopy(data, i, sha1, 0, 21);
                     i += 21;
-                    tree.AddItem(GitObject.factory(new String(mode, Charset.defaultCharset()).toCharArray(), fileName, sha1ToString(sha1)));
+                    tree.AddItem(GitObject.factory(tree, new String(mode, Charset.defaultCharset()).toCharArray(), fileName, sha1ToString(sha1)));
                 }
             }
 

+ 94 - 0
app/src/main/java/info/knacki/pass/git/GitLocal.java

@@ -0,0 +1,94 @@
+package info.knacki.pass.git;
+
+import java.io.BufferedReader;
+import java.io.File;
+import java.io.FileNotFoundException;
+import java.io.FileReader;
+import java.io.FileWriter;
+import java.io.IOException;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Set;
+import java.util.logging.Logger;
+
+public class GitLocal {
+    class ControlSum {
+        public final String fSha1;
+        public final String fMd5;
+
+        ControlSum(String sha1, String md5) {
+            fSha1 = sha1;
+            fMd5 = md5;
+        }
+    }
+    Map<String, ControlSum> cache;
+
+    public GitLocal(File file) {
+        cache = new HashMap<>();
+        BufferedReader in;
+
+        try {
+            in = new BufferedReader(new FileReader(file));
+        }
+        catch (FileNotFoundException e) {
+            return;
+        }
+        String line;
+        try {
+            while ((line = in.readLine()) != null) {
+                int lineSep = line.indexOf(' ');
+                String sha1;
+                if (lineSep > -1) {
+                    sha1 = line.substring(0, lineSep);
+                    line = line.substring(lineSep +1);
+                    lineSep = line.indexOf(' ');
+                    if (lineSep > -1) {
+                        String md5 = line.substring(0, lineSep);
+                        cache.put(line.substring(lineSep +1), new ControlSum(sha1, md5));
+                    }
+                }
+            }
+            in.close();
+        }
+        catch (IOException e) {}
+    }
+
+    public String GetLocalHash(String key) {
+        ControlSum sums = cache.get(key);
+        return sums == null ? null : sums.fMd5;
+    }
+
+    public String GetRemoteHash(String key) {
+        ControlSum sums = cache.get(key);
+        return sums == null ? null : sums.fSha1;
+    }
+
+    public GitLocal SetHashes(String filename, String gitSha, String localMd5) {
+        ControlSum sums = cache.put(filename, new ControlSum(gitSha, localMd5));
+        return this;
+    }
+
+    public Set<String> FileNames() {
+        return cache.keySet();
+    }
+
+    public GitLocal remove(String filename) {
+        cache.remove(filename);
+        return this;
+    }
+
+    public void Write(File f) {
+        try {
+            f.createNewFile();
+            FileWriter writer = new FileWriter(f);
+            Logger.getAnonymousLogger().severe("Start writing cache hashes");
+            for (HashMap.Entry<String, ControlSum> i: cache.entrySet()) {
+                writer.write(i.getValue().fSha1 +" " +i.getValue().fMd5 +" " +i.getKey() +"\n");
+                Logger.getAnonymousLogger().severe(i.getValue().fSha1 +" " +i.getValue().fMd5 +" " +i.getKey() +"\n");
+            }
+            writer.close();
+        }
+        catch (IOException e)
+        {}
+    }
+}

+ 45 - 9
app/src/main/java/info/knacki/pass/git/entities/GitObject.java

@@ -8,6 +8,7 @@ import java.util.TreeSet;
 import java.util.logging.Logger;
 
 public class GitObject implements Comparable<GitObject> {
+    public final GitTree fParent;
     public final char[] fMode;
     public final String fName;
     public final String fSha1;
@@ -23,20 +24,20 @@ public class GitObject implements Comparable<GitObject> {
     }
 
     public static class GitBlob extends GitObject {
-        private GitBlob(char[] mode, String name, String sha1) {
-            super(mode, name, sha1);
+        private GitBlob(GitTree parent, char[] mode, String name, String sha1) {
+            super(parent, mode, name, sha1);
         }
     }
 
     public static class GitTree extends GitObject {
         protected HashMap<String, GitObject> fItems = null;
 
-        private GitTree(char[] mode, String name, String sha1) {
-            super(mode, name, sha1);
+        private GitTree(GitTree parent, char[] mode, String name, String sha1) {
+            super(parent, mode, name, sha1);
         }
 
         public GitTree(String sha1) {
-            super(new char[] { '4', '0', '0', '0', '0' }, "", sha1);
+            super(null, new char[] { '4', '0', '0', '0', '0' }, "", sha1);
         }
 
         public GitTree Initialize() {
@@ -52,6 +53,20 @@ public class GitObject implements Comparable<GitObject> {
             return fItems.get(name);
         }
 
+        public GitObject GetObjectFullPath(String path) {
+            Logger.getAnonymousLogger().severe("get git obj {" +path +"}");
+            if (path.charAt(0) == '/')
+                path = path.substring(1);
+            int nextSlash = path.indexOf('/');
+            String filename = nextSlash == -1 ? path : path.substring(nextSlash);
+            GitObject obj = GetObject(filename);
+            if (obj == null || (obj instanceof GitBlob && nextSlash != -1))
+                return null;
+            if (nextSlash == -1)
+                return obj;
+            return ((GitTree) obj).GetObjectFullPath(path.substring(nextSlash));
+        }
+
         public GitTree AddItem(GitObject item) {
             fItems.put(item.fName, item);
             return this;
@@ -91,19 +106,40 @@ public class GitObject implements Comparable<GitObject> {
                     objects.add((GitTree) i);
             return objects;
         }
+
+        public String GetPath() {
+            if (fParent != null)
+                return fParent.GetPath() +GetFilename() +"/";
+            return GetFilename() +"/";
+        }
+
+        private void FindAllBlobs(HashMap<String, GitBlob> data) {
+            String rootPath = GetPath();
+            for (GitBlob i: GetBlobs())
+                data.put(rootPath +i.GetFilename(), i);
+            for (GitTree i: GetTrees())
+                i.FindAllBlobs(data);
+        }
+
+        public HashMap<String, GitBlob> FindAllBlobs() {
+            HashMap<String, GitBlob> result = new HashMap<>();
+            FindAllBlobs(result);
+            return result;
+        }
     }
 
-    private GitObject(char[] mode, String name, String sha1) {
+    private GitObject(GitTree parent, char[] mode, String name, String sha1) {
+        fParent = parent;
         fMode = mode;
         fName = name;
         fSha1 = sha1;
     }
 
-    public static GitObject factory(char[] mode, String name, String sha1) {
+    public static GitObject factory(GitTree parent, char[] mode, String name, String sha1) {
         if (mode[0] == '4') {
-            return new GitTree(mode, name, sha1);
+            return new GitTree(parent, mode, name, sha1);
         }
-        return new GitBlob(mode, name.substring(1), sha1);
+        return new GitBlob(parent, mode, name.substring(1), sha1);
     }
 
     public String GetHash() {

+ 232 - 0
app/src/main/java/info/knacki/pass/ui/GitPullActivity.java

@@ -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) {
     }
 }

+ 6 - 0
app/src/main/java/info/knacki/pass/ui/MainActivity.java

@@ -114,6 +114,12 @@ public class MainActivity extends AppCompatActivity implements PasswordClickList
     void requestPermissions() {
     }
 
+    @Override
+    protected void onResume() {
+        super.onResume();
+        vPasswordListView.refresh();
+    }
+
     @Override
     protected void onCreate(Bundle savedInstanceState) {
         super.onCreate(savedInstanceState);

+ 8 - 4
app/src/main/res/layout/activity_git_pull.xml

@@ -11,9 +11,13 @@
         android:indeterminate="true"
         android:layout_width="match_parent"
         android:layout_height="wrap_content" />
-    <android.support.v7.widget.AppCompatTextView
-        android:id="@+id/logView"
+    <ScrollView
         android:layout_width="match_parent"
-        android:layout_height="match_parent"
-        android:inputType="textMultiLine" />
+        android:layout_height="wrap_content">
+        <android.support.v7.widget.AppCompatTextView
+            android:id="@+id/logView"
+            android:layout_width="match_parent"
+            android:layout_height="match_parent"
+            android:inputType="textMultiLine" />
+    </ScrollView>
 </LinearLayout>

+ 1 - 0
app/src/main/res/values/strings.xml

@@ -1,6 +1,7 @@
 <resources>
     <string name="app_name">pass</string>
     <string name="data_dir_name">pass</string>
+    <string name="data_git_local">gitfiles</string>
 
     <string-array name="pref_enctype_values">
         <item>0</item>