Resources system rewrite + new "--save-config DIR" option
[jvcard.git] / src / be / nikiroo / jvcard / remote / Server.java
index 4b9936573fdecda0beac378d377fa19e2c4c2c1e..962f2d00133069eee26205f97e23205373b86e44 100644 (file)
@@ -7,18 +7,19 @@ 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.Vcard21Parser;
-import be.nikiroo.jvcard.remote.Command.Verb;
-import be.nikiroo.jvcard.resources.Bundles;
-import be.nikiroo.jvcard.tui.StringUtils;
+import be.nikiroo.jvcard.resources.StringUtils;
+import be.nikiroo.jvcard.resources.bundles.RemoteBundle;
+import be.nikiroo.jvcard.resources.enums.RemotingOption;
 
 /**
  * This class implements a small server that can listen for requests to
@@ -44,14 +45,10 @@ public class Server implements Runnable {
        private List<SimpleSocket> clients = new LinkedList<SimpleSocket>();
 
        private Object updateLock = new Object();
-
-       public static void main(String[] args) throws IOException {
-               Server server = new Server(4444);
-               server.run();
-       }
+       private Map<File, Integer> updates = new HashMap<File, Integer>();
 
        /**
-        * 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
@@ -61,9 +58,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();
 
@@ -90,7 +87,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();
@@ -174,7 +171,7 @@ public class Server implements Runnable {
        }
 
        /**
-        * Process a command.
+        * Process a first-level command.
         * 
         * @param s
         *            the {@link SimpleSocket} from which to get the command to
@@ -187,92 +184,192 @@ public class Server implements Runnable {
         *             in case of IO error
         */
        private boolean processCmd(SimpleSocket s) throws IOException {
-               Command cmd = s.receiveCommand();
-               Command.Verb verb = cmd.getVerb();
+               CommandInstance cmd = s.receiveCommand();
+               Command command = cmd.getCommand();
 
-               if (verb == null)
+               if (command == null)
                        return false;
 
                boolean clientContinue = true;
 
-               System.out.println(s + " ->  " + verb);
+               System.out.println(s + " ->  " + command
+                               + (cmd.getParam() == null ? "" : " " + cmd.getParam()));
 
-               switch (verb) {
-               case STOP:
+               switch (command) {
+               case STOP: {
                        clientContinue = false;
                        break;
-               case VERSION:
-                       s.sendCommand(Verb.VERSION);
+               }
+               case VERSION: {
+                       s.sendLine("" + SimpleSocket.CURRENT_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: "
-                                                                               + verb + " (" + e.getMessage() + ")");
+                                                                               + 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 DELETE_CARD:
-                       // TODO
-                       System.err
-                                       .println("Unsupported command received from a client connection, closing it: "
-                                                       + verb);
-                       clientContinue = false;
-                       break;
-               case LIST:
+               }
+               case LIST_CARD: {
                        for (File file : dataDir.listFiles()) {
-                               if (cmd.getParam() == null || cmd.getParam().length() == 0
-                                               || file.getName().contains(cmd.getParam())) {
+                               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:
+               }
+               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.send("- LIST_CARD: list the available cards on this server");
+                       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);
+                                                       + command);
                        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 {
+               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: {
+                       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();
+                                       s.sendLine(StringUtils.fromTime(card.getLastModified()));
+                               } catch (InvalidParameterException e) {
+                                       System.err
+                                                       .println("Unsupported command received from a client connection, closing it: "
+                                                                       + command + " (" + e.getMessage() + ")");
+                                       clientContinue = false;
+                               }
+                       }
+                       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);
+               }
+               }
 
                return clientContinue;
        }
@@ -295,17 +392,17 @@ public class Server implements Runnable {
         */
        private boolean processContactCmd(SimpleSocket s, Card card)
                        throws IOException {
-               Command cmd = s.receiveCommand();
-               Command.Verb verb = cmd.getVerb();
+               CommandInstance cmd = s.receiveCommand();
+               Command command = cmd.getCommand();
 
-               if (verb == null)
+               if (command == null)
                        return false;
 
                boolean clientContinue = true;
 
-               System.out.println(s + " ->  " + verb);
+               System.out.println(s + " ->  " + command);
 
-               switch (verb) {
+               switch (command) {
                case GET_CONTACT: {
                        Contact contact = card.getById(cmd.getParam());
                        if (contact != null)
@@ -315,16 +412,16 @@ public class Server implements Runnable {
                        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);
+                               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: {
@@ -349,12 +446,38 @@ public class Server implements Runnable {
                        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");
+                       throw new InvalidParameterException("command invalid here: "
+                                       + command);
                }
                }
 
@@ -379,30 +502,33 @@ public class Server implements Runnable {
         */
        private boolean processDataCmd(SimpleSocket s, Contact contact)
                        throws IOException {
-               Command cmd = s.receiveCommand();
-               Command.Verb verb = cmd.getVerb();
+               CommandInstance cmd = s.receiveCommand();
+               Command command = cmd.getCommand();
 
-               if (verb == null)
+               if (command == null)
                        return false;
 
                boolean clientContinue = true;
 
-               System.out.println(s + " ->  " + verb);
+               System.out.println(s + " ->  " + command);
 
-               switch (verb) {
+               switch (command) {
                case GET_DATA: {
-                       Data data = contact.getById(cmd.getParam());
-                       if (data != null)
-                               s.sendBlock(Vcard21Parser.toStrings(data));
-                       else
-                               s.sendBlock();
+                       for (Data data : contact) {
+                               if (data.getName().equals(cmd.getParam())) {
+                                       for (String line : Vcard21Parser.toStrings(data)) {
+                                               s.send(line);
+                                       }
+                               }
+                       }
+                       s.sendBlock();
                        break;
                }
                case POST_DATA: {
                        String cstate = cmd.getParam();
                        Data data = null;
                        for (Data d : contact) {
-                               if (cstate.equals(d.getContentState()))
+                               if (cstate.equals(d.getContentState(true)))
                                        data = d;
                        }
 
@@ -418,7 +544,7 @@ public class Server implements Runnable {
                        String cstate = cmd.getParam();
                        Data data = null;
                        for (Data d : contact) {
-                               if (cstate.equals(d.getContentState()))
+                               if (cstate.equals(d.getContentState(true)))
                                        data = d;
                        }
 
@@ -431,12 +557,34 @@ public class Server implements Runnable {
                        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");
+                       throw new InvalidParameterException("command invalid here: "
+                                       + command);
                }
                }
 
@@ -462,7 +610,7 @@ public class Server implements Runnable {
                if (vcf != null && vcf.exists()) {
                        Card card = new Card(vcf, Format.VCard21);
 
-                       // timestamp:
+                       // timestamp + data
                        lines.add(StringUtils.fromTime(card.getLastModified()));
                        lines.addAll(Vcard21Parser.toStrings(card));
                }