/**
* 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;
}
/**
*/
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.
*/
* 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();
if (file == null)
return false;
- this.replaceListContent(Parser.parse(file, format));
+ this.replaceListContent(Parser.parseContact(file, format));
lastModified = file.lastModified();
setPristine();
import be.nikiroo.jvcard.Data;
public class AbookParser {
- public static List<Contact> parse(List<String> 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<Contact> parseContact(List<String> lines) {
List<Contact> contacts = new LinkedList<Contact>();
for (String line : lines) {
* @throws IOException
* in case of IO error
*/
- public static List<Contact> parse(File file, Format format)
+ public static List<Contact> parseContact(File file, Format format)
throws IOException {
List<String> lines = null;
if (lines == null)
return new LinkedList<Contact>();
- return parse(lines, format);
+ return parseContact(lines, format);
}
/**
*
* @return the list of elements
*/
- public static List<Contact> parse(List<String> lines, Format format) {
+ public static List<Contact> parseContact(List<String> 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: "
* @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
*
import be.nikiroo.jvcard.TypeInfo;
public class Vcard21Parser {
- public static List<Contact> parse(Iterable<String> 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<Contact> parseContact(Iterable<String> textData) {
Iterator<String> lines = textData.iterator();
List<Contact> contacts = new LinkedList<Contact>();
- List<Data> datas = null;
+ List<String> datas = null;
String nextRawLine = null;
if (lines.hasNext()) {
String line = rawLine.toString();
if (line.equals("BEGIN:VCARD")) {
- datas = new LinkedList<Data>();
+ datas = new LinkedList<String>();
} 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) {
System.err
.println("VCARD Parser warning: data seen before any VCARD:BEGIN");
} else {
- List<TypeInfo> types = new LinkedList<TypeInfo>();
- 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<Data> parseData(Iterable<String> textData) {
+ List<Data> datas = new LinkedList<Data>();
+
+ for (String line : textData) {
+ List<TypeInfo> types = new LinkedList<TypeInfo>();
+ 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;
}
/**
* @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) {
+ 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
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");
}
/**
- * 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<String> toStrings(Card card) {
+ public static List<String> toStrings(Data data) {
List<String> lines = new LinkedList<String>();
- 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;
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;
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;
private Object clientsLock = new Object();
private List<SimpleSocket> clients = new LinkedList<SimpleSocket>();
- private Object cardsLock = new Object();
+ private Object updateLock = new Object();
public static void main(String[] args) throws IOException {
Server server = new Server(4444);
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) {
}
/**
- * 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<Contact> 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<Data> 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;
}
/**
private List<String> doGetCard(String name) throws IOException {
List<String> lines = new LinkedList<String>();
- 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;
*/
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));
+ File vcf = getFile(name);
+
+ if (vcf != null) {
+ Card card = new Card(Vcard21Parser.parseContact(data));
card.saveAs(vcf, Format.VCard21);
return StringUtils.fromTime(vcf.lastModified());
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;
+ }
}
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;
// 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);
}
// 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 + " ("
if (action != null) {
switch (action) {
- case GET:
- s.sendCommand(Verb.GET, name);
+ case GET_CARD:
+ s.sendCommand(Verb.GET_CARD, name);
List<String> 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<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));
+ }
+ 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