/** * $RCSfile: ,v $ * $Revision: $ * $Date: $ * * Copyright (C) 2004-2011 Jive Software. All rights reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.jivesoftware.sparkimpl.plugin.emoticons; import org.dom4j.Document; import org.dom4j.DocumentException; import org.dom4j.Element; import org.dom4j.Node; import org.dom4j.io.SAXReader; import org.jivesoftware.Spark; import org.jivesoftware.spark.util.URLFileSystem; import org.jivesoftware.spark.util.log.Log; import org.jivesoftware.sparkimpl.settings.local.LocalPreferences; import org.jivesoftware.sparkimpl.settings.local.SettingsManager; import org.xml.sax.SAXException; import javax.swing.ImageIcon; import java.io.File; import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; import java.net.MalformedURLException; import java.net.URL; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.Enumeration; import java.util.HashMap; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.jar.JarEntry; import java.util.jar.JarFile; import java.util.zip.ZipFile; /** * Responsible for the handling of all Emoticon packs. Using the * EmoticonManager, you can specify any defined Emoticon Pack, retrieve any * emoticon based on its text equivalant, and retrieve its associated image url. * * @author Derek DeMoro */ public class EmoticonManager { private static EmoticonManager singleton; private static final Object LOCK = new Object(); private Map<String, Collection<Emoticon>> emoticonMap = new HashMap<String, Collection<Emoticon>>(); private Map<String, ImageIcon> imageMap = new HashMap<String, ImageIcon>(); /** * The root emoticon directory. */ public static File EMOTICON_DIRECTORY; /** * Returns the singleton instance of <CODE>EmoticonManager</CODE>, creating * it if necessary. * <p/> * * @return the singleton instance of <Code>EmoticonManager</CODE> */ public static EmoticonManager getInstance() { // Synchronize on LOCK to ensure that we don't end up creating // two singletons. synchronized (LOCK) { if (null == singleton) { EmoticonManager controller = new EmoticonManager(); singleton = controller; return controller; } } return singleton; } /** * Initialize the EmoticonManager */ private EmoticonManager() { EMOTICON_DIRECTORY = new File(Spark.getBinDirectory().getParent(), "xtra/emoticons").getAbsoluteFile(); File[] files = null; files = EMOTICON_DIRECTORY.listFiles(); // If files in this directory, copy this files into the Spark User Home // Directory if (files != null) { // Copy over to allow for non-admins to extract. copyFiles(); final LocalPreferences pref = SettingsManager.getLocalPreferences(); String emoticonPack = pref.getEmoticonPack(); try { addEmoticonPack(emoticonPack); } catch (Exception e) { Log.error(e); } } } /** * Copy the files directly over to an accepted permissions directory. */ private void copyFiles() { // Current Plugin directory File newEmoticonDir = new File(Spark.getLogDirectory().getParentFile(), "xtra/emoticons").getAbsoluteFile(); newEmoticonDir.mkdirs(); File[] files = EMOTICON_DIRECTORY.listFiles(); for (File file : files) { if (file.isFile()) { try { // Copy over File newFile = new File(newEmoticonDir, file.getName()); // Check timestamps long installerFile = file.lastModified(); long copiedFile = newFile.lastModified(); if (installerFile > copiedFile) { // Check if File is Zip-File int endIndex = file.getName().indexOf(".zip"); if (endIndex > 0) { String unzipURL = file.getName().substring(0, endIndex); File unzipFile = new File(newEmoticonDir, unzipURL); if (!unzipFile.exists()) { // Copy over and expand :) URLFileSystem.copy(file.toURI().toURL(), newFile); expandNewPack(newFile, newEmoticonDir); } } } } catch (IOException e) { Log.error(e); } } } EMOTICON_DIRECTORY = newEmoticonDir; } /** * Returns the active emoticon set within Spark. * * @return the active set of emoticons. */ public Collection<Emoticon> getActiveEmoticonSet() { final LocalPreferences pref = SettingsManager.getLocalPreferences(); String emoticonPack = null; emoticonPack = pref.getEmoticonPack(); // If EmoticonPack is set //When no emoticon set is available, return an empty list if (emoticonPack != null) { Collection<Emoticon> emoticonSet = emoticonMap.get(emoticonPack); Collection<Emoticon> empty = Collections.emptyList(); return emoticonSet == null ? empty : emoticonSet; } return Collections.emptyList(); } /** * Returns the name of the active emoticon set. * * @return the name of the active emoticon set. */ public String getActiveEmoticonSetName() { final LocalPreferences pref = SettingsManager.getLocalPreferences(); return pref.getEmoticonPack(); } /** * Sets the active emoticon set. * * @param pack * the archive containing the emotiocon pack. */ public void setActivePack(String pack) { final LocalPreferences pref = SettingsManager.getLocalPreferences(); pref.setEmoticonPack(pack); SettingsManager.saveSettings(); imageMap.clear(); } /** * Installs a new Adium style emoticon pack into Spark. * * @param pack * the emotiocn pack (contains Emotiocons.plist) * @return the name of the newly installed emoticon set. */ public String installPack(File pack) { if (!containsEmoticonPList(pack)) { return null; } String name = null; // Copy to the emoticon area try { URLFileSystem.copy(pack.toURI().toURL(), new File( EMOTICON_DIRECTORY, pack.getName())); File rootDirectory = unzipPack(pack, EMOTICON_DIRECTORY); name = URLFileSystem.getName(rootDirectory.toURI().toURL()); addEmoticonPack(name); } catch (IOException e) { Log.error(e); } return name; } /** * Loads an emoticon set. * * @param packName * the name of the pack. */ public void addEmoticonPack(String packName) { File emoticonSet = new File(EMOTICON_DIRECTORY, packName + ".adiumemoticonset"); if (!emoticonSet.exists()) { emoticonSet = new File(EMOTICON_DIRECTORY, packName + ".AdiumEmoticonset"); } if (!emoticonSet.exists()) { emoticonSet = new File(EMOTICON_DIRECTORY, "Default.adiumemoticonset"); packName = "Default"; setActivePack("Default"); } List<Emoticon> emoticons = new ArrayList<Emoticon>(); final File plist = new File(emoticonSet, "Emoticons.plist"); // Create SaxReader and set to non-validating parser. // This will allow for non-http problems to not break spark :) final SAXReader saxParser = new SAXReader(); saxParser.setValidation(false); try { saxParser.setFeature("http://xml.org/sax/features/validation", false); saxParser.setFeature("http://xml.org/sax/features/namespaces", false); saxParser.setFeature( "http://apache.org/xml/features/validation/schema", false); saxParser .setFeature( "http://apache.org/xml/features/validation/schema-full-checking", false); saxParser.setFeature( "http://apache.org/xml/features/validation/dynamic", false); saxParser .setFeature( "http://apache.org/xml/features/allow-java-encodings", true); saxParser .setFeature( "http://apache.org/xml/features/continue-after-fatal-error", true); saxParser .setFeature( "http://apache.org/xml/features/nonvalidating/load-dtd-grammar", false); saxParser .setFeature( "http://apache.org/xml/features/nonvalidating/load-external-dtd", false); } catch (SAXException e) { e.printStackTrace(); } Document emoticonFile; try { emoticonFile = saxParser.read(plist); } catch (DocumentException e) { Log.error(e); return; } Node root = emoticonFile.selectSingleNode("/plist/dict/dict"); List<?> keyList = root.selectNodes("key"); List<?> dictonaryList = root.selectNodes("dict"); Iterator<?> keys = keyList.iterator(); Iterator<?> dicts = dictonaryList.iterator(); while (keys.hasNext()) { Element keyEntry = (Element) keys.next(); String key = keyEntry.getText(); Element dict = (Element) dicts.next(); String name = dict.selectSingleNode("string").getText(); // Load equivilants final List<String> equivs = new ArrayList<String>(); final List<?> equivilants = dict.selectNodes("array/string"); for (Object equivilant1 : equivilants) { Element equivilant = (Element) equivilant1; String equivilantString = equivilant.getText(); equivs.add(equivilantString); } final Emoticon emoticon = new Emoticon(key, name, equivs, emoticonSet); emoticons.add(emoticon); } emoticonMap.put(packName, emoticons); } /** * Retrieve the URL to an emoticon. * * @param emoticon * the emoticon. * @return the URL of the image. */ public URL getEmoticonURL(Emoticon emoticon) { final String imageName = emoticon.getImageName(); File file = new File(emoticon.getEmoticonDirectory(), imageName); try { return file.toURI().toURL(); } catch (MalformedURLException e) { e.printStackTrace(); } return null; } /** * Retrieves the associated key emoticon. * * @param packName * the name of the Archive Pack File. * @param key * the key. * @return the emoticon. */ public Emoticon getEmoticon(String packName, String key) { final Collection<Emoticon> emoticons = emoticonMap.get(packName); if (emoticons == null) { return null; } for (Emoticon emoticon : emoticons) { for (String string : emoticon.getEquivalants()) { if (key.equals(string)) { return emoticon; } } } return null; } /** * Returns the <code>Emoticon</code> associated with the given key. Note: * This gets the emoticon from the active emoticon pack. * * @param key * the key. * @return the Emoticon found. If no emoticon is found, null is returned. */ public Emoticon getEmoticon(String key) { final Collection<Emoticon> emoticons = emoticonMap .get(getActiveEmoticonSetName()); for (Emoticon emoticon : emoticons) { for (String string : emoticon.getEquivalants()) { if (key.toLowerCase().equals(string.toLowerCase())) { return emoticon; } } } return null; } /** * Returns the Icon that is mapped to a given key. * * @param key * the key to search for. * @return the Icon representing the key. */ public ImageIcon getEmoticonImage(String key) { final Emoticon emoticon = getEmoticon(key); if (emoticon != null) { ImageIcon icon = imageMap.get(key); if (icon == null) { URL url = getEmoticonURL(emoticon); icon = new ImageIcon(url); imageMap.put(key, icon); } return imageMap.get(key); } return null; } /** * Returns a list of all available emoticon packs. * * @return Collection of Emoticon Pack names. */ public Collection<String> getEmoticonPacks() { final List<String> emoticonList = new ArrayList<String>(); File[] dirs = EMOTICON_DIRECTORY.listFiles(); // If no emoticons are available if (dirs == null) { return null; } for (File file : dirs) { if (file.isDirectory() && file.getName().toLowerCase() .endsWith("adiumemoticonset")) { try { String name = URLFileSystem.getName(file.toURI().toURL()); name = name.replaceAll("adiumemoticonset", ""); name = name.replaceAll("AdiumEmoticonset", ""); emoticonList.add(name); } catch (MalformedURLException e) { e.printStackTrace(); } } } return emoticonList; } /** * Expands any zipped Emoticon Packs. * * @param file * File to unpack. * @param dist * Dist file. */ private void expandNewPack(File file, File dist) { URL url = null; try { url = file.toURI().toURL(); } catch (MalformedURLException e) { Log.error(e); } String name = URLFileSystem.getName(url); File directory = new File(dist, name); // Unzip contents into directory unzipPack(file, directory.getParentFile()); } /** * Checks zip file for the Emoticons.plist file. This is Sparks way of * detecting a valid file. * * @param zip * the zip file to check. * @return true if the EmoticonPlist exists in the archive. */ private boolean containsEmoticonPList(File zip) { try { ZipFile zipFile = new JarFile(zip); for (Enumeration<?> e = zipFile.entries(); e.hasMoreElements();) { JarEntry entry = (JarEntry) e.nextElement(); if (entry.getName().contains("Emoticons.plist")) { return true; } } } catch (IOException e) { Log.error(e); } return false; } /** * Unzips a theme from a ZIP file into a directory. * * @param zip * the ZIP file * @param dir * the directory to extract the plugin to. * @return the root directory. */ private File unzipPack(File zip, File dir) { File rootDirectory = null; try { ZipFile zipFile = new JarFile(zip); dir.mkdir(); for (Enumeration<?> e = zipFile.entries(); e.hasMoreElements();) { JarEntry entry = (JarEntry) e.nextElement(); File entryFile = new File(dir, entry.getName()); // Ignore any manifest.mf entries. if (entry.getName().toLowerCase().endsWith("manifest.mf")) { continue; } if (entry.isDirectory() && rootDirectory == null) { rootDirectory = entryFile; } if (!entry.isDirectory()) { entryFile.getParentFile().mkdirs(); FileOutputStream out = new FileOutputStream(entryFile); InputStream zin = zipFile.getInputStream(entry); byte[] b = new byte[512]; int len; while ((len = zin.read(b)) != -1) { out.write(b, 0, len); } out.flush(); out.close(); zin.close(); } } zipFile.close(); } catch (Exception e) { Log.error("Error unzipping emoticon pack", e); } return rootDirectory; } }