import java.io.File;
import java.io.IOException;
import java.lang.reflect.Field;
-import java.lang.reflect.InvocationTargetException;
-import java.lang.reflect.Method;
import java.net.Socket;
import java.nio.charset.Charset;
import java.util.LinkedList;
import java.util.List;
+import javax.imageio.ImageIO;
+
import be.nikiroo.jvcard.Card;
+import be.nikiroo.jvcard.Contact;
+import be.nikiroo.jvcard.Data;
+import be.nikiroo.jvcard.TypeInfo;
+import be.nikiroo.jvcard.launcher.CardResult.MergeCallback;
import be.nikiroo.jvcard.parsers.Format;
-import be.nikiroo.jvcard.remote.Command.Verb;
+import be.nikiroo.jvcard.remote.Command;
import be.nikiroo.jvcard.remote.SimpleSocket;
import be.nikiroo.jvcard.resources.Bundles;
import be.nikiroo.jvcard.resources.StringUtils;
-import be.nikiroo.jvcard.resources.Trans;
-import be.nikiroo.jvcard.resources.Trans.StringId;
+import be.nikiroo.jvcard.resources.bundles.ColorBundle;
+import be.nikiroo.jvcard.resources.bundles.DisplayBundle;
+import be.nikiroo.jvcard.resources.bundles.RemoteBundle;
+import be.nikiroo.jvcard.resources.bundles.TransBundle;
+import be.nikiroo.jvcard.resources.enums.DisplayOption;
+import be.nikiroo.jvcard.resources.enums.StringId;
/**
* This class contains the runnable Main method. It will parse the user supplied
*/
public class Main {
static public final String APPLICATION_TITLE = "jVcard";
- static public final String APPLICATION_VERSION = "1.0-beta2-dev";
+ static public final String APPLICATION_VERSION = "1.0-dev";
static private final int ERR_NO_FILE = 1;
static private final int ERR_SYNTAX = 2;
static private final int ERR_INTERNAL = 3;
- static private Trans transService;
+ static private TransBundle transService;
+
+ static private String defaultFn;
+ static private boolean forceComputedFn;
+
+ enum Mode {
+ CONTACT_MANAGER, I18N, SERVER, LOAD_PHOTO, SAVE_PHOTO, SAVE_CONFIG, HELP
+ }
/**
- * Translate the given {@link StringId}.
+ * Translate the given {@link StringId} into user text.
*
- * @param id
+ * @param stringId
* the ID to translate
+ * @param values
+ * the values to insert instead of the place holders in the
+ * translation
*
- * @return the translation
+ * @return the translated text with the given value where required
*/
- static public String trans(StringId id) {
- return transService.trans(id);
+ static public String trans(StringId id, Object... values) {
+ return transService.getString(id, values);
}
/**
// get the "system default" language to help translate the --help
// message if needed
String language = null;
- transService = new Trans(language);
+ transService = new TransBundle(language);
boolean unicode = transService.isUnicode();
- String i18nDir = null;
+ String dir = null;
List<String> files = new LinkedList<String>();
- Integer port = null;
+ int port = -1;
+ Mode mode = Mode.CONTACT_MANAGER;
+ String format = null;
for (int index = 0; index < args.length; index++) {
String arg = args[index];
if (!noMoreParams && arg.equals("--")) {
noMoreParams = true;
} else if (!noMoreParams && arg.equals("--help")) {
- System.out
- .println("TODO: implement some help text.\n"
- + "Usable switches:\n"
- + "\t--: stop looking for switches\n"
- + "\t--help: this here thingy\n"
- + "\t--lang LANGUAGE: choose the language, for instance en_GB\n"
- + "\t--tui: force pure text mode even if swing treminal is available\n"
- + "\t--gui: force swing terminal mode\n"
- + "\t--noutf: force non-utf8 mode if you need it\n"
- + "\t--config DIRECTORY: force the given directory as a CONFIG_DIR\n"
- + "\t--server PORT: start a remoting server instead of a client\n"
- + "\t--i18n DIR: generate the translation file for the given language (can be \"\") to/from the .properties given dir\n"
- + "everyhing else is either a file to open or a directory to open\n"
- + "(we will only open 1st level files in given directories)\n"
- + "('jvcard://hostname:8888/file' links -- or without 'file' -- are also ok)\n");
- return;
+ if (mode != Mode.CONTACT_MANAGER) {
+ SERR(StringId.CLI_SERR_MODES);
+ return;
+ }
+ mode = Mode.HELP;
} else if (!noMoreParams && arg.equals("--tui")) {
textMode = true;
} else if (!noMoreParams && arg.equals("--gui")) {
} else if (!noMoreParams && arg.equals("--lang")) {
index++;
if (index >= args.length) {
- System.err.println("Syntax error: no language given");
- System.exit(ERR_SYNTAX);
+ SERR(StringId.CLI_SERR_NOLANG);
return;
}
language = args[index];
- transService = new Trans(language);
+ transService = new TransBundle(language);
transService.setUnicode(unicode);
} else if (!noMoreParams && arg.equals("--config")) {
index++;
if (index >= args.length) {
- System.err
- .println("Syntax error: no config directory given");
- System.exit(ERR_SYNTAX);
+ SERR(StringId.CLI_SERR_NODIR);
return;
}
Bundles.setDirectory(args[index]);
- transService = new Trans(language);
+ transService = new TransBundle(language);
transService.setUnicode(unicode);
+ } else if (!noMoreParams && arg.equals("--save-config")) {
+ index++;
+ if (index >= args.length) {
+ SERR(StringId.CLI_SERR_NODIR);
+ return;
+ }
+ dir = args[index];
+
+ if (mode != Mode.CONTACT_MANAGER) {
+ SERR(StringId.CLI_SERR_MODES);
+ return;
+ }
+ mode = Mode.SAVE_CONFIG;
} else if (!noMoreParams && arg.equals("--server")) {
+ if (mode != Mode.CONTACT_MANAGER) {
+ SERR(StringId.CLI_SERR_MODES);
+ return;
+ }
+ mode = Mode.SERVER;
+
index++;
if (index >= args.length) {
- System.err.println("Syntax error: no port given");
- System.exit(ERR_SYNTAX);
+ SERR(StringId.CLI_SERR_NOPORT);
return;
}
try {
port = Integer.parseInt(args[index]);
} catch (NumberFormatException e) {
- System.err.println("Invalid port number: " + args[index]);
- System.exit(ERR_SYNTAX);
+ SERR(StringId.CLI_SERR_BADPORT, "" + args[index]);
return;
}
} else if (!noMoreParams && arg.equals("--i18n")) {
+ if (mode != Mode.CONTACT_MANAGER) {
+ SERR(StringId.CLI_SERR_MODES);
+ return;
+ }
+ mode = Mode.I18N;
+
index++;
if (index >= args.length) {
- System.err
- .println("Syntax error: no .properties directory given");
- System.exit(ERR_SYNTAX);
+ SERR(StringId.CLI_SERR_NODIR);
return;
}
- i18nDir = args[index];
+ dir = args[index];
+ } else if (!noMoreParams
+ && (arg.equals("--load-photo")
+ || arg.equals("--save-photo") || arg
+ .equals("--only-photo"))) {
+ if (mode != Mode.CONTACT_MANAGER) {
+ SERR(StringId.CLI_SERR_MODES);
+ return;
+ }
+
+ if (arg.equals("--load-photo")) {
+ mode = Mode.LOAD_PHOTO;
+ } else if (arg.equals("--save-photo")) {
+ mode = Mode.SAVE_PHOTO;
+ }
+
+ index++;
+ if (index >= args.length) {
+ SERR(StringId.CLI_SERR_NODIR);
+ return;
+ }
+
+ dir = args[index];
+
+ index++;
+ if (index >= args.length) {
+ SERR(StringId.CLI_SERR_NOFORMAT);
+ return;
+ }
+
+ format = args[index];
} else {
filesTried = true;
files.addAll(open(arg));
}
}
+ // Force headless mode if we run in forced-text mode
+ if (mode != Mode.CONTACT_MANAGER || (textMode != null && textMode)) {
+ // same as -Djava.awt.headless=true
+ System.setProperty("java.awt.headless", "true");
+ }
+
if (unicode) {
utf8();
}
+ // N/FN fix information:
+ readNFN();
+
// Error management:
- if (port != null && files.size() > 0) {
- System.err
- .println("Invalid syntax: you cannot both use --server and provide card files");
- System.exit(ERR_SYNTAX);
- } else if (i18nDir != null && files.size() > 0) {
- System.err
- .println("Invalid syntax: you cannot both use --i18n and provide card files");
- System.exit(ERR_SYNTAX);
- } else if (port != null && i18nDir != null) {
- System.err
- .println("Invalid syntax: you cannot both use --server and --i18n");
- System.exit(ERR_SYNTAX);
- } else if (i18nDir != null && language == null) {
- System.err
- .println("Invalid syntax: you cannot use --i18n without --lang");
- System.exit(ERR_SYNTAX);
- } else if (port == null && i18nDir == null && files.size() == 0) {
+ if (mode == Mode.SERVER && files.size() > 0) {
+ SERR(StringId.CLI_SERR_NOLANG, "--server");
+ return;
+ } else if (mode == Mode.I18N && files.size() > 0) {
+ SERR(StringId.CLI_SERR_NOLANG, "--i18n");
+ return;
+ } else if (mode == Mode.I18N && language == null) {
+ SERR(StringId.CLI_SERR_NOLANG);
+ } else if ((mode == Mode.CONTACT_MANAGER || mode == Mode.SAVE_PHOTO || mode == Mode.LOAD_PHOTO)
+ && files.size() == 0) {
if (files.size() == 0 && !filesTried) {
files.addAll(open("."));
}
if (files.size() == 0) {
- System.err.println("No files to open");
- System.exit(ERR_NO_FILE);
+ ERR(StringId.CLI_ERR, StringId.CLI_ERR_NOFILES, ERR_NO_FILE);
return;
}
}
//
- if (port != null) {
+ switch (mode) {
+ case SAVE_CONFIG: {
try {
- runServer(port);
+ if (!new File(dir).isDirectory()) {
+ if (!new File(dir).mkdir()) {
+ System.err.println(trans(
+ StringId.CLI_ERR_CANNOT_CREATE_CONFDIR, dir));
+ }
+ }
+
+ transService.updateFile(dir); // current lang TransBundle
+ new TransBundle().updateFile(dir);
+ new ColorBundle().updateFile(dir);
+ new DisplayBundle().updateFile(dir);
+ new RemoteBundle().updateFile(dir);
+ } catch (IOException e) {
+ e.printStackTrace();
+ System.err.flush();
+ System.exit(ERR_INTERNAL);
+ }
+ break;
+ }
+ case SERVER: {
+ try {
+ Optional.runServer(port);
} catch (Exception e) {
if (e instanceof IOException) {
System.err
.println("I/O Exception: Cannot start the server");
} else {
- System.err.println("Remoting support not available");
- System.exit(ERR_INTERNAL);
+ ERR(StringId.CLI_ERR, StringId.CLI_ERR_NO_REMOTING,
+ ERR_INTERNAL);
+ return;
}
}
- } else if (i18nDir != null) {
+ break;
+ }
+ case I18N: {
try {
- Trans.generateTranslationFile(i18nDir, language);
+ transService.updateFile(dir);
} catch (IOException e) {
- System.err
- .println("I/O Exception: Cannot create/update a language in directory: "
- + i18nDir);
+ System.err.println(trans(StringId.CLI_ERR_CANNOT_CREATE_LANG,
+ dir));
+ e.printStackTrace();
}
- } else {
+ break;
+ }
+ case LOAD_PHOTO: {
+ for (String file : files) {
+ try {
+ Card card = getCard(file, null).getCard();
+ for (Contact contact : card) {
+ String filename = contact.toString(format, "");
+ File f = new File(dir, filename);
+
+ if (f.exists()) {
+ System.out.println("Loading " + f);
+ try {
+ String type = "jpeg";
+ int dotIndex = filename.indexOf('.');
+ if (dotIndex >= 0
+ && (dotIndex + 1) < filename.length()) {
+ type = filename.substring(dotIndex + 1)
+ .toLowerCase();
+ }
+
+ String b64 = StringUtils.fromImage(f);
+
+ // remove previous photos:
+ for (Data photo = contact
+ .getPreferredData("PHOTO"); photo != null; photo = contact
+ .getPreferredData("PHOTO")) {
+ photo.delete();
+ }
+ //
+
+ List<TypeInfo> types = new LinkedList<TypeInfo>();
+ types.add(new TypeInfo("ENCODING", "b"));
+ types.add(new TypeInfo("TYPE", type));
+ Data photo = new Data(types, "PHOTO", b64, null);
+ contact.add(photo);
+ } catch (IOException e) {
+ System.err.println("Cannot read photo: "
+ + filename);
+ }
+ }
+ }
+ card.save();
+ } catch (IOException e) {
+ System.err
+ .println(trans(StringId.CLI_ERR_CANNOT_OPEN, file));
+ }
+ }
+ break;
+ }
+ case SAVE_PHOTO: {
+ for (String file : files) {
+ try {
+ Card card = getCard(file, null).getCard();
+ for (Contact contact : card) {
+ Data photo = contact.getPreferredData("PHOTO");
+ if (photo != null) {
+ String filename = contact.toString(format, "");
+ File f = new File(dir, filename + ".png");
+ System.out.println("Saving " + f);
+ try {
+ ImageIO.write(
+ StringUtils.toImage(photo.getValue()),
+ "png", f);
+ } catch (IOException e) {
+ System.err.println(trans(
+ StringId.CLI_ERR_CANNOT_SAVE_PHOTO,
+ contact.getPreferredDataValue("FN")));
+ }
+ }
+ }
+ } catch (IOException e) {
+ System.err
+ .println(trans(StringId.CLI_ERR_CANNOT_OPEN, file));
+ }
+ }
+ break;
+ }
+ case CONTACT_MANAGER: {
try {
- startTui(textMode, files);
+ Optional.startTui(textMode, files);
} catch (Exception e) {
if (e instanceof IOException) {
- System.err
- .println("I/O Exception: Cannot start the program with the given cards");
+ ERR(StringId.CLI_ERR, StringId.CLI_ERR_CANNOT_START,
+ ERR_NO_FILE);
+ return;
} else {
- System.err.println("TUI support not available");
- System.exit(ERR_INTERNAL);
+ ERR(StringId.CLI_ERR, StringId.CLI_ERR_NO_TUI, ERR_INTERNAL);
+ return;
}
}
+ break;
+ }
+ case HELP: {
+ System.out.println(APPLICATION_TITLE + " " + APPLICATION_VERSION);
+ System.out.println();
+
+ System.out.println(trans(StringId.CLI_HELP));
+ System.out.println();
+
+ System.out.println(trans(StringId.CLI_HELP_MODES));
+ System.out.println("\t--help : "
+ + trans(StringId.CLI_HELP_MODE_HELP));
+ System.out.println("\t(--tui|--gui) (--noutf) ... : "
+ + trans(StringId.CLI_HELP_MODE_CONTACT_MANAGER));
+ System.out.println("\t--server PORT ... : "
+ + trans(StringId.CLI_HELP_MODE_SERVER));
+ System.out.println("\t--save-config DIR : "
+ + trans(StringId.CLI_HELP_MODE_SAVE_CONFIG));
+ System.out.println("\t--i18n DIR ---lang LANG : "
+ + trans(StringId.CLI_HELP_MODE_I18N));
+ System.out.println("\t--load-photo DIR FORMAT ... : "
+ + trans(StringId.CLI_HELP_MODE_LOAD_PHOTO));
+ System.out.println("\t--save-photo DIR FORMAT ... : "
+ + trans(StringId.CLI_HELP_MODE_SAVE_PHOTO));
+ System.out.println();
+
+ System.out.println(trans(StringId.CLI_HELP_OPTIONS));
+ System.out.println("\t-- : " + trans(StringId.CLI_HELP_DD));
+ System.out.println("\t--lang LANG : "
+ + trans(StringId.CLI_HELP_LANG));
+ System.out.println("\t--tui : " + trans(StringId.CLI_HELP_TUI));
+ System.out.println("\t--gui : " + trans(StringId.CLI_HELP_GUI));
+ System.out.println("\t--noutf : "
+ + trans(StringId.CLI_HELP_NOUTF_OPTION));
+ System.out.println("\t--config : "
+ + trans(StringId.CLI_HELP_CONFIG));
+ System.out.println();
+
+ System.out.println(trans(StringId.CLI_HELP_FOOTER));
+ System.out.println();
+
+ }
}
}
/**
* Return the {@link Card} corresponding to the given resource name -- a
- * file or a remote jvcard URL
+ * file or a remote jvcard URL.
+ *
+ * <p>
+ * Will also fix the FN if required (see display.properties).
+ * </p>
*
* @param input
* a filename or a remote jvcard url with named resource (e.g.:
* <tt>jvcard://localhost:4444/coworkers.vcf</tt>)
+ * @param callback
+ * the {@link MergeCallback} to call in case of conflict, or NULL
+ * to disallow conflict management (the {@link Card} will not be
+ * allowed to synchronise in case of conflicts)
*
* @return the {@link Card}
*
* @throws IOException
* in case of IO error or remoting not available
*/
- static public Card getCard(String input) throws IOException {
+ static public CardResult getCard(String input, MergeCallback callback)
+ throws IOException {
boolean remote = false;
Format format = Format.Abook;
String ext = input;
remote = true;
}
- Card card = null;
+ CardResult card = null;
try {
if (remote) {
- card = syncCard(input);
+ card = Optional.syncCard(input, callback);
} else {
- card = new Card(new File(input), format);
+ card = new CardResult(new Card(new File(input), format), false,
+ false, false);
}
} catch (IOException ioe) {
throw ioe;
throw new IOException("Remoting support not available", e);
}
- return card;
- }
-
- /**
- * Create a new jVCard server on the given port, then run it.
- *
- * @param port
- * the port to run on
- *
- * @throws SecurityException
- * in case of internal error
- * @throws NoSuchMethodException
- * in case of internal error
- * @throws ClassNotFoundException
- * in case of internal error
- * @throws IllegalAccessException
- * in case of internal error
- * @throws InstantiationException
- * in case of internal error
- * @throws InvocationTargetException
- * in case of internal error
- * @throws IllegalArgumentException
- * in case of internal error
- * @throws IOException
- * in case of IO error
- */
- @SuppressWarnings("unchecked")
- static private void runServer(int port) throws NoSuchMethodException,
- SecurityException, ClassNotFoundException, InstantiationException,
- IllegalAccessException, IllegalArgumentException,
- InvocationTargetException {
- @SuppressWarnings("rawtypes")
- Class serverClass = Class.forName("be.nikiroo.jvcard.remote.Server");
- Method run = serverClass.getDeclaredMethod("run", new Class[] {});
- run.invoke(serverClass.getConstructor(int.class).newInstance(port));
- }
-
- /**
- * Start the TUI program.
- *
- * @param textMode
- * TRUE to force text mode, FALSE to force the Swing terminal
- * emulator, null to automatically determine the best choice
- * @param files
- * the files to show at startup
- *
- * @throws SecurityException
- * in case of internal error
- * @throws NoSuchMethodException
- * in case of internal error
- * @throws ClassNotFoundException
- * in case of internal error
- * @throws IllegalAccessException
- * in case of internal error
- * @throws InstantiationException
- * in case of internal error
- * @throws InvocationTargetException
- * in case of internal error
- * @throws IllegalArgumentException
- * in case of internal error
- * @throws IOException
- * in case of IO error
- */
- @SuppressWarnings("unchecked")
- static private void startTui(Boolean textMode, List<String> files)
- throws NoSuchMethodException, SecurityException,
- ClassNotFoundException, InstantiationException,
- IllegalAccessException, IllegalArgumentException,
- InvocationTargetException {
- @SuppressWarnings("rawtypes")
- Class launcherClass = Class
- .forName("be.nikiroo.jvcard.tui.TuiLauncher");
- Method start = launcherClass.getDeclaredMethod("start", new Class[] {
- Boolean.class, List.class });
- start.invoke(launcherClass.newInstance(), textMode, files);
- }
-
- /**
- * Return the {@link Card} corresponding to the given URL, synchronised if
- * necessary.
- *
- * @param input
- * the jvcard:// with resource name URL (e.g.:
- * <tt>jvcard://localhost:4444/coworkers</tt>)
- *
- * @throws SecurityException
- * in case of internal error
- * @throws NoSuchMethodException
- * in case of internal error
- * @throws ClassNotFoundException
- * in case of internal error
- * @throws IllegalAccessException
- * in case of internal error
- * @throws InstantiationException
- * in case of internal error
- * @throws InvocationTargetException
- * in case of internal error
- * @throws IllegalArgumentException
- * in case of internal error
- * @throws IOException
- * in case of IO error
- */
- @SuppressWarnings("unchecked")
- static private Card syncCard(String input) throws ClassNotFoundException,
- NoSuchMethodException, SecurityException, InstantiationException,
- IllegalAccessException, IllegalArgumentException,
- InvocationTargetException, IOException {
- @SuppressWarnings("rawtypes")
- Class syncClass = Class.forName("be.nikiroo.jvcard.remote.Sync");
- Method getCache = syncClass.getDeclaredMethod("getCache",
- new Class[] {});
- Method sync = syncClass.getDeclaredMethod("sync", new Class[] {
- Card.class, boolean.class });
-
- Object o = syncClass.getConstructor(String.class).newInstance(input);
-
- File file = (File) getCache.invoke(o);
- Card card = new Card(file, Format.VCard21);
- card.setRemote(true);
- sync.invoke(o, card, false);
+ // Fix the FN value
+ if (defaultFn != null) {
+ try {
+ for (Contact contact : card.getCard()) {
+ Data name = contact.getPreferredData("FN");
+ if (name == null || name.getValue().length() == 0
+ || forceComputedFn) {
+ name.setValue(contact.toString(defaultFn, "").trim());
+ }
+ }
+ } catch (Exception e) {
+ // sync failed -> getCard() throws.
+ // do not update.
+ }
+ }
return card;
}
"sync client");
s.open(true);
- s.sendCommand(Verb.LIST);
+ s.sendCommand(Command.LIST_CARD);
for (String p : s.receiveBlock()) {
files.add(path
+ p.substring(StringUtils.fromTime(0).length() + 1));
} catch (IllegalAccessException e) {
}
}
+
+ /**
+ * Read display.properties to know if we should fix the FN field when empty,
+ * or always, or never.
+ */
+ static private void readNFN() {
+ DisplayBundle map = new DisplayBundle();
+
+ defaultFn = map.getString(DisplayOption.CONTACT_DETAILS_DEFAULT_FN);
+
+ forceComputedFn = map.getBoolean(
+ DisplayOption.CONTACT_DETAILS_SHOW_COMPUTED_FN, false);
+ }
+
+ /**
+ * Syntax error detected, closing the application with an error message.
+ *
+ * @param err
+ * the syntax error case
+ */
+ static private void SERR(StringId err, Object... values) {
+ ERR(StringId.CLI_SERR, err, ERR_SYNTAX, values);
+ }
+
+ /**
+ * Error detected, closing the application with an error message.
+ *
+ * @param err
+ * the error case
+ * @param suberr
+ * the suberror or NULL if none
+ * @param CODE
+ * the error code as declared above
+ */
+ static private void ERR(StringId err, StringId suberr, int CODE,
+ Object... subvalues) {
+ if (suberr == null)
+ System.err.println(trans(err));
+ else
+ System.err.println(trans(err, trans(suberr, subvalues)));
+
+ System.err.flush();
+ System.exit(CODE);
+ }
}