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 be
.nikiroo
.jvcard
.Card
;
12 import be
.nikiroo
.jvcard
.Contact
;
13 import be
.nikiroo
.jvcard
.Data
;
14 import be
.nikiroo
.jvcard
.TypeInfo
;
15 import be
.nikiroo
.jvcard
.launcher
.CardResult
.MergeCallback
;
16 import be
.nikiroo
.jvcard
.launcher
.Optional
.NotSupportedException
;
17 import be
.nikiroo
.jvcard
.parsers
.Format
;
18 import be
.nikiroo
.jvcard
.remote
.Command
;
19 import be
.nikiroo
.jvcard
.remote
.SimpleSocket
;
20 import be
.nikiroo
.jvcard
.resources
.DisplayBundle
;
21 import be
.nikiroo
.jvcard
.resources
.DisplayOption
;
22 import be
.nikiroo
.jvcard
.resources
.RemoteBundle
;
23 import be
.nikiroo
.jvcard
.resources
.StringId
;
24 import be
.nikiroo
.jvcard
.resources
.TransBundle
;
25 import be
.nikiroo
.utils
.IOUtils
;
26 import be
.nikiroo
.utils
.Image
;
27 import be
.nikiroo
.utils
.ImageUtils
;
28 import be
.nikiroo
.utils
.StringUtils
;
29 import be
.nikiroo
.utils
.Version
;
30 import be
.nikiroo
.utils
.resources
.Bundles
;
33 * This class contains the runnable Main method. It will parse the user supplied
34 * parameters and take action based upon those. Most of the time, it will start
41 /** The name of the program */
42 static public final String APPLICATION_TITLE
= "jVcard";
44 static private final int ERR_NO_FILE
= 1;
45 static private final int ERR_SYNTAX
= 2;
46 static private final int ERR_INTERNAL
= 3;
47 static private TransBundle transService
;
49 static private String defaultFn
;
50 static private boolean forceComputedFn
;
53 CONTACT_MANAGER
, I18N
, SERVER
, LOAD_PHOTO
, SAVE_PHOTO
, SAVE_CONFIG
, HELP
, SAVE_TO
,
57 * Translate the given {@link StringId} into user text.
62 * the values to insert instead of the place holders in the
65 * @return the translated text with the given value where required
67 static public String
trans(StringId id
, Object
... values
) {
68 return transService
.getString(id
, values
);
72 * Check if unicode characters should be used.
74 * @return TRUE to allow unicode
76 static public boolean isUnicode() {
77 return transService
.isUnicode();
81 * Start the application.
84 * The returned exit codes are:
86 * <li>1: no files to open</li>
87 * <li>2: invalid syntax</li>
88 * <li>3: internal error</li>
93 * the parameters (see <tt>--help</tt> to know which are
96 public static void main(String
[] args
) {
97 Boolean textMode
= null;
98 boolean noMoreParams
= false;
99 boolean filesTried
= false;
101 // get the "system default" language to help translate the --help
103 String language
= null;
104 transService
= new TransBundle(language
);
106 boolean unicode
= transService
.isUnicode();
108 List
<String
> files
= new LinkedList
<String
>();
110 Mode mode
= Mode
.CONTACT_MANAGER
;
111 String format
= null;
112 String output
= null;
113 for (int index
= 0; index
< args
.length
; index
++) {
114 String arg
= args
[index
];
115 if (!noMoreParams
&& arg
.equals("--")) {
117 } else if (!noMoreParams
&& arg
.equals("--help")) {
118 if (mode
!= Mode
.CONTACT_MANAGER
) {
119 SERR(StringId
.CLI_SERR_MODES
);
123 } else if (!noMoreParams
&& arg
.equals("--tui")) {
125 } else if (!noMoreParams
&& arg
.equals("--gui")) {
127 } else if (!noMoreParams
&& arg
.equals("--noutf")) {
129 transService
.setUnicode(unicode
);
130 } else if (!noMoreParams
&& arg
.equals("--lang")) {
132 if (index
>= args
.length
) {
133 SERR(StringId
.CLI_SERR_NOLANG
);
137 language
= args
[index
];
138 transService
= new TransBundle(language
);
139 transService
.setUnicode(unicode
);
140 } else if (!noMoreParams
&& arg
.equals("--config")) {
142 if (index
>= args
.length
) {
143 SERR(StringId
.CLI_SERR_NODIR
);
147 Bundles
.setDirectory(args
[index
]);
148 transService
= new TransBundle(language
);
149 transService
.setUnicode(unicode
);
150 } else if (!noMoreParams
&& arg
.equals("--save-config")) {
152 if (index
>= args
.length
) {
153 SERR(StringId
.CLI_SERR_NODIR
);
158 if (mode
!= Mode
.CONTACT_MANAGER
) {
159 SERR(StringId
.CLI_SERR_MODES
);
162 mode
= Mode
.SAVE_CONFIG
;
163 } else if (!noMoreParams
&& arg
.equals("--server")) {
164 if (mode
!= Mode
.CONTACT_MANAGER
) {
165 SERR(StringId
.CLI_SERR_MODES
);
171 if (index
>= args
.length
) {
172 SERR(StringId
.CLI_SERR_NOPORT
);
177 port
= Integer
.parseInt(args
[index
]);
178 } catch (NumberFormatException e
) {
179 SERR(StringId
.CLI_SERR_BADPORT
, "" + args
[index
]);
182 } else if (!noMoreParams
&& arg
.equals("--i18n")) {
183 if (mode
!= Mode
.CONTACT_MANAGER
) {
184 SERR(StringId
.CLI_SERR_MODES
);
190 if (index
>= args
.length
) {
191 SERR(StringId
.CLI_SERR_NODIR
);
196 } else if (!noMoreParams
197 && (arg
.equals("--load-photo")
198 || arg
.equals("--save-photo") || arg
199 .equals("--only-photo"))) {
200 if (mode
!= Mode
.CONTACT_MANAGER
) {
201 SERR(StringId
.CLI_SERR_MODES
);
205 if (arg
.equals("--load-photo")) {
206 mode
= Mode
.LOAD_PHOTO
;
207 } else if (arg
.equals("--save-photo")) {
208 mode
= Mode
.SAVE_PHOTO
;
212 if (index
>= args
.length
) {
213 SERR(StringId
.CLI_SERR_NODIR
);
220 if (index
>= args
.length
) {
221 SERR(StringId
.CLI_SERR_NOFORMAT
);
225 format
= args
[index
];
226 } else if (!noMoreParams
&& (arg
.equals("--save-to"))) {
227 if (mode
!= Mode
.CONTACT_MANAGER
) {
228 SERR(StringId
.CLI_SERR_MODES
);
234 if (index
>= args
.length
) {
235 SERR(StringId
.CLI_ERR_NOFILES
);
239 output
= args
[index
];
242 files
.addAll(open(arg
));
246 // Force headless mode if we run in forced-text mode
247 if (mode
!= Mode
.CONTACT_MANAGER
|| (textMode
!= null && textMode
)) {
248 // same as -Djava.awt.headless=true
249 System
.setProperty("java.awt.headless", "true");
256 // N/FN fix information:
260 if (mode
== Mode
.SERVER
&& files
.size() > 0) {
261 SERR(StringId
.CLI_SERR_NOLANG
, "--server");
263 } else if (mode
== Mode
.I18N
&& files
.size() > 0) {
264 SERR(StringId
.CLI_SERR_NOLANG
, "--i18n");
266 } else if (mode
== Mode
.I18N
&& language
== null) {
267 SERR(StringId
.CLI_SERR_NOLANG
);
268 } else if ((mode
== Mode
.CONTACT_MANAGER
|| mode
== Mode
.SAVE_PHOTO
|| mode
== Mode
.LOAD_PHOTO
)
269 && files
.size() == 0) {
270 if (files
.size() == 0 && !filesTried
) {
271 files
.addAll(open("."));
274 if (files
.size() == 0) {
275 ERR(StringId
.CLI_ERR
, StringId
.CLI_ERR_NOFILES
, ERR_NO_FILE
);
284 if (!new File(dir
).isDirectory()) {
285 if (!new File(dir
).mkdir()) {
286 System
.err
.println(trans(
287 StringId
.CLI_ERR_CANNOT_CREATE_CONFDIR
, dir
));
291 new TransBundle().updateFile(dir
); // default locale
292 for (String lang
: new TransBundle().getKnownLanguages()) {
293 new TransBundle(lang
).updateFile(dir
);
296 // new UIColors().updateFile(dir);
297 new DisplayBundle().updateFile(dir
);
298 new RemoteBundle().updateFile(dir
);
299 } catch (IOException e
) {
302 System
.exit(ERR_INTERNAL
);
308 Optional
.runServer(port
);
309 } catch (IOException e
) {
310 ERR(StringId
.CLI_ERR
, StringId
.CLI_ERR_CANNOT_START
,
313 } catch (NotSupportedException e
) {
314 if (!e
.isCompiledIn()) {
315 ERR(StringId
.CLI_ERR
, StringId
.CLI_ERR_NO_REMOTING
,
320 ERR(StringId
.CLI_ERR
, StringId
.CLI_ERR
, ERR_INTERNAL
);
327 transService
.updateFile(dir
);
328 } catch (IOException e
) {
329 ERR(StringId
.CLI_ERR
, StringId
.CLI_ERR_CANNOT_CREATE_LANG
,
336 for (String file
: files
) {
338 Card card
= getCard(file
, null).getCard();
339 for (Contact contact
: card
) {
340 String filename
= contact
.toString(format
, "");
341 File f
= new File(dir
, filename
);
344 System
.out
.println("Loading " + f
);
346 String type
= "jpeg";
347 int dotIndex
= filename
.indexOf('.');
349 && (dotIndex
+ 1) < filename
.length()) {
350 type
= filename
.substring(dotIndex
+ 1)
355 Image img
= new Image(IOUtils
.toByteArray(f
));
357 b64
= img
.toBase64();
362 // remove previous photos:
363 for (Data photo
= contact
364 .getPreferredData("PHOTO"); photo
!= null; photo
= contact
365 .getPreferredData("PHOTO")) {
370 List
<TypeInfo
> types
= new LinkedList
<TypeInfo
>();
371 types
.add(new TypeInfo("ENCODING", "b"));
372 types
.add(new TypeInfo("TYPE", type
));
373 Data photo
= new Data(types
, "PHOTO", b64
, null);
375 } catch (IOException e
) {
376 System
.err
.println("Cannot read photo: "
382 } catch (IOException e
) {
384 .println(trans(StringId
.CLI_ERR_CANNOT_OPEN
, file
));
390 for (String file
: files
) {
392 Card card
= getCard(file
, null).getCard();
393 for (Contact contact
: card
) {
394 Data photo
= contact
.getPreferredData("PHOTO");
396 String filename
= contact
.toString(format
, "");
397 File f
= new File(dir
, filename
+ ".png");
398 System
.out
.println("Saving " + f
);
399 Image img
= new Image(photo
.getValue());
401 ImageUtils
.getInstance().saveAsImage(img
, f
,
403 } catch (IOException e
) {
404 System
.err
.println(trans(
405 StringId
.CLI_ERR_CANNOT_SAVE_PHOTO
,
406 contact
.getPreferredDataValue("FN")));
410 } catch (IOException e
) {
412 .println(trans(StringId
.CLI_ERR_CANNOT_OPEN
, file
));
417 case CONTACT_MANAGER
: {
419 Optional
.startTui(textMode
, files
);
420 } catch (IOException e
) {
421 ERR(StringId
.CLI_ERR
, StringId
.CLI_ERR_CANNOT_START
,
424 } catch (NotSupportedException e
) {
425 if (!e
.isCompiledIn()) {
426 ERR(StringId
.CLI_ERR
, StringId
.CLI_ERR_NO_TUI
, ERR_INTERNAL
);
430 ERR(StringId
.CLI_ERR
, StringId
.CLI_ERR
, ERR_INTERNAL
);
437 Card total
= new Card(null, getCardFormat(output
));
439 for (String file
: files
) {
441 Card card
= getCard(file
, null).getCard();
443 while (card
.size() > 0) {
444 total
.add(card
.remove(0));
446 } catch (IOException e
) {
447 System
.err
.println(trans(StringId
.CLI_ERR_CANNOT_OPEN
,
452 total
.saveAs(new File(output
), getCardFormat(output
));
453 } catch (IOException e
) {
454 System
.err
.println(trans(StringId
.CLI_ERR_CANNOT_OPEN
, output
));
460 System
.out
.println(APPLICATION_TITLE
+ " "
461 + Version
.getCurrentVersion());
462 System
.out
.println();
464 System
.out
.println(trans(StringId
.CLI_HELP
));
465 System
.out
.println();
467 System
.out
.println(trans(StringId
.CLI_HELP_MODES
));
468 System
.out
.println("\t--help : "
469 + trans(StringId
.CLI_HELP_MODE_HELP
));
470 System
.out
.println("\t(--tui|--gui) (--noutf) ... : "
471 + trans(StringId
.CLI_HELP_MODE_CONTACT_MANAGER
));
472 System
.out
.println("\t--server PORT ... : "
473 + trans(StringId
.CLI_HELP_MODE_SERVER
));
474 System
.out
.println("\t--save-config DIR : "
475 + trans(StringId
.CLI_HELP_MODE_SAVE_CONFIG
));
476 System
.out
.println("\t--i18n DIR ---lang LANG : "
477 + trans(StringId
.CLI_HELP_MODE_I18N
));
478 System
.out
.println("\t--load-photo DIR FORMAT ... : "
479 + trans(StringId
.CLI_HELP_MODE_LOAD_PHOTO
));
480 System
.out
.println("\t--save-photo DIR FORMAT ... : "
481 + trans(StringId
.CLI_HELP_MODE_SAVE_PHOTO
));
482 System
.out
.println("\t--save-to output(.vcf) ... : "
483 + trans(StringId
.CLI_HELP_MODE_SAVE_TO
));
484 System
.out
.println();
486 System
.out
.println(trans(StringId
.CLI_HELP_OPTIONS
));
487 System
.out
.println("\t-- : " + trans(StringId
.CLI_HELP_DD
));
488 System
.out
.println("\t--lang LANG : "
489 + trans(StringId
.CLI_HELP_LANG
));
490 System
.out
.println("\t--tui : " + trans(StringId
.CLI_HELP_TUI
));
491 System
.out
.println("\t--gui : " + trans(StringId
.CLI_HELP_GUI
));
492 System
.out
.println("\t--noutf : "
493 + trans(StringId
.CLI_HELP_NOUTF_OPTION
));
494 System
.out
.println("\t--config : "
495 + trans(StringId
.CLI_HELP_CONFIG
));
496 System
.out
.println();
498 System
.out
.println(trans(StringId
.CLI_HELP_FOOTER
));
499 System
.out
.println();
507 * Return the {@link Card} corresponding to the given resource name -- a
508 * file or a remote jvcard URL.
511 * Will also fix the FN if required (see display.properties).
515 * a filename or a remote jvcard url with named resource (e.g.:
516 * <tt>jvcard://localhost:4444/coworkers.vcf</tt>)
518 * the {@link MergeCallback} to call in case of conflict, or NULL
519 * to disallow conflict management (the {@link Card} will not be
520 * allowed to synchronise in case of conflicts)
522 * @return the {@link Card}
524 * @throws IOException
525 * in case of IO error or remoting not available
527 static public CardResult
getCard(String input
, MergeCallback callback
)
529 boolean remote
= isFileRemote(input
);
530 Format format
= getCardFormat(input
);
532 CardResult card
= null;
535 card
= Optional
.syncCard(input
, callback
);
537 card
= new CardResult(new Card(new File(input
), format
), false,
540 } catch (IOException ioe
) {
542 } catch (NotSupportedException e
) {
543 throw new IOException("Remoting support not available", e
);
547 if (defaultFn
!= null) {
549 for (Contact contact
: card
.getCard()) {
550 Data name
= contact
.getPreferredData("FN");
551 Data n
= contact
.getPreferredData("N");
552 boolean hasN
= n
!= null && n
.getValue().length() > 0;
553 if (name
== null || name
.getValue().length() == 0
554 || (forceComputedFn
&& hasN
)) {
555 name
.setValue(contact
.toString(defaultFn
, "").trim());
558 } catch (Exception e
) {
559 // sync failed -> getCard() throws.
567 static private boolean isFileRemote(String input
) {
568 return input
.contains("://");
571 static Format
getCardFormat(String input
) {
572 if (isFileRemote(input
)) {
573 return Format
.VCard21
;
576 Format format
= Format
.Abook
;
578 if (ext
.contains(".")) {
579 String tab
[] = ext
.split("\\.");
580 if (tab
.length
> 1 && tab
[tab
.length
- 1].equalsIgnoreCase("vcf")) {
581 format
= Format
.VCard21
;
589 * Open the given path and add all its files if it is a directory or just
590 * this one if not to the returned list.
595 * @return the list of opened files
597 static private List
<String
> open(String path
) {
598 List
<String
> files
= new LinkedList
<String
>();
600 if (path
!= null && path
.startsWith("jvcard://")) {
601 if (path
.endsWith("/")) {
602 files
.addAll(list(path
));
607 File file
= new File(path
);
609 if (file
.isDirectory()) {
610 for (File subfile
: file
.listFiles()) {
611 if (!subfile
.isDirectory())
612 files
.add(subfile
.getAbsolutePath());
615 files
.add(file
.getAbsolutePath());
618 System
.err
.println("File or directory not found: \"" + path
627 * List all the available {@link Card}s on the given network location (which
628 * is expected to be a jVCard remote server, obviously).
631 * the jVCard remote server path (e.g.:
632 * <tt>jvcard://localhost:4444/</tt>)
634 * @return the list of {@link Card}s
636 static private List
<String
> list(String path
) {
637 List
<String
> files
= new LinkedList
<String
>();
640 String host
= path
.split("\\:")[1].substring(2);
641 int port
= Integer
.parseInt(path
.split("\\:")[2].replaceAll("/$",
643 SimpleSocket s
= new SimpleSocket(new Socket(host
, port
),
647 s
.sendCommand(Command
.LIST_CARD
);
648 for (String p
: s
.receiveBlock()) {
650 + p
.substring(StringUtils
.fromTime(0).length() + 1));
653 } catch (Exception e
) {
661 * Really, really ask for UTF-8 encoding.
663 static private void utf8() {
665 System
.setProperty("file.encoding", "UTF-8");
666 Field charset
= Charset
.class.getDeclaredField("defaultCharset");
667 charset
.setAccessible(true);
668 charset
.set(null, null);
669 } catch (SecurityException e
) {
670 } catch (NoSuchFieldException e
) {
671 } catch (IllegalArgumentException e
) {
672 } catch (IllegalAccessException e
) {
677 * Read display.properties to know if we should fix the FN field when empty,
678 * or always, or never.
680 static private void readNFN() {
681 DisplayBundle map
= new DisplayBundle();
683 defaultFn
= map
.getString(DisplayOption
.CONTACT_DETAILS_DEFAULT_FN
);
685 forceComputedFn
= map
.getBoolean(
686 DisplayOption
.CONTACT_DETAILS_SHOW_COMPUTED_FN
, false);
690 * Syntax error detected, closing the application with an error message.
693 * the syntax error case
695 static private void SERR(StringId err
, Object
... values
) {
696 ERR(StringId
.CLI_SERR
, err
, ERR_SYNTAX
, values
);
700 * Error detected, closing the application with an error message.
705 * the suberror or NULL if none
707 * the error code as declared above
709 static private void ERR(StringId err
, StringId suberr
, int CODE
,
710 Object
... subvalues
) {
712 System
.err
.println(trans(err
));
714 System
.err
.println(trans(err
, trans(suberr
, subvalues
)));