Fix some bugs in remote/sync (still not complete)
authorNiki Roo <niki@nikiroo.be>
Wed, 16 Mar 2016 19:40:00 +0000 (20:40 +0100)
committerNiki Roo <niki@nikiroo.be>
Wed, 16 Mar 2016 19:40:00 +0000 (20:40 +0100)
src/be/nikiroo/jvcard/BaseClass.java
src/be/nikiroo/jvcard/launcher/Main.java
src/be/nikiroo/jvcard/remote/Command.java
src/be/nikiroo/jvcard/remote/Server.java
src/be/nikiroo/jvcard/remote/Sync.java
src/be/nikiroo/jvcard/resources/remote.properties

index ed9a693a355b01a7df8a222ee3296b13cea07d50..0aa18e6ff66336a3f2afecacc9ba6fbe1b4a380b 100644 (file)
@@ -155,17 +155,20 @@ public abstract class BaseClass<E extends BaseClass<?>> implements List<E> {
                Collections.sort(other, comparator);
 
                boolean equ = true;
-               while (mine.size() > 0 || other.size() > 0) {
-                       E here = (mine.size() > 0) ? mine.remove(0) : null;
-                       E there = (other.size() > 0) ? other.remove(0) : null;
+               E here = mine.size() > 0 ? mine.remove(0) : null;
+               E there = other.size() > 0 ? other.remove(0) : null;
 
-                       if (here == null || comparator.compare(here, there) > 0) {
+               while (here != null || there != null) {
+                       if (here == null
+                                       || (there != null && comparator.compare(here, there) > 0)) {
                                if (added != null)
                                        added.add(there);
+                               there = null;
                                equ = false;
                        } else if (there == null || comparator.compare(here, there) < 0) {
                                if (removed != null)
                                        removed.add(here);
+                               here = null;
                                equ = false;
                        } else {
                                // they represent the same item
@@ -176,7 +179,14 @@ public abstract class BaseClass<E extends BaseClass<?>> implements List<E> {
                                                to.add(there);
                                        equ = false;
                                }
+                               here = null;
+                               there = null;
                        }
+
+                       if (here == null && mine.size() > 0)
+                               here = mine.remove(0);
+                       if (there == null && other.size() > 0)
+                               there = other.remove(0);
                }
 
                return equ;
index b0e3bd80ca43ffbb69247132ccb2c581fcd7b3cb..a9587d275151c7817250714d907f8fb5379cd1d0 100644 (file)
@@ -211,8 +211,7 @@ public class Main {
                                        System.err
                                                        .println("I/O Exception: Cannot start the server");
                                } else {
-                                       System.err.println("FATAL ERROR");
-                                       e.printStackTrace();
+                                       System.err.println("Remoting support not available");
                                        System.exit(ERR_INTERNAL);
                                }
                        }
@@ -232,8 +231,7 @@ public class Main {
                                        System.err
                                                        .println("I/O Exception: Cannot start the program with the given cards");
                                } else {
-                                       System.err.println("FATAL ERROR");
-                                       e.printStackTrace();
+                                       System.err.println("TUI support not available");
                                        System.exit(ERR_INTERNAL);
                                }
                        }
@@ -279,7 +277,7 @@ public class Main {
                } catch (IOException ioe) {
                        throw ioe;
                } catch (Exception e) {
-                       throw new IOException("Remoting not available", e);
+                       throw new IOException("Remoting support not available", e);
                }
 
                return card;
index 3a32f0c49c965388204441704665965dc68251b9..1132bb279f8014925c284e75c286636a9d260f9d 100644 (file)
@@ -15,6 +15,8 @@ public class Command {
                LIST,
                /** HELP about the protocol for interactive access */
                HELP,
+               /** SELECT a resource (a card) to work on */
+               SELECT,
                /** GET a remote card */
                GET_CARD,
                /**
index 2ad561977c589be31abbcfe8acc85eff51052689..79547c9ddc38cc10058da648f70e909158751ed4 100644 (file)
@@ -7,8 +7,10 @@ import java.net.Socket;
 import java.net.UnknownHostException;
 import java.security.InvalidParameterException;
 import java.util.Date;
+import java.util.HashMap;
 import java.util.LinkedList;
 import java.util.List;
+import java.util.Map;
 import java.util.ResourceBundle;
 
 import be.nikiroo.jvcard.Card;
@@ -44,6 +46,7 @@ public class Server implements Runnable {
        private List<SimpleSocket> clients = new LinkedList<SimpleSocket>();
 
        private Object updateLock = new Object();
+       private Map<File, Integer> updates = new HashMap<File, Integer>();
 
        /**
         * Create a new jVCard server on the given port.
@@ -169,7 +172,7 @@ public class Server implements Runnable {
        }
 
        /**
-        * Process a command.
+        * Process a first-level command.
         * 
         * @param s
         *            the {@link SimpleSocket} from which to get the command to
@@ -190,42 +193,52 @@ public class Server implements Runnable {
 
                boolean clientContinue = true;
 
-               System.out.println(s + " ->  " + verb);
+               System.out.println(s + " ->  " + verb
+                               + (cmd.getParam() == null ? "" : " " + cmd.getParam()));
 
                switch (verb) {
-               case STOP:
+               case STOP: {
                        clientContinue = false;
                        break;
-               case VERSION:
+               }
+               case VERSION: {
                        s.sendCommand(Verb.VERSION);
                        break;
-               case TIME:
+               }
+               case TIME: {
                        s.sendLine(StringUtils.fromTime(new Date().getTime()));
                        break;
-               case GET_CARD:
-                       synchronized (updateLock) {
-                               s.sendBlock(doGetCard(cmd.getParam()));
-                       }
-                       break;
-               case POST_CARD:
-                       synchronized (updateLock) {
-                               s.sendLine(doPostCard(cmd.getParam(), s.receiveBlock()));
-                       }
-                       break;
-               case PUT_CARD:
-                       synchronized (updateLock) {
-                               File vcf = getFile(cmd.getParam());
-                               if (vcf == null) {
-                                       System.err
-                                                       .println("Fail to update a card, file not available: "
-                                                                       + cmd.getParam());
-                                       clientContinue = false;
-                               } else {
-                                       Card card = new Card(vcf, Format.VCard21);
+               }
+               case SELECT: {
+                       String name = cmd.getParam();
+                       File file = new File(dataDir.getAbsolutePath() + File.separator
+                                       + name);
+                       if (name == null || name.length() == 0 || !file.exists()) {
+                               System.err
+                                               .println("SELECT: resource not found, closing connection: "
+                                                               + name);
+                               clientContinue = false;
+                       } else {
+                               synchronized (updateLock) {
+                                       for (File f : updates.keySet()) {
+                                               if (f.getCanonicalPath()
+                                                               .equals(file.getCanonicalPath())) {
+                                                       file = f;
+                                                       break;
+                                               }
+                                       }
+
+                                       if (!updates.containsKey(file))
+                                               updates.put(file, 0);
+                                       updates.put(file, updates.get(file) + 1);
+                               }
+
+                               synchronized (file) {
                                        try {
-                                               while (processContactCmd(s, card))
+                                               s.sendLine(StringUtils.fromTime(file.lastModified()));
+
+                                               while (processLockedCmd(s, name))
                                                        ;
-                                               card.save();
                                        } catch (InvalidParameterException e) {
                                                System.err
                                                                .println("Unsupported command received from a client connection, closing it: "
@@ -233,16 +246,19 @@ public class Server implements Runnable {
                                                clientContinue = false;
                                        }
                                }
+
+                               synchronized (updateLock) {
+                                       int num = updates.get(file) - 1;
+                                       if (num == 0) {
+                                               updates.remove(file);
+                                       } else {
+                                               updates.put(file, num);
+                                       }
+                               }
                        }
                        break;
-               case DELETE_CARD:
-                       // TODO
-                       System.err
-                                       .println("Unsupported command received from a client connection, closing it: "
-                                                       + verb);
-                       clientContinue = false;
-                       break;
-               case LIST:
+               }
+               case LIST: {
                        for (File file : dataDir.listFiles()) {
                                if (cmd.getParam() == null || cmd.getParam().length() == 0
                                                || file.getName().contains(cmd.getParam())) {
@@ -252,7 +268,8 @@ public class Server implements Runnable {
                        }
                        s.sendBlock();
                        break;
-               case HELP:
+               }
+               case HELP: {
                        // TODO: i18n
                        s.send("The following commands are available:");
                        s.send("- TIME: get the server time");
@@ -261,13 +278,95 @@ public class Server implements Runnable {
                        s.send("- VERSION/GET/PUT/POST/DELETE/STOP: TODO");
                        s.sendBlock();
                        break;
-               default:
+               }
+               default: {
+                       System.err
+                                       .println("Unsupported command received from a client connection, closing it: "
+                                                       + verb);
+                       clientContinue = false;
+                       break;
+               }
+               }
+
+               return clientContinue;
+       }
+
+       /**
+        * Process a subcommand while protected for resource <tt>name</tt>.
+        * 
+        * @param s
+        *            the {@link SimpleSocket} to process
+        * 
+        * @param name
+        *            the resource that is protected (and to target)
+        * 
+        * @return TRUE if the client is ready for another command, FALSE when the
+        *         client is done
+        * 
+        * @throws IOException
+        *             in case of IO error
+        * 
+        * @throw InvalidParameterException in case of invalid subcommand
+        */
+       private boolean processLockedCmd(SimpleSocket s, String name)
+                       throws IOException {
+               Command cmd = s.receiveCommand();
+               Command.Verb verb = cmd.getVerb();
+
+               if (verb == null)
+                       return false;
+
+               boolean clientContinue = true;
+
+               System.out.println(s + " ->  " + verb);
+
+               switch (verb) {
+               case GET_CARD: {
+                       s.sendBlock(doGetCard(name));
+                       break;
+               }
+               case POST_CARD: {
+                       s.sendLine(doPostCard(name, s.receiveBlock()));
+                       break;
+               }
+               case PUT_CARD: {
+                       File vcf = getFile(name);
+                       if (vcf == null) {
+                               System.err
+                                               .println("Fail to update a card, file not available: "
+                                                               + name);
+                               clientContinue = false;
+                       } else {
+                               Card card = new Card(vcf, Format.VCard21);
+                               try {
+                                       while (processContactCmd(s, card))
+                                               ;
+                                       card.save();
+                               } catch (InvalidParameterException e) {
+                                       System.err
+                                                       .println("Unsupported command received from a client connection, closing it: "
+                                                                       + verb + " (" + e.getMessage() + ")");
+                                       clientContinue = false;
+                               }
+                       }
+                       break;
+               }
+               case DELETE_CARD: {
+                       // TODO
                        System.err
                                        .println("Unsupported command received from a client connection, closing it: "
                                                        + verb);
                        clientContinue = false;
                        break;
                }
+               case SELECT: {
+                       clientContinue = false;
+                       break;
+               }
+               default: {
+                       throw new InvalidParameterException("command invalid here");
+               }
+               }
 
                return clientContinue;
        }
index 70bf93c3f7e942e74bc0576ab6b50459aa5ed4a3..53d96320d47a1416d8d1167e6963f6bf6bfdcfba 100644 (file)
@@ -141,7 +141,7 @@ public class Sync {
        }
 
        // return: synced or not
-       //TODO jDoc
+       // TODO jDoc
        public boolean sync(Card card, boolean force) throws UnknownHostException,
                        IOException {
 
@@ -171,133 +171,149 @@ 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;
-               Card local = null;
-               Card original = null;
-               if (tsOriginal != -1) {
-                       local = new Card(getCache(cacheDir), Format.VCard21);
-                       original = new Card(getCache(cacheDirOrig), Format.VCard21);
-                       localChanges = !local.isEquals(original, true);
-               }
+                       // Check changes
+                       boolean serverChanges = (tsServer - tsOriginal) > GRACE_TIME;
+                       boolean localChanges = false;
+                       Card local = null;
+                       Card original = null;
+                       if (tsOriginal != -1) {
+                               local = new Card(getCache(cacheDir), Format.VCard21);
+                               original = new Card(getCache(cacheDirOrig), Format.VCard21);
+                               localChanges = !local.isEquals(original, true);
+                       }
 
-               Verb action = null;
+                       Verb action = null;
 
-               // Sync to server if:
-               if (localChanges) {
-                       action = Verb.PUT_CARD;
-               }
+                       // Sync to server if:
+                       if (localChanges) {
+                               action = Verb.PUT_CARD;
+                       }
 
-               // Sync from/to server if
-               if (serverChanges && localChanges) {
-                       action = Verb.PUT_CARD;
-               }
+                       // Sync from server if:
+                       if (serverChanges) {
+                               // TODO: only sends changed cstate if serverChanges
+                               action = Verb.GET_CARD;
+                       }
 
-               // Sync from server if:
-               if (serverChanges) {
-                       // TODO: only sends changed cstate if serverChanges
-                       action = Verb.GET_CARD;
-               }
+                       // Sync from/to server if
+                       if (serverChanges && localChanges) {
+                               // TODO
+                               action = Verb.HELP;
+                       }
 
-               // PUT the whole file if:
-               if (tsServer == -1) {
-                       action = Verb.POST_CARD;
-               }
+                       // PUT the whole file if:
+                       if (tsServer == -1) {
+                               action = Verb.POST_CARD;
+                       }
 
-               // GET the whole file if:
-               if (tsOriginal == -1) {
-                       action = Verb.GET_CARD;
-               }
+                       // GET the whole file if:
+                       if (tsOriginal == -1) {
+                               action = Verb.GET_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_CARD:
-                               s.sendCommand(Verb.GET_CARD, name);
-                               List<String> data = s.receiveBlock();
-                               setLastModified(data.remove(0));
-                               Card server = new Card(Vcard21Parser.parseContact(data));
-                               card.replaceListContent(server);
-
-                               if (card.isDirty())
-                                       card.save();
-                               card.saveAs(getCache(cacheDirOrig), Format.VCard21);
-                               break;
-                       case POST_CARD:
-                               s.sendCommand(Verb.POST_CARD, name);
-                               s.sendBlock(Vcard21Parser.toStrings(card));
-                               card.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(Verb.PUT_CARD, name);
-                               for (Contact c : removed) {
-                                       s.sendCommand(Verb.DELETE_CONTACT, c.getId());
-                               }
-                               for (Contact c : added) {
-                                       s.sendCommand(Verb.POST_CONTACT, c.getId());
-                                       s.sendBlock(Vcard21Parser.toStrings(c, -1));
+                       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(Verb.SELECT, name);
+                               if (tsServer != StringUtils.toTime(s.receiveLine())) {
+                                       System.err.println("DEBUG: it changed. retry.");
+                                       s.sendCommand(Verb.SELECT);
+                                       s.close();
+                                       return sync(card, force);
                                }
-                               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(Verb.PUT_CONTACT, name);
-                                               for (Data d : subremoved) {
-                                                       s.sendCommand(Verb.DELETE_DATA, d.getContentState());
-                                               }
-                                               for (Data d : subadded) {
-                                                       s.sendCommand(Verb.POST_DATA, d.getContentState());
-                                                       s.sendBlock(Vcard21Parser.toStrings(d));
+
+                               switch (action) {
+                               case GET_CARD:
+                                       s.sendCommand(Verb.GET_CARD);
+                                       List<String> data = s.receiveBlock();
+                                       setLastModified(data.remove(0));
+                                       Card server = new Card(Vcard21Parser.parseContact(data));
+                                       card.replaceListContent(server);
+
+                                       if (card.isDirty())
+                                               card.save();
+                                       card.saveAs(getCache(cacheDirOrig), Format.VCard21);
+                                       break;
+                               case POST_CARD:
+                                       s.sendCommand(Verb.POST_CARD);
+                                       s.sendBlock(Vcard21Parser.toStrings(card));
+                                       card.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(Verb.PUT_CARD);
+
+                                       for (Contact c : removed) {
+                                               s.sendCommand(Verb.DELETE_CONTACT, c.getId());
+                                       }
+                                       for (Contact c : added) {
+                                               s.sendCommand(Verb.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(Verb.PUT_CONTACT, name);
+                                                       for (Data d : subremoved) {
+                                                               s.sendCommand(Verb.DELETE_DATA,
+                                                                               d.getContentState());
+                                                       }
+                                                       for (Data d : subadded) {
+                                                               s.sendCommand(Verb.POST_DATA,
+                                                                               d.getContentState());
+                                                               s.sendBlock(Vcard21Parser.toStrings(d));
+                                                       }
                                                }
                                        }
+
+                                       s.sendCommand(Verb.PUT_CARD);
+                                       break;
+                               default:
+                                       // TODO
+                                       throw new IOException(action
+                                                       + " operation not supported yet :(");
                                }
-                               s.sendCommand(Verb.PUT_CARD);
-                               break;
-                       default:
-                               // TODO
-                               throw new IOException(action
-                                               + " operation not supported yet :(");
+
+                               s.sendCommand(Verb.SELECT);
                        }
+               } catch (IOException e) {
+                       throw e;
+               } catch (Exception e) {
+                       e.printStackTrace();
+                       return false;
+               } finally {
+                       s.close();
                }
 
-               s.close();
-
                return true;
        }
 
index d6c9e726e4dfa25da3927ffda510b09a36dd5a4d..f7a78c4f07e44a3a50b8a01e48f0cda805060e64 100644 (file)
@@ -6,7 +6,7 @@
 ###############
 
 # when starting as a jVCard remote server, where to look for data:
-SERVER_DATA_PATH = /tmp/server/
+SERVER_DATA_PATH = /tmp/client/original/
 
 ###############
 ### Client: ###