From 4298276a4b717753397508ce5432071827d5b294 Mon Sep 17 00:00:00 2001 From: Niki Roo Date: Wed, 16 Mar 2016 20:40:00 +0100 Subject: [PATCH] Fix some bugs in remote/sync (still not complete) --- src/be/nikiroo/jvcard/BaseClass.java | 18 +- src/be/nikiroo/jvcard/launcher/Main.java | 8 +- src/be/nikiroo/jvcard/remote/Command.java | 2 + src/be/nikiroo/jvcard/remote/Server.java | 173 ++++++++++--- src/be/nikiroo/jvcard/remote/Sync.java | 240 ++++++++++-------- .../jvcard/resources/remote.properties | 2 +- 6 files changed, 284 insertions(+), 159 deletions(-) diff --git a/src/be/nikiroo/jvcard/BaseClass.java b/src/be/nikiroo/jvcard/BaseClass.java index ed9a693..0aa18e6 100644 --- a/src/be/nikiroo/jvcard/BaseClass.java +++ b/src/be/nikiroo/jvcard/BaseClass.java @@ -155,17 +155,20 @@ public abstract class BaseClass> implements List { 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> implements List { 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; diff --git a/src/be/nikiroo/jvcard/launcher/Main.java b/src/be/nikiroo/jvcard/launcher/Main.java index b0e3bd8..a9587d2 100644 --- a/src/be/nikiroo/jvcard/launcher/Main.java +++ b/src/be/nikiroo/jvcard/launcher/Main.java @@ -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; diff --git a/src/be/nikiroo/jvcard/remote/Command.java b/src/be/nikiroo/jvcard/remote/Command.java index 3a32f0c..1132bb2 100644 --- a/src/be/nikiroo/jvcard/remote/Command.java +++ b/src/be/nikiroo/jvcard/remote/Command.java @@ -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, /** diff --git a/src/be/nikiroo/jvcard/remote/Server.java b/src/be/nikiroo/jvcard/remote/Server.java index 2ad5619..79547c9 100644 --- a/src/be/nikiroo/jvcard/remote/Server.java +++ b/src/be/nikiroo/jvcard/remote/Server.java @@ -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 clients = new LinkedList(); private Object updateLock = new Object(); + private Map updates = new HashMap(); /** * 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 name. + * + * @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; } diff --git a/src/be/nikiroo/jvcard/remote/Sync.java b/src/be/nikiroo/jvcard/remote/Sync.java index 70bf93c..53d9632 100644 --- a/src/be/nikiroo/jvcard/remote/Sync.java +++ b/src/be/nikiroo/jvcard/remote/Sync.java @@ -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 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 added = new LinkedList(); - List removed = new LinkedList(); - List from = new LinkedList(); - List to = new LinkedList(); - 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 subadded = new LinkedList(); - List subremoved = new LinkedList(); - 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 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 added = new LinkedList(); + List removed = new LinkedList(); + List from = new LinkedList(); + List to = new LinkedList(); + 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 subadded = new LinkedList(); + List subremoved = new LinkedList(); + 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; } diff --git a/src/be/nikiroo/jvcard/resources/remote.properties b/src/be/nikiroo/jvcard/resources/remote.properties index d6c9e72..f7a78c4 100644 --- a/src/be/nikiroo/jvcard/resources/remote.properties +++ b/src/be/nikiroo/jvcard/resources/remote.properties @@ -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: ### -- 2.27.0