Fix some sync issues
authorNiki Roo <niki@nikiroo.be>
Sun, 13 Mar 2016 22:17:05 +0000 (23:17 +0100)
committerNiki Roo <niki@nikiroo.be>
Sun, 13 Mar 2016 22:17:05 +0000 (23:17 +0100)
src/be/nikiroo/jvcard/BaseClass.java
src/be/nikiroo/jvcard/Card.java
src/be/nikiroo/jvcard/Contact.java
src/be/nikiroo/jvcard/parsers/AbookParser.java
src/be/nikiroo/jvcard/parsers/Parser.java
src/be/nikiroo/jvcard/parsers/Vcard21Parser.java
src/be/nikiroo/jvcard/remote/Server.java
src/be/nikiroo/jvcard/remote/Sync.java
src/be/nikiroo/jvcard/tui/StringUtils.java

index b6be10e47795fd31d1169c07068eb4ca3963c6b2..abaa9ccbd37d04fe7a48fc8a2a57b8c59ebb267a 100644 (file)
@@ -9,6 +9,8 @@ import java.util.LinkedList;
 import java.util.List;
 import java.util.ListIterator;
 
+import be.nikiroo.jvcard.tui.StringUtils;
+
 /**
  * This class is basically a List with a parent and a "dirty" state check. It
  * sends all commands down to the initial list, but will mark itself and its
@@ -167,7 +169,7 @@ public abstract class BaseClass<E extends BaseClass<?>> implements List<E> {
                                equ = false;
                        } else {
                                // they represent the same item
-                               if (!((BaseClass) here).isEquals(there)) {
+                               if (!((BaseClass) here).isEquals(there, false)) {
                                        if (from != null)
                                                from.add(here);
                                        if (to != null)
@@ -206,32 +208,55 @@ public abstract class BaseClass<E extends BaseClass<?>> implements List<E> {
         * @param other
         *            the other instance
         * 
+        * @param contentOnly
+        *            do not check the state of the object itslef, only its content
+        * 
         * @return TRUE if they are equivalent
         */
        @SuppressWarnings({ "unchecked", "rawtypes" })
-       public boolean isEquals(BaseClass<E> other) {
+       public boolean isEquals(BaseClass<E> other, boolean contentOnly) {
                if (other == null)
                        return false;
 
                if (size() != other.size())
                        return false;
 
-               if (!isSame(other))
-                       return false;
+               if (!contentOnly) {
+                       if (!isSame(other))
+                               return false;
 
-               if (!getState().equals(other.getState()))
-                       return false;
+                       if (!getState().equals(other.getState()))
+                               return false;
+               }
 
                Collections.sort(list, comparator);
                Collections.sort(other.list, other.comparator);
                for (int index = 0; index < size(); index++) {
-                       if (!((BaseClass) get(index)).isEquals(other.get(index)))
+                       if (!((BaseClass) get(index)).isEquals(other.get(index), false))
                                return false;
                }
 
                return true;
        }
 
+       /**
+        * Get the recursive state of the current object, i.e., its children. It
+        * represents the full state information about this object's children. It
+        * does not check the state of the object itself.
+        * 
+        * @return a {@link String} representing the current content state of this
+        *         object, i.e., its children
+        */
+       public String getContentState() {
+               StringBuilder builder = new StringBuilder();
+
+               for (E child : this) {
+                       builder.append(child.getContentState());
+               }
+
+               return StringUtils.getHash(builder.toString());
+       }
+
        /**
         * Return the current ID of this object -- it is allowed to change over time
         * (so, do not cache it).
@@ -242,10 +267,8 @@ public abstract class BaseClass<E extends BaseClass<?>> implements List<E> {
 
        /**
         * Get the state of the current object, children <b>not included</b>. It
-        * represents the full state information about this object, that is, two
-        * objects with the same state (and class) must return TRUE if
-        * {@link BaseClass#isEquals(BaseClass)} is called <b>and</b> their children
-        * are equivalent.
+        * represents the full state information about this object, but do not check
+        * its children (see {@link BaseClass#getContentState()} for that).
         * 
         * @return a {@link String} representing the current state of this object,
         *         children not included
index de5ab67a604207f0d294f3d9a7652a460c4754a3..52eec8c6e4af0d5ac3ae67dbb94f7d63b93f0f37 100644 (file)
@@ -42,10 +42,8 @@ public class Card extends BaseClass<Contact> {
        public Card(File file, Format format) throws IOException {
                this(Parser.parse(file, format));
 
-               if (file != null) {
-                       if (file.exists()) {
-                               lastModified = file.lastModified();
-                       }
+               if (file != null && file.exists()) {
+                       lastModified = file.lastModified();
                }
 
                this.format = format;
@@ -136,7 +134,9 @@ public class Card extends BaseClass<Contact> {
                        return false;
 
                this.replaceListContent(Parser.parse(file, format));
+               lastModified = file.lastModified();
                setPristine();
+
                return true;
        }
 
@@ -150,7 +150,12 @@ public class Card extends BaseClass<Contact> {
         * @return the {@link String}
         */
        public String toString(Format format) {
-               return Parser.toString(this, format);
+               StringBuilder builder = new StringBuilder();
+               for (String line : Parser.toStrings(this, format)) {
+                       builder.append(line);
+                       builder.append("\r\n");
+               }
+               return builder.toString();
        }
 
        /**
index 4c6d5d443b9075d42823af98660df19c904c88d9..87c1286a77093d436148a1563015f789e1b9beb6 100644 (file)
@@ -103,7 +103,14 @@ public class Contact extends BaseClass<Data> {
         */
        public String toString(Format format, int startingBKey) {
                updateBKeys(false);
-               return Parser.toString(this, format, startingBKey);
+
+               StringBuilder builder = new StringBuilder();
+               for (String line : Parser.toStrings(this, format, startingBKey)) {
+                       builder.append(line);
+                       builder.append("\r\n");
+               }
+
+               return builder.toString();
        }
 
        /**
index 913db9b70528066c08ee990f392c66285f348302..344ae705401652905fe49563fa5fac4110be9f41 100644 (file)
@@ -1,5 +1,6 @@
 package be.nikiroo.jvcard.parsers;
 
+import java.util.Arrays;
 import java.util.LinkedList;
 import java.util.List;
 
@@ -33,8 +34,24 @@ public class AbookParser {
                return contacts;
        }
 
-       // -1 = no bkeys
-       public static String toString(Contact contact, int startingBKey) {
+       /**
+        * Return a {@link String} representation of the given {@link Card}, line by
+        * line.
+        * 
+        * <p>
+        * Note that the BKey is actually not used in Pine mode.
+        * </p>
+        * 
+        * @param card
+        *            the card to convert
+        * 
+        * @param startingBKey
+        *            the starting BKey number (all the other will follow) or -1 for
+        *            no BKey
+        * 
+        * @return the {@link String} representation
+        */
+       public static List<String> toStrings(Contact contact, int startingBKey) {
                // BKey is not used in pine mode
 
                StringBuilder builder = new StringBuilder();
@@ -84,16 +101,25 @@ public class AbookParser {
                // note: save as pine means normal LN, nor CRLN
                builder.append('\n');
 
-               return builder.toString();
+               return Arrays.asList(new String[] { builder.toString() });
        }
 
-       public static String toString(Card card) {
-               StringBuilder builder = new StringBuilder();
+       /**
+        * Return a {@link String} representation of the given {@link Card}, line by
+        * line.
+        * 
+        * @param card
+        *            the card to convert
+        * 
+        * @return the {@link String} representation
+        */
+       public static List<String> toStrings(Card card) {
+               List<String> lines = new LinkedList<String>();
 
                for (int index = 0; index < card.size(); index++) {
-                       builder.append(toString(card.get(index), -1));
+                       lines.addAll(toStrings(card.get(index), -1));
                }
 
-               return builder.toString();
+               return lines;
        }
 }
index 5cdd25fee995dfe10aef8c6d4a5d00dcc18aa5ab..1fa7c5749805746d58e41f9fae52e575e2cb529d 100644 (file)
@@ -72,13 +72,28 @@ public class Parser {
                }
        }
 
-       // -1 = no bkeys
-       public static String toString(Card card, Format format) {
+       /**
+        * Return a {@link String} representation of the given {@link Card}, line by
+        * line.
+        * 
+        * @param card
+        *            the card to convert
+        * 
+        * @param startingBKey
+        *            the starting BKey number (all the other will follow) or -1 for
+        *            no BKey
+        * 
+        * @param format
+        *            the output {@link Format} to use
+        * 
+        * @return the {@link String} representation
+        */
+       public static List<String> toStrings(Card card, Format format) {
                switch (format) {
                case VCard21:
-                       return Vcard21Parser.toString(card);
+                       return Vcard21Parser.toStrings(card);
                case Abook:
-                       return AbookParser.toString(card);
+                       return AbookParser.toStrings(card);
 
                default:
                        throw new InvalidParameterException("Unknown format: "
@@ -86,14 +101,29 @@ public class Parser {
                }
        }
 
-       // -1 = no bkeys
-       public static String toString(Contact contact, Format format,
+       /**
+        * Return a {@link String} representation of the given {@link Card}, line by
+        * line.
+        * 
+        * @param card
+        *            the card to convert
+        * 
+        * @param startingBKey
+        *            the starting BKey number (all the other will follow) or -1 for
+        *            no BKey
+        * 
+        * @param format
+        *            the output {@link Format} to use
+        * 
+        * @return the {@link String} representation
+        */
+       public static List<String> toStrings(Contact contact, Format format,
                        int startingBKey) {
                switch (format) {
                case VCard21:
-                       return Vcard21Parser.toString(contact, startingBKey);
+                       return Vcard21Parser.toStrings(contact, startingBKey);
                case Abook:
-                       return AbookParser.toString(contact, startingBKey);
+                       return AbookParser.toStrings(contact, startingBKey);
 
                default:
                        throw new InvalidParameterException("Unknown format: "
index 225915d7ca131a8a974a10ef7f1b9741d4b8cb2f..4f9431733f122cf3f7b819e22e98fdc3c443ca9b 100644 (file)
@@ -105,14 +105,24 @@ public class Vcard21Parser {
                return contacts;
        }
 
-       // -1 = no bkeys
-       public static String toString(Contact contact, int startingBKey) {
-               StringBuilder builder = new StringBuilder();
-
-               builder.append("BEGIN:VCARD");
-               builder.append("\r\n");
-               builder.append("VERSION:2.1");
-               builder.append("\r\n");
+       /**
+        * Return a {@link String} representation of the given {@link Card}, line by
+        * line.
+        * 
+        * @param card
+        *            the card to convert
+        * 
+        * @param startingBKey
+        *            the starting BKey number (all the other will follow) or -1 for
+        *            no BKey
+        * 
+        * @return the {@link String} representation
+        */
+       public static List<String> toStrings(Contact contact, int startingBKey) {
+               List<String> lines = new LinkedList<String>();
+
+               lines.add("BEGIN:VCARD");
+               lines.add("VERSION:2.1");
                for (Data data : contact) {
                        StringBuilder dataBuilder = new StringBuilder();
                        if (data.getGroup() != null && !data.getGroup().trim().equals("")) {
@@ -151,31 +161,38 @@ public class Vcard21Parser {
                                }
 
                                stop = Math.min(stop, dataBuilder.length());
-                               if (continuation)
-                                       builder.append(' ');
-                               builder.append(dataBuilder, 0, stop);
-                               builder.append("\r\n");
+                               if (continuation) {
+                                       lines.add(' ' + dataBuilder.substring(0, stop));
+                               } else {
+                                       lines.add(dataBuilder.substring(0, stop));
+                               }
                                dataBuilder.delete(0, stop);
 
                                continuation = true;
                        }
                }
-               builder.append("END:VCARD");
-               builder.append("\r\n");
+               lines.add("END:VCARD");
 
-               return builder.toString();
+               return lines;
        }
 
-       public static String toString(Card card) {
-               StringBuilder builder = new StringBuilder();
+       /**
+        * Return a {@link String} representation of the given {@link Card}, line by
+        * line.
+        * 
+        * @param card
+        *            the card to convert
+        * 
+        * @return the {@link String} representation
+        */
+       public static List<String> toStrings(Card card) {
+               List<String> lines = new LinkedList<String>();
 
                for (Contact contact : card) {
-                       builder.append(toString(contact, -1));
+                       lines.addAll(toStrings(contact, -1));
                }
 
-               builder.append("\r\n");
-
-               return builder.toString();
+               return lines;
        }
 
        /**
index 2ead82b19a90d6bf3a84b25c9c44d9394fd0d171..e90e42d6ff1e76c5a1caec0d91d2e5c2a80ec808 100644 (file)
@@ -195,8 +195,7 @@ public class Server implements Runnable {
                                        break;
                                case POST:
                                        synchronized (cardsLock) {
-                                               doPostCard(cmd.getParam(), s.receiveBlock());
-                                               s.sendBlock();
+                                               s.sendLine(doPostCard(cmd.getParam(), s.receiveBlock()));
                                                break;
                                        }
                                case LIST:
@@ -259,11 +258,7 @@ public class Server implements Runnable {
 
                                // timestamp:
                                lines.add(StringUtils.fromTime(card.getLastModified()));
-
-                               // TODO: !!! fix this !!!
-                               for (String line : card.toString(Format.VCard21).split("\r\n")) {
-                                       lines.add(line);
-                               }
+                               lines.addAll(Parser.toStrings(card, Format.VCard21));
                        }
                }
 
@@ -278,16 +273,23 @@ public class Server implements Runnable {
         * @param data
         *            the data to save
         * 
+        * @return the date of last modification
+        * 
         * @throws IOException
         *             in case of error
         */
-       private void doPostCard(String name, List<String> data) throws IOException {
+       private String doPostCard(String name, List<String> data)
+                       throws IOException {
                if (name != null && name.length() > 0) {
                        File vcf = new File(dataDir.getAbsolutePath() + File.separator
                                        + name);
 
                        Card card = new Card(Parser.parse(data, Format.VCard21));
                        card.saveAs(vcf, Format.VCard21);
+
+                       return StringUtils.fromTime(vcf.lastModified());
                }
+
+               return "";
        }
 }
index dbf03242fb3e82083a6ef601ee50b3edb66ad975..9fff9b8985056039e42c14c51dfb7013efb1412f 100644 (file)
@@ -12,6 +12,7 @@ import java.io.OutputStreamWriter;
 import java.net.Socket;
 import java.net.UnknownHostException;
 import java.security.InvalidParameterException;
+import java.time.LocalDate;
 import java.util.List;
 import java.util.MissingResourceException;
 import java.util.ResourceBundle;
@@ -191,7 +192,7 @@ public class Sync {
                if (tsOriginal != -1) {
                        Card local = new Card(getCache(cacheDir), Format.VCard21);
                        Card original = new Card(getCache(cacheDirOrig), Format.VCard21);
-                       localChanges = !local.isEquals(original);
+                       localChanges = !local.isEquals(original, true);
                }
 
                Verb action = null;
@@ -234,6 +235,7 @@ public class Sync {
                                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);
@@ -241,6 +243,8 @@ public class Sync {
                        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
@@ -294,7 +298,7 @@ public class Sync {
                        return StringUtils.toTime(line);
                } catch (FileNotFoundException e) {
                        return -1;
-               } catch (IOException e) {
+               } catch (Exception e) {
                        return -1;
                }
        }
index 569b06cf3b60edf4f15587c3d9e80e2baaec81b3..efbce6af8a09027b941f3f1dc58344f5a88aef26 100644 (file)
@@ -1,5 +1,7 @@
 package be.nikiroo.jvcard.tui;
 
+import java.security.MessageDigest;
+import java.security.NoSuchAlgorithmException;
 import java.text.Normalizer;
 import java.text.Normalizer.Form;
 import java.text.ParseException;
@@ -170,4 +172,35 @@ public class StringUtils {
                        return -1;
                }
        }
+
+       /**
+        * Return a hash of the given {@link String}.
+        * 
+        * @param input
+        *            the input data
+        * 
+        * @return the hash
+        */
+       static public String getHash(String input) {
+               try {
+                       MessageDigest md = MessageDigest.getInstance("MD5");
+                       md.update(input.getBytes());
+                       byte byteData[] = md.digest();
+
+                       StringBuffer hexString = new StringBuffer();
+                       for (int i = 0; i < byteData.length; i++) {
+                               String hex = Integer.toHexString(0xff & byteData[i]);
+                               if (hex.length() == 1)
+                                       hexString.append('0');
+                               hexString.append(hex);
+                       }
+
+                       return hexString.toString();
+               } catch (NoSuchAlgorithmException e) {
+                       // all JVM most probably have an MD5 implementation, but even if
+                       // not, returning the input is "correct", if inefficient and
+                       // unsecure
+                       return input;
+               }
+       }
 }