Fix some bugs in remote/sync (still not complete)
[jvcard.git] / src / be / nikiroo / jvcard / launcher / Main.java
CommitLineData
7da41ecd
NR
1package be.nikiroo.jvcard.launcher;
2
3import java.io.File;
4import java.io.IOException;
5import java.lang.reflect.Field;
6import java.lang.reflect.InvocationTargetException;
7import java.lang.reflect.Method;
8import java.net.Socket;
9import java.nio.charset.Charset;
10import java.util.LinkedList;
11import java.util.List;
12
13import be.nikiroo.jvcard.Card;
14import be.nikiroo.jvcard.parsers.Format;
15import be.nikiroo.jvcard.remote.Command.Verb;
16import be.nikiroo.jvcard.remote.SimpleSocket;
17import be.nikiroo.jvcard.resources.Bundles;
18import be.nikiroo.jvcard.resources.StringUtils;
19import be.nikiroo.jvcard.resources.Trans;
20import 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 */
30public 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 {
4298276a 214 System.err.println("Remoting support not available");
7da41ecd
NR
215 System.exit(ERR_INTERNAL);
216 }
217 }
218 } else if (i18nDir != null) {
219 try {
220 Trans.generateTranslationFile(i18nDir, language);
221 } catch (IOException e) {
222 System.err
223 .println("I/O Exception: Cannot create/update a language in directory: "
224 + i18nDir);
225 }
226 } else {
227 try {
228 startTui(textMode, files);
229 } catch (Exception e) {
230 if (e instanceof IOException) {
231 System.err
232 .println("I/O Exception: Cannot start the program with the given cards");
233 } else {
4298276a 234 System.err.println("TUI support not available");
7da41ecd
NR
235 System.exit(ERR_INTERNAL);
236 }
237 }
238 }
239 }
240
241 /**
242 * Return the {@link Card} corresponding to the given resource name -- a
243 * file or a remote jvcard URL
244 *
245 * @param input
246 * a filename or a remote jvcard url with named resource (e.g.:
247 * <tt>jvcard://localhost:4444/coworkers.vcf</tt>)
248 *
249 * @return the {@link Card}
250 *
251 * @throws IOException
252 * in case of IO error or remoting not available
253 */
254 static public Card getCard(String input) throws IOException {
255 boolean remote = false;
256 Format format = Format.Abook;
257 String ext = input;
258 if (ext.contains(".")) {
259 String tab[] = ext.split("\\.");
260 if (tab.length > 1 && tab[tab.length - 1].equalsIgnoreCase("vcf")) {
261 format = Format.VCard21;
262 }
263 }
264
265 if (input.contains("://")) {
266 format = Format.VCard21;
267 remote = true;
268 }
269
270 Card card = null;
271 try {
272 if (remote) {
273 card = syncCard(input);
274 } else {
275 card = new Card(new File(input), format);
276 }
277 } catch (IOException ioe) {
278 throw ioe;
279 } catch (Exception e) {
4298276a 280 throw new IOException("Remoting support not available", e);
7da41ecd
NR
281 }
282
283 return card;
284 }
285
286 /**
287 * Create a new jVCard server on the given port, then run it.
288 *
289 * @param port
290 * the port to run on
291 *
292 * @throws SecurityException
293 * in case of internal error
294 * @throws NoSuchMethodException
295 * in case of internal error
296 * @throws ClassNotFoundException
297 * in case of internal error
298 * @throws IllegalAccessException
299 * in case of internal error
300 * @throws InstantiationException
301 * in case of internal error
302 * @throws InvocationTargetException
303 * in case of internal error
304 * @throws IllegalArgumentException
305 * in case of internal error
306 * @throws IOException
307 * in case of IO error
308 */
309 @SuppressWarnings("unchecked")
310 static private void runServer(int port) throws NoSuchMethodException,
311 SecurityException, ClassNotFoundException, InstantiationException,
312 IllegalAccessException, IllegalArgumentException,
313 InvocationTargetException {
314 @SuppressWarnings("rawtypes")
315 Class serverClass = Class.forName("be.nikiroo.jvcard.remote.Server");
316 Method run = serverClass.getDeclaredMethod("run", new Class[] {});
317 run.invoke(serverClass.getConstructor(int.class).newInstance(port));
318 }
319
320 /**
321 * Start the TUI program.
322 *
323 * @param textMode
324 * TRUE to force text mode, FALSE to force the Swing terminal
325 * emulator, null to automatically determine the best choice
326 * @param files
327 * the files to show at startup
328 *
329 * @throws SecurityException
330 * in case of internal error
331 * @throws NoSuchMethodException
332 * in case of internal error
333 * @throws ClassNotFoundException
334 * in case of internal error
335 * @throws IllegalAccessException
336 * in case of internal error
337 * @throws InstantiationException
338 * in case of internal error
339 * @throws InvocationTargetException
340 * in case of internal error
341 * @throws IllegalArgumentException
342 * in case of internal error
343 * @throws IOException
344 * in case of IO error
345 */
346 @SuppressWarnings("unchecked")
347 static private void startTui(Boolean textMode, List<String> files)
348 throws NoSuchMethodException, SecurityException,
349 ClassNotFoundException, InstantiationException,
350 IllegalAccessException, IllegalArgumentException,
351 InvocationTargetException {
352 @SuppressWarnings("rawtypes")
353 Class launcherClass = Class
354 .forName("be.nikiroo.jvcard.tui.TuiLauncher");
355 Method start = launcherClass.getDeclaredMethod("start", new Class[] {
356 Boolean.class, List.class });
357 start.invoke(launcherClass.newInstance(), textMode, files);
358 }
359
360 /**
361 * Return the {@link Card} corresponding to the given URL, synchronised if
362 * necessary.
363 *
364 * @param input
365 * the jvcard:// with resource name URL (e.g.:
366 * <tt>jvcard://localhost:4444/coworkers</tt>)
367 *
368 * @throws SecurityException
369 * in case of internal error
370 * @throws NoSuchMethodException
371 * in case of internal error
372 * @throws ClassNotFoundException
373 * in case of internal error
374 * @throws IllegalAccessException
375 * in case of internal error
376 * @throws InstantiationException
377 * in case of internal error
378 * @throws InvocationTargetException
379 * in case of internal error
380 * @throws IllegalArgumentException
381 * in case of internal error
382 * @throws IOException
383 * in case of IO error
384 */
385 @SuppressWarnings("unchecked")
386 static private Card syncCard(String input) throws ClassNotFoundException,
387 NoSuchMethodException, SecurityException, InstantiationException,
388 IllegalAccessException, IllegalArgumentException,
389 InvocationTargetException, IOException {
390 @SuppressWarnings("rawtypes")
391 Class syncClass = Class.forName("be.nikiroo.jvcard.remote.Sync");
392 Method getCache = syncClass.getDeclaredMethod("getCache",
393 new Class[] {});
394 Method sync = syncClass.getDeclaredMethod("sync", new Class[] {
395 Card.class, boolean.class });
396
397 Object o = syncClass.getConstructor(String.class).newInstance(input);
398
399 File file = (File) getCache.invoke(o);
400 Card card = new Card(file, Format.VCard21);
401 card.setRemote(true);
402 sync.invoke(o, card, false);
403
404 return card;
405 }
406
407 /**
408 * Open the given path and add all its files if it is a directory or just
409 * this one if not to the returned list.
410 *
411 * @param path
412 * the path to open
413 *
414 * @return the list of opened files
415 */
416 static private List<String> open(String path) {
417 List<String> files = new LinkedList<String>();
418
419 if (path != null && path.startsWith("jvcard://")) {
420 if (path.endsWith("/")) {
421 files.addAll(list(path));
422 } else {
423 files.add(path);
424 }
425 } else {
426 File file = new File(path);
427 if (file.exists()) {
428 if (file.isDirectory()) {
429 for (File subfile : file.listFiles()) {
430 if (!subfile.isDirectory())
431 files.add(subfile.getAbsolutePath());
432 }
433 } else {
434 files.add(file.getAbsolutePath());
435 }
436 } else {
437 System.err.println("File or directory not found: \"" + path
438 + "\"");
439 }
440 }
441
442 return files;
443 }
444
445 /**
446 * List all the available {@link Card}s on the given network location (which
447 * is expected to be a jVCard remote server, obviously).
448 *
449 * @param path
450 * the jVCard remote server path (e.g.:
451 * <tt>jvcard://localhost:4444/</tt>)
452 *
453 * @return the list of {@link Card}s
454 */
455 static private List<String> list(String path) {
456 List<String> files = new LinkedList<String>();
457
458 try {
459 String host = path.split("\\:")[1].substring(2);
460 int port = Integer.parseInt(path.split("\\:")[2].replaceAll("/$",
461 ""));
462 SimpleSocket s = new SimpleSocket(new Socket(host, port),
463 "sync client");
464 s.open(true);
465
466 s.sendCommand(Verb.LIST);
467 for (String p : s.receiveBlock()) {
468 files.add(path
469 + p.substring(StringUtils.fromTime(0).length() + 1));
470 }
471 s.close();
472 } catch (Exception e) {
473 e.printStackTrace();
474 }
475
476 return files;
477 }
478
479 /**
480 * Really, really ask for UTF-8 encoding.
481 */
482 static private void utf8() {
483 try {
484 System.setProperty("file.encoding", "UTF-8");
485 Field charset = Charset.class.getDeclaredField("defaultCharset");
486 charset.setAccessible(true);
487 charset.set(null, null);
488 } catch (SecurityException e) {
489 } catch (NoSuchFieldException e) {
490 } catch (IllegalArgumentException e) {
491 } catch (IllegalAccessException e) {
492 }
493 }
494}