Remoting support: update:
[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.net.Socket;
7 import java.nio.charset.Charset;
8 import java.util.LinkedList;
9 import java.util.List;
10
11 import be.nikiroo.jvcard.Card;
12 import be.nikiroo.jvcard.parsers.Format;
13 import be.nikiroo.jvcard.remote.Command;
14 import be.nikiroo.jvcard.remote.SimpleSocket;
15 import be.nikiroo.jvcard.resources.Bundles;
16 import be.nikiroo.jvcard.resources.StringUtils;
17 import be.nikiroo.jvcard.resources.Trans;
18 import be.nikiroo.jvcard.resources.Trans.StringId;
19
20 /**
21 * This class contains the runnable Main method. It will parse the user supplied
22 * parameters and take action based upon those. Most of the time, it will start
23 * a MainWindow.
24 *
25 * @author niki
26 *
27 */
28 public class Main {
29 static public final String APPLICATION_TITLE = "jVcard";
30 static public final String APPLICATION_VERSION = "1.0-beta2-dev";
31
32 static private final int ERR_NO_FILE = 1;
33 static private final int ERR_SYNTAX = 2;
34 static private final int ERR_INTERNAL = 3;
35 static private Trans transService;
36
37 /**
38 * Translate the given {@link StringId}.
39 *
40 * @param id
41 * the ID to translate
42 *
43 * @return the translation
44 */
45 static public String trans(StringId id) {
46 return transService.trans(id);
47 }
48
49 /**
50 * Check if unicode characters should be used.
51 *
52 * @return TRUE to allow unicode
53 */
54 static public boolean isUnicode() {
55 return transService.isUnicode();
56 }
57
58 /**
59 * Start the application.
60 *
61 * <p>
62 * The returned exit codes are:
63 * <ul>
64 * <li>1: no files to open</li>
65 * <li>2: invalid syntax</li>
66 * <li>3: internal error</li>
67 * </ul>
68 * </p>
69 *
70 * @param args
71 * the parameters (see <tt>--help</tt> to know which are
72 * supported)
73 */
74 public static void main(String[] args) {
75 Boolean textMode = null;
76 boolean noMoreParams = false;
77 boolean filesTried = false;
78
79 // get the "system default" language to help translate the --help
80 // message if needed
81 String language = null;
82 transService = new Trans(language);
83
84 boolean unicode = transService.isUnicode();
85 String i18nDir = null;
86 List<String> files = new LinkedList<String>();
87 Integer port = null;
88 for (int index = 0; index < args.length; index++) {
89 String arg = args[index];
90 if (!noMoreParams && arg.equals("--")) {
91 noMoreParams = true;
92 } else if (!noMoreParams && arg.equals("--help")) {
93 System.out
94 .println("TODO: implement some help text.\n"
95 + "Usable switches:\n"
96 + "\t--: stop looking for switches\n"
97 + "\t--help: this here thingy\n"
98 + "\t--lang LANGUAGE: choose the language, for instance en_GB\n"
99 + "\t--tui: force pure text mode even if swing treminal is available\n"
100 + "\t--gui: force swing terminal mode\n"
101 + "\t--noutf: force non-utf8 mode if you need it\n"
102 + "\t--config DIRECTORY: force the given directory as a CONFIG_DIR\n"
103 + "\t--server PORT: start a remoting server instead of a client\n"
104 + "\t--i18n DIR: generate the translation file for the given language (can be \"\") to/from the .properties given dir\n"
105 + "everyhing else is either a file to open or a directory to open\n"
106 + "(we will only open 1st level files in given directories)\n"
107 + "('jvcard://hostname:8888/file' links -- or without 'file' -- are also ok)\n");
108 return;
109 } else if (!noMoreParams && arg.equals("--tui")) {
110 textMode = true;
111 } else if (!noMoreParams && arg.equals("--gui")) {
112 textMode = false;
113 } else if (!noMoreParams && arg.equals("--noutf")) {
114 unicode = false;
115 transService.setUnicode(unicode);
116 } else if (!noMoreParams && arg.equals("--lang")) {
117 index++;
118 if (index >= args.length) {
119 System.err.println("Syntax error: no language given");
120 System.exit(ERR_SYNTAX);
121 return;
122 }
123
124 language = args[index];
125 transService = new Trans(language);
126 transService.setUnicode(unicode);
127 } else if (!noMoreParams && arg.equals("--config")) {
128 index++;
129 if (index >= args.length) {
130 System.err
131 .println("Syntax error: no config directory given");
132 System.exit(ERR_SYNTAX);
133 return;
134 }
135
136 Bundles.setDirectory(args[index]);
137 transService = new Trans(language);
138 transService.setUnicode(unicode);
139 } else if (!noMoreParams && arg.equals("--server")) {
140 index++;
141 if (index >= args.length) {
142 System.err.println("Syntax error: no port given");
143 System.exit(ERR_SYNTAX);
144 return;
145 }
146
147 try {
148 port = Integer.parseInt(args[index]);
149 } catch (NumberFormatException e) {
150 System.err.println("Invalid port number: " + args[index]);
151 System.exit(ERR_SYNTAX);
152 return;
153 }
154 } else if (!noMoreParams && arg.equals("--i18n")) {
155 index++;
156 if (index >= args.length) {
157 System.err
158 .println("Syntax error: no .properties directory given");
159 System.exit(ERR_SYNTAX);
160 return;
161 }
162
163 i18nDir = args[index];
164 } else {
165 filesTried = true;
166 files.addAll(open(arg));
167 }
168 }
169
170 if (unicode) {
171 utf8();
172 }
173
174 // Error management:
175 if (port != null && files.size() > 0) {
176 System.err
177 .println("Invalid syntax: you cannot both use --server and provide card files");
178 System.exit(ERR_SYNTAX);
179 } else if (i18nDir != null && files.size() > 0) {
180 System.err
181 .println("Invalid syntax: you cannot both use --i18n and provide card files");
182 System.exit(ERR_SYNTAX);
183 } else if (port != null && i18nDir != null) {
184 System.err
185 .println("Invalid syntax: you cannot both use --server and --i18n");
186 System.exit(ERR_SYNTAX);
187 } else if (i18nDir != null && language == null) {
188 System.err
189 .println("Invalid syntax: you cannot use --i18n without --lang");
190 System.exit(ERR_SYNTAX);
191 } else if (port == null && i18nDir == null && files.size() == 0) {
192 if (files.size() == 0 && !filesTried) {
193 files.addAll(open("."));
194 }
195
196 if (files.size() == 0) {
197 System.err.println("No files to open");
198 System.exit(ERR_NO_FILE);
199 return;
200 }
201 }
202 //
203
204 if (port != null) {
205 try {
206 Optional.runServer(port);
207 } catch (Exception e) {
208 if (e instanceof IOException) {
209 System.err
210 .println("I/O Exception: Cannot start the server");
211 } else {
212 System.err.println("Remoting support not available");
213 System.exit(ERR_INTERNAL);
214 }
215 }
216 } else if (i18nDir != null) {
217 try {
218 Trans.generateTranslationFile(i18nDir, language);
219 } catch (IOException e) {
220 System.err
221 .println("I/O Exception: Cannot create/update a language in directory: "
222 + i18nDir);
223 }
224 } else {
225 try {
226 Optional.startTui(textMode, files);
227 } catch (Exception e) {
228 if (e instanceof IOException) {
229 System.err
230 .println("I/O Exception: Cannot start the program with the given cards");
231 } else {
232 System.err.println("TUI support not available");
233 System.exit(ERR_INTERNAL);
234 }
235 }
236 }
237 }
238
239 /**
240 * Return the {@link Card} corresponding to the given resource name -- a
241 * file or a remote jvcard URL
242 *
243 * @param input
244 * a filename or a remote jvcard url with named resource (e.g.:
245 * <tt>jvcard://localhost:4444/coworkers.vcf</tt>)
246 *
247 * @return the {@link Card}
248 *
249 * @throws IOException
250 * in case of IO error or remoting not available
251 */
252 static public Card getCard(String input) throws IOException {
253 boolean remote = false;
254 Format format = Format.Abook;
255 String ext = input;
256 if (ext.contains(".")) {
257 String tab[] = ext.split("\\.");
258 if (tab.length > 1 && tab[tab.length - 1].equalsIgnoreCase("vcf")) {
259 format = Format.VCard21;
260 }
261 }
262
263 if (input.contains("://")) {
264 format = Format.VCard21;
265 remote = true;
266 }
267
268 Card card = null;
269 try {
270 if (remote) {
271 card = Optional.syncCard(input);
272 } else {
273 card = new Card(new File(input), format);
274 }
275 } catch (IOException ioe) {
276 throw ioe;
277 } catch (Exception e) {
278 throw new IOException("Remoting support not available", e);
279 }
280
281 return card;
282 }
283
284 /**
285 * Open the given path and add all its files if it is a directory or just
286 * this one if not to the returned list.
287 *
288 * @param path
289 * the path to open
290 *
291 * @return the list of opened files
292 */
293 static private List<String> open(String path) {
294 List<String> files = new LinkedList<String>();
295
296 if (path != null && path.startsWith("jvcard://")) {
297 if (path.endsWith("/")) {
298 files.addAll(list(path));
299 } else {
300 files.add(path);
301 }
302 } else {
303 File file = new File(path);
304 if (file.exists()) {
305 if (file.isDirectory()) {
306 for (File subfile : file.listFiles()) {
307 if (!subfile.isDirectory())
308 files.add(subfile.getAbsolutePath());
309 }
310 } else {
311 files.add(file.getAbsolutePath());
312 }
313 } else {
314 System.err.println("File or directory not found: \"" + path
315 + "\"");
316 }
317 }
318
319 return files;
320 }
321
322 /**
323 * List all the available {@link Card}s on the given network location (which
324 * is expected to be a jVCard remote server, obviously).
325 *
326 * @param path
327 * the jVCard remote server path (e.g.:
328 * <tt>jvcard://localhost:4444/</tt>)
329 *
330 * @return the list of {@link Card}s
331 */
332 static private List<String> list(String path) {
333 List<String> files = new LinkedList<String>();
334
335 try {
336 String host = path.split("\\:")[1].substring(2);
337 int port = Integer.parseInt(path.split("\\:")[2].replaceAll("/$",
338 ""));
339 SimpleSocket s = new SimpleSocket(new Socket(host, port),
340 "sync client");
341 s.open(true);
342
343 s.sendCommand(Command.LIST_CARD);
344 for (String p : s.receiveBlock()) {
345 files.add(path
346 + p.substring(StringUtils.fromTime(0).length() + 1));
347 }
348 s.close();
349 } catch (Exception e) {
350 e.printStackTrace();
351 }
352
353 return files;
354 }
355
356 /**
357 * Really, really ask for UTF-8 encoding.
358 */
359 static private void utf8() {
360 try {
361 System.setProperty("file.encoding", "UTF-8");
362 Field charset = Charset.class.getDeclaredField("defaultCharset");
363 charset.setAccessible(true);
364 charset.set(null, null);
365 } catch (SecurityException e) {
366 } catch (NoSuchFieldException e) {
367 } catch (IllegalArgumentException e) {
368 } catch (IllegalAccessException e) {
369 }
370 }
371 }