From 0b6140e4a200c4952c9dc003d8389f375191564e Mon Sep 17 00:00:00 2001 From: Niki Roo Date: Mon, 14 Mar 2016 21:40:09 +0100 Subject: [PATCH] Update on remote-server --- src/be/nikiroo/jvcard/BaseClass.java | 44 +- src/be/nikiroo/jvcard/Card.java | 4 +- .../nikiroo/jvcard/parsers/AbookParser.java | 12 +- src/be/nikiroo/jvcard/parsers/Parser.java | 14 +- .../nikiroo/jvcard/parsers/Vcard21Parser.java | 232 ++++++----- src/be/nikiroo/jvcard/remote/Command.java | 30 +- src/be/nikiroo/jvcard/remote/Server.java | 390 ++++++++++++++---- src/be/nikiroo/jvcard/remote/Sync.java | 78 +++- 8 files changed, 586 insertions(+), 218 deletions(-) diff --git a/src/be/nikiroo/jvcard/BaseClass.java b/src/be/nikiroo/jvcard/BaseClass.java index abaa9cc..2686390 100644 --- a/src/be/nikiroo/jvcard/BaseClass.java +++ b/src/be/nikiroo/jvcard/BaseClass.java @@ -241,20 +241,37 @@ public abstract class BaseClass> implements List { /** * 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. + * represents the full state information about this object's children. * * @return a {@link String} representing the current content state of this - * object, i.e., its children + * object, i.e., its children included */ public String getContentState() { StringBuilder builder = new StringBuilder(); + buildContentStateRaw(builder); + return StringUtils.getHash(builder.toString()); + } + /** + * Return the (first) child element with the given ID or NULL if not found. + * + * @param id + * the id to look for + * + * @return the child element or NULL + */ + public E getById(String id) { for (E child : this) { - builder.append(child.getContentState()); + if (id == null) { + if (child.getId() == null) + return child; + } else { + if (id.equals(child.getId())) + return child; + } } - return StringUtils.getHash(builder.toString()); + return null; } /** @@ -275,6 +292,23 @@ public abstract class BaseClass> implements List { */ abstract public String getState(); + /** + * Get the recursive state of the current object, i.e., its children. It + * represents the full state information about this object's children. + * + * It is not hashed. + * + * @param builder + * the {@link StringBuilder} that will represent the current + * content state of this object, i.e., its children included + */ + void buildContentStateRaw(StringBuilder builder) { + builder.append(getState()); + for (E child : this) { + child.buildContentStateRaw(builder); + } + } + /** * Notify that this element has unsaved changes. */ diff --git a/src/be/nikiroo/jvcard/Card.java b/src/be/nikiroo/jvcard/Card.java index 52eec8c..d550ad4 100644 --- a/src/be/nikiroo/jvcard/Card.java +++ b/src/be/nikiroo/jvcard/Card.java @@ -40,7 +40,7 @@ public class Card extends BaseClass { * if format is NULL */ public Card(File file, Format format) throws IOException { - this(Parser.parse(file, format)); + this(Parser.parseContact(file, format)); if (file != null && file.exists()) { lastModified = file.lastModified(); @@ -133,7 +133,7 @@ public class Card extends BaseClass { if (file == null) return false; - this.replaceListContent(Parser.parse(file, format)); + this.replaceListContent(Parser.parseContact(file, format)); lastModified = file.lastModified(); setPristine(); diff --git a/src/be/nikiroo/jvcard/parsers/AbookParser.java b/src/be/nikiroo/jvcard/parsers/AbookParser.java index 344ae70..cb4f667 100644 --- a/src/be/nikiroo/jvcard/parsers/AbookParser.java +++ b/src/be/nikiroo/jvcard/parsers/AbookParser.java @@ -9,7 +9,17 @@ import be.nikiroo.jvcard.Contact; import be.nikiroo.jvcard.Data; public class AbookParser { - public static List parse(List lines) { + /** + * Load the given data from under the given {@link Format}. + * + * @param lines + * the input to load from + * @param format + * the {@link Format} to load as + * + * @return the list of elements + */ + public static List parseContact(List lines) { List contacts = new LinkedList(); for (String line : lines) { diff --git a/src/be/nikiroo/jvcard/parsers/Parser.java b/src/be/nikiroo/jvcard/parsers/Parser.java index 1fa7c57..ceb35dc 100644 --- a/src/be/nikiroo/jvcard/parsers/Parser.java +++ b/src/be/nikiroo/jvcard/parsers/Parser.java @@ -28,7 +28,7 @@ public class Parser { * @throws IOException * in case of IO error */ - public static List parse(File file, Format format) + public static List parseContact(File file, Format format) throws IOException { List lines = null; @@ -46,7 +46,7 @@ public class Parser { if (lines == null) return new LinkedList(); - return parse(lines, format); + return parseContact(lines, format); } /** @@ -59,12 +59,12 @@ public class Parser { * * @return the list of elements */ - public static List parse(List lines, Format format) { + public static List parseContact(List lines, Format format) { switch (format) { case VCard21: - return Vcard21Parser.parse(lines); + return Vcard21Parser.parseContact(lines); case Abook: - return AbookParser.parse(lines); + return AbookParser.parseContact(lines); default: throw new InvalidParameterException("Unknown format: " @@ -79,10 +79,6 @@ public class Parser { * @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 * diff --git a/src/be/nikiroo/jvcard/parsers/Vcard21Parser.java b/src/be/nikiroo/jvcard/parsers/Vcard21Parser.java index 4f94317..7d0f5e8 100644 --- a/src/be/nikiroo/jvcard/parsers/Vcard21Parser.java +++ b/src/be/nikiroo/jvcard/parsers/Vcard21Parser.java @@ -10,10 +10,20 @@ import be.nikiroo.jvcard.Data; import be.nikiroo.jvcard.TypeInfo; public class Vcard21Parser { - public static List parse(Iterable textData) { + /** + * Load the given data from under the given {@link Format}. + * + * @param lines + * the input to load from + * @param format + * the {@link Format} to load as + * + * @return the list of elements + */ + public static List parseContact(Iterable textData) { Iterator lines = textData.iterator(); List contacts = new LinkedList(); - List datas = null; + List datas = null; String nextRawLine = null; if (lines.hasNext()) { @@ -43,14 +53,14 @@ public class Vcard21Parser { String line = rawLine.toString(); if (line.equals("BEGIN:VCARD")) { - datas = new LinkedList(); + datas = new LinkedList(); } else if (line.equals("END:VCARD")) { if (datas == null) { // BAD INPUT FILE. IGNORE. System.err .println("VCARD Parser warning: END:VCARD seen before any VCARD:BEGIN"); } else { - contacts.add(new Contact(datas)); + contacts.add(new Contact(parseData(datas))); } } else { if (datas == null) { @@ -58,51 +68,69 @@ public class Vcard21Parser { System.err .println("VCARD Parser warning: data seen before any VCARD:BEGIN"); } else { - List types = new LinkedList(); - String name = ""; - String value = ""; - String group = ""; - - if (line.contains(":")) { - int colIndex = line.indexOf(':'); - String rest = line.substring(0, colIndex); - value = line.substring(colIndex + 1); - - if (rest.contains(";")) { - String tab[] = rest.split(";"); - name = tab[0]; - - for (int i = 1; i < tab.length; i++) { - if (tab[i].contains("=")) { - int equIndex = tab[i].indexOf('='); - String tname = tab[i] - .substring(0, equIndex); - String tvalue = tab[i] - .substring(equIndex + 1); - types.add(new TypeInfo(tname, tvalue)); - } else { - types.add(new TypeInfo(tab[i], "")); - } - } + datas.add(line); + } + } + } + + return contacts; + } + + /** + * Load the given data from under the given {@link Format}. + * + * @param lines + * the input to load from + * @param format + * the {@link Format} to load as + * + * @return the list of elements + */ + public static List parseData(Iterable textData) { + List datas = new LinkedList(); + + for (String line : textData) { + List types = new LinkedList(); + String name = ""; + String value = ""; + String group = ""; + + if (line.contains(":")) { + int colIndex = line.indexOf(':'); + String rest = line.substring(0, colIndex); + value = line.substring(colIndex + 1); + + if (rest.contains(";")) { + String tab[] = rest.split(";"); + name = tab[0]; + + for (int i = 1; i < tab.length; i++) { + if (tab[i].contains("=")) { + int equIndex = tab[i].indexOf('='); + String tname = tab[i].substring(0, equIndex); + String tvalue = tab[i].substring(equIndex + 1); + types.add(new TypeInfo(tname, tvalue)); } else { - name = rest; + types.add(new TypeInfo(tab[i], "")); } - } else { - name = line; - } - - if (name.contains(".")) { - int dotIndex = name.indexOf('.'); - group = name.substring(0, dotIndex); - name = name.substring(dotIndex + 1); } - - datas.add(new Data(types, name, value, group)); + } else { + name = rest; } + } else { + name = line; + } + + if (name.contains(".")) { + int dotIndex = name.indexOf('.'); + group = name.substring(0, dotIndex); + name = name.substring(dotIndex + 1); } + + datas.add(new Data(types, name, value, group)); } - return contacts; + return datas; } /** @@ -112,6 +140,25 @@ public class Vcard21Parser { * @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) { + lines.addAll(toStrings(contact, -1)); + } + + return lines; + } + + /** + * Return a {@link String} representation of the given {@link Contact}, line + * by line. + * + * @param card + * the contact to convert + * * @param startingBKey * the starting BKey number (all the other will follow) or -1 for * no BKey @@ -124,52 +171,7 @@ public class Vcard21Parser { 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("")) { - dataBuilder.append(data.getGroup().trim()); - dataBuilder.append('.'); - } - dataBuilder.append(data.getName()); - for (TypeInfo type : data) { - dataBuilder.append(';'); - dataBuilder.append(type.getName()); - if (type.getValue() != null - && !type.getValue().trim().equals("")) { - dataBuilder.append('='); - dataBuilder.append(type.getValue()); - } - } - dataBuilder.append(':'); - - // TODO: bkey! - dataBuilder.append(data.getValue()); - - // RFC says: Content lines SHOULD be folded to a maximum width of 75 - // octets -> since it is SHOULD, we will just cut it as 74/75 chars - // depending if the last one fits in one char (note: chars != octet) - boolean continuation = false; - while (dataBuilder.length() > 0) { - int stop = 74; - if (continuation) - stop--; // the space takes 1 - if (dataBuilder.length() > stop) { - char car = dataBuilder.charAt(stop - 1); - // RFC forbids cutting a character in 2 - if (Character.isHighSurrogate(car)) { - stop++; - } - } - - stop = Math.min(stop, dataBuilder.length()); - if (continuation) { - lines.add(' ' + dataBuilder.substring(0, stop)); - } else { - lines.add(dataBuilder.substring(0, stop)); - } - dataBuilder.delete(0, stop); - - continuation = true; - } + lines.addAll(toStrings(data)); } lines.add("END:VCARD"); @@ -177,19 +179,61 @@ public class Vcard21Parser { } /** - * Return a {@link String} representation of the given {@link Card}, line by + * Return a {@link String} representation of the given {@link Data}, line by * line. * - * @param card - * the card to convert + * @param data + * the data to convert * * @return the {@link String} representation */ - public static List toStrings(Card card) { + public static List toStrings(Data data) { List lines = new LinkedList(); - for (Contact contact : card) { - lines.addAll(toStrings(contact, -1)); + StringBuilder dataBuilder = new StringBuilder(); + if (data.getGroup() != null && !data.getGroup().trim().equals("")) { + dataBuilder.append(data.getGroup().trim()); + dataBuilder.append('.'); + } + dataBuilder.append(data.getName()); + for (TypeInfo type : data) { + dataBuilder.append(';'); + dataBuilder.append(type.getName()); + if (type.getValue() != null && !type.getValue().trim().equals("")) { + dataBuilder.append('='); + dataBuilder.append(type.getValue()); + } + } + dataBuilder.append(':'); + + // TODO: bkey! + dataBuilder.append(data.getValue()); + + // RFC says: Content lines SHOULD be folded to a maximum width of 75 + // octets -> since it is SHOULD, we will just cut it as 74/75 chars + // depending if the last one fits in one char (note: chars != octet) + boolean continuation = false; + while (dataBuilder.length() > 0) { + int stop = 74; + if (continuation) + stop--; // the space takes 1 + if (dataBuilder.length() > stop) { + char car = dataBuilder.charAt(stop - 1); + // RFC forbids cutting a character in 2 + if (Character.isHighSurrogate(car)) { + stop++; + } + } + + stop = Math.min(stop, dataBuilder.length()); + if (continuation) { + lines.add(' ' + dataBuilder.substring(0, stop)); + } else { + lines.add(dataBuilder.substring(0, stop)); + } + dataBuilder.delete(0, stop); + + continuation = true; } return lines; diff --git a/src/be/nikiroo/jvcard/remote/Command.java b/src/be/nikiroo/jvcard/remote/Command.java index 0fb0a73..3a32f0c 100644 --- a/src/be/nikiroo/jvcard/remote/Command.java +++ b/src/be/nikiroo/jvcard/remote/Command.java @@ -15,14 +15,34 @@ public class Command { LIST, /** HELP about the protocol for interactive access */ HELP, + /** GET a remote card */ + GET_CARD, + /** + * PUT mode activation toggle for a card on the remote server (you can + * issue *_CONTACT commands when in PUT mode) + */ + PUT_CARD, + /** POST a new card to the remote server */ + POST_CARD, + /** DELETE an existing contact from the remote server */ + DELETE_CARD, /** GET a remote contact */ - GET, - /** PUT a new contact to the remote server or update an existing one */ - PUT, + GET_CONTACT, + /** + * PUT mode activation toggle for a contact on the remote server (you + * can issue *_DATA commands when in PUT mode) + */ + PUT_CONTACT, /** POST a new contact to the remote server */ - POST, + POST_CONTACT, /** DELETE an existing contact from the remote server */ - DELETE, + DELETE_CONTACT, + /** GET a remote data */ + GET_DATA, + /** POST a new data to the remote server */ + POST_DATA, + /** DELETE an existing data from the remote server */ + DELETE_DATA, } private Verb verb; diff --git a/src/be/nikiroo/jvcard/remote/Server.java b/src/be/nikiroo/jvcard/remote/Server.java index e90e42d..4b99365 100644 --- a/src/be/nikiroo/jvcard/remote/Server.java +++ b/src/be/nikiroo/jvcard/remote/Server.java @@ -5,14 +5,17 @@ import java.io.IOException; import java.net.ServerSocket; import java.net.Socket; import java.net.UnknownHostException; +import java.security.InvalidParameterException; import java.util.Date; import java.util.LinkedList; import java.util.List; 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.parsers.Vcard21Parser; import be.nikiroo.jvcard.remote.Command.Verb; import be.nikiroo.jvcard.resources.Bundles; import be.nikiroo.jvcard.tui.StringUtils; @@ -40,7 +43,7 @@ public class Server implements Runnable { private Object clientsLock = new Object(); private List clients = new LinkedList(); - private Object cardsLock = new Object(); + private Object updateLock = new Object(); public static void main(String[] args) throws IOException { Server server = new Server(4444); @@ -124,7 +127,21 @@ public class Server implements Runnable { new Thread(new Runnable() { @Override public void run() { - accept(new SimpleSocket(s, "[request]")); + SimpleSocket ss = new SimpleSocket(s, "[request]"); + + addClient(ss); + try { + ss.open(false); + + while (processCmd(ss)) + ; + + } catch (IOException e) { + e.printStackTrace(); + } finally { + ss.close(); + } + removeClient(ss); } }).start(); } catch (IOException ioe) { @@ -157,82 +174,273 @@ public class Server implements Runnable { } /** - * Accept a client and process it. + * Process a command. * * @param s - * the client to process + * the {@link SimpleSocket} from which to get the command to + * process + * + * @return TRUE if the client is ready for another command, FALSE when the + * client exited + * + * @throws IOException + * in case of IO error */ - private void accept(SimpleSocket s) { - addClient(s); - - try { - s.open(false); - - boolean clientStop = false; - while (!clientStop) { - Command cmd = s.receiveCommand(); - Command.Verb verb = cmd.getVerb(); - - if (verb == null) - break; - - System.out.println(s + " -> " + verb); - - switch (verb) { - case STOP: - clientStop = true; - break; - case VERSION: - s.sendCommand(Verb.VERSION); - break; - case TIME: - s.sendLine(StringUtils.fromTime(new Date().getTime())); - break; - case GET: - synchronized (cardsLock) { - s.sendBlock(doGetCard(cmd.getParam())); - } - break; - case POST: - synchronized (cardsLock) { - s.sendLine(doPostCard(cmd.getParam(), s.receiveBlock())); - break; - } - case LIST: - for (File file : dataDir.listFiles()) { - if (cmd.getParam() == null - || cmd.getParam().length() == 0 - || file.getName().contains(cmd.getParam())) { - s.send(StringUtils.fromTime(file.lastModified()) - + " " + file.getName()); - } - } - s.sendBlock(); - break; - case HELP: - // TODO: i18n - s.send("The following commands are available:"); - s.send("- TIME: get the server time"); - s.send("- HELP: this help screen"); - s.send("- LIST: list the available cards on this server"); - s.send("- VERSION/GET/PUT/POST/DELETE/STOP: TODO"); - s.sendBlock(); - break; - default: + private boolean processCmd(SimpleSocket s) 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 STOP: + clientContinue = false; + break; + case VERSION: + s.sendCommand(Verb.VERSION); + break; + 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("Unsupported command received from a client connection, closing it: " - + verb); - clientStop = true; - break; + .println("Fail to update a card, file not available: " + + cmd.getParam()); + 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; + } } } - } catch (IOException e) { - e.printStackTrace(); - } finally { - s.close(); + break; + case DELETE_CARD: + // TODO + System.err + .println("Unsupported command received from a client connection, closing it: " + + verb); + clientContinue = false; + break; + case LIST: + for (File file : dataDir.listFiles()) { + if (cmd.getParam() == null || cmd.getParam().length() == 0 + || file.getName().contains(cmd.getParam())) { + s.send(StringUtils.fromTime(file.lastModified()) + " " + + file.getName()); + } + } + s.sendBlock(); + break; + case HELP: + // TODO: i18n + s.send("The following commands are available:"); + s.send("- TIME: get the server time"); + s.send("- HELP: this help screen"); + s.send("- LIST: list the available cards on this server"); + s.send("- VERSION/GET/PUT/POST/DELETE/STOP: TODO"); + s.sendBlock(); + break; + default: + System.err + .println("Unsupported command received from a client connection, closing it: " + + verb); + clientContinue = false; + break; + } + + return clientContinue; + } + + /** + * Process a *_CONTACT subcommand. + * + * @param s + * the {@link SimpleSocket} to process + * @param card + * the target {@link Card} + * + * @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 processContactCmd(SimpleSocket s, Card card) + 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_CONTACT: { + Contact contact = card.getById(cmd.getParam()); + if (contact != null) + s.sendBlock(Vcard21Parser.toStrings(contact, -1)); + else + s.sendBlock(); + break; + } + case POST_CONTACT: { + String uid = cmd.getParam(); + Contact contact = card.getById(uid); + if (contact != null) + contact.delete(); + List list = Vcard21Parser.parseContact(s.receiveBlock()); + if (list.size() > 0) { + contact = list.get(0); + contact.getPreferredData("UID").setValue(uid); + card.add(contact); + } + break; + } + case PUT_CONTACT: { + String uid = cmd.getParam(); + Contact contact = card.getById(uid); + if (contact == null) { + throw new InvalidParameterException( + "Cannot find contact to modify for UID: " + uid); + } + while (processDataCmd(s, contact)) + ; + break; + } + case DELETE_CONTACT: { + String uid = cmd.getParam(); + Contact contact = card.getById(uid); + if (contact == null) { + throw new InvalidParameterException( + "Cannot find contact to delete for UID: " + uid); + } + + contact.delete(); + break; + } + case PUT_CARD: { + clientContinue = false; + break; + } + default: { + throw new InvalidParameterException("command invalid here"); + } } - removeClient(s); + return clientContinue; + } + + /** + * Process a *_DATA subcommand. + * + * @param s + * the {@link SimpleSocket} to process + * @param card + * the target {@link Contact} + * + * @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 processDataCmd(SimpleSocket s, Contact contact) + 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_DATA: { + Data data = contact.getById(cmd.getParam()); + if (data != null) + s.sendBlock(Vcard21Parser.toStrings(data)); + else + s.sendBlock(); + break; + } + case POST_DATA: { + String cstate = cmd.getParam(); + Data data = null; + for (Data d : contact) { + if (cstate.equals(d.getContentState())) + data = d; + } + + if (data != null) + data.delete(); + List list = Vcard21Parser.parseData(s.receiveBlock()); + if (list.size() > 0) { + contact.add(list.get(0)); + } + break; + } + case DELETE_DATA: { + String cstate = cmd.getParam(); + Data data = null; + for (Data d : contact) { + if (cstate.equals(d.getContentState())) + data = d; + } + + if (data == null) { + throw new InvalidParameterException( + "Cannot find data to delete for content state: " + + cstate); + } + + contact.delete(); + break; + } + case PUT_CONTACT: { + clientContinue = false; + break; + } + default: { + throw new InvalidParameterException("command invalid here"); + } + } + + return clientContinue; } /** @@ -249,17 +457,14 @@ public class Server implements Runnable { private List doGetCard(String name) throws IOException { List lines = new LinkedList(); - if (name != null && name.length() > 0) { - File vcf = new File(dataDir.getAbsolutePath() + File.separator - + name); + File vcf = getFile(name); - if (vcf.exists()) { - Card card = new Card(vcf, Format.VCard21); + if (vcf != null && vcf.exists()) { + Card card = new Card(vcf, Format.VCard21); - // timestamp: - lines.add(StringUtils.fromTime(card.getLastModified())); - lines.addAll(Parser.toStrings(card, Format.VCard21)); - } + // timestamp: + lines.add(StringUtils.fromTime(card.getLastModified())); + lines.addAll(Vcard21Parser.toStrings(card)); } return lines; @@ -280,11 +485,11 @@ public class Server implements Runnable { */ 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)); + File vcf = getFile(name); + + if (vcf != null) { + Card card = new Card(Vcard21Parser.parseContact(data)); card.saveAs(vcf, Format.VCard21); return StringUtils.fromTime(vcf.lastModified()); @@ -292,4 +497,21 @@ public class Server implements Runnable { return ""; } + + /** + * Return the {@link File} corresponding to the given resource name. + * + * @param name + * the resource name + * + * @return the corresponding {@link File} or NULL if the name was NULL or + * empty + */ + private File getFile(String name) { + if (name != null && name.length() > 0) { + return new File(dataDir.getAbsolutePath() + File.separator + name); + } + + return null; + } } diff --git a/src/be/nikiroo/jvcard/remote/Sync.java b/src/be/nikiroo/jvcard/remote/Sync.java index 9fff9b8..d382a2f 100644 --- a/src/be/nikiroo/jvcard/remote/Sync.java +++ b/src/be/nikiroo/jvcard/remote/Sync.java @@ -12,14 +12,16 @@ import java.io.OutputStreamWriter; import java.net.Socket; import java.net.UnknownHostException; import java.security.InvalidParameterException; -import java.time.LocalDate; +import java.util.LinkedList; import java.util.List; 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.parsers.Vcard21Parser; import be.nikiroo.jvcard.remote.Command.Verb; import be.nikiroo.jvcard.resources.Bundles; import be.nikiroo.jvcard.tui.StringUtils; @@ -189,9 +191,11 @@ public class Sync { // Check changes boolean serverChanges = (tsServer - tsOriginal) > GRACE_TIME; boolean localChanges = false; + Card local = null; + Card original = null; if (tsOriginal != -1) { - Card local = new Card(getCache(cacheDir), Format.VCard21); - Card original = new Card(getCache(cacheDirOrig), Format.VCard21); + local = new Card(getCache(cacheDir), Format.VCard21); + original = new Card(getCache(cacheDirOrig), Format.VCard21); localChanges = !local.isEquals(original, true); } @@ -199,24 +203,28 @@ public class Sync { // Sync to server if: if (localChanges) { - // TODO: sync instead (with PUT) - action = Verb.POST; + action = Verb.PUT_CARD; } - // Sync from server if + // Sync from/to server if if (serverChanges && localChanges) { - // TODO - throw new IOException("Sync operation not supported yet :("); + action = Verb.PUT_CARD; + } + + // Sync from server if: + if (serverChanges) { + // TODO: only sends changed cstate if serverChanges + action = Verb.GET_CARD; } // PUT the whole file if: if (tsServer == -1) { - action = Verb.POST; + action = Verb.POST_CARD; } // GET the whole file if: - if (tsOriginal == -1 || serverChanges) { - action = Verb.GET; + if (tsOriginal == -1) { + action = Verb.GET_CARD; } System.err.println("remote: " + (tsServer / 1000) % 1000 + " (" @@ -229,23 +237,57 @@ public class Sync { if (action != null) { switch (action) { - case GET: - s.sendCommand(Verb.GET, name); + case GET_CARD: + s.sendCommand(Verb.GET_CARD, name); List data = s.receiveBlock(); setLastModified(data.remove(0)); - Card server = new Card(Parser.parse(data, Format.VCard21)); + Card server = new Card(Vcard21Parser.parseContact(data)); 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)); + 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)); + } + 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 -- 2.27.0