Remoting: lot of fixes
[jvcard.git] / src / be / nikiroo / jvcard / remote / Sync.java
index 9fff9b8985056039e42c14c51dfb7013efb1412f..29344d1e33db3ad6a01341ba56d212347280c930 100644 (file)
@@ -12,17 +12,20 @@ import java.io.OutputStreamWriter;
 import java.net.Socket;
 import java.net.UnknownHostException;
 import java.security.InvalidParameterException;
-import java.time.LocalDate;
+import java.util.HashMap;
+import java.util.LinkedList;
 import java.util.List;
+import java.util.Map;
 import java.util.MissingResourceException;
 import java.util.ResourceBundle;
 
 import be.nikiroo.jvcard.Card;
+import be.nikiroo.jvcard.Contact;
+import be.nikiroo.jvcard.Data;
 import be.nikiroo.jvcard.parsers.Format;
-import be.nikiroo.jvcard.parsers.Parser;
-import be.nikiroo.jvcard.remote.Command.Verb;
+import be.nikiroo.jvcard.parsers.Vcard21Parser;
 import be.nikiroo.jvcard.resources.Bundles;
-import be.nikiroo.jvcard.tui.StringUtils;
+import be.nikiroo.jvcard.resources.StringUtils;
 
 /**
  * This class will synchronise {@link Card}s between a local instance an a
@@ -121,7 +124,7 @@ public class Sync {
                        SimpleSocket s = new SimpleSocket(new Socket(host, port),
                                        "check avail client");
                        s.open(true);
-                       s.sendCommand(Verb.LIST);
+                       s.sendCommand(Command.LIST_CARD);
                        List<String> timestampedFiles = s.receiveBlock();
                        s.close();
 
@@ -139,15 +142,18 @@ public class Sync {
        }
 
        // return: synced or not
-       public boolean sync(Card card, boolean force) throws UnknownHostException,
-                       IOException {
+       // TODO jDoc
+       public Card sync(boolean force) throws UnknownHostException, IOException {
 
                long tsOriginal = getLastModified();
 
+               Card local = new Card(getCache(cacheDir), Format.VCard21);
+               local.setRemote(true);
+
                // do NOT update unless we are in autoSync or forced mode or we don't
                // have the file on cache
                if (!autoSync && !force && tsOriginal != -1) {
-                       return false;
+                       return local;
                }
 
                SimpleSocket s = new SimpleSocket(new Socket(host, port), "sync client");
@@ -156,7 +162,7 @@ public class Sync {
                long tsServer = -1;
                try {
                        s.open(true);
-                       s.sendCommand(Verb.LIST);
+                       s.sendCommand(Command.LIST_CARD);
                        List<String> timestampedFiles = s.receiveBlock();
 
                        for (String timestampedFile : timestampedFiles) {
@@ -168,94 +174,210 @@ public class Sync {
                                        break;
                                }
                        }
-               } catch (IOException e) {
-                       s.close();
-                       throw e;
-               } catch (Exception e) {
-                       e.printStackTrace();
-                       s.close();
-                       return false;
-               }
 
-               // Error cases:
-               // - file not preset neither in cache nor on server
-               // - remote < previous
-               if ((tsServer == -1 && tsOriginal == -1)
-                               || (tsServer != -1 && tsOriginal != -1 && ((tsOriginal - tsServer) > GRACE_TIME))) {
-                       throw new IOException(
-                                       "The timestamps between server and client are invalid");
-               }
+                       // Error cases:
+                       // - file not preset neither in cache nor on server
+                       // - remote < previous
+                       if ((tsServer == -1 && tsOriginal == -1)
+                                       || (tsServer != -1 && tsOriginal != -1 && ((tsOriginal - tsServer) > GRACE_TIME))) {
+                               throw new IOException(
+                                               "The timestamps between server and client are invalid");
+                       }
 
-               // Check changes
-               boolean serverChanges = (tsServer - tsOriginal) > GRACE_TIME;
-               boolean localChanges = false;
-               if (tsOriginal != -1) {
-                       Card local = new Card(getCache(cacheDir), Format.VCard21);
-                       Card original = new Card(getCache(cacheDirOrig), Format.VCard21);
-                       localChanges = !local.isEquals(original, true);
-               }
+                       // Check changes
+                       boolean serverChanges = (tsServer - tsOriginal) > GRACE_TIME;
+                       boolean localChanges = false;
+                       Card original = null;
+                       if (tsOriginal != -1) {
+                               original = new Card(getCache(cacheDirOrig), Format.VCard21);
+                               localChanges = !local.isEquals(original, true);
+                       }
 
-               Verb action = null;
+                       Command action = null;
 
-               // Sync to server if:
-               if (localChanges) {
-                       // TODO: sync instead (with PUT)
-                       action = Verb.POST;
-               }
+                       // Sync to server if:
+                       if (localChanges) {
+                               action = Command.PUT_CARD;
+                       }
 
-               // Sync from server if
-               if (serverChanges && localChanges) {
-                       // TODO
-                       throw new IOException("Sync operation not supported yet :(");
-               }
+                       // Sync from server if:
+                       if (serverChanges) {
+                               action = Command.HASH_CONTACT;
+                       }
 
-               // PUT the whole file if:
-               if (tsServer == -1) {
-                       action = Verb.POST;
-               }
+                       // Sync from/to server if
+                       if (serverChanges && localChanges) {
+                               // TODO
+                               action = Command.HELP;
+                       }
 
-               // GET the whole file if:
-               if (tsOriginal == -1 || serverChanges) {
-                       action = Verb.GET;
-               }
+                       // PUT the whole file if:
+                       if (tsServer == -1) {
+                               action = Command.POST_CARD;
+                       }
 
-               System.err.println("remote: " + (tsServer / 1000) % 1000 + " ("
-                               + tsServer + ")");
-               System.err.println("previous: " + (tsOriginal / 1000) % 1000 + " ("
-                               + tsOriginal + ")");
-               System.err.println("local changes: " + localChanges);
-               System.err.println("server changes: " + serverChanges);
-               System.err.println("choosen action: " + action);
-
-               if (action != null) {
-                       switch (action) {
-                       case GET:
-                               s.sendCommand(Verb.GET, name);
-                               List<String> data = s.receiveBlock();
-                               setLastModified(data.remove(0));
-                               Card server = new Card(Parser.parse(data, Format.VCard21));
-                               card.replaceListContent(server);
-
-                               if (card.isDirty())
-                                       card.save();
-                               card.saveAs(getCache(cacheDirOrig), Format.VCard21);
-                               break;
-                       case POST:
-                               s.sendCommand(Verb.POST, name);
-                               s.sendLine(card.toString(Format.VCard21));
-                               card.saveAs(getCache(cacheDirOrig), Format.VCard21);
-                               setLastModified(s.receiveLine());
-                               break;
-                       default:
-                               // TODO
-                               throw new IOException(action
-                                               + " operation not supported yet :(");
+                       // GET the whole file if:
+                       if (tsOriginal == -1) {
+                               action = Command.GET_CARD;
                        }
-               }
 
-               s.close();
+                       System.err.println("remote: " + (tsServer / 1000) % 1000 + " ("
+                                       + tsServer + ")");
+                       System.err.println("previous: " + (tsOriginal / 1000) % 1000 + " ("
+                                       + tsOriginal + ")");
+                       System.err.println("local changes: " + localChanges);
+                       System.err.println("server changes: " + serverChanges);
+                       System.err.println("choosen action: " + action);
+
+                       if (action != null) {
+                               s.sendCommand(Command.SELECT, name);
+                               if (tsServer != StringUtils.toTime(s.receiveLine())) {
+                                       System.err.println("DEBUG: it changed. retry.");
+                                       s.sendCommand(Command.SELECT);
+                                       s.close();
+                                       return sync(force);
+                               }
+
+                               switch (action) {
+                               case GET_CARD:
+                                       s.sendCommand(Command.GET_CARD);
+                                       List<String> data = s.receiveBlock();
+                                       setLastModified(data.remove(0));
+                                       Card server = new Card(Vcard21Parser.parseContact(data));
+                                       local.replaceListContent(server);
+
+                                       if (local.isDirty())
+                                               local.save();
+                                       local.saveAs(getCache(cacheDirOrig), Format.VCard21);
+                                       break;
+                               case POST_CARD:
+                                       s.sendCommand(Command.POST_CARD);
+                                       s.sendBlock(Vcard21Parser.toStrings(local));
+                                       local.saveAs(getCache(cacheDirOrig), Format.VCard21);
+                                       setLastModified(s.receiveLine());
+                                       break;
+                               case PUT_CARD: {
+                                       List<Contact> added = new LinkedList<Contact>();
+                                       List<Contact> removed = new LinkedList<Contact>();
+                                       List<Contact> from = new LinkedList<Contact>();
+                                       List<Contact> to = new LinkedList<Contact>();
+                                       original.compare(local, added, removed, from, to);
+
+                                       s.sendCommand(Command.PUT_CARD);
+
+                                       for (Contact c : removed) {
+                                               s.sendCommand(Command.DELETE_CONTACT, c.getId());
+                                       }
+                                       for (Contact c : added) {
+                                               s.sendCommand(Command.POST_CONTACT, c.getId());
+                                               s.sendBlock(Vcard21Parser.toStrings(c, -1));
+                                       }
+                                       if (from.size() > 0) {
+                                               for (int index = 0; index < from.size(); index++) {
+                                                       Contact f = from.get(index);
+                                                       Contact t = to.get(index);
+
+                                                       List<Data> subadded = new LinkedList<Data>();
+                                                       List<Data> subremoved = new LinkedList<Data>();
+                                                       f.compare(t, subadded, subremoved, subremoved,
+                                                                       subadded);
+                                                       s.sendCommand(Command.PUT_CONTACT, name);
+                                                       for (Data d : subremoved) {
+                                                               s.sendCommand(Command.DELETE_DATA,
+                                                                               d.getContentState());
+                                                       }
+                                                       for (Data d : subadded) {
+                                                               s.sendCommand(Command.POST_DATA,
+                                                                               d.getContentState());
+                                                               s.sendBlock(Vcard21Parser.toStrings(d));
+                                                       }
+                                               }
+                                       }
+
+                                       local.saveAs(getCache(cacheDirOrig), Format.VCard21);
+                                       s.sendCommand(Command.PUT_CARD);
+                                       setLastModified(s.receiveLine());
+
+                                       break;
+                               }
+                               case HASH_CONTACT: {
+                                       s.sendCommand(Command.PUT_CARD);
+
+                                       s.sendCommand(Command.LIST_CONTACT);
+                                       Map<String, String> remote = new HashMap<String, String>();
+                                       for (String line : s.receiveBlock()) {
+                                               int indexSp = line.indexOf(" ");
+                                               String hash = line.substring(0, indexSp);
+                                               String uid = line.substring(indexSp + 1);
+
+                                               remote.put(uid, hash);
+                                       }
+
+                                       List<Contact> deleted = new LinkedList<Contact>();
+                                       List<Contact> changed = new LinkedList<Contact>();
+                                       List<String> added = new LinkedList<String>();
+
+                                       for (Contact c : local) {
+                                               String hash = remote.get(c.getId());
+                                               if (hash == null) {
+                                                       deleted.add(c);
+                                               } else if (!hash.equals(c.getContentState())) {
+                                                       changed.add(c);
+                                               }
+                                       }
+
+                                       for (String uid : remote.keySet()) {
+                                               if (local.getById(uid) == null)
+                                                       added.add(uid);
+                                       }
+
+                                       // process:
+
+                                       for (Contact c : deleted) {
+                                               c.delete();
+                                       }
+
+                                       for (String uid : added) {
+                                               s.sendCommand(Command.GET_CONTACT, uid);
+                                               for (Contact cc : Vcard21Parser.parseContact(s
+                                                               .receiveBlock())) {
+                                                       local.add(cc);
+                                               }
+                                       }
+
+                                       for (Contact c : changed) {
+                                               c.delete();
+                                               s.sendCommand(Command.GET_CONTACT, c.getId());
+                                               for (Contact cc : Vcard21Parser.parseContact(s
+                                                               .receiveBlock())) {
+                                                       local.add(cc);
+                                               }
+                                       }
+
+                                       local.save();
+                                       local.saveAs(getCache(cacheDirOrig), Format.VCard21);
+                                       s.sendCommand(Command.PUT_CARD);
+                                       setLastModified(s.receiveLine());
+                                       break;
+                               }
+                               default:
+                                       // TODO
+                                       throw new IOException(action
+                                                       + " operation not supported yet :(");
+                               }
+
+                               s.sendCommand(Command.SELECT);
+                       }
+               } catch (IOException e) {
+                       throw e;
+               } catch (Exception e) {
+                       e.printStackTrace();
+                       return local;
+               } finally {
+                       s.close();
+               }
 
-               return true;
+               return local;
        }
 
        /**