New option: save/load contacts' photos
authorNiki Roo <niki@nikiroo.be>
Sat, 26 Mar 2016 21:15:09 +0000 (22:15 +0100)
committerNiki Roo <niki@nikiroo.be>
Sat, 26 Mar 2016 21:15:09 +0000 (22:15 +0100)
src/be/nikiroo/jvcard/Contact.java
src/be/nikiroo/jvcard/launcher/Main.java
src/be/nikiroo/jvcard/resources/StringUtils.java
src/be/nikiroo/jvcard/resources/display.properties
src/be/nikiroo/jvcard/tui/panes/ContactDetails.java
src/be/nikiroo/jvcard/tui/panes/ContactList.java

index 1b888961466737bace2f6f647ee5d9d21b2b739b..779b60a3c596ee6ee9bcbc20b09bcf66e3d4edfd 100644 (file)
@@ -113,6 +113,7 @@ public class Contact extends BaseClass<Data> {
         * Return a {@link String} representation of this contact formated
         * accordingly to the given format.
         * 
+        * <p>
         * The format is basically a list of field names separated by a pipe and
         * optionally parametrised. The parameters allows you to:
         * <ul>
@@ -120,31 +121,48 @@ public class Contact extends BaseClass<Data> {
         * <li>@n: limit the size to a fixed value 'n'</li>
         * <li>@+: expand the size of this field as much as possible</li>
         * </ul>
+        * </p>
         * 
-        * Example: "N@10|FN@20|NICK@+|PHOTO@x"
+        * <p>
+        * You can also add a fixed text if it starts with a simple-quote (').
+        * </p>
+        * 
+        * <p>
+        * Example: "'Contact: |N@10|FN@20|NICK@+|PHOTO@x"
+        * </p>
         * 
         * @param format
         *            the format to use
+        * @param separator
+        *            the separator {@link String} to use between fields
         * 
         * @return the {@link String} representation
         */
-       public String toString(String format) {
-               return toString(format, "|", null, -1, true, false);
+       public String toString(String format, String separator) {
+               return toString(format, separator, null, -1, true, false);
        }
 
        /**
         * Return a {@link String} representation of this contact formated
         * accordingly to the given format.
         * 
+        * <p>
         * The format is basically a list of field names separated by a pipe and
         * optionally parametrised. The parameters allows you to:
         * <ul>
-        * <li>@x: (the 'x' is the letter 'x') show only a present/not present info</li>
+        * <li>@x: show only a present/not present info</li>
         * <li>@n: limit the size to a fixed value 'n'</li>
         * <li>@+: expand the size of this field as much as possible</li>
         * </ul>
+        * </p>
+        * 
+        * <p>
+        * You can also add a fixed text if it starts with a simple-quote (').
+        * </p>
         * 
-        * Example: "N@10|FN@20|NICK@+|PHOTO@x"
+        * <p>
+        * Example: "'Contact: |N@10|FN@20|NICK@+|PHOTO@x"
+        * </p>
         * 
         * @param format
         *            the format to use
@@ -176,6 +194,7 @@ public class Contact extends BaseClass<Data> {
         * Return a {@link String} representation of this contact formated
         * accordingly to the given format, part by part.
         * 
+        * <p>
         * The format is basically a list of field names separated by a pipe and
         * optionally parametrised. The parameters allows you to:
         * <ul>
@@ -183,8 +202,15 @@ public class Contact extends BaseClass<Data> {
         * <li>@n: limit the size to a fixed value 'n'</li>
         * <li>@+: expand the size of this field as much as possible</li>
         * </ul>
+        * </p>
         * 
-        * Example: "N@10|FN@20|NICK@+|PHOTO@x"
+        * <p>
+        * You can also add a fixed text if it starts with a simple-quote (').
+        * </p>
+        * 
+        * <p>
+        * Example: "'Contact: |N@10|FN@20|NICK@+|PHOTO@x"
+        * </p>
         * 
         * @param format
         *            the format to use
@@ -236,6 +262,7 @@ public class Contact extends BaseClass<Data> {
         * Return a {@link String} representation of this contact formated
         * accordingly to the given format, part by part.
         * 
+        * <p>
         * The format is basically a list of field names separated by a pipe and
         * optionally parametrised. The parameters allows you to:
         * <ul>
@@ -243,8 +270,15 @@ public class Contact extends BaseClass<Data> {
         * <li>@n: limit the size to a fixed value 'n'</li>
         * <li>@+: expand the size of this field as much as possible</li>
         * </ul>
+        * </p>
+        * 
+        * <p>
+        * You can also add a fixed text if it starts with a simple-quote (').
+        * </p>
         * 
-        * Example: "N@10|FN@20|NICK@+|PHOTO@x"
+        * <p>
+        * Example: "'Contact: |N@10|FN@20|NICK@+|PHOTO@x"
+        * </p>
         * 
         * @param format
         *            the format to use
@@ -280,7 +314,8 @@ public class Contact extends BaseClass<Data> {
                        boolean binary = false;
                        boolean expand = false;
 
-                       if (field.contains("@")) {
+                       if (field.length() > 0 && field.charAt(0) != '\''
+                                       && field.contains("@")) {
                                String[] opts = field.split("@");
                                if (opts.length > 0)
                                        field = opts[0];
@@ -300,7 +335,13 @@ public class Contact extends BaseClass<Data> {
                                }
                        }
 
-                       String value = getPreferredDataValue(field);
+                       String value = null;
+                       if (field.length() > 0 && field.charAt(0) == '\'') {
+                               value = field.substring(1);
+                       } else {
+                               value = getPreferredDataValue(field);
+                       }
+
                        if (value == null) {
                                value = "";
                        } else {
index 1a77291c28903120079f430661ca181f7ac4a6dd..9df98e2e93eb8a0306836a3516f1389d952c21a2 100644 (file)
@@ -8,7 +8,12 @@ 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;
@@ -35,6 +40,10 @@ public class Main {
        static private final int ERR_INTERNAL = 3;
        static private Trans transService;
 
+       enum Mode {
+               CONTACT_MANAGER, I18N, SERVER, LOAD_PHOTO, SAVE_PHOTO, ONLY_PHOTO,
+       }
+
        /**
         * Translate the given {@link StringId} into user text.
         * 
@@ -86,9 +95,11 @@ public class Main {
                transService = new Trans(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("--")) {
@@ -106,6 +117,9 @@ public class Main {
                                                                + "\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"
+                                                               + "\t--save-photo DIR FORMAT: save the contacts' photos to DIR, named after FORMAT\n"
+                                                               + "\t--load-photo DIR FORMAT: load the contacts' photos from DIR, named after FORMAT\n"
+                                                               + "\t--only-photo DIR FORMAT: load the contacts' photos from DIR, named after FORMAT, overwrite all other photos of selected contacts\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");
@@ -141,6 +155,19 @@ public class Main {
                                transService = new Trans(language);
                                transService.setUnicode(unicode);
                        } else if (!noMoreParams && arg.equals("--server")) {
+                               if (mode != Mode.CONTACT_MANAGER) {
+                                       System.err
+                                                       .println("Syntax error: you can only use one of: \n"
+                                                                       + "--server\n"
+                                                                       + "--i18n\n"
+                                                                       + "--load-photo\n"
+                                                                       + "--save-photo\n"
+                                                                       + "--only-photo\n");
+                                       System.exit(ERR_SYNTAX);
+                                       return;
+                               }
+                               mode = Mode.SERVER;
+
                                index++;
                                if (index >= args.length) {
                                        System.err.println("Syntax error: no port given");
@@ -156,6 +183,19 @@ public class Main {
                                        return;
                                }
                        } else if (!noMoreParams && arg.equals("--i18n")) {
+                               if (mode != Mode.CONTACT_MANAGER) {
+                                       System.err
+                                                       .println("Syntax error: you can only use one of: \n"
+                                                                       + "--server\n"
+                                                                       + "--i18n\n"
+                                                                       + "--load-photo\n"
+                                                                       + "--save-photo\n"
+                                                                       + "--only-photo\n");
+                                       System.exit(ERR_SYNTAX);
+                                       return;
+                               }
+                               mode = Mode.I18N;
+
                                index++;
                                if (index >= args.length) {
                                        System.err
@@ -164,7 +204,48 @@ public class Main {
                                        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) {
+                                       System.err
+                                                       .println("Syntax error: you can only use one of: \n"
+                                                                       + "--server\n"
+                                                                       + "--i18n\n"
+                                                                       + "--load-photo\n"
+                                                                       + "--save-photo\n"
+                                                                       + "--only-photo\n");
+                                       System.exit(ERR_SYNTAX);
+                                       return;
+                               }
+
+                               if (arg.equals("--load-photo")) {
+                                       mode = Mode.LOAD_PHOTO;
+                               } else if (arg.equals("--save-photo")) {
+                                       mode = Mode.SAVE_PHOTO;
+                               } else {
+                                       mode = Mode.ONLY_PHOTO;
+                               }
+
+                               index++;
+                               if (index >= args.length) {
+                                       System.err.println("Syntax error: photo directory given");
+                                       System.exit(ERR_SYNTAX);
+                                       return;
+                               }
+
+                               dir = args[index];
+
+                               index++;
+                               if (index >= args.length) {
+                                       System.err.println("Syntax error: photo format given");
+                                       System.exit(ERR_SYNTAX);
+                                       return;
+                               }
+
+                               format = args[index];
                        } else {
                                filesTried = true;
                                files.addAll(open(arg));
@@ -172,7 +253,7 @@ public class Main {
                }
 
                // Force headless mode if we run in forced-text mode
-               if (textMode != null && textMode) {
+               if (mode != Mode.CONTACT_MANAGER || (textMode != null && textMode)) {
                        // same as -Djava.awt.headless=true
                        System.setProperty("java.awt.headless", "true");
                }
@@ -182,23 +263,20 @@ public class Main {
                }
 
                // Error management:
-               if (port != null && files.size() > 0) {
+               if (mode == Mode.SERVER && 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) {
+               } else if (mode == Mode.I18N && 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) {
+               } else if (mode == Mode.I18N && 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) {
+               } 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("."));
                        }
@@ -211,7 +289,8 @@ public class Main {
                }
                //
 
-               if (port != null) {
+               switch (mode) {
+               case SERVER: {
                        try {
                                Optional.runServer(port);
                        } catch (Exception e) {
@@ -223,15 +302,86 @@ public class Main {
                                        System.exit(ERR_INTERNAL);
                                }
                        }
-               } else if (i18nDir != null) {
+                       break;
+               }
+               case I18N: {
                        try {
-                               Trans.generateTranslationFile(i18nDir, language);
+                               Trans.generateTranslationFile(dir, language);
                        } catch (IOException e) {
                                System.err
                                                .println("I/O Exception: Cannot create/update a language in directory: "
-                                                               + i18nDir);
+                                                               + dir);
                        }
-               } else {
+                       break;
+               }
+               case ONLY_PHOTO:
+               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 + ".png");
+
+                                               if (f.exists()) {
+                                                       try {
+                                                               String b64 = StringUtils.fromImage(ImageIO
+                                                                               .read(f));
+
+                                                               if (mode == Mode.ONLY_PHOTO) {
+                                                                       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", "png"));
+                                                               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("Card cannot be opened: " + 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");
+                                                       try {
+                                                               ImageIO.write(
+                                                                               StringUtils.toImage(photo.getValue()),
+                                                                               "png", f);
+                                                       } catch (IOException e) {
+                                                               System.err
+                                                                               .println("Cannot save photo of contact: "
+                                                                                               + contact
+                                                                                                               .getPreferredDataValue("FN"));
+                                                       }
+                                               }
+                                       }
+                               } catch (IOException e) {
+                                       System.err.println("Card cannot be opened: " + file);
+                               }
+                       }
+                       break;
+               }
+               case CONTACT_MANAGER: {
                        try {
                                Optional.startTui(textMode, files);
                        } catch (Exception e) {
@@ -243,6 +393,8 @@ public class Main {
                                        System.exit(ERR_INTERNAL);
                                }
                        }
+                       break;
+               }
                }
        }
 
index 2009dce628ef48864d48cf51c31bab5e6eae9813..cee9fb62715158ec6c2479ea01da4975a84c1159 100644 (file)
@@ -1,5 +1,10 @@
 package be.nikiroo.jvcard.resources;
 
+import java.awt.Image;
+import java.awt.image.BufferedImage;
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
 import java.security.MessageDigest;
 import java.security.NoSuchAlgorithmException;
 import java.text.Normalizer;
@@ -9,6 +14,9 @@ import java.text.SimpleDateFormat;
 import java.util.Date;
 import java.util.regex.Pattern;
 
+import javax.imageio.ImageIO;
+import javax.xml.bind.DatatypeConverter;
+
 import com.googlecode.lanterna.gui2.LinearLayout.Alignment;
 
 public class StringUtils {
@@ -49,7 +57,7 @@ public class StringUtils {
         */
        static public String padString(String text, int width, boolean cut,
                        Alignment align) {
-               
+
                if (width >= 0) {
                        if (text == null)
                                text = "";
@@ -172,6 +180,51 @@ public class StringUtils {
                }
        }
 
+       /**
+        * Convert the given {@link Image} object into a Base64 representation of
+        * the same {@link Image}. object.
+        * 
+        * @param image
+        *            the {@link Image} object to convert
+        * 
+        * @return the Base64 representation
+        */
+       static public String fromImage(BufferedImage image) {
+               String imageString = null;
+               ByteArrayOutputStream out = new ByteArrayOutputStream();
+
+               try {
+                       ImageIO.write(image, "png", out);
+                       byte[] imageBytes = out.toByteArray();
+
+                       imageString = DatatypeConverter.printBase64Binary(imageBytes);
+
+                       out.close();
+               } catch (IOException e) {
+               }
+
+               return imageString;
+       }
+
+       /**
+        * Convert the given Base64 representation of an image into an {@link Image}
+        * object.
+        * 
+        * @param b64data
+        *            the {@link Image} in Base64 format
+        * 
+        * @return the {@link Image} object
+        * 
+        * @throws IOException
+        *             in case of IO error
+        */
+       static public BufferedImage toImage(String b64data) throws IOException {
+               BufferedImage image = ImageIO.read(new ByteArrayInputStream(
+                               DatatypeConverter.parseBase64Binary(b64data)));
+               image.toString();
+               return image;
+       }
+
        /**
         * Return a hash of the given {@link String}.
         * 
index 106bdc32237e953d6d28b02d87cb0689d12f1fb9..bdb370e52b3a0732888b90a2f6d27e8efa3f883b 100644 (file)
@@ -6,6 +6,10 @@
 # - @x: (the 'x' is the letter 'x') show only a present/not present info
 # - @n: limit the size to a fixed value 'n'
 # - @+: expand the size of this field as much as possible
+#
+# You can also add a fixed text if it starts with a simple-quote (').
+#
+# Example: "'Contact: |N@10|FN@20|NICK@+|PHOTO@x"
 # 
 # You can cycle through them if you have more than one
 # (in this case, separate them with a comma (',')
index 1d6beb9998bf01b461fb4590746edf1bb4d13868..5756c7cb1a25cd7aa6d8bde5ba379823f5455a13 100644 (file)
@@ -1,15 +1,11 @@
 package be.nikiroo.jvcard.tui.panes;
 
 import java.awt.Image;
-import java.io.ByteArrayInputStream;
 import java.util.LinkedList;
 import java.util.List;
 import java.util.MissingResourceException;
 import java.util.ResourceBundle;
 
-import javax.imageio.ImageIO;
-import javax.xml.bind.DatatypeConverter;
-
 import be.nikiroo.jvcard.Contact;
 import be.nikiroo.jvcard.Data;
 import be.nikiroo.jvcard.TypeInfo;
@@ -191,8 +187,7 @@ public class ContactDetails extends MainContent {
                        Data photo = contact.getPreferredData("PHOTO");
                        if (photo != null) {
                                TypeInfo encoding = null;
-                               for (int index = 0; index < photo.size(); index++) {
-                                       TypeInfo info = photo.get(index);
+                               for (TypeInfo info : photo) {
                                        if (info.getName() != null) {
                                                if (info.getName().equalsIgnoreCase("ENCODING"))
                                                        encoding = info;
@@ -205,10 +200,7 @@ public class ContactDetails extends MainContent {
                                                && encoding.getValue().equalsIgnoreCase("b")) {
 
                                        try {
-                                               image = ImageIO.read(new ByteArrayInputStream(
-                                                               DatatypeConverter.parseBase64Binary(photo
-                                                                               .getValue())));
-                                               image.toString();
+                                               image = StringUtils.toImage(photo.getValue());
                                        } catch (Exception e) {
                                                System.err.println("Cannot parse image for contact: "
                                                                + contact.getPreferredDataValue("UID"));
index a68efa249c6c502d4f6b071ef21d35b870ee00ff..600f90ca9931bfc8b108b99c00ee88e5cff92023 100644 (file)
@@ -56,7 +56,7 @@ public class ContactList extends MainContentList {
                if (card != null) {
                        for (Contact c : card) {
                                if (filter == null
-                                               || c.toString(format).toLowerCase()
+                                               || c.toString(format, "|").toLowerCase()
                                                                .contains(filter.toLowerCase())) {
                                        addItem("x");
                                        contacts.add(c);
@@ -130,7 +130,8 @@ public class ContactList extends MainContentList {
                                if (contact != null)
                                        contactName = "" + contact.getPreferredDataValue("FN");
 
-                               return Main.trans(Trans.StringId.CONFIRM_USER_DELETE_CONTACT, contactName);
+                               return Main.trans(Trans.StringId.CONFIRM_USER_DELETE_CONTACT,
+                                               contactName);
                        }
 
                        @Override