X-Git-Url: http://git.nikiroo.be/?a=blobdiff_plain;f=src%2Fbe%2Fnikiroo%2Fjvcard%2Fremote%2FServer.java;h=6cba0c33cbb09047ed09f4004f88266e9ee4193d;hb=c8398c23a885b1b7b78fba35a423c7136fb975cf;hp=e90e42d6ff1e76c5a1caec0d91d2e5c2a80ec808;hpb=cf77cb3542f2aefbebdb9aa00b358dbeb4489a73;p=jvcard.git
diff --git a/src/be/nikiroo/jvcard/remote/Server.java b/src/be/nikiroo/jvcard/remote/Server.java
index e90e42d..6cba0c3 100644
--- a/src/be/nikiroo/jvcard/remote/Server.java
+++ b/src/be/nikiroo/jvcard/remote/Server.java
@@ -5,17 +5,22 @@ 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.HashMap;
import java.util.LinkedList;
import java.util.List;
-import java.util.ResourceBundle;
+import java.util.Map;
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.remote.Command.Verb;
-import be.nikiroo.jvcard.resources.Bundles;
-import be.nikiroo.jvcard.tui.StringUtils;
+import be.nikiroo.jvcard.parsers.Vcard21Parser;
+import be.nikiroo.jvcard.remote.SimpleSocket.BlockAppendable;
+import be.nikiroo.jvcard.resources.RemoteBundle;
+import be.nikiroo.jvcard.resources.RemotingOption;
+import be.nikiroo.utils.StringUtils;
/**
* This class implements a small server that can listen for requests to
@@ -29,7 +34,7 @@ import be.nikiroo.jvcard.tui.StringUtils;
*
*
* @author niki
- *
+ *
*/
public class Server implements Runnable {
private ServerSocket ss;
@@ -40,15 +45,11 @@ public class Server implements Runnable {
private Object clientsLock = new Object();
private List clients = new LinkedList();
- private Object cardsLock = new Object();
-
- public static void main(String[] args) throws IOException {
- Server server = new Server(4444);
- server.run();
- }
+ private Object updateLock = new Object();
+ private Map updates = new HashMap();
/**
- * Create a new jVCard sercer on the given port.
+ * Create a new jVCard server on the given port.
*
* @param port
* the port to run on
@@ -58,9 +59,9 @@ public class Server implements Runnable {
*/
public Server(int port) throws IOException {
this.port = port;
- ResourceBundle bundle = Bundles.getBundle("remote");
+ RemoteBundle bundle = new RemoteBundle();
try {
- String dir = bundle.getString("SERVER_DATA_PATH");
+ String dir = bundle.getString(RemotingOption.SERVER_DATA_PATH);
dataDir = new File(dir);
dataDir.mkdir();
@@ -87,7 +88,7 @@ public class Server implements Runnable {
SimpleSocket c = new SimpleSocket(new Socket((String) null, port),
"special STOP client");
c.open(true);
- c.sendCommand(Verb.STOP);
+ c.sendCommand(Command.STOP);
c.close();
} catch (UnknownHostException e) {
e.printStackTrace();
@@ -124,7 +125,22 @@ 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)) {
+ // nothing to do: process the command
+ }
+
+ } catch (IOException e) {
+ e.printStackTrace();
+ } finally {
+ ss.close();
+ }
+ removeClient(ss);
}
}).start();
} catch (IOException ioe) {
@@ -157,82 +173,430 @@ public class Server implements Runnable {
}
/**
- * Accept a client and process it.
+ * Process a first-level 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);
+ private boolean processCmd(SimpleSocket s) throws IOException {
+ CommandInstance cmd = s.receiveCommand();
+ Command command = cmd.getCommand();
- 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;
+ if (command == null)
+ return false;
+
+ boolean clientContinue = true;
+
+ System.out.println(s + " -> " + command
+ + (cmd.getParam() == null ? "" : " " + cmd.getParam()));
+
+ switch (command) {
+ case STOP: {
+ clientContinue = false;
+ break;
+ }
+ case VERSION: {
+ s.sendLine("" + SimpleSocket.CURRENT_VERSION);
+ break;
+ }
+ case TIME: {
+ s.sendLine(StringUtils.fromTime(new Date().getTime()));
+ break;
+ }
+ 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;
+ }
}
- 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());
+
+ if (!updates.containsKey(file))
+ updates.put(file, 0);
+ updates.put(file, updates.get(file) + 1);
+ }
+
+ synchronized (file) {
+ try {
+ s.sendLine(StringUtils.fromTime(file.lastModified()));
+
+ while (processLockedCmd(s, name)) {
+ // nothing to do: process the command
}
+ } catch (InvalidParameterException e) {
+ System.err
+ .println("Unsupported command received from a client connection, closing it: "
+ + command + " (" + e.getMessage() + ")");
+ clientContinue = false;
+ }
+ }
+
+ synchronized (updateLock) {
+ int num = updates.get(file) - 1;
+ if (num == 0) {
+ updates.remove(file);
+ } else {
+ updates.put(file, num);
+ }
+ }
+ }
+ break;
+ }
+ case LIST_CARD: {
+ for (File file : dataDir.listFiles()) {
+ if (cmd.getParam() == null
+ || cmd.getParam().length() == 0
+ || file.getName().toLowerCase()
+ .contains(cmd.getParam().toLowerCase())) {
+ 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_CARD: 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: "
+ + command);
+ clientContinue = false;
+ break;
+ }
+ }
+
+ return clientContinue;
+ }
+
+ /**
+ * Process a subcommand while protected for resource name.
+ *
+ * @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 {
+ CommandInstance cmd = s.receiveCommand();
+ Command command = cmd.getCommand();
+
+ if (command == null)
+ return false;
+
+ boolean clientContinue = true;
+
+ System.out.println(s + " -> " + command);
+
+ switch (command) {
+ case GET_CARD: {
+ sendCardBlock(s, 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)) {
+ // nothing to do: process the command
}
- 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:
+ card.save();
+ s.sendLine(StringUtils.fromTime(card.getLastModified()));
+ } catch (InvalidParameterException e) {
System.err
.println("Unsupported command received from a client connection, closing it: "
- + verb);
- clientStop = true;
- break;
+ + command + " (" + 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: "
+ + command);
+ clientContinue = false;
+ break;
+ }
+ case SELECT: {
+ clientContinue = false;
+ break;
+ }
+ default: {
+ throw new InvalidParameterException("command invalid here: "
+ + command);
+ }
}
- removeClient(s);
+ 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 {
+ CommandInstance cmd = s.receiveCommand();
+ Command command = cmd.getCommand();
+
+ if (command == null)
+ return false;
+
+ boolean clientContinue = true;
+
+ System.out.println(s + " -> " + command);
+
+ switch (command) {
+ case GET_CONTACT: {
+ Contact contact = card.getById(cmd.getParam());
+ if (contact != null) {
+ BlockAppendable app = s.createBlockAppendable();
+ Vcard21Parser.write(app, contact, -1);
+ app.close();
+ } else {
+ s.sendBlock();
+ }
+ break;
+ }
+ case POST_CONTACT: {
+ List list = Vcard21Parser.parseContact(s.receiveBlock());
+ if (list.size() > 0) {
+ Contact newContact = list.get(0);
+ String uid = newContact.getPreferredDataValue("UID");
+ Contact oldContact = card.getById(uid);
+ if (oldContact != null)
+ oldContact.delete();
+ card.add(newContact);
+ }
+
+ 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)) {
+ // nothing to do: process the command
+ }
+ 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 HASH_CONTACT: {
+ String uid = cmd.getParam();
+ Contact contact = card.getById(uid);
+
+ if (contact == null) {
+ s.sendBlock();
+ } else {
+ s.sendLine(contact.getContentState(true));
+ }
+ break;
+ }
+ case LIST_CONTACT: {
+ for (Contact contact : card) {
+ if (cmd.getParam() == null
+ || cmd.getParam().length() == 0
+ || (contact.getPreferredDataValue("FN") + contact
+ .getPreferredDataValue("N")).toLowerCase()
+ .contains(cmd.getParam().toLowerCase())) {
+ s.send(contact.getContentState(true) + " "
+ + contact.getId());
+ }
+ }
+ s.sendBlock();
+ break;
+ }
+ case PUT_CARD: {
+ clientContinue = false;
+ break;
+ }
+ default: {
+ throw new InvalidParameterException("command invalid here: "
+ + command);
+ }
+ }
+
+ 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 {
+ CommandInstance cmd = s.receiveCommand();
+ Command command = cmd.getCommand();
+
+ if (command == null)
+ return false;
+
+ boolean clientContinue = true;
+
+ System.out.println(s + " -> " + command);
+
+ switch (command) {
+ case GET_DATA: {
+ for (Data data : contact) {
+ if (data.getName().equals(cmd.getParam())) {
+ BlockAppendable app = s.createBlockAppendable();
+ Vcard21Parser.write(app, data);
+ // note: we do NOT close 'app', since it would send an EOB
+ }
+ }
+ s.sendBlock();
+ break;
+ }
+ case POST_DATA: {
+ String cstate = cmd.getParam();
+ Data data = null;
+ for (Data d : contact) {
+ if (cstate.equals(d.getContentState(true)))
+ 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(true)))
+ data = d;
+ }
+
+ if (data == null) {
+ throw new InvalidParameterException(
+ "Cannot find data to delete for content state: "
+ + cstate);
+ }
+
+ contact.delete();
+ break;
+ }
+ case HASH_DATA: {
+ for (Data data : contact) {
+ if (data.getId().equals(cmd.getParam())) {
+ s.send(data.getContentState(true));
+ }
+ }
+ s.sendBlock();
+ break;
+ }
+ case LIST_DATA: {
+ for (Data data : contact) {
+ if (cmd.getParam() == null
+ || cmd.getParam().length() == 0
+ || data.getName().toLowerCase()
+ .contains(cmd.getParam().toLowerCase())) {
+ s.send(data.getContentState(true) + " " + data.getName());
+ }
+ }
+ s.sendBlock();
+ break;
+ }
+ case PUT_CONTACT: {
+ clientContinue = false;
+ break;
+ }
+ default: {
+ throw new InvalidParameterException("command invalid here: "
+ + command);
+ }
+ }
+
+ return clientContinue;
}
/**
@@ -246,23 +610,19 @@ public class Server implements Runnable {
* @throws IOException
* in case of error
*/
- private List doGetCard(String name) throws IOException {
- List lines = new LinkedList();
+ private void sendCardBlock(SimpleSocket s, String name) throws IOException {
+ File vcf = getFile(name);
+ BlockAppendable app = s.createBlockAppendable();
- if (name != null && name.length() > 0) {
- File vcf = new File(dataDir.getAbsolutePath() + File.separator
- + 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 + data
+ app.append(StringUtils.fromTime(card.getLastModified()) + "\r\n");
+ Vcard21Parser.write(app, card);
}
- return lines;
+ app.close();
}
/**
@@ -280,11 +640,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 +652,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;
+ }
}