New launcher class to start all 3 modes:
[jvcard.git] / src / be / nikiroo / jvcard / launcher / Main.java
1 package be.nikiroo.jvcard.launcher;
2
3 import java.io.File;
4 import java.io.IOException;
5 import java.lang.reflect.Field;
6 import java.lang.reflect.InvocationTargetException;
7 import java.lang.reflect.Method;
8 import java.net.Socket;
9 import java.nio.charset.Charset;
10 import java.util.LinkedList;
11 import java.util.List;
12
13 import be.nikiroo.jvcard.Card;
14 import be.nikiroo.jvcard.parsers.Format;
15 import be.nikiroo.jvcard.remote.Command.Verb;
16 import be.nikiroo.jvcard.remote.SimpleSocket;
17 import be.nikiroo.jvcard.resources.Bundles;
18 import be.nikiroo.jvcard.resources.StringUtils;
19 import be.nikiroo.jvcard.resources.Trans;
20 import be.nikiroo.jvcard.resources.Trans.StringId;
21
22 /**
23 * This class contains the runnable Main method. It will parse the user supplied
24 * parameters and take action based upon those. Most of the time, it will start
25 * a MainWindow.
26 *
27 * @author niki
28 *
29 */
30 public class Main {
31 static public final String APPLICATION_TITLE = "jVcard";
32 static public final String APPLICATION_VERSION = "1.0-beta2-dev";
33
34 static private final int ERR_NO_FILE = 1;
35 static private final int ERR_SYNTAX = 2;
36 static private final int ERR_INTERNAL = 3;
37 static private Trans transService;
38
39 /**
40 * Translate the given {@link StringId}.
41 *
42 * @param id
43 * the ID to translate
44 *
45 * @return the translation
46 */
47 static public String trans(StringId id) {
48 return transService.trans(id);
49 }
50
51 /**
52 * Check if unicode characters should be used.
53 *
54 * @return TRUE to allow unicode
55 */
56 static public boolean isUnicode() {
57 return transService.isUnicode();
58 }
59
60 /**
61 * Start the application.
62 *
63 * <p>
64 * The returned exit codes are:
65 * <ul>
66 * <li>1: no files to open</li>
67 * <li>2: invalid syntax</li>
68 * <li>3: internal error</li>
69 * </ul>
70 * </p>
71 *
72 * @param args
73 * the parameters (see <tt>--help</tt> to know which are
74 * supported)
75 */
76 public static void main(String[] args) {
77 Boolean textMode = null;
78 boolean noMoreParams = false;
79 boolean filesTried = false;
80
81 // get the "system default" language to help translate the --help
82 // message if needed
83 String language = null;
84 transService = new Trans(language);
85
86 boolean unicode = transService.isUnicode();
87 String i18nDir = null;
88 List<String> files = new LinkedList<String>();
89 Integer port = null;
90 for (int index = 0; index < args.length; index++) {
91 String arg = args[index];
92 if (!noMoreParams && arg.equals("--")) {
93 noMoreParams = true;
94 } else if (!noMoreParams && arg.equals("--help")) {
95 System.out
96 .println("TODO: implement some help text.\n"
97 + "Usable switches:\n"
98 + "\t--: stop looking for switches\n"
99 + "\t--help: this here thingy\n"
100 + "\t--lang LANGUAGE: choose the language, for instance en_GB\n"
101 + "\t--tui: force pure text mode even if swing treminal is available\n"
102 + "\t--gui: force swing terminal mode\n"
103 + "\t--noutf: force non-utf8 mode if you need it\n"
104 + "\t--config DIRECTORY: force the given directory as a CONFIG_DIR\n"
105 + "\t--server PORT: start a remoting server instead of a client\n"
106 + "\t--i18n DIR: generate the translation file for the given language (can be \"\") to/from the .properties given dir\n"
107 + "everyhing else is either a file to open or a directory to open\n"
108 + "(we will only open 1st level files in given directories)\n"
109 + "('jvcard://hostname:8888/file' links -- or without 'file' -- are also ok)\n");
110 return;
111 } else if (!noMoreParams && arg.equals("--tui")) {
112 textMode = true;
113 } else if (!noMoreParams && arg.equals("--gui")) {
114 textMode = false;
115 } else if (!noMoreParams && arg.equals("--noutf")) {
116 unicode = false;
117 transService.setUnicode(unicode);
118 } else if (!noMoreParams && arg.equals("--lang")) {
119 index++;
120 if (index >= args.length) {
121 System.err.println("Syntax error: no language given");
122 System.exit(ERR_SYNTAX);
123 return;
124 }
125
126 language = args[index];
127 transService = new Trans(language);
128 transService.setUnicode(unicode);
129 } else if (!noMoreParams && arg.equals("--config")) {
130 index++;
131 if (index >= args.length) {
132 System.err
133 .println("Syntax error: no config directory given");
134 System.exit(ERR_SYNTAX);
135 return;
136 }
137
138 Bundles.setDirectory(args[index]);
139 transService = new Trans(language);
140 transService.setUnicode(unicode);
141 } else if (!noMoreParams && arg.equals("--server")) {
142 index++;
143 if (index >= args.length) {
144 System.err.println("Syntax error: no port given");
145 System.exit(ERR_SYNTAX);
146 return;
147 }
148
149 try {
150 port = Integer.parseInt(args[index]);
151 } catch (NumberFormatException e) {
152 System.err.println("Invalid port number: " + args[index]);
153 System.exit(ERR_SYNTAX);
154 return;
155 }
156 } else if (!noMoreParams && arg.equals("--i18n")) {
157 index++;
158 if (index >= args.length) {
159 System.err
160 .println("Syntax error: no .properties directory given");
161 System.exit(ERR_SYNTAX);
162 return;
163 }
164
165 i18nDir = args[index];
166 } else {
167 filesTried = true;
168 files.addAll(open(arg));
169 }
170 }
171
172 if (unicode) {
173 utf8();
174 }
175
176 // Error management:
177 if (port != null && files.size() > 0) {
178 System.err
179 .println("Invalid syntax: you cannot both use --server and provide card files");
180 System.exit(ERR_SYNTAX);
181 } else if (i18nDir != null && files.size() > 0) {
182 System.err
183 .println("Invalid syntax: you cannot both use --i18n and provide card files");
184 System.exit(ERR_SYNTAX);
185 } else if (port != null && i18nDir != null) {
186 System.err
187 .println("Invalid syntax: you cannot both use --server and --i18n");
188 System.exit(ERR_SYNTAX);
189 } else if (i18nDir != null && language == null) {
190 System.err
191 .println("Invalid syntax: you cannot use --i18n without --lang");
192 System.exit(ERR_SYNTAX);
193 } else if (port == null && i18nDir == null && files.size() == 0) {
194 if (files.size() == 0 && !filesTried) {
195 files.addAll(open("."));
196 }
197
198 if (files.size() == 0) {
199 System.err.println("No files to open");
200 System.exit(ERR_NO_FILE);
201 return;
202 }
203 }
204 //
205
206 if (port != null) {
207 try {
208 runServer(port);
209 } catch (Exception e) {
210 if (e instanceof IOException) {
211 System.err
212 .println("I/O Exception: Cannot start the server");
213 } else {
214 System.err.println("FATAL ERROR");
215 e.printStackTrace();
216 System.exit(ERR_INTERNAL);
217 }
218 }
219 } else if (i18nDir != null) {
220 try {
221 Trans.generateTranslationFile(i18nDir, language);
222 } catch (IOException e) {
223 System.err
224 .println("I/O Exception: Cannot create/update a language in directory: "
225 + i18nDir);
226 }
227 } else {
228 try {
229 startTui(textMode, files);
230 } catch (Exception e) {
231 if (e instanceof IOException) {
232 System.err
233 .println("I/O Exception: Cannot start the program with the given cards");
234 } else {
235 System.err.println("FATAL ERROR");
236 e.printStackTrace();
237 System.exit(ERR_INTERNAL);
238 }
239 }
240 }
241 }
242
243 /**
244 * Return the {@link Card} corresponding to the given resource name -- a
245 * file or a remote jvcard URL
246 *
247 * @param input
248 * a filename or a remote jvcard url with named resource (e.g.:
249 * <tt>jvcard://localhost:4444/coworkers.vcf</tt>)
250 *
251 * @return the {@link Card}
252 *
253 * @throws IOException
254 * in case of IO error or remoting not available
255 */
256 static public Card getCard(String input) throws IOException {
257 boolean remote = false;
258 Format format = Format.Abook;
259 String ext = input;
260 if (ext.contains(".")) {
261 String tab[] = ext.split("\\.");
262 if (tab.length > 1 && tab[tab.length - 1].equalsIgnoreCase("vcf")) {
263 format = Format.VCard21;
264 }
265 }
266
267 if (input.contains("://")) {
268 format = Format.VCard21;
269 remote = true;
270 }
271
272 Card card = null;
273 try {
274 if (remote) {
275 card = syncCard(input);
276 } else {
277 card = new Card(new File(input), format);
278 }
279 } catch (IOException ioe) {
280 throw ioe;
281 } catch (Exception e) {
282 throw new IOException("Remoting not available", e);
283 }
284
285 return card;
286 }
287
288 /**
289 * Create a new jVCard server on the given port, then run it.
290 *
291 * @param port
292 * the port to run on
293 *
294 * @throws SecurityException
295 * in case of internal error
296 * @throws NoSuchMethodException
297 * in case of internal error
298 * @throws ClassNotFoundException
299 * in case of internal error
300 * @throws IllegalAccessException
301 * in case of internal error
302 * @throws InstantiationException
303 * in case of internal error
304 * @throws InvocationTargetException
305 * in case of internal error
306 * @throws IllegalArgumentException
307 * in case of internal error
308 * @throws IOException
309 * in case of IO error
310 */
311 @SuppressWarnings("unchecked")
312 static private void runServer(int port) throws NoSuchMethodException,
313 SecurityException, ClassNotFoundException, InstantiationException,
314 IllegalAccessException, IllegalArgumentException,
315 InvocationTargetException {
316 @SuppressWarnings("rawtypes")
317 Class serverClass = Class.forName("be.nikiroo.jvcard.remote.Server");
318 Method run = serverClass.getDeclaredMethod("run", new Class[] {});
319 run.invoke(serverClass.getConstructor(int.class).newInstance(port));
320 }
321
322 /**
323 * Start the TUI program.
324 *
325 * @param textMode
326 * TRUE to force text mode, FALSE to force the Swing terminal
327 * emulator, null to automatically determine the best choice
328 * @param files
329 * the files to show at startup
330 *
331 * @throws SecurityException
332 * in case of internal error
333 * @throws NoSuchMethodException
334 * in case of internal error
335 * @throws ClassNotFoundException
336 * in case of internal error
337 * @throws IllegalAccessException
338 * in case of internal error
339 * @throws InstantiationException
340 * in case of internal error
341 * @throws InvocationTargetException
342 * in case of internal error
343 * @throws IllegalArgumentException
344 * in case of internal error
345 * @throws IOException
346 * in case of IO error
347 */
348 @SuppressWarnings("unchecked")
349 static private void startTui(Boolean textMode, List<String> files)
350 throws NoSuchMethodException, SecurityException,
351 ClassNotFoundException, InstantiationException,
352 IllegalAccessException, IllegalArgumentException,
353 InvocationTargetException {
354 @SuppressWarnings("rawtypes")
355 Class launcherClass = Class
356 .forName("be.nikiroo.jvcard.tui.TuiLauncher");
357 Method start = launcherClass.getDeclaredMethod("start", new Class[] {
358 Boolean.class, List.class });
359 start.invoke(launcherClass.newInstance(), textMode, files);
360 }
361
362 /**
363 * Return the {@link Card} corresponding to the given URL, synchronised if
364 * necessary.
365 *
366 * @param input
367 * the jvcard:// with resource name URL (e.g.:
368 * <tt>jvcard://localhost:4444/coworkers</tt>)
369 *
370 * @throws SecurityException
371 * in case of internal error
372 * @throws NoSuchMethodException
373 * in case of internal error
374 * @throws ClassNotFoundException
375 * in case of internal error
376 * @throws IllegalAccessException
377 * in case of internal error
378 * @throws InstantiationException
379 * in case of internal error
380 * @throws InvocationTargetException
381 * in case of internal error
382 * @throws IllegalArgumentException
383 * in case of internal error
384 * @throws IOException
385 * in case of IO error
386 */
387 @SuppressWarnings("unchecked")
388 static private Card syncCard(String input) throws ClassNotFoundException,
389 NoSuchMethodException, SecurityException, InstantiationException,
390 IllegalAccessException, IllegalArgumentException,
391 InvocationTargetException, IOException {
392 @SuppressWarnings("rawtypes")
393 Class syncClass = Class.forName("be.nikiroo.jvcard.remote.Sync");
394 Method getCache = syncClass.getDeclaredMethod("getCache",
395 new Class[] {});
396 Method sync = syncClass.getDeclaredMethod("sync", new Class[] {
397 Card.class, boolean.class });
398
399 Object o = syncClass.getConstructor(String.class).newInstance(input);
400
401 File file = (File) getCache.invoke(o);
402 Card card = new Card(file, Format.VCard21);
403 card.setRemote(true);
404 sync.invoke(o, card, false);
405
406 return card;
407 }
408
409 /**
410 * Open the given path and add all its files if it is a directory or just
411 * this one if not to the returned list.
412 *
413 * @param path
414 * the path to open
415 *
416 * @return the list of opened files
417 */
418 static private List<String> open(String path) {
419 List<String> files = new LinkedList<String>();
420
421 if (path != null && path.startsWith("jvcard://")) {
422 if (path.endsWith("/")) {
423 files.addAll(list(path));
424 } else {
425 files.add(path);
426 }
427 } else {
428 File file = new File(path);
429 if (file.exists()) {
430 if (file.isDirectory()) {
431 for (File subfile : file.listFiles()) {
432 if (!subfile.isDirectory())
433 files.add(subfile.getAbsolutePath());
434 }
435 } else {
436 files.add(file.getAbsolutePath());
437 }
438 } else {
439 System.err.println("File or directory not found: \"" + path
440 + "\"");
441 }
442 }
443
444 return files;
445 }
446
447 /**
448 * List all the available {@link Card}s on the given network location (which
449 * is expected to be a jVCard remote server, obviously).
450 *
451 * @param path
452 * the jVCard remote server path (e.g.:
453 * <tt>jvcard://localhost:4444/</tt>)
454 *
455 * @return the list of {@link Card}s
456 */
457 static private List<String> list(String path) {
458 List<String> files = new LinkedList<String>();
459
460 try {
461 String host = path.split("\\:")[1].substring(2);
462 int port = Integer.parseInt(path.split("\\:")[2].replaceAll("/$",
463 ""));
464 SimpleSocket s = new SimpleSocket(new Socket(host, port),
465 "sync client");
466 s.open(true);
467
468 s.sendCommand(Verb.LIST);
469 for (String p : s.receiveBlock()) {
470 files.add(path
471 + p.substring(StringUtils.fromTime(0).length() + 1));
472 }
473 s.close();
474 } catch (Exception e) {
475 e.printStackTrace();
476 }
477
478 return files;
479 }
480
481 /**
482 * Really, really ask for UTF-8 encoding.
483 */
484 static private void utf8() {
485 try {
486 System.setProperty("file.encoding", "UTF-8");
487 Field charset = Charset.class.getDeclaredField("defaultCharset");
488 charset.setAccessible(true);
489 charset.set(null, null);
490 } catch (SecurityException e) {
491 } catch (NoSuchFieldException e) {
492 } catch (IllegalArgumentException e) {
493 } catch (IllegalAccessException e) {
494 }
495 }
496 }