From: Niki Roo Date: Sun, 13 Mar 2016 22:17:05 +0000 (+0100) Subject: Fix some sync issues X-Git-Tag: v1.0-beta3~15 X-Git-Url: http://git.nikiroo.be/?p=jvcard.git;a=commitdiff_plain;h=cf77cb3542f2aefbebdb9aa00b358dbeb4489a73 Fix some sync issues --- diff --git a/src/be/nikiroo/jvcard/BaseClass.java b/src/be/nikiroo/jvcard/BaseClass.java index b6be10e..abaa9cc 100644 --- a/src/be/nikiroo/jvcard/BaseClass.java +++ b/src/be/nikiroo/jvcard/BaseClass.java @@ -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> implements List { 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> implements List { * @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 other) { + public boolean isEquals(BaseClass 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> implements List { /** * Get the state of the current object, children not included. 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 and 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 diff --git a/src/be/nikiroo/jvcard/Card.java b/src/be/nikiroo/jvcard/Card.java index de5ab67..52eec8c 100644 --- a/src/be/nikiroo/jvcard/Card.java +++ b/src/be/nikiroo/jvcard/Card.java @@ -42,10 +42,8 @@ public class Card extends BaseClass { 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 { return false; this.replaceListContent(Parser.parse(file, format)); + lastModified = file.lastModified(); setPristine(); + return true; } @@ -150,7 +150,12 @@ public class Card extends BaseClass { * @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(); } /** diff --git a/src/be/nikiroo/jvcard/Contact.java b/src/be/nikiroo/jvcard/Contact.java index 4c6d5d4..87c1286 100644 --- a/src/be/nikiroo/jvcard/Contact.java +++ b/src/be/nikiroo/jvcard/Contact.java @@ -103,7 +103,14 @@ public class Contact extends BaseClass { */ 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(); } /** diff --git a/src/be/nikiroo/jvcard/parsers/AbookParser.java b/src/be/nikiroo/jvcard/parsers/AbookParser.java index 913db9b..344ae70 100644 --- a/src/be/nikiroo/jvcard/parsers/AbookParser.java +++ b/src/be/nikiroo/jvcard/parsers/AbookParser.java @@ -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. + * + *

+ * Note that the BKey is actually not used in Pine mode. + *

+ * + * @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 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 toStrings(Card card) { + List lines = new LinkedList(); 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; } } diff --git a/src/be/nikiroo/jvcard/parsers/Parser.java b/src/be/nikiroo/jvcard/parsers/Parser.java index 5cdd25f..1fa7c57 100644 --- a/src/be/nikiroo/jvcard/parsers/Parser.java +++ b/src/be/nikiroo/jvcard/parsers/Parser.java @@ -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 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 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: " diff --git a/src/be/nikiroo/jvcard/parsers/Vcard21Parser.java b/src/be/nikiroo/jvcard/parsers/Vcard21Parser.java index 225915d..4f94317 100644 --- a/src/be/nikiroo/jvcard/parsers/Vcard21Parser.java +++ b/src/be/nikiroo/jvcard/parsers/Vcard21Parser.java @@ -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 toStrings(Contact contact, int startingBKey) { + List lines = new LinkedList(); + + 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 toStrings(Card card) { + List lines = new LinkedList(); for (Contact contact : card) { - builder.append(toString(contact, -1)); + lines.addAll(toStrings(contact, -1)); } - builder.append("\r\n"); - - return builder.toString(); + return lines; } /** diff --git a/src/be/nikiroo/jvcard/remote/Server.java b/src/be/nikiroo/jvcard/remote/Server.java index 2ead82b..e90e42d 100644 --- a/src/be/nikiroo/jvcard/remote/Server.java +++ b/src/be/nikiroo/jvcard/remote/Server.java @@ -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 data) throws IOException { + private String doPostCard(String name, List 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 ""; } } diff --git a/src/be/nikiroo/jvcard/remote/Sync.java b/src/be/nikiroo/jvcard/remote/Sync.java index dbf0324..9fff9b8 100644 --- a/src/be/nikiroo/jvcard/remote/Sync.java +++ b/src/be/nikiroo/jvcard/remote/Sync.java @@ -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; } } diff --git a/src/be/nikiroo/jvcard/tui/StringUtils.java b/src/be/nikiroo/jvcard/tui/StringUtils.java index 569b06c..efbce6a 100644 --- a/src/be/nikiroo/jvcard/tui/StringUtils.java +++ b/src/be/nikiroo/jvcard/tui/StringUtils.java @@ -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; + } + } }