1 package be
.nikiroo
.jvcard
.launcher
;
4 import java
.io
.IOException
;
5 import java
.lang
.reflect
.Field
;
6 import java
.net
.Socket
;
7 import java
.nio
.charset
.Charset
;
8 import java
.util
.LinkedList
;
11 import javax
.imageio
.ImageIO
;
13 import be
.nikiroo
.jvcard
.Card
;
14 import be
.nikiroo
.jvcard
.Contact
;
15 import be
.nikiroo
.jvcard
.Data
;
16 import be
.nikiroo
.jvcard
.TypeInfo
;
17 import be
.nikiroo
.jvcard
.launcher
.CardResult
.MergeCallback
;
18 import be
.nikiroo
.jvcard
.launcher
.Optional
.NotSupportedException
;
19 import be
.nikiroo
.jvcard
.parsers
.Format
;
20 import be
.nikiroo
.jvcard
.remote
.Command
;
21 import be
.nikiroo
.jvcard
.remote
.SimpleSocket
;
22 import be
.nikiroo
.jvcard
.resources
.Bundles
;
23 import be
.nikiroo
.jvcard
.resources
.StringUtils
;
24 import be
.nikiroo
.jvcard
.resources
.bundles
.ColorBundle
;
25 import be
.nikiroo
.jvcard
.resources
.bundles
.DisplayBundle
;
26 import be
.nikiroo
.jvcard
.resources
.bundles
.RemoteBundle
;
27 import be
.nikiroo
.jvcard
.resources
.bundles
.TransBundle
;
28 import be
.nikiroo
.jvcard
.resources
.enums
.DisplayOption
;
29 import be
.nikiroo
.jvcard
.resources
.enums
.StringId
;
32 * This class contains the runnable Main method. It will parse the user supplied
33 * parameters and take action based upon those. Most of the time, it will start
40 static public final String APPLICATION_TITLE
= "jVcard";
41 static public final String APPLICATION_VERSION
= "1.1-dev";
43 static private final int ERR_NO_FILE
= 1;
44 static private final int ERR_SYNTAX
= 2;
45 static private final int ERR_INTERNAL
= 3;
46 static private TransBundle transService
;
48 static private String defaultFn
;
49 static private boolean forceComputedFn
;
52 CONTACT_MANAGER
, I18N
, SERVER
, LOAD_PHOTO
, SAVE_PHOTO
, SAVE_CONFIG
, HELP
56 * Translate the given {@link StringId} into user text.
61 * the values to insert instead of the place holders in the
64 * @return the translated text with the given value where required
66 static public String
trans(StringId id
, Object
... values
) {
67 return transService
.getString(id
, values
);
71 * Check if unicode characters should be used.
73 * @return TRUE to allow unicode
75 static public boolean isUnicode() {
76 return transService
.isUnicode();
80 * Start the application.
83 * The returned exit codes are:
85 * <li>1: no files to open</li>
86 * <li>2: invalid syntax</li>
87 * <li>3: internal error</li>
92 * the parameters (see <tt>--help</tt> to know which are
95 public static void main(String
[] args
) {
96 Boolean textMode
= null;
97 boolean noMoreParams
= false;
98 boolean filesTried
= false;
100 // get the "system default" language to help translate the --help
102 String language
= null;
103 transService
= new TransBundle(language
);
105 boolean unicode
= transService
.isUnicode();
107 List
<String
> files
= new LinkedList
<String
>();
109 Mode mode
= Mode
.CONTACT_MANAGER
;
110 String format
= null;
111 for (int index
= 0; index
< args
.length
; index
++) {
112 String arg
= args
[index
];
113 if (!noMoreParams
&& arg
.equals("--")) {
115 } else if (!noMoreParams
&& arg
.equals("--help")) {
116 if (mode
!= Mode
.CONTACT_MANAGER
) {
117 SERR(StringId
.CLI_SERR_MODES
);
121 } else if (!noMoreParams
&& arg
.equals("--tui")) {
123 } else if (!noMoreParams
&& arg
.equals("--gui")) {
125 } else if (!noMoreParams
&& arg
.equals("--noutf")) {
127 transService
.setUnicode(unicode
);
128 } else if (!noMoreParams
&& arg
.equals("--lang")) {
130 if (index
>= args
.length
) {
131 SERR(StringId
.CLI_SERR_NOLANG
);
135 language
= args
[index
];
136 transService
= new TransBundle(language
);
137 transService
.setUnicode(unicode
);
138 } else if (!noMoreParams
&& arg
.equals("--config")) {
140 if (index
>= args
.length
) {
141 SERR(StringId
.CLI_SERR_NODIR
);
145 Bundles
.setDirectory(args
[index
]);
146 transService
= new TransBundle(language
);
147 transService
.setUnicode(unicode
);
148 } else if (!noMoreParams
&& arg
.equals("--save-config")) {
150 if (index
>= args
.length
) {
151 SERR(StringId
.CLI_SERR_NODIR
);
156 if (mode
!= Mode
.CONTACT_MANAGER
) {
157 SERR(StringId
.CLI_SERR_MODES
);
160 mode
= Mode
.SAVE_CONFIG
;
161 } else if (!noMoreParams
&& arg
.equals("--server")) {
162 if (mode
!= Mode
.CONTACT_MANAGER
) {
163 SERR(StringId
.CLI_SERR_MODES
);
169 if (index
>= args
.length
) {
170 SERR(StringId
.CLI_SERR_NOPORT
);
175 port
= Integer
.parseInt(args
[index
]);
176 } catch (NumberFormatException e
) {
177 SERR(StringId
.CLI_SERR_BADPORT
, "" + args
[index
]);
180 } else if (!noMoreParams
&& arg
.equals("--i18n")) {
181 if (mode
!= Mode
.CONTACT_MANAGER
) {
182 SERR(StringId
.CLI_SERR_MODES
);
188 if (index
>= args
.length
) {
189 SERR(StringId
.CLI_SERR_NODIR
);
194 } else if (!noMoreParams
195 && (arg
.equals("--load-photo")
196 || arg
.equals("--save-photo") || arg
197 .equals("--only-photo"))) {
198 if (mode
!= Mode
.CONTACT_MANAGER
) {
199 SERR(StringId
.CLI_SERR_MODES
);
203 if (arg
.equals("--load-photo")) {
204 mode
= Mode
.LOAD_PHOTO
;
205 } else if (arg
.equals("--save-photo")) {
206 mode
= Mode
.SAVE_PHOTO
;
210 if (index
>= args
.length
) {
211 SERR(StringId
.CLI_SERR_NODIR
);
218 if (index
>= args
.length
) {
219 SERR(StringId
.CLI_SERR_NOFORMAT
);
223 format
= args
[index
];
226 files
.addAll(open(arg
));
230 // Force headless mode if we run in forced-text mode
231 if (mode
!= Mode
.CONTACT_MANAGER
|| (textMode
!= null && textMode
)) {
232 // same as -Djava.awt.headless=true
233 System
.setProperty("java.awt.headless", "true");
240 // N/FN fix information:
244 if (mode
== Mode
.SERVER
&& files
.size() > 0) {
245 SERR(StringId
.CLI_SERR_NOLANG
, "--server");
247 } else if (mode
== Mode
.I18N
&& files
.size() > 0) {
248 SERR(StringId
.CLI_SERR_NOLANG
, "--i18n");
250 } else if (mode
== Mode
.I18N
&& language
== null) {
251 SERR(StringId
.CLI_SERR_NOLANG
);
252 } else if ((mode
== Mode
.CONTACT_MANAGER
|| mode
== Mode
.SAVE_PHOTO
|| mode
== Mode
.LOAD_PHOTO
)
253 && files
.size() == 0) {
254 if (files
.size() == 0 && !filesTried
) {
255 files
.addAll(open("."));
258 if (files
.size() == 0) {
259 ERR(StringId
.CLI_ERR
, StringId
.CLI_ERR_NOFILES
, ERR_NO_FILE
);
268 if (!new File(dir
).isDirectory()) {
269 if (!new File(dir
).mkdir()) {
270 System
.err
.println(trans(
271 StringId
.CLI_ERR_CANNOT_CREATE_CONFDIR
, dir
));
275 new TransBundle().updateFile(dir
); // default locale
276 for (String lang
: TransBundle
.getKnownLanguages()) {
277 new TransBundle(lang
).updateFile(dir
);
280 new ColorBundle().updateFile(dir
);
281 new DisplayBundle().updateFile(dir
);
282 new RemoteBundle().updateFile(dir
);
283 } catch (IOException e
) {
286 System
.exit(ERR_INTERNAL
);
292 Optional
.runServer(port
);
293 } catch (IOException e
) {
294 ERR(StringId
.CLI_ERR
, StringId
.CLI_ERR_CANNOT_START
,
297 } catch (NotSupportedException e
) {
298 if (!e
.isCompiledIn()) {
299 ERR(StringId
.CLI_ERR
, StringId
.CLI_ERR_NO_REMOTING
,
304 ERR(StringId
.CLI_ERR
, StringId
.CLI_ERR
, ERR_INTERNAL
);
312 transService
.updateFile(dir
);
313 } catch (IOException e
) {
314 ERR(StringId
.CLI_ERR
, StringId
.CLI_ERR_CANNOT_CREATE_LANG
,
321 for (String file
: files
) {
323 Card card
= getCard(file
, null).getCard();
324 for (Contact contact
: card
) {
325 String filename
= contact
.toString(format
, "");
326 File f
= new File(dir
, filename
);
329 System
.out
.println("Loading " + f
);
331 String type
= "jpeg";
332 int dotIndex
= filename
.indexOf('.');
334 && (dotIndex
+ 1) < filename
.length()) {
335 type
= filename
.substring(dotIndex
+ 1)
339 String b64
= StringUtils
.fromImage(f
);
341 // remove previous photos:
342 for (Data photo
= contact
343 .getPreferredData("PHOTO"); photo
!= null; photo
= contact
344 .getPreferredData("PHOTO")) {
349 List
<TypeInfo
> types
= new LinkedList
<TypeInfo
>();
350 types
.add(new TypeInfo("ENCODING", "b"));
351 types
.add(new TypeInfo("TYPE", type
));
352 Data photo
= new Data(types
, "PHOTO", b64
, null);
354 } catch (IOException e
) {
355 System
.err
.println("Cannot read photo: "
361 } catch (IOException e
) {
363 .println(trans(StringId
.CLI_ERR_CANNOT_OPEN
, file
));
369 for (String file
: files
) {
371 Card card
= getCard(file
, null).getCard();
372 for (Contact contact
: card
) {
373 Data photo
= contact
.getPreferredData("PHOTO");
375 String filename
= contact
.toString(format
, "");
376 File f
= new File(dir
, filename
+ ".png");
377 System
.out
.println("Saving " + f
);
380 StringUtils
.toImage(photo
.getValue()),
382 } catch (IOException e
) {
383 System
.err
.println(trans(
384 StringId
.CLI_ERR_CANNOT_SAVE_PHOTO
,
385 contact
.getPreferredDataValue("FN")));
389 } catch (IOException e
) {
391 .println(trans(StringId
.CLI_ERR_CANNOT_OPEN
, file
));
396 case CONTACT_MANAGER
: {
398 Optional
.startTui(textMode
, files
);
399 } catch (IOException e
) {
400 ERR(StringId
.CLI_ERR
, StringId
.CLI_ERR_CANNOT_START
,
403 } catch (NotSupportedException e
) {
404 if (!e
.isCompiledIn()) {
405 ERR(StringId
.CLI_ERR
, StringId
.CLI_ERR_NO_TUI
, ERR_INTERNAL
);
409 ERR(StringId
.CLI_ERR
, StringId
.CLI_ERR
, ERR_INTERNAL
);
416 System
.out
.println(APPLICATION_TITLE
+ " " + APPLICATION_VERSION
);
417 System
.out
.println();
419 System
.out
.println(trans(StringId
.CLI_HELP
));
420 System
.out
.println();
422 System
.out
.println(trans(StringId
.CLI_HELP_MODES
));
423 System
.out
.println("\t--help : "
424 + trans(StringId
.CLI_HELP_MODE_HELP
));
425 System
.out
.println("\t(--tui|--gui) (--noutf) ... : "
426 + trans(StringId
.CLI_HELP_MODE_CONTACT_MANAGER
));
427 System
.out
.println("\t--server PORT ... : "
428 + trans(StringId
.CLI_HELP_MODE_SERVER
));
429 System
.out
.println("\t--save-config DIR : "
430 + trans(StringId
.CLI_HELP_MODE_SAVE_CONFIG
));
431 System
.out
.println("\t--i18n DIR ---lang LANG : "
432 + trans(StringId
.CLI_HELP_MODE_I18N
));
433 System
.out
.println("\t--load-photo DIR FORMAT ... : "
434 + trans(StringId
.CLI_HELP_MODE_LOAD_PHOTO
));
435 System
.out
.println("\t--save-photo DIR FORMAT ... : "
436 + trans(StringId
.CLI_HELP_MODE_SAVE_PHOTO
));
437 System
.out
.println();
439 System
.out
.println(trans(StringId
.CLI_HELP_OPTIONS
));
440 System
.out
.println("\t-- : " + trans(StringId
.CLI_HELP_DD
));
441 System
.out
.println("\t--lang LANG : "
442 + trans(StringId
.CLI_HELP_LANG
));
443 System
.out
.println("\t--tui : " + trans(StringId
.CLI_HELP_TUI
));
444 System
.out
.println("\t--gui : " + trans(StringId
.CLI_HELP_GUI
));
445 System
.out
.println("\t--noutf : "
446 + trans(StringId
.CLI_HELP_NOUTF_OPTION
));
447 System
.out
.println("\t--config : "
448 + trans(StringId
.CLI_HELP_CONFIG
));
449 System
.out
.println();
451 System
.out
.println(trans(StringId
.CLI_HELP_FOOTER
));
452 System
.out
.println();
459 * Return the {@link Card} corresponding to the given resource name -- a
460 * file or a remote jvcard URL.
463 * Will also fix the FN if required (see display.properties).
467 * a filename or a remote jvcard url with named resource (e.g.:
468 * <tt>jvcard://localhost:4444/coworkers.vcf</tt>)
470 * the {@link MergeCallback} to call in case of conflict, or NULL
471 * to disallow conflict management (the {@link Card} will not be
472 * allowed to synchronise in case of conflicts)
474 * @return the {@link Card}
476 * @throws IOException
477 * in case of IO error or remoting not available
479 static public CardResult
getCard(String input
, MergeCallback callback
)
481 boolean remote
= false;
482 Format format
= Format
.Abook
;
484 if (ext
.contains(".")) {
485 String tab
[] = ext
.split("\\.");
486 if (tab
.length
> 1 && tab
[tab
.length
- 1].equalsIgnoreCase("vcf")) {
487 format
= Format
.VCard21
;
491 if (input
.contains("://")) {
492 format
= Format
.VCard21
;
496 CardResult card
= null;
499 card
= Optional
.syncCard(input
, callback
);
501 card
= new CardResult(new Card(new File(input
), format
), false,
504 } catch (IOException ioe
) {
506 } catch (NotSupportedException e
) {
507 throw new IOException("Remoting support not available", e
);
511 if (defaultFn
!= null) {
513 for (Contact contact
: card
.getCard()) {
514 Data name
= contact
.getPreferredData("FN");
515 if (name
== null || name
.getValue().length() == 0
516 || forceComputedFn
) {
517 name
.setValue(contact
.toString(defaultFn
, "").trim());
520 } catch (Exception e
) {
521 // sync failed -> getCard() throws.
530 * Open the given path and add all its files if it is a directory or just
531 * this one if not to the returned list.
536 * @return the list of opened files
538 static private List
<String
> open(String path
) {
539 List
<String
> files
= new LinkedList
<String
>();
541 if (path
!= null && path
.startsWith("jvcard://")) {
542 if (path
.endsWith("/")) {
543 files
.addAll(list(path
));
548 File file
= new File(path
);
550 if (file
.isDirectory()) {
551 for (File subfile
: file
.listFiles()) {
552 if (!subfile
.isDirectory())
553 files
.add(subfile
.getAbsolutePath());
556 files
.add(file
.getAbsolutePath());
559 System
.err
.println("File or directory not found: \"" + path
568 * List all the available {@link Card}s on the given network location (which
569 * is expected to be a jVCard remote server, obviously).
572 * the jVCard remote server path (e.g.:
573 * <tt>jvcard://localhost:4444/</tt>)
575 * @return the list of {@link Card}s
577 static private List
<String
> list(String path
) {
578 List
<String
> files
= new LinkedList
<String
>();
581 String host
= path
.split("\\:")[1].substring(2);
582 int port
= Integer
.parseInt(path
.split("\\:")[2].replaceAll("/$",
584 SimpleSocket s
= new SimpleSocket(new Socket(host
, port
),
588 s
.sendCommand(Command
.LIST_CARD
);
589 for (String p
: s
.receiveBlock()) {
591 + p
.substring(StringUtils
.fromTime(0).length() + 1));
594 } catch (Exception e
) {
602 * Really, really ask for UTF-8 encoding.
604 static private void utf8() {
606 System
.setProperty("file.encoding", "UTF-8");
607 Field charset
= Charset
.class.getDeclaredField("defaultCharset");
608 charset
.setAccessible(true);
609 charset
.set(null, null);
610 } catch (SecurityException e
) {
611 } catch (NoSuchFieldException e
) {
612 } catch (IllegalArgumentException e
) {
613 } catch (IllegalAccessException e
) {
618 * Read display.properties to know if we should fix the FN field when empty,
619 * or always, or never.
621 static private void readNFN() {
622 DisplayBundle map
= new DisplayBundle();
624 defaultFn
= map
.getString(DisplayOption
.CONTACT_DETAILS_DEFAULT_FN
);
626 forceComputedFn
= map
.getBoolean(
627 DisplayOption
.CONTACT_DETAILS_SHOW_COMPUTED_FN
, false);
631 * Syntax error detected, closing the application with an error message.
634 * the syntax error case
636 static private void SERR(StringId err
, Object
... values
) {
637 ERR(StringId
.CLI_SERR
, err
, ERR_SYNTAX
, values
);
641 * Error detected, closing the application with an error message.
646 * the suberror or NULL if none
648 * the error code as declared above
650 static private void ERR(StringId err
, StringId suberr
, int CODE
,
651 Object
... subvalues
) {
653 System
.err
.println(trans(err
));
655 System
.err
.println(trans(err
, trans(suberr
, subvalues
)));