1 package be
.nikiroo
.jvcard
.remote
;
4 import java
.io
.IOException
;
5 import java
.net
.ServerSocket
;
6 import java
.net
.Socket
;
7 import java
.net
.UnknownHostException
;
8 import java
.security
.InvalidParameterException
;
10 import java
.util
.LinkedList
;
11 import java
.util
.List
;
12 import java
.util
.ResourceBundle
;
14 import be
.nikiroo
.jvcard
.Card
;
15 import be
.nikiroo
.jvcard
.Contact
;
16 import be
.nikiroo
.jvcard
.Data
;
17 import be
.nikiroo
.jvcard
.parsers
.Format
;
18 import be
.nikiroo
.jvcard
.parsers
.Vcard21Parser
;
19 import be
.nikiroo
.jvcard
.remote
.Command
.Verb
;
20 import be
.nikiroo
.jvcard
.resources
.Bundles
;
21 import be
.nikiroo
.jvcard
.tui
.StringUtils
;
24 * This class implements a small server that can listen for requests to
25 * synchronise, get and put {@link Card}s.
28 * It is <b>NOT</b> secured in any way (it even is nice enough to give you a
29 * help message when you connect in raw mode via nc on how to use it), so do
30 * <b>NOT</b> enable such a server to be accessible from internet. This is not
31 * safe. Use a ssh/openssl tunnel or similar.
37 public class Server
implements Runnable
{
38 private ServerSocket ss
;
43 private Object clientsLock
= new Object();
44 private List
<SimpleSocket
> clients
= new LinkedList
<SimpleSocket
>();
46 private Object updateLock
= new Object();
48 public static void main(String
[] args
) throws IOException
{
49 Server server
= new Server(4444);
54 * Create a new jVCard sercer on the given port.
62 public Server(int port
) throws IOException
{
64 ResourceBundle bundle
= Bundles
.getBundle("remote");
66 String dir
= bundle
.getString("SERVER_DATA_PATH");
67 dataDir
= new File(dir
);
70 if (!dataDir
.exists()) {
71 throw new IOException("Cannot open or create data store at: "
74 } catch (Exception e
) {
76 throw new IOException("Cannot open or create data store at: "
80 ss
= new ServerSocket(port
);
84 * Stop the server. It may take some time before returning, but will only
85 * return when the server is actually stopped.
90 SimpleSocket c
= new SimpleSocket(new Socket((String
) null, port
),
91 "special STOP client");
93 c
.sendCommand(Verb
.STOP
);
95 } catch (UnknownHostException e
) {
97 } catch (IOException e
) {
101 if (clients
.size() > 0) {
104 } catch (InterruptedException e
) {
107 if (clients
.size() > 0) {
108 synchronized (clientsLock
) {
109 for (SimpleSocket s
: clients
) {
111 .println("Forcefully closing client connection");
125 final Socket s
= ss
.accept();
126 // TODO: thread pool?
127 new Thread(new Runnable() {
130 SimpleSocket ss
= new SimpleSocket(s
, "[request]");
136 while (processCmd(ss
))
139 } catch (IOException e
) {
147 } catch (IOException ioe
) {
148 ioe
.printStackTrace();
154 * Add a client to the current count.
156 * @return the client index number
158 private void addClient(SimpleSocket s
) {
159 synchronized (clientsLock
) {
165 * Remove a client from the current count.
168 * the client index number
170 private void removeClient(SimpleSocket s
) {
171 synchronized (clientsLock
) {
180 * the {@link SimpleSocket} from which to get the command to
183 * @return TRUE if the client is ready for another command, FALSE when the
186 * @throws IOException
187 * in case of IO error
189 private boolean processCmd(SimpleSocket s
) throws IOException
{
190 Command cmd
= s
.receiveCommand();
191 Command
.Verb verb
= cmd
.getVerb();
196 boolean clientContinue
= true;
198 System
.out
.println(s
+ " -> " + verb
);
202 clientContinue
= false;
205 s
.sendCommand(Verb
.VERSION
);
208 s
.sendLine(StringUtils
.fromTime(new Date().getTime()));
211 synchronized (updateLock
) {
212 s
.sendBlock(doGetCard(cmd
.getParam()));
216 synchronized (updateLock
) {
217 s
.sendLine(doPostCard(cmd
.getParam(), s
.receiveBlock()));
221 synchronized (updateLock
) {
222 File vcf
= getFile(cmd
.getParam());
225 .println("Fail to update a card, file not available: "
227 clientContinue
= false;
229 Card card
= new Card(vcf
, Format
.VCard21
);
231 while (processContactCmd(s
, card
))
234 } catch (InvalidParameterException e
) {
236 .println("Unsupported command received from a client connection, closing it: "
237 + verb
+ " (" + e
.getMessage() + ")");
238 clientContinue
= false;
246 .println("Unsupported command received from a client connection, closing it: "
248 clientContinue
= false;
251 for (File file
: dataDir
.listFiles()) {
252 if (cmd
.getParam() == null || cmd
.getParam().length() == 0
253 || file
.getName().contains(cmd
.getParam())) {
254 s
.send(StringUtils
.fromTime(file
.lastModified()) + " "
262 s
.send("The following commands are available:");
263 s
.send("- TIME: get the server time");
264 s
.send("- HELP: this help screen");
265 s
.send("- LIST: list the available cards on this server");
266 s
.send("- VERSION/GET/PUT/POST/DELETE/STOP: TODO");
271 .println("Unsupported command received from a client connection, closing it: "
273 clientContinue
= false;
277 return clientContinue
;
281 * Process a *_CONTACT subcommand.
284 * the {@link SimpleSocket} to process
286 * the target {@link Card}
288 * @return TRUE if the client is ready for another command, FALSE when the
291 * @throws IOException
292 * in case of IO error
294 * @throw InvalidParameterException in case of invalid subcommand
296 private boolean processContactCmd(SimpleSocket s
, Card card
)
298 Command cmd
= s
.receiveCommand();
299 Command
.Verb verb
= cmd
.getVerb();
304 boolean clientContinue
= true;
306 System
.out
.println(s
+ " -> " + verb
);
310 Contact contact
= card
.getById(cmd
.getParam());
312 s
.sendBlock(Vcard21Parser
.toStrings(contact
, -1));
318 String uid
= cmd
.getParam();
319 Contact contact
= card
.getById(uid
);
322 List
<Contact
> list
= Vcard21Parser
.parseContact(s
.receiveBlock());
323 if (list
.size() > 0) {
324 contact
= list
.get(0);
325 contact
.getPreferredData("UID").setValue(uid
);
331 String uid
= cmd
.getParam();
332 Contact contact
= card
.getById(uid
);
333 if (contact
== null) {
334 throw new InvalidParameterException(
335 "Cannot find contact to modify for UID: " + uid
);
337 while (processDataCmd(s
, contact
))
341 case DELETE_CONTACT
: {
342 String uid
= cmd
.getParam();
343 Contact contact
= card
.getById(uid
);
344 if (contact
== null) {
345 throw new InvalidParameterException(
346 "Cannot find contact to delete for UID: " + uid
);
353 clientContinue
= false;
357 throw new InvalidParameterException("command invalid here");
361 return clientContinue
;
365 * Process a *_DATA subcommand.
368 * the {@link SimpleSocket} to process
370 * the target {@link Contact}
372 * @return TRUE if the client is ready for another command, FALSE when the
375 * @throws IOException
376 * in case of IO error
378 * @throw InvalidParameterException in case of invalid subcommand
380 private boolean processDataCmd(SimpleSocket s
, Contact contact
)
382 Command cmd
= s
.receiveCommand();
383 Command
.Verb verb
= cmd
.getVerb();
388 boolean clientContinue
= true;
390 System
.out
.println(s
+ " -> " + verb
);
394 Data data
= contact
.getById(cmd
.getParam());
396 s
.sendBlock(Vcard21Parser
.toStrings(data
));
402 String cstate
= cmd
.getParam();
404 for (Data d
: contact
) {
405 if (cstate
.equals(d
.getContentState()))
411 List
<Data
> list
= Vcard21Parser
.parseData(s
.receiveBlock());
412 if (list
.size() > 0) {
413 contact
.add(list
.get(0));
418 String cstate
= cmd
.getParam();
420 for (Data d
: contact
) {
421 if (cstate
.equals(d
.getContentState()))
426 throw new InvalidParameterException(
427 "Cannot find data to delete for content state: "
435 clientContinue
= false;
439 throw new InvalidParameterException("command invalid here");
443 return clientContinue
;
447 * Return the serialised {@link Card} (with timestamp).
450 * the resource name to load
452 * @return the serialised data
454 * @throws IOException
457 private List
<String
> doGetCard(String name
) throws IOException
{
458 List
<String
> lines
= new LinkedList
<String
>();
460 File vcf
= getFile(name
);
462 if (vcf
!= null && vcf
.exists()) {
463 Card card
= new Card(vcf
, Format
.VCard21
);
466 lines
.add(StringUtils
.fromTime(card
.getLastModified()));
467 lines
.addAll(Vcard21Parser
.toStrings(card
));
474 * Save the data to the new given resource.
477 * the resource name to save
481 * @return the date of last modification
483 * @throws IOException
486 private String
doPostCard(String name
, List
<String
> data
)
489 File vcf
= getFile(name
);
492 Card card
= new Card(Vcard21Parser
.parseContact(data
));
493 card
.saveAs(vcf
, Format
.VCard21
);
495 return StringUtils
.fromTime(vcf
.lastModified());
502 * Return the {@link File} corresponding to the given resource name.
507 * @return the corresponding {@link File} or NULL if the name was NULL or
510 private File
getFile(String name
) {
511 if (name
!= null && name
.length() > 0) {
512 return new File(dataDir
.getAbsolutePath() + File
.separator
+ name
);