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;
private List<SimpleSocket> clients = new LinkedList<SimpleSocket>();
private Object updateLock = new Object();
+ private Map<File, Integer> updates = new HashMap<File, Integer>();
/**
* Create a new jVCard server on the given port.
}
/**
- * Process a command.
+ * Process a first-level command.
*
* @param s
* the {@link SimpleSocket} from which to get the command to
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: "
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())) {
}
s.sendBlock();
break;
- case HELP:
+ }
+ case HELP: {
// TODO: i18n
s.send("The following commands are available:");
s.send("- TIME: get the server time");
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 <tt>name</tt>.
+ *
+ * @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;
}
}
// return: synced or not
- //TODO jDoc
+ // TODO jDoc
public boolean sync(Card card, boolean force) throws UnknownHostException,
IOException {
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<String> 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<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));
+ 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<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));
+
+ switch (action) {
+ case GET_CARD:
+ s.sendCommand(Verb.GET_CARD);
+ List<String> 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<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);
+
+ 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
+ + " 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;
}