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
.parsers
.Format
;
19 import be
.nikiroo
.jvcard
.remote
.Command
;
20 import be
.nikiroo
.jvcard
.remote
.SimpleSocket
;
21 import be
.nikiroo
.jvcard
.resources
.Bundles
;
22 import be
.nikiroo
.jvcard
.resources
.StringUtils
;
23 import be
.nikiroo
.jvcard
.resources
.Trans
;
24 import be
.nikiroo
.jvcard
.resources
.Trans
.StringId
;
27 * This class contains the runnable Main method. It will parse the user supplied
28 * parameters and take action based upon those. Most of the time, it will start
35 static public final String APPLICATION_TITLE
= "jVcard";
36 static public final String APPLICATION_VERSION
= "1.0-beta3-dev";
38 static private final int ERR_NO_FILE
= 1;
39 static private final int ERR_SYNTAX
= 2;
40 static private final int ERR_INTERNAL
= 3;
41 static private Trans transService
;
44 CONTACT_MANAGER
, I18N
, SERVER
, LOAD_PHOTO
, SAVE_PHOTO
, ONLY_PHOTO
,
48 * Translate the given {@link StringId} into user text.
53 * the values to insert instead of the place holders in the
56 * @return the translated text with the given value where required
58 static public String
trans(StringId id
, String
... values
) {
59 return transService
.trans(id
, (String
[]) values
);
63 * Check if unicode characters should be used.
65 * @return TRUE to allow unicode
67 static public boolean isUnicode() {
68 return transService
.isUnicode();
72 * Start the application.
75 * The returned exit codes are:
77 * <li>1: no files to open</li>
78 * <li>2: invalid syntax</li>
79 * <li>3: internal error</li>
84 * the parameters (see <tt>--help</tt> to know which are
87 public static void main(String
[] args
) {
88 Boolean textMode
= null;
89 boolean noMoreParams
= false;
90 boolean filesTried
= false;
92 // get the "system default" language to help translate the --help
94 String language
= null;
95 transService
= new Trans(language
);
97 boolean unicode
= transService
.isUnicode();
99 List
<String
> files
= new LinkedList
<String
>();
101 Mode mode
= Mode
.CONTACT_MANAGER
;
102 String format
= null;
103 for (int index
= 0; index
< args
.length
; index
++) {
104 String arg
= args
[index
];
105 if (!noMoreParams
&& arg
.equals("--")) {
107 } else if (!noMoreParams
&& arg
.equals("--help")) {
109 .println("TODO: implement some help text.\n"
110 + "Usable switches:\n"
111 + "\t--: stop looking for switches\n"
112 + "\t--help: this here thingy\n"
113 + "\t--lang LANGUAGE: choose the language, for instance en_GB\n"
114 + "\t--tui: force pure text mode even if swing treminal is available\n"
115 + "\t--gui: force swing terminal mode\n"
116 + "\t--noutf: force non-utf8 mode if you need it\n"
117 + "\t--config DIRECTORY: force the given directory as a CONFIG_DIR\n"
118 + "\t--server PORT: start a remoting server instead of a client\n"
119 + "\t--i18n DIR: generate the translation file for the given language (can be \"\") to/from the .properties given dir\n"
120 + "\t--save-photo DIR FORMAT: save the contacts' photos to DIR, named after FORMAT\n"
121 + "\t--load-photo DIR FORMAT: load the contacts' photos from DIR, named after FORMAT\n"
122 + "\t--only-photo DIR FORMAT: load the contacts' photos from DIR, named after FORMAT, overwrite all other photos of selected contacts\n"
123 + "everyhing else is either a file to open or a directory to open\n"
124 + "(we will only open 1st level files in given directories)\n"
125 + "('jvcard://hostname:8888/file' links -- or without 'file' -- are also ok)\n");
127 } else if (!noMoreParams
&& arg
.equals("--tui")) {
129 } else if (!noMoreParams
&& arg
.equals("--gui")) {
131 } else if (!noMoreParams
&& arg
.equals("--noutf")) {
133 transService
.setUnicode(unicode
);
134 } else if (!noMoreParams
&& arg
.equals("--lang")) {
136 if (index
>= args
.length
) {
137 System
.err
.println("Syntax error: no language given");
138 System
.exit(ERR_SYNTAX
);
142 language
= args
[index
];
143 transService
= new Trans(language
);
144 transService
.setUnicode(unicode
);
145 } else if (!noMoreParams
&& arg
.equals("--config")) {
147 if (index
>= args
.length
) {
149 .println("Syntax error: no config directory given");
150 System
.exit(ERR_SYNTAX
);
154 Bundles
.setDirectory(args
[index
]);
155 transService
= new Trans(language
);
156 transService
.setUnicode(unicode
);
157 } else if (!noMoreParams
&& arg
.equals("--server")) {
158 if (mode
!= Mode
.CONTACT_MANAGER
) {
160 .println("Syntax error: you can only use one of: \n"
166 System
.exit(ERR_SYNTAX
);
172 if (index
>= args
.length
) {
173 System
.err
.println("Syntax error: no port given");
174 System
.exit(ERR_SYNTAX
);
179 port
= Integer
.parseInt(args
[index
]);
180 } catch (NumberFormatException e
) {
181 System
.err
.println("Invalid port number: " + args
[index
]);
182 System
.exit(ERR_SYNTAX
);
185 } else if (!noMoreParams
&& arg
.equals("--i18n")) {
186 if (mode
!= Mode
.CONTACT_MANAGER
) {
188 .println("Syntax error: you can only use one of: \n"
194 System
.exit(ERR_SYNTAX
);
200 if (index
>= args
.length
) {
202 .println("Syntax error: no .properties directory given");
203 System
.exit(ERR_SYNTAX
);
208 } else if (!noMoreParams
209 && (arg
.equals("--load-photo")
210 || arg
.equals("--save-photo") || arg
211 .equals("--only-photo"))) {
212 if (mode
!= Mode
.CONTACT_MANAGER
) {
214 .println("Syntax error: you can only use one of: \n"
220 System
.exit(ERR_SYNTAX
);
224 if (arg
.equals("--load-photo")) {
225 mode
= Mode
.LOAD_PHOTO
;
226 } else if (arg
.equals("--save-photo")) {
227 mode
= Mode
.SAVE_PHOTO
;
229 mode
= Mode
.ONLY_PHOTO
;
233 if (index
>= args
.length
) {
234 System
.err
.println("Syntax error: photo directory given");
235 System
.exit(ERR_SYNTAX
);
242 if (index
>= args
.length
) {
243 System
.err
.println("Syntax error: photo format given");
244 System
.exit(ERR_SYNTAX
);
248 format
= args
[index
];
251 files
.addAll(open(arg
));
255 // Force headless mode if we run in forced-text mode
256 if (mode
!= Mode
.CONTACT_MANAGER
|| (textMode
!= null && textMode
)) {
257 // same as -Djava.awt.headless=true
258 System
.setProperty("java.awt.headless", "true");
266 if (mode
== Mode
.SERVER
&& files
.size() > 0) {
268 .println("Invalid syntax: you cannot both use --server and provide card files");
269 System
.exit(ERR_SYNTAX
);
270 } else if (mode
== Mode
.I18N
&& files
.size() > 0) {
272 .println("Invalid syntax: you cannot both use --i18n and provide card files");
273 System
.exit(ERR_SYNTAX
);
274 } else if (mode
== Mode
.I18N
&& language
== null) {
276 .println("Invalid syntax: you cannot use --i18n without --lang");
277 System
.exit(ERR_SYNTAX
);
278 } else if ((mode
== Mode
.CONTACT_MANAGER
|| mode
== Mode
.SAVE_PHOTO
|| mode
== Mode
.LOAD_PHOTO
)
279 && files
.size() == 0) {
280 if (files
.size() == 0 && !filesTried
) {
281 files
.addAll(open("."));
284 if (files
.size() == 0) {
285 System
.err
.println("No files to open");
286 System
.exit(ERR_NO_FILE
);
295 Optional
.runServer(port
);
296 } catch (Exception e
) {
297 if (e
instanceof IOException
) {
299 .println("I/O Exception: Cannot start the server");
301 System
.err
.println("Remoting support not available");
302 System
.exit(ERR_INTERNAL
);
309 Trans
.generateTranslationFile(dir
, language
);
310 } catch (IOException e
) {
312 .println("I/O Exception: Cannot create/update a language in directory: "
319 for (String file
: files
) {
321 Card card
= getCard(file
, null).getCard();
322 for (Contact contact
: card
) {
323 String filename
= contact
.toString(format
, "");
324 File f
= new File(dir
, filename
);
328 String b64
= StringUtils
.fromImage(ImageIO
331 if (mode
== Mode
.ONLY_PHOTO
) {
332 for (Data photo
= contact
333 .getPreferredData("PHOTO"); photo
!= null; photo
= contact
334 .getPreferredData("PHOTO")) {
339 List
<TypeInfo
> types
= new LinkedList
<TypeInfo
>();
340 types
.add(new TypeInfo("ENCODING", "b"));
341 types
.add(new TypeInfo("TYPE", "png"));
342 Data photo
= new Data(types
, "PHOTO", b64
, null);
344 } catch (IOException e
) {
345 System
.err
.println("Cannot read photo: "
351 } catch (IOException e
) {
352 System
.err
.println("Card cannot be opened: " + file
);
358 for (String file
: files
) {
360 Card card
= getCard(file
, null).getCard();
361 for (Contact contact
: card
) {
362 Data photo
= contact
.getPreferredData("PHOTO");
364 String filename
= contact
.toString(format
, "");
365 File f
= new File(dir
, filename
+ ".png");
368 StringUtils
.toImage(photo
.getValue()),
370 } catch (IOException e
) {
372 .println("Cannot save photo of contact: "
374 .getPreferredDataValue("FN"));
378 } catch (IOException e
) {
379 System
.err
.println("Card cannot be opened: " + file
);
384 case CONTACT_MANAGER
: {
386 Optional
.startTui(textMode
, files
);
387 } catch (Exception e
) {
388 if (e
instanceof IOException
) {
390 .println("I/O Exception: Cannot start the program with the given cards");
392 System
.err
.println("TUI support not available");
393 System
.exit(ERR_INTERNAL
);
402 * Return the {@link Card} corresponding to the given resource name -- a
403 * file or a remote jvcard URL
406 * a filename or a remote jvcard url with named resource (e.g.:
407 * <tt>jvcard://localhost:4444/coworkers.vcf</tt>)
409 * the {@link MergeCallback} to call in case of conflict, or NULL
410 * to disallow conflict management (the {@link Card} will not be
411 * allowed to synchronise in case of conflicts)
413 * @return the {@link Card}
415 * @throws IOException
416 * in case of IO error or remoting not available
418 static public CardResult
getCard(String input
, MergeCallback callback
)
420 boolean remote
= false;
421 Format format
= Format
.Abook
;
423 if (ext
.contains(".")) {
424 String tab
[] = ext
.split("\\.");
425 if (tab
.length
> 1 && tab
[tab
.length
- 1].equalsIgnoreCase("vcf")) {
426 format
= Format
.VCard21
;
430 if (input
.contains("://")) {
431 format
= Format
.VCard21
;
435 CardResult card
= null;
438 card
= Optional
.syncCard(input
, callback
);
440 card
= new CardResult(new Card(new File(input
), format
), false,
443 } catch (IOException ioe
) {
445 } catch (Exception e
) {
446 throw new IOException("Remoting support not available", e
);
453 * Open the given path and add all its files if it is a directory or just
454 * this one if not to the returned list.
459 * @return the list of opened files
461 static private List
<String
> open(String path
) {
462 List
<String
> files
= new LinkedList
<String
>();
464 if (path
!= null && path
.startsWith("jvcard://")) {
465 if (path
.endsWith("/")) {
466 files
.addAll(list(path
));
471 File file
= new File(path
);
473 if (file
.isDirectory()) {
474 for (File subfile
: file
.listFiles()) {
475 if (!subfile
.isDirectory())
476 files
.add(subfile
.getAbsolutePath());
479 files
.add(file
.getAbsolutePath());
482 System
.err
.println("File or directory not found: \"" + path
491 * List all the available {@link Card}s on the given network location (which
492 * is expected to be a jVCard remote server, obviously).
495 * the jVCard remote server path (e.g.:
496 * <tt>jvcard://localhost:4444/</tt>)
498 * @return the list of {@link Card}s
500 static private List
<String
> list(String path
) {
501 List
<String
> files
= new LinkedList
<String
>();
504 String host
= path
.split("\\:")[1].substring(2);
505 int port
= Integer
.parseInt(path
.split("\\:")[2].replaceAll("/$",
507 SimpleSocket s
= new SimpleSocket(new Socket(host
, port
),
511 s
.sendCommand(Command
.LIST_CARD
);
512 for (String p
: s
.receiveBlock()) {
514 + p
.substring(StringUtils
.fromTime(0).length() + 1));
517 } catch (Exception e
) {
525 * Really, really ask for UTF-8 encoding.
527 static private void utf8() {
529 System
.setProperty("file.encoding", "UTF-8");
530 Field charset
= Charset
.class.getDeclaredField("defaultCharset");
531 charset
.setAccessible(true);
532 charset
.set(null, null);
533 } catch (SecurityException e
) {
534 } catch (NoSuchFieldException e
) {
535 } catch (IllegalArgumentException e
) {
536 } catch (IllegalAccessException e
) {