From: Niki Roo Date: Sun, 19 Apr 2020 08:51:59 +0000 (+0200) Subject: reset subtree fanfix X-Git-Tag: fanfix-swing-0.0.1~13 X-Git-Url: https://git.nikiroo.be/?a=commitdiff_plain;h=b57e40df8fde80b8f9444f2894dda51279ae43a1;p=fanfix-swing.git reset subtree fanfix --- diff --git a/src/be/nikiroo/fanfix/DataLoader.java b/src/be/nikiroo/fanfix/DataLoader.java deleted file mode 100644 index 901e8da4..00000000 --- a/src/be/nikiroo/fanfix/DataLoader.java +++ /dev/null @@ -1,396 +0,0 @@ -package be.nikiroo.fanfix; - -import java.io.File; -import java.io.IOException; -import java.io.InputStream; -import java.net.URL; -import java.util.Map; - -import be.nikiroo.fanfix.bundles.Config; -import be.nikiroo.fanfix.supported.BasicSupport; -import be.nikiroo.utils.Cache; -import be.nikiroo.utils.CacheMemory; -import be.nikiroo.utils.Downloader; -import be.nikiroo.utils.Image; -import be.nikiroo.utils.ImageUtils; -import be.nikiroo.utils.TraceHandler; - -/** - * This cache will manage Internet (and local) downloads, as well as put the - * downloaded files into a cache. - *

- * As long the cached resource is not too old, it will use it instead of - * retrieving the file again. - * - * @author niki - */ -public class DataLoader { - private Downloader downloader; - private Downloader downloaderNoCache; - private Cache cache; - private boolean offline; - - /** - * Create a new {@link DataLoader} object. - * - * @param dir - * the directory to use as cache - * @param UA - * the User-Agent to use to download the resources - * @param hoursChanging - * the number of hours after which a cached file that is thought - * to change ~often is considered too old (or -1 for - * "never too old") - * @param hoursStable - * the number of hours after which a LARGE cached file that is - * thought to change rarely is considered too old (or -1 for - * "never too old") - * - * @throws IOException - * in case of I/O error - */ - public DataLoader(File dir, String UA, int hoursChanging, int hoursStable) - throws IOException { - downloader = new Downloader(UA, new Cache(dir, hoursChanging, - hoursStable)); - downloaderNoCache = new Downloader(UA); - - cache = downloader.getCache(); - } - - /** - * Create a new {@link DataLoader} object without disk cache (will keep a - * memory cache for manual cache operations). - * - * @param UA - * the User-Agent to use to download the resources - */ - public DataLoader(String UA) { - downloader = new Downloader(UA); - downloaderNoCache = downloader; - cache = new CacheMemory(); - } - - /** - * This {@link Downloader} is forbidden to try and connect to the network. - *

- * If TRUE, it will only check the cache (even in no-cache mode!). - *

- * Default is FALSE. - * - * @return TRUE if offline - */ - public boolean isOffline() { - return offline; - } - - /** - * This {@link Downloader} is forbidden to try and connect to the network. - *

- * If TRUE, it will only check the cache (even in no-cache mode!). - *

- * Default is FALSE. - * - * @param offline TRUE for offline, FALSE for online - */ - public void setOffline(boolean offline) { - this.offline = offline; - downloader.setOffline(offline); - downloaderNoCache.setOffline(offline); - - // If we don't, we cannot support no-cache using code in OFFLINE mode - if (offline) { - downloaderNoCache.setCache(cache); - } else { - downloaderNoCache.setCache(null); - } - } - - /** - * The traces handler for this {@link Cache}. - * - * @param tracer - * the new traces handler - */ - public void setTraceHandler(TraceHandler tracer) { - downloader.setTraceHandler(tracer); - downloaderNoCache.setTraceHandler(tracer); - cache.setTraceHandler(tracer); - if (downloader.getCache() != null) { - downloader.getCache().setTraceHandler(tracer); - } - - } - - /** - * Open a resource (will load it from the cache if possible, or save it into - * the cache after downloading if not). - *

- * The cached resource will be assimilated to the given original {@link URL} - * - * @param url - * the resource to open - * @param support - * the support to use to download the resource (can be NULL) - * @param stable - * TRUE for more stable resources, FALSE when they often change - * - * @return the opened resource, NOT NULL - * - * @throws IOException - * in case of I/O error - */ - public InputStream open(URL url, BasicSupport support, boolean stable) - throws IOException { - return open(url, url, support, stable, null, null, null); - } - - /** - * Open a resource (will load it from the cache if possible, or save it into - * the cache after downloading if not). - *

- * The cached resource will be assimilated to the given original {@link URL} - * - * @param url - * the resource to open - * @param originalUrl - * the original {@link URL} before any redirection occurs, which - * is also used for the cache ID if needed (so we can retrieve - * the content with this URL if needed) - * @param support - * the support to use to download the resource - * @param stable - * TRUE for more stable resources, FALSE when they often change - * - * @return the opened resource, NOT NULL - * - * @throws IOException - * in case of I/O error - */ - public InputStream open(URL url, URL originalUrl, BasicSupport support, - boolean stable) throws IOException { - return open(url, originalUrl, support, stable, null, null, null); - } - - /** - * Open a resource (will load it from the cache if possible, or save it into - * the cache after downloading if not). - *

- * The cached resource will be assimilated to the given original {@link URL} - * - * @param url - * the resource to open - * @param originalUrl - * the original {@link URL} before any redirection occurs, which - * is also used for the cache ID if needed (so we can retrieve - * the content with this URL if needed) - * @param support - * the support to use to download the resource (can be NULL) - * @param stable - * TRUE for more stable resources, FALSE when they often change - * @param postParams - * the POST parameters - * @param getParams - * the GET parameters (priority over POST) - * @param oauth - * OAuth authorization (aka, "bearer XXXXXXX") - * - * @return the opened resource, NOT NULL - * - * @throws IOException - * in case of I/O error - */ - public InputStream open(URL url, URL originalUrl, BasicSupport support, - boolean stable, Map postParams, - Map getParams, String oauth) throws IOException { - - Map cookiesValues = null; - URL currentReferer = url; - - if (support != null) { - cookiesValues = support.getCookies(); - currentReferer = support.getCurrentReferer(); - // priority: arguments - if (oauth == null) { - oauth = support.getOAuth(); - } - } - - return downloader.open(url, originalUrl, currentReferer, cookiesValues, - postParams, getParams, oauth, stable); - } - - /** - * Open the given {@link URL} without using the cache, but still using and - * updating the cookies. - * - * @param url - * the {@link URL} to open - * @param support - * the {@link BasicSupport} used for the cookies - * @param postParams - * the POST parameters - * @param getParams - * the GET parameters (priority over POST) - * @param oauth - * OAuth authorization (aka, "bearer XXXXXXX") - * - * @return the {@link InputStream} of the opened page - * - * @throws IOException - * in case of I/O error - */ - public InputStream openNoCache(URL url, BasicSupport support, - Map postParams, Map getParams, - String oauth) throws IOException { - - Map cookiesValues = null; - URL currentReferer = url; - if (support != null) { - cookiesValues = support.getCookies(); - currentReferer = support.getCurrentReferer(); - // priority: arguments - if (oauth == null) { - oauth = support.getOAuth(); - } - } - - return downloaderNoCache.open(url, currentReferer, cookiesValues, - postParams, getParams, oauth); - } - - /** - * Refresh the resource into cache if needed. - * - * @param url - * the resource to open - * @param support - * the support to use to download the resource (can be NULL) - * @param stable - * TRUE for more stable resources, FALSE when they often change - * - * @throws IOException - * in case of I/O error - */ - public void refresh(URL url, BasicSupport support, boolean stable) - throws IOException { - if (!check(url, stable)) { - open(url, url, support, stable, null, null, null).close(); - } - } - - /** - * Check the resource to see if it is in the cache. - * - * @param url - * the resource to check - * @param stable - * a stable file (that dones't change too often) -- parameter - * used to check if the file is too old to keep or not - * - * @return TRUE if it is - * - */ - public boolean check(URL url, boolean stable) { - return downloader.getCache() != null - && downloader.getCache().check(url, false, stable); - } - - /** - * Save the given resource as an image on disk using the default image - * format for content or cover -- will automatically add the extension, too. - * - * @param img - * the resource - * @param target - * the target file without extension - * @param cover - * use the cover image format instead of the content image format - * - * @throws IOException - * in case of I/O error - */ - public void saveAsImage(Image img, File target, boolean cover) - throws IOException { - String format; - if (cover) { - format = Instance.getInstance().getConfig().getString(Config.FILE_FORMAT_IMAGE_FORMAT_COVER).toLowerCase(); - } else { - format = Instance.getInstance().getConfig().getString(Config.FILE_FORMAT_IMAGE_FORMAT_CONTENT) - .toLowerCase(); - } - saveAsImage(img, new File(target.toString() + "." + format), format); - } - - /** - * Save the given resource as an image on disk using the given image format - * for content, or with "png" format if it fails. - * - * @param img - * the resource - * @param target - * the target file - * @param format - * the file format ("png", "jpeg", "bmp"...) - * - * @throws IOException - * in case of I/O error - */ - public void saveAsImage(Image img, File target, String format) - throws IOException { - ImageUtils.getInstance().saveAsImage(img, target, format); - } - - /** - * Manually add this item to the cache. - * - * @param in - * the input data - * @param uniqueID - * a unique ID for this resource - * - * - * @throws IOException - * in case of I/O error - */ - public void addToCache(InputStream in, String uniqueID) throws IOException { - cache.save(in, uniqueID); - } - - /** - * Return the {@link InputStream} corresponding to the given unique ID, or - * NULL if none found. - * - * @param uniqueID - * the unique ID - * - * @return the content or NULL - */ - public InputStream getFromCache(String uniqueID) { - return cache.load(uniqueID, true, true); - } - - /** - * Remove the given resource from the cache. - * - * @param uniqueID - * a unique ID used to locate the cached resource - * - * @return TRUE if it was removed - */ - public boolean removeFromCache(String uniqueID) { - return cache.remove(uniqueID); - } - - /** - * Clean the cache (delete the cached items). - * - * @param onlyOld - * only clean the files that are considered too old - * - * @return the number of cleaned items - */ - public int cleanCache(boolean onlyOld) { - return cache.clean(onlyOld); - } -} diff --git a/src/be/nikiroo/fanfix/Instance.java b/src/be/nikiroo/fanfix/Instance.java deleted file mode 100644 index f48d05b7..00000000 --- a/src/be/nikiroo/fanfix/Instance.java +++ /dev/null @@ -1,631 +0,0 @@ -package be.nikiroo.fanfix; - -import java.io.File; -import java.io.IOException; -import java.util.Date; - -import be.nikiroo.fanfix.bundles.Config; -import be.nikiroo.fanfix.bundles.ConfigBundle; -import be.nikiroo.fanfix.bundles.StringId; -import be.nikiroo.fanfix.bundles.StringIdBundle; -import be.nikiroo.fanfix.bundles.StringIdGuiBundle; -import be.nikiroo.fanfix.bundles.UiConfig; -import be.nikiroo.fanfix.bundles.UiConfigBundle; -import be.nikiroo.fanfix.library.BasicLibrary; -import be.nikiroo.fanfix.library.CacheLibrary; -import be.nikiroo.fanfix.library.LocalLibrary; -import be.nikiroo.fanfix.library.RemoteLibrary; -import be.nikiroo.utils.Cache; -import be.nikiroo.utils.IOUtils; -import be.nikiroo.utils.Image; -import be.nikiroo.utils.Proxy; -import be.nikiroo.utils.TempFiles; -import be.nikiroo.utils.TraceHandler; -import be.nikiroo.utils.resources.Bundles; - -/** - * Global state for the program (services and singletons). - * - * @author niki - */ -public class Instance { - static private Instance instance; - static private Object instancelock = new Object(); - - private ConfigBundle config; - private UiConfigBundle uiconfig; - private StringIdBundle trans; - private DataLoader cache; - private StringIdGuiBundle transGui; - private BasicLibrary lib; - private File coverDir; - private File readerTmp; - private File remoteDir; - private String configDir; - private TraceHandler tracer; - private TempFiles tempFiles; - - /** - * Initialise the instance -- if already initialised, nothing will happen. - *

- * Before calling this method, you may call {@link Bundles#setDirectory(String)} - * if wanted. - */ - static public void init() { - init(false); - } - - /** - * Initialise the instance -- if already initialised, nothing will happen unless - * you pass TRUE to force. - *

- * Before calling this method, you may call {@link Bundles#setDirectory(String)} - * if wanted. - *

- * Note: forcing the initialisation can be dangerous, so make sure to only make - * it under controlled circumstances -- for instance, at the start of the - * program, you could call {@link Instance#init()}, change some settings because - * you want to force those settings (it will also forbid users to change them!) - * and then call {@link Instance#init(boolean)} with force set to TRUE. - * - * @param force force the initialisation even if already initialised - */ - static public void init(boolean force) { - synchronized (instancelock) { - if (instance == null || force) { - instance = new Instance(); - } - } - - } - - /** - * Force-initialise the {@link Instance} to a known value. - *

- * Usually for DEBUG/Test purposes. - * - * @param instance the actual Instance to use - */ - static public void init(Instance instance) { - Instance.instance = instance; - } - - /** - * The (mostly unique) instance of this {@link Instance}. - * - * @return the (mostly unique) instance - */ - public static Instance getInstance() { - return instance; - } - - /** - * Actually initialise the instance. - *

- * Before calling this method, you may call {@link Bundles#setDirectory(String)} - * if wanted. - */ - protected Instance() { - // Before we can configure it: - Boolean debug = checkEnv("DEBUG"); - boolean trace = debug != null && debug; - tracer = new TraceHandler(true, trace, trace); - - // config dir: - configDir = getConfigDir(); - if (!new File(configDir).exists()) { - new File(configDir).mkdirs(); - } - - // Most of the rest is dependent upon this: - createConfigs(configDir, false); - - // Proxy support - Proxy.use(config.getString(Config.NETWORK_PROXY)); - - // update tracer: - if (debug == null) { - debug = config.getBoolean(Config.DEBUG_ERR, false); - trace = config.getBoolean(Config.DEBUG_TRACE, false); - } - - tracer = new TraceHandler(true, debug, trace); - - // default Library - remoteDir = new File(configDir, "remote"); - lib = createDefaultLibrary(remoteDir); - - // create cache and TMP - File tmp = getFile(Config.CACHE_DIR, new File(configDir, "tmp")); - if (!tmp.isAbsolute()) { - tmp = new File(configDir, tmp.getPath()); - } - Image.setTemporaryFilesRoot(new File(tmp.getParent(), "tmp.images")); - - String ua = config.getString(Config.NETWORK_USER_AGENT, ""); - try { - int hours = config.getInteger(Config.CACHE_MAX_TIME_CHANGING, 0); - int hoursLarge = config.getInteger(Config.CACHE_MAX_TIME_STABLE, 0); - cache = new DataLoader(tmp, ua, hours, hoursLarge); - } catch (IOException e) { - tracer.error(new IOException("Cannot create cache (will continue without cache)", e)); - cache = new DataLoader(ua); - } - - cache.setTraceHandler(tracer); - - // readerTmp / coverDir - readerTmp = getFile(UiConfig.CACHE_DIR_LOCAL_READER, new File(configDir, "tmp-reader")); - - coverDir = getFile(Config.DEFAULT_COVERS_DIR, new File(configDir, "covers")); - coverDir.mkdirs(); - - try { - tempFiles = new TempFiles("fanfix"); - } catch (IOException e) { - tracer.error(new IOException("Cannot create temporary directory", e)); - } - } - - /** - * The traces handler for this {@link Cache}. - *

- * It is never NULL. - * - * @return the traces handler (never NULL) - */ - public TraceHandler getTraceHandler() { - return tracer; - } - - /** - * The traces handler for this {@link Cache}. - * - * @param tracer the new traces handler or NULL - */ - public void setTraceHandler(TraceHandler tracer) { - if (tracer == null) { - tracer = new TraceHandler(false, false, false); - } - - this.tracer = tracer; - cache.setTraceHandler(tracer); - } - - /** - * Get the (unique) configuration service for the program. - * - * @return the configuration service - */ - public ConfigBundle getConfig() { - return config; - } - - /** - * Get the (unique) UI configuration service for the program. - * - * @return the configuration service - */ - public UiConfigBundle getUiConfig() { - return uiconfig; - } - - /** - * Reset the configuration. - * - * @param resetTrans also reset the translation files - */ - public void resetConfig(boolean resetTrans) { - String dir = Bundles.getDirectory(); - Bundles.setDirectory(null); - try { - try { - ConfigBundle config = new ConfigBundle(); - config.updateFile(configDir); - } catch (IOException e) { - tracer.error(e); - } - try { - UiConfigBundle uiconfig = new UiConfigBundle(); - uiconfig.updateFile(configDir); - } catch (IOException e) { - tracer.error(e); - } - - if (resetTrans) { - try { - StringIdBundle trans = new StringIdBundle(null); - trans.updateFile(configDir); - } catch (IOException e) { - tracer.error(e); - } - } - } finally { - Bundles.setDirectory(dir); - } - } - - /** - * Get the (unique) {@link DataLoader} for the program. - * - * @return the {@link DataLoader} - */ - public DataLoader getCache() { - return cache; - } - - /** - * Get the (unique) {link StringIdBundle} for the program. - *

- * This is used for the translations of the core parts of Fanfix. - * - * @return the {link StringIdBundle} - */ - public StringIdBundle getTrans() { - return trans; - } - - /** - * Get the (unique) {link StringIdGuiBundle} for the program. - *

- * This is used for the translations of the GUI parts of Fanfix. - * - * @return the {link StringIdGuiBundle} - */ - public StringIdGuiBundle getTransGui() { - return transGui; - } - - /** - * Get the (unique) {@link LocalLibrary} for the program. - * - * @return the {@link LocalLibrary} - */ - public BasicLibrary getLibrary() { - if (lib == null) { - throw new NullPointerException("We don't have a library to return"); - } - - return lib; - } - - /** - * Return the directory where to look for default cover pages. - * - * @return the default covers directory - */ - public File getCoverDir() { - return coverDir; - } - - /** - * Return the directory where to store temporary files for the local reader. - * - * @return the directory - */ - public File getReaderDir() { - return readerTmp; - } - - /** - * Return the directory where to store temporary files for the remote - * {@link LocalLibrary}. - * - * @param host the remote for this host - * - * @return the directory - */ - public File getRemoteDir(String host) { - return getRemoteDir(remoteDir, host); - } - - /** - * Return the directory where to store temporary files for the remote - * {@link LocalLibrary}. - * - * @param remoteDir the base remote directory - * @param host the remote for this host - * - * @return the directory - */ - private File getRemoteDir(File remoteDir, String host) { - remoteDir.mkdirs(); - - if (host != null) { - return new File(remoteDir, host); - } - - return remoteDir; - } - - /** - * Check if we need to check that a new version of Fanfix is available. - * - * @return TRUE if we need to - */ - public boolean isVersionCheckNeeded() { - try { - long wait = config.getInteger(Config.NETWORK_UPDATE_INTERVAL, 0) * 24 * 60 * 60 * 1000; - if (wait >= 0) { - String lastUpString = IOUtils.readSmallFile(new File(configDir, "LAST_UPDATE")); - long delay = new Date().getTime() - Long.parseLong(lastUpString); - if (delay > wait) { - return true; - } - } else { - return false; - } - } catch (Exception e) { - // No file or bad file: - return true; - } - - return false; - } - - /** - * Notify that we checked for a new version of Fanfix. - */ - public void setVersionChecked() { - try { - IOUtils.writeSmallFile(new File(configDir), "LAST_UPDATE", Long.toString(new Date().getTime())); - } catch (IOException e) { - tracer.error(e); - } - } - - /** - * The facility to use temporary files in this program. - *

- * MUST be closed at end of program. - * - * @return the facility - */ - public TempFiles getTempFiles() { - return tempFiles; - } - - /** - * The configuration directory (will check, in order of preference, the system - * properties, the environment and then defaults to - * {@link Instance#getHome()}/.fanfix). - * - * @return the config directory - */ - private String getConfigDir() { - String configDir = System.getProperty("CONFIG_DIR"); - - if (configDir == null) { - configDir = System.getenv("CONFIG_DIR"); - } - - if (configDir == null) { - configDir = new File(getHome(), ".fanfix").getPath(); - } - - return configDir; - } - - /** - * Create the config variables ({@link Instance#config}, - * {@link Instance#uiconfig}, {@link Instance#trans} and - * {@link Instance#transGui}). - * - * @param configDir the directory where to find the configuration files - * @param refresh TRUE to reset the configuration files from the default - * included ones - */ - private void createConfigs(String configDir, boolean refresh) { - if (!refresh) { - Bundles.setDirectory(configDir); - } - - try { - config = new ConfigBundle(); - config.updateFile(configDir); - } catch (IOException e) { - tracer.error(e); - } - - try { - uiconfig = new UiConfigBundle(); - uiconfig.updateFile(configDir); - } catch (IOException e) { - tracer.error(e); - } - - // No updateFile for this one! (we do not want the user to have custom - // translations that won't accept updates from newer versions) - trans = new StringIdBundle(getLang()); - transGui = new StringIdGuiBundle(getLang()); - - // Fix an old bug (we used to store custom translation files by - // default): - if (trans.getString(StringId.INPUT_DESC_CBZ) == null) { - trans.deleteFile(configDir); - } - - Boolean noutf = checkEnv("NOUTF"); - if (noutf != null && noutf) { - trans.setUnicode(false); - transGui.setUnicode(false); - } - - Bundles.setDirectory(configDir); - } - - /** - * Create the default library as specified by the config. - * - * @param remoteDir the base remote directory if needed - * - * @return the default {@link BasicLibrary} - */ - private BasicLibrary createDefaultLibrary(File remoteDir) { - BasicLibrary lib = null; - - boolean useRemote = config.getBoolean(Config.REMOTE_LIBRARY_ENABLED, false); - - if (useRemote) { - String host = null; - int port = -1; - try { - host = config.getString(Config.REMOTE_LIBRARY_HOST); - port = config.getInteger(Config.REMOTE_LIBRARY_PORT, -1); - String key = config.getString(Config.REMOTE_LIBRARY_KEY); - - tracer.trace("Selecting remote library " + host + ":" + port); - lib = new RemoteLibrary(key, host, port); - lib = new CacheLibrary(getRemoteDir(remoteDir, host), lib, uiconfig); - } catch (Exception e) { - tracer.error(new IOException("Cannot create remote library for: " + host + ":" + port, e)); - } - } else { - String libDir = System.getenv("BOOKS_DIR"); - if (libDir == null || libDir.isEmpty()) { - libDir = config.getString(Config.LIBRARY_DIR, "$HOME/Books"); - if (!getFile(libDir).isAbsolute()) { - libDir = new File(configDir, libDir).getPath(); - } - } - try { - lib = new LocalLibrary(getFile(libDir), config); - } catch (Exception e) { - tracer.error(new IOException("Cannot create library for directory: " + getFile(libDir), e)); - } - } - - return lib; - } - - /** - * Return a path, but support the special $HOME variable. - * - * @param id the key for the path, which may contain "$HOME" - * @param def the default value if none - * @return the path, with expanded "$HOME" if needed - */ - protected File getFile(Config id, File def) { - String path = config.getString(id, def.getPath()); - return getFile(path); - } - - /** - * Return a path, but support the special $HOME variable. - * - * @param id the key for the path, which may contain "$HOME" - * @param def the default value if none - * @return the path, with expanded "$HOME" if needed - */ - protected File getFile(UiConfig id, File def) { - String path = uiconfig.getString(id, def.getPath()); - return getFile(path); - } - - /** - * Return a path, but support the special $HOME variable. - * - * @param path the path, which may contain "$HOME" - * @return the path, with expanded "$HOME" if needed - */ - protected File getFile(String path) { - File file = null; - if (path != null && !path.isEmpty()) { - path = path.replace('/', File.separatorChar); - if (path.contains("$HOME")) { - path = path.replace("$HOME", getHome()); - } - - file = new File(path); - } - - return file; - } - - /** - * Return the home directory from the environment (FANFIX_DIR) or the system - * properties. - *

- * The environment variable is tested first. Then, the custom property - * "fanfix.home" is tried, followed by the usual "user.home" then "java.io.tmp" - * if nothing else is found. - * - * @return the home - */ - protected String getHome() { - String home = System.getenv("FANFIX_DIR"); - if (home != null && new File(home).isFile()) { - home = null; - } - - if (home == null || home.trim().isEmpty()) { - home = System.getProperty("fanfix.home"); - if (home != null && new File(home).isFile()) { - home = null; - } - } - - if (home == null || home.trim().isEmpty()) { - home = System.getProperty("user.home"); - if (!new File(home).isDirectory()) { - home = null; - } - } - - if (home == null || home.trim().isEmpty()) { - home = System.getProperty("java.io.tmpdir"); - if (!new File(home).isDirectory()) { - home = null; - } - } - - if (home == null) { - home = ""; - } - - return home; - } - - /** - * The language to use for the application (NULL = default system language). - * - * @return the language - */ - protected String getLang() { - String lang = config.getString(Config.LANG); - - if (lang == null || lang.isEmpty()) { - if (System.getenv("LANG") != null && !System.getenv("LANG").isEmpty()) { - lang = System.getenv("LANG"); - } - } - - if (lang != null && lang.isEmpty()) { - lang = null; - } - - return lang; - } - - /** - * Check that the given environment variable is "enabled". - * - * @param key the variable to check - * - * @return TRUE if it is - */ - protected Boolean checkEnv(String key) { - String value = System.getenv(key); - if (value != null) { - value = value.trim().toLowerCase(); - if ("yes".equals(value) || "true".equals(value) || "on".equals(value) || "1".equals(value) - || "y".equals(value)) { - return true; - } - - return false; - } - - return null; - } -} diff --git a/src/be/nikiroo/fanfix/Main.java b/src/be/nikiroo/fanfix/Main.java deleted file mode 100644 index 961816a3..00000000 --- a/src/be/nikiroo/fanfix/Main.java +++ /dev/null @@ -1,885 +0,0 @@ -package be.nikiroo.fanfix; - -import java.io.File; -import java.io.IOException; -import java.net.MalformedURLException; -import java.net.URL; -import java.util.ArrayList; -import java.util.List; - -import javax.net.ssl.SSLException; - -import be.nikiroo.fanfix.bundles.Config; -import be.nikiroo.fanfix.bundles.StringId; -import be.nikiroo.fanfix.data.Chapter; -import be.nikiroo.fanfix.data.MetaData; -import be.nikiroo.fanfix.data.Story; -import be.nikiroo.fanfix.library.BasicLibrary; -import be.nikiroo.fanfix.library.CacheLibrary; -import be.nikiroo.fanfix.library.LocalLibrary; -import be.nikiroo.fanfix.library.RemoteLibrary; -import be.nikiroo.fanfix.library.RemoteLibraryServer; -import be.nikiroo.fanfix.output.BasicOutput; -import be.nikiroo.fanfix.output.BasicOutput.OutputType; -import be.nikiroo.fanfix.reader.BasicReader; -import be.nikiroo.fanfix.reader.Reader; -import be.nikiroo.fanfix.reader.Reader.ReaderType; -import be.nikiroo.fanfix.searchable.BasicSearchable; -import be.nikiroo.fanfix.supported.BasicSupport; -import be.nikiroo.fanfix.supported.SupportType; -import be.nikiroo.utils.Progress; -import be.nikiroo.utils.Version; -import be.nikiroo.utils.serial.server.ServerObject; - -/** - * Main program entry point. - * - * @author niki - */ -public class Main { - private enum MainAction { - IMPORT, EXPORT, CONVERT, READ, READ_URL, LIST, HELP, SET_READER, START, VERSION, SERVER, STOP_SERVER, REMOTE, SET_SOURCE, SET_TITLE, SET_AUTHOR, SEARCH, SEARCH_TAG - } - - /** - * Main program entry point. - *

- * Known environment variables: - *

- *

- *

- * - * @param args - * see method description - */ - public static void main(String[] args) { - // Only one line, but very important: - Instance.init(); - - String urlString = null; - String luid = null; - String sourceString = null; - String titleString = null; - String authorString = null; - String chapString = null; - String target = null; - String key = null; - MainAction action = MainAction.START; - Boolean plusInfo = null; - String host = null; - Integer port = null; - SupportType searchOn = null; - String search = null; - List tags = new ArrayList(); - Integer page = null; - Integer item = null; - - boolean noMoreActions = false; - - int exitCode = 0; - for (int i = 0; exitCode == 0 && i < args.length; i++) { - // Action (--) handling: - if (!noMoreActions && args[i].startsWith("--")) { - if (args[i].equals("--")) { - noMoreActions = true; - } else { - try { - action = MainAction.valueOf(args[i].substring(2) - .toUpperCase().replace("-", "_")); - } catch (Exception e) { - Instance.getInstance().getTraceHandler() - .error(new IllegalArgumentException("Unknown action: " + args[i], e)); - exitCode = 255; - } - } - - continue; - } - - switch (action) { - case IMPORT: - if (urlString == null) { - urlString = args[i]; - } else { - exitCode = 255; - } - break; - case EXPORT: - if (luid == null) { - luid = args[i]; - } else if (sourceString == null) { - sourceString = args[i]; - } else if (target == null) { - target = args[i]; - } else { - exitCode = 255; - } - break; - case CONVERT: - if (urlString == null) { - urlString = args[i]; - } else if (sourceString == null) { - sourceString = args[i]; - } else if (target == null) { - target = args[i]; - } else if (plusInfo == null) { - if ("+info".equals(args[i])) { - plusInfo = true; - } else { - exitCode = 255; - } - } else { - exitCode = 255; - } - break; - case LIST: - if (sourceString == null) { - sourceString = args[i]; - } else { - exitCode = 255; - } - break; - case SET_SOURCE: - if (luid == null) { - luid = args[i]; - } else if (sourceString == null) { - sourceString = args[i]; - } else { - exitCode = 255; - } - break; - case SET_TITLE: - if (luid == null) { - luid = args[i]; - } else if (sourceString == null) { - titleString = args[i]; - } else { - exitCode = 255; - } - break; - case SET_AUTHOR: - if (luid == null) { - luid = args[i]; - } else if (sourceString == null) { - authorString = args[i]; - } else { - exitCode = 255; - } - break; - case READ: - if (luid == null) { - luid = args[i]; - } else if (chapString == null) { - chapString = args[i]; - } else { - exitCode = 255; - } - break; - case READ_URL: - if (urlString == null) { - urlString = args[i]; - } else if (chapString == null) { - chapString = args[i]; - } else { - exitCode = 255; - } - break; - case SEARCH: - if (searchOn == null) { - searchOn = SupportType.valueOfAllOkUC(args[i]); - - if (searchOn == null) { - Instance.getInstance().getTraceHandler().error("Website not known: <" + args[i] + ">"); - exitCode = 41; - break; - } - - if (BasicSearchable.getSearchable(searchOn) == null) { - Instance.getInstance().getTraceHandler().error("Website not supported: " + searchOn); - exitCode = 42; - break; - } - } else if (search == null) { - search = args[i]; - } else if (page != null && page == -1) { - try { - page = Integer.parseInt(args[i]); - } catch (Exception e) { - page = -2; - } - } else if (item != null && item == -1) { - try { - item = Integer.parseInt(args[i]); - } catch (Exception e) { - item = -2; - } - } else if (page == null || item == null) { - if (page == null && "page".equals(args[i])) { - page = -1; - } else if (item == null && "item".equals(args[i])) { - item = -1; - } else { - exitCode = 255; - } - } else { - exitCode = 255; - } - break; - case SEARCH_TAG: - if (searchOn == null) { - searchOn = SupportType.valueOfAllOkUC(args[i]); - - if (searchOn == null) { - Instance.getInstance().getTraceHandler().error("Website not known: <" + args[i] + ">"); - exitCode = 255; - } - - if (BasicSearchable.getSearchable(searchOn) == null) { - Instance.getInstance().getTraceHandler().error("Website not supported: " + searchOn); - exitCode = 255; - } - } else if (page == null && item == null) { - if ("page".equals(args[i])) { - page = -1; - } else if ("item".equals(args[i])) { - item = -1; - } else { - try { - int index = Integer.parseInt(args[i]); - tags.add(index); - } catch (NumberFormatException e) { - Instance.getInstance().getTraceHandler().error("Invalid tag index: " + args[i]); - exitCode = 255; - } - } - } else if (page != null && page == -1) { - try { - page = Integer.parseInt(args[i]); - } catch (Exception e) { - page = -2; - } - } else if (item != null && item == -1) { - try { - item = Integer.parseInt(args[i]); - } catch (Exception e) { - item = -2; - } - } else if (page == null || item == null) { - if (page == null && "page".equals(args[i])) { - page = -1; - } else if (item == null && "item".equals(args[i])) { - item = -1; - } else { - exitCode = 255; - } - } else { - exitCode = 255; - } - break; - case HELP: - exitCode = 255; - break; - case SET_READER: - exitCode = setReaderType(args[i]); - action = MainAction.START; - break; - case START: - exitCode = 255; // not supposed to be selected by user - break; - case VERSION: - exitCode = 255; // no arguments for this option - break; - case SERVER: - exitCode = 255; // no arguments for this option - break; - case STOP_SERVER: - exitCode = 255; // no arguments for this option - break; - case REMOTE: - if (key == null) { - key = args[i]; - } else if (host == null) { - host = args[i]; - } else if (port == null) { - port = Integer.parseInt(args[i]); - - BasicLibrary lib = new RemoteLibrary(key, host, port); - lib = new CacheLibrary(Instance.getInstance().getRemoteDir(host), lib, - Instance.getInstance().getUiConfig()); - - BasicReader.setDefaultLibrary(lib); - - action = MainAction.START; - } else { - exitCode = 255; - } - break; - } - } - - final Progress mainProgress = new Progress(0, 80); - mainProgress.addProgressListener(new Progress.ProgressListener() { - private int current = mainProgress.getMin(); - - @Override - public void progress(Progress progress, String name) { - int diff = progress.getProgress() - current; - current += diff; - - if (diff <= 0) - return; - - StringBuilder builder = new StringBuilder(); - for (int i = 0; i < diff; i++) { - builder.append('.'); - } - - System.err.print(builder.toString()); - - if (progress.isDone()) { - System.err.println(""); - } - } - }); - Progress pg = new Progress(); - mainProgress.addProgress(pg, mainProgress.getMax()); - - VersionCheck updates = VersionCheck.check(); - if (updates.isNewVersionAvailable()) { - // Sent to syserr so not to cause problem if one tries to capture a - // story content in text mode - System.err - .println("A new version of the program is available at https://github.com/nikiroo/fanfix/releases"); - System.err.println(""); - for (Version v : updates.getNewer()) { - System.err.println("\tVersion " + v); - System.err.println("\t-------------"); - System.err.println(""); - for (String it : updates.getChanges().get(v)) { - System.err.println("\t- " + it); - } - System.err.println(""); - } - } - - if (exitCode == 0) { - switch (action) { - case IMPORT: - exitCode = imprt(urlString, pg); - updates.ok(); // we consider it read - break; - case EXPORT: - exitCode = export(luid, sourceString, target, pg); - updates.ok(); // we consider it read - break; - case CONVERT: - exitCode = convert(urlString, sourceString, target, - plusInfo == null ? false : plusInfo, pg); - updates.ok(); // we consider it read - break; - case LIST: - if (BasicReader.getReader() == null) { - Instance.getInstance().getTraceHandler().error(new Exception("No reader type has been configured")); - exitCode = 10; - break; - } - exitCode = list(sourceString); - break; - case SET_SOURCE: - try { - Instance.getInstance().getLibrary().changeSource(luid, sourceString, pg); - } catch (IOException e1) { - Instance.getInstance().getTraceHandler().error(e1); - exitCode = 21; - } - break; - case SET_TITLE: - try { - Instance.getInstance().getLibrary().changeTitle(luid, titleString, pg); - } catch (IOException e1) { - Instance.getInstance().getTraceHandler().error(e1); - exitCode = 22; - } - break; - case SET_AUTHOR: - try { - Instance.getInstance().getLibrary().changeAuthor(luid, authorString, pg); - } catch (IOException e1) { - Instance.getInstance().getTraceHandler().error(e1); - exitCode = 23; - } - break; - case READ: - if (BasicReader.getReader() == null) { - Instance.getInstance().getTraceHandler().error(new Exception("No reader type has been configured")); - exitCode = 10; - break; - } - exitCode = read(luid, chapString, true); - break; - case READ_URL: - if (BasicReader.getReader() == null) { - Instance.getInstance().getTraceHandler().error(new Exception("No reader type has been configured")); - exitCode = 10; - break; - } - exitCode = read(urlString, chapString, false); - break; - case SEARCH: - page = page == null ? 1 : page; - if (page < 0) { - Instance.getInstance().getTraceHandler().error("Incorrect page number"); - exitCode = 255; - break; - } - - item = item == null ? 0 : item; - if (item < 0) { - Instance.getInstance().getTraceHandler().error("Incorrect item number"); - exitCode = 255; - break; - } - - if (BasicReader.getReader() == null) { - Instance.getInstance().getTraceHandler().error(new Exception("No reader type has been configured")); - exitCode = 10; - break; - } - - try { - if (searchOn == null) { - BasicReader.getReader().search(true); - } else if (search != null) { - - BasicReader.getReader().search(searchOn, search, page, - item, true); - } else { - exitCode = 255; - } - } catch (IOException e1) { - Instance.getInstance().getTraceHandler().error(e1); - exitCode = 20; - } - - break; - case SEARCH_TAG: - if (searchOn == null) { - exitCode = 255; - break; - } - - page = page == null ? 1 : page; - if (page < 0) { - Instance.getInstance().getTraceHandler().error("Incorrect page number"); - exitCode = 255; - break; - } - - item = item == null ? 0 : item; - if (item < 0) { - Instance.getInstance().getTraceHandler().error("Incorrect item number"); - exitCode = 255; - break; - } - - if (BasicReader.getReader() == null) { - Instance.getInstance().getTraceHandler().error(new Exception("No reader type has been configured")); - exitCode = 10; - break; - } - - try { - BasicReader.getReader().searchTag(searchOn, page, item, - true, tags.toArray(new Integer[] {})); - } catch (IOException e1) { - Instance.getInstance().getTraceHandler().error(e1); - } - - break; - case HELP: - syntax(true); - exitCode = 0; - break; - case SET_READER: - exitCode = 255; - break; - case VERSION: - System.out - .println(String.format("Fanfix version %s" - + "%nhttps://github.com/nikiroo/fanfix/" - + "%n\tWritten by Nikiroo", - Version.getCurrentVersion())); - updates.ok(); // we consider it read - break; - case START: - if (BasicReader.getReader() == null) { - Instance.getInstance().getTraceHandler().error(new Exception("No reader type has been configured")); - exitCode = 10; - break; - } - try { - BasicReader.getReader().browse(null); - } catch (IOException e) { - Instance.getInstance().getTraceHandler().error(e); - exitCode = 66; - } - break; - case SERVER: - key = Instance.getInstance().getConfig().getString(Config.SERVER_KEY); - port = Instance.getInstance().getConfig().getInteger(Config.SERVER_PORT); - if (port == null) { - System.err.println("No port configured in the config file"); - exitCode = 15; - break; - } - try { - ServerObject server = new RemoteLibraryServer(key, port); - server.setTraceHandler(Instance.getInstance().getTraceHandler()); - server.run(); - } catch (IOException e) { - Instance.getInstance().getTraceHandler().error(e); - } - return; - case STOP_SERVER: - // Can be given via "--remote XX XX XX" - if (key == null) - key = Instance.getInstance().getConfig().getString(Config.SERVER_KEY); - if (port == null) - port = Instance.getInstance().getConfig().getInteger(Config.SERVER_PORT); - - if (port == null) { - System.err.println("No port given nor configured in the config file"); - exitCode = 15; - break; - } - try { - new RemoteLibrary(key, host, port).exit(); - } catch (SSLException e) { - Instance.getInstance().getTraceHandler().error( - "Bad access key for remote library"); - exitCode = 43; - } catch (IOException e) { - Instance.getInstance().getTraceHandler().error(e); - exitCode = 44; - } - - break; - case REMOTE: - exitCode = 255; // should not be reachable (REMOTE -> START) - break; - } - } - - try { - Instance.getInstance().getTempFiles().close(); - } catch (IOException e) { - Instance.getInstance().getTraceHandler().error(new IOException("Cannot dispose of the temporary files", e)); - } - - if (exitCode == 255) { - syntax(false); - } - - System.exit(exitCode); - } - - /** - * Import the given resource into the {@link LocalLibrary}. - * - * @param urlString - * the resource to import - * @param pg - * the optional progress reporter - * - * @return the exit return code (0 = success) - */ - public static int imprt(String urlString, Progress pg) { - try { - MetaData meta = Instance.getInstance().getLibrary().imprt(BasicReader.getUrl(urlString), pg); - System.out.println(meta.getLuid() + ": \"" + meta.getTitle() + "\" imported."); - } catch (IOException e) { - Instance.getInstance().getTraceHandler().error(e); - return 1; - } - - return 0; - } - - /** - * Export the {@link Story} from the {@link LocalLibrary} to the given - * target. - * - * @param luid - * the story LUID - * @param typeString - * the {@link OutputType} to use - * @param target - * the target - * @param pg - * the optional progress reporter - * - * @return the exit return code (0 = success) - */ - public static int export(String luid, String typeString, String target, - Progress pg) { - OutputType type = OutputType.valueOfNullOkUC(typeString, null); - if (type == null) { - Instance.getInstance().getTraceHandler().error(new Exception(trans(StringId.OUTPUT_DESC, typeString))); - return 1; - } - - try { - Instance.getInstance().getLibrary().export(luid, type, target, pg); - } catch (IOException e) { - Instance.getInstance().getTraceHandler().error(e); - return 4; - } - - return 0; - } - - /** - * List the stories of the given source from the {@link LocalLibrary} - * (unless NULL is passed, in which case all stories will be listed). - * - * @param source - * the source to list the known stories of, or NULL to list all - * stories - * - * @return the exit return code (0 = success) - */ - private static int list(String source) { - BasicReader.setDefaultReaderType(ReaderType.CLI); - try { - BasicReader.getReader().browse(source); - } catch (IOException e) { - Instance.getInstance().getTraceHandler().error(e); - return 66; - } - - return 0; - } - - /** - * Start the current reader for this {@link Story}. - * - * @param story - * the LUID of the {@link Story} in the {@link LocalLibrary} - * or the {@link Story} {@link URL} - * @param chapString - * which {@link Chapter} to read (starting at 1), or NULL to get - * the {@link Story} description - * @param library - * TRUE if the source is the {@link Story} LUID, FALSE if it is a - * {@link URL} - * - * @return the exit return code (0 = success) - */ - private static int read(String story, String chapString, boolean library) { - try { - Reader reader = BasicReader.getReader(); - if (library) { - reader.setMeta(story); - } else { - reader.setMeta(BasicReader.getUrl(story), null); - } - - if (chapString != null) { - try { - reader.setChapter(Integer.parseInt(chapString)); - reader.read(true); - } catch (NumberFormatException e) { - Instance.getInstance().getTraceHandler() - .error(new IOException("Chapter number cannot be parsed: " + chapString, e)); - return 2; - } - } else { - reader.read(true); - } - } catch (IOException e) { - Instance.getInstance().getTraceHandler().error(e); - return 1; - } - - return 0; - } - - /** - * Convert the {@link Story} into another format. - * - * @param urlString - * the source {@link Story} to convert - * @param typeString - * the {@link OutputType} to convert to - * @param target - * the target file - * @param infoCover - * TRUE to also export the cover and info file, even if the given - * {@link OutputType} does not usually save them - * @param pg - * the optional progress reporter - * - * @return the exit return code (0 = success) - */ - public static int convert(String urlString, String typeString, - String target, boolean infoCover, Progress pg) { - int exitCode = 0; - - Instance.getInstance().getTraceHandler().trace("Convert: " + urlString); - String sourceName = urlString; - try { - URL source = BasicReader.getUrl(urlString); - sourceName = source.toString(); - if (sourceName.startsWith("file://")) { - sourceName = sourceName.substring("file://".length()); - } - - OutputType type = OutputType.valueOfAllOkUC(typeString, null); - if (type == null) { - Instance.getInstance().getTraceHandler() - .error(new IOException(trans(StringId.ERR_BAD_OUTPUT_TYPE, typeString))); - - exitCode = 2; - } else { - try { - BasicSupport support = BasicSupport.getSupport(source); - - if (support != null) { - Instance.getInstance().getTraceHandler().trace("Support found: " + support.getClass()); - Progress pgIn = new Progress(); - Progress pgOut = new Progress(); - if (pg != null) { - pg.setMax(2); - pg.addProgress(pgIn, 1); - pg.addProgress(pgOut, 1); - } - - Story story = support.process(pgIn); - try { - target = new File(target).getAbsolutePath(); - BasicOutput.getOutput(type, infoCover, infoCover).process(story, target, pgOut); - } catch (IOException e) { - Instance.getInstance().getTraceHandler() - .error(new IOException(trans(StringId.ERR_SAVING, target), e)); - exitCode = 5; - } - } else { - Instance.getInstance().getTraceHandler() - .error(new IOException(trans( StringId.ERR_NOT_SUPPORTED, source))); - - exitCode = 4; - } - } catch (IOException e) { - Instance.getInstance().getTraceHandler() - .error(new IOException(trans(StringId.ERR_LOADING, sourceName), e)); - exitCode = 3; - } - } - } catch (MalformedURLException e) { - Instance.getInstance().getTraceHandler().error(new IOException(trans(StringId.ERR_BAD_URL, sourceName), e)); - exitCode = 1; - } - - return exitCode; - } - - /** - * Simple shortcut method to call {link Instance#getTrans()#getString()}. - * - * @param id - * the ID to translate - * - * @return the translated result - */ - private static String trans(StringId id, Object... params) { - return Instance.getInstance().getTrans().getString(id, params); - } - - /** - * Display the correct syntax of the program to the user to stdout, or an - * error message if the syntax used was wrong on stderr. - * - * @param showHelp - * TRUE to show the syntax help, FALSE to show "syntax error" - */ - private static void syntax(boolean showHelp) { - if (showHelp) { - StringBuilder builder = new StringBuilder(); - for (SupportType type : SupportType.values()) { - builder.append(trans(StringId.ERR_SYNTAX_TYPE, type.toString(), - type.getDesc())); - builder.append('\n'); - } - - String typesIn = builder.toString(); - builder.setLength(0); - - for (OutputType type : OutputType.values()) { - builder.append(trans(StringId.ERR_SYNTAX_TYPE, type.toString(), - type.getDesc(true))); - builder.append('\n'); - } - - String typesOut = builder.toString(); - - System.out.println(trans(StringId.HELP_SYNTAX, typesIn, typesOut)); - } else { - System.err.println(trans(StringId.ERR_SYNTAX)); - } - } - - /** - * Set the default reader type for this session only (it can be changed in - * the configuration file, too, but this value will override it). - * - * @param readerTypeString - * the type - */ - private static int setReaderType(String readerTypeString) { - try { - ReaderType readerType = ReaderType.valueOf(readerTypeString - .toUpperCase()); - BasicReader.setDefaultReaderType(readerType); - return 0; - } catch (IllegalArgumentException e) { - Instance.getInstance().getTraceHandler() - .error(new IOException("Unknown reader type: " + readerTypeString, e)); - return 1; - } - } -} diff --git a/src/be/nikiroo/fanfix/VersionCheck.java b/src/be/nikiroo/fanfix/VersionCheck.java deleted file mode 100644 index f64159ab..00000000 --- a/src/be/nikiroo/fanfix/VersionCheck.java +++ /dev/null @@ -1,171 +0,0 @@ -package be.nikiroo.fanfix; - -import java.io.BufferedReader; -import java.io.IOException; -import java.io.InputStream; -import java.io.InputStreamReader; -import java.net.URL; -import java.util.ArrayList; -import java.util.HashMap; -import java.util.List; -import java.util.Locale; -import java.util.Map; - -import be.nikiroo.utils.Version; - -/** - * Version checker: can check the current version of the program against a - * remote changelog, and list the missed updates and their description. - * - * @author niki - */ -public class VersionCheck { - private static final String base = "https://github.com/nikiroo/fanfix/raw/master/changelog${LANG}.md"; - - private Version current; - private List newer; - private Map> changes; - - /** - * Create a new {@link VersionCheck}. - * - * @param current - * the current version of the program - * @param newer - * the list of available {@link Version}s newer the current one - * @param changes - * the list of changes - */ - private VersionCheck(Version current, List newer, - Map> changes) { - this.current = current; - this.newer = newer; - this.changes = changes; - } - - /** - * Check if there are more recent {@link Version}s of this program - * available. - * - * @return TRUE if there is at least one - */ - public boolean isNewVersionAvailable() { - return !newer.isEmpty(); - } - - /** - * The current {@link Version} of the program. - * - * @return the current {@link Version} - */ - public Version getCurrentVersion() { - return current; - } - - /** - * The list of available {@link Version}s newer than the current one. - * - * @return the newer {@link Version}s - */ - public List getNewer() { - return newer; - } - - /** - * The list of changes for each available {@link Version} newer than the - * current one. - * - * @return the list of changes - */ - public Map> getChanges() { - return changes; - } - - /** - * Ignore the check result. - */ - public void ignore() { - - } - - /** - * Accept the information, and do not check again until the minimum wait - * time has elapsed. - */ - public void ok() { - Instance.getInstance().setVersionChecked(); - } - - /** - * Check if there are available {@link Version}s of this program more recent - * than the current one. - * - * @return a {@link VersionCheck} - */ - public static VersionCheck check() { - Version current = Version.getCurrentVersion(); - List newer = new ArrayList(); - Map> changes = new HashMap>(); - - if (Instance.getInstance().isVersionCheckNeeded()) { - try { - // Prepare the URLs according to the user's language - Locale lang = Instance.getInstance().getTrans().getLocale(); - String fr = lang.getLanguage(); - String BE = lang.getCountry().replace(".UTF8", ""); - String urlFrBE = base.replace("${LANG}", "-" + fr + "_" + BE); - String urlFr = base.replace("${LANG}", "-" + fr); - String urlDefault = base.replace("${LANG}", ""); - - InputStream in = null; - for (String url : new String[] { urlFrBE, urlFr, urlDefault }) { - try { - in = Instance.getInstance().getCache().open(new URL(url), null, false); - break; - } catch (IOException e) { - } - } - - if (in == null) { - throw new IOException("No changelog found"); - } - - BufferedReader reader = new BufferedReader( - new InputStreamReader(in, "UTF-8")); - try { - Version version = new Version(); - for (String line = reader.readLine(); line != null; line = reader - .readLine()) { - if (line.startsWith("## Version ")) { - version = new Version(line.substring("## Version " - .length())); - if (version.isNewerThan(current)) { - newer.add(version); - changes.put(version, new ArrayList()); - } else { - version = new Version(); - } - } else if (!version.isEmpty() && !newer.isEmpty() - && !line.isEmpty()) { - List ch = changes.get(newer.get(newer - .size() - 1)); - if (!ch.isEmpty() && !line.startsWith("- ")) { - int i = ch.size() - 1; - ch.set(i, ch.get(i) + " " + line.trim()); - } else { - ch.add(line.substring("- ".length()).trim()); - } - } - } - } finally { - reader.close(); - } - } catch (IOException e) { - Instance.getInstance().getTraceHandler() - .error(new IOException("Cannot download latest changelist on github.com", e)); - } - } - - return new VersionCheck(current, newer, changes); - } -} diff --git a/src/be/nikiroo/fanfix/bundles/Config.java b/src/be/nikiroo/fanfix/bundles/Config.java deleted file mode 100644 index 7360f393..00000000 --- a/src/be/nikiroo/fanfix/bundles/Config.java +++ /dev/null @@ -1,164 +0,0 @@ -package be.nikiroo.fanfix.bundles; - -import be.nikiroo.utils.resources.Meta; -import be.nikiroo.utils.resources.Meta.Format; - -/** - * The configuration options. - * - * @author niki - */ -@SuppressWarnings("javadoc") -public enum Config { - @Meta(description = "The language to use for in the program (example: en-GB, fr-BE...) or nothing for default system language (can be overwritten with the variable $LANG)",// - format = Format.LOCALE, list = { "en-GB", "fr-BE" }) - LANG, // - @Meta(description = "The default reader type to use to read stories:\nCLI = simple output to console\nTUI = a Text User Interface with menus and windows, based upon Jexer\nGUI = a GUI with locally stored files, based upon Swing", // - format = Format.FIXED_LIST, list = { "CLI", "GUI", "TUI" }, def = "GUI") - READER_TYPE, // - - @Meta(description = "File format options",// - group = true) - FILE_FORMAT, // - @Meta(description = "How to save non-images documents in the library",// - format = Format.FIXED_LIST, list = { "INFO_TEXT", "EPUB", "HTML", "TEXT" }, def = "INFO_TEXT") - FILE_FORMAT_NON_IMAGES_DOCUMENT_TYPE, // - @Meta(description = "How to save images documents in the library",// - format = Format.FIXED_LIST, list = { "CBZ", "HTML" }, def = "CBZ") - FILE_FORMAT_IMAGES_DOCUMENT_TYPE, // - @Meta(description = "How to save cover images",// - format = Format.FIXED_LIST, list = { "PNG", "JPG", "BMP" }, def = "PNG") - FILE_FORMAT_IMAGE_FORMAT_COVER, // - @Meta(description = "How to save content images",// - format = Format.FIXED_LIST, list = { "PNG", "JPG", "BMP" }, def = "JPG") - FILE_FORMAT_IMAGE_FORMAT_CONTENT, // - - @Meta(description = "Cache management",// - group = true) - CACHE, // - @Meta(description = "The directory where to store temporary files; any relative path uses the applciation config directory as base, $HOME notation is supported, / is always accepted as directory separator",// - format = Format.DIRECTORY, def = "tmp/") - CACHE_DIR, // - @Meta(description = "The delay in hours after which a cached resource that is thought to change ~often is considered too old and triggers a refresh delay (or 0 for no cache, or -1 for infinite time)", // - format = Format.INT, def = "24") - CACHE_MAX_TIME_CHANGING, // - @Meta(description = "The delay in hours after which a cached resource that is thought to change rarely is considered too old and triggers a refresh delay (or 0 for no cache, or -1 for infinite time)", // - format = Format.INT, def = "720") - CACHE_MAX_TIME_STABLE, // - - @Meta(description = "The directory where to get the default story covers; any relative path uses the applciation config directory as base, $HOME notation is supported, / is always accepted as directory separator",// - format = Format.DIRECTORY, def = "covers/") - DEFAULT_COVERS_DIR, // - @Meta(description = "The directory where to store the library (can be overriden by the envvironment variable \"BOOKS_DIR\"; any relative path uses the applciation config directory as base, $HOME notation is supported, / is always accepted as directory separator",// - format = Format.DIRECTORY, def = "$HOME/Books/") - LIBRARY_DIR, // - - @Meta(description = "Remote library\nA remote library can be configured to fetch the stories from a remote Fanfix server",// - group = true) - REMOTE_LIBRARY, // - @Meta(description = "Use the remote Fanfix server configured here instead of the local library (if FALSE, the local library will be used instead)",// - format = Format.BOOLEAN, def = "false") - REMOTE_LIBRARY_ENABLED, // - @Meta(description = "The remote Fanfix server to connect to",// - format = Format.STRING) - REMOTE_LIBRARY_HOST, // - @Meta(description = "The port to use for the remote Fanfix server",// - format = Format.INT, def = "58365") - REMOTE_LIBRARY_PORT, // - @Meta(description = "The key is structured: \"KEY|SUBKEY|wl|rw\"\n- \"KEY\" is the actual encryption key (it can actually be empty, which will still encrypt the messages but of course it will be easier to guess the key)\n- \"SUBKEY\" is the (optional) subkey to use to get additional privileges\n- \"wl\" is a special privilege that allows that subkey to ignore white lists\n- \"rw\" is a special privilege that allows that subkey to modify the library, even if it is not in RW (by default) mode\n\nSome examples:\n- \"super-secret\": a normal key, no special privileges\n- \"you-will-not-guess|azOpd8|wl\": a white-list ignoring key\n- \"new-password|subpass|rw\": a key that allows modifications on the library",// - format = Format.PASSWORD) - REMOTE_LIBRARY_KEY, // - - @Meta(description = "Network configuration",// - group = true) - NETWORK, // - @Meta(description = "The user-agent to use to download files",// - def = "Mozilla/5.0 (X11; Linux x86_64; rv:44.0) Gecko/20100101 Firefox/44.0 -- ELinks/0.9.3 (Linux 2.6.11 i686; 80x24) -- Fanfix (https://github.com/nikiroo/fanfix/)") - NETWORK_USER_AGENT, // - @Meta(description = "The proxy server to use under the format 'user:pass@proxy:port', 'user@proxy:port', 'proxy:port' or ':' alone (system proxy); an empty String means no proxy",// - format = Format.STRING, def = "") - NETWORK_PROXY, // - @Meta(description = "If the last update check was done at least that many days ago, check for updates at startup (-1 for 'no checks')", // - format = Format.INT, def = "1") - NETWORK_UPDATE_INTERVAL, // - - @Meta(description = "Remote Server configuration\nNote that the key is structured: \"KEY|SUBKEY|wl|rw\"\n- \"KEY\" is the actual encryption key (it can actually be empty, which will still encrypt the messages but of course it will be easier to guess the key)\n- \"SUBKEY\" is the (optional) subkey to use to get additional privileges\n- \"wl\" is a special privilege that allows that subkey to ignore white lists\n- \"rw\" is a special privilege that allows that subkey to modify the library, even if it is not in RW (by default) mode\n\nSome examples:\n- \"super-secret\": a normal key, no special privileges\n- \"you-will-not-guess|azOpd8|wl\": a white-list ignoring key\n- \"new-password|subpass|rw\": a key that allows modifications on the library",// - group = true) - SERVER, // - @Meta(description = "The port on which we can start the server (must be a valid port, from 1 to 65535)", // - format = Format.INT, def = "58365") - SERVER_PORT, // - @Meta(description = "The encryption key for the server (NOT including a subkey), it cannot contain the pipe character \"|\" but can be empty (it is *still* encrypted, but with an empty, easy to guess key)",// - format = Format.PASSWORD, def = "") - SERVER_KEY, // - @Meta(description = "Allow write access to the clients (download story, move story...) without RW subkeys", // - format = Format.BOOLEAN, def = "true") - SERVER_RW, // - @Meta(description = "If not empty, only the EXACT listed sources will be available for clients without BL subkeys",// - array = true, format = Format.STRING, def = "") - SERVER_WHITELIST, // - @Meta(description = "The subkeys that the server will allow, including the modes\nA subkey ", // - array = true, format = Format.STRING, def = "") - SERVER_ALLOWED_SUBKEYS, // - - @Meta(description = "DEBUG options",// - group = true) - DEBUG, // - @Meta(description = "Show debug information on errors",// - format = Format.BOOLEAN, def = "false") - DEBUG_ERR, // - @Meta(description = "Show debug trace information",// - format = Format.BOOLEAN, def = "false") - DEBUG_TRACE, // - - @Meta(description = "Internal configuration\nThose options are internal to the program and should probably not be changed",// - group = true) - CONF, // - @Meta(description = "LaTeX configuration",// - group = true) - CONF_LATEX_LANG, // - @Meta(description = "LaTeX output language (full name) for \"English\"",// - format = Format.STRING, def = "english") - CONF_LATEX_LANG_EN, // - @Meta(description = "LaTeX output language (full name) for \"French\"",// - format = Format.STRING, def = "french") - CONF_LATEX_LANG_FR, // - @Meta(description = "other 'by' prefixes before author name, used to identify the author",// - array = true, format = Format.STRING, def = "\"by\",\"par\",\"de\",\"©\",\"(c)\"") - CONF_BYS, // - @Meta(description = "List of languages codes used for chapter identification (should not be changed)", // - array = true, format = Format.STRING, def = "\"EN\",\"FR\"") - CONF_CHAPTER, // - @Meta(description = "Chapter identification string in English, used to identify a starting chapter in text mode",// - format = Format.STRING, def = "Chapter") - CONF_CHAPTER_EN, // - @Meta(description = "Chapter identification string in French, used to identify a starting chapter in text mode",// - format = Format.STRING, def = "Chapitre") - CONF_CHAPTER_FR, // - - @Meta(description = "YiffStar/SoFurry credentials\nYou can give your YiffStar credentials here to have access to all the stories, though it should not be necessary anymore (some stories used to beblocked for anonymous viewers)",// - group = true) - LOGIN_YIFFSTAR, // - @Meta(description = "Your YiffStar/SoFurry login",// - format = Format.STRING) - LOGIN_YIFFSTAR_USER, // - @Meta(description = "Your YiffStar/SoFurry password",// - format = Format.PASSWORD) - LOGIN_YIFFSTAR_PASS, // - - @Meta(description = "FimFiction APIKEY credentials\nFimFiction can be queried via an API, but requires an API key to do that. One has been created for this program, but if you have another API key you can set it here. You can also set a login and password instead, in that case, a new API key will be generated (and stored) if you still haven't set one.",// - group = true) - LOGIN_FIMFICTION_APIKEY, // - @Meta(description = "The login of the API key used to create a new token from FimFiction", // - format = Format.STRING) - LOGIN_FIMFICTION_APIKEY_CLIENT_ID, // - @Meta(description = "The password of the API key used to create a new token from FimFiction", // - format = Format.PASSWORD) - LOGIN_FIMFICTION_APIKEY_CLIENT_SECRET, // - @Meta(description = "Do not use the new API, even if we have a token, and force HTML scraping",// - format = Format.BOOLEAN, def = "false") - LOGIN_FIMFICTION_APIKEY_FORCE_HTML, // - @Meta(description = "The token required to use the beta APIv2 from FimFiction (see APIKEY_CLIENT_* if you want to generate a new one from your own API key)", // - format = Format.PASSWORD, def = "Bearer WnZ5oHlzQoDocv1GcgHfcoqctHkSwL-D") - LOGIN_FIMFICTION_APIKEY_TOKEN, // -} diff --git a/src/be/nikiroo/fanfix/bundles/ConfigBundle.java b/src/be/nikiroo/fanfix/bundles/ConfigBundle.java deleted file mode 100644 index ce72b3da..00000000 --- a/src/be/nikiroo/fanfix/bundles/ConfigBundle.java +++ /dev/null @@ -1,41 +0,0 @@ -package be.nikiroo.fanfix.bundles; - -import java.io.File; -import java.io.IOException; - -import be.nikiroo.utils.resources.Bundle; - -/** - * This class manages the configuration of the application. - * - * @author niki - */ -public class ConfigBundle extends Bundle { - /** - * Create a new {@link ConfigBundle}. - */ - public ConfigBundle() { - super(Config.class, Target.config5, null); - } - - /** - * Update resource file. - * - * @param args - * not used - * - * @throws IOException - * in case of I/O error - */ - public static void main(String[] args) throws IOException { - String path = new File(".").getAbsolutePath() - + "/src/be/nikiroo/fanfix/bundles/"; - new ConfigBundle().updateFile(path); - System.out.println("Path updated: " + path); - } - - @Override - protected String getBundleDisplayName() { - return "Configuration options"; - } -} diff --git a/src/be/nikiroo/fanfix/bundles/StringId.java b/src/be/nikiroo/fanfix/bundles/StringId.java deleted file mode 100644 index 97722484..00000000 --- a/src/be/nikiroo/fanfix/bundles/StringId.java +++ /dev/null @@ -1,151 +0,0 @@ -package be.nikiroo.fanfix.bundles; - -import java.io.IOException; -import java.io.Writer; - -import be.nikiroo.utils.resources.Bundle; -import be.nikiroo.utils.resources.Meta; - -/** - * The {@link Enum} representing textual information to be translated to the - * user as a key. - * - * Note that each key that should be translated must be annotated with a - * {@link Meta} annotation. - * - * @author niki - */ -@SuppressWarnings("javadoc") -public enum StringId { - /** - * A special key used for technical reasons only, without annotations so it - * is not visible in .properties files. - *

- * Use it when you need NO translation. - */ - NULL, // - /** - * A special key used for technical reasons only, without annotations so it - * is not visible in .properties files. - *

- * Use it when you need a real translation but still don't have a key. - */ - DUMMY, // - @Meta(info = "%s = supported input, %s = supported output", description = "help message for the syntax") - HELP_SYNTAX, // - @Meta(description = "syntax error message") - ERR_SYNTAX, // - @Meta(info = "%s = support name, %s = support desc", description = "an input or output support type description") - ERR_SYNTAX_TYPE, // - @Meta(info = "%s = input string", description = "Error when retrieving data") - ERR_LOADING, // - @Meta(info = "%s = save target", description = "Error when saving to given target") - ERR_SAVING, // - @Meta(info = "%s = bad output format", description = "Error when unknown output format") - ERR_BAD_OUTPUT_TYPE, // - @Meta(info = "%s = input string", description = "Error when converting input to URL/File") - ERR_BAD_URL, // - @Meta(info = "%s = input url", description = "URL/File not supported") - ERR_NOT_SUPPORTED, // - @Meta(info = "%s = cover URL", description = "Failed to download cover : %s") - ERR_BS_NO_COVER, // - @Meta(def = "`", info = "single char", description = "Canonical OPEN SINGLE QUOTE char (for instance: ‘)") - OPEN_SINGLE_QUOTE, // - @Meta(def = "‘", info = "single char", description = "Canonical CLOSE SINGLE QUOTE char (for instance: ’)") - CLOSE_SINGLE_QUOTE, // - @Meta(def = "“", info = "single char", description = "Canonical OPEN DOUBLE QUOTE char (for instance: “)") - OPEN_DOUBLE_QUOTE, // - @Meta(def = "”", info = "single char", description = "Canonical CLOSE DOUBLE QUOTE char (for instance: ”)") - CLOSE_DOUBLE_QUOTE, // - @Meta(def = "Description", description = "Name of the description fake chapter") - DESCRIPTION, // - @Meta(def = "Chapter %d: %s", info = "%d = number, %s = name", description = "Name of a chapter with a name") - CHAPTER_NAMED, // - @Meta(def = "Chapter %d", info = "%d = number, %s = name", description = "Name of a chapter without name") - CHAPTER_UNNAMED, // - @Meta(info = "%s = type", description = "Default description when the type is not known by i18n") - INPUT_DESC, // - @Meta(description = "Description of this input type") - INPUT_DESC_EPUB, // - @Meta(description = "Description of this input type") - INPUT_DESC_TEXT, // - @Meta(description = "Description of this input type") - INPUT_DESC_INFO_TEXT, // - @Meta(description = "Description of this input type") - INPUT_DESC_FANFICTION, // - @Meta(description = "Description of this input type") - INPUT_DESC_FIMFICTION, // - @Meta(description = "Description of this input type") - INPUT_DESC_MANGAFOX, // - @Meta(description = "Description of this input type") - INPUT_DESC_E621, // - @Meta(description = "Description of this input type") - INPUT_DESC_E_HENTAI, // - @Meta(description = "Description of this input type") - INPUT_DESC_YIFFSTAR, // - @Meta(description = "Description of this input type") - INPUT_DESC_CBZ, // - @Meta(description = "Description of this input type") - INPUT_DESC_HTML, // - @Meta(info = "%s = type", description = "Default description when the type is not known by i18n") - OUTPUT_DESC, // - @Meta(description = "Description of this output type") - OUTPUT_DESC_EPUB, // - @Meta(description = "Description of this output type") - OUTPUT_DESC_TEXT, // - @Meta(description = "Description of this output type") - OUTPUT_DESC_INFO_TEXT, // - @Meta(description = "Description of this output type") - OUTPUT_DESC_CBZ, // - @Meta(description = "Description of this output type") - OUTPUT_DESC_HTML, // - @Meta(description = "Description of this output type") - OUTPUT_DESC_LATEX, // - @Meta(description = "Description of this output type") - OUTPUT_DESC_SYSOUT, // - @Meta(group = true, info = "%s = type", description = "Default description when the type is not known by i18n") - OUTPUT_DESC_SHORT, // - @Meta(description = "Short description of this output type") - OUTPUT_DESC_SHORT_EPUB, // - @Meta(description = "Short description of this output type") - OUTPUT_DESC_SHORT_TEXT, // - @Meta(description = "Short description of this output type") - OUTPUT_DESC_SHORT_INFO_TEXT, // - @Meta(description = "Short description of this output type") - OUTPUT_DESC_SHORT_CBZ, // - @Meta(description = "Short description of this output type") - OUTPUT_DESC_SHORT_LATEX, // - @Meta(description = "Short description of this output type") - OUTPUT_DESC_SHORT_SYSOUT, // - @Meta(description = "Short description of this output type") - OUTPUT_DESC_SHORT_HTML, // - @Meta(info = "%s = the unknown 2-code language", description = "Error message for unknown 2-letter LaTeX language code") - LATEX_LANG_UNKNOWN, // - @Meta(def = "by", description = "'by' prefix before author name used to output the author, make sure it is covered by Config.BYS for input detection") - BY, // - - ; - - /** - * Write the header found in the configuration .properties file of - * this {@link Bundle}. - * - * @param writer - * the {@link Writer} to write the header in - * @param name - * the file name - * - * @throws IOException - * in case of IO error - */ - static public void writeHeader(Writer writer, String name) - throws IOException { - writer.write("# " + name + " translation file (UTF-8)\n"); - writer.write("# \n"); - writer.write("# Note that any key can be doubled with a _NOUTF suffix\n"); - writer.write("# to use when the NOUTF env variable is set to 1\n"); - writer.write("# \n"); - writer.write("# Also, the comments always refer to the key below them.\n"); - writer.write("# \n"); - } -} diff --git a/src/be/nikiroo/fanfix/bundles/StringIdBundle.java b/src/be/nikiroo/fanfix/bundles/StringIdBundle.java deleted file mode 100644 index b9a0d797..00000000 --- a/src/be/nikiroo/fanfix/bundles/StringIdBundle.java +++ /dev/null @@ -1,40 +0,0 @@ -package be.nikiroo.fanfix.bundles; - -import java.io.File; -import java.io.IOException; - -import be.nikiroo.utils.resources.TransBundle; - -/** - * This class manages the translation resources of the application (Core). - * - * @author niki - */ -public class StringIdBundle extends TransBundle { - /** - * Create a translation service for the given language (will fall back to - * the default one if not found). - * - * @param lang - * the language to use - */ - public StringIdBundle(String lang) { - super(StringId.class, Target.resources_core, lang); - } - - /** - * Update resource file. - * - * @param args - * not used - * - * @throws IOException - * in case of I/O error - */ - public static void main(String[] args) throws IOException { - String path = new File(".").getAbsolutePath() - + "/src/be/nikiroo/fanfix/bundles/"; - new StringIdBundle(null).updateFile(path); - System.out.println("Path updated: " + path); - } -} diff --git a/src/be/nikiroo/fanfix/bundles/StringIdGui.java b/src/be/nikiroo/fanfix/bundles/StringIdGui.java deleted file mode 100644 index 2c9d222a..00000000 --- a/src/be/nikiroo/fanfix/bundles/StringIdGui.java +++ /dev/null @@ -1,199 +0,0 @@ -package be.nikiroo.fanfix.bundles; - -import java.io.IOException; -import java.io.Writer; - -import be.nikiroo.utils.resources.Bundle; -import be.nikiroo.utils.resources.Meta; -import be.nikiroo.utils.resources.Meta.Format; - -/** - * The {@link Enum} representing textual information to be translated to the - * user as a key. - * - * Note that each key that should be translated must be annotated with a - * {@link Meta} annotation. - * - * @author niki - */ -@SuppressWarnings("javadoc") -public enum StringIdGui { - /** - * A special key used for technical reasons only, without annotations so it - * is not visible in .properties files. - *

- * Use it when you need NO translation. - */ - NULL, // - /** - * A special key used for technical reasons only, without annotations so it - * is not visible in .properties files. - *

- * Use it when you need a real translation but still don't have a key. - */ - DUMMY, // - @Meta(def = "Fanfix %s", format = Format.STRING, description = "the title of the main window of Fanfix, the library", info = "%s = current Fanfix version") - // The titles/subtitles: - TITLE_LIBRARY, // - @Meta(def = "Fanfix %s", format = Format.STRING, description = "the title of the main window of Fanfix, the library, when the library has a name (i.e., is not local)", info = "%s = current Fanfix version, %s = library name") - TITLE_LIBRARY_WITH_NAME, // - @Meta(def = "Fanfix Configuration", format = Format.STRING, description = "the title of the configuration window of Fanfix, also the name of the menu button") - TITLE_CONFIG, // - @Meta(def = "This is where you configure the options of the program.", format = Format.STRING, description = "the subtitle of the configuration window of Fanfix") - SUBTITLE_CONFIG, // - @Meta(def = "UI Configuration", format = Format.STRING, description = "the title of the UI configuration window of Fanfix, also the name of the menu button") - TITLE_CONFIG_UI, // - @Meta(def = "This is where you configure the graphical appearence of the program.", format = Format.STRING, description = "the subtitle of the UI configuration window of Fanfix") - SUBTITLE_CONFIG_UI, // - @Meta(def = "Save", format = Format.STRING, description = "the title of the 'save to/export to' window of Fanfix") - TITLE_SAVE, // - @Meta(def = "Moving story", format = Format.STRING, description = "the title of the 'move to' window of Fanfix") - TITLE_MOVE_TO, // - @Meta(def = "Move to:", format = Format.STRING, description = "the subtitle of the 'move to' window of Fanfix") - SUBTITLE_MOVE_TO, // - @Meta(def = "Delete story", format = Format.STRING, description = "the title of the 'delete' window of Fanfix") - TITLE_DELETE, // - @Meta(def = "Delete %s: %s", format = Format.STRING, description = "the subtitle of the 'delete' window of Fanfix", info = "%s = LUID of the story, %s = title of the story") - SUBTITLE_DELETE, // - @Meta(def = "Library error", format = Format.STRING, description = "the title of the 'library error' dialogue") - TITLE_ERROR_LIBRARY, // - @Meta(def = "Importing from URL", format = Format.STRING, description = "the title of the 'import URL' dialogue") - TITLE_IMPORT_URL, // - @Meta(def = "URL of the story to import:", format = Format.STRING, description = "the subtitle of the 'import URL' dialogue") - SUBTITLE_IMPORT_URL, // - @Meta(def = "Error", format = Format.STRING, description = "the title of general error dialogues") - TITLE_ERROR, // - @Meta(def = "%s: %s", format = Format.STRING, description = "the title of a story for the properties dialogue, the viewers...", info = "%s = LUID of the story, %s = title of the story") - TITLE_STORY, // - - // - - @Meta(def = "A new version of the program is available at %s", format = Format.STRING, description = "HTML text used to notify of a new version", info = "%s = url link in HTML") - NEW_VERSION_AVAILABLE, // - @Meta(def = "Updates available", format = Format.STRING, description = "text used as title for the update dialogue") - NEW_VERSION_TITLE, // - @Meta(def = "Version %s", format = Format.STRING, description = "HTML text used to specify a newer version title and number, used for each version newer than the current one", info = "%s = the newer version number") - NEW_VERSION_VERSION, // - @Meta(def = "%s words", format = Format.STRING, description = "show the number of words of a book", info = "%s = the number") - BOOK_COUNT_WORDS, // - @Meta(def = "%s images", format = Format.STRING, description = "show the number of images of a book", info = "%s = the number") - BOOK_COUNT_IMAGES, // - @Meta(def = "%s stories", format = Format.STRING, description = "show the number of stories of a meta-book (a book representing allthe types/sources or all the authors present)", info = "%s = the number") - BOOK_COUNT_STORIES, // - - // Menu (and popup) items: - - @Meta(def = "File", format = Format.STRING, description = "the file menu") - MENU_FILE, // - @Meta(def = "Exit", format = Format.STRING, description = "the file/exit menu button") - MENU_FILE_EXIT, // - @Meta(def = "Import File...", format = Format.STRING, description = "the file/import_file menu button") - MENU_FILE_IMPORT_FILE, // - @Meta(def = "Import URL...", format = Format.STRING, description = "the file/import_url menu button") - MENU_FILE_IMPORT_URL, // - @Meta(def = "Save as...", format = Format.STRING, description = "the file/export menu button") - MENU_FILE_EXPORT, // - @Meta(def = "Move to", format = Format.STRING, description = "the file/move to menu button") - MENU_FILE_MOVE_TO, // - @Meta(def = "Set author", format = Format.STRING, description = "the file/set author menu button") - MENU_FILE_SET_AUTHOR, // - @Meta(def = "New source...", format = Format.STRING, description = "the file/move to/new type-source menu button, that will trigger a dialogue to create a new type/source") - MENU_FILE_MOVE_TO_NEW_TYPE, // - @Meta(def = "New author...", format = Format.STRING, description = "the file/move to/new author menu button, that will trigger a dialogue to create a new author") - MENU_FILE_MOVE_TO_NEW_AUTHOR, // - @Meta(def = "Rename...", format = Format.STRING, description = "the file/rename menu item, that will trigger a dialogue to ask for a new title for the story") - MENU_FILE_RENAME, // - @Meta(def = "Properties", format = Format.STRING, description = "the file/Properties menu item, that will trigger a dialogue to show the properties of the story") - MENU_FILE_PROPERTIES, // - @Meta(def = "Open", format = Format.STRING, description = "the file/open menu item, that will open the story or fake-story (an author or a source/type)") - MENU_FILE_OPEN, // - @Meta(def = "Edit", format = Format.STRING, description = "the edit menu") - MENU_EDIT, // - @Meta(def = "Download to cache", format = Format.STRING, description = "the edit/send to cache menu button, to download the story into the cache if not already done") - MENU_EDIT_DOWNLOAD_TO_CACHE, // - @Meta(def = "Clear cache", format = Format.STRING, description = "the clear cache menu button, to clear the cache for a single book") - MENU_EDIT_CLEAR_CACHE, // - @Meta(def = "Redownload", format = Format.STRING, description = "the edit/redownload menu button, to download the latest version of the book") - MENU_EDIT_REDOWNLOAD, // - @Meta(def = "Delete", format = Format.STRING, description = "the edit/delete menu button") - MENU_EDIT_DELETE, // - @Meta(def = "Set as cover for source", format = Format.STRING, description = "the edit/Set as cover for source menu button") - MENU_EDIT_SET_COVER_FOR_SOURCE, // - @Meta(def = "Set as cover for author", format = Format.STRING, description = "the edit/Set as cover for author menu button") - MENU_EDIT_SET_COVER_FOR_AUTHOR, // - @Meta(def = "Search", format = Format.STRING, description = "the search menu to open the earch stories on one of the searchable websites") - MENU_SEARCH, - @Meta(def = "View", format = Format.STRING, description = "the view menu") - MENU_VIEW, // - @Meta(def = "Word count", format = Format.STRING, description = "the view/word_count menu button, to show the word/image/story count as secondary info") - MENU_VIEW_WCOUNT, // - @Meta(def = "Author", format = Format.STRING, description = "the view/author menu button, to show the author as secondary info") - MENU_VIEW_AUTHOR, // - @Meta(def = "Sources", format = Format.STRING, description = "the sources menu, to select the books from a specific source; also used as a title for the source books") - MENU_SOURCES, // - @Meta(def = "Authors", format = Format.STRING, description = "the authors menu, to select the books of a specific author; also used as a title for the author books") - MENU_AUTHORS, // - @Meta(def = "Options", format = Format.STRING, description = "the options menu, to configure Fanfix from the GUI") - MENU_OPTIONS, // - @Meta(def = "All", format = Format.STRING, description = "a special menu button to select all the sources/types or authors, by group (one book = one group)") - MENU_XXX_ALL_GROUPED, // - @Meta(def = "Listing", format = Format.STRING, description = "a special menu button to select all the sources/types or authors, in a listing (all the included books are listed, grouped by source/type or author)") - MENU_XXX_ALL_LISTING, // - @Meta(def = "[unknown]", format = Format.STRING, description = "a special menu button to select the books without author") - MENU_AUTHORS_UNKNOWN, // - - // Progress names - @Meta(def = "Reload books", format = Format.STRING, description = "progress bar caption for the 'reload books' step of all outOfUi operations") - PROGRESS_OUT_OF_UI_RELOAD_BOOKS, // - @Meta(def = "Change the source of the book to %s", format = Format.STRING, description = "progress bar caption for the 'change source' step of the ReDownload operation", info = "%s = new source name") - PROGRESS_CHANGE_SOURCE, // - - // Error messages - @Meta(def = "An error occured when contacting the library", format = Format.STRING, description = "default description if the error is not known") - ERROR_LIB_STATUS, // - @Meta(def = "You are not allowed to access this library", format = Format.STRING, description = "library access not allowed") - ERROR_LIB_STATUS_UNAUTHORIZED, // - @Meta(def = "Library not valid", format = Format.STRING, description = "the library is invalid (not correctly set up)") - ERROR_LIB_STATUS_INVALID, // - @Meta(def = "Library currently unavailable", format = Format.STRING, description = "the library is out of commission") - ERROR_LIB_STATUS_UNAVAILABLE, // - @Meta(def = "Cannot open the selected book", format = Format.STRING, description = "cannot open the book, internal or external viewer") - ERROR_CANNOT_OPEN, // - @Meta(def = "URL not supported: %s", format = Format.STRING, description = "URL is not supported by Fanfix", info = "%s = URL") - ERROR_URL_NOT_SUPPORTED, // - @Meta(def = "Failed to import %s:\n%s", format = Format.STRING, description = "cannot import the URL", info = "%s = URL, %s = reasons") - ERROR_URL_IMPORT_FAILED, - - // Others - @Meta(def = "  Chapitre %d / %d", format = Format.STRING, description = "(html) the chapter progression value used on the viewers", info = "%d = chapter number, %d = total chapters") - CHAPTER_HTML_UNNAMED, // - @Meta(def = "  Chapitre %d / %d: %s", format = Format.STRING, description = "(html) the chapter progression value used on the viewers", info = "%d = chapter number, %d = total chapters, %s = chapter name") - CHAPTER_HTML_NAMED, // - @Meta(def = "Image %d / %d", format = Format.STRING, description = "(NO html) the chapter progression value used on the viewers", info = "%d = current image number, %d = total images") - IMAGE_PROGRESSION, // - - ; - - /** - * Write the header found in the configuration .properties file of - * this {@link Bundle}. - * - * @param writer - * the {@link Writer} to write the header in - * @param name - * the file name - * - * @throws IOException - * in case of IO error - */ - static public void writeHeader(Writer writer, String name) - throws IOException { - writer.write("# " + name + " translation file (UTF-8)\n"); - writer.write("# \n"); - writer.write("# Note that any key can be doubled with a _NOUTF suffix\n"); - writer.write("# to use when the NOUTF env variable is set to 1\n"); - writer.write("# \n"); - writer.write("# Also, the comments always refer to the key below them.\n"); - writer.write("# \n"); - } -} diff --git a/src/be/nikiroo/fanfix/bundles/StringIdGuiBundle.java b/src/be/nikiroo/fanfix/bundles/StringIdGuiBundle.java deleted file mode 100644 index c036381b..00000000 --- a/src/be/nikiroo/fanfix/bundles/StringIdGuiBundle.java +++ /dev/null @@ -1,40 +0,0 @@ -package be.nikiroo.fanfix.bundles; - -import java.io.File; -import java.io.IOException; - -import be.nikiroo.utils.resources.TransBundle; - -/** - * This class manages the translation resources of the application (GUI). - * - * @author niki - */ -public class StringIdGuiBundle extends TransBundle { - /** - * Create a translation service for the given language (will fall back to - * the default one if not found). - * - * @param lang - * the language to use - */ - public StringIdGuiBundle(String lang) { - super(StringIdGui.class, Target.resources_gui, lang); - } - - /** - * Update resource file. - * - * @param args - * not used - * - * @throws IOException - * in case of I/O error - */ - public static void main(String[] args) throws IOException { - String path = new File(".").getAbsolutePath() - + "/src/be/nikiroo/fanfix/bundles/"; - new StringIdGuiBundle(null).updateFile(path); - System.out.println("Path updated: " + path); - } -} diff --git a/src/be/nikiroo/fanfix/bundles/Target.java b/src/be/nikiroo/fanfix/bundles/Target.java deleted file mode 100644 index 64284c6e..00000000 --- a/src/be/nikiroo/fanfix/bundles/Target.java +++ /dev/null @@ -1,27 +0,0 @@ -package be.nikiroo.fanfix.bundles; - -import be.nikiroo.utils.resources.Bundle; - -/** - * The type of configuration information the associated {@link Bundle} will - * convey. - *

- * Those values can change when the file is not compatible anymore. - * - * @author niki - */ -public enum Target { - /** - * Configuration options that the user can change in the - * .properties file - */ - config5, - /** Translation resources (Core) */ - resources_core, - /** Translation resources (GUI) */ - resources_gui, - /** UI resources (from colours to behaviour) */ - ui, - /** Description of UI resources. */ - ui_description, -} diff --git a/src/be/nikiroo/fanfix/bundles/UiConfig.java b/src/be/nikiroo/fanfix/bundles/UiConfig.java deleted file mode 100644 index 0640db83..00000000 --- a/src/be/nikiroo/fanfix/bundles/UiConfig.java +++ /dev/null @@ -1,37 +0,0 @@ -package be.nikiroo.fanfix.bundles; - -import be.nikiroo.utils.resources.Meta; -import be.nikiroo.utils.resources.Meta.Format; - -/** - * The configuration options. - * - * @author niki - */ -@SuppressWarnings("javadoc") -public enum UiConfig { - @Meta(description = "The directory where to store temporary files for the GUI reader; any relative path uses the applciation config directory as base, $HOME notation is supported, / is always accepted as directory separator",// - format = Format.DIRECTORY, def = "tmp-reader/") - CACHE_DIR_LOCAL_READER, // - @Meta(description = "How to save the cached stories for the GUI Reader (non-images documents) -- those files will be sent to the reader",// - format = Format.COMBO_LIST, list = { "INFO_TEXT", "EPUB", "HTML", "TEXT" }, def = "EPUB") - GUI_NON_IMAGES_DOCUMENT_TYPE, // - @Meta(description = "How to save the cached stories for the GUI Reader (images documents) -- those files will be sent to the reader",// - format = Format.COMBO_LIST, list = { "CBZ", "HTML" }, def = "CBZ") - GUI_IMAGES_DOCUMENT_TYPE, // - @Meta(description = "Use the internal reader for images documents",// - format = Format.BOOLEAN, def = "true") - IMAGES_DOCUMENT_USE_INTERNAL_READER, // - @Meta(description = "The external viewer for images documents (or empty to use the system default program for the given file type)",// - format = Format.STRING) - IMAGES_DOCUMENT_READER, // - @Meta(description = "Use the internal reader for non-images documents",// - format = Format.BOOLEAN, def = "true") - NON_IMAGES_DOCUMENT_USE_INTERNAL_READER, // - @Meta(description = "The external viewer for non-images documents (or empty to use the system default program for the given file type)",// - format = Format.STRING) - NON_IMAGES_DOCUMENT_READER, // - @Meta(description = "The background colour of the library if you don't like the default system one",// - format = Format.COLOR) - BACKGROUND_COLOR, // -} diff --git a/src/be/nikiroo/fanfix/bundles/UiConfigBundle.java b/src/be/nikiroo/fanfix/bundles/UiConfigBundle.java deleted file mode 100644 index 8b2c008c..00000000 --- a/src/be/nikiroo/fanfix/bundles/UiConfigBundle.java +++ /dev/null @@ -1,39 +0,0 @@ -package be.nikiroo.fanfix.bundles; - -import java.io.File; -import java.io.IOException; - -import be.nikiroo.utils.resources.Bundle; - -/** - * This class manages the configuration of UI of the application (colours and - * behaviour) - * - * @author niki - */ -public class UiConfigBundle extends Bundle { - public UiConfigBundle() { - super(UiConfig.class, Target.ui, new UiConfigBundleDesc()); - } - - /** - * Update resource file. - * - * @param args - * not used - * - * @throws IOException - * in case of I/O error - */ - public static void main(String[] args) throws IOException { - String path = new File(".").getAbsolutePath() - + "/src/be/nikiroo/fanfix/bundles/"; - new UiConfigBundle().updateFile(path); - System.out.println("Path updated: " + path); - } - - @Override - protected String getBundleDisplayName() { - return "UI configuration options"; - } -} diff --git a/src/be/nikiroo/fanfix/bundles/UiConfigBundleDesc.java b/src/be/nikiroo/fanfix/bundles/UiConfigBundleDesc.java deleted file mode 100644 index da429503..00000000 --- a/src/be/nikiroo/fanfix/bundles/UiConfigBundleDesc.java +++ /dev/null @@ -1,39 +0,0 @@ -package be.nikiroo.fanfix.bundles; - -import java.io.File; -import java.io.IOException; - -import be.nikiroo.utils.resources.TransBundle; - -/** - * This class manages the configuration of UI of the application (colours and - * behaviour) - * - * @author niki - */ -public class UiConfigBundleDesc extends TransBundle { - public UiConfigBundleDesc() { - super(UiConfig.class, Target.ui_description); - } - - /** - * Update resource file. - * - * @param args - * not used - * - * @throws IOException - * in case of I/O error - */ - public static void main(String[] args) throws IOException { - String path = new File(".").getAbsolutePath() - + "/src/be/nikiroo/fanfix/bundles/"; - new UiConfigBundleDesc().updateFile(path); - System.out.println("Path updated: " + path); - } - - @Override - protected String getBundleDisplayName() { - return "UI configuration options description"; - } -} diff --git a/src/be/nikiroo/fanfix/bundles/package-info.java b/src/be/nikiroo/fanfix/bundles/package-info.java deleted file mode 100644 index 80cdd15f..00000000 --- a/src/be/nikiroo/fanfix/bundles/package-info.java +++ /dev/null @@ -1,8 +0,0 @@ -/** - * This package encloses the different - * {@link be.nikiroo.utils.resources.Bundle} and their associated - * {@link java.lang.Enum}s used by the application. - * - * @author niki - */ -package be.nikiroo.fanfix.bundles; \ No newline at end of file diff --git a/src/be/nikiroo/fanfix/bundles/resources_core.properties b/src/be/nikiroo/fanfix/bundles/resources_core.properties deleted file mode 100644 index dc7881ad..00000000 --- a/src/be/nikiroo/fanfix/bundles/resources_core.properties +++ /dev/null @@ -1,207 +0,0 @@ -# United Kingdom (en_GB) resources_core translation file (UTF-8) -# -# Note that any key can be doubled with a _NOUTF suffix -# to use when the NOUTF env variable is set to 1 -# -# Also, the comments always refer to the key below them. -# - - -# help message for the syntax -# (FORMAT: STRING) -HELP_SYNTAX = Valid options:\n\ -\t--import [URL]: import into library\n\ -\t--export [id] [output_type] [target]: export story to target\n\ -\t--convert [URL] [output_type] [target] (+info): convert URL into target\n\ -\t--read [id] ([chapter number]): read the given story from the library\n\ -\t--read-url [URL] ([chapter number]): convert on the fly and read the \n\ -\t\tstory, without saving it\n\ -\t--search WEBSITE [free text] ([page] ([item])): search for the given terms, show the\n\ -\t\tgiven page (page 0 means "how many page do we have", starts at page 1)\n\ -\t--search-tag WEBSITE ([tag 1] [tag2...] ([page] ([item]))): list the known tags or \n\ -\t\tsearch the stories for the given tag(s), show the given page of results\n\ -\t--search: list the supported websites (where)\n\ -\t--search [where] [keywords] (page [page]) (item [item]): search on the supported \n\ -\t\twebsite and display the given results page of stories it found, or the story \n\ -\t\tdetails if asked\n\ -\t--search-tag [where]: list all the tags supported by this website\n\ -\t--search-tag [index 1]... (page [page]) (item [item]): search for the given stories or \n\ -\t\tsubtags, tag by tag, and display information about a specific page of results or \n\ -\t\tabout a specific item if requested\n\ -\t--list ([type]) : list the stories present in the library\n\ -\t--set-source [id] [new source]: change the source of the given story\n\ -\t--set-title [id] [new title]: change the title of the given story\n\ -\t--set-author [id] [new author]: change the author of the given story\n\ -\t--set-reader [reader type]: set the reader type to CLI, TUI or GUI for \n\ -\t\tthis command\n\ -\t--server: start the server mode (see config file for parameters)\n\ -\t--stop-server: stop the remote server running on this port\n\ -\t\tif any (key must be set to the same value)\n\ -\t--remote [key] [host] [port]: select this remote server to get \n\ -\t\t(or update or...) the stories from (key must be set to the \n\ -\t\tsame value)\n\ -\t--help: this help message\n\ -\t--version: return the version of the program\n\ -\n\ -Supported input types:\n\ -%s\n\ -\n\ -Supported output types:\n\ -%s -# syntax error message -# (FORMAT: STRING) -ERR_SYNTAX = Syntax error (try "--help") -# an input or output support type description -# (FORMAT: STRING) -ERR_SYNTAX_TYPE = > %s: %s -# Error when retrieving data -# (FORMAT: STRING) -ERR_LOADING = Error when retrieving data from: %s -# Error when saving to given target -# (FORMAT: STRING) -ERR_SAVING = Error when saving to target: %s -# Error when unknown output format -# (FORMAT: STRING) -ERR_BAD_OUTPUT_TYPE = Unknown output type: %s -# Error when converting input to URL/File -# (FORMAT: STRING) -ERR_BAD_URL = Cannot understand file or protocol: %s -# URL/File not supported -# (FORMAT: STRING) -ERR_NOT_SUPPORTED = URL not supported: %s -# Failed to download cover : %s -# (FORMAT: STRING) -ERR_BS_NO_COVER = Failed to download cover: %s -# Canonical OPEN SINGLE QUOTE char (for instance: ‘) -# (FORMAT: STRING) -OPEN_SINGLE_QUOTE = ‘ -# Canonical CLOSE SINGLE QUOTE char (for instance: ’) -# (FORMAT: STRING) -CLOSE_SINGLE_QUOTE = ’ -# Canonical OPEN DOUBLE QUOTE char (for instance: “) -# (FORMAT: STRING) -OPEN_DOUBLE_QUOTE = “ -# Canonical CLOSE DOUBLE QUOTE char (for instance: ”) -# (FORMAT: STRING) -CLOSE_DOUBLE_QUOTE = ” -# Name of the description fake chapter -# (FORMAT: STRING) -DESCRIPTION = Description -# Name of a chapter with a name -# (FORMAT: STRING) -CHAPTER_NAMED = Chapter %d: %s -# Name of a chapter without name -# (FORMAT: STRING) -CHAPTER_UNNAMED = Chapter %d -# Default description when the type is not known by i18n -# (FORMAT: STRING) -INPUT_DESC = Unknown type: %s -# Description of this input type -# (FORMAT: STRING) -INPUT_DESC_EPUB = EPUB files created by this program (we do not support "all" EPUB files) -# Description of this input type -# (FORMAT: STRING) -INPUT_DESC_TEXT = Stories encoded in textual format, with a few rules :\n\ -\tthe title must be on the first line, \n\ -\tthe author (preceded by nothing, "by " or "©") must be on the second \n\ -\t\tline, possibly with the publication date in parenthesis\n\ -\t\t(i.e., "By Unknown (3rd October 1998)"), \n\ -\tchapters must be declared with "Chapter x" or "Chapter x: NAME OF THE \n\ -\t\tCHAPTER", where "x" is the chapter number,\n\ -\ta description of the story must be given as chapter number 0,\n\ -\ta cover image may be present with the same filename but a PNG, \n\ -\t\tJPEG or JPG extension. -# Description of this input type -# (FORMAT: STRING) -INPUT_DESC_INFO_TEXT = Contains the same information as the TEXT format, but with a \n\ -\tcompanion ".info" file to store some metadata -# Description of this input type -# (FORMAT: STRING) -INPUT_DESC_FANFICTION = Fanfictions of many, many different universes, from TV shows to \n\ -\tnovels to games. -# Description of this input type -# (FORMAT: STRING) -INPUT_DESC_FIMFICTION = Fanfictions devoted to the My Little Pony show -# Description of this input type -# (FORMAT: STRING) -INPUT_DESC_MANGAHUB = A well filled repository of mangas, in English -# Description of this input type -# (FORMAT: STRING) -INPUT_DESC_E621 = Furry website supporting comics, including MLP -# Description of this input type -# (FORMAT: STRING) -INPUT_DESC_E_HENTAI = Website offering many comics/mangas, mostly but not always NSFW -# Description of this input type -# (FORMAT: STRING) -INPUT_DESC_YIFFSTAR = A Furry website, story-oriented -# Description of this input type -# (FORMAT: STRING) -INPUT_DESC_CBZ = CBZ files coming from this very program -# Description of this input type -# (FORMAT: STRING) -INPUT_DESC_HTML = HTML files coming from this very program -# Default description when the type is not known by i18n -# (FORMAT: STRING) -OUTPUT_DESC = Unknown type: %s -# Description of this output type -# (FORMAT: STRING) -OUTPUT_DESC_EPUB = Standard EPUB file working on most e-book readers and viewers -# Description of this output type -# (FORMAT: STRING) -OUTPUT_DESC_TEXT = Local stories encoded in textual format, with a few rules :\n\ -\tthe title must be on the first line, \n\ -\tthe author (preceded by nothing, "by " or "©") must be on the second \n\ -\t\tline, possibly with the publication date in parenthesis \n\ -\t\t(i.e., "By Unknown (3rd October 1998)"), \n\ -\tchapters must be declared with "Chapter x" or "Chapter x: NAME OF THE \n\ -\t\tCHAPTER", where "x" is the chapter number,\n\ -\ta description of the story must be given as chapter number 0,\n\ -\ta cover image may be present with the same filename but a PNG, JPEG \n\ -\t\tor JPG extension. -# Description of this output type -# (FORMAT: STRING) -OUTPUT_DESC_INFO_TEXT = Contains the same information as the TEXT format, but with a \n\ -\tcompanion ".info" file to store some metadata -# Description of this output type -# (FORMAT: STRING) -OUTPUT_DESC_CBZ = CBZ file (basically a ZIP file containing images -- we store the images \n\ -\tin PNG format by default) -# Description of this output type -# (FORMAT: STRING) -OUTPUT_DESC_HTML = HTML files (a directory containing the resources and "index.html") -# Description of this output type -# (FORMAT: STRING) -OUTPUT_DESC_LATEX = A LaTeX file using the "book" template -# Description of this output type -# (FORMAT: STRING) -OUTPUT_DESC_SYSOUT = A simple DEBUG console output -# Default description when the type is not known by i18n -# This item is used as a group, its content is not expected to be used. -OUTPUT_DESC_SHORT = %s -# Short description of this output type -# (FORMAT: STRING) -OUTPUT_DESC_SHORT_EPUB = Electronic book (.epub) -# Short description of this output type -# (FORMAT: STRING) -OUTPUT_DESC_SHORT_TEXT = Plain text (.txt) -# Short description of this output type -# (FORMAT: STRING) -OUTPUT_DESC_SHORT_INFO_TEXT = Plain text and metadata -# Short description of this output type -# (FORMAT: STRING) -OUTPUT_DESC_SHORT_CBZ = Comic book (.cbz) -# Short description of this output type -# (FORMAT: STRING) -OUTPUT_DESC_SHORT_LATEX = LaTeX (.tex) -# Short description of this output type -# (FORMAT: STRING) -OUTPUT_DESC_SHORT_SYSOUT = Console output -# Short description of this output type -# (FORMAT: STRING) -OUTPUT_DESC_SHORT_HTML = HTML files with resources (directory, .html) -# Error message for unknown 2-letter LaTeX language code -# (FORMAT: STRING) -LATEX_LANG_UNKNOWN = Unknown language: %s -# 'by' prefix before author name used to output the author, make sure it is covered by Config.BYS for input detection -# (FORMAT: STRING) -BY = by diff --git a/src/be/nikiroo/fanfix/bundles/resources_core_fr.properties b/src/be/nikiroo/fanfix/bundles/resources_core_fr.properties deleted file mode 100644 index a64a5a09..00000000 --- a/src/be/nikiroo/fanfix/bundles/resources_core_fr.properties +++ /dev/null @@ -1,192 +0,0 @@ -# français (fr) resources_core translation file (UTF-8) -# -# Note that any key can be doubled with a _NOUTF suffix -# to use when the NOUTF env variable is set to 1 -# -# Also, the comments always refer to the key below them. -# - - -# help message for the syntax -# (FORMAT: STRING) -HELP_SYNTAX = Options reconnues :\n\ -\t--import [URL]: importer une histoire dans la librairie\n\ -\t--export [id] [output_type] [target]: exporter l'histoire "id" vers le fichier donné\n\ -\t--convert [URL] [output_type] [target] (+info): convertir l'histoire vers le fichier donné, et forcer l'ajout d'un fichier .info si +info est utilisé\n\ -\t--read [id] ([chapter number]): afficher l'histoire "id"\n\ -\t--read-url [URL] ([chapter number]): convertir l'histoire et la lire à la volée, sans la sauver\n\ -\t--search: liste les sites supportés (where)\n\ -\t--search [where] [keywords] (page [page]) (item [item]): lance une recherche et \n\ -\t\taffiche les résultats de la page page (page 1 par défaut), et de l'item item \n\ -\t\tspécifique si demandé\n\ -\t--search-tag [where]: liste tous les tags supportés par ce site web\n\ -\t--search-tag [index 1]... (page [page]) (item [item]): affine la recherche, tag par tag,\n\ -\t\tet affiche si besoin les sous-tags, les histoires ou les infos précises de \n\ -\t\tl'histoire demandée\n\ -\t--list ([type]): lister les histoires presentes dans la librairie et leurs IDs\n\ -\t--set-source [id] [nouvelle source]: change la source de l'histoire\n\ -\t--set-title [id] [nouveau titre]: change le titre de l'histoire\n\ -\t--set-author [id] [nouvel auteur]: change l'auteur de l'histoire\n\ -\t--set-reader [reader type]: changer le type de lecteur pour la commande en cours sur CLI, TUI ou GUI\n\ -\t--server: démarre le mode serveur (les paramètres sont dans le fichier de config)\n\ -\t--stop-server: arrêter le serveur distant sur ce port (key doit avoir la même valeur) \n\ -\t--remote [key] [host] [port]: contacter ce server au lieu de la librairie habituelle (key doit avoir la même valeur)\n\ -\t--help: afficher la liste des options disponibles\n\ -\t--version: retourne la version du programme\n\ -\n\ -Types supportés en entrée :\n\ -%s\n\ -\n\ -Types supportés en sortie :\n\ -%s -# syntax error message -# (FORMAT: STRING) -ERR_SYNTAX = Erreur de syntaxe (essayez "--help") -# an input or output support type description -# (FORMAT: STRING) -ERR_SYNTAX_TYPE = > %s : %s -# Error when retrieving data -# (FORMAT: STRING) -ERR_LOADING = Erreur de récupération des données depuis : %s -# Error when saving to given target -# (FORMAT: STRING) -ERR_SAVING = Erreur lors de la sauvegarde sur : %s -# Error when unknown output format -# (FORMAT: STRING) -ERR_BAD_OUTPUT_TYPE = Type de sortie inconnu : %s -# Error when converting input to URL/File -# (FORMAT: STRING) -ERR_BAD_URL = Protocole ou type de fichier inconnu : %s -# URL/File not supported -# (FORMAT: STRING) -ERR_NOT_SUPPORTED = Site web non supporté : %s -# Failed to download cover : %s -# (FORMAT: STRING) -ERR_BS_NO_COVER = Échec de la récupération de la page de couverture : %s -# Canonical OPEN SINGLE QUOTE char (for instance: ‘) -# (FORMAT: STRING) -OPEN_SINGLE_QUOTE = ‘ -# Canonical CLOSE SINGLE QUOTE char (for instance: ’) -# (FORMAT: STRING) -CLOSE_SINGLE_QUOTE = ’ -# Canonical OPEN DOUBLE QUOTE char (for instance: “) -# (FORMAT: STRING) -OPEN_DOUBLE_QUOTE = “ -# Canonical CLOSE DOUBLE QUOTE char (for instance: ”) -# (FORMAT: STRING) -CLOSE_DOUBLE_QUOTE = ” -# Name of the description fake chapter -# (FORMAT: STRING) -DESCRIPTION = Description -# Name of a chapter with a name -# (FORMAT: STRING) -CHAPTER_NAMED = Chapitre %d : %s -# Name of a chapter without name -# (FORMAT: STRING) -CHAPTER_UNNAMED = Chapitre %d -# Default description when the type is not known by i18n -# (FORMAT: STRING) -INPUT_DESC = Type d'entrée inconnu : %s -# Description of this input type -# (FORMAT: STRING) -INPUT_DESC_EPUB = Les fichiers .epub créés avec Fanfix (nous ne supportons pas les autres fichiers .epub, du moins pour le moment) -# Description of this input type -# (FORMAT: STRING) -INPUT_DESC_TEXT = Les histoires enregistrées en texte (.txt), avec quelques règles spécifiques : \n\ -\tle titre doit être sur la première ligne\n\ -\tl'auteur (précédé de rien, "Par ", "De " ou "©") doit être sur la deuxième ligne, optionnellement suivi de la date de publication entre parenthèses (i.e., "Par Quelqu'un (3 octobre 1998)")\n\ -\tles chapitres doivent être déclarés avec "Chapitre x" ou "Chapitre x: NOM DU CHAPTITRE", où "x" est le numéro du chapitre\n\ -\tune description de l'histoire doit être donnée en tant que chaptire 0\n\ -\tune image de couverture peut être présente avec le même nom de fichier que l'histoire, mais une extension .png, .jpeg ou .jpg -# Description of this input type -# (FORMAT: STRING) -INPUT_DESC_INFO_TEXT = Fort proche du format texte, mais avec un fichier .info accompagnant l'histoire pour y enregistrer quelques metadata (le fichier de metadata est supposé être créé par Fanfix, ou être compatible avec) -# Description of this input type -# (FORMAT: STRING) -INPUT_DESC_FANFICTION = Fanfictions venant d'une multitude d'univers différents, depuis les shows télévisés aux livres en passant par les jeux-vidéos -# Description of this input type -# (FORMAT: STRING) -INPUT_DESC_FIMFICTION = Fanfictions dévouées à la série My Little Pony -# Description of this input type -# (FORMAT: STRING) -INPUT_DESC_MANGAHUB = Un site répertoriant une quantité non négligeable de mangas, en anglais -# Description of this input type -# (FORMAT: STRING) -INPUT_DESC_E621 = Un site Furry proposant des comics, y compris de MLP -# Description of this input type -# (FORMAT: STRING) -INPUT_DESC_E_HENTAI = Un site web proposant beaucoup de comics/mangas, souvent mais pas toujours NSFW -# Description of this input type -# (FORMAT: STRING) -INPUT_DESC_YIFFSTAR = Un site web Furry, orienté sur les histoires plutôt que les images -# Description of this input type -# (FORMAT: STRING) -INPUT_DESC_CBZ = Les fichiers .cbz (une collection d'images zipées), de préférence créés avec Fanfix (même si les autres .cbz sont aussi supportés, mais sans la majorité des metadata de Fanfix dans ce cas) -# Description of this input type -# (FORMAT: STRING) -INPUT_DESC_HTML = Les fichiers HTML que vous pouvez ouvrir avec n'importe quel navigateur ; remarquez que Fanfix créera un répertoire pour y mettre les fichiers nécessaires, dont un fichier "index.html" pour afficher le tout -- nous ne supportons en entrée que les fichiers HTML créés par Fanfix -# Default description when the type is not known by i18n -# (FORMAT: STRING) -OUTPUT_DESC = Type de sortie inconnu : %s -# Description of this output type -# (FORMAT: STRING) -OUTPUT_DESC_EPUB = Standard EPUB file working on most e-book readers and viewers -# Description of this output type -# (FORMAT: STRING) -OUTPUT_DESC_TEXT = Local stories encoded in textual format, with a few rules :\n\ -\tthe title must be on the first line, \n\ -\tthe author (preceded by nothing, "by " or "©") must be on the second \n\ -\t\tline, possibly with the publication date in parenthesis \n\ -\t\t(i.e., "By Unknown (3rd October 1998)"), \n\ -\tchapters must be declared with "Chapter x" or "Chapter x: NAME OF THE \n\ -\t\tCHAPTER", where "x" is the chapter number,\n\ -\ta description of the story must be given as chapter number 0,\n\ -\ta cover image may be present with the same filename but a PNG, JPEG \n\ -\t\tor JPG extension. -# Description of this output type -# (FORMAT: STRING) -OUTPUT_DESC_INFO_TEXT = Contains the same information as the TEXT format, but with a \n\ -\tcompanion ".info" file to store some metadata -# Description of this output type -# (FORMAT: STRING) -OUTPUT_DESC_CBZ = CBZ file (basically a ZIP file containing images -- we store the images \n\ -\tin PNG format by default) -# Description of this output type -# (FORMAT: STRING) -OUTPUT_DESC_HTML = HTML files (a directory containing the resources and "index.html") -# Description of this output type -# (FORMAT: STRING) -OUTPUT_DESC_LATEX = A LaTeX file using the "book" template -# Description of this output type -# (FORMAT: STRING) -OUTPUT_DESC_SYSOUT = A simple DEBUG console output -# Default description when the type is not known by i18n -# This item is used as a group, its content is not expected to be used. -OUTPUT_DESC_SHORT = %s -# Short description of this output type -# (FORMAT: STRING) -OUTPUT_DESC_SHORT_EPUB = Electronic book (.epub) -# Short description of this output type -# (FORMAT: STRING) -OUTPUT_DESC_SHORT_TEXT = Plain text (.txt) -# Short description of this output type -# (FORMAT: STRING) -OUTPUT_DESC_SHORT_INFO_TEXT = Plain text and metadata -# Short description of this output type -# (FORMAT: STRING) -OUTPUT_DESC_SHORT_CBZ = Comic book (.cbz) -# Short description of this output type -# (FORMAT: STRING) -OUTPUT_DESC_SHORT_LATEX = LaTeX (.tex) -# Short description of this output type -# (FORMAT: STRING) -OUTPUT_DESC_SHORT_SYSOUT = Console output -# Short description of this output type -# (FORMAT: STRING) -OUTPUT_DESC_SHORT_HTML = HTML files with resources (directory, .html) -# Error message for unknown 2-letter LaTeX language code -# (FORMAT: STRING) -LATEX_LANG_UNKNOWN = Unknown language: %s -# 'by' prefix before author name used to output the author, make sure it is covered by Config.BYS for input detection -# (FORMAT: STRING) -BY = by diff --git a/src/be/nikiroo/fanfix/bundles/resources_gui.properties b/src/be/nikiroo/fanfix/bundles/resources_gui.properties deleted file mode 100644 index 6d46af41..00000000 --- a/src/be/nikiroo/fanfix/bundles/resources_gui.properties +++ /dev/null @@ -1,199 +0,0 @@ -# United Kingdom (en_GB) resources_gui translation file (UTF-8) -# -# Note that any key can be doubled with a _NOUTF suffix -# to use when the NOUTF env variable is set to 1 -# -# Also, the comments always refer to the key below them. -# - - -# the title of the main window of Fanfix, the library -# (FORMAT: STRING) -TITLE_LIBRARY = Fanfix %s -# the title of the main window of Fanfix, the library, when the library has a name (i.e., is not local) -# (FORMAT: STRING) -TITLE_LIBRARY_WITH_NAME = Fanfix %s -# the title of the configuration window of Fanfix, also the name of the menu button -# (FORMAT: STRING) -TITLE_CONFIG = Fanfix Configuration -# the subtitle of the configuration window of Fanfix -# (FORMAT: STRING) -SUBTITLE_CONFIG = This is where you configure the options of the program. -# the title of the UI configuration window of Fanfix, also the name of the menu button -# (FORMAT: STRING) -TITLE_CONFIG_UI = UI Configuration -# the subtitle of the UI configuration window of Fanfix -# (FORMAT: STRING) -SUBTITLE_CONFIG_UI = This is where you configure the graphical appearence of the program. -# the title of the 'save to/export to' window of Fanfix -# (FORMAT: STRING) -TITLE_SAVE = Save -# the title of the 'move to' window of Fanfix -# (FORMAT: STRING) -TITLE_MOVE_TO = Moving story -# the subtitle of the 'move to' window of Fanfix -# (FORMAT: STRING) -SUBTITLE_MOVE_TO = Move to: -# the title of the 'delete' window of Fanfix -# (FORMAT: STRING) -TITLE_DELETE = Delete story -# the subtitle of the 'delete' window of Fanfix -# (FORMAT: STRING) -SUBTITLE_DELETE = Delete %s: %s -# the title of the 'library error' dialogue -# (FORMAT: STRING) -TITLE_ERROR_LIBRARY = Library error -# the title of the 'import URL' dialogue -# (FORMAT: STRING) -TITLE_IMPORT_URL = Importing from URL -# the subtitle of the 'import URL' dialogue -# (FORMAT: STRING) -SUBTITLE_IMPORT_URL = URL of the story to import: -# the title of general error dialogues -# (FORMAT: STRING) -TITLE_ERROR = Error -# the title of a story for the properties dialogue, the viewers... -# (FORMAT: STRING) -TITLE_STORY = %s: %s -# HTML text used to notify of a new version -# (FORMAT: STRING) -NEW_VERSION_AVAILABLE = A new version of the program is available at %s -# text used as title for the update dialogue -# (FORMAT: STRING) -NEW_VERSION_TITLE = Updates available -# HTML text used to specify a newer version title and number, used for each version newer than the current one -# (FORMAT: STRING) -NEW_VERSION_VERSION = Version %s -# show the number of words of a book -# (FORMAT: STRING) -BOOK_COUNT_WORDS = %s words -# show the number of images of a book -# (FORMAT: STRING) -BOOK_COUNT_IMAGES = %s images -# show the number of stories of a meta-book (a book representing allthe types/sources or all the authors present) -# (FORMAT: STRING) -BOOK_COUNT_STORIES = %s stories -# the file menu -# (FORMAT: STRING) -MENU_FILE = File -# the file/exit menu button -# (FORMAT: STRING) -MENU_FILE_EXIT = Exit -# the file/import_file menu button -# (FORMAT: STRING) -MENU_FILE_IMPORT_FILE = Import File... -# the file/import_url menu button -# (FORMAT: STRING) -MENU_FILE_IMPORT_URL = Import URL... -# the file/export menu button -# (FORMAT: STRING) -MENU_FILE_EXPORT = Save as... -# the file/move to menu button -# (FORMAT: STRING) -MENU_FILE_MOVE_TO = Move to -# the file/set author menu button -# (FORMAT: STRING) -MENU_FILE_SET_AUTHOR = Set author -# the file/move to/new type-source menu button, that will trigger a dialogue to create a new type/source -# (FORMAT: STRING) -MENU_FILE_MOVE_TO_NEW_TYPE = New source... -# the file/move to/new author menu button, that will trigger a dialogue to create a new author -# (FORMAT: STRING) -MENU_FILE_MOVE_TO_NEW_AUTHOR = New author... -# the file/rename menu item, that will trigger a dialogue to ask for a new title for the story -# (FORMAT: STRING) -MENU_FILE_RENAME = Rename... -# the file/Properties menu item, that will trigger a dialogue to show the properties of the story -# (FORMAT: STRING) -MENU_FILE_PROPERTIES = Properties -# the file/open menu item, that will open the story or fake-story (an author or a source/type) -# (FORMAT: STRING) -MENU_FILE_OPEN = Open -# the edit menu -# (FORMAT: STRING) -MENU_EDIT = Edit -# the edit/send to cache menu button, to download the story into the cache if not already done -# (FORMAT: STRING) -MENU_EDIT_DOWNLOAD_TO_CACHE = Download to cache -# the clear cache menu button, to clear the cache for a single book -# (FORMAT: STRING) -MENU_EDIT_CLEAR_CACHE = Clear cache -# the edit/redownload menu button, to download the latest version of the book -# (FORMAT: STRING) -MENU_EDIT_REDOWNLOAD = Redownload -# the edit/delete menu button -# (FORMAT: STRING) -MENU_EDIT_DELETE = Delete -# the edit/Set as cover for source menu button -# (FORMAT: STRING) -MENU_EDIT_SET_COVER_FOR_SOURCE = Set as cover for source -# the edit/Set as cover for author menu button -# (FORMAT: STRING) -MENU_EDIT_SET_COVER_FOR_AUTHOR = Set as cover for author -# the search menu to open the earch stories on one of the searchable websites -# (FORMAT: STRING) -MENU_SEARCH = Search -# the view menu -# (FORMAT: STRING) -MENU_VIEW = View -# the view/word_count menu button, to show the word/image/story count as secondary info -# (FORMAT: STRING) -MENU_VIEW_WCOUNT = Word count -# the view/author menu button, to show the author as secondary info -# (FORMAT: STRING) -MENU_VIEW_AUTHOR = Author -# the sources menu, to select the books from a specific source; also used as a title for the source books -# (FORMAT: STRING) -MENU_SOURCES = Sources -# the authors menu, to select the books of a specific author; also used as a title for the author books -# (FORMAT: STRING) -MENU_AUTHORS = Authors -# the options menu, to configure Fanfix from the GUI -# (FORMAT: STRING) -MENU_OPTIONS = Options -# a special menu button to select all the sources/types or authors, by group (one book = one group) -# (FORMAT: STRING) -MENU_XXX_ALL_GROUPED = All -# a special menu button to select all the sources/types or authors, in a listing (all the included books are listed, grouped by source/type or author) -# (FORMAT: STRING) -MENU_XXX_ALL_LISTING = Listing -# a special menu button to select the books without author -# (FORMAT: STRING) -MENU_AUTHORS_UNKNOWN = [unknown] -# progress bar caption for the 'reload books' step of all outOfUi operations -# (FORMAT: STRING) -PROGRESS_OUT_OF_UI_RELOAD_BOOKS = Reload books -# progress bar caption for the 'change source' step of the ReDownload operation -# (FORMAT: STRING) -PROGRESS_CHANGE_SOURCE = Change the source of the book to %s -# default description if the error is not known -# (FORMAT: STRING) -ERROR_LIB_STATUS = An error occured when contacting the library -# library access not allowed -# (FORMAT: STRING) -ERROR_LIB_STATUS_UNAUTHORIZED = You are not allowed to access this library -# the library is invalid (not correctly set up) -# (FORMAT: STRING) -ERROR_LIB_STATUS_INVALID = Library not valid -# the library is out of commission -# (FORMAT: STRING) -ERROR_LIB_STATUS_UNAVAILABLE = Library currently unavailable -# cannot open the book, internal or external viewer -# (FORMAT: STRING) -ERROR_CANNOT_OPEN = Cannot open the selected book -# URL is not supported by Fanfix -# (FORMAT: STRING) -ERROR_URL_NOT_SUPPORTED = URL not supported: %s -# cannot import the URL -# (FORMAT: STRING) -ERROR_URL_IMPORT_FAILED = Failed to import %s:\n\ -%s -# (html) the chapter progression value used on the viewers -# (FORMAT: STRING) -CHAPTER_HTML_UNNAMED =   Chapter %d/%d -# (html) the chapter progression value used on the viewers -# (FORMAT: STRING) -CHAPTER_HTML_NAMED =   Chapter %d/%d: %s -# (NO html) the chapter progression value used on the viewers -# (FORMAT: STRING) -IMAGE_PROGRESSION = Image %d / %d diff --git a/src/be/nikiroo/fanfix/bundles/resources_gui_fr.properties b/src/be/nikiroo/fanfix/bundles/resources_gui_fr.properties deleted file mode 100644 index 1ede37c0..00000000 --- a/src/be/nikiroo/fanfix/bundles/resources_gui_fr.properties +++ /dev/null @@ -1,199 +0,0 @@ -# français (fr) resources_gui translation file (UTF-8) -# -# Note that any key can be doubled with a _NOUTF suffix -# to use when the NOUTF env variable is set to 1 -# -# Also, the comments always refer to the key below them. -# - - -# the title of the main window of Fanfix, the library -# (FORMAT: STRING) -TITLE_LIBRARY = Fanfix %s -# the title of the main window of Fanfix, the library, when the library has a name (i.e., is not local) -# (FORMAT: STRING) -TITLE_LIBRARY_WITH_NAME = Fanfix %s -# the title of the configuration window of Fanfix, also the name of the menu button -# (FORMAT: STRING) -TITLE_CONFIG = Configuration de Fanfix -# the subtitle of the configuration window of Fanfix -# (FORMAT: STRING) -SUBTITLE_CONFIG = C'est ici que vous pouvez configurer les options du programme. -# the title of the UI configuration window of Fanfix, also the name of the menu button -# (FORMAT: STRING) -TITLE_CONFIG_UI = Configuration de l'interface -# the subtitle of the UI configuration window of Fanfix -# (FORMAT: STRING) -SUBTITLE_CONFIG_UI = C'est ici que vous pouvez configurer les options de l'apparence de l'application. -# the title of the 'save to/export to' window of Fanfix -# (FORMAT: STRING) -TITLE_SAVE = Sauver -# the title of the 'move to' window of Fanfix -# (FORMAT: STRING) -TITLE_MOVE_TO = Déplacer le livre -# the subtitle of the 'move to' window of Fanfix -# (FORMAT: STRING) -SUBTITLE_MOVE_TO = Déplacer vers : -# the title of the 'delete' window of Fanfix -# (FORMAT: STRING) -TITLE_DELETE = Supprimer le livre -# the subtitle of the 'delete' window of Fanfix -# (FORMAT: STRING) -SUBTITLE_DELETE = Supprimer %s : %s -# the title of the 'library error' dialogue -# (FORMAT: STRING) -TITLE_ERROR_LIBRARY = Erreur avec la librairie -# the title of the 'import URL' dialogue -# (FORMAT: STRING) -TITLE_IMPORT_URL = Importer depuis une URL -# the subtitle of the 'import URL' dialogue -# (FORMAT: STRING) -SUBTITLE_IMPORT_URL = L'URL du livre à importer -# the title of general error dialogues -# (FORMAT: STRING) -TITLE_ERROR = Error -# the title of a story for the properties dialogue, the viewers... -# (FORMAT: STRING) -TITLE_STORY = %s: %s -# HTML text used to notify of a new version -# (FORMAT: STRING) -NEW_VERSION_AVAILABLE = Une nouvelle version du programme est disponible sur %s -# text used as title for the update dialogue -# (FORMAT: STRING) -NEW_VERSION_TITLE = Mise-à-jour disponible -# HTML text used to specify a newer version title and number, used for each version newer than the current one -# (FORMAT: STRING) -NEW_VERSION_VERSION = Version %s -# show the number of words of a book -# (FORMAT: STRING) -BOOK_COUNT_WORDS = %s mots -# show the number of images of a book -# (FORMAT: STRING) -BOOK_COUNT_IMAGES = %s images -# show the number of stories of a meta-book (a book representing allthe types/sources or all the authors present) -# (FORMAT: STRING) -BOOK_COUNT_STORIES = %s livres -# the file menu -# (FORMAT: STRING) -MENU_FILE = Fichier -# the file/exit menu button -# (FORMAT: STRING) -MENU_FILE_EXIT = Quiter -# the file/import_file menu button -# (FORMAT: STRING) -MENU_FILE_IMPORT_FILE = Importer un fichier... -# the file/import_url menu button -# (FORMAT: STRING) -MENU_FILE_IMPORT_URL = Importer une URL... -# the file/export menu button -# (FORMAT: STRING) -MENU_FILE_EXPORT = Sauver sous... -# the file/move to menu button -# (FORMAT: STRING) -MENU_FILE_MOVE_TO = Déplacer vers -# the file/set author menu button -# (FORMAT: STRING) -MENU_FILE_SET_AUTHOR = Changer l'auteur -# the file/move to/new type-source menu button, that will trigger a dialogue to create a new type/source -# (FORMAT: STRING) -MENU_FILE_MOVE_TO_NEW_TYPE = Nouvelle source... -# the file/move to/new author menu button, that will trigger a dialogue to create a new author -# (FORMAT: STRING) -MENU_FILE_MOVE_TO_NEW_AUTHOR = Nouvel auteur... -# the file/rename menu item, that will trigger a dialogue to ask for a new title for the story -# (FORMAT: STRING) -MENU_FILE_RENAME = Renommer... -# the file/Properties menu item, that will trigger a dialogue to show the properties of the story -# (FORMAT: STRING) -MENU_FILE_PROPERTIES = Propriétés -# the file/open menu item, that will open the story or fake-story (an author or a source/type) -# (FORMAT: STRING) -MENU_FILE_OPEN = Ouvrir -# the edit menu -# (FORMAT: STRING) -MENU_EDIT = Edition -# the edit/send to cache menu button, to download the story into the cache if not already done -# (FORMAT: STRING) -MENU_EDIT_DOWNLOAD_TO_CACHE = Charger en cache -# the clear cache menu button, to clear the cache for a single book -# (FORMAT: STRING) -MENU_EDIT_CLEAR_CACHE = Nettoyer le cache -# the edit/redownload menu button, to download the latest version of the book -# (FORMAT: STRING) -MENU_EDIT_REDOWNLOAD = Re-downloader -# the edit/delete menu button -# (FORMAT: STRING) -MENU_EDIT_DELETE = Supprimer -# the edit/Set as cover for source menu button -# (FORMAT: STRING) -MENU_EDIT_SET_COVER_FOR_SOURCE = Utiliser comme cover pour la source -# the edit/Set as cover for author menu button -# (FORMAT: STRING) -MENU_EDIT_SET_COVER_FOR_AUTHOR = Utiliser comme cover pour l'auteur -# the search menu to open the earch stories on one of the searchable websites -# (FORMAT: STRING) -MENU_SEARCH = Recherche -# the view menu -# (FORMAT: STRING) -MENU_VIEW = Affichage -# the view/word_count menu button, to show the word/image/story count as secondary info -# (FORMAT: STRING) -MENU_VIEW_WCOUNT = Nombre de mots -# the view/author menu button, to show the author as secondary info -# (FORMAT: STRING) -MENU_VIEW_AUTHOR = Auteur -# the sources menu, to select the books from a specific source; also used as a title for the source books -# (FORMAT: STRING) -MENU_SOURCES = Sources -# the authors menu, to select the books of a specific author; also used as a title for the author books -# (FORMAT: STRING) -MENU_AUTHORS = Auteurs -# the options menu, to configure Fanfix from the GUI -# (FORMAT: STRING) -MENU_OPTIONS = Options -# a special menu button to select all the sources/types or authors, by group (one book = one group) -# (FORMAT: STRING) -MENU_XXX_ALL_GROUPED = Tout -# a special menu button to select all the sources/types or authors, in a listing (all the included books are listed, grouped by source/type or author) -# (FORMAT: STRING) -MENU_XXX_ALL_LISTING = Listing -# a special menu button to select the books without author -# (FORMAT: STRING) -MENU_AUTHORS_UNKNOWN = [inconnu] -# progress bar caption for the 'reload books' step of all outOfUi operations -# (FORMAT: STRING) -PROGRESS_OUT_OF_UI_RELOAD_BOOKS = Recharger les livres -# progress bar caption for the 'change source' step of the ReDownload operation -# (FORMAT: STRING) -PROGRESS_CHANGE_SOURCE = Change la source du livre en %s -# default description if the error is not known -# (FORMAT: STRING) -ERROR_LIB_STATUS = Une erreur est survenue en contactant la librairie -# library access not allowed -# (FORMAT: STRING) -ERROR_LIB_STATUS_UNAUTHORIZED = Vous n'êtes pas autorisé à accéder à cette librairie -# the library is invalid (not correctly set up) -# (FORMAT: STRING) -ERROR_LIB_STATUS_INVALID = Librairie invalide -# the library is out of commission -# (FORMAT: STRING) -ERROR_LIB_STATUS_UNAVAILABLE = Librairie indisponible -# cannot open the book, internal or external viewer -# (FORMAT: STRING) -ERROR_CANNOT_OPEN = Impossible d'ouvrir le livre sélectionné -# URL is not supported by Fanfix -# (FORMAT: STRING) -ERROR_URL_NOT_SUPPORTED = URL non supportée : %s -# cannot import the URL -# (FORMAT: STRING) -ERROR_URL_IMPORT_FAILED = Erreur lors de l'import de %s:\n\ -%s -# (html) the chapter progression value used on the viewers -# (FORMAT: STRING) -CHAPTER_HTML_UNNAMED =   Chapitre %d / %d -# (html) the chapter progression value used on the viewers -# (FORMAT: STRING) -CHAPTER_HTML_NAMED =   Chapitre %d / %d: %s -# (NO html) the chapter progression value used on the viewers -# (FORMAT: STRING) -IMAGE_PROGRESSION = Image %d / %d diff --git a/src/be/nikiroo/fanfix/bundles/ui_description.properties b/src/be/nikiroo/fanfix/bundles/ui_description.properties deleted file mode 100644 index 5cb2a9fb..00000000 --- a/src/be/nikiroo/fanfix/bundles/ui_description.properties +++ /dev/null @@ -1,35 +0,0 @@ -# United Kingdom (en_GB) UI configuration options description translation file (UTF-8) -# -# Note that any key can be doubled with a _NOUTF suffix -# to use when the NOUTF env variable is set to 1 -# -# Also, the comments always refer to the key below them. -# - - -# The directory where to store temporary files, defaults to directory 'tmp.reader' in the config directory (usually $HOME/.fanfix) -# (FORMAT: DIRECTORY) absolute path, $HOME variable supported, / is always accepted as dir separator -CACHE_DIR_LOCAL_READER = The directory where to store temporary files, defaults to directory 'tmp.reader' in the config directory (usually $HOME/.fanfix) -- this is an absolute path, $HOME variable supported, / is always accepted as dir separator -# The type of output for the GUI Reader for non-images documents -# (FORMAT: COMBO_LIST) One of the known output type -# ALLOWED VALUES: "INFO_TEXT" "EPUB" "HTML" "TEXT" -GUI_NON_IMAGES_DOCUMENT_TYPE = -# The type of output for the GUI Reader for images documents -# (FORMAT: COMBO_LIST) -# ALLOWED VALUES: "CBZ" "HTML" -GUI_IMAGES_DOCUMENT_TYPE = -# Use the internal reader for images documents -- this is TRUE by default -# (FORMAT: BOOLEAN) -IMAGES_DOCUMENT_USE_INTERNAL_READER = -# The command launched for images documents -- default to the system default for the current file type -# (FORMAT: STRING) A command to start -IMAGES_DOCUMENT_READER = -# Use the internal reader for non images documents -- this is TRUE by default -# (FORMAT: BOOLEAN) -NON_IMAGES_DOCUMENT_USE_INTERNAL_READER = -# The command launched for non images documents -- default to the system default for the current file type -# (FORMAT: STRING) A command to start -NON_IMAGES_DOCUMENT_READER = -# The background colour if you don't want the default system one -# (FORMAT: COLOR) -BACKGROUND_COLOR = diff --git a/src/be/nikiroo/fanfix/data/Chapter.java b/src/be/nikiroo/fanfix/data/Chapter.java deleted file mode 100644 index d490058a..00000000 --- a/src/be/nikiroo/fanfix/data/Chapter.java +++ /dev/null @@ -1,154 +0,0 @@ -package be.nikiroo.fanfix.data; - -import java.io.Serializable; -import java.util.ArrayList; -import java.util.Iterator; -import java.util.List; - -/** - * A chapter in the story (or the resume/description). - * - * @author niki - */ -public class Chapter implements Iterable, Cloneable, Serializable { - private static final long serialVersionUID = 1L; - - private String name; - private int number; - private List paragraphs = new ArrayList(); - private List empty = new ArrayList(); - private long words; - - /** - * Empty constructor, not to use. - */ - @SuppressWarnings("unused") - private Chapter() { - // for serialisation purposes - } - - /** - * Create a new {@link Chapter} with the given information. - * - * @param number - * the chapter number, or 0 for the description/resume. - * @param name - * the chapter name - */ - public Chapter(int number, String name) { - this.number = number; - this.name = name; - } - - /** - * The chapter name. - * - * @return the name - */ - public String getName() { - return name; - } - - /** - * The chapter name. - * - * @param name - * the name to set - */ - public void setName(String name) { - this.name = name; - } - - /** - * The chapter number, or 0 for the description/resume. - * - * @return the number - */ - public int getNumber() { - return number; - } - - /** - * The chapter number, or 0 for the description/resume. - * - * @param number - * the number to set - */ - public void setNumber(int number) { - this.number = number; - } - - /** - * The included paragraphs. - * - * @return the paragraphs - */ - public List getParagraphs() { - return paragraphs; - } - - /** - * The included paragraphs. - * - * @param paragraphs - * the paragraphs to set - */ - public void setParagraphs(List paragraphs) { - this.paragraphs = paragraphs; - } - - /** - * Get an iterator on the {@link Paragraph}s. - */ - @Override - public Iterator iterator() { - return paragraphs == null ? empty.iterator() : paragraphs.iterator(); - } - - /** - * The number of words (or images) in this {@link Chapter}. - * - * @return the number of words - */ - public long getWords() { - return words; - } - - /** - * The number of words (or images) in this {@link Chapter}. - * - * @param words - * the number of words to set - */ - public void setWords(long words) { - this.words = words; - } - - /** - * Display a DEBUG {@link String} representation of this object. - */ - @Override - public String toString() { - return "Chapter " + number + ": " + name; - } - - @Override - public Chapter clone() { - Chapter chap = null; - try { - chap = (Chapter) super.clone(); - } catch (CloneNotSupportedException e) { - // Did the clones rebel? - System.err.println(e); - } - - if (paragraphs != null) { - chap.paragraphs = new ArrayList(); - for (Paragraph para : paragraphs) { - chap.paragraphs.add(para.clone()); - } - } - - return chap; - } -} diff --git a/src/be/nikiroo/fanfix/data/MetaData.java b/src/be/nikiroo/fanfix/data/MetaData.java deleted file mode 100644 index 586196a6..00000000 --- a/src/be/nikiroo/fanfix/data/MetaData.java +++ /dev/null @@ -1,486 +0,0 @@ -package be.nikiroo.fanfix.data; - -import java.io.Serializable; -import java.util.ArrayList; -import java.util.List; - -import be.nikiroo.utils.Image; -import be.nikiroo.utils.StringUtils; - -/** - * The meta data associated to a {@link Story} object. - * - * @author niki - */ -public class MetaData implements Cloneable, Comparable, Serializable { - private static final long serialVersionUID = 1L; - - private String title; - private String author; - private String date; - private Chapter resume; - private List tags; - private Image cover; - private String subject; - private String source; - private String url; - private String uuid; - private String luid; - private String lang; - private String publisher; - private String type; - private boolean imageDocument; - private long words; - private String creationDate; - private boolean fakeCover; - - /** - * The title of the story. - * - * @return the title - */ - public String getTitle() { - return title; - } - - /** - * The title of the story. - * - * @param title - * the title to set - */ - public void setTitle(String title) { - this.title = title; - } - - /** - * The author of the story. - * - * @return the author - */ - public String getAuthor() { - return author; - } - - /** - * The author of the story. - * - * @param author - * the author to set - */ - public void setAuthor(String author) { - this.author = author; - } - - /** - * The story publication date. - * - * @return the date - */ - public String getDate() { - return date; - } - - /** - * The story publication date. - * - * @param date - * the date to set - */ - public void setDate(String date) { - this.date = date; - } - - /** - * The tags associated with this story. - * - * @return the tags - */ - public List getTags() { - return tags; - } - - /** - * The tags associated with this story. - * - * @param tags - * the tags to set - */ - public void setTags(List tags) { - this.tags = tags; - } - - /** - * The story resume (a.k.a. description). - *

- * This can be NULL if we don't have a resume for this {@link Story}. - * - * @return the resume - */ - public Chapter getResume() { - return resume; - } - - /** - * The story resume (a.k.a. description). - * - * @param resume - * the resume to set - */ - public void setResume(Chapter resume) { - this.resume = resume; - } - - /** - * The cover image of the story if any (can be NULL). - * - * @return the cover - */ - public Image getCover() { - return cover; - } - - /** - * The cover image of the story if any (can be NULL). - * - * @param cover - * the cover to set - */ - public void setCover(Image cover) { - this.cover = cover; - } - - /** - * The subject of the story (or instance, if it is a fanfiction, what is the - * original work; if it is a technical text, what is the technical - * subject...). - * - * @return the subject - */ - public String getSubject() { - return subject; - } - - /** - * The subject of the story (for instance, if it is a fanfiction, what is - * the original work; if it is a technical text, what is the technical - * subject...). - * - * @param subject - * the subject to set - */ - public void setSubject(String subject) { - this.subject = subject; - } - - /** - * The source of this story (which online library it was downloaded from). - * - * @return the source - */ - public String getSource() { - return source; - } - - /** - * The source of this story (which online library it was downloaded from). - * - * @param source - * the source to set - */ - public void setSource(String source) { - this.source = source; - } - - /** - * The original URL from which this {@link Story} was imported. - * - * @return the url - */ - public String getUrl() { - return url; - } - - /** - * The original URL from which this {@link Story} was imported. - * - * @param url - * the new url to set - */ - public void setUrl(String url) { - this.url = url; - } - - /** - * A unique value representing the story (it is often a URL). - * - * @return the uuid - */ - public String getUuid() { - return uuid; - } - - /** - * A unique value representing the story (it is often a URL). - * - * @param uuid - * the uuid to set - */ - public void setUuid(String uuid) { - this.uuid = uuid; - } - - /** - * A unique value representing the story in the local library. - * - * @return the luid - */ - public String getLuid() { - return luid; - } - - /** - * A unique value representing the story in the local library. - * - * @param luid - * the luid to set - */ - public void setLuid(String luid) { - this.luid = luid; - } - - /** - * The 2-letter code language of this story. - * - * @return the lang - */ - public String getLang() { - return lang; - } - - /** - * The 2-letter code language of this story. - * - * @param lang - * the lang to set - */ - public void setLang(String lang) { - this.lang = lang; - } - - /** - * The story publisher (other the same as the source). - * - * @return the publisher - */ - public String getPublisher() { - return publisher; - } - - /** - * The story publisher (other the same as the source). - * - * @param publisher - * the publisher to set - */ - public void setPublisher(String publisher) { - this.publisher = publisher; - } - - /** - * The output type this {@link Story} is in. - * - * @return the type the type - */ - public String getType() { - return type; - } - - /** - * The output type this {@link Story} is in. - * - * @param type - * the new type to set - */ - public void setType(String type) { - this.type = type; - } - - /** - * Document catering mostly to image files. - * - * @return the imageDocument state - */ - public boolean isImageDocument() { - return imageDocument; - } - - /** - * Document catering mostly to image files. - * - * @param imageDocument - * the imageDocument state to set - */ - public void setImageDocument(boolean imageDocument) { - this.imageDocument = imageDocument; - } - - /** - * The number of words in the related {@link Story}. - * - * @return the number of words - */ - public long getWords() { - return words; - } - - /** - * The number of words in the related {@link Story}. - * - * @param words - * the number of words to set - */ - public void setWords(long words) { - this.words = words; - } - - /** - * The (Fanfix) {@link Story} creation date. - * - * @return the creationDate - */ - public String getCreationDate() { - return creationDate; - } - - /** - * The (Fanfix) {@link Story} creation date. - * - * @param creationDate - * the creationDate to set - */ - public void setCreationDate(String creationDate) { - this.creationDate = creationDate; - } - - /** - * The cover in this {@link MetaData} object is "fake", in the sens that it - * comes from the actual content images. - * - * @return TRUE for a fake cover - */ - public boolean isFakeCover() { - return fakeCover; - } - - /** - * The cover in this {@link MetaData} object is "fake", in the sens that it - * comes from the actual content images - * - * @param fakeCover - * TRUE for a fake cover - */ - public void setFakeCover(boolean fakeCover) { - this.fakeCover = fakeCover; - } - - @Override - public int compareTo(MetaData o) { - if (o == null) { - return 1; - } - - String id = (getTitle() == null ? "" : getTitle()) - + (getUuid() == null ? "" : getUuid()) - + (getLuid() == null ? "" : getLuid()); - String oId = (getTitle() == null ? "" : o.getTitle()) - + (getUuid() == null ? "" : o.getUuid()) - + (o.getLuid() == null ? "" : o.getLuid()); - - return id.compareToIgnoreCase(oId); - } - - @Override - public boolean equals(Object obj) { - if (!(obj instanceof MetaData)) { - return false; - } - - return compareTo((MetaData) obj) == 0; - } - - @Override - public int hashCode() { - String uuid = getUuid(); - if (uuid == null) { - uuid = "" + title + author + source; - } - - return uuid.hashCode(); - } - - @Override - public MetaData clone() { - MetaData meta = null; - try { - meta = (MetaData) super.clone(); - } catch (CloneNotSupportedException e) { - // Did the clones rebel? - System.err.println(e); - } - - if (tags != null) { - meta.tags = new ArrayList(tags); - } - - if (resume != null) { - meta.resume = resume.clone(); - } - - return meta; - } - - /** - * Display a DEBUG {@link String} representation of this object. - *

- * This is not efficient, nor intended to be. - */ - @Override - public String toString() { - String title = ""; - if (getTitle() != null) { - title = getTitle(); - } - - StringBuilder tags = new StringBuilder(); - if (getTags() != null) { - for (String tag : getTags()) { - if (tags.length() > 0) { - tags.append(", "); - } - tags.append(tag); - } - } - - String resume = ""; - if (getResume() != null) { - for (Paragraph para : getResume()) { - resume += "\n\t"; - resume += para.toString().substring(0, - Math.min(para.toString().length(), 120)); - } - resume += "\n"; - } - - String cover = "none"; - if (getCover() != null) { - cover = StringUtils.formatNumber(getCover().getSize()) - + "bytes"; - } - - return String.format( - "Meta %s:\n\tTitle: [%s]\n\tAuthor: [%s]\n\tDate: [%s]\n\tTags: [%s]" - + "\n\tResume: [%s]\n\tCover: [%s]", luid, title, - getAuthor(), getDate(), tags.toString(), resume, cover); - } -} diff --git a/src/be/nikiroo/fanfix/data/Paragraph.java b/src/be/nikiroo/fanfix/data/Paragraph.java deleted file mode 100644 index 9adc51c4..00000000 --- a/src/be/nikiroo/fanfix/data/Paragraph.java +++ /dev/null @@ -1,172 +0,0 @@ -package be.nikiroo.fanfix.data; - -import java.io.Serializable; - -import be.nikiroo.utils.Image; - -/** - * A paragraph in a chapter of the story. - * - * @author niki - */ -public class Paragraph implements Cloneable, Serializable { - private static final long serialVersionUID = 1L; - - /** - * A paragraph type, that will dictate how the paragraph will be handled. - * - * @author niki - */ - public enum ParagraphType { - /** Normal paragraph (text) */ - NORMAL, - /** Blank line */ - BLANK, - /** A Break paragraph, i.e.: HR (Horizontal Line) or '* * *' or whatever */ - BREAK, - /** Quotation (dialogue) */ - QUOTE, - /** An image (no text) */ - IMAGE, ; - - /** - * This paragraph type is of a text kind (quote or not). - * - * @param allowEmpty - * allow empty text as text, too (blanks, breaks...) - * @return TRUE if it is - */ - public boolean isText(boolean allowEmpty) { - return (this == NORMAL || this == QUOTE) - || (allowEmpty && (this == BLANK || this == BREAK)); - } - } - - private ParagraphType type; - private String content; - private Image contentImage; - private long words; - - /** - * Empty constructor, not to use. - */ - @SuppressWarnings("unused") - private Paragraph() { - // for serialisation purposes - } - - /** - * Create a new {@link Paragraph} with the given image. - * - * @param contentImage - * the image - */ - public Paragraph(Image contentImage) { - this(ParagraphType.IMAGE, null, 1); - this.contentImage = contentImage; - } - - /** - * Create a new {@link Paragraph} with the given values. - * - * @param type - * the {@link ParagraphType} - * @param content - * the content of this paragraph - * @param words - * the number of words (or images) - */ - public Paragraph(ParagraphType type, String content, long words) { - this.type = type; - this.content = content; - this.words = words; - } - - /** - * The {@link ParagraphType}. - * - * @return the type - */ - public ParagraphType getType() { - return type; - } - - /** - * The {@link ParagraphType}. - * - * @param type - * the type to set - */ - public void setType(ParagraphType type) { - this.type = type; - } - - /** - * The content of this {@link Paragraph} if it is not an image. - * - * @return the content - */ - public String getContent() { - return content; - } - - /** - * The content of this {@link Paragraph}. - * - * @param content - * the content to set - */ - public void setContent(String content) { - this.content = content; - } - - /** - * The content of this {@link Paragraph} if it is an image. - * - * @return the content - */ - public Image getContentImage() { - return contentImage; - } - - /** - * The number of words (or images) in this {@link Paragraph}. - * - * @return the number of words - */ - public long getWords() { - return words; - } - - /** - * The number of words (or images) in this {@link Paragraph}. - * - * @param words - * the number of words to set - */ - public void setWords(long words) { - this.words = words; - } - - /** - * Display a DEBUG {@link String} representation of this object. - */ - @Override - public String toString() { - return String.format("%s: [%s]", "" + type, content == null ? "N/A" - : content); - } - - @Override - public Paragraph clone() { - Paragraph para = null; - try { - para = (Paragraph) super.clone(); - } catch (CloneNotSupportedException e) { - // Did the clones rebel? - System.err.println(e); - } - - return para; - } -} diff --git a/src/be/nikiroo/fanfix/data/Story.java b/src/be/nikiroo/fanfix/data/Story.java deleted file mode 100644 index fc3f9098..00000000 --- a/src/be/nikiroo/fanfix/data/Story.java +++ /dev/null @@ -1,101 +0,0 @@ -package be.nikiroo.fanfix.data; - -import java.io.Serializable; -import java.util.ArrayList; -import java.util.Iterator; -import java.util.List; - -/** - * The main data class, where the whole story resides. - * - * @author niki - */ -public class Story implements Iterable, Cloneable, Serializable { - private static final long serialVersionUID = 1L; - - private MetaData meta; - private List chapters = new ArrayList(); - private List empty = new ArrayList(); - - /** - * The metadata about this {@link Story}. - * - * @return the meta - */ - public MetaData getMeta() { - return meta; - } - - /** - * The metadata about this {@link Story}. - * - * @param meta - * the meta to set - */ - public void setMeta(MetaData meta) { - this.meta = meta; - } - - /** - * The chapters of the story. - * - * @return the chapters - */ - public List getChapters() { - return chapters; - } - - /** - * The chapters of the story. - * - * @param chapters - * the chapters to set - */ - public void setChapters(List chapters) { - this.chapters = chapters; - } - - /** - * Get an iterator on the {@link Chapter}s. - */ - @Override - public Iterator iterator() { - return chapters == null ? empty.iterator() : chapters.iterator(); - } - - /** - * Display a DEBUG {@link String} representation of this object. - *

- * This is not efficient, nor intended to be. - */ - @Override - public String toString() { - if (getMeta() != null) - return "Story: [\n" + getMeta().toString() + "\n]"; - return "Story: [ no metadata found ]"; - } - - @Override - public Story clone() { - Story story = null; - try { - story = (Story) super.clone(); - } catch (CloneNotSupportedException e) { - // Did the clones rebel? - System.err.println(e); - } - - if (meta != null) { - story.meta = meta.clone(); - } - - if (chapters != null) { - story.chapters = new ArrayList(); - for (Chapter chap : chapters) { - story.chapters.add(chap.clone()); - } - } - - return story; - } -} diff --git a/src/be/nikiroo/fanfix/data/package-info.java b/src/be/nikiroo/fanfix/data/package-info.java deleted file mode 100644 index 57db36b4..00000000 --- a/src/be/nikiroo/fanfix/data/package-info.java +++ /dev/null @@ -1,9 +0,0 @@ -/** - * This package contains the data structure used by the program, without the - * logic behind them. - *

- * All the classes inside are serializable. - * - * @author niki - */ -package be.nikiroo.fanfix.data; \ No newline at end of file diff --git a/src/be/nikiroo/fanfix/derename.sh b/src/be/nikiroo/fanfix/derename.sh deleted file mode 100755 index 6c8cbff0..00000000 --- a/src/be/nikiroo/fanfix/derename.sh +++ /dev/null @@ -1,11 +0,0 @@ -#!/bin/sh - -git status | grep renamed: | sed 's/[^:]*: *\([^>]*\) -> \(.*\)/\1>\2/g' | while read -r ln; do - old="`echo "$ln" | cut -f1 -d'>'`" - new="`echo "$ln" | cut -f2 -d'>'`" - mkdir -p "`dirname "$old"`" - git mv "$new" "$old" - rmdir "`dirname "$new"`" 2>/dev/null - true -done - diff --git a/src/be/nikiroo/fanfix/library/BasicLibrary.java b/src/be/nikiroo/fanfix/library/BasicLibrary.java deleted file mode 100644 index 586c4ef1..00000000 --- a/src/be/nikiroo/fanfix/library/BasicLibrary.java +++ /dev/null @@ -1,1080 +0,0 @@ -package be.nikiroo.fanfix.library; - -import java.io.File; -import java.io.IOException; -import java.net.URL; -import java.net.UnknownHostException; -import java.util.ArrayList; -import java.util.Collections; -import java.util.List; -import java.util.Map; -import java.util.TreeMap; - -import be.nikiroo.fanfix.Instance; -import be.nikiroo.fanfix.data.MetaData; -import be.nikiroo.fanfix.data.Story; -import be.nikiroo.fanfix.output.BasicOutput; -import be.nikiroo.fanfix.output.BasicOutput.OutputType; -import be.nikiroo.fanfix.supported.BasicSupport; -import be.nikiroo.fanfix.supported.SupportType; -import be.nikiroo.utils.Image; -import be.nikiroo.utils.Progress; -import be.nikiroo.utils.StringUtils; - -/** - * Manage a library of Stories: import, export, list, modify. - *

- * Each {@link Story} object will be associated with a (local to the library) - * unique ID, the LUID, which will be used to identify the {@link Story}. - *

- * Most of the {@link BasicLibrary} functions work on a partial (cover - * MAY not be included) {@link MetaData} object. - * - * @author niki - */ -abstract public class BasicLibrary { - /** - * A {@link BasicLibrary} status. - * - * @author niki - */ - public enum Status { - /** The library is ready and r/w. */ - READ_WRITE, - /** The library is ready, but read-only. */ - READ_ONLY, - /** The library is invalid (not correctly set up). */ - INVALID, - /** You are not allowed to access this library. */ - UNAUTHORIZED, - /** The library is currently out of commission. */ - UNAVAILABLE; - - /** - * The library is available (you can query it). - *

- * It does not specify if it is read-only or not. - * - * @return TRUE if it is - */ - public boolean isReady() { - return (this == READ_WRITE || this == READ_ONLY); - } - - /** - * This library can be modified (= you are allowed to modify it). - * - * @return TRUE if it is - */ - public boolean isWritable() { - return (this == READ_WRITE); - } - } - - /** - * Return a name for this library (the UI may display this). - *

- * Must not be NULL. - * - * @return the name, or an empty {@link String} if none - */ - public String getLibraryName() { - return ""; - } - - /** - * The library status. - * - * @return the current status - */ - public Status getStatus() { - return Status.READ_WRITE; - } - - /** - * Retrieve the main {@link File} corresponding to the given {@link Story}, - * which can be passed to an external reader or instance. - *

- * Do NOT alter this file. - * - * @param luid - * the Library UID of the story, can be NULL - * @param pg - * the optional {@link Progress} - * - * @return the corresponding {@link Story} - * - * @throws IOException - * in case of IOException - */ - public abstract File getFile(String luid, Progress pg) throws IOException; - - /** - * Return the cover image associated to this story. - * - * @param luid - * the Library UID of the story - * - * @return the cover image - * - * @throws IOException - * in case of IOException - */ - public abstract Image getCover(String luid) throws IOException; - - // TODO: ensure it is the main used interface - public MetaResultList getList(Progress pg) throws IOException { - return new MetaResultList(getMetas(pg)); - } - - // TODO: make something for (normal and custom) not-story covers - - /** - * Return the cover image associated to this source. - *

- * By default, return the custom cover if any, and if not, return the cover - * of the first story with this source. - * - * @param source - * the source - * - * @return the cover image or NULL - * - * @throws IOException - * in case of IOException - */ - public Image getSourceCover(String source) throws IOException { - Image custom = getCustomSourceCover(source); - if (custom != null) { - return custom; - } - - List metas = getList().filter(source, null, null); - if (metas.size() > 0) { - return getCover(metas.get(0).getLuid()); - } - - return null; - } - - /** - * Return the cover image associated to this author. - *

- * By default, return the custom cover if any, and if not, return the cover - * of the first story with this author. - * - * @param author - * the author - * - * @return the cover image or NULL - * - * @throws IOException - * in case of IOException - */ - public Image getAuthorCover(String author) throws IOException { - Image custom = getCustomAuthorCover(author); - if (custom != null) { - return custom; - } - - List metas = getList().filter(null, author, null); - if (metas.size() > 0) { - return getCover(metas.get(0).getLuid()); - } - - return null; - } - - /** - * Return the custom cover image associated to this source. - *

- * By default, return NULL. - * - * @param source - * the source to look for - * - * @return the custom cover or NULL if none - * - * @throws IOException - * in case of IOException - */ - @SuppressWarnings("unused") - public Image getCustomSourceCover(String source) throws IOException { - return null; - } - - /** - * Return the custom cover image associated to this author. - *

- * By default, return NULL. - * - * @param author - * the author to look for - * - * @return the custom cover or NULL if none - * - * @throws IOException - * in case of IOException - */ - @SuppressWarnings("unused") - public Image getCustomAuthorCover(String author) throws IOException { - return null; - } - - /** - * Set the source cover to the given story cover. - * - * @param source - * the source to change - * @param luid - * the story LUID - * - * @throws IOException - * in case of IOException - */ - public abstract void setSourceCover(String source, String luid) - throws IOException; - - /** - * Set the author cover to the given story cover. - * - * @param author - * the author to change - * @param luid - * the story LUID - * - * @throws IOException - * in case of IOException - */ - public abstract void setAuthorCover(String author, String luid) - throws IOException; - - /** - * Return the list of stories (represented by their {@link MetaData}, which - * MAY not have the cover included). - *

- * The returned list MUST be a copy, not the original one. - * - * @param pg - * the optional {@link Progress} - * - * @return the list (can be empty but not NULL) - * - * @throws IOException - * in case of IOException - */ - protected abstract List getMetas(Progress pg) throws IOException; - - /** - * Invalidate the {@link Story} cache (when the content should be re-read - * because it was changed). - */ - protected void invalidateInfo() { - invalidateInfo(null); - } - - /** - * Invalidate the {@link Story} cache (when the content is removed). - *

- * All the cache can be deleted if NULL is passed as meta. - * - * @param luid - * the LUID of the {@link Story} to clear from the cache, or NULL - * for all stories - */ - protected abstract void invalidateInfo(String luid); - - /** - * Invalidate the {@link Story} cache (when the content has changed, but we - * already have it) with the new given meta. - * - * @param meta - * the {@link Story} to clear from the cache - * - * @throws IOException - * in case of IOException - */ - protected abstract void updateInfo(MetaData meta) throws IOException; - - /** - * Return the next LUID that can be used. - * - * @return the next luid - */ - protected abstract int getNextId(); - - /** - * Delete the target {@link Story}. - * - * @param luid - * the LUID of the {@link Story} - * - * @throws IOException - * in case of I/O error or if the {@link Story} wa not found - */ - protected abstract void doDelete(String luid) throws IOException; - - /** - * Actually save the story to the back-end. - * - * @param story - * the {@link Story} to save - * @param pg - * the optional {@link Progress} - * - * @return the saved {@link Story} (which may have changed, especially - * regarding the {@link MetaData}) - * - * @throws IOException - * in case of I/O error - */ - protected abstract Story doSave(Story story, Progress pg) - throws IOException; - - /** - * Refresh the {@link BasicLibrary}, that is, make sure all metas are - * loaded. - * - * @param pg - * the optional progress reporter - */ - public void refresh(Progress pg) { - try { - getMetas(pg); - } catch (IOException e) { - // We will let it fail later - } - } - - /** - * Check if the {@link Story} denoted by this Library UID is present in the - * cache (if we have no cache, we default to true). - * - * @param luid - * the Library UID - * - * @return TRUE if it is - */ - public boolean isCached(String luid) { - // By default, everything is cached - return true; - } - - /** - * Clear the {@link Story} from the cache, if needed. - *

- * The next time we try to retrieve the {@link Story}, it may be required to - * cache it again. - * - * @param luid - * the story to clear - * - * @throws IOException - * in case of I/O error - */ - public void clearFromCache(String luid) throws IOException { - // By default, this is a noop. - } - - /** - * List all the known types (sources) of stories. - * - * @return the sources - * - * @throws IOException - * in case of IOException - */ - public List getSources() throws IOException { - List list = new ArrayList(); - for (MetaData meta : getMetas(null)) { - String storySource = meta.getSource(); - if (!list.contains(storySource)) { - list.add(storySource); - } - } - - Collections.sort(list); - return list; - } - - /** - * List all the known types (sources) of stories, grouped by directory - * ("Source_1/a" and "Source_1/b" will be grouped into "Source_1"). - *

- * Note that an empty item in the list means a non-grouped source (type) -- - * e.g., you could have for Source_1: - *

    - *
  • : empty, so source is "Source_1"
  • - *
  • a: empty, so source is "Source_1/a"
  • - *
  • b: empty, so source is "Source_1/b"
  • - *
- * - * @return the grouped list - * - * @throws IOException - * in case of IOException - */ - public Map> getSourcesGrouped() throws IOException { - Map> map = new TreeMap>(); - for (String source : getSources()) { - String name; - String subname; - - int pos = source.indexOf('/'); - if (pos > 0 && pos < source.length() - 1) { - name = source.substring(0, pos); - subname = source.substring(pos + 1); - - } else { - name = source; - subname = ""; - } - - List list = map.get(name); - if (list == null) { - list = new ArrayList(); - map.put(name, list); - } - list.add(subname); - } - - return map; - } - - /** - * List all the known authors of stories. - * - * @return the authors - * - * @throws IOException - * in case of IOException - */ - public List getAuthors() throws IOException { - List list = new ArrayList(); - for (MetaData meta : getMetas(null)) { - String storyAuthor = meta.getAuthor(); - if (!list.contains(storyAuthor)) { - list.add(storyAuthor); - } - } - - Collections.sort(list); - return list; - } - - /** - * Return the list of authors, grouped by starting letter(s) if needed. - *

- * If the number of author is not too high, only one group with an empty - * name and all the authors will be returned. - *

- * If not, the authors will be separated into groups: - *

    - *
  • *: any author whose name doesn't contain letters nor numbers - *
  • - *
  • 0-9: any authors whose name starts with a number
  • - *
  • A-C (for instance): any author whose name starts with - * A, B or C
  • - *
- * Note that the letters used in the groups can vary (except * and - * 0-9, which may only be present or not). - * - * @return the authors' names, grouped by letter(s) - * - * @throws IOException - * in case of IOException - */ - public Map> getAuthorsGrouped() throws IOException { - int MAX = 20; - - Map> groups = new TreeMap>(); - List authors = getAuthors(); - - // If all authors fit the max, just report them as is - if (authors.size() <= MAX) { - groups.put("", authors); - return groups; - } - - // Create groups A to Z, which can be empty here - for (char car = 'A'; car <= 'Z'; car++) { - groups.put(Character.toString(car), getAuthorsGroup(authors, car)); - } - - // Collapse them - List keys = new ArrayList(groups.keySet()); - for (int i = 0; i + 1 < keys.size(); i++) { - String keyNow = keys.get(i); - String keyNext = keys.get(i + 1); - - List now = groups.get(keyNow); - List next = groups.get(keyNext); - - int currentTotal = now.size() + next.size(); - if (currentTotal <= MAX) { - String key = keyNow.charAt(0) + "-" - + keyNext.charAt(keyNext.length() - 1); - - List all = new ArrayList(); - all.addAll(now); - all.addAll(next); - - groups.remove(keyNow); - groups.remove(keyNext); - groups.put(key, all); - - keys.set(i, key); // set the new key instead of key(i) - keys.remove(i + 1); // remove the next, consumed key - i--; // restart at key(i) - } - } - - // Add "special" groups - groups.put("*", getAuthorsGroup(authors, '*')); - groups.put("0-9", getAuthorsGroup(authors, '0')); - - // Prune empty groups - keys = new ArrayList(groups.keySet()); - for (String key : keys) { - if (groups.get(key).isEmpty()) { - groups.remove(key); - } - } - - return groups; - } - - /** - * Get all the authors that start with the given character: - *
    - *
  • *: any author whose name doesn't contain letters nor numbers - *
  • - *
  • 0: any authors whose name starts with a number
  • - *
  • A (any capital latin letter): any author whose name starts - * with A
  • - *
- * - * @param authors - * the full list of authors - * @param car - * the starting character, *, 0 or a capital - * letter - * - * @return the authors that fulfil the starting letter - */ - private List getAuthorsGroup(List authors, char car) { - List accepted = new ArrayList(); - for (String author : authors) { - char first = '*'; - for (int i = 0; first == '*' && i < author.length(); i++) { - String san = StringUtils.sanitize(author, true, true); - char c = san.charAt(i); - if (c >= '0' && c <= '9') { - first = '0'; - } else if (c >= 'a' && c <= 'z') { - first = (char) (c - 'a' + 'A'); - } else if (c >= 'A' && c <= 'Z') { - first = c; - } - } - - if (first == car) { - accepted.add(author); - } - } - - return accepted; - } - - /** - * List all the stories in the {@link BasicLibrary}. - *

- * Cover images MAYBE not included. - * - * @return the stories - * - * @throws IOException - * in case of IOException - */ - public MetaResultList getList() throws IOException { - return getList(null); - } - - /** - * Retrieve a {@link MetaData} corresponding to the given {@link Story}, - * cover image MAY not be included. - * - * @param luid - * the Library UID of the story, can be NULL - * - * @return the corresponding {@link Story} or NULL if not found - * - * @throws IOException - * in case of IOException - */ - public MetaData getInfo(String luid) throws IOException { - if (luid != null) { - for (MetaData meta : getMetas(null)) { - if (luid.equals(meta.getLuid())) { - return meta; - } - } - } - - return null; - } - - /** - * Retrieve a specific {@link Story}. - * - * @param luid - * the Library UID of the story - * @param pg - * the optional progress reporter - * - * @return the corresponding {@link Story} or NULL if not found - * - * @throws IOException - * in case of IOException - */ - public synchronized Story getStory(String luid, Progress pg) - throws IOException { - Progress pgMetas = new Progress(); - Progress pgStory = new Progress(); - if (pg != null) { - pg.setMinMax(0, 100); - pg.addProgress(pgMetas, 10); - pg.addProgress(pgStory, 90); - } - - MetaData meta = null; - for (MetaData oneMeta : getMetas(pgMetas)) { - if (oneMeta.getLuid().equals(luid)) { - meta = oneMeta; - break; - } - } - - pgMetas.done(); - - Story story = getStory(luid, meta, pgStory); - pgStory.done(); - - return story; - } - - /** - * Retrieve a specific {@link Story}. - * - * @param luid - * the LUID of the story - * @param meta - * the meta of the story - * @param pg - * the optional progress reporter - * - * @return the corresponding {@link Story} or NULL if not found - * - * @throws IOException - * in case of IOException - */ - public synchronized Story getStory(String luid, MetaData meta, Progress pg) - throws IOException { - - if (pg == null) { - pg = new Progress(); - } - - Progress pgGet = new Progress(); - Progress pgProcess = new Progress(); - - pg.setMinMax(0, 2); - pg.addProgress(pgGet, 1); - pg.addProgress(pgProcess, 1); - - Story story = null; - File file = null; - - if (luid != null && meta != null) { - file = getFile(luid, pgGet); - } - - pgGet.done(); - try { - if (file != null) { - SupportType type = SupportType.valueOfAllOkUC(meta.getType()); - if (type == null) { - throw new IOException("Unknown type: " + meta.getType()); - } - - URL url = file.toURI().toURL(); - story = BasicSupport.getSupport(type, url) // - .process(pgProcess); - - // Because we do not want to clear the meta cache: - meta.setCover(story.getMeta().getCover()); - meta.setResume(story.getMeta().getResume()); - story.setMeta(meta); - } - } catch (IOException e) { - // We should not have not-supported files in the library - Instance.getInstance().getTraceHandler() - .error(new IOException(String.format( - "Cannot load file of type '%s' from library: %s", - meta.getType(), file), e)); - } finally { - pgProcess.done(); - pg.done(); - } - - return story; - } - - /** - * Import the {@link Story} at the given {@link URL} into the - * {@link BasicLibrary}. - * - * @param url - * the {@link URL} to import - * @param pg - * the optional progress reporter - * - * @return the imported Story {@link MetaData} - * - * @throws UnknownHostException - * if the host is not supported - * @throws IOException - * in case of I/O error - */ - public MetaData imprt(URL url, Progress pg) throws IOException { - if (pg == null) - pg = new Progress(); - - pg.setMinMax(0, 1000); - Progress pgProcess = new Progress(); - Progress pgSave = new Progress(); - pg.addProgress(pgProcess, 800); - pg.addProgress(pgSave, 200); - - BasicSupport support = BasicSupport.getSupport(url); - if (support == null) { - throw new UnknownHostException("" + url); - } - - Story story = save(support.process(pgProcess), pgSave); - pg.setName(story.getMeta().getTitle()); - pg.done(); - - return story.getMeta(); - } - - /** - * Import the story from one library to another, and keep the same LUID. - * - * @param other - * the other library to import from - * @param luid - * the Library UID - * @param pg - * the optional progress reporter - * - * @throws IOException - * in case of I/O error - */ - public void imprt(BasicLibrary other, String luid, Progress pg) - throws IOException { - Progress pgGetStory = new Progress(); - Progress pgSave = new Progress(); - if (pg == null) { - pg = new Progress(); - } - - pg.setMinMax(0, 2); - pg.addProgress(pgGetStory, 1); - pg.addProgress(pgSave, 1); - - Story story = other.getStory(luid, pgGetStory); - if (story != null) { - story = this.save(story, luid, pgSave); - pg.done(); - } else { - pg.done(); - throw new IOException("Cannot find story in Library: " + luid); - } - } - - /** - * Export the {@link Story} to the given target in the given format. - * - * @param luid - * the {@link Story} ID - * @param type - * the {@link OutputType} to transform it to - * @param target - * the target to save to - * @param pg - * the optional progress reporter - * - * @return the saved resource (the main saved {@link File}) - * - * @throws IOException - * in case of I/O error - */ - public File export(String luid, OutputType type, String target, Progress pg) - throws IOException { - Progress pgGetStory = new Progress(); - Progress pgOut = new Progress(); - if (pg != null) { - pg.setMax(2); - pg.addProgress(pgGetStory, 1); - pg.addProgress(pgOut, 1); - } - - BasicOutput out = BasicOutput.getOutput(type, false, false); - if (out == null) { - throw new IOException("Output type not supported: " + type); - } - - Story story = getStory(luid, pgGetStory); - if (story == null) { - throw new IOException("Cannot find story to export: " + luid); - } - - return out.process(story, target, pgOut); - } - - /** - * Save a {@link Story} to the {@link BasicLibrary}. - * - * @param story - * the {@link Story} to save - * @param pg - * the optional progress reporter - * - * @return the same {@link Story}, whose LUID may have changed - * - * @throws IOException - * in case of I/O error - */ - public Story save(Story story, Progress pg) throws IOException { - return save(story, null, pg); - } - - /** - * Save a {@link Story} to the {@link BasicLibrary} -- the LUID must - * be correct, or NULL to get the next free one. - *

- * Will override any previous {@link Story} with the same LUID. - * - * @param story - * the {@link Story} to save - * @param luid - * the correct LUID or NULL to get the next free one - * @param pg - * the optional progress reporter - * - * @return the same {@link Story}, whose LUID may have changed - * - * @throws IOException - * in case of I/O error - */ - public synchronized Story save(Story story, String luid, Progress pg) - throws IOException { - if (pg == null) { - pg = new Progress(); - } - - Instance.getInstance().getTraceHandler().trace( - this.getClass().getSimpleName() + ": saving story " + luid); - - // Do not change the original metadata, but change the original story - MetaData meta = story.getMeta().clone(); - story.setMeta(meta); - - pg.setName("Saving story"); - - if (luid == null || luid.isEmpty()) { - meta.setLuid(String.format("%03d", getNextId())); - } else { - meta.setLuid(luid); - } - - if (luid != null && getInfo(luid) != null) { - delete(luid); - } - - story = doSave(story, pg); - - updateInfo(story.getMeta()); - - Instance.getInstance().getTraceHandler() - .trace(this.getClass().getSimpleName() + ": story saved (" - + luid + ")"); - - pg.setName(meta.getTitle()); - pg.done(); - return story; - } - - /** - * Delete the given {@link Story} from this {@link BasicLibrary}. - * - * @param luid - * the LUID of the target {@link Story} - * - * @throws IOException - * in case of I/O error - */ - public synchronized void delete(String luid) throws IOException { - Instance.getInstance().getTraceHandler().trace( - this.getClass().getSimpleName() + ": deleting story " + luid); - - doDelete(luid); - invalidateInfo(luid); - - Instance.getInstance().getTraceHandler() - .trace(this.getClass().getSimpleName() + ": story deleted (" - + luid + ")"); - } - - /** - * Change the type (source) of the given {@link Story}. - * - * @param luid - * the {@link Story} LUID - * @param newSource - * the new source - * @param pg - * the optional progress reporter - * - * @throws IOException - * in case of I/O error or if the {@link Story} was not found - */ - public synchronized void changeSource(String luid, String newSource, - Progress pg) throws IOException { - MetaData meta = getInfo(luid); - if (meta == null) { - throw new IOException("Story not found: " + luid); - } - - changeSTA(luid, newSource, meta.getTitle(), meta.getAuthor(), pg); - } - - /** - * Change the title (name) of the given {@link Story}. - * - * @param luid - * the {@link Story} LUID - * @param newTitle - * the new title - * @param pg - * the optional progress reporter - * - * @throws IOException - * in case of I/O error or if the {@link Story} was not found - */ - public synchronized void changeTitle(String luid, String newTitle, - Progress pg) throws IOException { - MetaData meta = getInfo(luid); - if (meta == null) { - throw new IOException("Story not found: " + luid); - } - - changeSTA(luid, meta.getSource(), newTitle, meta.getAuthor(), pg); - } - - /** - * Change the author of the given {@link Story}. - * - * @param luid - * the {@link Story} LUID - * @param newAuthor - * the new author - * @param pg - * the optional progress reporter - * - * @throws IOException - * in case of I/O error or if the {@link Story} was not found - */ - public synchronized void changeAuthor(String luid, String newAuthor, - Progress pg) throws IOException { - MetaData meta = getInfo(luid); - if (meta == null) { - throw new IOException("Story not found: " + luid); - } - - changeSTA(luid, meta.getSource(), meta.getTitle(), newAuthor, pg); - } - - /** - * Change the Source, Title and Author of the {@link Story} in one single - * go. - * - * @param luid - * the {@link Story} LUID - * @param newSource - * the new source - * @param newTitle - * the new title - * @param newAuthor - * the new author - * @param pg - * the optional progress reporter - * - * @throws IOException - * in case of I/O error or if the {@link Story} was not found - */ - protected synchronized void changeSTA(String luid, String newSource, - String newTitle, String newAuthor, Progress pg) throws IOException { - MetaData meta = getInfo(luid); - if (meta == null) { - throw new IOException("Story not found: " + luid); - } - - meta.setSource(newSource); - meta.setTitle(newTitle); - meta.setAuthor(newAuthor); - saveMeta(meta, pg); - } - - /** - * Save back the current state of the {@link MetaData} (LUID MUST NOT - * change) for this {@link Story}. - *

- * By default, delete the old {@link Story} then recreate a new - * {@link Story}. - *

- * Note that this behaviour can lead to data loss in case of problems! - * - * @param meta - * the new {@link MetaData} (LUID MUST NOT change) - * @param pg - * the optional {@link Progress} - * - * @throws IOException - * in case of I/O error or if the {@link Story} was not found - */ - protected synchronized void saveMeta(MetaData meta, Progress pg) - throws IOException { - if (pg == null) { - pg = new Progress(); - } - - Progress pgGet = new Progress(); - Progress pgSet = new Progress(); - pg.addProgress(pgGet, 50); - pg.addProgress(pgSet, 50); - - Story story = getStory(meta.getLuid(), pgGet); - if (story == null) { - throw new IOException("Story not found: " + meta.getLuid()); - } - - // TODO: this is not safe! - delete(meta.getLuid()); - story.setMeta(meta); - save(story, meta.getLuid(), pgSet); - - pg.done(); - } -} diff --git a/src/be/nikiroo/fanfix/library/CacheLibrary.java b/src/be/nikiroo/fanfix/library/CacheLibrary.java deleted file mode 100644 index 23f19a9e..00000000 --- a/src/be/nikiroo/fanfix/library/CacheLibrary.java +++ /dev/null @@ -1,434 +0,0 @@ -package be.nikiroo.fanfix.library; - -import java.io.File; -import java.io.IOException; -import java.net.URL; -import java.util.ArrayList; -import java.util.List; -import java.util.TreeSet; - -import be.nikiroo.fanfix.Instance; -import be.nikiroo.fanfix.bundles.UiConfig; -import be.nikiroo.fanfix.bundles.UiConfigBundle; -import be.nikiroo.fanfix.data.MetaData; -import be.nikiroo.fanfix.data.Story; -import be.nikiroo.fanfix.output.BasicOutput.OutputType; -import be.nikiroo.utils.Image; -import be.nikiroo.utils.Progress; - -/** - * This library will cache another pre-existing {@link BasicLibrary}. - * - * @author niki - */ -public class CacheLibrary extends BasicLibrary { - private List metasReal; - private List metasMixed; - private Object metasLock = new Object(); - - private BasicLibrary lib; - private LocalLibrary cacheLib; - - /** - * Create a cache library around the given one. - *

- * It will return the same result, but those will be saved to disk at the - * same time to be fetched quicker the next time. - * - * @param cacheDir - * the cache directory where to save the files to disk - * @param lib - * the original library to wrap - * @param config - * the configuration used to know which kind of default - * {@link OutputType} to use for images and non-images stories - */ - public CacheLibrary(File cacheDir, BasicLibrary lib, - UiConfigBundle config) { - this.cacheLib = new LocalLibrary(cacheDir, // - config.getString(UiConfig.GUI_NON_IMAGES_DOCUMENT_TYPE), - config.getString(UiConfig.GUI_IMAGES_DOCUMENT_TYPE), true); - this.lib = lib; - } - - @Override - public String getLibraryName() { - return lib.getLibraryName(); - } - - @Override - public Status getStatus() { - return lib.getStatus(); - } - - @Override - protected List getMetas(Progress pg) throws IOException { - if (pg == null) { - pg = new Progress(); - } - - synchronized (metasLock) { - // We make sure that cached metas have precedence - if (metasMixed == null) { - if (metasReal == null) { - metasReal = lib.getMetas(pg); - } - - metasMixed = new ArrayList(); - TreeSet cachedLuids = new TreeSet(); - for (MetaData cachedMeta : cacheLib.getMetas(null)) { - metasMixed.add(cachedMeta); - cachedLuids.add(cachedMeta.getLuid()); - } - for (MetaData realMeta : metasReal) { - if (!cachedLuids.contains(realMeta.getLuid())) { - metasMixed.add(realMeta); - } - } - } - } - - pg.done(); - return new ArrayList(metasMixed); - } - - @Override - public synchronized Story getStory(String luid, MetaData meta, Progress pg) - throws IOException { - if (pg == null) { - pg = new Progress(); - } - - Progress pgImport = new Progress(); - Progress pgGet = new Progress(); - - pg.setMinMax(0, 4); - pg.addProgress(pgImport, 3); - pg.addProgress(pgGet, 1); - - if (!isCached(luid)) { - try { - cacheLib.imprt(lib, luid, pgImport); - updateMetaCache(metasMixed, cacheLib.getInfo(luid)); - pgImport.done(); - } catch (IOException e) { - Instance.getInstance().getTraceHandler().error(e); - } - - pgImport.done(); - pgGet.done(); - } - - String type = cacheLib.getOutputType(meta.isImageDocument()); - MetaData cachedMeta = meta.clone(); - cachedMeta.setType(type); - - return cacheLib.getStory(luid, cachedMeta, pg); - } - - @Override - public synchronized File getFile(final String luid, Progress pg) - throws IOException { - if (pg == null) { - pg = new Progress(); - } - - Progress pgGet = new Progress(); - Progress pgRecall = new Progress(); - - pg.setMinMax(0, 5); - pg.addProgress(pgGet, 4); - pg.addProgress(pgRecall, 1); - - if (!isCached(luid)) { - getStory(luid, pgGet); - pgGet.done(); - } - - File file = cacheLib.getFile(luid, pgRecall); - pgRecall.done(); - - pg.done(); - return file; - } - - @Override - public Image getCover(final String luid) throws IOException { - if (isCached(luid)) { - return cacheLib.getCover(luid); - } - - // We could update the cache here, but it's not easy - return lib.getCover(luid); - } - - @Override - public Image getSourceCover(String source) throws IOException { - Image custom = getCustomSourceCover(source); - if (custom != null) { - return custom; - } - - Image cached = cacheLib.getSourceCover(source); - if (cached != null) { - return cached; - } - - return lib.getSourceCover(source); - } - - @Override - public Image getAuthorCover(String author) throws IOException { - Image custom = getCustomAuthorCover(author); - if (custom != null) { - return custom; - } - - Image cached = cacheLib.getAuthorCover(author); - if (cached != null) { - return cached; - } - - return lib.getAuthorCover(author); - } - - @Override - public Image getCustomSourceCover(String source) throws IOException { - Image custom = cacheLib.getCustomSourceCover(source); - if (custom == null) { - custom = lib.getCustomSourceCover(source); - if (custom != null) { - cacheLib.setSourceCover(source, custom); - } - } - - return custom; - } - - @Override - public Image getCustomAuthorCover(String author) throws IOException { - Image custom = cacheLib.getCustomAuthorCover(author); - if (custom == null) { - custom = lib.getCustomAuthorCover(author); - if (custom != null) { - cacheLib.setAuthorCover(author, custom); - } - } - - return custom; - } - - @Override - public void setSourceCover(String source, String luid) throws IOException { - lib.setSourceCover(source, luid); - cacheLib.setSourceCover(source, getCover(luid)); - } - - @Override - public void setAuthorCover(String author, String luid) throws IOException { - lib.setAuthorCover(author, luid); - cacheLib.setAuthorCover(author, getCover(luid)); - } - - /** - * Invalidate the {@link Story} cache (when the content has changed, but we - * already have it) with the new given meta. - *

- * Make sure to always use {@link MetaData} from the cached library in - * priority, here. - * - * @param meta - * the {@link Story} to clear from the cache - * - * @throws IOException - * in case of IOException - */ - @Override - @Deprecated - protected void updateInfo(MetaData meta) throws IOException { - throw new IOException( - "This method is not supported in a CacheLibrary, please use updateMetaCache"); - } - - // relplace the meta in Metas by Meta, add it if needed - // return TRUE = added - private boolean updateMetaCache(List metas, MetaData meta) { - if (meta != null && metas != null) { - synchronized (metasLock) { - boolean changed = false; - for (int i = 0; i < metas.size(); i++) { - if (metas.get(i).getLuid().equals(meta.getLuid())) { - metas.set(i, meta); - changed = true; - } - } - - if (!changed) { - metas.add(meta); - return true; - } - } - } - - return false; - } - - @Override - protected void invalidateInfo(String luid) { - if (luid == null) { - synchronized (metasLock) { - metasReal = null; - metasMixed = null; - } - } else { - invalidateInfo(metasReal, luid); - invalidateInfo(metasMixed, luid); - } - - cacheLib.invalidateInfo(luid); - lib.invalidateInfo(luid); - } - - // luid cannot be null - private void invalidateInfo(List metas, String luid) { - if (metas != null) { - synchronized (metasLock) { - for (int i = 0; i < metas.size(); i++) { - if (metas.get(i).getLuid().equals(luid)) { - metas.remove(i--); - } - } - } - } - } - - @Override - public synchronized Story save(Story story, String luid, Progress pg) - throws IOException { - Progress pgLib = new Progress(); - Progress pgCacheLib = new Progress(); - - if (pg == null) { - pg = new Progress(); - } - - pg.setMinMax(0, 2); - pg.addProgress(pgLib, 1); - pg.addProgress(pgCacheLib, 1); - - story = lib.save(story, luid, pgLib); - updateMetaCache(metasReal, story.getMeta()); - - story = cacheLib.save(story, story.getMeta().getLuid(), pgCacheLib); - updateMetaCache(metasMixed, story.getMeta()); - - return story; - } - - @Override - public void delete(String luid) throws IOException { - if (isCached(luid)) { - cacheLib.delete(luid); - } - lib.delete(luid); - - invalidateInfo(luid); - } - - @Override - protected synchronized void changeSTA(String luid, String newSource, - String newTitle, String newAuthor, Progress pg) throws IOException { - if (pg == null) { - pg = new Progress(); - } - - Progress pgCache = new Progress(); - Progress pgOrig = new Progress(); - pg.setMinMax(0, 2); - pg.addProgress(pgCache, 1); - pg.addProgress(pgOrig, 1); - - MetaData meta = getInfo(luid); - if (meta == null) { - throw new IOException("Story not found: " + luid); - } - - if (isCached(luid)) { - cacheLib.changeSTA(luid, newSource, newTitle, newAuthor, pgCache); - } - pgCache.done(); - - lib.changeSTA(luid, newSource, newTitle, newAuthor, pgOrig); - pgOrig.done(); - - meta.setSource(newSource); - meta.setTitle(newTitle); - meta.setAuthor(newAuthor); - pg.done(); - - if (isCached(luid)) { - updateMetaCache(metasMixed, meta); - updateMetaCache(metasReal, lib.getInfo(luid)); - } else { - updateMetaCache(metasReal, meta); - } - } - - @Override - public boolean isCached(String luid) { - try { - return cacheLib.getInfo(luid) != null; - } catch (IOException e) { - return false; - } - } - - @Override - public void clearFromCache(String luid) throws IOException { - if (isCached(luid)) { - cacheLib.delete(luid); - } - } - - @Override - public MetaData imprt(URL url, Progress pg) throws IOException { - if (pg == null) { - pg = new Progress(); - } - - Progress pgImprt = new Progress(); - Progress pgCache = new Progress(); - pg.setMinMax(0, 10); - pg.addProgress(pgImprt, 7); - pg.addProgress(pgCache, 3); - - MetaData meta = lib.imprt(url, pgImprt); - updateMetaCache(metasReal, meta); - synchronized (metasLock) { - metasMixed = null; - } - - clearFromCache(meta.getLuid()); - - pg.done(); - return meta; - } - - // All the following methods are only used by Save and Delete in - // BasicLibrary: - - @Override - protected int getNextId() { - throw new java.lang.InternalError("Should not have been called"); - } - - @Override - protected void doDelete(String luid) throws IOException { - throw new java.lang.InternalError("Should not have been called"); - } - - @Override - protected Story doSave(Story story, Progress pg) throws IOException { - throw new java.lang.InternalError("Should not have been called"); - } -} diff --git a/src/be/nikiroo/fanfix/library/LocalLibrary.java b/src/be/nikiroo/fanfix/library/LocalLibrary.java deleted file mode 100644 index 3cf5a25b..00000000 --- a/src/be/nikiroo/fanfix/library/LocalLibrary.java +++ /dev/null @@ -1,755 +0,0 @@ -package be.nikiroo.fanfix.library; - -import java.io.File; -import java.io.FileFilter; -import java.io.FileInputStream; -import java.io.FileNotFoundException; -import java.io.IOException; -import java.io.InputStream; -import java.util.ArrayList; -import java.util.HashMap; -import java.util.List; -import java.util.Map; - -import be.nikiroo.fanfix.Instance; -import be.nikiroo.fanfix.bundles.Config; -import be.nikiroo.fanfix.bundles.ConfigBundle; -import be.nikiroo.fanfix.data.MetaData; -import be.nikiroo.fanfix.data.Story; -import be.nikiroo.fanfix.output.BasicOutput; -import be.nikiroo.fanfix.output.BasicOutput.OutputType; -import be.nikiroo.fanfix.output.InfoCover; -import be.nikiroo.fanfix.supported.InfoReader; -import be.nikiroo.utils.IOUtils; -import be.nikiroo.utils.Image; -import be.nikiroo.utils.Progress; -import be.nikiroo.utils.StringUtils; - -/** - * This {@link BasicLibrary} will store the stories locally on disk. - * - * @author niki - */ -public class LocalLibrary extends BasicLibrary { - private int lastId; - private Object lock = new Object(); - private Map stories; // Files: [ infoFile, TargetFile ] - private Map sourceCovers; - private Map authorCovers; - - private File baseDir; - private OutputType text; - private OutputType image; - - /** - * Create a new {@link LocalLibrary} with the given back-end directory. - * - * @param baseDir - * the directory where to find the {@link Story} objects - * @param config - * the configuration used to know which kind of default - * {@link OutputType} to use for images and non-images stories - */ - public LocalLibrary(File baseDir, ConfigBundle config) { - this(baseDir, // - config.getString(Config.FILE_FORMAT_NON_IMAGES_DOCUMENT_TYPE), - config.getString(Config.FILE_FORMAT_IMAGES_DOCUMENT_TYPE), - false); - } - - /** - * Create a new {@link LocalLibrary} with the given back-end directory. - * - * @param baseDir - * the directory where to find the {@link Story} objects - * @param text - * the {@link OutputType} to use for non-image documents - * @param image - * the {@link OutputType} to use for image documents - * @param defaultIsHtml - * if the given text or image is invalid, use HTML by default (if - * not, it will be INFO_TEXT/CBZ by default) - */ - public LocalLibrary(File baseDir, String text, String image, - boolean defaultIsHtml) { - this(baseDir, - OutputType.valueOfAllOkUC(text, - defaultIsHtml ? OutputType.HTML : OutputType.INFO_TEXT), - OutputType.valueOfAllOkUC(image, - defaultIsHtml ? OutputType.HTML : OutputType.CBZ)); - } - - /** - * Create a new {@link LocalLibrary} with the given back-end directory. - * - * @param baseDir - * the directory where to find the {@link Story} objects - * @param text - * the {@link OutputType} to use for non-image documents - * @param image - * the {@link OutputType} to use for image documents - */ - public LocalLibrary(File baseDir, OutputType text, OutputType image) { - this.baseDir = baseDir; - this.text = text; - this.image = image; - - this.lastId = 0; - this.stories = null; - this.sourceCovers = null; - - baseDir.mkdirs(); - } - - @Override - protected List getMetas(Progress pg) { - return new ArrayList(getStories(pg).keySet()); - } - - @Override - public File getFile(String luid, Progress pg) throws IOException { - Instance.getInstance().getTraceHandler().trace( - this.getClass().getSimpleName() + ": get file for " + luid); - - File file = null; - String mess = "no file found for "; - - MetaData meta = getInfo(luid); - if (meta != null) { - File[] files = getStories(pg).get(meta); - if (files != null) { - mess = "file retrieved for "; - file = files[1]; - } - } - - Instance.getInstance().getTraceHandler() - .trace(this.getClass().getSimpleName() + ": " + mess + luid - + " (" + meta.getTitle() + ")"); - - return file; - } - - @Override - public Image getCover(String luid) throws IOException { - MetaData meta = getInfo(luid); - if (meta != null) { - if (meta.getCover() != null) { - return meta.getCover(); - } - - File[] files = getStories(null).get(meta); - if (files != null) { - File infoFile = files[0]; - - try { - meta = InfoReader.readMeta(infoFile, true); - return meta.getCover(); - } catch (IOException e) { - Instance.getInstance().getTraceHandler().error(e); - } - } - } - - return null; - } - - @Override - protected void updateInfo(MetaData meta) { - invalidateInfo(); - } - - @Override - protected void invalidateInfo(String luid) { - synchronized (lock) { - stories = null; - sourceCovers = null; - } - } - - @Override - protected int getNextId() { - getStories(null); // make sure lastId is set - - synchronized (lock) { - return ++lastId; - } - } - - @Override - protected void doDelete(String luid) throws IOException { - for (File file : getRelatedFiles(luid)) { - // TODO: throw an IOException if we cannot delete the files? - IOUtils.deltree(file); - file.getParentFile().delete(); - } - } - - @Override - protected Story doSave(Story story, Progress pg) throws IOException { - MetaData meta = story.getMeta(); - - File expectedTarget = getExpectedFile(meta); - expectedTarget.getParentFile().mkdirs(); - - BasicOutput it = BasicOutput.getOutput(getOutputType(meta), true, true); - it.process(story, expectedTarget.getPath(), pg); - - return story; - } - - @Override - protected synchronized void saveMeta(MetaData meta, Progress pg) - throws IOException { - File newDir = getExpectedDir(meta.getSource()); - if (!newDir.exists()) { - newDir.mkdirs(); - } - - List relatedFiles = getRelatedFiles(meta.getLuid()); - for (File relatedFile : relatedFiles) { - // TODO: this is not safe at all. - // We should copy all the files THEN delete them - // Maybe also adding some rollback cleanup if possible - if (relatedFile.getName().endsWith(".info")) { - try { - String name = relatedFile.getName().replaceFirst("\\.info$", - ""); - relatedFile.delete(); - InfoCover.writeInfo(newDir, name, meta); - relatedFile.getParentFile().delete(); - } catch (IOException e) { - Instance.getInstance().getTraceHandler().error(e); - } - } else { - relatedFile.renameTo(new File(newDir, relatedFile.getName())); - relatedFile.getParentFile().delete(); - } - } - - updateInfo(meta); - } - - @Override - public Image getCustomSourceCover(String source) { - synchronized (lock) { - if (sourceCovers == null) { - sourceCovers = new HashMap(); - } - } - - synchronized (lock) { - Image img = sourceCovers.get(source); - if (img != null) { - return img; - } - } - - File coverDir = getExpectedDir(source); - if (coverDir.isDirectory()) { - File cover = new File(coverDir, ".cover.png"); - if (cover.exists()) { - InputStream in; - try { - in = new FileInputStream(cover); - try { - synchronized (lock) { - sourceCovers.put(source, new Image(in)); - } - } finally { - in.close(); - } - } catch (FileNotFoundException e) { - e.printStackTrace(); - } catch (IOException e) { - Instance.getInstance().getTraceHandler() - .error(new IOException( - "Cannot load the existing custom source cover: " - + cover, - e)); - } - } - } - - synchronized (lock) { - return sourceCovers.get(source); - } - } - - @Override - public Image getCustomAuthorCover(String author) { - synchronized (lock) { - if (authorCovers == null) { - authorCovers = new HashMap(); - } - } - - synchronized (lock) { - Image img = authorCovers.get(author); - if (img != null) { - return img; - } - } - - File cover = getAuthorCoverFile(author); - if (cover.exists()) { - InputStream in; - try { - in = new FileInputStream(cover); - try { - synchronized (lock) { - authorCovers.put(author, new Image(in)); - } - } finally { - in.close(); - } - } catch (FileNotFoundException e) { - e.printStackTrace(); - } catch (IOException e) { - Instance.getInstance().getTraceHandler() - .error(new IOException( - "Cannot load the existing custom author cover: " - + cover, - e)); - } - } - - synchronized (lock) { - return authorCovers.get(author); - } - } - - @Override - public void setSourceCover(String source, String luid) throws IOException { - setSourceCover(source, getCover(luid)); - } - - @Override - public void setAuthorCover(String author, String luid) throws IOException { - setAuthorCover(author, getCover(luid)); - } - - /** - * Set the source cover to the given story cover. - * - * @param source - * the source to change - * @param coverImage - * the cover image - */ - void setSourceCover(String source, Image coverImage) { - File dir = getExpectedDir(source); - dir.mkdirs(); - File cover = new File(dir, ".cover"); - try { - Instance.getInstance().getCache().saveAsImage(coverImage, cover, - true); - synchronized (lock) { - if (sourceCovers != null) { - sourceCovers.put(source, coverImage); - } - } - } catch (IOException e) { - Instance.getInstance().getTraceHandler().error(e); - } - } - - /** - * Set the author cover to the given story cover. - * - * @param author - * the author to change - * @param coverImage - * the cover image - */ - void setAuthorCover(String author, Image coverImage) { - File cover = getAuthorCoverFile(author); - cover.getParentFile().mkdirs(); - try { - Instance.getInstance().getCache().saveAsImage(coverImage, cover, - true); - synchronized (lock) { - if (authorCovers != null) { - authorCovers.put(author, coverImage); - } - } - } catch (IOException e) { - Instance.getInstance().getTraceHandler().error(e); - } - } - - @Override - public void imprt(BasicLibrary other, String luid, Progress pg) - throws IOException { - if (pg == null) { - pg = new Progress(); - } - - // Check if we can simply copy the files instead of the whole process - if (other instanceof LocalLibrary) { - LocalLibrary otherLocalLibrary = (LocalLibrary) other; - - MetaData meta = otherLocalLibrary.getInfo(luid); - String expectedType = "" - + (meta != null && meta.isImageDocument() ? image : text); - if (meta != null && meta.getType().equals(expectedType)) { - File from = otherLocalLibrary.getExpectedDir(meta.getSource()); - File to = this.getExpectedDir(meta.getSource()); - List relatedFiles = otherLocalLibrary - .getRelatedFiles(luid); - if (!relatedFiles.isEmpty()) { - pg.setMinMax(0, relatedFiles.size()); - } - - for (File relatedFile : relatedFiles) { - File target = new File(relatedFile.getAbsolutePath() - .replace(from.getAbsolutePath(), - to.getAbsolutePath())); - if (!relatedFile.equals(target)) { - target.getParentFile().mkdirs(); - InputStream in = null; - try { - in = new FileInputStream(relatedFile); - IOUtils.write(in, target); - } catch (IOException e) { - if (in != null) { - try { - in.close(); - } catch (Exception ee) { - } - } - - pg.done(); - throw e; - } - } - - pg.add(1); - } - - invalidateInfo(); - pg.done(); - return; - } - } - - super.imprt(other, luid, pg); - } - - /** - * Return the {@link OutputType} for this {@link Story}. - * - * @param meta - * the {@link Story} {@link MetaData} - * - * @return the type - */ - private OutputType getOutputType(MetaData meta) { - if (meta != null && meta.isImageDocument()) { - return image; - } - - return text; - } - - /** - * Return the default {@link OutputType} for this kind of {@link Story}. - * - * @param imageDocument - * TRUE for images document, FALSE for text documents - * - * @return the type - */ - public String getOutputType(boolean imageDocument) { - if (imageDocument) { - return image.toString(); - } - - return text.toString(); - } - - /** - * Get the target {@link File} related to the given .info - * {@link File} and {@link MetaData}. - * - * @param meta - * the meta - * @param infoFile - * the .info {@link File} - * - * @return the target {@link File} - */ - private File getTargetFile(MetaData meta, File infoFile) { - // Replace .info with whatever is needed: - String path = infoFile.getPath(); - path = path.substring(0, path.length() - ".info".length()); - String newExt = getOutputType(meta).getDefaultExtension(true); - - return new File(path + newExt); - } - - /** - * The target (full path) where the {@link Story} related to this - * {@link MetaData} should be located on disk for a new {@link Story}. - * - * @param key - * the {@link Story} {@link MetaData} - * - * @return the target - */ - private File getExpectedFile(MetaData key) { - String title = key.getTitle(); - if (title == null) { - title = ""; - } - title = title.replaceAll("[^a-zA-Z0-9._+-]", "_"); - if (title.length() > 40) { - title = title.substring(0, 40); - } - return new File(getExpectedDir(key.getSource()), - key.getLuid() + "_" + title); - } - - /** - * The directory (full path) where the new {@link Story} related to this - * {@link MetaData} should be located on disk. - * - * @param source - * the type (source) - * - * @return the target directory - */ - private File getExpectedDir(String source) { - String sanitizedSource = source.replaceAll("[^a-zA-Z0-9._+/-]", "_"); - - while (sanitizedSource.startsWith("/") - || sanitizedSource.startsWith("_")) { - if (sanitizedSource.length() > 1) { - sanitizedSource = sanitizedSource.substring(1); - } else { - sanitizedSource = ""; - } - } - - sanitizedSource = sanitizedSource.replace("/", File.separator); - - if (sanitizedSource.isEmpty()) { - sanitizedSource = "_EMPTY"; - } - - return new File(baseDir, sanitizedSource); - } - - /** - * Return the full path to the file to use for the custom cover of this - * author. - *

- * One or more of the parent directories MAY not exist. - * - * @param author - * the author - * - * @return the custom cover file - */ - private File getAuthorCoverFile(String author) { - File aDir = new File(baseDir, "_AUTHORS"); - String hash = StringUtils.getMd5Hash(author); - String ext = Instance.getInstance().getConfig() - .getString(Config.FILE_FORMAT_IMAGE_FORMAT_COVER); - return new File(aDir, hash + "." + ext.toLowerCase()); - } - - /** - * Return the list of files/directories on disk for this {@link Story}. - *

- * If the {@link Story} is not found, and empty list is returned. - * - * @param luid - * the {@link Story} LUID - * - * @return the list of {@link File}s - * - * @throws IOException - * if the {@link Story} was not found - */ - private List getRelatedFiles(String luid) throws IOException { - List files = new ArrayList(); - - MetaData meta = getInfo(luid); - if (meta == null) { - throw new IOException("Story not found: " + luid); - } - - File infoFile = getStories(null).get(meta)[0]; - File targetFile = getStories(null).get(meta)[1]; - - files.add(infoFile); - files.add(targetFile); - - String readerExt = getOutputType(meta).getDefaultExtension(true); - String fileExt = getOutputType(meta).getDefaultExtension(false); - - String path = targetFile.getAbsolutePath(); - if (readerExt != null && !readerExt.equals(fileExt)) { - path = path.substring(0, path.length() - readerExt.length()) - + fileExt; - File relatedFile = new File(path); - - if (relatedFile.exists()) { - files.add(relatedFile); - } - } - - String coverExt = "." + Instance.getInstance().getConfig() - .getString(Config.FILE_FORMAT_IMAGE_FORMAT_COVER).toLowerCase(); - File coverFile = new File(path + coverExt); - if (!coverFile.exists()) { - coverFile = new File( - path.substring(0, path.length() - fileExt.length()) - + coverExt); - } - - if (coverFile.exists()) { - files.add(coverFile); - } - - return files; - } - - /** - * Fill the list of stories by reading the content of the local directory - * {@link LocalLibrary#baseDir}. - *

- * Will use a cached list when possible (see - * {@link BasicLibrary#invalidateInfo()}). - * - * @param pg - * the optional {@link Progress} - * - * @return the list of stories (for each item, the first {@link File} is the - * info file, the second file is the target {@link File}) - */ - private Map getStories(Progress pg) { - if (pg == null) { - pg = new Progress(); - } else { - pg.setMinMax(0, 100); - } - - Map stories = this.stories; - synchronized (lock) { - if (stories == null) { - stories = getStoriesDo(pg); - this.stories = stories; - } - } - - pg.done(); - return stories; - - } - - /** - * Actually do the work of {@link LocalLibrary#getStories(Progress)} (i.e., - * do not retrieve the cache). - * - * @param pg - * the optional {@link Progress} - * - * @return the list of stories (for each item, the first {@link File} is the - * info file, the second file is the target {@link File}) - */ - private synchronized Map getStoriesDo(Progress pg) { - if (pg == null) { - pg = new Progress(); - } else { - pg.setMinMax(0, 100); - } - - Map stories = new HashMap(); - - File[] dirs = baseDir.listFiles(new FileFilter() { - @Override - public boolean accept(File file) { - return file != null && file.isDirectory(); - } - }); - - if (dirs != null) { - Progress pgDirs = new Progress(0, 100 * dirs.length); - pg.addProgress(pgDirs, 100); - - for (File dir : dirs) { - Progress pgFiles = new Progress(); - pgDirs.addProgress(pgFiles, 100); - pgDirs.setName("Loading from: " + dir.getName()); - - addToStories(stories, pgFiles, dir); - - pgFiles.setName(null); - } - - pgDirs.setName("Loading directories"); - } - - pg.done(); - - return stories; - } - - private void addToStories(Map stories, Progress pgFiles, - File dir) { - File[] infoFilesAndSubdirs = dir.listFiles(new FileFilter() { - @Override - public boolean accept(File file) { - boolean info = file != null && file.isFile() - && file.getPath().toLowerCase().endsWith(".info"); - boolean dir = file != null && file.isDirectory(); - boolean isExpandedHtml = new File(file, "index.html").isFile(); - return info || (dir && !isExpandedHtml); - } - }); - - if (pgFiles != null) { - pgFiles.setMinMax(0, infoFilesAndSubdirs.length); - } - - for (File infoFileOrSubdir : infoFilesAndSubdirs) { - if (pgFiles != null) { - pgFiles.setName(infoFileOrSubdir.getName()); - } - - if (infoFileOrSubdir.isDirectory()) { - addToStories(stories, null, infoFileOrSubdir); - } else { - try { - MetaData meta = InfoReader.readMeta(infoFileOrSubdir, - false); - try { - int id = Integer.parseInt(meta.getLuid()); - if (id > lastId) { - lastId = id; - } - - stories.put(meta, new File[] { infoFileOrSubdir, - getTargetFile(meta, infoFileOrSubdir) }); - } catch (Exception e) { - // not normal!! - throw new IOException("Cannot understand the LUID of " - + infoFileOrSubdir + ": " + meta.getLuid(), e); - } - } catch (IOException e) { - // We should not have not-supported files in the - // library - Instance.getInstance().getTraceHandler().error( - new IOException("Cannot load file from library: " - + infoFileOrSubdir, e)); - } - } - - if (pgFiles != null) { - pgFiles.add(1); - } - } - } -} diff --git a/src/be/nikiroo/fanfix/library/MetaResultList.java b/src/be/nikiroo/fanfix/library/MetaResultList.java deleted file mode 100644 index b5f33122..00000000 --- a/src/be/nikiroo/fanfix/library/MetaResultList.java +++ /dev/null @@ -1,190 +0,0 @@ -package be.nikiroo.fanfix.library; - -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Collections; -import java.util.Comparator; -import java.util.List; - -import be.nikiroo.fanfix.data.MetaData; - -public class MetaResultList { - private List metas; - - // Lazy lists: - // TODO: sync-protect them? - private List sources; - private List authors; - private List tags; - - // can be null (will consider it empty) - public MetaResultList(List metas) { - if (metas == null) { - metas = new ArrayList(); - } - - Collections.sort(metas); - this.metas = metas; - } - - // not NULL - // sorted - public List getMetas() { - return metas; - } - - public List getSources() { - if (sources == null) { - sources = new ArrayList(); - for (MetaData meta : metas) { - if (!sources.contains(meta.getSource())) - sources.add(meta.getSource()); - } - sort(sources); - } - - return sources; - } - - // A -> (A), A/ -> (A, A/*) if we can find something for "*" - public List getSources(String source) { - List linked = new ArrayList(); - if (source != null && !source.isEmpty()) { - if (!source.endsWith("/")) { - linked.add(source); - } else { - linked.add(source.substring(0, source.length() - 1)); - for (String src : getSources()) { - if (src.startsWith(source)) { - linked.add(src); - } - } - } - } - - sort(linked); - return linked; - } - - public List getAuthors() { - if (authors == null) { - authors = new ArrayList(); - for (MetaData meta : metas) { - if (!authors.contains(meta.getAuthor())) - authors.add(meta.getAuthor()); - } - sort(authors); - } - - return authors; - } - - public List getTags() { - if (tags == null) { - tags = new ArrayList(); - for (MetaData meta : metas) { - for (String tag : meta.getTags()) { - if (!tags.contains(tag)) - tags.add(tag); - } - } - sort(tags); - } - - return tags; - } - - // helper - public List filter(String source, String author, String tag) { - List sources = source == null ? null : Arrays.asList(source); - List authors = author == null ? null : Arrays.asList(author); - List tags = tag == null ? null : Arrays.asList(tag); - - return filter(sources, authors, tags); - } - - // null or empty -> no check, rest = must be included - // source: a source ending in "/" means "this or any source starting with - // this", - // i;e., to enable source hierarchy - // + sorted - public List filter(List sources, List authors, - List tags) { - if (sources != null && sources.isEmpty()) - sources = null; - if (authors != null && authors.isEmpty()) - authors = null; - if (tags != null && tags.isEmpty()) - tags = null; - - // Quick check - if (sources == null && authors == null && tags == null) { - return metas; - } - - // allow "sources/" hierarchy - if (sources != null) { - List folders = new ArrayList(); - List leaves = new ArrayList(); - for (String source : sources) { - if (source.endsWith("/")) { - if (!folders.contains(source)) - folders.add(source); - } else { - if (!leaves.contains(source)) - leaves.add(source); - } - } - - sources = leaves; - for (String folder : folders) { - for (String otherLeaf : getSources(folder)) { - if (!sources.contains(otherLeaf)) { - sources.add(otherLeaf); - } - } - } - } - - List result = new ArrayList(); - for (MetaData meta : metas) { - if (sources != null && !sources.contains(meta.getSource())) { - continue; - } - if (authors != null && !authors.contains(meta.getAuthor())) { - continue; - } - - if (tags != null) { - boolean keep = false; - for (String thisTag : meta.getTags()) { - if (tags.contains(thisTag)) - keep = true; - } - - if (!keep) - continue; - } - - result.add(meta); - } - - Collections.sort(result); - return result; - } - - /** - * Sort the given {@link String} values, ignoring case. - * - * @param values - * the values to sort - */ - private void sort(List values) { - Collections.sort(values, new Comparator() { - @Override - public int compare(String o1, String o2) { - return ("" + o1).compareToIgnoreCase("" + o2); - } - }); - } -} diff --git a/src/be/nikiroo/fanfix/library/RemoteLibrary.java b/src/be/nikiroo/fanfix/library/RemoteLibrary.java deleted file mode 100644 index bbe772a8..00000000 --- a/src/be/nikiroo/fanfix/library/RemoteLibrary.java +++ /dev/null @@ -1,580 +0,0 @@ -package be.nikiroo.fanfix.library; - -import java.io.File; -import java.io.IOException; -import java.net.URL; -import java.net.UnknownHostException; -import java.util.ArrayList; -import java.util.List; - -import javax.net.ssl.SSLException; - -import be.nikiroo.fanfix.Instance; -import be.nikiroo.fanfix.data.MetaData; -import be.nikiroo.fanfix.data.Story; -import be.nikiroo.utils.Image; -import be.nikiroo.utils.Progress; -import be.nikiroo.utils.Version; -import be.nikiroo.utils.serial.server.ConnectActionClientObject; - -/** - * This {@link BasicLibrary} will access a remote server to list the available - * stories, and download the ones you try to load to the local directory - * specified in the configuration. - * - * @author niki - */ -public class RemoteLibrary extends BasicLibrary { - interface RemoteAction { - public void action(ConnectActionClientObject action) throws Exception; - } - - class RemoteConnectAction extends ConnectActionClientObject { - public RemoteConnectAction() throws IOException { - super(host, port, key); - } - - @Override - public Object send(Object data) throws IOException, - NoSuchFieldException, NoSuchMethodException, - ClassNotFoundException { - Object rep = super.send(data); - if (rep instanceof RemoteLibraryException) { - RemoteLibraryException remoteEx = (RemoteLibraryException) rep; - throw remoteEx.unwrapException(); - } - - return rep; - } - } - - private String host; - private int port; - private final String key; - private final String subkey; - - // informative only (server will make the actual checks) - private boolean rw; - - /** - * Create a {@link RemoteLibrary} linked to the given server. - *

- * Note that the key is structured: - * xxx(|yyy|wl)(|rw) - *

- * Note that anything before the first pipe (|) character is - * considered to be the encryption key, anything after that character is - * called the subkey (including the other pipe characters and flags!). - *

- * This is important because the subkey (including the pipe characters and - * flags) must be present as-is in the server configuration file to be - * allowed. - *

    - *
  • xxx: the encryption key used to communicate with the - * server
  • - *
  • yyy: the secondary key
  • - *
  • rw: flag to allow read and write access if it is not the - * default on this server
  • - *
  • wl: flag to allow access to all the stories (bypassing the - * whitelist if it exists)
  • - *
- *

- * Some examples: - *

    - *
  • my_key: normal connection, will take the default server - * options
  • - *
  • my_key|agzyzz|wl: will ask to bypass the white list (if it - * exists)
  • - *
  • my_key|agzyzz|rw: will ask read-write access (if the default - * is read-only)
  • - *
  • my_key|agzyzz|wl|rw: will ask both read-write access and white - * list bypass
  • - *
- * - * @param key - * the key that will allow us to exchange information with the - * server - * @param host - * the host to contact or NULL for localhost - * @param port - * the port to contact it on - */ - public RemoteLibrary(String key, String host, int port) { - int index = -1; - if (key != null) { - index = key.indexOf('|'); - } - - if (index >= 0) { - this.key = key.substring(0, index); - this.subkey = key.substring(index + 1); - } else { - this.key = key; - this.subkey = ""; - } - - this.host = host; - this.port = port; - } - - @Override - public String getLibraryName() { - return (rw ? "[READ-ONLY] " : "") + host + ":" + port; - } - - @Override - public Status getStatus() { - Instance.getInstance().getTraceHandler().trace("Getting remote lib status..."); - Status status = getStatusDo(); - Instance.getInstance().getTraceHandler().trace("Remote lib status: " + status); - return status; - } - - private Status getStatusDo() { - final Status[] result = new Status[1]; - - result[0] = Status.INVALID; - - try { - new RemoteConnectAction() { - @Override - public void action(Version serverVersion) throws Exception { - Object rep = send(new Object[] { subkey, "PING" }); - - if ("r/w".equals(rep)) { - rw = true; - result[0] = Status.READ_WRITE; - } else if ("r/o".equals(rep)) { - rw = false; - result[0] = Status.READ_ONLY; - } else { - result[0] = Status.UNAUTHORIZED; - } - } - - @Override - protected void onError(Exception e) { - if (e instanceof SSLException) { - result[0] = Status.UNAUTHORIZED; - } else { - result[0] = Status.UNAVAILABLE; - } - } - }.connect(); - } catch (UnknownHostException e) { - result[0] = Status.INVALID; - } catch (IllegalArgumentException e) { - result[0] = Status.INVALID; - } catch (Exception e) { - result[0] = Status.UNAVAILABLE; - } - - return result[0]; - } - - @Override - public Image getCover(final String luid) throws IOException { - final Image[] result = new Image[1]; - - connectRemoteAction(new RemoteAction() { - @Override - public void action(ConnectActionClientObject action) - throws Exception { - Object rep = action.send(new Object[] { subkey, "GET_COVER", - luid }); - result[0] = (Image) rep; - } - }); - - return result[0]; - } - - @Override - public Image getCustomSourceCover(final String source) throws IOException { - return getCustomCover(source, "SOURCE"); - } - - @Override - public Image getCustomAuthorCover(final String author) throws IOException { - return getCustomCover(author, "AUTHOR"); - } - - // type: "SOURCE" or "AUTHOR" - private Image getCustomCover(final String source, final String type) - throws IOException { - final Image[] result = new Image[1]; - - connectRemoteAction(new RemoteAction() { - @Override - public void action(ConnectActionClientObject action) - throws Exception { - Object rep = action.send(new Object[] { subkey, - "GET_CUSTOM_COVER", type, source }); - result[0] = (Image) rep; - } - }); - - return result[0]; - } - - @Override - public synchronized Story getStory(final String luid, Progress pg) - throws IOException { - final Progress pgF = pg; - final Story[] result = new Story[1]; - - connectRemoteAction(new RemoteAction() { - @Override - public void action(ConnectActionClientObject action) - throws Exception { - Progress pg = pgF; - if (pg == null) { - pg = new Progress(); - } - - Object rep = action.send(new Object[] { subkey, "GET_STORY", - luid }); - - MetaData meta = null; - if (rep instanceof MetaData) { - meta = (MetaData) rep; - if (meta.getWords() <= Integer.MAX_VALUE) { - pg.setMinMax(0, (int) meta.getWords()); - } - } - - List list = new ArrayList(); - for (Object obj = action.send(null); obj != null; obj = action - .send(null)) { - list.add(obj); - pg.add(1); - } - - result[0] = RemoteLibraryServer.rebuildStory(list); - pg.done(); - } - }); - - return result[0]; - } - - @Override - public synchronized Story save(final Story story, final String luid, - Progress pg) throws IOException { - - final String[] luidSaved = new String[1]; - Progress pgSave = new Progress(); - Progress pgRefresh = new Progress(); - if (pg == null) { - pg = new Progress(); - } - - pg.setMinMax(0, 10); - pg.addProgress(pgSave, 9); - pg.addProgress(pgRefresh, 1); - - final Progress pgF = pgSave; - - connectRemoteAction(new RemoteAction() { - @Override - public void action(ConnectActionClientObject action) - throws Exception { - Progress pg = pgF; - if (story.getMeta().getWords() <= Integer.MAX_VALUE) { - pg.setMinMax(0, (int) story.getMeta().getWords()); - } - - action.send(new Object[] { subkey, "SAVE_STORY", luid }); - - List list = RemoteLibraryServer.breakStory(story); - for (Object obj : list) { - action.send(obj); - pg.add(1); - } - - luidSaved[0] = (String) action.send(null); - - pg.done(); - } - }); - - // because the meta changed: - MetaData meta = getInfo(luidSaved[0]); - if (story.getMeta().getClass() != null) { - // If already available locally: - meta.setCover(story.getMeta().getCover()); - } else { - // If required: - meta.setCover(getCover(meta.getLuid())); - } - story.setMeta(meta); - - pg.done(); - - return story; - } - - @Override - public synchronized void delete(final String luid) throws IOException { - connectRemoteAction(new RemoteAction() { - @Override - public void action(ConnectActionClientObject action) - throws Exception { - action.send(new Object[] { subkey, "DELETE_STORY", luid }); - } - }); - } - - @Override - public void setSourceCover(final String source, final String luid) - throws IOException { - setCover(source, luid, "SOURCE"); - } - - @Override - public void setAuthorCover(final String author, final String luid) - throws IOException { - setCover(author, luid, "AUTHOR"); - } - - // type = "SOURCE" | "AUTHOR" - private void setCover(final String value, final String luid, - final String type) throws IOException { - connectRemoteAction(new RemoteAction() { - @Override - public void action(ConnectActionClientObject action) - throws Exception { - action.send(new Object[] { subkey, "SET_COVER", type, value, - luid }); - } - }); - } - - @Override - // Could work (more slowly) without it - public MetaData imprt(final URL url, Progress pg) throws IOException { - // Import the file locally if it is actually a file - - if (url == null || url.getProtocol().equalsIgnoreCase("file")) { - return super.imprt(url, pg); - } - - // Import it remotely if it is an URL - - if (pg == null) { - pg = new Progress(); - } - - final Progress pgF = pg; - final String[] luid = new String[1]; - - connectRemoteAction(new RemoteAction() { - @Override - public void action(ConnectActionClientObject action) - throws Exception { - Progress pg = pgF; - - Object rep = action.send(new Object[] { subkey, "IMPORT", - url.toString() }); - - while (true) { - if (!RemoteLibraryServer.updateProgress(pg, rep)) { - break; - } - - rep = action.send(null); - } - - pg.done(); - luid[0] = (String) rep; - } - }); - - if (luid[0] == null) { - throw new IOException("Remote failure"); - } - - pg.done(); - return getInfo(luid[0]); - } - - @Override - // Could work (more slowly) without it - protected synchronized void changeSTA(final String luid, - final String newSource, final String newTitle, - final String newAuthor, Progress pg) throws IOException { - - final Progress pgF = pg == null ? new Progress() : pg; - - connectRemoteAction(new RemoteAction() { - @Override - public void action(ConnectActionClientObject action) - throws Exception { - Progress pg = pgF; - - Object rep = action.send(new Object[] { subkey, "CHANGE_STA", - luid, newSource, newTitle, newAuthor }); - while (true) { - if (!RemoteLibraryServer.updateProgress(pg, rep)) { - break; - } - - rep = action.send(null); - } - } - }); - } - - @Override - public File getFile(final String luid, Progress pg) { - throw new java.lang.InternalError( - "Operation not supportorted on remote Libraries"); - } - - /** - * Stop the server. - * - * @throws IOException - * in case of I/O error (including bad key) - */ - public void exit() throws IOException { - connectRemoteAction(new RemoteAction() { - @Override - public void action(ConnectActionClientObject action) - throws Exception { - action.send(new Object[] { subkey, "EXIT" }); - Thread.sleep(100); - } - }); - } - - @Override - public MetaData getInfo(String luid) throws IOException { - List metas = getMetasList(luid, null); - if (!metas.isEmpty()) { - return metas.get(0); - } - - return null; - } - - @Override - protected List getMetas(Progress pg) throws IOException { - return getMetasList("*", pg); - } - - @Override - protected void updateInfo(MetaData meta) { - // Will be taken care of directly server side - } - - @Override - protected void invalidateInfo(String luid) { - // Will be taken care of directly server side - } - - // The following methods are only used by Save and Delete in BasicLibrary: - - @Override - protected int getNextId() { - throw new java.lang.InternalError("Should not have been called"); - } - - @Override - protected void doDelete(String luid) throws IOException { - throw new java.lang.InternalError("Should not have been called"); - } - - @Override - protected Story doSave(Story story, Progress pg) throws IOException { - throw new java.lang.InternalError("Should not have been called"); - } - - // - - /** - * Return the meta of the given story or a list of all known metas if the - * luid is "*". - *

- * Will not get the covers. - * - * @param luid - * the luid of the story or * - * @param pg - * the optional progress - * - * @return the metas - * - * @throws IOException - * in case of I/O error or bad key (SSLException) - */ - private List getMetasList(final String luid, Progress pg) - throws IOException { - final Progress pgF = pg; - final List metas = new ArrayList(); - - connectRemoteAction(new RemoteAction() { - @Override - public void action(ConnectActionClientObject action) - throws Exception { - Progress pg = pgF; - if (pg == null) { - pg = new Progress(); - } - - Object rep = action.send(new Object[] { subkey, "GET_METADATA", - luid }); - - while (true) { - if (!RemoteLibraryServer.updateProgress(pg, rep)) { - break; - } - - rep = action.send(null); - } - - if (rep instanceof MetaData[]) { - for (MetaData meta : (MetaData[]) rep) { - metas.add(meta); - } - } else if (rep != null) { - metas.add((MetaData) rep); - } - } - }); - - return metas; - } - - private void connectRemoteAction(final RemoteAction runAction) - throws IOException { - final IOException[] err = new IOException[1]; - try { - final RemoteConnectAction[] array = new RemoteConnectAction[1]; - RemoteConnectAction ra = new RemoteConnectAction() { - @Override - public void action(Version serverVersion) throws Exception { - runAction.action(array[0]); - } - - @Override - protected void onError(Exception e) { - if (!(e instanceof IOException)) { - Instance.getInstance().getTraceHandler().error(e); - return; - } - - err[0] = (IOException) e; - } - }; - array[0] = ra; - ra.connect(); - } catch (Exception e) { - err[0] = (IOException) e; - } - - if (err[0] != null) { - throw err[0]; - } - } -} diff --git a/src/be/nikiroo/fanfix/library/RemoteLibraryException.java b/src/be/nikiroo/fanfix/library/RemoteLibraryException.java deleted file mode 100644 index 4cbb631c..00000000 --- a/src/be/nikiroo/fanfix/library/RemoteLibraryException.java +++ /dev/null @@ -1,100 +0,0 @@ -package be.nikiroo.fanfix.library; - -import java.io.IOException; - -/** - * Exceptions sent from remote to local. - * - * @author niki - */ -public class RemoteLibraryException extends IOException { - private static final long serialVersionUID = 1L; - - private boolean wrapped; - - @SuppressWarnings("unused") - private RemoteLibraryException() { - // for serialization purposes - } - - /** - * Wrap an {@link IOException} to allow it to pass across the network. - * - * @param cause - * the exception to wrap - * @param remote - * this exception is used to send the contained - * {@link IOException} to the other end of the network - */ - public RemoteLibraryException(IOException cause, boolean remote) { - this(null, cause, remote); - } - - /** - * Wrap an {@link IOException} to allow it to pass across the network. - * - * @param message - * the error message - * @param wrapped - * this exception is used to send the contained - * {@link IOException} to the other end of the network - */ - public RemoteLibraryException(String message, boolean wrapped) { - this(message, null, wrapped); - } - - /** - * Wrap an {@link IOException} to allow it to pass across the network. - * - * @param message - * the error message - * @param cause - * the exception to wrap - * @param wrapped - * this exception is used to send the contained - * {@link IOException} to the other end of the network - */ - public RemoteLibraryException(String message, IOException cause, - boolean wrapped) { - super(message, cause); - this.wrapped = wrapped; - } - - /** - * Return the actual exception we should return to the client code. It can - * be: - *

    - *
  • the cause if {@link RemoteLibraryException#isWrapped()} is - * TRUE
  • - *
  • this if {@link RemoteLibraryException#isWrapped()} is FALSE - * (
  • - *
  • this if the cause is NULL (so we never return NULL) - *
  • - *
- * It is never NULL. - * - * @return the unwrapped exception or this, never NULL - */ - public synchronized IOException unwrapException() { - Throwable ex = super.getCause(); - if (!isWrapped() || !(ex instanceof IOException)) { - ex = this; - } - - return (IOException) ex; - } - - /** - * This exception is used to send the contained {@link IOException} to the - * other end of the network. - *

- * In other words, do not use this exception in client code when it - * has reached the other end of the network, but use its cause instead (see - * {@link RemoteLibraryException#unwrapException()}). - * - * @return TRUE if it is - */ - public boolean isWrapped() { - return wrapped; - } -} diff --git a/src/be/nikiroo/fanfix/library/RemoteLibraryServer.java b/src/be/nikiroo/fanfix/library/RemoteLibraryServer.java deleted file mode 100644 index 4f89a1fa..00000000 --- a/src/be/nikiroo/fanfix/library/RemoteLibraryServer.java +++ /dev/null @@ -1,502 +0,0 @@ -package be.nikiroo.fanfix.library; - -import java.io.IOException; -import java.net.URL; -import java.util.ArrayList; -import java.util.Date; -import java.util.HashMap; -import java.util.List; -import java.util.Map; - -import javax.net.ssl.SSLException; - -import be.nikiroo.fanfix.Instance; -import be.nikiroo.fanfix.bundles.Config; -import be.nikiroo.fanfix.data.Chapter; -import be.nikiroo.fanfix.data.MetaData; -import be.nikiroo.fanfix.data.Paragraph; -import be.nikiroo.fanfix.data.Story; -import be.nikiroo.utils.Progress; -import be.nikiroo.utils.Progress.ProgressListener; -import be.nikiroo.utils.StringUtils; -import be.nikiroo.utils.Version; -import be.nikiroo.utils.serial.server.ConnectActionServerObject; -import be.nikiroo.utils.serial.server.ServerObject; - -/** - * Create a new remote server that will listen for orders on the given port. - *

- * The available commands are given as arrays of objects (first item is the - * command, the rest are the arguments). - *

- * All the commands are always prefixed by the subkey (which can be EMPTY if - * none). - *

- *

    - *
  • PING: will return the mode if the key is accepted (mode can be: "r/o" or - * "r/w")
  • - *
  • GET_METADATA *: will return the metadata of all the stories in the - * library (array)
  • * - *
  • GET_METADATA [luid]: will return the metadata of the story of LUID luid
  • - *
  • GET_STORY [luid]: will return the given story if it exists (or NULL if - * not)
  • - *
  • SAVE_STORY [luid]: save the story (that must be sent just after the - * command) with the given LUID, then return the LUID
  • - *
  • IMPORT [url]: save the story found at the given URL, then return the LUID - *
  • - *
  • DELETE_STORY [luid]: delete the story of LUID luid
  • - *
  • GET_COVER [luid]: return the cover of the story
  • - *
  • GET_CUSTOM_COVER ["SOURCE"|"AUTHOR"] [source]: return the cover for this - * source/author
  • - *
  • SET_COVER ["SOURCE"|"AUTHOR"] [value] [luid]: set the default cover for - * the given source/author to the cover of the story denoted by luid
  • - *
  • CHANGE_SOURCE [luid] [new source]: change the source of the story of LUID - * luid
  • - *
  • EXIT: stop the server
  • - *
- * - * @author niki - */ -public class RemoteLibraryServer extends ServerObject { - private Map commands = new HashMap(); - private Map times = new HashMap(); - private Map wls = new HashMap(); - private Map rws = new HashMap(); - - /** - * Create a new remote server (will not be active until - * {@link RemoteLibraryServer#start()} is called). - *

- * Note: the key we use here is the encryption key (it must not contain a - * subkey). - * - * @param key - * the key that will restrict access to this server - * @param port - * the port to listen on - * - * @throws IOException - * in case of I/O error - */ - public RemoteLibraryServer(String key, int port) throws IOException { - super("Fanfix remote library", port, key); - setTraceHandler(Instance.getInstance().getTraceHandler()); - } - - @Override - protected Object onRequest(ConnectActionServerObject action, - Version clientVersion, Object data, long id) throws Exception { - long start = new Date().getTime(); - - // defaults are positive (as previous versions without the feature) - boolean rw = true; - boolean wl = true; - - String subkey = ""; - String command = ""; - Object[] args = new Object[0]; - if (data instanceof Object[]) { - Object[] dataArray = (Object[]) data; - if (dataArray.length > 0) { - subkey = "" + dataArray[0]; - } - if (dataArray.length > 1) { - command = "" + dataArray[1]; - - args = new Object[dataArray.length - 2]; - for (int i = 2; i < dataArray.length; i++) { - args[i - 2] = dataArray[i]; - } - } - } - - List whitelist = Instance.getInstance().getConfig().getList(Config.SERVER_WHITELIST); - if (whitelist == null) { - whitelist = new ArrayList(); - } - - if (whitelist.isEmpty()) { - wl = false; - } - - rw = Instance.getInstance().getConfig().getBoolean(Config.SERVER_RW, rw); - if (!subkey.isEmpty()) { - List allowed = Instance.getInstance().getConfig().getList(Config.SERVER_ALLOWED_SUBKEYS); - if (allowed.contains(subkey)) { - if ((subkey + "|").contains("|rw|")) { - rw = true; - } - if ((subkey + "|").contains("|wl|")) { - wl = false; // |wl| = bypass whitelist - whitelist = new ArrayList(); - } - } - } - - String mode = display(wl, rw); - - String trace = mode + "[ " + command + "] "; - for (Object arg : args) { - trace += arg + " "; - } - long now = System.currentTimeMillis(); - System.out.println(StringUtils.fromTime(now) + ": " + trace); - - Object rep = null; - try { - rep = doRequest(action, command, args, rw, whitelist); - } catch (IOException e) { - rep = new RemoteLibraryException(e, true); - } - - commands.put(id, command); - wls.put(id, wl); - rws.put(id, rw); - times.put(id, (new Date().getTime() - start)); - - return rep; - } - - private String display(boolean whitelist, boolean rw) { - String mode = ""; - if (!rw) { - mode += "RO: "; - } - if (whitelist) { - mode += "WL: "; - } - - return mode; - } - - @Override - protected void onRequestDone(long id, long bytesReceived, long bytesSent) { - boolean whitelist = wls.get(id); - boolean rw = rws.get(id); - wls.remove(id); - rws.remove(id); - - String rec = StringUtils.formatNumber(bytesReceived) + "b"; - String sent = StringUtils.formatNumber(bytesSent) + "b"; - long now = System.currentTimeMillis(); - System.out.println(StringUtils.fromTime(now) - + ": " - + String.format("%s[>%s]: (%s sent, %s rec) in %d ms", - display(whitelist, rw), commands.get(id), sent, rec, - times.get(id))); - - commands.remove(id); - times.remove(id); - } - - private Object doRequest(ConnectActionServerObject action, String command, - Object[] args, boolean rw, List whitelist) - throws NoSuchFieldException, NoSuchMethodException, - ClassNotFoundException, IOException { - if ("PING".equals(command)) { - return rw ? "r/w" : "r/o"; - } else if ("GET_METADATA".equals(command)) { - List metas = new ArrayList(); - - if ("*".equals(args[0])) { - Progress pg = createPgForwarder(action); - - for (MetaData meta : Instance.getInstance().getLibrary().getMetas(pg)) { - MetaData light; - if (meta.getCover() == null) { - light = meta; - } else { - light = meta.clone(); - light.setCover(null); - } - - metas.add(light); - } - - forcePgDoneSent(pg); - } else { - MetaData meta = Instance.getInstance().getLibrary().getInfo((String) args[0]); - MetaData light; - if (meta.getCover() == null) { - light = meta; - } else { - light = meta.clone(); - light.setCover(null); - } - - metas.add(light); - } - - if (!whitelist.isEmpty()) { - for (int i = 0; i < metas.size(); i++) { - if (!whitelist.contains(metas.get(i).getSource())) { - metas.remove(i); - i--; - } - } - } - - return metas.toArray(new MetaData[0]); - } else if ("GET_STORY".equals(command)) { - MetaData meta = Instance.getInstance().getLibrary().getInfo((String) args[0]); - if (meta == null) { - return null; - } - - if (!whitelist.isEmpty()) { - if (!whitelist.contains(meta.getSource())) { - return null; - } - } - - meta = meta.clone(); - meta.setCover(null); - - action.send(meta); - action.rec(); - - Story story = Instance.getInstance().getLibrary().getStory((String) args[0], null); - for (Object obj : breakStory(story)) { - action.send(obj); - action.rec(); - } - } else if ("SAVE_STORY".equals(command)) { - if (!rw) { - throw new RemoteLibraryException("Read-Only remote library: " - + args[0], false); - } - - List list = new ArrayList(); - - action.send(null); - Object obj = action.rec(); - while (obj != null) { - list.add(obj); - action.send(null); - obj = action.rec(); - } - - Story story = rebuildStory(list); - Instance.getInstance().getLibrary().save(story, (String) args[0], null); - return story.getMeta().getLuid(); - } else if ("IMPORT".equals(command)) { - if (!rw) { - throw new RemoteLibraryException("Read-Only remote library: " - + args[0], false); - } - - Progress pg = createPgForwarder(action); - MetaData meta = Instance.getInstance().getLibrary().imprt(new URL((String) args[0]), pg); - forcePgDoneSent(pg); - return meta.getLuid(); - } else if ("DELETE_STORY".equals(command)) { - if (!rw) { - throw new RemoteLibraryException("Read-Only remote library: " - + args[0], false); - } - - Instance.getInstance().getLibrary().delete((String) args[0]); - } else if ("GET_COVER".equals(command)) { - return Instance.getInstance().getLibrary().getCover((String) args[0]); - } else if ("GET_CUSTOM_COVER".equals(command)) { - if ("SOURCE".equals(args[0])) { - return Instance.getInstance().getLibrary().getCustomSourceCover((String) args[1]); - } else if ("AUTHOR".equals(args[0])) { - return Instance.getInstance().getLibrary().getCustomAuthorCover((String) args[1]); - } else { - return null; - } - } else if ("SET_COVER".equals(command)) { - if (!rw) { - throw new RemoteLibraryException("Read-Only remote library: " - + args[0] + ", " + args[1], false); - } - - if ("SOURCE".equals(args[0])) { - Instance.getInstance().getLibrary().setSourceCover((String) args[1], (String) args[2]); - } else if ("AUTHOR".equals(args[0])) { - Instance.getInstance().getLibrary().setAuthorCover((String) args[1], (String) args[2]); - } - } else if ("CHANGE_STA".equals(command)) { - if (!rw) { - throw new RemoteLibraryException("Read-Only remote library: " + args[0] + ", " + args[1], false); - } - - Progress pg = createPgForwarder(action); - Instance.getInstance().getLibrary().changeSTA((String) args[0], (String) args[1], (String) args[2], - (String) args[3], pg); - forcePgDoneSent(pg); - } else if ("EXIT".equals(command)) { - if (!rw) { - throw new RemoteLibraryException( - "Read-Only remote library: EXIT", false); - } - - stop(10000, false); - } - - return null; - } - - @Override - protected void onError(Exception e) { - if (e instanceof SSLException) { - long now = System.currentTimeMillis(); - System.out.println(StringUtils.fromTime(now) + ": " - + "[Client connection refused (bad key)]"); - } else { - getTraceHandler().error(e); - } - } - - /** - * Break a story in multiple {@link Object}s for easier serialisation. - * - * @param story - * the {@link Story} to break - * - * @return the list of {@link Object}s - */ - static List breakStory(Story story) { - List list = new ArrayList(); - - story = story.clone(); - list.add(story); - - if (story.getMeta().isImageDocument()) { - for (Chapter chap : story) { - list.add(chap); - list.addAll(chap.getParagraphs()); - chap.setParagraphs(new ArrayList()); - } - story.setChapters(new ArrayList()); - } - - return list; - } - - /** - * Rebuild a story from a list of broke up {@link Story} parts. - * - * @param list - * the list of {@link Story} parts - * - * @return the reconstructed {@link Story} - */ - static Story rebuildStory(List list) { - Story story = null; - Chapter chap = null; - - for (Object obj : list) { - if (obj instanceof Story) { - story = (Story) obj; - } else if (obj instanceof Chapter) { - chap = (Chapter) obj; - story.getChapters().add(chap); - } else if (obj instanceof Paragraph) { - chap.getParagraphs().add((Paragraph) obj); - } - } - - return story; - } - - /** - * Update the {@link Progress} with the adequate {@link Object} received - * from the network via {@link RemoteLibraryServer}. - * - * @param pg - * the {@link Progress} to update - * @param rep - * the object received from the network - * - * @return TRUE if it was a progress event, FALSE if not - */ - static boolean updateProgress(Progress pg, Object rep) { - if (rep instanceof Integer[]) { - Integer[] a = (Integer[]) rep; - if (a.length == 3) { - int min = a[0]; - int max = a[1]; - int progress = a[2]; - - if (min >= 0 && min <= max) { - pg.setMinMax(min, max); - pg.setProgress(progress); - - return true; - } - } - } - - return false; - } - - /** - * Create a {@link Progress} that will forward its progress over the - * network. - * - * @param action - * the {@link ConnectActionServerObject} to use to forward it - * - * @return the {@link Progress} - */ - private Progress createPgForwarder(final ConnectActionServerObject action) { - final Boolean[] isDoneForwarded = new Boolean[] { false }; - final Progress pg = new Progress() { - @Override - public boolean isDone() { - return isDoneForwarded[0]; - } - }; - - final Integer[] p = new Integer[] { -1, -1, -1 }; - final Long[] lastTime = new Long[] { new Date().getTime() }; - pg.addProgressListener(new ProgressListener() { - @Override - public void progress(Progress progress, String name) { - int min = pg.getMin(); - int max = pg.getMax(); - int relativeProgress = min - + (int) Math.round(pg.getRelativeProgress() - * (max - min)); - - // Do not re-send the same value twice over the wire, - // unless more than 2 seconds have elapsed (to maintain the - // connection) - if ((p[0] != min || p[1] != max || p[2] != relativeProgress) - || (new Date().getTime() - lastTime[0] > 2000)) { - p[0] = min; - p[1] = max; - p[2] = relativeProgress; - - try { - action.send(new Integer[] { min, max, relativeProgress }); - action.rec(); - } catch (Exception e) { - getTraceHandler().error(e); - } - - lastTime[0] = new Date().getTime(); - } - - isDoneForwarded[0] = (pg.getProgress() >= pg.getMax()); - } - }); - - return pg; - } - - // with 30 seconds timeout - private void forcePgDoneSent(Progress pg) { - long start = new Date().getTime(); - pg.done(); - while (!pg.isDone() && new Date().getTime() - start < 30000) { - try { - Thread.sleep(100); - } catch (InterruptedException e) { - getTraceHandler().error(e); - } - } - } -} diff --git a/src/be/nikiroo/fanfix/library/package-info.java b/src/be/nikiroo/fanfix/library/package-info.java deleted file mode 100644 index 1bb63ea0..00000000 --- a/src/be/nikiroo/fanfix/library/package-info.java +++ /dev/null @@ -1,9 +0,0 @@ -/** - * This package offer a Libraries to store stories into. - *

- * It currently has a local library and a remote one, as well as the required - * remote server. - * - * @author niki - */ -package be.nikiroo.fanfix.library; \ No newline at end of file diff --git a/src/be/nikiroo/fanfix/libs.txt b/src/be/nikiroo/fanfix/libs.txt deleted file mode 100644 index 091daf58..00000000 --- a/src/be/nikiroo/fanfix/libs.txt +++ /dev/null @@ -1,6 +0,0 @@ -# Required librairies: - -They are available in the other branches if needed: -- libs/jsoup-1.10.3-sources.jar -- libs/unbescape-1.1.4-sources.jar - diff --git a/src/be/nikiroo/fanfix/output/BasicOutput.java b/src/be/nikiroo/fanfix/output/BasicOutput.java deleted file mode 100644 index 41634faa..00000000 --- a/src/be/nikiroo/fanfix/output/BasicOutput.java +++ /dev/null @@ -1,553 +0,0 @@ -package be.nikiroo.fanfix.output; - -import java.io.File; -import java.io.IOException; -import java.util.ArrayList; -import java.util.List; - -import be.nikiroo.fanfix.Instance; -import be.nikiroo.fanfix.bundles.StringId; -import be.nikiroo.fanfix.data.Chapter; -import be.nikiroo.fanfix.data.Paragraph; -import be.nikiroo.fanfix.data.Paragraph.ParagraphType; -import be.nikiroo.fanfix.data.Story; -import be.nikiroo.utils.Progress; -import be.nikiroo.utils.Version; - -/** - * This class is the base class used by the other output classes. It can be used - * outside of this package, and have static method that you can use to get - * access to the correct support class. - * - * @author niki - */ -public abstract class BasicOutput { - /** - * The supported output types for which we can get a {@link BasicOutput} - * object. - * - * @author niki - */ - public enum OutputType { - /** EPUB files created with this program */ - EPUB, - /** Pure text file with some rules */ - TEXT, - /** TEXT but with associated .info file */ - INFO_TEXT, - /** DEBUG output to console */ - SYSOUT, - /** ZIP with (PNG) images */ - CBZ, - /** LaTeX file with "book" template */ - LATEX, - /** HTML files in a dedicated directory */ - HTML, - - ; - - @Override - public String toString() { - return super.toString().toLowerCase(); - } - - /** - * A description of this output type. - * - * @param longDesc - * TRUE for the long description, FALSE for the short one - * - * @return the description - */ - public String getDesc(boolean longDesc) { - StringId id = longDesc ? StringId.OUTPUT_DESC - : StringId.OUTPUT_DESC_SHORT; - - String desc = Instance.getInstance().getTrans().getStringX(id, this.name()); - - if (desc == null) { - desc = Instance.getInstance().getTrans().getString(id, this.toString()); - } - - if (desc == null || desc.isEmpty()) { - desc = this.toString(); - } - - return desc; - } - - /** - * The default extension to add to the output files. - * - * @param readerTarget - * TRUE to point to the main {@link Story} entry point for a - * reader (for instance, the main entry point if this - * {@link Story} is in a directory bundle), FALSE to point to - * the main file even if it is a directory for instance - * - * @return the extension - */ - public String getDefaultExtension(boolean readerTarget) { - BasicOutput output = BasicOutput.getOutput(this, false, false); - if (output != null) { - return output.getDefaultExtension(readerTarget); - } - - return null; - } - - /** - * Call {@link OutputType#valueOf(String)} after conversion to upper - * case. - * - * @param typeName - * the possible type name - * - * @return NULL or the type - */ - public static OutputType valueOfUC(String typeName) { - return OutputType.valueOf(typeName == null ? null : typeName - .toUpperCase()); - } - - /** - * Call {@link OutputType#valueOf(String)} after conversion to upper - * case but return def for NULL and empty instead of raising an - * exception. - * - * @param typeName - * the possible type name - * @param def - * the default value - * - * @return NULL or the type - */ - public static OutputType valueOfNullOkUC(String typeName, OutputType def) { - if (typeName == null || typeName.isEmpty()) { - return def; - } - - return OutputType.valueOfUC(typeName); - } - - /** - * Call {@link OutputType#valueOf(String)} after conversion to upper - * case but return def in case of error instead of raising an exception. - * - * @param typeName - * the possible type name - * @param def - * the default value - * - * @return NULL or the type - */ - public static OutputType valueOfAllOkUC(String typeName, OutputType def) { - try { - return OutputType.valueOfUC(typeName); - } catch (Exception e) { - return def; - } - } - } - - /** The creator name (this program, by me!) */ - static protected final String EPUB_CREATOR = "Fanfix " - + Version.getCurrentVersion() + " (by Niki)"; - - /** The current best name for an image */ - private String imageName; - private File targetDir; - private String targetName; - private OutputType type; - private boolean writeCover; - private boolean writeInfo; - private Progress storyPg; - private Progress chapPg; - - /** - * Process the {@link Story} into the given target. - * - * @param story - * the {@link Story} to export - * @param target - * the target where to save to (will not necessary be taken as is - * by the processor, for instance an extension can be added) - * @param pg - * the optional progress reporter - * - * @return the actual main target saved, which can be slightly different - * that the input one - * - * @throws IOException - * in case of I/O error - */ - public File process(Story story, String target, Progress pg) - throws IOException { - storyPg = pg; - - File targetDir = null; - String targetName = null; - if (target != null) { - target = new File(target).getAbsolutePath(); - targetDir = new File(target).getParentFile(); - targetName = new File(target).getName(); - - String ext = getDefaultExtension(false); - if (ext != null && !ext.isEmpty()) { - if (targetName.toLowerCase().endsWith(ext)) { - targetName = targetName.substring(0, targetName.length() - - ext.length()); - } - } - } - - return process(story, targetDir, targetName); - } - - /** - * Process the {@link Story} into the given target. - *

- * This method is expected to be overridden in most cases. - * - * @param story - * the {@link Story} to export - * @param targetDir - * the target dir where to save to - * @param targetName - * the target filename (will not necessary be taken as is by the - * processor, for instance an extension can be added) - * - * - * @return the actual main target saved, which can be slightly different - * that the input one - * - * @throws IOException - * in case of I/O error - */ - protected File process(Story story, File targetDir, String targetName) - throws IOException { - this.targetDir = targetDir; - this.targetName = targetName; - - writeStory(story); - - return null; - } - - /** - * The output type. - * - * @return the type - */ - public OutputType getType() { - return type; - } - - /** - * Enable the creation of a .info file next to the resulting processed file. - * - * @return TRUE to enable it - */ - protected boolean isWriteInfo() { - return writeInfo; - } - - /** - * Enable the creation of a cover file next to the resulting processed file - * if possible. - * - * @return TRUE to enable it - */ - protected boolean isWriteCover() { - return writeCover; - } - - /** - * The output type. - * - * @param type - * the new type - * @param writeCover - * TRUE to enable the creation of a cover if possible - * @param writeInfo - * TRUE to enable the creation of a .info file - * - * @return this - */ - protected BasicOutput setType(OutputType type, boolean writeInfo, - boolean writeCover) { - this.type = type; - this.writeInfo = writeInfo; - this.writeCover = writeCover; - - return this; - } - - /** - * The default extension to add to the output files. - * - * @param readerTarget - * TRUE to point to the main {@link Story} entry point for a - * reader (for instance, the main entry point if this - * {@link Story} is in a directory bundle), FALSE to point to the - * main file even if it is a directory for instance - * - * @return the extension - */ - public String getDefaultExtension( - @SuppressWarnings("unused") boolean readerTarget) { - return ""; - } - - @SuppressWarnings("unused") - protected void writeStoryHeader(Story story) throws IOException { - } - - @SuppressWarnings("unused") - protected void writeChapterHeader(Chapter chap) throws IOException { - } - - @SuppressWarnings("unused") - protected void writeParagraphHeader(Paragraph para) throws IOException { - } - - @SuppressWarnings("unused") - protected void writeStoryFooter(Story story) throws IOException { - } - - @SuppressWarnings("unused") - protected void writeChapterFooter(Chapter chap) throws IOException { - } - - @SuppressWarnings("unused") - protected void writeParagraphFooter(Paragraph para) throws IOException { - } - - protected void writeStory(Story story) throws IOException { - if (storyPg == null) { - storyPg = new Progress(0, story.getChapters().size() + 2); - } else { - storyPg.setMinMax(0, story.getChapters().size() + 2); - } - - String chapterNameNum = String.format("%03d", 0); - String paragraphNumber = String.format("%04d", 0); - imageName = paragraphNumber + "_" + chapterNameNum; - - if (story.getMeta() != null) { - story.getMeta().setType("" + getType()); - } - - if (isWriteCover()) { - InfoCover.writeCover(targetDir, targetName, story.getMeta()); - } - if (isWriteInfo()) { - InfoCover.writeInfo(targetDir, targetName, story.getMeta()); - } - - storyPg.setProgress(1); - - List chapPgs = new ArrayList(story.getChapters() - .size()); - for (Chapter chap : story) { - chapPg = new Progress(0, chap.getParagraphs().size()); - storyPg.addProgress(chapPg, 1); - chapPgs.add(chapPg); - chapPg = null; - } - - writeStoryHeader(story); - for (int i = 0; i < story.getChapters().size(); i++) { - chapPg = chapPgs.get(i); - writeChapter(story.getChapters().get(i)); - chapPg.setProgress(chapPg.getMax()); - chapPg = null; - } - writeStoryFooter(story); - - storyPg.setProgress(storyPg.getMax()); - storyPg = null; - } - - protected void writeChapter(Chapter chap) throws IOException { - String chapterNameNum; - if (chap.getName() == null || chap.getName().isEmpty()) { - chapterNameNum = String.format("%03d", chap.getNumber()); - } else { - chapterNameNum = String.format("%03d", chap.getNumber()) + "_" - + chap.getName().replace(" ", "_"); - } - - int num = 0; - String paragraphNumber = String.format("%04d", num++); - imageName = chapterNameNum + "_" + paragraphNumber; - - writeChapterHeader(chap); - int i = 1; - for (Paragraph para : chap) { - paragraphNumber = String.format("%04d", num++); - imageName = chapterNameNum + "_" + paragraphNumber; - writeParagraph(para); - if (chapPg != null) { - chapPg.setProgress(i++); - } - } - writeChapterFooter(chap); - } - - protected void writeParagraph(Paragraph para) throws IOException { - writeParagraphHeader(para); - writeTextLine(para.getType(), para.getContent()); - writeParagraphFooter(para); - } - - @SuppressWarnings("unused") - protected void writeTextLine(ParagraphType type, String line) - throws IOException { - } - - /** - * Return the current best guess for an image name, based upon the current - * {@link Chapter} and {@link Paragraph}. - * - * @param prefix - * add the original target name as a prefix - * - * @return the guessed name - */ - protected String getCurrentImageBestName(boolean prefix) { - if (prefix) { - return targetName + "_" + imageName; - } - - return imageName; - } - - /** - * Return the given word or sentence as bold. - * - * @param word - * the input - * - * @return the bold output - */ - protected String enbold(String word) { - return word; - } - - /** - * Return the given word or sentence as italic. - * - * @param word - * the input - * - * @return the italic output - */ - protected String italize(String word) { - return word; - } - - /** - * Decorate the given text with bold and italic words, - * according to {@link BasicOutput#enbold(String)} and - * {@link BasicOutput#italize(String)}. - * - * @param text - * the input - * - * @return the decorated output - */ - protected String decorateText(String text) { - StringBuilder builder = new StringBuilder(); - - int bold = -1; - int italic = -1; - char prev = '\0'; - for (char car : text.toCharArray()) { - switch (car) { - case '*': - if (bold >= 0 && prev != ' ') { - String data = builder.substring(bold); - builder.setLength(bold); - builder.append(enbold(data)); - bold = -1; - } else if (bold < 0 - && (prev == ' ' || prev == '\0' || prev == '\n')) { - bold = builder.length(); - } else { - builder.append(car); - } - - break; - case '_': - if (italic >= 0 && prev != ' ') { - String data = builder.substring(italic); - builder.setLength(italic); - builder.append(enbold(data)); - italic = -1; - } else if (italic < 0 - && (prev == ' ' || prev == '\0' || prev == '\n')) { - italic = builder.length(); - } else { - builder.append(car); - } - - break; - default: - builder.append(car); - break; - } - - prev = car; - } - - if (bold >= 0) { - builder.insert(bold, '*'); - } - - if (italic >= 0) { - builder.insert(italic, '_'); - } - - return builder.toString(); - } - - /** - * Return a {@link BasicOutput} object compatible with the given - * {@link OutputType}. - * - * @param type - * the type - * @param writeCover - * TRUE to enable the creation of a cover if possible to be saved - * next to the main target file - * @param writeInfo - * TRUE to enable the creation of a .info file to be saved next - * to the main target file - * - * @return the {@link BasicOutput} - */ - public static BasicOutput getOutput(OutputType type, boolean writeInfo, - boolean writeCover) { - if (type != null) { - switch (type) { - case EPUB: - return new Epub().setType(type, writeInfo, writeCover); - case TEXT: - return new Text().setType(type, writeInfo, true); - case INFO_TEXT: - return new InfoText().setType(type, true, true); - case SYSOUT: - return new Sysout().setType(type, false, false); - case CBZ: - return new Cbz().setType(type, writeInfo, writeCover); - case LATEX: - return new LaTeX().setType(type, writeInfo, writeCover); - case HTML: - return new Html().setType(type, writeInfo, writeCover); - } - } - - return null; - } -} diff --git a/src/be/nikiroo/fanfix/output/Cbz.java b/src/be/nikiroo/fanfix/output/Cbz.java deleted file mode 100644 index ee671e77..00000000 --- a/src/be/nikiroo/fanfix/output/Cbz.java +++ /dev/null @@ -1,101 +0,0 @@ -package be.nikiroo.fanfix.output; - -import java.io.BufferedWriter; -import java.io.File; -import java.io.FileOutputStream; -import java.io.IOException; -import java.io.OutputStreamWriter; - -import be.nikiroo.fanfix.Instance; -import be.nikiroo.fanfix.data.MetaData; -import be.nikiroo.fanfix.data.Paragraph; -import be.nikiroo.fanfix.data.Story; -import be.nikiroo.utils.IOUtils; - -class Cbz extends BasicOutput { - private File dir; - - @Override - public File process(Story story, File targetDir, String targetName) - throws IOException { - String targetNameOrig = targetName; - targetName += getDefaultExtension(false); - - File target = new File(targetDir, targetName); - - dir = Instance.getInstance().getTempFiles().createTempDir("fanfic-reader-cbz-dir"); - try { - // will also save the images! (except the cover -> false) - BasicOutput - .getOutput(OutputType.TEXT, isWriteInfo(), isWriteCover()) - // Force cover to FALSE: - .setType(OutputType.TEXT, isWriteInfo(), false) - .process(story, dir, targetNameOrig); - - try { - super.process(story, targetDir, targetNameOrig); - } finally { - } - - InfoCover.writeInfo(dir, targetNameOrig, story.getMeta()); - if (story.getMeta() != null && !story.getMeta().isFakeCover()) { - InfoCover.writeCover(dir, targetNameOrig, story.getMeta()); - } - - IOUtils.writeSmallFile(dir, "version", "3.0"); - - IOUtils.zip(dir, target, true); - } finally { - IOUtils.deltree(dir); - } - - return target; - } - - @Override - public String getDefaultExtension(boolean readerTarget) { - return ".cbz"; - } - - @Override - protected void writeStoryHeader(Story story) throws IOException { - MetaData meta = story.getMeta(); - - StringBuilder builder = new StringBuilder(); - if (meta != null && meta.getResume() != null) { - for (Paragraph para : story.getMeta().getResume()) { - builder.append(para.getContent()); - builder.append("\n"); - } - } - - BufferedWriter writer = new BufferedWriter(new OutputStreamWriter( - new FileOutputStream(new File(dir, "URL")), "UTF-8")); - try { - if (meta != null) { - writer.write(meta.getUrl()); - } - } finally { - writer.close(); - } - - writer = new BufferedWriter(new OutputStreamWriter( - new FileOutputStream(new File(dir, "SUMMARY")), "UTF-8")); - try { - String title = ""; - if (meta != null && meta.getTitle() != null) { - title = meta.getTitle(); - } - - writer.write(title); - if (meta != null && meta.getAuthor() != null) { - writer.write("\n©"); - writer.write(meta.getAuthor()); - } - writer.write("\n\n"); - writer.write(builder.toString()); - } finally { - writer.close(); - } - } -} diff --git a/src/be/nikiroo/fanfix/output/Epub.java b/src/be/nikiroo/fanfix/output/Epub.java deleted file mode 100644 index fc2dc8c9..00000000 --- a/src/be/nikiroo/fanfix/output/Epub.java +++ /dev/null @@ -1,514 +0,0 @@ -package be.nikiroo.fanfix.output; - -import java.io.BufferedWriter; -import java.io.File; -import java.io.FileInputStream; -import java.io.FileOutputStream; -import java.io.IOException; -import java.io.InputStream; -import java.io.OutputStream; -import java.io.OutputStreamWriter; -import java.util.zip.ZipEntry; -import java.util.zip.ZipOutputStream; - -import be.nikiroo.fanfix.Instance; -import be.nikiroo.fanfix.bundles.Config; -import be.nikiroo.fanfix.bundles.StringId; -import be.nikiroo.fanfix.data.Chapter; -import be.nikiroo.fanfix.data.MetaData; -import be.nikiroo.fanfix.data.Paragraph; -import be.nikiroo.fanfix.data.Paragraph.ParagraphType; -import be.nikiroo.fanfix.data.Story; -import be.nikiroo.utils.IOUtils; -import be.nikiroo.utils.StringUtils; - -class Epub extends BasicOutput { - private File tmpDir; - private BufferedWriter writer; - private boolean inDialogue = false; - private boolean inNormal = false; - private File images; - private boolean nextParaIsCover = true; - - @Override - public File process(Story story, File targetDir, String targetName) - throws IOException { - String targetNameOrig = targetName; - targetName += getDefaultExtension(false); - - tmpDir = Instance.getInstance().getTempFiles().createTempDir("fanfic-reader-epub"); - tmpDir.delete(); - - if (!tmpDir.mkdir()) { - throw new IOException( - "Cannot create a temporary directory: no space left on device?"); - } - - super.process(story, targetDir, targetNameOrig); - - File epub = null; - try { - // "Originals" - File data = new File(tmpDir, "DATA"); - data.mkdir(); - BasicOutput.getOutput(OutputType.TEXT, isWriteInfo(), - isWriteCover()).process(story, data, targetNameOrig); - InfoCover.writeInfo(data, targetNameOrig, story.getMeta()); - IOUtils.writeSmallFile(data, "version", "3.0"); - - // zip/epub - epub = new File(targetDir, targetName); - IOUtils.zip(tmpDir, epub, true); - - OutputStream out = new FileOutputStream(epub); - try { - ZipOutputStream zip = new ZipOutputStream(out); - try { - // "mimetype" MUST be the first element and not compressed - zip.setLevel(ZipOutputStream.STORED); - File mimetype = new File(tmpDir, "mimetype"); - IOUtils.writeSmallFile(tmpDir, "mimetype", - "application/epub+zip"); - ZipEntry entry = new ZipEntry("mimetype"); - entry.setExtra(new byte[] {}); - zip.putNextEntry(entry); - FileInputStream in = new FileInputStream(mimetype); - try { - IOUtils.write(in, zip); - } finally { - in.close(); - } - IOUtils.deltree(mimetype); - zip.setLevel(ZipOutputStream.DEFLATED); - // - - IOUtils.zip(zip, "", tmpDir, true); - } finally { - zip.close(); - } - } finally { - out.close(); - } - } finally { - IOUtils.deltree(tmpDir); - tmpDir = null; - } - - return epub; - } - - @Override - public String getDefaultExtension(boolean readerTarget) { - return ".epub"; - } - - @Override - protected void writeStoryHeader(Story story) throws IOException { - File ops = new File(tmpDir, "OPS"); - ops.mkdirs(); - File css = new File(ops, "css"); - css.mkdirs(); - images = new File(ops, "images"); - images.mkdirs(); - File metaInf = new File(tmpDir, "META-INF"); - metaInf.mkdirs(); - - // META-INF - String containerContent = "\n" - + "\n" - + "\t\n" - + "\t\t\n" - + "\t\n" + "\n"; - - IOUtils.writeSmallFile(metaInf, "container.xml", containerContent); - - // OPS/css - InputStream inStyle = getClass().getResourceAsStream("epub.style.css"); - if (inStyle == null) { - throw new IOException("Cannot find style.css resource"); - } - try { - IOUtils.write(inStyle, new File(css, "style.css")); - } finally { - inStyle.close(); - } - - // OPS/images - if (story.getMeta() != null && story.getMeta().getCover() != null) { - File file = new File(images, "cover"); - try { - Instance.getInstance().getCache().saveAsImage(story.getMeta().getCover(), file, true); - } catch (Exception e) { - Instance.getInstance().getTraceHandler().error(e); - } - } - - // OPS/* except chapters - IOUtils.writeSmallFile(ops, "epb.ncx", generateNcx(story)); - IOUtils.writeSmallFile(ops, "epb.opf", generateOpf(story)); - IOUtils.writeSmallFile(ops, "title.xhtml", generateTitleXml(story)); - - // Resume - if (story.getMeta() != null && story.getMeta().getResume() != null) { - writeChapter(story.getMeta().getResume()); - } - } - - @Override - protected void writeChapterHeader(Chapter chap) throws IOException { - String filename = String.format("%s%03d%s", "chapter-", - chap.getNumber(), ".xhtml"); - writer = new BufferedWriter(new OutputStreamWriter( - new FileOutputStream(new File(tmpDir + File.separator + "OPS", - filename)), "UTF-8")); - inDialogue = false; - inNormal = false; - try { - String title = "Chapter " + chap.getNumber(); - String nameOrNum = Integer.toString(chap.getNumber()); - if (chap.getName() != null && !chap.getName().isEmpty()) { - title += ": " + chap.getName(); - nameOrNum = chap.getName(); - } - - writer.append(""); - writer.append("\n"); - writer.append("\n"); - writer.write("\n"); - writer.write("\n " + StringUtils.xmlEscape(title) - + ""); - writer.write("\n "); - writer.write("\n"); - writer.write("\n"); - writer.write("\n

"); - writer.write("\n Chapter " - + chap.getNumber() + ": "); - writer.write("\n " - + StringUtils.xmlEscape(nameOrNum) + ""); - writer.write("\n

"); - writer.write("\n "); - writer.write("\n
\n"); - } catch (Exception e) { - writer.close(); - throw new IOException(e); - } - } - - @Override - protected void writeChapterFooter(Chapter chap) throws IOException { - try { - if (inDialogue) { - writer.write("
\n"); - inDialogue = false; - } - if (inNormal) { - writer.write(" \n"); - inNormal = false; - } - writer.write(" \n\n\n"); - } finally { - writer.close(); - writer = null; - } - } - - @Override - protected void writeParagraphHeader(Paragraph para) throws IOException { - if (para.getType() == ParagraphType.QUOTE && !inDialogue) { - writer.write("
\n"); - inDialogue = true; - } else if (para.getType() != ParagraphType.QUOTE && inDialogue) { - writer.write("
\n"); - inDialogue = false; - } - - if (para.getType() == ParagraphType.NORMAL && !inNormal) { - writer.write("
\n"); - inNormal = true; - } else if (para.getType() != ParagraphType.NORMAL && inNormal) { - writer.write("
\n"); - inNormal = false; - } - - switch (para.getType()) { - case BLANK: - writer.write("
"); - break; - case BREAK: - writer.write("
"); - break; - case NORMAL: - writer.write(" "); - break; - case QUOTE: - writer.write("
— "); - break; - case IMAGE: - File file = new File(images, getCurrentImageBestName(false)); - Instance.getInstance().getCache().saveAsImage(para.getContentImage(), file, nextParaIsCover); - writer.write(" page image"); - break; - } - - nextParaIsCover = false; - } - - @Override - protected void writeParagraphFooter(Paragraph para) throws IOException { - switch (para.getType()) { - case NORMAL: - writer.write("\n"); - break; - case QUOTE: - writer.write("
\n"); - break; - default: - writer.write("\n"); - break; - } - } - - @Override - protected void writeTextLine(ParagraphType type, String line) - throws IOException { - switch (type) { - case QUOTE: - case NORMAL: - writer.write(decorateText(StringUtils.xmlEscape(line))); - break; - default: - break; - } - } - - @Override - protected String enbold(String word) { - return "" + word + ""; - } - - @Override - protected String italize(String word) { - return "" + word + ""; - } - - private String generateNcx(Story story) { - StringBuilder builder = new StringBuilder(); - - String title = ""; - String uuid = ""; - String author = ""; - if (story.getMeta() != null) { - MetaData meta = story.getMeta(); - uuid = meta.getUuid(); - author = meta.getAuthor(); - title = meta.getTitle(); - } - - builder.append(""); - builder.append("\n"); - builder.append("\n"); - builder.append("\n "); - builder.append("\n "); - builder.append("\n "); - builder.append("\n "); - builder.append("\n "); - builder.append("\n "); - builder.append("\n "); - builder.append("\n "); - builder.append("\n "); - builder.append("\n " + StringUtils.xmlEscape(title) + ""); - builder.append("\n "); - builder.append("\n "); - - builder.append("\n " + StringUtils.xmlEscape(author) + ""); - builder.append("\n "); - builder.append("\n "); - builder.append("\n "); - builder.append("\n "); - builder.append("\n Title Page"); - builder.append("\n "); - builder.append("\n "); - builder.append("\n "); - - int navPoint = 2; // 1 is above - - if (story.getMeta() != null & story.getMeta().getResume() != null) { - Chapter chap = story.getMeta().getResume(); - generateNcx(chap, builder, navPoint++); - } - - for (Chapter chap : story) { - generateNcx(chap, builder, navPoint++); - } - - builder.append("\n "); - builder.append("\n\n"); - - return builder.toString(); - } - - private void generateNcx(Chapter chap, StringBuilder builder, int navPoint) { - String name; - if (chap.getName() != null && !chap.getName().isEmpty()) { - name = Instance.getInstance().getTrans().getString(StringId.CHAPTER_NAMED, chap.getNumber(), - chap.getName()); - } else { - name = Instance.getInstance().getTrans().getString(StringId.CHAPTER_UNNAMED, chap.getNumber()); - } - - String nnn = String.format("%03d", (navPoint - 2)); - - builder.append("\n "); - builder.append("\n "); - builder.append("\n " + name + ""); - builder.append("\n "); - builder.append("\n "); - builder.append("\n \n"); - } - - private String generateOpf(Story story) { - StringBuilder builder = new StringBuilder(); - - String title = ""; - String uuid = ""; - String author = ""; - String date = ""; - String publisher = ""; - String subject = ""; - String source = ""; - String lang = ""; - if (story.getMeta() != null) { - MetaData meta = story.getMeta(); - title = meta.getTitle(); - uuid = meta.getUuid(); - author = meta.getAuthor(); - date = meta.getDate(); - publisher = meta.getPublisher(); - subject = meta.getSubject(); - source = meta.getSource(); - lang = meta.getLang(); - } - - builder.append(""); - builder.append("\n"); - builder.append("\n "); - builder.append("\n " + StringUtils.xmlEscape(title) - + ""); - builder.append("\n " - + StringUtils.xmlEscape(author) + ""); - builder.append("\n " - + StringUtils.xmlEscape(date) + ""); - builder.append("\n " - + StringUtils.xmlEscape(publisher) + ""); - builder.append("\n "); - builder.append("\n " + StringUtils.xmlEscape(subject) - + ""); - builder.append("\n " + StringUtils.xmlEscape(source) - + ""); - builder.append("\n Not for commercial use."); - builder.append("\n " - + StringUtils.xmlEscape(uuid) + ""); - builder.append("\n " + StringUtils.xmlEscape(lang) - + ""); - builder.append("\n "); - builder.append("\n "); - builder.append("\n "); - builder.append("\n "); - for (int i = 0; i <= story.getChapters().size(); i++) { - String name = String.format("%s%03d", "chapter-", i); - builder.append("\n "); - } - - builder.append("\n "); - builder.append("\n "); - - builder.append("\n "); - - if (story.getMeta() != null && story.getMeta().getCover() != null) { - String format = Instance.getInstance().getConfig() - .getString(Config.FILE_FORMAT_IMAGE_FORMAT_COVER) - .toLowerCase(); - builder.append("\n "); - } - - builder.append("\n "); - builder.append("\n "); - builder.append("\n "); - builder.append("\n "); - builder.append("\n "); - for (int i = 0; i <= story.getChapters().size(); i++) { - String name = String.format("%s%03d", "chapter-", i); - builder.append("\n "); - } - builder.append("\n "); - builder.append("\n\n"); - - return builder.toString(); - } - - private String generateTitleXml(Story story) { - StringBuilder builder = new StringBuilder(); - - String title = ""; - String tags = ""; - String author = ""; - if (story.getMeta() != null) { - MetaData meta = story.getMeta(); - title = meta.getTitle(); - if (meta.getTags() != null) { - for (String tag : meta.getTags()) { - if (!tags.isEmpty()) { - tags += ", "; - } - tags += tag; - } - - if (!tags.isEmpty()) { - tags = "(" + tags + ")"; - } - } - author = meta.getAuthor(); - } - - String format = Instance.getInstance().getConfig() - .getString(Config.FILE_FORMAT_IMAGE_FORMAT_COVER).toLowerCase(); - - builder.append(""); - builder.append("\n"); - builder.append("\n"); - builder.append("\n"); - builder.append("\n " + StringUtils.xmlEscape(title) + ""); - builder.append("\n "); - builder.append("\n"); - builder.append("\n"); - builder.append("\n
"); - builder.append("\n

" + StringUtils.xmlEscape(title) + "

"); - builder.append("\n
" - + StringUtils.xmlEscape(tags) + "
"); - builder.append("\n
"); - builder.append("\n \"cover"); - builder.append("\n
"); - builder.append("\n
" - + StringUtils.xmlEscape(author) + "
"); - builder.append("\n
"); - builder.append("\n"); - builder.append("\n\n"); - - return builder.toString(); - } -} diff --git a/src/be/nikiroo/fanfix/output/Html.java b/src/be/nikiroo/fanfix/output/Html.java deleted file mode 100644 index da79466a..00000000 --- a/src/be/nikiroo/fanfix/output/Html.java +++ /dev/null @@ -1,264 +0,0 @@ -package be.nikiroo.fanfix.output; - -import java.io.BufferedWriter; -import java.io.File; -import java.io.FileOutputStream; -import java.io.IOException; -import java.io.InputStream; -import java.io.OutputStreamWriter; - -import be.nikiroo.fanfix.Instance; -import be.nikiroo.fanfix.bundles.Config; -import be.nikiroo.fanfix.data.Chapter; -import be.nikiroo.fanfix.data.MetaData; -import be.nikiroo.fanfix.data.Paragraph; -import be.nikiroo.fanfix.data.Paragraph.ParagraphType; -import be.nikiroo.fanfix.data.Story; -import be.nikiroo.utils.IOUtils; -import be.nikiroo.utils.StringUtils; - -class Html extends BasicOutput { - private File dir; - protected BufferedWriter writer; - private boolean inDialogue = false; - private boolean inNormal = false; - - @Override - public File process(Story story, File targetDir, String targetName) - throws IOException { - String targetNameOrig = targetName; - - File target = new File(targetDir, targetName); - target.mkdir(); - dir = target; - - target = new File(targetDir, targetName + getDefaultExtension(true)); - - writer = new BufferedWriter(new OutputStreamWriter( - new FileOutputStream(target), "UTF-8")); - try { - super.process(story, targetDir, targetNameOrig); - } finally { - writer.close(); - writer = null; - } - - // write a copy of the originals inside - InfoCover.writeInfo(dir, targetName, story.getMeta()); - InfoCover.writeCover(dir, targetName, story.getMeta()); - BasicOutput.getOutput(OutputType.TEXT, isWriteInfo(), isWriteCover()) - .process(story, dir, targetNameOrig); - - if (story.getMeta().getCover() != null) { - Instance.getInstance().getCache().saveAsImage(story.getMeta().getCover(), new File(dir, "cover"), true); - } - - return target; - } - - @Override - public String getDefaultExtension(boolean readerTarget) { - if (readerTarget) { - return File.separator + "index.html"; - } - - return ""; - } - - @Override - protected void writeStoryHeader(Story story) throws IOException { - String title = ""; - String tags = ""; - String author = ""; - Chapter resume = null; - if (story.getMeta() != null) { - MetaData meta = story.getMeta(); - title = meta.getTitle(); - resume = meta.getResume(); - if (meta.getTags() != null) { - for (String tag : meta.getTags()) { - if (!tags.isEmpty()) { - tags += ", "; - } - tags += tag; - } - - if (!tags.isEmpty()) { - tags = "(" + tags + ")"; - } - } - author = meta.getAuthor(); - } - - String format = Instance.getInstance().getConfig() - .getString(Config.FILE_FORMAT_IMAGE_FORMAT_COVER).toLowerCase(); - - InputStream inStyle = getClass().getResourceAsStream("html.style.css"); - if (inStyle == null) { - throw new IOException("Cannot find style.css resource"); - } - try { - IOUtils.write(inStyle, new File(dir, "style.css")); - } finally { - inStyle.close(); - } - - writer.write(""); - writer.write("\n"); - writer.write("\n"); - writer.write("\n "); - writer.write("\n "); - writer.write("\n "); - writer.write("\n " + StringUtils.xmlEscape(title) + ""); - writer.write("\n"); - writer.write("\n\n"); - - writer.write("\n
"); - writer.write("\n

" + StringUtils.xmlEscape(title) + "

"); - writer.write("\n
" + StringUtils.xmlEscape(tags) - + "
"); - writer.write("\n
"); - writer.write("\n "); - writer.write("\n
"); - writer.write("\n
" - + StringUtils.xmlEscape(author) + "
"); - writer.write("\n
"); - - writer.write("\n

"); - - if (resume != null) { - for (Paragraph para : resume) { - writeParagraph(para); - } - if (inDialogue) { - writer.write(" \n"); - inDialogue = false; - } - if (inNormal) { - writer.write(" \n"); - inNormal = false; - } - } - - writer.write("\n
"); - } - - @Override - protected void writeStoryFooter(Story story) throws IOException { - writer.write("\n"); - } - - @Override - protected void writeChapterHeader(Chapter chap) throws IOException { - String nameOrNumber; - if (chap.getName() != null && !chap.getName().isEmpty()) { - nameOrNumber = chap.getName(); - } else { - nameOrNumber = Integer.toString(chap.getNumber()); - } - - writer.write("\n

"); - writer.write("\n Chapter " - + chap.getNumber() + ": "); - writer.write("\n " - + StringUtils.xmlEscape(nameOrNumber) + ""); - writer.write("\n

"); - writer.write("\n "); - writer.write("\n
\n"); - - inDialogue = false; - inNormal = false; - } - - @Override - protected void writeChapterFooter(Chapter chap) throws IOException { - if (inDialogue) { - writer.write("
\n"); - inDialogue = false; - } - if (inNormal) { - writer.write(" \n"); - inNormal = false; - } - - writer.write("\n "); - } - - @Override - protected void writeParagraphHeader(Paragraph para) throws IOException { - if (para.getType() == ParagraphType.QUOTE && !inDialogue) { - writer.write("
\n"); - inDialogue = true; - } else if (para.getType() != ParagraphType.QUOTE && inDialogue) { - writer.write("
\n"); - inDialogue = false; - } - - if (para.getType() == ParagraphType.NORMAL && !inNormal) { - writer.write("
\n"); - inNormal = true; - } else if (para.getType() != ParagraphType.NORMAL && inNormal) { - writer.write("
\n"); - inNormal = false; - } - - switch (para.getType()) { - case BLANK: - writer.write("
"); - break; - case BREAK: - writer.write("
"); - break; - case NORMAL: - writer.write(" "); - break; - case QUOTE: - writer.write("
— "); - break; - case IMAGE: - // TODO - writer.write("" - + StringUtils.xmlEscape(para.getContent()) + ""); - break; - } - } - - @Override - protected void writeParagraphFooter(Paragraph para) throws IOException { - switch (para.getType()) { - case NORMAL: - writer.write("\n"); - break; - case QUOTE: - writer.write("
\n"); - break; - default: - writer.write("\n"); - break; - } - } - - @Override - protected void writeTextLine(ParagraphType type, String line) - throws IOException { - switch (type) { - case QUOTE: - case NORMAL: - writer.write(decorateText(StringUtils.xmlEscape(line))); - break; - default: - break; - } - } - - @Override - protected String enbold(String word) { - return "" + word + ""; - } - - @Override - protected String italize(String word) { - return "" + word + ""; - } -} diff --git a/src/be/nikiroo/fanfix/output/InfoCover.java b/src/be/nikiroo/fanfix/output/InfoCover.java deleted file mode 100644 index d8ca49a6..00000000 --- a/src/be/nikiroo/fanfix/output/InfoCover.java +++ /dev/null @@ -1,90 +0,0 @@ -package be.nikiroo.fanfix.output; - -import java.io.BufferedWriter; -import java.io.File; -import java.io.FileOutputStream; -import java.io.IOException; -import java.io.OutputStreamWriter; - -import be.nikiroo.fanfix.Instance; -import be.nikiroo.fanfix.bundles.Config; -import be.nikiroo.fanfix.data.MetaData; - -public class InfoCover { - public static void writeInfo(File targetDir, String targetName, - MetaData meta) throws IOException { - File info = new File(targetDir, targetName + ".info"); - - BufferedWriter infoWriter = null; - try { - infoWriter = new BufferedWriter(new OutputStreamWriter( - new FileOutputStream(info), "UTF-8")); - - if (meta != null) { - String tags = ""; - if (meta.getTags() != null) { - for (String tag : meta.getTags()) { - if (!tags.isEmpty()) { - tags += ", "; - } - tags += tag; - } - } - - writeMeta(infoWriter, "TITLE", meta.getTitle()); - writeMeta(infoWriter, "AUTHOR", meta.getAuthor()); - writeMeta(infoWriter, "DATE", meta.getDate()); - writeMeta(infoWriter, "SUBJECT", meta.getSubject()); - writeMeta(infoWriter, "SOURCE", meta.getSource()); - writeMeta(infoWriter, "URL", meta.getUrl()); - writeMeta(infoWriter, "TAGS", tags); - writeMeta(infoWriter, "UUID", meta.getUuid()); - writeMeta(infoWriter, "LUID", meta.getLuid()); - writeMeta(infoWriter, "LANG", meta.getLang() == null ? "" - : meta.getLang().toLowerCase()); - writeMeta(infoWriter, "IMAGES_DOCUMENT", - meta.isImageDocument() ? "true" : "false"); - writeMeta(infoWriter, "TYPE", meta.getType()); - if (meta.getCover() != null) { - String format = Instance.getInstance().getConfig() - .getString(Config.FILE_FORMAT_IMAGE_FORMAT_COVER).toLowerCase(); - writeMeta(infoWriter, "COVER", targetName + "." + format); - } else { - writeMeta(infoWriter, "COVER", ""); - } - writeMeta(infoWriter, "EPUBCREATOR", BasicOutput.EPUB_CREATOR); - writeMeta(infoWriter, "PUBLISHER", meta.getPublisher()); - writeMeta(infoWriter, "WORDCOUNT", - Long.toString(meta.getWords())); - writeMeta(infoWriter, "CREATION_DATE", meta.getCreationDate()); - writeMeta(infoWriter, "FAKE_COVER", - Boolean.toString(meta.isFakeCover())); - } - } finally { - if (infoWriter != null) { - infoWriter.close(); - } - } - } - - public static void writeCover(File targetDir, String targetName, - MetaData meta) { - if (meta != null && meta.getCover() != null) { - try { - Instance.getInstance().getCache().saveAsImage(meta.getCover(), new File(targetDir, targetName), true); - } catch (IOException e) { - // Allow to continue without cover - Instance.getInstance().getTraceHandler().error(new IOException("Failed to save the cover image", e)); - } - } - } - - private static void writeMeta(BufferedWriter writer, String key, - String value) throws IOException { - if (value == null) { - value = ""; - } - - writer.write(String.format("%s=\"%s\"\n", key, value.replace("\"", "'"))); - } -} diff --git a/src/be/nikiroo/fanfix/output/InfoText.java b/src/be/nikiroo/fanfix/output/InfoText.java deleted file mode 100644 index 935da870..00000000 --- a/src/be/nikiroo/fanfix/output/InfoText.java +++ /dev/null @@ -1,69 +0,0 @@ -package be.nikiroo.fanfix.output; - -import java.io.IOException; - -import be.nikiroo.fanfix.Instance; -import be.nikiroo.fanfix.bundles.StringId; -import be.nikiroo.fanfix.data.Chapter; -import be.nikiroo.fanfix.data.Paragraph.ParagraphType; - -class InfoText extends Text { - // quote chars - private char openQuote = Instance.getInstance().getTrans().getCharacter(StringId.OPEN_SINGLE_QUOTE); - private char closeQuote = Instance.getInstance().getTrans().getCharacter(StringId.CLOSE_SINGLE_QUOTE); - private char openDoubleQuote = Instance.getInstance().getTrans().getCharacter(StringId.OPEN_DOUBLE_QUOTE); - private char closeDoubleQuote = Instance.getInstance().getTrans().getCharacter(StringId.CLOSE_DOUBLE_QUOTE); - - @Override - public String getDefaultExtension(boolean readerTarget) { - return ""; - } - - @Override - protected void writeChapterHeader(Chapter chap) throws IOException { - writer.write("\n"); - - if (chap.getName() != null && !chap.getName().isEmpty()) { - writer.write(Instance.getInstance().getTrans().getString(StringId.CHAPTER_NAMED, chap.getNumber(), - chap.getName())); - } else { - writer.write(Instance.getInstance().getTrans().getString(StringId.CHAPTER_UNNAMED, chap.getNumber())); - } - - writer.write("\n\n"); - } - - @Override - protected void writeTextLine(ParagraphType type, String line) - throws IOException { - switch (type) { - case NORMAL: - case QUOTE: - StringBuilder builder = new StringBuilder(); - for (char car : line.toCharArray()) { - if (car == '—') { - builder.append("---"); - } else if (car == '–') { - builder.append("--"); - } else if (car == openDoubleQuote) { - builder.append("\""); - } else if (car == closeDoubleQuote) { - builder.append("\""); - } else if (car == openQuote) { - builder.append("'"); - } else if (car == closeQuote) { - builder.append("'"); - } else { - builder.append(car); - } - } - - line = builder.toString(); - break; - default: - break; - } - - super.writeTextLine(type, line); - } -} diff --git a/src/be/nikiroo/fanfix/output/LaTeX.java b/src/be/nikiroo/fanfix/output/LaTeX.java deleted file mode 100644 index a15e67c9..00000000 --- a/src/be/nikiroo/fanfix/output/LaTeX.java +++ /dev/null @@ -1,180 +0,0 @@ -package be.nikiroo.fanfix.output; - -import java.io.BufferedWriter; -import java.io.File; -import java.io.FileOutputStream; -import java.io.IOException; -import java.io.OutputStreamWriter; - -import be.nikiroo.fanfix.Instance; -import be.nikiroo.fanfix.bundles.Config; -import be.nikiroo.fanfix.bundles.StringId; -import be.nikiroo.fanfix.data.Chapter; -import be.nikiroo.fanfix.data.MetaData; -import be.nikiroo.fanfix.data.Paragraph.ParagraphType; -import be.nikiroo.fanfix.data.Story; - -class LaTeX extends BasicOutput { - protected BufferedWriter writer; - private boolean lastWasQuote = false; - - // quote chars - private char openQuote = Instance.getInstance().getTrans().getCharacter(StringId.OPEN_SINGLE_QUOTE); - private char closeQuote = Instance.getInstance().getTrans().getCharacter(StringId.CLOSE_SINGLE_QUOTE); - private char openDoubleQuote = Instance.getInstance().getTrans().getCharacter(StringId.OPEN_DOUBLE_QUOTE); - private char closeDoubleQuote = Instance.getInstance().getTrans().getCharacter(StringId.CLOSE_DOUBLE_QUOTE); - - @Override - public File process(Story story, File targetDir, String targetName) - throws IOException { - String targetNameOrig = targetName; - targetName += getDefaultExtension(false); - - File target = new File(targetDir, targetName); - - writer = new BufferedWriter(new OutputStreamWriter( - new FileOutputStream(target), "UTF-8")); - try { - super.process(story, targetDir, targetNameOrig); - } finally { - writer.close(); - writer = null; - } - - return target; - } - - @Override - public String getDefaultExtension(boolean readerTarget) { - return ".tex"; - } - - @Override - protected void writeStoryHeader(Story story) throws IOException { - String date = ""; - String author = ""; - String title = "\\title{}"; - String lang = ""; - if (story.getMeta() != null) { - MetaData meta = story.getMeta(); - title = "\\title{" + latexEncode(meta.getTitle()) + "}"; - date = "\\date{" + latexEncode(meta.getDate()) + "}"; - author = "\\author{" + latexEncode(meta.getAuthor()) + "}"; - lang = meta.getLang().toLowerCase(); - if (lang != null && !lang.isEmpty()) { - lang = Instance.getInstance().getConfig().getStringX(Config.CONF_LATEX_LANG, lang); - if (lang == null) { - System.err.println(Instance.getInstance().getTrans().getString(StringId.LATEX_LANG_UNKNOWN, lang)); - } - } - } - - writer.append("%\n"); - writer.append("% This LaTeX document was auto-generated by Fanfic Reader, created by Niki.\n"); - writer.append("%\n\n"); - writer.append("\\documentclass[a4paper]{book}\n"); - if (lang != null && !lang.isEmpty()) { - writer.append("\\usepackage[" + lang + "]{babel}\n"); - } - writer.append("\\usepackage[utf8]{inputenc}\n"); - writer.append("\\usepackage[T1]{fontenc}\n"); - writer.append("\\usepackage{lmodern}\n"); - writer.append("\\newcommand{\\br}{\\vspace{10 mm}}\n"); - writer.append("\\newcommand{\\say}{--- \\noindent\\emph}\n"); - writer.append("\\hyphenpenalty=1000\n"); - writer.append("\\tolerance=5000\n"); - writer.append("\\begin{document}\n"); - if (story.getMeta() != null && story.getMeta().getDate() != null) - writer.append(date + "\n"); - writer.append(title + "\n"); - writer.append(author + "\n"); - writer.append("\\maketitle\n"); - writer.append("\n"); - - // TODO: cover - } - - @Override - protected void writeStoryFooter(Story story) throws IOException { - writer.append("\\end{document}\n"); - } - - @Override - protected void writeChapterHeader(Chapter chap) throws IOException { - writer.append("\n\n\\chapter{" + latexEncode(chap.getName()) + "}" - + "\n"); - } - - @Override - protected void writeChapterFooter(Chapter chap) throws IOException { - writer.write("\n"); - } - - @Override - protected String enbold(String word) { - return "\\textsc{" + word + "}"; - } - - @Override - protected String italize(String word) { - return "\\emph{" + word + "}"; - } - - @Override - protected void writeTextLine(ParagraphType type, String line) - throws IOException { - - line = decorateText(latexEncode(line)); - - switch (type) { - case BLANK: - writer.write("\n"); - lastWasQuote = false; - break; - case BREAK: - writer.write("\n\\br"); - writer.write("\n"); - lastWasQuote = false; - break; - case NORMAL: - writer.write(line); - writer.write("\n"); - lastWasQuote = false; - break; - case QUOTE: - writer.write("\n\\say{" + line + "}\n"); - if (lastWasQuote) { - writer.write("\n\\noindent{}"); - } - lastWasQuote = true; - break; - case IMAGE: - // TODO - break; - } - } - - private String latexEncode(String input) { - StringBuilder builder = new StringBuilder(); - for (char car : input.toCharArray()) { - // TODO: check restricted chars? - if (car == '^' || car == '$' || car == '\\' || car == '#' - || car == '%') { - builder.append('\\'); - builder.append(car); - } else if (car == openQuote) { - builder.append('`'); - } else if (car == closeQuote) { - builder.append('\''); - } else if (car == openDoubleQuote) { - builder.append("``"); - } else if (car == closeDoubleQuote) { - builder.append("''"); - } else { - builder.append(car); - } - } - - return builder.toString(); - } -} diff --git a/src/be/nikiroo/fanfix/output/Sysout.java b/src/be/nikiroo/fanfix/output/Sysout.java deleted file mode 100644 index f6cd789c..00000000 --- a/src/be/nikiroo/fanfix/output/Sysout.java +++ /dev/null @@ -1,22 +0,0 @@ -package be.nikiroo.fanfix.output; - -import be.nikiroo.fanfix.data.Chapter; -import be.nikiroo.fanfix.data.Paragraph; -import be.nikiroo.fanfix.data.Story; - -class Sysout extends BasicOutput { - @Override - protected void writeStoryHeader(Story story) { - System.out.println(story); - } - - @Override - protected void writeChapterHeader(Chapter chap) { - System.out.println(chap); - } - - @Override - protected void writeParagraphHeader(Paragraph para) { - System.out.println(para); - } -} diff --git a/src/be/nikiroo/fanfix/output/Text.java b/src/be/nikiroo/fanfix/output/Text.java deleted file mode 100644 index f0516dc6..00000000 --- a/src/be/nikiroo/fanfix/output/Text.java +++ /dev/null @@ -1,133 +0,0 @@ -package be.nikiroo.fanfix.output; - -import java.io.BufferedWriter; -import java.io.File; -import java.io.FileOutputStream; -import java.io.IOException; -import java.io.OutputStreamWriter; - -import be.nikiroo.fanfix.Instance; -import be.nikiroo.fanfix.bundles.StringId; -import be.nikiroo.fanfix.data.Chapter; -import be.nikiroo.fanfix.data.MetaData; -import be.nikiroo.fanfix.data.Paragraph; -import be.nikiroo.fanfix.data.Paragraph.ParagraphType; -import be.nikiroo.fanfix.data.Story; - -class Text extends BasicOutput { - protected BufferedWriter writer; - protected File targetDir; - private boolean nextParaIsCover = true; - - @Override - public File process(Story story, File targetDir, String targetName) - throws IOException { - String targetNameOrig = targetName; - targetName += getDefaultExtension(false); - - this.targetDir = targetDir; - - File target = new File(targetDir, targetName); - - writer = new BufferedWriter(new OutputStreamWriter( - new FileOutputStream(target), "UTF-8")); - try { - super.process(story, targetDir, targetNameOrig); - } finally { - writer.close(); - writer = null; - } - - return target; - } - - @Override - public String getDefaultExtension(boolean readerTarget) { - return ".txt"; - } - - @Override - protected void writeStoryHeader(Story story) throws IOException { - String title = ""; - String author = null; - String date = null; - - MetaData meta = story.getMeta(); - if (meta != null) { - title = meta.getTitle() == null ? "" : meta.getTitle(); - author = meta.getAuthor(); - date = meta.getDate(); - } - - writer.write(title); - writer.write("\n"); - if (author != null && !author.isEmpty()) { - writer.write(Instance.getInstance().getTrans().getString(StringId.BY) + " " + author); - } - if (date != null && !date.isEmpty()) { - writer.write(" ("); - writer.write(date); - writer.write(")"); - } - writer.write("\n"); - - // resume: - if (meta != null && meta.getResume() != null) { - writeChapter(meta.getResume()); - } - } - - @Override - protected void writeChapterHeader(Chapter chap) throws IOException { - String txt; - if (chap.getName() != null && !chap.getName().isEmpty()) { - txt = Instance.getInstance().getTrans().getString(StringId.CHAPTER_NAMED, chap.getNumber(), chap.getName()); - } else { - txt = Instance.getInstance().getTrans().getString(StringId.CHAPTER_UNNAMED, chap.getNumber()); - } - - writer.write("\n" + txt + "\n"); - for (int i = 0; i < txt.length(); i++) { - writer.write("—"); - } - writer.write("\n\n"); - } - - @Override - protected void writeParagraphFooter(Paragraph para) throws IOException { - writer.write("\n"); - } - - @Override - protected void writeParagraphHeader(Paragraph para) throws IOException { - if (para.getType() == ParagraphType.IMAGE) { - File file = new File(targetDir, getCurrentImageBestName(true)); - try { - Instance.getInstance().getCache().saveAsImage(para.getContentImage(), file, nextParaIsCover); - } catch (IOException e) { - Instance.getInstance().getTraceHandler().error(new IOException("Cannot save an image", e)); - } - } - - nextParaIsCover = false; - } - - @Override - protected void writeTextLine(ParagraphType type, String line) - throws IOException { - switch (type) { - case BLANK: - break; - case BREAK: - writer.write("\n* * *\n"); - break; - case NORMAL: - case QUOTE: - writer.write(line); - break; - case IMAGE: - writer.write("[" + getCurrentImageBestName(true) + "]"); - break; - } - } -} diff --git a/src/be/nikiroo/fanfix/output/epub.style.css b/src/be/nikiroo/fanfix/output/epub.style.css deleted file mode 100644 index 2c4a961b..00000000 --- a/src/be/nikiroo/fanfix/output/epub.style.css +++ /dev/null @@ -1,110 +0,0 @@ -html { - text-align: justify; -} - -.titlepage { - padding-left: 10%; - padding-right: 10%; - width: 80%; -} - -h1 { - padding-bottom: 0; - margin-bottom: 0; - text-align: left; -} - -.type { - position: relative; - font-size: large; - color: #666666; - font-weight: bold; - padding-bottom: 10px; - text-align: left; -} - -.cover, .page-image { - width: 100%; -} - -.cover img { - height: 45%; - max-width: 100%; - margin: auto; -} - -.author { - text-align: right; - font-size: large; - font-style: italic; -} - -.book, .chapter_content { - NO_text-indent: 40px; - padding-top: 40px; - padding-left: 5%; - padding-right: 5%; - width: 90%; -} - -h2 { - border: 1px solid black; - color: #222222; - padding-left: 10px; - padding-right: 10px; - display: block; - padding-bottom: 0; - margin-bottom: 0; -} - -h2 .chap { - color: #000000; - font-size: large; - font-variant: small-caps; - display: block; -} - -h2 .chap:first-letter { - font-weight: bold; -} - -h2 .chapnumber { - color: #000000; - font-size: xx-large; -} - -h2 .chaptitle { - color: #444444; - font-size: large; - font-style: italic; - padding-bottom: 5px; - text-align: right; - display: block; -} - -.normals { -} - -.normal { - /* Can be removed if you want a more "compact" view */ - display: block; -} - -.blank { - /* Can be removed if you want a more "compact" view */ - height: 24px; - width: 100%; -} - -hr.break { - /* Can be removed if you want a more "compact" view */ - margin-top: 48px; - margin-bottom: 48px; -} - -.dialogues { -} - -.dialogue { - font-style: italic; -} \ No newline at end of file diff --git a/src/be/nikiroo/fanfix/output/html.style.css b/src/be/nikiroo/fanfix/output/html.style.css deleted file mode 100644 index 6b6d0d20..00000000 --- a/src/be/nikiroo/fanfix/output/html.style.css +++ /dev/null @@ -1,112 +0,0 @@ -html { - text-align: justify; - max-width: 800px; - margin: auto; -} - -.titlepage { - padding-left: 10%; - padding-right: 10%; - width: 80%; -} - -h1 { - padding-bottom: 0; - margin-bottom: 0; - text-align: left; -} - -.type { - position: relative; - font-size: large; - color: #666666; - font-weight: bold; - padding-bottom: 10px; - text-align: left; -} - -.cover, .page-image { - width: 100%; -} - -.cover img { - height: 45%; - max-width: 100%; - margin: auto; -} - -.author { - text-align: right; - font-size: large; - font-style: italic; -} - -.book, .chapter_content { - NO_text-indent: 40px; - padding-top: 40px; - padding-left: 5%; - padding-right: 5%; - width: 90%; -} - -h2 { - border: 1px solid black; - color: #222222; - padding-left: 10px; - padding-right: 10px; - display: block; - padding-bottom: 0; - margin-bottom: 0; -} - -h2 .chap { - color: #000000; - font-size: large; - font-variant: small-caps; - display: block; -} - -h2 .chap:first-letter { - font-weight: bold; -} - -h2 .chapnumber { - color: #000000; - font-size: xx-large; -} - -h2 .chaptitle { - color: #444444; - font-size: large; - font-style: italic; - padding-bottom: 5px; - text-align: right; - display: block; -} - -.normals { -} - -.normal { - /* Can be removed if you want a more "compact" view */ - display: block; -} - -.blank { - /* Can be removed if you want a more "compact" view */ - height: 24px; - width: 100%; -} - -hr.break { - /* Can be removed if you want a more "compact" view */ - margin-top: 48px; - margin-bottom: 48px; -} - -.dialogues { -} - -.dialogue { - font-style: italic; -} diff --git a/src/be/nikiroo/fanfix/output/package-info.java b/src/be/nikiroo/fanfix/output/package-info.java deleted file mode 100644 index 8314c803..00000000 --- a/src/be/nikiroo/fanfix/output/package-info.java +++ /dev/null @@ -1,12 +0,0 @@ -/** - * This package contains all the output processors. - *

- * Of those, only {@link be.nikiroo.fanfix.output.BasicOutput} is public, - * but it contains a method - * ({@link be.nikiroo.fanfix.output.BasicOutput#getOutput(be.nikiroo.fanfix.output.BasicOutput.OutputType, boolean,boolean)}) - * to get all the other - * {@link be.nikiroo.fanfix.output.BasicOutput.OutputType}s. - * - * @author niki - */ -package be.nikiroo.fanfix.output; \ No newline at end of file diff --git a/src/be/nikiroo/fanfix/package-info.java b/src/be/nikiroo/fanfix/package-info.java deleted file mode 100644 index cfd9cbef..00000000 --- a/src/be/nikiroo/fanfix/package-info.java +++ /dev/null @@ -1,11 +0,0 @@ -/** - * Fanfix is a program that can support a few different websites from - * which to retrieve stories, then process them into epub (or other) - * files that you can read anywhere. - *

- * It has support for a {@link be.nikiroo.fanfix.library.BasicLibrary} system, - * too, and can even offer its services over the network. - * - * @author niki - */ -package be.nikiroo.fanfix; \ No newline at end of file diff --git a/src/be/nikiroo/fanfix/reader/BasicReader.java b/src/be/nikiroo/fanfix/reader/BasicReader.java deleted file mode 100644 index 7f79da3a..00000000 --- a/src/be/nikiroo/fanfix/reader/BasicReader.java +++ /dev/null @@ -1,385 +0,0 @@ -package be.nikiroo.fanfix.reader; - -import java.io.File; -import java.io.IOException; -import java.net.MalformedURLException; -import java.net.URL; -import java.text.ParseException; -import java.text.SimpleDateFormat; -import java.util.Date; -import java.util.Map; -import java.util.TreeMap; - -import be.nikiroo.fanfix.Instance; -import be.nikiroo.fanfix.bundles.Config; -import be.nikiroo.fanfix.bundles.UiConfig; -import be.nikiroo.fanfix.data.MetaData; -import be.nikiroo.fanfix.data.Story; -import be.nikiroo.fanfix.library.BasicLibrary; -import be.nikiroo.fanfix.library.LocalLibrary; -import be.nikiroo.fanfix.supported.BasicSupport; -import be.nikiroo.utils.Progress; -import be.nikiroo.utils.StringUtils; -import be.nikiroo.utils.serial.SerialUtils; - -/** - * The class that handles the different {@link Story} readers you can use. - *

- * All the readers should be accessed via {@link BasicReader#getReader()}. - * - * @author niki - */ -public abstract class BasicReader implements Reader { - private static BasicLibrary defaultLibrary = Instance.getInstance().getLibrary(); - private static ReaderType defaultType = ReaderType.GUI; - - private BasicLibrary lib; - private MetaData meta; - private Story story; - private int chapter; - - /** - * Take the default reader type configuration from the config file. - */ - static { - String typeString = Instance.getInstance().getConfig().getString(Config.READER_TYPE); - if (typeString != null && !typeString.isEmpty()) { - try { - ReaderType type = ReaderType.valueOf(typeString.toUpperCase()); - defaultType = type; - } catch (IllegalArgumentException e) { - // Do nothing - } - } - } - - @Override - public synchronized Story getStory(Progress pg) throws IOException { - if (story == null) { - story = getLibrary().getStory(meta.getLuid(), pg); - } - - return story; - } - - @Override - public BasicLibrary getLibrary() { - if (lib == null) { - lib = defaultLibrary; - } - - return lib; - } - - @Override - public void setLibrary(BasicLibrary lib) { - this.lib = lib; - } - - @Override - public synchronized MetaData getMeta() { - return meta; - } - - @Override - public synchronized void setMeta(MetaData meta) throws IOException { - setMeta(meta == null ? null : meta.getLuid()); // must check the library - } - - @Override - public synchronized void setMeta(String luid) throws IOException { - story = null; - meta = getLibrary().getInfo(luid); - - if (meta == null) { - throw new IOException("Cannot retrieve story from library: " + luid); - } - } - - @Override - public synchronized void setMeta(URL url, Progress pg) throws IOException { - BasicSupport support = BasicSupport.getSupport(url); - if (support == null) { - throw new IOException("URL not supported: " + url.toString()); - } - - story = support.process(pg); - if (story == null) { - throw new IOException( - "Cannot retrieve story from external source: " - + url.toString()); - } - - meta = story.getMeta(); - } - - @Override - public int getChapter() { - return chapter; - } - - @Override - public void setChapter(int chapter) { - this.chapter = chapter; - } - - /** - * Return a new {@link BasicReader} ready for use if one is configured. - *

- * Can return NULL if none are configured. - * - * @return a {@link BasicReader}, or NULL if none configured - */ - public static Reader getReader() { - try { - if (defaultType != null) { - return (Reader) SerialUtils.createObject(defaultType - .getTypeName()); - } - } catch (Exception e) { - Instance.getInstance().getTraceHandler() - .error(new Exception("Cannot create a reader of type: " + defaultType + " (Not compiled in?)", e)); - } - - return null; - } - - /** - * The default {@link Reader.ReaderType} used when calling - * {@link BasicReader#getReader()}. - * - * @return the default type - */ - public static ReaderType getDefaultReaderType() { - return defaultType; - } - - /** - * The default {@link Reader.ReaderType} used when calling - * {@link BasicReader#getReader()}. - * - * @param defaultType - * the new default type - */ - public static void setDefaultReaderType(ReaderType defaultType) { - BasicReader.defaultType = defaultType; - } - - /** - * Change the default {@link LocalLibrary} to open with the - * {@link BasicReader}s. - * - * @param lib - * the new {@link LocalLibrary} - */ - public static void setDefaultLibrary(BasicLibrary lib) { - BasicReader.defaultLibrary = lib; - } - - /** - * Return an {@link URL} from this {@link String}, be it a file path or an - * actual {@link URL}. - * - * @param sourceString - * the source - * - * @return the corresponding {@link URL} - * - * @throws MalformedURLException - * if this is neither a file nor a conventional {@link URL} - */ - public static URL getUrl(String sourceString) throws MalformedURLException { - if (sourceString == null || sourceString.isEmpty()) { - throw new MalformedURLException("Empty url"); - } - - URL source = null; - try { - source = new URL(sourceString); - } catch (MalformedURLException e) { - File sourceFile = new File(sourceString); - source = sourceFile.toURI().toURL(); - } - - return source; - } - - /** - * Describe a {@link Story} from its {@link MetaData} and return a list of - * title/value that represent this {@link Story}. - * - * @param meta - * the {@link MetaData} to represent - * - * @return the information - */ - public static Map getMetaDesc(MetaData meta) { - Map metaDesc = new TreeMap(); - - // TODO: i18n - - StringBuilder tags = new StringBuilder(); - for (String tag : meta.getTags()) { - if (tags.length() > 0) { - tags.append(", "); - } - tags.append(tag); - } - - // TODO: i18n - metaDesc.put("Author", meta.getAuthor()); - metaDesc.put("Publication date", formatDate(meta.getDate())); - metaDesc.put("Published on", meta.getPublisher()); - metaDesc.put("URL", meta.getUrl()); - String count = ""; - if (meta.getWords() > 0) { - count = StringUtils.formatNumber(meta.getWords()); - } - if (meta.isImageDocument()) { - metaDesc.put("Number of images", count); - } else { - metaDesc.put("Number of words", count); - } - metaDesc.put("Source", meta.getSource()); - metaDesc.put("Subject", meta.getSubject()); - metaDesc.put("Language", meta.getLang()); - metaDesc.put("Tags", tags.toString()); - - return metaDesc; - } - - /** - * Open the {@link Story} with an external reader (the program will be - * passed the main file associated with this {@link Story}). - * - * @param lib - * the {@link BasicLibrary} to select the {@link Story} from - * @param luid - * the {@link Story} LUID - * @param sync - * execute the process synchronously (wait until it is terminated - * before returning) - * - * @throws IOException - * in case of I/O error - */ - @Override - public void openExternal(BasicLibrary lib, String luid, boolean sync) - throws IOException { - MetaData meta = lib.getInfo(luid); - File target = lib.getFile(luid, null); - - openExternal(meta, target, sync); - } - - /** - * Open the {@link Story} with an external reader (the program will be - * passed the given target file). - * - * @param meta - * the {@link Story} to load - * @param target - * the target {@link File} - * @param sync - * execute the process synchronously (wait until it is terminated - * before returning) - * - * @throws IOException - * in case of I/O error - */ - protected void openExternal(MetaData meta, File target, boolean sync) - throws IOException { - String program = null; - if (meta.isImageDocument()) { - program = Instance.getInstance().getUiConfig().getString(UiConfig.IMAGES_DOCUMENT_READER); - } else { - program = Instance.getInstance().getUiConfig().getString(UiConfig.NON_IMAGES_DOCUMENT_READER); - } - - if (program != null && program.trim().isEmpty()) { - program = null; - } - - start(target, program, sync); - } - - /** - * Start a file and open it with the given program if given or the first - * default system starter we can find. - * - * @param target - * the target to open - * @param program - * the program to use or NULL for the default system starter - * @param sync - * execute the process synchronously (wait until it is terminated - * before returning) - * - * @throws IOException - * in case of I/O error - */ - protected void start(File target, String program, boolean sync) - throws IOException { - - Process proc = null; - if (program == null) { - boolean ok = false; - for (String starter : new String[] { "xdg-open", "open", "see", - "start", "run" }) { - try { - Instance.getInstance().getTraceHandler().trace("starting external program"); - proc = Runtime.getRuntime().exec(new String[] { starter, target.getAbsolutePath() }); - ok = true; - break; - } catch (IOException e) { - } - } - if (!ok) { - throw new IOException("Cannot find a program to start the file"); - } - } else { - Instance.getInstance().getTraceHandler().trace("starting external program"); - proc = Runtime.getRuntime().exec( - new String[] { program, target.getAbsolutePath() }); - } - - if (proc != null && sync) { - try { - proc.waitFor(); - } catch (InterruptedException e) { - } - } - } - - static private String formatDate(String date) { - long ms = 0; - - if (date != null && !date.isEmpty()) { - try { - ms = StringUtils.toTime(date); - } catch (ParseException e) { - } - - if (ms <= 0) { - SimpleDateFormat sdf = new SimpleDateFormat( - "yyyy-MM-dd'T'HH:mm:ssSSS"); - try { - ms = sdf.parse(date).getTime(); - } catch (ParseException e) { - } - } - - if (ms > 0) { - SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd"); - return sdf.format(new Date(ms)); - } - } - - if (date == null) { - date = ""; - } - - // :( - return date; - } -} diff --git a/src/be/nikiroo/fanfix/reader/Reader.java b/src/be/nikiroo/fanfix/reader/Reader.java deleted file mode 100644 index 3ecf2470..00000000 --- a/src/be/nikiroo/fanfix/reader/Reader.java +++ /dev/null @@ -1,267 +0,0 @@ -package be.nikiroo.fanfix.reader; - -import java.io.IOException; -import java.net.URL; - -import be.nikiroo.fanfix.data.MetaData; -import be.nikiroo.fanfix.data.Story; -import be.nikiroo.fanfix.library.BasicLibrary; -import be.nikiroo.fanfix.supported.SupportType; -import be.nikiroo.utils.Progress; - -/** - * A {@link Reader} is a class that will handle {@link Story} reading and - * browsing. - * - * @author niki - */ -public interface Reader { - /** - * A type of {@link BasicReader}. - * - * @author niki - */ - public enum ReaderType { - /** Simple reader that outputs everything on the console */ - CLI, - /** Reader that starts local programs to handle the stories */ - GUI, - /** A text (UTF-8) reader with menu and text windows */ - TUI, - /** A GUI reader implemented with the Android framework */ - ANDROID, - - ; - - /** - * Return the full class name of a type that implements said - * {@link ReaderType}. - * - * @return the class name - */ - public String getTypeName() { - String pkg = "be.nikiroo.fanfix.reader."; - switch (this) { - case CLI: - return pkg + "cli.CliReader"; - case TUI: - return pkg + "tui.TuiReader"; - case GUI: - return pkg + "ui.GuiReader"; - case ANDROID: - return pkg + "android.AndroidReader"; - } - - return null; - } - } - - /** - * Return the current target {@link MetaData}. - * - * @return the meta - */ - public MetaData getMeta(); - - /** - * Return the current {@link Story} as described by the current - * {@link MetaData}. - * - * @param pg - * the optional progress - * - * @return the {@link Story} - * - * @throws IOException - * in case of I/O error - * - */ - public Story getStory(Progress pg) throws IOException; - - /** - * The {@link BasicLibrary} to load the stories from (by default, takes the - * default {@link BasicLibrary}). - * - * @return the {@link BasicLibrary} - */ - public BasicLibrary getLibrary(); - - /** - * Change the {@link BasicLibrary} that will be managed by this - * {@link BasicReader}. - * - * @param lib - * the new {@link BasicLibrary} - */ - public void setLibrary(BasicLibrary lib); - - /** - * Set a {@link Story} from the current {@link BasicLibrary} into the - * {@link Reader}. - * - * @param luid - * the {@link Story} ID - * - * @throws IOException - * in case of I/O error - */ - public void setMeta(String luid) throws IOException; - - /** - * Set a {@link Story} from the current {@link BasicLibrary} into the - * {@link Reader}. - * - * @param meta - * the meta - * - * @throws IOException - * in case of I/O error - */ - public void setMeta(MetaData meta) throws IOException; - - /** - * Set an external {@link Story} into this {@link Reader}. - * - * @param source - * the {@link Story} {@link URL} - * @param pg - * the optional progress reporter - * - * @throws IOException - * in case of I/O error - */ - public void setMeta(URL source, Progress pg) throws IOException; - - /** - * Start the {@link Story} Reading. - * - * @param sync - * execute the process synchronously (wait until it is terminated - * before returning) - * - * @throws IOException - * in case of I/O error or if the {@link Story} was not - * previously set - */ - public void read(boolean sync) throws IOException; - - /** - * The selected chapter to start reading at (starting at 1, 0 = description, - * -1 = none). - * - * @return the chapter, or -1 for "no chapter" - */ - public int getChapter(); - - /** - * The selected chapter to start reading at (starting at 1, 0 = description, - * -1 = none). - * - * @param chapter - * the chapter, or -1 for "no chapter" - */ - public void setChapter(int chapter); - - /** - * Start the reader in browse mode for the given source (or pass NULL for - * all sources). - *

- * Note that this must be a synchronous action. - * - * @param source - * the type of {@link Story} to take into account, or NULL for - * all - * - * @throws IOException - * in case of I/O error - */ - public void browse(String source) throws IOException; - - /** - * Display all supports that allow search operations. - * - * @param sync - * execute the process synchronously (wait until it is terminated - * before returning) - * - * @throws IOException - * in case of I/O error - */ - public void search(boolean sync) throws IOException; - - /** - * Search for the given terms and find stories that correspond if possible. - * - * @param searchOn - * the website to search on - * @param keywords - * the words to search for (cannot be NULL) - * @param page - * the page of results to show (0 = request the maximum number of - * pages, pages start at 1) - * @param item - * the item to select (0 = do not select a specific item but show - * all the page, items start at 1) - * @param sync - * execute the process synchronously (wait until it is terminated - * before returning) - * - * @throws IOException - * in case of I/O error - */ - public void search(SupportType searchOn, String keywords, int page, - int item, boolean sync) throws IOException; - - /** - * Search based upon a hierarchy of tags, or search for (sub)tags. - *

- * We use the tags DisplayName. - *

- * If no tag is given, the main tags will be shown. - *

- * If a non-leaf tag is given, the subtags will be shown. - *

- * If a leaf tag is given (or a full hierarchy ending with a leaf tag), - * stories will be shown. - *

- * You can select the story you want with the item number. - * - * @param searchOn - * the website to search on - * @param page - * the page of results to show (0 = request the maximum number of - * pages, pages start at 1) - * @param item - * the item to select (0 = do not select a specific item but show - * all the page, items start at 1) - * @param sync - * execute the process synchronously (wait until it is terminated - * before returning) - * @param tags - * the tags indices to search for (this is a tag - * hierarchy, NOT a multiple tags choice) - * - * @throws IOException - * in case of I/O error - */ - public void searchTag(SupportType searchOn, int page, int item, - boolean sync, Integer... tags) throws IOException; - - /** - * Open the {@link Story} with an external reader (the program should be - * passed the main file associated with this {@link Story}). - * - * @param lib - * the {@link BasicLibrary} to select the {@link Story} from - * @param luid - * the {@link Story} LUID - * @param sync - * execute the process synchronously (wait until it is terminated - * before returning) - * - * @throws IOException - * in case of I/O error - */ - public void openExternal(BasicLibrary lib, String luid, boolean sync) - throws IOException; -} diff --git a/src/be/nikiroo/fanfix/searchable/BasicSearchable.java b/src/be/nikiroo/fanfix/searchable/BasicSearchable.java deleted file mode 100644 index b943abd2..00000000 --- a/src/be/nikiroo/fanfix/searchable/BasicSearchable.java +++ /dev/null @@ -1,275 +0,0 @@ -package be.nikiroo.fanfix.searchable; - -import java.io.IOException; -import java.net.URL; -import java.util.List; - -import org.jsoup.helper.DataUtil; -import org.jsoup.nodes.Document; - -import be.nikiroo.fanfix.Instance; -import be.nikiroo.fanfix.data.MetaData; -import be.nikiroo.fanfix.supported.BasicSupport; -import be.nikiroo.fanfix.supported.SupportType; - -/** - * This class supports browsing through stories on the supported websites. It - * will fetch some {@link MetaData} that satisfy a search query or some tags if - * supported. - * - * @author niki - */ -public abstract class BasicSearchable { - private SupportType type; - private BasicSupport support; - - /** - * Create a new {@link BasicSearchable} of the given type. - * - * @param type - * the type, must not be NULL - */ - public BasicSearchable(SupportType type) { - setType(type); - support = BasicSupport.getSupport(getType(), null); - } - - /** - * Find the given tag by its hierarchical IDs. - *

- * I.E., it will take the tag A, subtag B, subsubtag C... - * - * @param ids - * the IDs to look for - * - * @return the appropriate tag fully filled, or NULL if not found - * - * @throws IOException - * in case of I/O error - */ - public SearchableTag getTag(Integer... ids) throws IOException { - SearchableTag tag = null; - List tags = getTags(); - - for (Integer tagIndex : ids) { - // ! 1-based index ! - if (tagIndex == null || tags == null || tagIndex <= 0 - || tagIndex > tags.size()) { - return null; - } - - tag = tags.get(tagIndex - 1); - fillTag(tag); - tags = tag.getChildren(); - } - - return tag; - } - - /** - * The support type. - * - * @return the type - */ - public SupportType getType() { - return type; - } - - /** - * The support type. - * - * @param type - * the new type - */ - protected void setType(SupportType type) { - this.type = type; - } - - /** - * The associated {@link BasicSupport}. - *

- * Mostly used to download content. - * - * @return the support - */ - protected BasicSupport getSupport() { - return support; - } - - /** - * Get a list of tags that can be browsed here. - * - * @return the list of tags - * - * @throws IOException - * in case of I/O error - */ - abstract public List getTags() throws IOException; - - /** - * Fill the tag (set it 'complete') with more information from the support. - * - * @param tag - * the tag to fill - * - * @throws IOException - * in case of I/O error - */ - abstract public void fillTag(SearchableTag tag) throws IOException; - - /** - * Search for the given term and return the number of pages of results of - * stories satisfying this search term. - * - * @param search - * the term to search for - * - * @return a number of pages - * - * @throws IOException - * in case of I/O error - */ - abstract public int searchPages(String search) throws IOException; - - /** - * Search for the given tag and return the number of pages of results of - * stories satisfying this tag. - * - * @param tag - * the tag to search for - * - * @return a number of pages - * - * @throws IOException - * in case of I/O error - */ - abstract public int searchPages(SearchableTag tag) throws IOException; - - /** - * Search for the given term and return a list of stories satisfying this - * search term. - *

- * Not that the returned stories will NOT be complete, but will only - * contain enough information to present them to the user and retrieve them. - *

- * URL is guaranteed to be usable, LUID will always be NULL. - * - * @param search - * the term to search for - * @param page - * the page to use for result pagination, index is 1-based - * - * @return a list of stories that satisfy that search term - * - * @throws IOException - * in case of I/O error - */ - abstract public List search(String search, int page) - throws IOException; - - /** - * Search for the given tag and return a list of stories satisfying this - * tag. - *

- * Not that the returned stories will NOT be complete, but will only - * contain enough information to present them to the user and retrieve them. - *

- * URL is guaranteed to be usable, LUID will always be NULL. - * - * @param tag - * the tag to search for - * @param page - * the page to use for result pagination (see - * {@link SearchableTag#getPages()}, remember to check for -1), - * index is 1-based - * - * @return a list of stories that satisfy that search term - * - * @throws IOException - * in case of I/O error - */ - abstract public List search(SearchableTag tag, int page) - throws IOException; - - /** - * Load a document from its url. - * - * @param url - * the URL to load - * @param stable - * TRUE for more stable resources, FALSE when they often change - * - * @return the document - * - * @throws IOException - * in case of I/O error - */ - protected Document load(String url, boolean stable) throws IOException { - return load(new URL(url), stable); - } - - /** - * Load a document from its url. - * - * @param url - * the URL to load - * @param stable - * TRUE for more stable resources, FALSE when they often change - * - * @return the document - * - * @throws IOException - * in case of I/O error - */ - protected Document load(URL url, boolean stable) throws IOException { - return DataUtil.load(Instance.getInstance().getCache().open(url, support, stable), "UTF-8", url.toString()); - } - - /** - * Return a {@link BasicSearchable} implementation supporting the given - * type, or NULL if it does not exist. - * - * @param type - * the type, can be NULL (will just return NULL, since we do not - * support it) - * - * @return an implementation that supports it, or NULL - */ - static public BasicSearchable getSearchable(SupportType type) { - BasicSearchable support = null; - - if (type != null) { - switch (type) { - case FIMFICTION: - // TODO - break; - case FANFICTION: - support = new Fanfiction(type); - break; - case MANGAHUB: - // TODO - break; - case E621: - // TODO - break; - case YIFFSTAR: - // TODO - break; - case E_HENTAI: - // TODO - break; - case MANGA_LEL: - support = new MangaLel(); - break; - case CBZ: - case HTML: - case INFO_TEXT: - case TEXT: - case EPUB: - break; - } - } - - return support; - } -} diff --git a/src/be/nikiroo/fanfix/searchable/Fanfiction.java b/src/be/nikiroo/fanfix/searchable/Fanfiction.java deleted file mode 100644 index e2fba1ff..00000000 --- a/src/be/nikiroo/fanfix/searchable/Fanfiction.java +++ /dev/null @@ -1,411 +0,0 @@ -package be.nikiroo.fanfix.searchable; - -import java.io.IOException; -import java.io.InputStream; -import java.net.URL; -import java.net.URLEncoder; -import java.text.SimpleDateFormat; -import java.util.ArrayList; -import java.util.Date; -import java.util.HashMap; -import java.util.List; -import java.util.Map; - -import org.jsoup.nodes.Document; -import org.jsoup.nodes.Element; -import org.jsoup.select.Elements; - -import be.nikiroo.fanfix.Instance; -import be.nikiroo.fanfix.bundles.StringId; -import be.nikiroo.fanfix.data.MetaData; -import be.nikiroo.fanfix.supported.SupportType; -import be.nikiroo.utils.Image; -import be.nikiroo.utils.StringUtils; - -/** - * A {@link BasicSearchable} for Fanfiction.NET. - * - * @author niki - */ -class Fanfiction extends BasicSearchable { - static private String BASE_URL = "http://fanfiction.net/"; - - /** - * Create a new {@link Fanfiction}. - * - * @param type - * {@link SupportType#FANFICTION} - */ - public Fanfiction(SupportType type) { - super(type); - } - - @Override - public List getTags() throws IOException { - String storiesName = null; - String crossoversName = null; - Map stories = new HashMap(); - Map crossovers = new HashMap(); - - Document mainPage = load(BASE_URL, true); - Element menu = mainPage.getElementsByClass("dropdown").first(); - if (menu != null) { - Element ul = menu.getElementsByClass("dropdown-menu").first(); - if (ul != null) { - Map currentList = null; - for (Element li : ul.getElementsByTag("li")) { - if (li.hasClass("disabled")) { - if (storiesName == null) { - storiesName = li.text(); - currentList = stories; - } else { - crossoversName = li.text(); - currentList = crossovers; - } - } else if (currentList != null) { - Element a = li.getElementsByTag("a").first(); - if (a != null) { - currentList.put(a.absUrl("href"), a.text()); - } - } - } - } - } - - List tags = new ArrayList(); - - if (storiesName != null) { - SearchableTag tag = new SearchableTag(null, storiesName, false); - for (String id : stories.keySet()) { - tag.add(new SearchableTag(id, stories.get(id), false, false)); - } - tags.add(tag); - } - - if (crossoversName != null) { - SearchableTag tag = new SearchableTag(null, crossoversName, false); - for (String id : crossovers.keySet()) { - tag.add(new SearchableTag(id, crossovers.get(id), false, false)); - } - tags.add(tag); - } - - return tags; - } - - @Override - public void fillTag(SearchableTag tag) throws IOException { - if (tag.getId() == null || tag.isComplete()) { - return; - } - - Document doc = load(tag.getId(), false); - Element list = doc.getElementById("list_output"); - if (list != null) { - Element table = list.getElementsByTag("table").first(); - if (table != null) { - for (Element div : table.getElementsByTag("div")) { - Element a = div.getElementsByTag("a").first(); - Element span = div.getElementsByTag("span").first(); - - if (a != null) { - String subid = a.absUrl("href"); - boolean crossoverSubtag = subid - .contains("/crossovers/"); - - SearchableTag subtag = new SearchableTag(subid, - a.text(), !crossoverSubtag, !crossoverSubtag); - - tag.add(subtag); - if (span != null) { - String nr = span.text(); - if (nr.startsWith("(")) { - nr = nr.substring(1); - } - if (nr.endsWith(")")) { - nr = nr.substring(0, nr.length() - 1); - } - nr = nr.trim(); - - // TODO: fix toNumber/fromNumber - nr = nr.replaceAll("\\.[0-9]*", ""); - - subtag.setCount(StringUtils.toNumber(nr)); - } - } - } - } - } - - tag.setComplete(true); - } - - @Override - public List search(String search, int page) throws IOException { - String encoded = URLEncoder.encode(search.toLowerCase(), "utf-8"); - String url = BASE_URL + "search/?ready=1&type=story&keywords=" - + encoded + "&ppage=" + page; - - return getStories(url, null, null); - } - - @Override - public List search(SearchableTag tag, int page) - throws IOException { - List metas = new ArrayList(); - - String url = tag.getId(); - if (url != null) { - if (page > 1) { - int pos = url.indexOf("&p="); - if (pos >= 0) { - url = url.replaceAll("(.*\\&p=)[0-9]*(.*)", "$1\\" + page - + "$2"); - } else { - url += "&p=" + page; - } - } - - Document doc = load(url, false); - - // Update the pages number if needed - if (tag.getPages() < 0 && tag.isLeaf()) { - tag.setPages(getPages(doc)); - } - - // Find out the full subjects (including parents) - String subjects = ""; - for (SearchableTag t = tag; t != null; t = t.getParent()) { - if (!subjects.isEmpty()) { - subjects += ", "; - } - subjects += t.getName(); - } - - metas = getStories(url, doc, subjects); - } - - return metas; - } - - @Override - public int searchPages(String search) throws IOException { - String encoded = URLEncoder.encode(search.toLowerCase(), "utf-8"); - String url = BASE_URL + "search/?ready=1&type=story&keywords=" - + encoded; - - return getPages(load(url, false)); - } - - @Override - public int searchPages(SearchableTag tag) throws IOException { - if (tag.isLeaf()) { - String url = tag.getId(); - return getPages(load(url, false)); - } - - return 0; - } - - /** - * Return the number of pages in this stories result listing. - * - * @param doc - * the document - * - * @return the number of pages or -1 if unknown - */ - private int getPages(Document doc) { - int pages = -1; - - if (doc != null) { - Element center = doc.getElementsByTag("center").first(); - if (center != null) { - for (Element a : center.getElementsByTag("a")) { - if (a.absUrl("href").contains("&p=")) { - int thisLinkPages = -1; - try { - String[] tab = a.absUrl("href").split("="); - tab = tab[tab.length - 1].split("&"); - thisLinkPages = Integer - .parseInt(tab[tab.length - 1]); - } catch (Exception e) { - } - - pages = Math.max(pages, thisLinkPages); - } - } - } - } - - return pages; - } - - /** - * Fetch the stories from the given page. - * - * @param sourceUrl - * the url of the document - * @param doc - * the document to use (if NULL, will be loaded from - * sourceUrl) - * @param mainSubject - * the main subject (the anime/book/movie item related to the - * stories, like "MLP" or "Doctor Who"), or NULL if none - * - * @return the stories found in it - * - * @throws IOException - * in case of I/O errors - */ - private List getStories(String sourceUrl, Document doc, - String mainSubject) throws IOException { - List metas = new ArrayList(); - - if (doc == null) { - doc = load(sourceUrl, false); - } - - for (Element story : doc.getElementsByClass("z-list")) { - MetaData meta = new MetaData(); - meta.setImageDocument(false); - meta.setSource(getType().getSourceName()); - meta.setPublisher(getType().getSourceName()); - meta.setType(getType().toString()); - - // Title, URL, Cover - Element stitle = story.getElementsByClass("stitle").first(); - if (stitle != null) { - meta.setTitle(stitle.text()); - meta.setUrl(stitle.absUrl("href")); - meta.setUuid(meta.getUrl()); - Element cover = stitle.getElementsByTag("img").first(); - if (cover != null) { - // note: see data-original if needed? - String coverUrl = cover.absUrl("src"); - - try { - InputStream in = Instance.getInstance().getCache().open(new URL(coverUrl), getSupport(), true); - try { - meta.setCover(new Image(in)); - } finally { - in.close(); - } - } catch (Exception e) { - // Should not happen on Fanfiction.net - Instance.getInstance().getTraceHandler().error(new Exception( - "Cannot download cover for Fanfiction story in search mode: " + meta.getTitle(), e)); - } - } - } - - // Author - Elements as = story.getElementsByTag("a"); - if (as.size() > 1) { - meta.setAuthor(as.get(1).text()); - } - - // Tags (concatenated text), published date, updated date, Resume - String tags = ""; - List tagList = new ArrayList(); - Elements divs = story.getElementsByTag("div"); - if (divs.size() > 1 && divs.get(1).childNodeSize() > 0) { - String resume = divs.get(1).text(); - if (divs.size() > 2) { - tags = divs.get(2).text(); - resume = resume.substring(0, - resume.length() - tags.length()).trim(); - - for (Element d : divs.get(2).getElementsByAttribute( - "data-xutime")) { - String secs = d.attr("data-xutime"); - try { - String date = new SimpleDateFormat("yyyy-MM-dd") - .format(new Date( - Long.parseLong(secs) * 1000)); - // (updated, ) published - if (meta.getDate() != null) { - tagList.add("Updated: " + meta.getDate()); - } - meta.setDate(date); - } catch (Exception e) { - } - } - } - - meta.setResume(getSupport().makeChapter(new URL(sourceUrl), 0, - Instance.getInstance().getTrans().getString(StringId.DESCRIPTION), resume)); - } - - // How are the tags ordered? - // We have "Rated: xx", then the language, then all other tags - // If the subject(s) is/are present, they are before "Rated: xx" - - // //////////// - // Examples: // - // //////////// - - // Search (Luna) Tags: [Harry Potter, Rated: T, English, Chapters: - // 1, Words: 270, Reviews: 2, Published: 2/19/2013, Luna L.] - - // Normal (MLP) Tags: [Rated: T, Spanish, Drama/Suspense, Chapters: - // 2, Words: 8,686, Reviews: 1, Favs: 1, Follows: 1, Updated: 4/7, - // Published: 4/2] - - // Crossover (MLP/Who) Tags: [Rated: K+, English, Adventure/Romance, - // Chapters: 8, Words: 7,788, Reviews: 2, Favs: 2, Follows: 1, - // Published: 9/1/2016] - - boolean rated = false; - boolean isLang = false; - String subject = mainSubject == null ? "" : mainSubject; - String[] tab = tags.split(" *- *"); - for (int i = 0; i < tab.length; i++) { - String tag = tab[i]; - if (tag.startsWith("Rated: ")) { - rated = true; - } - - if (!rated) { - if (!subject.isEmpty()) { - subject += ", "; - } - subject += tag; - } else if (isLang) { - meta.setLang(tag); - isLang = false; - } else { - if (tag.contains(":")) { - // Handle special tags: - if (tag.startsWith("Words: ")) { - try { - meta.setWords(Long.parseLong(tag - .substring("Words: ".length()) - .replace(",", "").trim())); - } catch (Exception e) { - } - } else if (tag.startsWith("Rated: ")) { - tagList.add(tag); - } - } else { - // Normal tags are "/"-separated - for (String t : tag.split("/")) { - tagList.add(t); - } - } - - if (tag.startsWith("Rated: ")) { - isLang = true; - } - } - } - - meta.setSubject(subject); - meta.setTags(tagList); - - metas.add(meta); - } - - return metas; - } -} diff --git a/src/be/nikiroo/fanfix/searchable/MangaLel.java b/src/be/nikiroo/fanfix/searchable/MangaLel.java deleted file mode 100644 index 5ba21a0e..00000000 --- a/src/be/nikiroo/fanfix/searchable/MangaLel.java +++ /dev/null @@ -1,184 +0,0 @@ -package be.nikiroo.fanfix.searchable; - -import java.io.IOException; -import java.io.InputStream; -import java.net.URL; -import java.net.URLEncoder; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.List; - -import org.jsoup.helper.DataUtil; -import org.jsoup.nodes.Document; -import org.jsoup.nodes.Element; -import org.jsoup.select.Elements; - -import be.nikiroo.fanfix.Instance; -import be.nikiroo.fanfix.bundles.StringId; -import be.nikiroo.fanfix.data.MetaData; -import be.nikiroo.fanfix.supported.SupportType; -import be.nikiroo.utils.Image; -import be.nikiroo.utils.StringUtils; - -class MangaLel extends BasicSearchable { - private String BASE_URL = "http://mangas-lecture-en-ligne.fr/index_lel.php"; - - public MangaLel() { - super(SupportType.MANGA_LEL); - } - - @Override - public List getTags() throws IOException { - List tags = new ArrayList(); - - String url = BASE_URL + "?page=recherche"; - Document doc = load(url, false); - - Element genre = doc.getElementsByClass("genre").first(); - if (genre != null) { - for (Element el : genre.getElementsByAttributeValueStarting("for", - "genre")) { - tags.add(new SearchableTag(el.attr("for"), el.text(), true)); - } - } - - return tags; - } - - @Override - public void fillTag(SearchableTag tag) throws IOException { - // Tags are always complete - } - - @Override - public List search(String search, int page) throws IOException { - String url = BASE_URL + "?nomProjet=" - + URLEncoder.encode(search, "utf-8") - + "&nomAuteur=&nomTeam=&page=recherche&truc=truc"; - - // No pagination - return getResults(url); - } - - @Override - public List search(SearchableTag tag, int page) - throws IOException { - String url = BASE_URL + "?nomProjet=&nomAuteur=&nomTeam=&" - + tag.getId() + "=on&page=recherche&truc=truc"; - - // No pagination - return getResults(url); - } - - @Override - public int searchPages(String search) throws IOException { - // No pagination - return 1; - } - - @Override - public int searchPages(SearchableTag tag) throws IOException { - if (tag.isLeaf()) { - // No pagination - return 1; - } - - return 0; - } - - private List getResults(String sourceUrl) throws IOException { - List metas = new ArrayList(); - - Document doc = DataUtil.load(Instance.getInstance().getCache().open(new URL(sourceUrl), getSupport(), false), - "UTF-8", sourceUrl); - - for (Element result : doc.getElementsByClass("rechercheAffichage")) { - Element a = result.getElementsByTag("a").first(); - if (a != null) { - int projectId = -1; - - MetaData meta = new MetaData(); - - // Target: - // http://mangas-lecture-en-ligne.fr/index_lel.php?page=presentationProjet&idProjet=218 - - // a.absUrl("href"): - // http://mangas-lecture-en-ligne.fr/index_lel?onCommence=oui&idChapitre=2805 - - // ...but we need the PROJECT id, not the CHAPTER id -> use - // - - Elements infos = result.getElementsByClass("texte"); - if (infos != null) { - String[] tab = infos.outerHtml().split("
"); - - meta.setLang("fr"); - meta.setSource(getType().getSourceName()); - meta.setPublisher(getType().getSourceName()); - meta.setType(getType().toString()); - meta.setSubject("manga"); - meta.setImageDocument(true); - meta.setTitle(getVal(tab, 0)); - meta.setAuthor(getVal(tab, 1)); - meta.setTags(Arrays.asList(getVal(tab, 2).split(" "))); - - meta.setResume(getSupport().makeChapter(new URL(sourceUrl), 0, - Instance.getInstance().getTrans().getString(StringId.DESCRIPTION), getVal(tab, 5))); - } - - Element img = result.getElementsByTag("img").first(); - if (img != null) { - try { - String[] tab = img.attr("src").split("/"); - String str = tab[tab.length - 1]; - tab = str.split("\\."); - str = tab[0]; - projectId = Integer.parseInt(str); - - String coverUrl = img.absUrl("src"); - try { - InputStream in = Instance.getInstance().getCache().open(new URL(coverUrl), getSupport(), - true); - try { - meta.setCover(new Image(in)); - } finally { - in.close(); - } - } catch (Exception e) { - // Happen often on MangaLEL... - Instance.getInstance().getTraceHandler().trace( - "Cannot download cover for MangaLEL story in search mode: " + meta.getTitle()); - } - } catch (Exception e) { - // no project id... cannot use the story :( - Instance.getInstance().getTraceHandler() - .error("Cannot find ProjectId for MangaLEL story in search mode: " + meta.getTitle()); - } - } - - if (projectId >= 0) { - meta.setUrl("http://mangas-lecture-en-ligne.fr/index_lel.php?page=presentationProjet&idProjet=" - + projectId); - meta.setUuid(meta.getUrl()); - metas.add(meta); - } - } - } - - return metas; - } - - private String getVal(String[] tab, int i) { - String val = ""; - - if (i < tab.length) { - val = StringUtils.unhtml(tab[i]); - int pos = val.indexOf(":"); - if (pos >= 0) { - val = val.substring(pos + 1).trim(); - } - } - - return val; - } -} diff --git a/src/be/nikiroo/fanfix/searchable/SearchableTag.java b/src/be/nikiroo/fanfix/searchable/SearchableTag.java deleted file mode 100644 index de867983..00000000 --- a/src/be/nikiroo/fanfix/searchable/SearchableTag.java +++ /dev/null @@ -1,324 +0,0 @@ -package be.nikiroo.fanfix.searchable; - -import java.util.ArrayList; -import java.util.List; - -/** - * This class represents a tag that can be searched on a supported website. - * - * @author niki - */ -public class SearchableTag { - private String id; - private String name; - private boolean complete; - private long count; - - private SearchableTag parent; - private List children; - - /** - * The number of stories result pages this tag can get. - *

- * We keep more information than what the getter/setter returns/accepts. - *

    - *
  • -2: this tag does not support stories results (not a leaf tag)
  • - *
  • -1: the number is not yet known, but will be known after a - * {@link BasicSearchable#fillTag(SearchableTag)} operation
  • - *
  • X: the number of pages
  • - *
- */ - private int pages; - - /** - * Create a new {@link SearchableTag}. - *

- * Note that tags are complete by default. - * - * @param id - * the ID (usually a way to find the linked stories later on) - * @param name - * the tag name, which can be displayed to the user - * @param leaf - * the tag is a leaf tag, that is, it will not return subtags - * with {@link BasicSearchable#fillTag(SearchableTag)} but will - * return stories with - * {@link BasicSearchable#search(SearchableTag, int)} - */ - public SearchableTag(String id, String name, boolean leaf) { - this(id, name, leaf, true); - } - - /** - * Create a new {@link SearchableTag}. - * - * @param id - * the ID (usually a way to find the linked stories later on) - * @param name - * the tag name, which can be displayed to the user - * @param leaf - * the tag is a leaf tag, that is, it will not return subtags - * with {@link BasicSearchable#fillTag(SearchableTag)} but will - * return stories with - * {@link BasicSearchable#search(SearchableTag, int)} - * @param complete - * the tag {@link SearchableTag#isComplete()} or not - */ - public SearchableTag(String id, String name, boolean leaf, boolean complete) { - this.id = id; - this.name = name; - this.complete = leaf || complete; - - setLeaf(leaf); - - children = new ArrayList(); - } - - /** - * The ID (usually a way to find the linked stories later on). - * - * @return the ID - */ - public String getId() { - return id; - } - - /** - * The tag name, which can be displayed to the user. - * - * @return then name - */ - public String getName() { - return name; - } - - /** - * The fully qualified tag name, which can be displayed to the user. - *

- * It will display all the tags that lead to this one as well as this one. - * - * @return the fully qualified name - */ - public String getFqName() { - if (parent != null) { - return parent.getFqName() + " / " + name; - } - - return "" + name; - } - - /** - * Non-complete, non-leaf tags can still be completed via a - * {@link BasicSearchable#fillTag(SearchableTag)} operation from a - * {@link BasicSearchable}, in order to gain (more?) subtag children. - *

- * Leaf tags are always considered complete. - * - * @return TRUE if it is complete - */ - public boolean isComplete() { - return complete; - } - - /** - * Non-complete, non-leaf tags can still be completed via a - * {@link BasicSearchable#fillTag(SearchableTag)} operation from a - * {@link BasicSearchable}, in order to gain (more?) subtag children. - *

- * Leaf tags are always considered complete. - * - * @param complete - * TRUE if it is complete - */ - public void setComplete(boolean complete) { - this.complete = isLeaf() || complete; - } - - /** - * The number of items that can be found with this tag if it is searched. - *

- * Will report the number of subtags by default. - * - * @return the number of items - */ - public long getCount() { - long count = this.count; - if (count <= 0) { - count = children.size(); - } - - return count; - } - - /** - * The number of items that can be found with this tag if it is searched. - * - * @param count - * the new count - */ - public void setCount(long count) { - this.count = count; - } - - /** - * The number of stories result pages this tag contains, only make sense if - * {@link SearchableTag#isLeaf()} returns TRUE. - *

- * Will return -1 if the number is not yet known. - * - * @return the number of pages, or -1 - */ - public int getPages() { - return Math.max(-1, pages); - } - - /** - * The number of stories result pages this tag contains, only make sense if - * {@link SearchableTag#isLeaf()} returns TRUE. - * - * @param pages - * the (positive or 0) number of pages - */ - public void setPages(int pages) { - this.pages = Math.max(-1, pages); - } - - /** - * This tag is a leaf tag, that is, it will not return other subtags with - * {@link BasicSearchable#fillTag(SearchableTag)} but will return stories - * with {@link BasicSearchable#search(SearchableTag, int)}. - * - * @return TRUE if it is - */ - public boolean isLeaf() { - return pages > -2; - } - - /** - * This tag is a leaf tag, that is, it will not return other subtags with - * {@link BasicSearchable#fillTag(SearchableTag)} but will return stories - * with {@link BasicSearchable#search(SearchableTag, int)}. - *

- * Will reset the number of pages to -1. - * - * @param leaf - * TRUE if it is - */ - public void setLeaf(boolean leaf) { - pages = leaf ? -1 : -2; - if (leaf) { - complete = true; - } - } - - /** - * The subtag children of this {@link SearchableTag}. - *

- * Never NULL. - *

- * Note that if {@link SearchableTag#isComplete()} returns false, you can - * still fill (more?) subtag children with a {@link BasicSearchable}. - * - * @return the subtag children, never NULL - */ - public List getChildren() { - return children; - } - - /** - * Add the given {@link SearchableTag} as a subtag child. - * - * @param tag - * the tag to add - */ - public void add(SearchableTag tag) { - if (tag == null) { - throw new NullPointerException("tag"); - } - - for (SearchableTag p = this; p != null; p = p.parent) { - if (p.equals(tag)) { - throw new IllegalArgumentException( - "Tags do not allow recursion"); - } - } - for (SearchableTag p = tag; p != null; p = p.parent) { - if (p.equals(this)) { - throw new IllegalArgumentException( - "Tags do not allow recursion"); - } - } - - children.add(tag); - tag.parent = this; - } - - /** - * This {@link SearchableTag} parent tag, or NULL if none. - * - * @return the parent or NULL - */ - public SearchableTag getParent() { - return parent; - } - - /** - * Display a DEBUG {@link String} representation of this object. - */ - @Override - public String toString() { - String rep = name + " [" + id + "]"; - if (!complete) { - rep += "*"; - } - - if (getCount() > 0) { - rep += " (" + getCount() + ")"; - } - - if (!children.isEmpty()) { - String tags = ""; - int i = 1; - for (SearchableTag tag : children) { - if (!tags.isEmpty()) { - tags += ", "; - } - - if (i > 10) { - tags += "..."; - break; - } - - tags += tag; - i++; - } - - rep += ": " + tags; - } - - return rep; - } - - @Override - public int hashCode() { - return getFqName().hashCode(); - } - - @Override - public boolean equals(Object otherObj) { - if (otherObj instanceof SearchableTag) { - SearchableTag other = (SearchableTag) otherObj; - if ((id == null && other.id == null) - || (id != null && id.equals(other.id))) { - if (getFqName().equals(other.getFqName())) { - if ((parent == null && other.parent == null) - || (parent != null && parent.equals(other.parent))) { - return true; - } - } - } - } - - return false; - } -} diff --git a/src/be/nikiroo/fanfix/subtree.txt b/src/be/nikiroo/fanfix/subtree.txt deleted file mode 100755 index ae585c77..00000000 --- a/src/be/nikiroo/fanfix/subtree.txt +++ /dev/null @@ -1,11 +0,0 @@ -# Subtrees - -# The subtrees used for this program: -# ```git subtree add -P src/be/nikiroo/utils git@github.com:nikiroo/nikiroo-utils.git subtree``` -# ```git subtree add -P src/jexer git@github.com:nikiroo/jexer.git subtree``` - -# Update all subtrees: - -git subtree pull -P src/be/nikiroo/utils git@github.com:nikiroo/nikiroo-utils.git subtree -git subtree pull -P src/jexer git@github.com:nikiroo/jexer.git subtree - diff --git a/src/be/nikiroo/fanfix/supported/BasicSupport.java b/src/be/nikiroo/fanfix/supported/BasicSupport.java deleted file mode 100644 index ba2164ca..00000000 --- a/src/be/nikiroo/fanfix/supported/BasicSupport.java +++ /dev/null @@ -1,525 +0,0 @@ -package be.nikiroo.fanfix.supported; - -import java.io.IOException; -import java.io.InputStream; -import java.net.URL; -import java.util.ArrayList; -import java.util.Date; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.Map.Entry; - -import org.jsoup.helper.DataUtil; -import org.jsoup.nodes.Document; -import org.jsoup.nodes.Element; -import org.jsoup.nodes.Node; - -import be.nikiroo.fanfix.Instance; -import be.nikiroo.fanfix.bundles.StringId; -import be.nikiroo.fanfix.data.Chapter; -import be.nikiroo.fanfix.data.MetaData; -import be.nikiroo.fanfix.data.Story; -import be.nikiroo.utils.Progress; -import be.nikiroo.utils.StringUtils; - -/** - * This class is the base class used by the other support classes. It can be - * used outside of this package, and have static method that you can use to get - * access to the correct support class. - *

- * It will be used with 'resources' (usually web pages or files). - * - * @author niki - */ -public abstract class BasicSupport { - private Document sourceNode; - private URL source; - private SupportType type; - private URL currentReferer; // with only one 'r', as in 'HTTP'... - - static protected BasicSupportHelper bsHelper = new BasicSupportHelper(); - static protected BasicSupportImages bsImages = new BasicSupportImages(); - static protected BasicSupportPara bsPara = new BasicSupportPara(new BasicSupportHelper(), new BasicSupportImages()); - - /** - * Check if the given resource is supported by this {@link BasicSupport}. - * - * @param url - * the resource to check for - * - * @return TRUE if it is - */ - protected abstract boolean supports(URL url); - - /** - * Return TRUE if the support will return HTML encoded content values for - * the chapters content. - * - * @return TRUE for HTML - */ - protected abstract boolean isHtml(); - - /** - * Return the {@link MetaData} of this story. - * - * @return the associated {@link MetaData}, never NULL - * - * @throws IOException - * in case of I/O error - */ - protected abstract MetaData getMeta() throws IOException; - - /** - * Return the story description. - * - * @return the description - * - * @throws IOException - * in case of I/O error - */ - protected abstract String getDesc() throws IOException; - - /** - * Return the list of chapters (name and resource). - *

- * Can be NULL if this {@link BasicSupport} do no use chapters. - * - * @param pg - * the optional progress reporter - * - * @return the chapters or NULL - * - * @throws IOException - * in case of I/O error - */ - protected abstract List> getChapters(Progress pg) - throws IOException; - - /** - * Return the content of the chapter (possibly HTML encoded, if - * {@link BasicSupport#isHtml()} is TRUE). - * - * @param chapUrl - * the chapter {@link URL} - * @param number - * the chapter number - * @param pg - * the optional progress reporter - * - * @return the content - * - * @throws IOException - * in case of I/O error - */ - protected abstract String getChapterContent(URL chapUrl, int number, - Progress pg) throws IOException; - - /** - * Return the list of cookies (values included) that must be used to - * correctly fetch the resources. - *

- * You are expected to call the super method implementation if you override - * it. - * - * @return the cookies - */ - public Map getCookies() { - return new HashMap(); - } - - /** - * OAuth authorisation (aka, "bearer XXXXXXX"). - * - * @return the OAuth string - */ - public String getOAuth() { - return null; - } - - /** - * Return the canonical form of the main {@link URL}. - * - * @param source - * the source {@link URL}, which can be NULL - * - * @return the canonical form of this {@link URL} or NULL if the source was - * NULL - */ - protected URL getCanonicalUrl(URL source) { - return source; - } - - /** - * The main {@link Node} for this {@link Story}. - * - * @return the node - */ - protected Element getSourceNode() { - return sourceNode; - } - - /** - * The main {@link URL} for this {@link Story}. - * - * @return the URL - */ - protected URL getSource() { - return source; - } - - /** - * The current referer {@link URL} (only one 'r', as in 'HTML'...), i.e., - * the current {@link URL} we work on. - * - * @return the referer - */ - public URL getCurrentReferer() { - return currentReferer; - } - - /** - * The current referer {@link URL} (only one 'r', as in 'HTML'...), i.e., - * the current {@link URL} we work on. - * - * @param currentReferer - * the new referer - */ - protected void setCurrentReferer(URL currentReferer) { - this.currentReferer = currentReferer; - } - - /** - * The support type. - * - * @return the type - */ - public SupportType getType() { - return type; - } - - /** - * The support type. - * - * @param type - * the new type - */ - protected void setType(SupportType type) { - this.type = type; - } - - /** - * Open an input link that will be used for the support. - *

- * Can return NULL, in which case you are supposed to work without a source - * node. - * - * @param source - * the source {@link URL} - * - * @return the {@link InputStream} - * - * @throws IOException - * in case of I/O error - */ - protected Document loadDocument(URL source) throws IOException { - String url = getCanonicalUrl(source).toString(); - return DataUtil.load(Instance.getInstance().getCache().open(source, this, false), "UTF-8", url.toString()); - } - - /** - * Log into the support (can be a no-op depending upon the support). - * - * @throws IOException - * in case of I/O error - */ - protected void login() throws IOException { - } - - /** - * Now that we have processed the {@link Story}, close the resources if any. - */ - protected void close() { - setCurrentReferer(null); - } - - /** - * Process the given story resource into a partially filled {@link Story} - * object containing the name and metadata. - * - * @param getDesc - * retrieve the description of the story, or not - * @param pg - * the optional progress reporter - * - * @return the {@link Story}, never NULL - * - * @throws IOException - * in case of I/O error - */ - protected Story processMeta(boolean getDesc, Progress pg) - throws IOException { - if (pg == null) { - pg = new Progress(); - } else { - pg.setMinMax(0, 100); - } - - pg.setProgress(30); - - Story story = new Story(); - MetaData meta = getMeta(); - if (meta.getCreationDate() == null || meta.getCreationDate().isEmpty()) { - meta.setCreationDate(StringUtils.fromTime(new Date().getTime())); - } - story.setMeta(meta); - - pg.setProgress(50); - - if (meta.getCover() == null) { - meta.setCover(bsHelper.getDefaultCover(meta.getSubject())); - } - - pg.setProgress(60); - - if (getDesc) { - String descChapterName = Instance.getInstance().getTrans().getString(StringId.DESCRIPTION); - story.getMeta().setResume(bsPara.makeChapter(this, source, 0, descChapterName, // - getDesc(), isHtml(), null)); - } - - pg.done(); - return story; - } - - /** - * Process the given story resource into a fully filled {@link Story} - * object. - * - * @param pg - * the optional progress reporter - * - * @return the {@link Story}, never NULL - * - * @throws IOException - * in case of I/O error - */ - // TODO: ADD final when BasicSupport_Deprecated is gone - public Story process(Progress pg) throws IOException { - setCurrentReferer(source); - login(); - sourceNode = loadDocument(source); - - try { - return doProcess(pg); - } finally { - close(); - } - } - - /** - * Actual processing step, without the calls to other methods. - *

- * Will convert the story resource into a fully filled {@link Story} object. - * - * @param pg - * the optional progress reporter - * - * @return the {@link Story}, never NULL - * - * @throws IOException - * in case of I/O error - */ - protected Story doProcess(Progress pg) throws IOException { - if (pg == null) { - pg = new Progress(); - } else { - pg.setMinMax(0, 100); - } - - pg.setName("Initialising"); - - pg.setProgress(1); - Progress pgMeta = new Progress(); - pg.addProgress(pgMeta, 10); - Story story = processMeta(true, pgMeta); - pgMeta.done(); // 10% - - pg.setName(story.getMeta().getTitle()); - - Progress pgGetChapters = new Progress(); - pg.addProgress(pgGetChapters, 10); - story.setChapters(new ArrayList()); - List> chapters = getChapters(pgGetChapters); - pgGetChapters.done(); // 20% - - if (chapters != null) { - Progress pgChaps = new Progress("Extracting chapters", 0, - chapters.size() * 300); - pg.addProgress(pgChaps, 80); - - long words = 0; - int i = 1; - for (Entry chap : chapters) { - pgChaps.setName("Extracting chapter " + i); - URL chapUrl = chap.getValue(); - String chapName = chap.getKey(); - if (chapUrl != null) { - setCurrentReferer(chapUrl); - } - - pgChaps.setProgress(i * 100); - Progress pgGetChapterContent = new Progress(); - Progress pgMakeChapter = new Progress(); - pgChaps.addProgress(pgGetChapterContent, 100); - pgChaps.addProgress(pgMakeChapter, 100); - - String content = getChapterContent(chapUrl, i, - pgGetChapterContent); - pgGetChapterContent.done(); - Chapter cc = bsPara.makeChapter(this, chapUrl, i, - chapName, content, isHtml(), pgMakeChapter); - pgMakeChapter.done(); - - words += cc.getWords(); - story.getChapters().add(cc); - story.getMeta().setWords(words); - - i++; - } - - pgChaps.setName("Extracting chapters"); - pgChaps.done(); - } - - pg.setName(story.getMeta().getTitle()); - pg.done(); - - return story; - } - - /** - * Create a chapter from the given data. - * - * @param source - * the source URL for this content, which can be used to try and - * find images if images are present in the format [image-url] - * @param number - * the chapter number (0 = description) - * @param name - * the chapter name - * @param content - * the content of the chapter - * @return the {@link Chapter} - * - * @throws IOException - * in case of I/O error - */ - public Chapter makeChapter(URL source, int number, String name, - String content) throws IOException { - return bsPara.makeChapter(this, source, number, name, - content, isHtml(), null); - } - - /** - * Return a {@link BasicSupport} implementation supporting the given - * resource if possible. - * - * @param url - * the story resource - * - * @return an implementation that supports it, or NULL - */ - public static BasicSupport getSupport(URL url) { - if (url == null) { - return null; - } - - // TEXT and INFO_TEXT always support files (not URLs though) - for (SupportType type : SupportType.values()) { - if (type != SupportType.TEXT && type != SupportType.INFO_TEXT) { - BasicSupport support = getSupport(type, url); - if (support != null && support.supports(url)) { - return support; - } - } - } - - for (SupportType type : new SupportType[] { SupportType.INFO_TEXT, - SupportType.TEXT }) { - BasicSupport support = getSupport(type, url); - if (support != null && support.supports(url)) { - return support; - } - } - - return null; - } - - /** - * Return a {@link BasicSupport} implementation supporting the given type. - * - * @param type - * the type, must not be NULL - * @param url - * the {@link URL} to support (can be NULL to get an - * "abstract support"; if not NULL, will be used as the source - * URL) - * - * @return an implementation that supports it, or NULL - */ - public static BasicSupport getSupport(SupportType type, URL url) { - BasicSupport support = null; - - switch (type) { - case EPUB: - support = new Epub(); - break; - case INFO_TEXT: - support = new InfoText(); - break; - case FIMFICTION: - try { - // Can fail if no client key or NO in options - support = new FimfictionApi(); - } catch (IOException e) { - support = new Fimfiction(); - } - break; - case FANFICTION: - support = new Fanfiction(); - break; - case TEXT: - support = new Text(); - break; - case MANGAHUB: - support = new MangaHub(); - break; - case E621: - support = new E621(); - break; - case YIFFSTAR: - support = new YiffStar(); - break; - case E_HENTAI: - support = new EHentai(); - break; - case MANGA_LEL: - support = new MangaLel(); - break; - case CBZ: - support = new Cbz(); - break; - case HTML: - support = new Html(); - break; - } - - if (support != null) { - support.setType(type); - support.source = support.getCanonicalUrl(url); - } - - return support; - } -} diff --git a/src/be/nikiroo/fanfix/supported/BasicSupportHelper.java b/src/be/nikiroo/fanfix/supported/BasicSupportHelper.java deleted file mode 100644 index b5c7bb9c..00000000 --- a/src/be/nikiroo/fanfix/supported/BasicSupportHelper.java +++ /dev/null @@ -1,223 +0,0 @@ -package be.nikiroo.fanfix.supported; - -import java.io.File; -import java.io.IOException; -import java.io.InputStream; -import java.net.MalformedURLException; -import java.net.URL; - -import be.nikiroo.fanfix.Instance; -import be.nikiroo.fanfix.bundles.Config; -import be.nikiroo.utils.Image; - -/** - * Helper class for {@link BasicSupport}, mostly dedicated to text formating for - * the classes that implement {@link BasicSupport}. - * - * @author niki - */ -public class BasicSupportHelper { - /** - * Get the default cover related to this subject (see .info files). - * - * @param subject - * the subject - * - * @return the cover if any, or NULL - */ - public Image getDefaultCover(String subject) { - if (subject != null && !subject.isEmpty() && Instance.getInstance().getCoverDir() != null) { - try { - File fileCover = new File(Instance.getInstance().getCoverDir(), subject); - return getImage(null, fileCover.toURI().toURL(), subject); - } catch (MalformedURLException e) { - } - } - - return null; - } - - /** - * Return the list of supported image extensions. - * - * @param emptyAllowed - * TRUE to allow an empty extension on first place, which can be - * used when you may already have an extension in your input but - * are not sure about it - * - * @return the extensions - */ - public String[] getImageExt(boolean emptyAllowed) { - if (emptyAllowed) { - return new String[] { "", ".png", ".jpg", ".jpeg", ".gif", ".bmp" }; - } - - return new String[] { ".png", ".jpg", ".jpeg", ".gif", ".bmp" }; - } - - /** - * Check if the given resource can be a local image or a remote image, then - * refresh the cache with it if it is. - * - * @param support - * the linked {@link BasicSupport} (can be NULL) - * @param source - * the source of the story (for image lookup in the same path if - * the source is a file, can be NULL) - * @param line - * the resource to check - * - * @return the image if found, or NULL - * - */ - public Image getImage(BasicSupport support, URL source, String line) { - URL url = getImageUrl(support, source, line); - if (url != null) { - if ("file".equals(url.getProtocol())) { - if (new File(url.getPath()).isDirectory()) { - return null; - } - } - InputStream in = null; - try { - in = Instance.getInstance().getCache().open(url, support, true); - return new Image(in); - } catch (IOException e) { - } finally { - if (in != null) { - try { - in.close(); - } catch (IOException e) { - } - } - } - } - - return null; - } - - /** - * Check if the given resource can be a local image or a remote image, then - * refresh the cache with it if it is. - * - * @param support - * the linked {@link BasicSupport} (can be NULL) - * @param source - * the source of the story (for image lookup in the same path if - * the source is a file, can be NULL) - * @param line - * the resource to check - * - * @return the image URL if found, or NULL - * - */ - public URL getImageUrl(BasicSupport support, URL source, String line) { - URL url = null; - - if (line != null) { - // try for files - if (source != null) { - try { - - String relPath = null; - String absPath = null; - try { - String path = new File(source.getFile()).getParent(); - relPath = new File(new File(path), line.trim()) - .getAbsolutePath(); - } catch (Exception e) { - // Cannot be converted to path (one possibility to take - // into account: absolute path on Windows) - } - try { - absPath = new File(line.trim()).getAbsolutePath(); - } catch (Exception e) { - // Cannot be converted to path (at all) - } - - for (String ext : getImageExt(true)) { - File absFile = new File(absPath + ext); - File relFile = new File(relPath + ext); - if (absPath != null && absFile.exists() - && absFile.isFile()) { - url = absFile.toURI().toURL(); - } else if (relPath != null && relFile.exists() - && relFile.isFile()) { - url = relFile.toURI().toURL(); - } - } - } catch (Exception e) { - // Should not happen since we control the correct arguments - } - } - - if (url == null) { - // try for URLs - try { - for (String ext : getImageExt(true)) { - if (Instance.getInstance().getCache().check(new URL(line + ext), true)) { - url = new URL(line + ext); - break; - } - } - - // try out of cache - if (url == null) { - for (String ext : getImageExt(true)) { - try { - url = new URL(line + ext); - Instance.getInstance().getCache().refresh(url, support, true); - break; - } catch (IOException e) { - // no image with this ext - url = null; - } - } - } - } catch (MalformedURLException e) { - // Not an url - } - } - - // refresh the cached file - if (url != null) { - try { - Instance.getInstance().getCache().refresh(url, support, true); - } catch (IOException e) { - // woops, broken image - url = null; - } - } - } - - return url; - } - - /** - * Fix the author name if it is prefixed with some "by" {@link String}. - * - * @param author - * the author with a possible prefix - * - * @return the author without prefixes - */ - public String fixAuthor(String author) { - if (author != null) { - for (String suffix : new String[] { " ", ":" }) { - for (String byString : Instance.getInstance().getConfig().getList(Config.CONF_BYS)) { - byString += suffix; - if (author.toUpperCase().startsWith(byString.toUpperCase())) { - author = author.substring(byString.length()).trim(); - } - } - } - - // Special case (without suffix): - if (author.startsWith("©")) { - author = author.substring(1); - } - } - - return author; - } -} diff --git a/src/be/nikiroo/fanfix/supported/BasicSupportImages.java b/src/be/nikiroo/fanfix/supported/BasicSupportImages.java deleted file mode 100644 index 576cb17e..00000000 --- a/src/be/nikiroo/fanfix/supported/BasicSupportImages.java +++ /dev/null @@ -1,185 +0,0 @@ -package be.nikiroo.fanfix.supported; - -import java.io.File; -import java.io.IOException; -import java.io.InputStream; -import java.net.MalformedURLException; -import java.net.URL; - -import be.nikiroo.fanfix.Instance; -import be.nikiroo.utils.Image; - -/** - * Helper class for {@link BasicSupport}, mostly dedicated to images for - * the classes that implement {@link BasicSupport}. - * - * @author niki - */ -public class BasicSupportImages { - /** - * Check if the given resource can be a local image or a remote image, then - * refresh the cache with it if it is. - * - * @param support - * the support to use to download the resource (can be NULL) - * @param dir - * the local directory to search, if any - * @param line - * the resource to check - * - * @return the image if found, or NULL - */ - public Image getImage(BasicSupport support, File dir, String line) { - URL url = getImageUrl(support, dir, line); - return getImage(support,url); - } - - /** - * Check if the given resource can be a local image or a remote image, then - * refresh the cache with it if it is. - * - * @param support - * the support to use to download the resource (can be NULL) - * @param url - * the actual URL to check (file or remote, can be NULL) - * - * @return the image if found, or NULL - */ - public Image getImage(BasicSupport support, URL url) { - if (url != null) { - if ("file".equals(url.getProtocol())) { - if (new File(url.getPath()).isDirectory()) { - return null; - } - } - InputStream in = null; - try { - in = Instance.getInstance().getCache().open(url, support, true); - return new Image(in); - } catch (IOException e) { - } finally { - if (in != null) { - try { - in.close(); - } catch (IOException e) { - } - } - } - } - - return null; - } - - /** - * Check if the given resource can be a local image or a remote image, then - * refresh the cache with it if it is. - * - * @param support - * the support to use to download the resource (can be NULL) - * @param dir - * the local directory to search, if any - * @param line - * the resource to check - * - * @return the image URL if found, or NULL - * - */ - public URL getImageUrl(BasicSupport support, File dir, String line) { - URL url = null; - - if (line != null) { - // try for files - if (dir != null && dir.exists() && !dir.isFile()) { - try { - - String relPath = null; - String absPath = null; - try { - relPath = new File(dir, line.trim()).getAbsolutePath(); - } catch (Exception e) { - // Cannot be converted to path (one possibility to take - // into account: absolute path on Windows) - } - try { - absPath = new File(line.trim()).getAbsolutePath(); - } catch (Exception e) { - // Cannot be converted to path (at all) - } - - for (String ext : getImageExt(true)) { - File absFile = new File(absPath + ext); - File relFile = new File(relPath + ext); - if (absPath != null && absFile.exists() - && absFile.isFile()) { - url = absFile.toURI().toURL(); - } else if (relPath != null && relFile.exists() - && relFile.isFile()) { - url = relFile.toURI().toURL(); - } - } - } catch (Exception e) { - // Should not happen since we control the correct arguments - } - } - - if (url == null) { - // try for URLs - try { - for (String ext : getImageExt(true)) { - if (Instance.getInstance().getCache() - .check(new URL(line + ext), true)) { - url = new URL(line + ext); - break; - } - } - - // try out of cache - if (url == null) { - for (String ext : getImageExt(true)) { - try { - url = new URL(line + ext); - Instance.getInstance().getCache().refresh(url, support, true); - break; - } catch (IOException e) { - // no image with this ext - url = null; - } - } - } - } catch (MalformedURLException e) { - // Not an url - } - } - - // refresh the cached file - if (url != null) { - try { - Instance.getInstance().getCache().refresh(url, support, true); - } catch (IOException e) { - // woops, broken image - url = null; - } - } - } - - return url; - } - - /** - * Return the list of supported image extensions. - * - * @param emptyAllowed - * TRUE to allow an empty extension on first place, which can be - * used when you may already have an extension in your input but - * are not sure about it - * - * @return the extensions - */ - public String[] getImageExt(boolean emptyAllowed) { - if (emptyAllowed) { - return new String[] { "", ".png", ".jpg", ".jpeg", ".gif", ".bmp" }; - } - - return new String[] { ".png", ".jpg", ".jpeg", ".gif", ".bmp" }; - } -} diff --git a/src/be/nikiroo/fanfix/supported/BasicSupportPara.java b/src/be/nikiroo/fanfix/supported/BasicSupportPara.java deleted file mode 100644 index 58c363af..00000000 --- a/src/be/nikiroo/fanfix/supported/BasicSupportPara.java +++ /dev/null @@ -1,579 +0,0 @@ -package be.nikiroo.fanfix.supported; - -import java.io.BufferedReader; -import java.io.ByteArrayInputStream; -import java.io.IOException; -import java.io.InputStreamReader; -import java.net.URL; -import java.util.ArrayList; -import java.util.List; - -import be.nikiroo.fanfix.Instance; -import be.nikiroo.fanfix.bundles.Config; -import be.nikiroo.fanfix.bundles.StringId; -import be.nikiroo.fanfix.data.Chapter; -import be.nikiroo.fanfix.data.Paragraph; -import be.nikiroo.fanfix.data.Paragraph.ParagraphType; -import be.nikiroo.utils.Image; -import be.nikiroo.utils.Progress; -import be.nikiroo.utils.StringUtils; - -/** - * Helper class for {@link BasicSupport}, mostly dedicated to {@link Paragraph} - * and text formating for the {@link BasicSupport} class. - * - * @author niki - */ -public class BasicSupportPara { - // quote chars - private static char openQuote = Instance.getInstance().getTrans().getCharacter(StringId.OPEN_SINGLE_QUOTE); - private static char closeQuote = Instance.getInstance().getTrans().getCharacter(StringId.CLOSE_SINGLE_QUOTE); - private static char openDoubleQuote = Instance.getInstance().getTrans().getCharacter(StringId.OPEN_DOUBLE_QUOTE); - private static char closeDoubleQuote = Instance.getInstance().getTrans().getCharacter(StringId.CLOSE_DOUBLE_QUOTE); - - // used by this class: - BasicSupportHelper bsHelper; - BasicSupportImages bsImages; - - public BasicSupportPara(BasicSupportHelper bsHelper, BasicSupportImages bsImages) { - this.bsHelper = bsHelper; - this.bsImages = bsImages; - } - - /** - * Create a {@link Chapter} object from the given information, formatting - * the content as it should be. - * - * @param support - * the linked {@link BasicSupport} - * @param source - * the source of the story (for image lookup in the same path if - * the source is a file, can be NULL) - * @param number - * the chapter number - * @param name - * the chapter name - * @param content - * the chapter content - * @param pg - * the optional progress reporter - * @param html - * TRUE if the input content is in HTML mode - * - * @return the {@link Chapter} - * - * @throws IOException - * in case of I/O error - */ - public Chapter makeChapter(BasicSupport support, URL source, - int number, String name, String content, boolean html, Progress pg) - throws IOException { - // Chapter name: process it correctly, then remove the possible - // redundant "Chapter x: " in front of it, or "-" (as in - // "Chapter 5: - Fun!" after the ": " was automatically added) - String chapterName = processPara(name, false) - .getContent().trim(); - for (String lang : Instance.getInstance().getConfig().getList(Config.CONF_CHAPTER)) { - String chapterWord = Instance.getInstance().getConfig().getStringX(Config.CONF_CHAPTER, lang); - if (chapterName.startsWith(chapterWord)) { - chapterName = chapterName.substring(chapterWord.length()) - .trim(); - break; - } - } - - if (chapterName.startsWith(Integer.toString(number))) { - chapterName = chapterName.substring( - Integer.toString(number).length()).trim(); - } - - while (chapterName.startsWith(":") || chapterName.startsWith("-")) { - chapterName = chapterName.substring(1).trim(); - } - // - - Chapter chap = new Chapter(number, chapterName); - - if (content != null) { - List paras = makeParagraphs(support, source, content, - html, pg); - long words = 0; - for (Paragraph para : paras) { - words += para.getWords(); - } - chap.setParagraphs(paras); - chap.setWords(words); - } - - return chap; - } - - /** - * Check quotes for bad format (i.e., quotes with normal paragraphs inside) - * and requotify them (i.e., separate them into QUOTE paragraphs and other - * paragraphs (quotes or not)). - * - * @param para - * the paragraph to requotify (not necessarily a quote) - * @param html - * TRUE if the input content is in HTML mode - * - * @return the correctly (or so we hope) quotified paragraphs - */ - protected List requotify(Paragraph para, boolean html) { - List newParas = new ArrayList(); - - if (para.getType() == ParagraphType.QUOTE - && para.getContent().length() > 2) { - String line = para.getContent(); - boolean singleQ = line.startsWith("" + openQuote); - boolean doubleQ = line.startsWith("" + openDoubleQuote); - - // Do not try when more than one quote at a time - // (some stories are not easily readable if we do) - if (singleQ - && line.indexOf(closeQuote, 1) < line - .lastIndexOf(closeQuote)) { - newParas.add(para); - return newParas; - } - if (doubleQ - && line.indexOf(closeDoubleQuote, 1) < line - .lastIndexOf(closeDoubleQuote)) { - newParas.add(para); - return newParas; - } - // - - if (!singleQ && !doubleQ) { - line = openDoubleQuote + line + closeDoubleQuote; - newParas.add(new Paragraph(ParagraphType.QUOTE, line, para - .getWords())); - } else { - char open = singleQ ? openQuote : openDoubleQuote; - char close = singleQ ? closeQuote : closeDoubleQuote; - - int posDot = -1; - boolean inQuote = false; - int i = 0; - for (char car : line.toCharArray()) { - if (car == open) { - inQuote = true; - } else if (car == close) { - inQuote = false; - } else if (car == '.' && !inQuote) { - posDot = i; - break; - } - i++; - } - - if (posDot >= 0) { - String rest = line.substring(posDot + 1).trim(); - line = line.substring(0, posDot + 1).trim(); - long words = 1; - for (char car : line.toCharArray()) { - if (car == ' ') { - words++; - } - } - newParas.add(new Paragraph(ParagraphType.QUOTE, line, words)); - if (!rest.isEmpty()) { - newParas.addAll(requotify(processPara(rest, html), html)); - } - } else { - newParas.add(para); - } - } - } else { - newParas.add(para); - } - - return newParas; - } - - /** - * Process a {@link Paragraph} from a raw line of text. - *

- * Will also fix quotes and HTML encoding if needed. - * - * @param line - * the raw line - * @param html - * TRUE if the input content is in HTML mode - * - * @return the processed {@link Paragraph} - */ - protected Paragraph processPara(String line, boolean html) { - if (html) { - line = StringUtils.unhtml(line).trim(); - } - boolean space = true; - boolean brk = true; - boolean quote = false; - boolean tentativeCloseQuote = false; - char prev = '\0'; - int dashCount = 0; - long words = 1; - - StringBuilder builder = new StringBuilder(); - for (char car : line.toCharArray()) { - if (car != '-') { - if (dashCount > 0) { - // dash, ndash and mdash: - – — - // currently: always use mdash - builder.append(dashCount == 1 ? '-' : '—'); - } - dashCount = 0; - } - - if (tentativeCloseQuote) { - tentativeCloseQuote = false; - if (Character.isLetterOrDigit(car)) { - builder.append("'"); - } else { - // handle double-single quotes as double quotes - if (prev == car) { - builder.append(closeDoubleQuote); - continue; - } - - builder.append(closeQuote); - } - } - - switch (car) { - case ' ': // note: unbreakable space - case ' ': - case '\t': - case '\n': // just in case - case '\r': // just in case - if (builder.length() > 0 - && builder.charAt(builder.length() - 1) != ' ') { - words++; - } - builder.append(' '); - break; - - case '\'': - if (space || (brk && quote)) { - quote = true; - // handle double-single quotes as double quotes - if (prev == car) { - builder.deleteCharAt(builder.length() - 1); - builder.append(openDoubleQuote); - } else { - builder.append(openQuote); - } - } else if (prev == ' ' || prev == car) { - // handle double-single quotes as double quotes - if (prev == car) { - builder.deleteCharAt(builder.length() - 1); - builder.append(openDoubleQuote); - } else { - builder.append(openQuote); - } - } else { - // it is a quote ("I'm off") or a 'quote' ("This - // 'good' restaurant"...) - tentativeCloseQuote = true; - } - break; - - case '"': - if (space || (brk && quote)) { - quote = true; - builder.append(openDoubleQuote); - } else if (prev == ' ') { - builder.append(openDoubleQuote); - } else { - builder.append(closeDoubleQuote); - } - break; - - case '-': - if (space) { - quote = true; - } else { - dashCount++; - } - space = false; - break; - - case '*': - case '~': - case '/': - case '\\': - case '<': - case '>': - case '=': - case '+': - case '_': - case '–': - case '—': - space = false; - builder.append(car); - break; - - case '‘': - case '`': - case '‹': - case '﹁': - case '〈': - case '「': - if (space || (brk && quote)) { - quote = true; - builder.append(openQuote); - } else { - // handle double-single quotes as double quotes - if (prev == car) { - builder.deleteCharAt(builder.length() - 1); - builder.append(openDoubleQuote); - } else { - builder.append(openQuote); - } - } - space = false; - brk = false; - break; - - case '’': - case '›': - case '﹂': - case '〉': - case '」': - space = false; - brk = false; - // handle double-single quotes as double quotes - if (prev == car) { - builder.deleteCharAt(builder.length() - 1); - builder.append(closeDoubleQuote); - } else { - builder.append(closeQuote); - } - break; - - case '«': - case '“': - case '﹃': - case '《': - case '『': - if (space || (brk && quote)) { - quote = true; - builder.append(openDoubleQuote); - } else { - builder.append(openDoubleQuote); - } - space = false; - brk = false; - break; - - case '»': - case '”': - case '﹄': - case '》': - case '』': - space = false; - brk = false; - builder.append(closeDoubleQuote); - break; - - default: - space = false; - brk = false; - builder.append(car); - break; - } - - prev = car; - } - - if (tentativeCloseQuote) { - tentativeCloseQuote = false; - builder.append(closeQuote); - } - - line = builder.toString().trim(); - - ParagraphType type = ParagraphType.NORMAL; - if (space) { - type = ParagraphType.BLANK; - } else if (brk) { - type = ParagraphType.BREAK; - } else if (quote) { - type = ParagraphType.QUOTE; - } - - return new Paragraph(type, line, words); - } - - /** - * Convert the given content into {@link Paragraph}s. - * - * @param support - * the linked {@link BasicSupport} (can be NULL), used to - * download optional image content in [] - * @param source - * the source URL of the story (for image lookup in the same path - * if the source is a file, can be NULL) - * @param content - * the textual content - * @param html - * TRUE if the input content is in HTML mode - * @param pg - * the optional progress reporter - * - * @return the {@link Paragraph}s - * - * @throws IOException - * in case of I/O error - */ - protected List makeParagraphs(BasicSupport support, - URL source, String content, boolean html, Progress pg) - throws IOException { - if (pg == null) { - pg = new Progress(); - } - - if (html) { - // Special


processing: - content = content.replaceAll("(
]*>)|(
)|(
)", - "
* * *
"); - } - - List paras = new ArrayList(); - - if (content != null && !content.trim().isEmpty()) { - if (html) { - String[] tab = content.split("(

|

|
|
)"); - pg.setMinMax(0, tab.length); - int i = 1; - for (String line : tab) { - if (line.startsWith("[") && line.endsWith("]")) { - pg.setName("Extracting image " + i); - } - paras.add(makeParagraph(support, source, line.trim(), html)); - pg.setProgress(i++); - } - } else { - List lines = new ArrayList(); - BufferedReader buff = null; - try { - buff = new BufferedReader( - new InputStreamReader(new ByteArrayInputStream( - content.getBytes("UTF-8")), "UTF-8")); - for (String line = buff.readLine(); line != null; line = buff - .readLine()) { - lines.add(line.trim()); - } - } finally { - if (buff != null) { - buff.close(); - } - } - - pg.setMinMax(0, lines.size()); - int i = 0; - for (String line : lines) { - if (line.startsWith("[") && line.endsWith("]")) { - pg.setName("Extracting image " + i); - } - paras.add(makeParagraph(support, source, line, html)); - pg.setProgress(i++); - } - } - - pg.done(); - pg.setName(null); - - // Check quotes for "bad" format - List newParas = new ArrayList(); - for (Paragraph para : paras) { - newParas.addAll(requotify(para, html)); - } - paras = newParas; - - // Remove double blanks/brks - fixBlanksBreaks(paras); - } - - return paras; - } - - /** - * Convert the given line into a single {@link Paragraph}. - * - * @param support - * the linked {@link BasicSupport} (can be NULL), used to - * download optional image content in [] - * @param source - * the source URL of the story (for image lookup in the same path - * if the source is a file, can be NULL) - * @param line - * the textual content of the paragraph - * @param html - * TRUE if the input content is in HTML mode - * - * @return the {@link Paragraph} - */ - protected Paragraph makeParagraph(BasicSupport support, URL source, - String line, boolean html) { - Image image = null; - if (line.startsWith("[") && line.endsWith("]")) { - image = bsHelper.getImage(support, source, line - .substring(1, line.length() - 1).trim()); - } - - if (image != null) { - return new Paragraph(image); - } - - return processPara(line, html); - } - - /** - * Fix the {@link ParagraphType#BLANK}s and {@link ParagraphType#BREAK}s of - * those {@link Paragraph}s. - *

- * The resulting list will not contain a starting or trailing blank/break - * nor 2 blanks or breaks following each other. - * - * @param paras - * the list of {@link Paragraph}s to fix - */ - protected void fixBlanksBreaks(List paras) { - boolean space = false; - boolean brk = true; - for (int i = 0; i < paras.size(); i++) { - Paragraph para = paras.get(i); - boolean thisSpace = para.getType() == ParagraphType.BLANK; - boolean thisBrk = para.getType() == ParagraphType.BREAK; - - if (i > 0 && space && thisBrk) { - paras.remove(i - 1); - i--; - } else if ((space || brk) && (thisSpace || thisBrk)) { - paras.remove(i); - i--; - } - - space = thisSpace; - brk = thisBrk; - } - - // Remove blank/brk at start - if (paras.size() > 0 - && (paras.get(0).getType() == ParagraphType.BLANK || paras.get( - 0).getType() == ParagraphType.BREAK)) { - paras.remove(0); - } - - // Remove blank/brk at end - int last = paras.size() - 1; - if (paras.size() > 0 - && (paras.get(last).getType() == ParagraphType.BLANK || paras - .get(last).getType() == ParagraphType.BREAK)) { - paras.remove(last); - } - } -} diff --git a/src/be/nikiroo/fanfix/supported/BasicSupport_Deprecated.java b/src/be/nikiroo/fanfix/supported/BasicSupport_Deprecated.java deleted file mode 100644 index 4a7b65b9..00000000 --- a/src/be/nikiroo/fanfix/supported/BasicSupport_Deprecated.java +++ /dev/null @@ -1,1311 +0,0 @@ -package be.nikiroo.fanfix.supported; - -import java.io.BufferedReader; -import java.io.ByteArrayInputStream; -import java.io.File; -import java.io.IOException; -import java.io.InputStream; -import java.io.InputStreamReader; -import java.net.MalformedURLException; -import java.net.URL; -import java.util.ArrayList; -import java.util.Date; -import java.util.List; -import java.util.Map.Entry; -import java.util.Scanner; - -import be.nikiroo.fanfix.Instance; -import be.nikiroo.fanfix.bundles.Config; -import be.nikiroo.fanfix.bundles.StringId; -import be.nikiroo.fanfix.data.Chapter; -import be.nikiroo.fanfix.data.MetaData; -import be.nikiroo.fanfix.data.Paragraph; -import be.nikiroo.fanfix.data.Paragraph.ParagraphType; -import be.nikiroo.fanfix.data.Story; -import be.nikiroo.utils.Image; -import be.nikiroo.utils.Progress; -import be.nikiroo.utils.StringUtils; - -/** - * DEPRECATED: use the new Jsoup 'Node' system. - *

- * This class is the base class used by the other support classes. It can be - * used outside of this package, and have static method that you can use to get - * access to the correct support class. - *

- * It will be used with 'resources' (usually web pages or files). - * - * @author niki - */ -@Deprecated -public abstract class BasicSupport_Deprecated extends BasicSupport { - private InputStream in; - - // quote chars - private char openQuote = Instance.getInstance().getTrans().getCharacter(StringId.OPEN_SINGLE_QUOTE); - private char closeQuote = Instance.getInstance().getTrans().getCharacter(StringId.CLOSE_SINGLE_QUOTE); - private char openDoubleQuote = Instance.getInstance().getTrans().getCharacter(StringId.OPEN_DOUBLE_QUOTE); - private char closeDoubleQuote = Instance.getInstance().getTrans().getCharacter(StringId.CLOSE_DOUBLE_QUOTE); - - // New methods not used in Deprecated mode - @Override - protected String getDesc() throws IOException { - throw new RuntimeException("should not be used by legacy code"); - } - - @Override - protected MetaData getMeta() throws IOException { - throw new RuntimeException("should not be used by legacy code"); - } - - @Override - protected List> getChapters(Progress pg) - throws IOException { - throw new RuntimeException("should not be used by legacy code"); - } - - @Override - protected String getChapterContent(URL chapUrl, int number, Progress pg) - throws IOException { - throw new RuntimeException("should not be used by legacy code"); - } - - @Override - public Story process(Progress pg) throws IOException { - return process(getSource(), pg); - } - - // - - /** - * Return the {@link MetaData} of this story. - * - * @param source - * the source of the story - * @param in - * the input (the main resource) - * - * @return the associated {@link MetaData}, never NULL - * - * @throws IOException - * in case of I/O error - */ - protected abstract MetaData getMeta(URL source, InputStream in) - throws IOException; - - /** - * Return the story description. - * - * @param source - * the source of the story - * @param in - * the input (the main resource) - * - * @return the description - * - * @throws IOException - * in case of I/O error - */ - protected abstract String getDesc(URL source, InputStream in) - throws IOException; - - /** - * Return the list of chapters (name and resource). - * - * @param source - * the source of the story - * @param in - * the input (the main resource) - * @param pg - * the optional progress reporter - * - * @return the chapters - * - * @throws IOException - * in case of I/O error - */ - protected abstract List> getChapters(URL source, - InputStream in, Progress pg) throws IOException; - - /** - * Return the content of the chapter (possibly HTML encoded, if - * {@link BasicSupport_Deprecated#isHtml()} is TRUE). - * - * @param source - * the source of the story - * @param in - * the input (the main resource) - * @param number - * the chapter number - * @param pg - * the optional progress reporter - * - * @return the content - * - * @throws IOException - * in case of I/O error - */ - protected abstract String getChapterContent(URL source, InputStream in, - int number, Progress pg) throws IOException; - - /** - * Process the given story resource into a partially filled {@link Story} - * object containing the name and metadata, except for the description. - * - * @param url - * the story resource - * - * @return the {@link Story} - * - * @throws IOException - * in case of I/O error - */ - public Story processMeta(URL url) throws IOException { - return processMeta(url, true, false, null); - } - - /** - * Process the given story resource into a partially filled {@link Story} - * object containing the name and metadata. - * - * @param url - * the story resource - * @param close - * close "this" and "in" when done - * @param getDesc - * retrieve the description of the story, or not - * @param pg - * the optional progress reporter - * - * @return the {@link Story}, never NULL - * - * @throws IOException - * in case of I/O error - */ - protected Story processMeta(URL url, boolean close, boolean getDesc, - Progress pg) throws IOException { - if (pg == null) { - pg = new Progress(); - } else { - pg.setMinMax(0, 100); - } - - login(); - pg.setProgress(10); - - url = getCanonicalUrl(url); - - setCurrentReferer(url); - - in = openInput(url); // NULL allowed here - try { - preprocess(url, getInput()); - pg.setProgress(30); - - Story story = new Story(); - MetaData meta = getMeta(url, getInput()); - if (meta.getCreationDate() == null - || meta.getCreationDate().isEmpty()) { - meta.setCreationDate(StringUtils.fromTime(new Date().getTime())); - } - story.setMeta(meta); - - pg.setProgress(50); - - if (meta.getCover() == null) { - meta.setCover(getDefaultCover(meta.getSubject())); - } - - pg.setProgress(60); - - if (getDesc) { - String descChapterName = Instance.getInstance().getTrans().getString(StringId.DESCRIPTION); - story.getMeta().setResume(makeChapter(url, 0, descChapterName, getDesc(url, getInput()), null)); - } - - pg.setProgress(100); - return story; - } finally { - if (close) { - close(); - - if (in != null) { - in.close(); - } - } - } - } - - /** - * Process the given story resource into a fully filled {@link Story} - * object. - * - * @param url - * the story resource - * @param pg - * the optional progress reporter - * - * @return the {@link Story}, never NULL - * - * @throws IOException - * in case of I/O error - */ - protected Story process(URL url, Progress pg) throws IOException { - if (pg == null) { - pg = new Progress(); - } else { - pg.setMinMax(0, 100); - } - - url = getCanonicalUrl(url); - pg.setProgress(1); - try { - Progress pgMeta = new Progress(); - pg.addProgress(pgMeta, 10); - Story story = processMeta(url, false, true, pgMeta); - if (!pgMeta.isDone()) { - pgMeta.setProgress(pgMeta.getMax()); // 10% - } - - pg.setName("Retrieving " + story.getMeta().getTitle()); - - setCurrentReferer(url); - - Progress pgGetChapters = new Progress(); - pg.addProgress(pgGetChapters, 10); - story.setChapters(new ArrayList()); - List> chapters = getChapters(url, getInput(), - pgGetChapters); - if (!pgGetChapters.isDone()) { - pgGetChapters.setProgress(pgGetChapters.getMax()); // 20% - } - - if (chapters != null) { - Progress pgChaps = new Progress("Extracting chapters", 0, - chapters.size() * 300); - pg.addProgress(pgChaps, 80); - - long words = 0; - int i = 1; - for (Entry chap : chapters) { - pgChaps.setName("Extracting chapter " + i); - InputStream chapIn = null; - if (chap.getValue() != null) { - setCurrentReferer(chap.getValue()); - chapIn = Instance.getInstance().getCache().open(chap.getValue(), this, false); - } - pgChaps.setProgress(i * 100); - try { - Progress pgGetChapterContent = new Progress(); - Progress pgMakeChapter = new Progress(); - pgChaps.addProgress(pgGetChapterContent, 100); - pgChaps.addProgress(pgMakeChapter, 100); - - String content = getChapterContent(url, chapIn, i, - pgGetChapterContent); - if (!pgGetChapterContent.isDone()) { - pgGetChapterContent.setProgress(pgGetChapterContent - .getMax()); - } - - Chapter cc = makeChapter(url, i, chap.getKey(), - content, pgMakeChapter); - if (!pgMakeChapter.isDone()) { - pgMakeChapter.setProgress(pgMakeChapter.getMax()); - } - - words += cc.getWords(); - story.getChapters().add(cc); - story.getMeta().setWords(words); - } finally { - if (chapIn != null) { - chapIn.close(); - } - } - - i++; - } - - pgChaps.setName("Extracting chapters"); - } else { - pg.setProgress(80); - } - - return story; - - } finally { - close(); - - if (in != null) { - in.close(); - } - } - } - - /** - * Prepare the support if needed before processing. - * - * @param source - * the source of the story - * @param in - * the input (the main resource) - * - * @throws IOException - * on I/O error - */ - @SuppressWarnings("unused") - protected void preprocess(URL source, InputStream in) throws IOException { - } - - /** - * Create a {@link Chapter} object from the given information, formatting - * the content as it should be. - * - * @param source - * the source of the story - * @param number - * the chapter number - * @param name - * the chapter name - * @param content - * the chapter content - * @param pg - * the optional progress reporter - * - * @return the {@link Chapter} - * - * @throws IOException - * in case of I/O error - */ - protected Chapter makeChapter(URL source, int number, String name, - String content, Progress pg) throws IOException { - // Chapter name: process it correctly, then remove the possible - // redundant "Chapter x: " in front of it, or "-" (as in - // "Chapter 5: - Fun!" after the ": " was automatically added) - String chapterName = processPara(name).getContent().trim(); - for (String lang : Instance.getInstance().getConfig().getList(Config.CONF_CHAPTER)) { - String chapterWord = Instance.getInstance().getConfig().getStringX(Config.CONF_CHAPTER, lang); - if (chapterName.startsWith(chapterWord)) { - chapterName = chapterName.substring(chapterWord.length()) - .trim(); - break; - } - } - - if (chapterName.startsWith(Integer.toString(number))) { - chapterName = chapterName.substring( - Integer.toString(number).length()).trim(); - } - - while (chapterName.startsWith(":") || chapterName.startsWith("-")) { - chapterName = chapterName.substring(1).trim(); - } - // - - Chapter chap = new Chapter(number, chapterName); - - if (content != null) { - List paras = makeParagraphs(source, content, pg); - long words = 0; - for (Paragraph para : paras) { - words += para.getWords(); - } - chap.setParagraphs(paras); - chap.setWords(words); - } - - return chap; - - } - - /** - * Convert the given content into {@link Paragraph}s. - * - * @param source - * the source URL of the story - * @param content - * the textual content - * @param pg - * the optional progress reporter - * - * @return the {@link Paragraph}s - * - * @throws IOException - * in case of I/O error - */ - protected List makeParagraphs(URL source, String content, - Progress pg) throws IOException { - if (pg == null) { - pg = new Progress(); - } - - if (isHtml()) { - // Special


processing: - content = content.replaceAll("(
]*>)|(
)|(
)", - "
* * *
"); - } - - List paras = new ArrayList(); - - if (content != null && !content.trim().isEmpty()) { - if (isHtml()) { - String[] tab = content.split("(

|

|
|
)"); - pg.setMinMax(0, tab.length); - int i = 1; - for (String line : tab) { - if (line.startsWith("[") && line.endsWith("]")) { - pg.setName("Extracting image " + i); - } - paras.add(makeParagraph(source, line.trim())); - pg.setProgress(i++); - } - pg.setName(null); - } else { - List lines = new ArrayList(); - BufferedReader buff = null; - try { - buff = new BufferedReader( - new InputStreamReader(new ByteArrayInputStream( - content.getBytes("UTF-8")), "UTF-8")); - for (String line = buff.readLine(); line != null; line = buff - .readLine()) { - lines.add(line.trim()); - } - } finally { - if (buff != null) { - buff.close(); - } - } - - pg.setMinMax(0, lines.size()); - int i = 0; - for (String line : lines) { - if (line.startsWith("[") && line.endsWith("]")) { - pg.setName("Extracting image " + i); - } - paras.add(makeParagraph(source, line)); - pg.setProgress(i++); - } - pg.setName(null); - } - - // Check quotes for "bad" format - List newParas = new ArrayList(); - for (Paragraph para : paras) { - newParas.addAll(requotify(para)); - } - paras = newParas; - - // Remove double blanks/brks - fixBlanksBreaks(paras); - } - - return paras; - } - - /** - * Convert the given line into a single {@link Paragraph}. - * - * @param source - * the source URL of the story - * @param line - * the textual content of the paragraph - * - * @return the {@link Paragraph} - */ - private Paragraph makeParagraph(URL source, String line) { - Image image = null; - if (line.startsWith("[") && line.endsWith("]")) { - image = getImage(this, source, line.substring(1, line.length() - 1) - .trim()); - } - - if (image != null) { - return new Paragraph(image); - } - - return processPara(line); - } - - /** - * Fix the {@link ParagraphType#BLANK}s and {@link ParagraphType#BREAK}s of - * those {@link Paragraph}s. - *

- * The resulting list will not contain a starting or trailing blank/break - * nor 2 blanks or breaks following each other. - * - * @param paras - * the list of {@link Paragraph}s to fix - */ - protected void fixBlanksBreaks(List paras) { - boolean space = false; - boolean brk = true; - for (int i = 0; i < paras.size(); i++) { - Paragraph para = paras.get(i); - boolean thisSpace = para.getType() == ParagraphType.BLANK; - boolean thisBrk = para.getType() == ParagraphType.BREAK; - - if (i > 0 && space && thisBrk) { - paras.remove(i - 1); - i--; - } else if ((space || brk) && (thisSpace || thisBrk)) { - paras.remove(i); - i--; - } - - space = thisSpace; - brk = thisBrk; - } - - // Remove blank/brk at start - if (paras.size() > 0 - && (paras.get(0).getType() == ParagraphType.BLANK || paras.get( - 0).getType() == ParagraphType.BREAK)) { - paras.remove(0); - } - - // Remove blank/brk at end - int last = paras.size() - 1; - if (paras.size() > 0 - && (paras.get(last).getType() == ParagraphType.BLANK || paras - .get(last).getType() == ParagraphType.BREAK)) { - paras.remove(last); - } - } - - /** - * Get the default cover related to this subject (see .info files). - * - * @param subject - * the subject - * - * @return the cover if any, or NULL - */ - static Image getDefaultCover(String subject) { - if (subject != null && !subject.isEmpty() && Instance.getInstance().getCoverDir() != null) { - try { - File fileCover = new File(Instance.getInstance().getCoverDir(), subject); - return getImage(null, fileCover.toURI().toURL(), subject); - } catch (MalformedURLException e) { - } - } - - return null; - } - - /** - * Return the list of supported image extensions. - * - * @param emptyAllowed - * TRUE to allow an empty extension on first place, which can be - * used when you may already have an extension in your input but - * are not sure about it - * - * @return the extensions - */ - static String[] getImageExt(boolean emptyAllowed) { - if (emptyAllowed) { - return new String[] { "", ".png", ".jpg", ".jpeg", ".gif", ".bmp" }; - } - - return new String[] { ".png", ".jpg", ".jpeg", ".gif", ".bmp" }; - } - - /** - * Check if the given resource can be a local image or a remote image, then - * refresh the cache with it if it is. - * - * @param source - * the story source - * @param line - * the resource to check - * - * @return the image if found, or NULL - * - */ - static Image getImage(BasicSupport_Deprecated support, URL source, - String line) { - URL url = getImageUrl(support, source, line); - if (url != null) { - if ("file".equals(url.getProtocol())) { - if (new File(url.getPath()).isDirectory()) { - return null; - } - } - InputStream in = null; - try { - in = Instance.getInstance().getCache().open(url, getSupport(url), true); - return new Image(in); - } catch (IOException e) { - } finally { - if (in != null) { - try { - in.close(); - } catch (IOException e) { - } - } - } - } - - return null; - } - - /** - * Check if the given resource can be a local image or a remote image, then - * refresh the cache with it if it is. - * - * @param source - * the story source - * @param line - * the resource to check - * - * @return the image URL if found, or NULL - * - */ - static URL getImageUrl(BasicSupport_Deprecated support, URL source, - String line) { - URL url = null; - - if (line != null) { - // try for files - if (source != null) { - try { - String relPath = null; - String absPath = null; - try { - String path = new File(source.getFile()).getParent(); - relPath = new File(new File(path), line.trim()) - .getAbsolutePath(); - } catch (Exception e) { - // Cannot be converted to path (one possibility to take - // into account: absolute path on Windows) - } - try { - absPath = new File(line.trim()).getAbsolutePath(); - } catch (Exception e) { - // Cannot be converted to path (at all) - } - - for (String ext : getImageExt(true)) { - File absFile = new File(absPath + ext); - File relFile = new File(relPath + ext); - if (absPath != null && absFile.exists() - && absFile.isFile()) { - url = absFile.toURI().toURL(); - } else if (relPath != null && relFile.exists() - && relFile.isFile()) { - url = relFile.toURI().toURL(); - } - } - } catch (Exception e) { - // Should not happen since we control the correct arguments - } - } - - if (url == null) { - // try for URLs - try { - for (String ext : getImageExt(true)) { - if (Instance.getInstance().getCache().check(new URL(line + ext), true)) { - url = new URL(line + ext); - break; - } - } - - // try out of cache - if (url == null) { - for (String ext : getImageExt(true)) { - try { - url = new URL(line + ext); - Instance.getInstance().getCache().refresh(url, support, true); - break; - } catch (IOException e) { - // no image with this ext - url = null; - } - } - } - } catch (MalformedURLException e) { - // Not an url - } - } - - // refresh the cached file - if (url != null) { - try { - Instance.getInstance().getCache().refresh(url, support, true); - } catch (IOException e) { - // woops, broken image - url = null; - } - } - } - - return url; - } - - /** - * Open the input file that will be used through the support. - *

- * Can return NULL, in which case you are supposed to work without an - * {@link InputStream}. - * - * @param source - * the source {@link URL} - * - * @return the {@link InputStream} - * - * @throws IOException - * in case of I/O error - */ - protected InputStream openInput(URL source) throws IOException { - return Instance.getInstance().getCache().open(source, this, false); - } - - /** - * Reset then return {@link BasicSupport_Deprecated#in}. - * - * @return {@link BasicSupport_Deprecated#in} - */ - protected InputStream getInput() { - return reset(in); - } - - /** - * Check quotes for bad format (i.e., quotes with normal paragraphs inside) - * and requotify them (i.e., separate them into QUOTE paragraphs and other - * paragraphs (quotes or not)). - * - * @param para - * the paragraph to requotify (not necessarily a quote) - * - * @return the correctly (or so we hope) quotified paragraphs - */ - protected List requotify(Paragraph para) { - List newParas = new ArrayList(); - - if (para.getType() == ParagraphType.QUOTE - && para.getContent().length() > 2) { - String line = para.getContent(); - boolean singleQ = line.startsWith("" + openQuote); - boolean doubleQ = line.startsWith("" + openDoubleQuote); - - // Do not try when more than one quote at a time - // (some stories are not easily readable if we do) - if (singleQ - && line.indexOf(closeQuote, 1) < line - .lastIndexOf(closeQuote)) { - newParas.add(para); - return newParas; - } - if (doubleQ - && line.indexOf(closeDoubleQuote, 1) < line - .lastIndexOf(closeDoubleQuote)) { - newParas.add(para); - return newParas; - } - // - - if (!singleQ && !doubleQ) { - line = openDoubleQuote + line + closeDoubleQuote; - newParas.add(new Paragraph(ParagraphType.QUOTE, line, para - .getWords())); - } else { - char open = singleQ ? openQuote : openDoubleQuote; - char close = singleQ ? closeQuote : closeDoubleQuote; - - int posDot = -1; - boolean inQuote = false; - int i = 0; - for (char car : line.toCharArray()) { - if (car == open) { - inQuote = true; - } else if (car == close) { - inQuote = false; - } else if (car == '.' && !inQuote) { - posDot = i; - break; - } - i++; - } - - if (posDot >= 0) { - String rest = line.substring(posDot + 1).trim(); - line = line.substring(0, posDot + 1).trim(); - long words = 1; - for (char car : line.toCharArray()) { - if (car == ' ') { - words++; - } - } - newParas.add(new Paragraph(ParagraphType.QUOTE, line, words)); - if (!rest.isEmpty()) { - newParas.addAll(requotify(processPara(rest))); - } - } else { - newParas.add(para); - } - } - } else { - newParas.add(para); - } - - return newParas; - } - - /** - * Process a {@link Paragraph} from a raw line of text. - *

- * Will also fix quotes and HTML encoding if needed. - * - * @param line - * the raw line - * - * @return the processed {@link Paragraph} - */ - protected Paragraph processPara(String line) { - line = ifUnhtml(line).trim(); - - boolean space = true; - boolean brk = true; - boolean quote = false; - boolean tentativeCloseQuote = false; - char prev = '\0'; - int dashCount = 0; - long words = 1; - - StringBuilder builder = new StringBuilder(); - for (char car : line.toCharArray()) { - if (car != '-') { - if (dashCount > 0) { - // dash, ndash and mdash: - – — - // currently: always use mdash - builder.append(dashCount == 1 ? '-' : '—'); - } - dashCount = 0; - } - - if (tentativeCloseQuote) { - tentativeCloseQuote = false; - if (Character.isLetterOrDigit(car)) { - builder.append("'"); - } else { - // handle double-single quotes as double quotes - if (prev == car) { - builder.append(closeDoubleQuote); - continue; - } - - builder.append(closeQuote); - } - } - - switch (car) { - case ' ': // note: unbreakable space - case ' ': - case '\t': - case '\n': // just in case - case '\r': // just in case - if (builder.length() > 0 - && builder.charAt(builder.length() - 1) != ' ') { - words++; - } - builder.append(' '); - break; - - case '\'': - if (space || (brk && quote)) { - quote = true; - // handle double-single quotes as double quotes - if (prev == car) { - builder.deleteCharAt(builder.length() - 1); - builder.append(openDoubleQuote); - } else { - builder.append(openQuote); - } - } else if (prev == ' ' || prev == car) { - // handle double-single quotes as double quotes - if (prev == car) { - builder.deleteCharAt(builder.length() - 1); - builder.append(openDoubleQuote); - } else { - builder.append(openQuote); - } - } else { - // it is a quote ("I'm off") or a 'quote' ("This - // 'good' restaurant"...) - tentativeCloseQuote = true; - } - break; - - case '"': - if (space || (brk && quote)) { - quote = true; - builder.append(openDoubleQuote); - } else if (prev == ' ') { - builder.append(openDoubleQuote); - } else { - builder.append(closeDoubleQuote); - } - break; - - case '-': - if (space) { - quote = true; - } else { - dashCount++; - } - space = false; - break; - - case '*': - case '~': - case '/': - case '\\': - case '<': - case '>': - case '=': - case '+': - case '_': - case '–': - case '—': - space = false; - builder.append(car); - break; - - case '‘': - case '`': - case '‹': - case '﹁': - case '〈': - case '「': - if (space || (brk && quote)) { - quote = true; - builder.append(openQuote); - } else { - // handle double-single quotes as double quotes - if (prev == car) { - builder.deleteCharAt(builder.length() - 1); - builder.append(openDoubleQuote); - } else { - builder.append(openQuote); - } - } - space = false; - brk = false; - break; - - case '’': - case '›': - case '﹂': - case '〉': - case '」': - space = false; - brk = false; - // handle double-single quotes as double quotes - if (prev == car) { - builder.deleteCharAt(builder.length() - 1); - builder.append(closeDoubleQuote); - } else { - builder.append(closeQuote); - } - break; - - case '«': - case '“': - case '﹃': - case '《': - case '『': - if (space || (brk && quote)) { - quote = true; - builder.append(openDoubleQuote); - } else { - builder.append(openDoubleQuote); - } - space = false; - brk = false; - break; - - case '»': - case '”': - case '﹄': - case '》': - case '』': - space = false; - brk = false; - builder.append(closeDoubleQuote); - break; - - default: - space = false; - brk = false; - builder.append(car); - break; - } - - prev = car; - } - - if (tentativeCloseQuote) { - tentativeCloseQuote = false; - builder.append(closeQuote); - } - - line = builder.toString().trim(); - - ParagraphType type = ParagraphType.NORMAL; - if (space) { - type = ParagraphType.BLANK; - } else if (brk) { - type = ParagraphType.BREAK; - } else if (quote) { - type = ParagraphType.QUOTE; - } - - return new Paragraph(type, line, words); - } - - /** - * Remove the HTML from the input if - * {@link BasicSupport_Deprecated#isHtml()} is true. - * - * @param input - * the input - * - * @return the no html version if needed - */ - private String ifUnhtml(String input) { - if (isHtml() && input != null) { - return StringUtils.unhtml(input); - } - - return input; - } - - /** - * Reset the given {@link InputStream} and return it. - * - * @param in - * the {@link InputStream} to reset - * - * @return the same {@link InputStream} after reset - */ - static protected InputStream reset(InputStream in) { - try { - if (in != null) { - in.reset(); - } - } catch (IOException e) { - } - - return in; - } - - /** - * Return the first line from the given input which correspond to the given - * selectors. - * - * @param in - * the input - * @param needle - * a string that must be found inside the target line (also - * supports "^" at start to say "only if it starts with" the - * needle) - * @param relativeLine - * the line to return based upon the target line position (-1 = - * the line before, 0 = the target line...) - * - * @return the line, or NULL if not found - */ - static protected String getLine(InputStream in, String needle, - int relativeLine) { - return getLine(in, needle, relativeLine, true); - } - - /** - * Return a line from the given input which correspond to the given - * selectors. - * - * @param in - * the input - * @param needle - * a string that must be found inside the target line (also - * supports "^" at start to say "only if it starts with" the - * needle) - * @param relativeLine - * the line to return based upon the target line position (-1 = - * the line before, 0 = the target line...) - * @param first - * takes the first result (as opposed to the last one, which will - * also always spend the input) - * - * @return the line, or NULL if not found - */ - static protected String getLine(InputStream in, String needle, - int relativeLine, boolean first) { - String rep = null; - - reset(in); - - List lines = new ArrayList(); - @SuppressWarnings("resource") - Scanner scan = new Scanner(in, "UTF-8"); - int index = -1; - scan.useDelimiter("\\n"); - while (scan.hasNext()) { - lines.add(scan.next()); - - if (index == -1) { - if (needle.startsWith("^")) { - if (lines.get(lines.size() - 1).startsWith( - needle.substring(1))) { - index = lines.size() - 1; - } - - } else { - if (lines.get(lines.size() - 1).contains(needle)) { - index = lines.size() - 1; - } - } - } - - if (index >= 0 && index + relativeLine < lines.size()) { - rep = lines.get(index + relativeLine); - if (first) { - break; - } - } - } - - return rep; - } - - /** - * Return the text between the key and the endKey (and optional subKey can - * be passed, in this case we will look for the key first, then take the - * text between the subKey and the endKey). - *

- * Will only match the first line with the given key if more than one are - * possible. Which also means that if the subKey or endKey is not found on - * that line, NULL will be returned. - * - * @param in - * the input - * @param key - * the key to match (also supports "^" at start to say - * "only if it starts with" the key) - * @param subKey - * the sub key or NULL if none - * @param endKey - * the end key or NULL for "up to the end" - * @return the text or NULL if not found - */ - static protected String getKeyLine(InputStream in, String key, - String subKey, String endKey) { - return getKeyText(getLine(in, key, 0), key, subKey, endKey); - } - - /** - * Return the text between the key and the endKey (and optional subKey can - * be passed, in this case we will look for the key first, then take the - * text between the subKey and the endKey). - * - * @param in - * the input - * @param key - * the key to match (also supports "^" at start to say - * "only if it starts with" the key) - * @param subKey - * the sub key or NULL if none - * @param endKey - * the end key or NULL for "up to the end" - * @return the text or NULL if not found - */ - static protected String getKeyText(String in, String key, String subKey, - String endKey) { - String result = null; - - String line = in; - if (line != null && line.contains(key)) { - line = line.substring(line.indexOf(key) + key.length()); - if (subKey == null || subKey.isEmpty() || line.contains(subKey)) { - if (subKey != null) { - line = line.substring(line.indexOf(subKey) - + subKey.length()); - } - if (endKey == null || line.contains(endKey)) { - if (endKey != null) { - line = line.substring(0, line.indexOf(endKey)); - result = line; - } - } - } - } - - return result; - } - - /** - * Return the text between the key and the endKey (optional subKeys can be - * passed, in this case we will look for the subKeys first, then take the - * text between the key and the endKey). - * - * @param in - * the input - * @param key - * the key to match - * @param endKey - * the end key or NULL for "up to the end" - * @param afters - * the sub-keys to find before checking for key/endKey - * - * @return the text or NULL if not found - */ - static protected String getKeyTextAfter(String in, String key, - String endKey, String... afters) { - - if (in != null && !in.isEmpty()) { - int pos = indexOfAfter(in, 0, afters); - if (pos < 0) { - return null; - } - - in = in.substring(pos); - } - - return getKeyText(in, key, null, endKey); - } - - /** - * Return the first index after all the given "afters" have been found in - * the {@link String}, or -1 if it was not possible. - * - * @param in - * the input - * @param startAt - * start at this position in the string - * @param afters - * the sub-keys to find before checking for key/endKey - * - * @return the text or NULL if not found - */ - static protected int indexOfAfter(String in, int startAt, String... afters) { - int pos = -1; - if (in != null && !in.isEmpty()) { - pos = startAt; - if (afters != null) { - for (int i = 0; pos >= 0 && i < afters.length; i++) { - String subKey = afters[i]; - if (!subKey.isEmpty()) { - pos = in.indexOf(subKey, pos); - if (pos >= 0) { - pos += subKey.length(); - } - } - } - } - } - - return pos; - } -} diff --git a/src/be/nikiroo/fanfix/supported/Cbz.java b/src/be/nikiroo/fanfix/supported/Cbz.java deleted file mode 100644 index cf603352..00000000 --- a/src/be/nikiroo/fanfix/supported/Cbz.java +++ /dev/null @@ -1,226 +0,0 @@ -package be.nikiroo.fanfix.supported; - -import java.io.File; -import java.io.IOException; -import java.io.InputStream; -import java.net.URL; -import java.util.ArrayList; -import java.util.Collections; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.zip.ZipEntry; -import java.util.zip.ZipInputStream; - -import be.nikiroo.fanfix.Instance; -import be.nikiroo.fanfix.bundles.Config; -import be.nikiroo.fanfix.data.Chapter; -import be.nikiroo.fanfix.data.MetaData; -import be.nikiroo.fanfix.data.Paragraph; -import be.nikiroo.fanfix.data.Paragraph.ParagraphType; -import be.nikiroo.fanfix.data.Story; -import be.nikiroo.utils.IOUtils; -import be.nikiroo.utils.Image; -import be.nikiroo.utils.Progress; -import be.nikiroo.utils.streams.MarkableFileInputStream; - -/** - * Support class for CBZ files (works better with CBZ created with this program, - * as they have some metadata available). - * - * @author niki - */ -class Cbz extends Epub { - @Override - protected boolean supports(URL url) { - return url.toString().toLowerCase().endsWith(".cbz"); - } - - @Override - protected String getDataPrefix() { - return ""; - } - - @Override - protected boolean requireInfo() { - return false; - } - - @Override - protected boolean isImagesDocumentByDefault() { - return true; - } - - @Override - protected boolean getCover() { - return false; - } - - @Override - public Story doProcess(Progress pg) throws IOException { - if (pg == null) { - pg = new Progress(); - } else { - pg.setMinMax(0, 100); - } - - pg.setName("Initialising"); - - Progress pgMeta = new Progress(); - pg.addProgress(pgMeta, 10); - Story story = processMeta(true, pgMeta); - MetaData meta = story.getMeta(); - - pgMeta.done(); // 10% - - pg.setName(meta.getTitle()); - - File tmpDir = Instance.getInstance().getTempFiles().createTempDir("info-text"); - String basename = null; - - Map images = new HashMap(); - InputStream cbzIn = null; - ZipInputStream zipIn = null; - try { - cbzIn = new MarkableFileInputStream(getSourceFileOriginal()); - zipIn = new ZipInputStream(cbzIn); - for (ZipEntry entry = zipIn.getNextEntry(); entry != null; entry = zipIn - .getNextEntry()) { - if (!entry.isDirectory() - && entry.getName().startsWith(getDataPrefix())) { - String entryLName = entry.getName().toLowerCase(); - boolean imageEntry = false; - for (String ext : bsImages.getImageExt(false)) { - if (entryLName.endsWith(ext)) { - imageEntry = true; - } - } - - if (imageEntry) { - String uuid = meta.getUuid() + "_" + entry.getName(); - try { - images.put(uuid, new Image(zipIn)); - } catch (Exception e) { - Instance.getInstance().getTraceHandler().error(e); - } - - if (pg.getProgress() < 85) { - pg.add(1); - } - } else if (entryLName.endsWith(".info")) { - basename = entryLName.substring(0, entryLName.length() - - ".info".length()); - IOUtils.write(zipIn, new File(tmpDir, entryLName)); - } else if (entryLName.endsWith(".txt")) { - IOUtils.write(zipIn, new File(tmpDir, entryLName)); - } - } - } - - String ext = "." - + Instance.getInstance().getConfig().getString(Config.FILE_FORMAT_IMAGE_FORMAT_COVER).toLowerCase(); - String coverName = meta.getUuid() + "_" + basename + ext; - Image cover = images.get(coverName); - images.remove(coverName); - - pg.setProgress(85); - - // ZIP order is not correct for us - List imagesList = new ArrayList(images.keySet()); - Collections.sort(imagesList); - - pg.setProgress(90); - - // only the description/cover is kept - Story origStory = getStoryFromTxt(tmpDir, basename); - if (origStory != null) { - if (origStory.getMeta().getCover() == null) { - origStory.getMeta().setCover(story.getMeta().getCover()); - } - story.setMeta(origStory.getMeta()); - } - if (story.getMeta().getCover() == null) { - story.getMeta().setCover(cover); - } - story.setChapters(new ArrayList()); - - // Check if we can find non-images chapters, for hybrid-cbz support - if (origStory != null) { - for (Chapter chap : origStory) { - Boolean isImages = null; - for (Paragraph para : chap) { - ParagraphType t = para.getType(); - if (isImages == null && !t.isText(true)) { - isImages = true; - } - if (t.isText(false)) { - String line = para.getContent(); - // Images are saved in text mode as "[image-link]" - if (!(line.startsWith("[") && line.endsWith("]"))) { - isImages = false; - } - } - } - - if (isImages != null && !isImages) { - story.getChapters().add(chap); - chap.setNumber(story.getChapters().size()); - } - } - } - - if (!imagesList.isEmpty()) { - Chapter chap = new Chapter(story.getChapters().size() + 1, null); - story.getChapters().add(chap); - - for (String uuid : imagesList) { - try { - chap.getParagraphs().add( - new Paragraph(images.get(uuid))); - } catch (Exception e) { - Instance.getInstance().getTraceHandler().error(e); - } - } - } - - if (meta.getCover() == null && !images.isEmpty()) { - meta.setCover(images.get(imagesList.get(0))); - meta.setFakeCover(true); - } - } finally { - IOUtils.deltree(tmpDir); - if (zipIn != null) { - zipIn.close(); - } - if (cbzIn != null) { - cbzIn.close(); - } - } - - pg.setName(meta.getTitle()); - pg.done(); - - return story; - } - - private Story getStoryFromTxt(File tmpDir, String basename) { - Story origStory = null; - - File txt = new File(tmpDir, basename + ".txt"); - if (!txt.exists()) { - basename = null; - } - if (basename != null) { - try { - BasicSupport support = BasicSupport.getSupport(txt.toURI() - .toURL()); - origStory = support.process(null); - } catch (Exception e) { - basename = null; - } - } - - return origStory; - - } -} diff --git a/src/be/nikiroo/fanfix/supported/E621.java b/src/be/nikiroo/fanfix/supported/E621.java deleted file mode 100644 index de754c8b..00000000 --- a/src/be/nikiroo/fanfix/supported/E621.java +++ /dev/null @@ -1,315 +0,0 @@ -package be.nikiroo.fanfix.supported; - -import java.io.IOException; -import java.io.InputStream; -import java.io.UnsupportedEncodingException; -import java.net.MalformedURLException; -import java.net.URL; -import java.net.URLDecoder; -import java.util.AbstractMap; -import java.util.ArrayList; -import java.util.Collections; -import java.util.Date; -import java.util.LinkedList; -import java.util.List; -import java.util.Map.Entry; - -import org.jsoup.helper.DataUtil; -import org.jsoup.nodes.Document; -import org.jsoup.nodes.Element; - -import be.nikiroo.fanfix.Instance; -import be.nikiroo.fanfix.data.MetaData; -import be.nikiroo.utils.IOUtils; -import be.nikiroo.utils.Image; -import be.nikiroo.utils.Progress; -import be.nikiroo.utils.StringUtils; - -/** - * Support class for e621.net and - * e926.net, a Furry website supporting comics, - * including some of MLP. - *

- * e926.net only shows the "clean" images and - * comics, but it can be difficult to browse. - * - * @author niki - */ -class E621 extends BasicSupport { - @Override - protected boolean supports(URL url) { - String host = url.getHost(); - if (host.startsWith("www.")) { - host = host.substring("www.".length()); - } - - return ("e621.net".equals(host) || "e926.net".equals(host)) && (isPool(url) || isSearchOrSet(url)); - } - - @Override - protected boolean isHtml() { - return true; - } - - @Override - protected MetaData getMeta() throws IOException { - MetaData meta = new MetaData(); - - meta.setTitle(getTitle()); - meta.setAuthor(getAuthor()); - meta.setDate(""); - meta.setTags(getTags()); - meta.setSource(getType().getSourceName()); - meta.setUrl(getSource().toString()); - meta.setPublisher(getType().getSourceName()); - meta.setUuid(getSource().toString()); - meta.setLuid(""); - meta.setLang("en"); - meta.setSubject("Furry"); - meta.setType(getType().toString()); - meta.setImageDocument(true); - meta.setCover(getCover()); - meta.setFakeCover(true); - - return meta; - } - - @Override - protected String getDesc() throws IOException { - if (isSearchOrSet(getSource())) { - StringBuilder builder = new StringBuilder(); - builder.append("A collection of images from ").append(getSource().getHost()).append("\n") // - .append("\tTime of creation: " + StringUtils.fromTime(new Date().getTime())).append("\n") // - .append("\tTags: ");// - for (String tag : getTags()) { - builder.append("\t\t").append(tag); - } - - return builder.toString(); - } - - if (isPool(getSource())) { - Element el = getSourceNode().getElementById("description"); - if (el != null) { - return el.text(); - } - } - - return null; - } - - @Override - protected List> getChapters(Progress pg) throws IOException { - if (isPool(getSource())) { - String baseUrl = "https://e621.net/" + getSource().getPath() + "?page="; - return getChapters(getSource(), pg, baseUrl, ""); - } else if (isSearchOrSet(getSource())) { - String baseUrl = "https://e621.net/posts/?page="; - String search = "&tags=" + getTagsFromUrl(getSource()); - // sets are sorted in reverse order on the website - List> urls = getChapters(getSource(), pg, - baseUrl, search); - Collections.reverse(urls); - return urls; - } - - return new LinkedList>(); - } - - private List> getChapters(URL source, Progress pg, String baseUrl, String parameters) - throws IOException { - List> urls = new ArrayList>(); - - if (source.getHost().contains("e926")) { - baseUrl = baseUrl.replace("e621", "e926"); - } - - for (int i = 1; true; i++) { - URL url = new URL(baseUrl + i + parameters); - try { - InputStream pageI = Instance.getInstance().getCache().open(url, this, false); - try { - if (IOUtils.readSmallStream(pageI).contains("Nobody here but us chickens!")) { - break; - } - urls.add(new AbstractMap.SimpleEntry("Page " + Integer.toString(i), url)); - } finally { - pageI.close(); - } - } catch (Exception e) { - break; - } - } - - return urls; - } - - @Override - protected String getChapterContent(URL chapUrl, int number, Progress pg) throws IOException { - StringBuilder builder = new StringBuilder(); - Document chapterNode = loadDocument(chapUrl); - for (Element el : chapterNode.getElementsByTag("article")) { - builder.append("["); - builder.append(el.attr("data-file-url")); - builder.append("]
"); - } - - return builder.toString(); - } - - @Override - protected URL getCanonicalUrl(URL source) { - if (isSetOriginalUrl(source)) { - try { - Document doc = DataUtil.load(Instance.getInstance().getCache().open(source, this, false), "UTF-8", source.toString()); - for (Element shortname : doc.getElementsByClass("set-shortname")) { - for (Element el : shortname.getElementsByTag("a")) { - if (!el.attr("href").isEmpty()) - return new URL(el.absUrl("href")); - } - } - } catch (IOException e) { - Instance.getInstance().getTraceHandler().error(e); - } - } - - if (isPool(source)) { - try { - return new URL(source.toString().replace("/pool/show/", "/pools/")); - } catch (MalformedURLException e) { - } - } - - return super.getCanonicalUrl(source); - } - - // returns "xxx+ddd+ggg" if "tags=xxx+ddd+ggg" was present in the query - private String getTagsFromUrl(URL url) { - String tags = url == null ? "" : url.getQuery(); - int pos = tags.indexOf("tags="); - - if (pos >= 0) { - tags = tags.substring(pos).substring("tags=".length()); - } else { - return ""; - } - - pos = tags.indexOf('&'); - if (pos > 0) { - tags = tags.substring(0, pos); - } - pos = tags.indexOf('/'); - if (pos > 0) { - tags = tags.substring(0, pos); - } - - return tags; - } - - private String getTitle() { - String title = ""; - - Element el = getSourceNode().getElementsByTag("title").first(); - if (el != null) { - title = el.text().trim(); - } - - for (String s : new String[] { "e621", "-", "e621" }) { - if (title.startsWith(s)) { - title = title.substring(s.length()).trim(); - } - if (title.endsWith(s)) { - title = title.substring(0, title.length() - s.length()).trim(); - } - - } - - if (isSearchOrSet(getSource())) { - title = title.isEmpty() ? "e621" : "[e621] " + title; - } - return title; - } - - private String getAuthor() throws IOException { - StringBuilder builder = new StringBuilder(); - - if (isSearchOrSet(getSource())) { - for (Element el : getSourceNode().getElementsByClass("search-tag")) { - if (el.attr("itemprop").equals("author")) { - if (builder.length() > 0) { - builder.append(", "); - } - builder.append(el.text().trim()); - } - } - } - - if (isPool(getSource())) { - String desc = getDesc(); - String descL = desc.toLowerCase(); - - if (descL.startsWith("by:") || descL.startsWith("by ")) { - desc = desc.substring(3).trim(); - desc = desc.split("\n")[0]; - - String tab[] = desc.split(" "); - for (int i = 0; i < Math.min(tab.length, 5); i++) { - if (tab[i].startsWith("http")) - break; - builder.append(" ").append(tab[i]); - } - } - } - - return builder.toString(); - } - - // no tags for pools - private List getTags() { - List tags = new ArrayList(); - if (isSearchOrSet(getSource())) { - String str = getTagsFromUrl(getSource()); - for (String tag : str.split("\\+")) { - try { - tags.add(URLDecoder.decode(tag.trim(), "UTF-8").trim()); - } catch (UnsupportedEncodingException e) { - } - } - } - - return tags; - } - - private Image getCover() throws IOException { - Image image = null; - List> chapters = getChapters(null); - if (!chapters.isEmpty()) { - URL chap1Url = chapters.get(0).getValue(); - String imgsChap1 = getChapterContent(chap1Url, 1, null); - if (!imgsChap1.isEmpty()) { - imgsChap1 = imgsChap1.split("]")[0].substring(1).trim(); - image = bsImages.getImage(this, new URL(imgsChap1)); - } - } - - return image; - } - - // note: will be removed at getCanonicalUrl() - private boolean isSetOriginalUrl(URL originalUrl) { - return originalUrl.getPath().startsWith("/post_sets/"); - } - - private boolean isPool(URL url) { - return url.getPath().startsWith("/pools/") || url.getPath().startsWith("/pool/show/"); - } - - // set will be renamed into search by canonical url - private boolean isSearchOrSet(URL url) { - return - // search: - (url.getPath().equals("/posts") && url.getQuery().contains("tags=")) - // or set: - || isSetOriginalUrl(url); - } -} diff --git a/src/be/nikiroo/fanfix/supported/EHentai.java b/src/be/nikiroo/fanfix/supported/EHentai.java deleted file mode 100644 index 03c15574..00000000 --- a/src/be/nikiroo/fanfix/supported/EHentai.java +++ /dev/null @@ -1,292 +0,0 @@ -package be.nikiroo.fanfix.supported; - -import java.io.IOException; -import java.io.InputStream; -import java.net.MalformedURLException; -import java.net.URL; -import java.util.AbstractMap; -import java.util.ArrayList; -import java.util.List; -import java.util.Map; -import java.util.Map.Entry; -import java.util.Scanner; - -import be.nikiroo.fanfix.Instance; -import be.nikiroo.fanfix.data.Chapter; -import be.nikiroo.fanfix.data.MetaData; -import be.nikiroo.fanfix.data.Story; -import be.nikiroo.utils.Image; -import be.nikiroo.utils.Progress; -import be.nikiroo.utils.StringUtils; - -/** - * Support class for e-hentai.org, a website - * supporting mostly but not always NSFW comics, including some of MLP. - * - * @author niki - */ -class EHentai extends BasicSupport_Deprecated { - @Override - protected MetaData getMeta(URL source, InputStream in) throws IOException { - MetaData meta = new MetaData(); - - meta.setTitle(getTitle(reset(in))); - meta.setAuthor(getAuthor(reset(in))); - meta.setDate(getDate(reset(in))); - meta.setTags(getTags(reset(in))); - meta.setSource(getType().getSourceName()); - meta.setUrl(source.toString()); - meta.setPublisher(getType().getSourceName()); - meta.setUuid(source.toString()); - meta.setLuid(""); - meta.setLang(getLang(reset(in))); - meta.setSubject("Hentai"); - meta.setType(getType().toString()); - meta.setImageDocument(true); - meta.setCover(getCover(source, reset(in))); - meta.setFakeCover(true); - - return meta; - } - - @Override - public Story process(URL url, Progress pg) throws IOException { - // There is no chapters on e621, just pagination... - Story story = super.process(url, pg); - - Chapter only = new Chapter(1, null); - for (Chapter chap : story) { - only.getParagraphs().addAll(chap.getParagraphs()); - } - - story.getChapters().clear(); - story.getChapters().add(only); - - return story; - } - - @Override - protected boolean supports(URL url) { - return "e-hentai.org".equals(url.getHost()); - } - - @Override - protected boolean isHtml() { - return true; - } - - @Override - public Map getCookies() { - Map cookies = super.getCookies(); - cookies.put("nw", "1"); - return cookies; - } - - private Image getCover(URL source, InputStream in) { - Image author = null; - String coverLine = getKeyLine(in, "

tagsAuthor = getTagsAuthor(in); - if (!tagsAuthor.isEmpty()) { - author = tagsAuthor.get(0); - } - - return author; - } - - private String getLang(InputStream in) { - String lang = null; - - String langLine = getKeyLine(in, "class=\"gdt1\">Language", - "class=\"gdt2\"", ""); - if (langLine != null) { - langLine = StringUtils.unhtml(langLine).trim(); - if (langLine.equalsIgnoreCase("English")) { - lang = "en"; - } else if (langLine.equalsIgnoreCase("Japanese")) { - lang = "jp"; - } else if (langLine.equalsIgnoreCase("French")) { - lang = "fr"; - } else { - // TODO find the code? - lang = langLine; - } - } - - return lang; - } - - private String getDate(InputStream in) { - String date = null; - - String dateLine = getKeyLine(in, "class=\"gdt1\">Posted", - "class=\"gdt2\"", ""); - if (dateLine != null) { - dateLine = StringUtils.unhtml(dateLine).trim(); - if (dateLine.length() > 10) { - dateLine = dateLine.substring(0, 10).trim(); - } - - date = dateLine; - } - - return date; - } - - private List getTags(InputStream in) { - List tags = new ArrayList(); - List tagsAuthor = getTagsAuthor(in); - - for (int i = 1; i < tagsAuthor.size(); i++) { - tags.add(tagsAuthor.get(i)); - } - - return tags; - } - - private List getTagsAuthor(InputStream in) { - List tags = new ArrayList(); - String tagLine = getKeyLine(in, "", 0); - if (title != null) { - title = StringUtils.unhtml(title).trim(); - if (title.endsWith(siteName)) { - title = title.substring(0, title.length() - siteName.length()) - .trim(); - } - } - - return title; - } - - @Override - protected String getDesc(URL source, InputStream in) throws IOException { - String desc = null; - - String descLine = getKeyLine(in, "Uploader Comment", null, - "
> getChapters(URL source, InputStream in, - Progress pg) throws IOException { - List> urls = new ArrayList>(); - int last = 0; // no pool/show when only one page, first page == page 0 - - @SuppressWarnings("resource") - Scanner scan = new Scanner(in, "UTF-8"); - scan.useDelimiter(">"); - while (scan.hasNext()) { - String line = scan.next(); - if (line.contains(source.toString())) { - String page = line.substring(line.indexOf(source.toString())); - String pkey = "?p="; - if (page.contains(pkey)) { - page = page.substring(page.indexOf(pkey) + pkey.length()); - String number = ""; - while (!page.isEmpty() && page.charAt(0) >= '0' - && page.charAt(0) <= '9') { - number += page.charAt(0); - page = page.substring(1); - } - if (number.isEmpty()) { - number = "0"; - } - - int current = Integer.parseInt(number); - if (last < current) { - last = current; - } - } - } - } - - for (int i = 0; i <= last; i++) { - urls.add(new AbstractMap.SimpleEntry(Integer - .toString(i + 1), new URL(source.toString() + "?p=" + i))); - } - - return urls; - } - - @Override - protected String getChapterContent(URL source, InputStream in, int number, - Progress pg) throws IOException { - String staticSite = "https://e-hentai.org/s/"; - List pages = new ArrayList(); - - @SuppressWarnings("resource") - Scanner scan = new Scanner(in, "UTF-8"); - scan.useDelimiter("\""); - while (scan.hasNext()) { - String line = scan.next(); - if (line.startsWith(staticSite)) { - try { - pages.add(new URL(line)); - } catch (MalformedURLException e) { - Instance.getInstance().getTraceHandler() - .error(new IOException("Parsing error, a link is not correctly parsed: " + line, e)); - } - } - } - - if (pg == null) { - pg = new Progress(); - } - pg.setMinMax(0, pages.size()); - pg.setProgress(0); - - StringBuilder builder = new StringBuilder(); - - for (URL page : pages) { - InputStream pageIn = Instance.getInstance().getCache().open(page, this, false); - try { - String link = getKeyLine(pageIn, "id=\"img\"", "src=\"", "\""); - if (link != null && !link.isEmpty()) { - builder.append("["); - builder.append(link); - builder.append("]
"); - } - pg.add(1); - } finally { - if (pageIn != null) { - pageIn.close(); - } - } - } - - pg.done(); - return builder.toString(); - } -} diff --git a/src/be/nikiroo/fanfix/supported/Epub.java b/src/be/nikiroo/fanfix/supported/Epub.java deleted file mode 100644 index f8e46783..00000000 --- a/src/be/nikiroo/fanfix/supported/Epub.java +++ /dev/null @@ -1,244 +0,0 @@ -package be.nikiroo.fanfix.supported; - -import java.io.File; -import java.io.IOException; -import java.io.InputStream; -import java.net.URISyntaxException; -import java.net.URL; -import java.net.URLDecoder; -import java.util.ArrayList; -import java.util.zip.ZipEntry; -import java.util.zip.ZipInputStream; - -import org.jsoup.nodes.Document; - -import be.nikiroo.fanfix.Instance; -import be.nikiroo.fanfix.data.MetaData; -import be.nikiroo.utils.IOUtils; -import be.nikiroo.utils.Image; -import be.nikiroo.utils.StringUtils; -import be.nikiroo.utils.streams.MarkableFileInputStream; - -/** - * Support class for EPUB files created with this program (as we need some - * metadata available in those we create). - * - * @author niki - */ -class Epub extends InfoText { - private MetaData meta; - private File tmpDir; - private String desc; - - private URL fakeSource; - private InputStream fakeIn; - - public File getSourceFileOriginal() { - return super.getSourceFile(); - } - - @Override - protected File getSourceFile() { - try { - return new File(fakeSource.toURI()); - } catch (URISyntaxException e) { - Instance.getInstance().getTraceHandler() - .error(new IOException("Cannot get the source file from the info-text URL", e)); - } - - return null; - } - - @Override - protected InputStream getInput() { - if (fakeIn != null) { - try { - fakeIn.reset(); - } catch (IOException e) { - Instance.getInstance().getTraceHandler().error(new IOException("Cannot reset the Epub Text stream", e)); - } - - return fakeIn; - } - - return null; - } - - @Override - protected boolean supports(URL url) { - return url.getPath().toLowerCase().endsWith(".epub"); - } - - @Override - protected MetaData getMeta() throws IOException { - return meta; - } - - @Override - protected Document loadDocument(URL source) throws IOException { - super.loadDocument(source); // prepares super.getSourceFile() and - // super.getInput() - - InputStream in = super.getInput(); - ZipInputStream zipIn = null; - try { - zipIn = new ZipInputStream(in); - tmpDir = Instance.getInstance().getTempFiles().createTempDir("fanfic-reader-parser"); - File tmp = new File(tmpDir, "file.txt"); - File tmpInfo = new File(tmpDir, "file.info"); - - fakeSource = tmp.toURI().toURL(); - Image cover = null; - - String url; - try { - url = getSource().toURI().toURL().toString(); - } catch (URISyntaxException e1) { - url = getSource().toString(); - } - String title = null; - String author = null; - - for (ZipEntry entry = zipIn.getNextEntry(); entry != null; entry = zipIn - .getNextEntry()) { - if (!entry.isDirectory() - && entry.getName().startsWith(getDataPrefix())) { - String entryLName = entry.getName().toLowerCase(); - - boolean imageEntry = false; - for (String ext : bsImages.getImageExt(false)) { - if (entryLName.endsWith(ext)) { - imageEntry = true; - } - } - - if (entry.getName().equals(getDataPrefix() + "version")) { - // Nothing to do for now ("first" - // version is 3.0) - } else if (entryLName.endsWith(".info")) { - // Info file - IOUtils.write(zipIn, tmpInfo); - } else if (imageEntry) { - // Cover - if (getCover()) { - try { - cover = new Image(zipIn); - } catch (Exception e) { - Instance.getInstance().getTraceHandler().error(e); - } - } - } else if (entry.getName().equals(getDataPrefix() + "URL")) { - String[] descArray = StringUtils - .unhtml(IOUtils.readSmallStream(zipIn)).trim() - .split("\n"); - if (descArray.length > 0) { - url = descArray[0].trim(); - } - } else if (entry.getName().equals( - getDataPrefix() + "SUMMARY")) { - String[] descArray = StringUtils - .unhtml(IOUtils.readSmallStream(zipIn)).trim() - .split("\n"); - int skip = 0; - if (descArray.length > 1) { - title = descArray[0].trim(); - skip = 1; - if (descArray.length > 2 - && descArray[1].startsWith("©")) { - author = descArray[1].substring(1).trim(); - skip = 2; - } - } - this.desc = ""; - for (int i = skip; i < descArray.length; i++) { - this.desc += descArray[i].trim() + "\n"; - } - - this.desc = this.desc.trim(); - } else { - // Hopefully the data file - IOUtils.write(zipIn, tmp); - } - } - } - - if (requireInfo() && (!tmp.exists() || !tmpInfo.exists())) { - throw new IOException( - "file not supported (maybe not created with this program or corrupt)"); - } - - if (tmp.exists()) { - this.fakeIn = new MarkableFileInputStream(tmp); - } - - if (tmpInfo.exists()) { - meta = InfoReader.readMeta(tmpInfo, true); - tmpInfo.delete(); - } else { - if (title == null || title.isEmpty()) { - title = getSourceFileOriginal().getName(); - if (title.toLowerCase().endsWith(".cbz")) { - title = title.substring(0, title.length() - 4); - } - title = URLDecoder.decode(title, "UTF-8").trim(); - } - - meta = new MetaData(); - meta.setLang("en"); - meta.setTags(new ArrayList()); - meta.setSource(getType().getSourceName()); - meta.setUuid(url); - meta.setUrl(url); - meta.setTitle(title); - meta.setAuthor(author); - meta.setImageDocument(isImagesDocumentByDefault()); - } - - if (meta.getCover() == null) { - if (cover != null) { - meta.setCover(cover); - } else { - meta.setCover(InfoReader - .getCoverByName(getSourceFileOriginal().toURI() - .toURL())); - } - } - } finally { - if (zipIn != null) { - zipIn.close(); - } - if (in != null) { - in.close(); - } - } - - return null; - } - - @Override - protected void close() { - if (tmpDir != null) { - IOUtils.deltree(tmpDir); - } - - tmpDir = null; - - super.close(); - } - - protected String getDataPrefix() { - return "DATA/"; - } - - protected boolean requireInfo() { - return true; - } - - protected boolean getCover() { - return true; - } - - protected boolean isImagesDocumentByDefault() { - return false; - } -} diff --git a/src/be/nikiroo/fanfix/supported/Fanfiction.java b/src/be/nikiroo/fanfix/supported/Fanfiction.java deleted file mode 100644 index 282192e0..00000000 --- a/src/be/nikiroo/fanfix/supported/Fanfiction.java +++ /dev/null @@ -1,331 +0,0 @@ -package be.nikiroo.fanfix.supported; - -import java.io.IOException; -import java.io.InputStream; -import java.net.MalformedURLException; -import java.net.URL; -import java.text.SimpleDateFormat; -import java.util.AbstractMap; -import java.util.ArrayList; -import java.util.Date; -import java.util.List; -import java.util.Map.Entry; -import java.util.Scanner; - -import be.nikiroo.fanfix.Instance; -import be.nikiroo.fanfix.bundles.Config; -import be.nikiroo.fanfix.data.MetaData; -import be.nikiroo.utils.Image; -import be.nikiroo.utils.Progress; -import be.nikiroo.utils.StringUtils; - -/** - * Support class for Faniction.net - * stories, a website dedicated to fanfictions of many, many different - * universes, from TV shows to novels to games. - * - * @author niki - */ -class Fanfiction extends BasicSupport_Deprecated { - @Override - protected boolean isHtml() { - return true; - } - - @Override - protected MetaData getMeta(URL source, InputStream in) throws IOException { - MetaData meta = new MetaData(); - - meta.setTitle(getTitle(reset(in))); - meta.setAuthor(getAuthor(reset(in))); - meta.setDate(getDate(reset(in))); - meta.setTags(getTags(reset(in))); - meta.setSource(getType().getSourceName()); - meta.setUrl(source.toString()); - meta.setPublisher(getType().getSourceName()); - meta.setUuid(source.toString()); - meta.setLuid(""); - meta.setLang("en"); // TODO! - meta.setSubject(getSubject(reset(in))); - meta.setType(getType().toString()); - meta.setImageDocument(false); - meta.setCover(getCover(source, reset(in))); - - return meta; - } - - private String getSubject(InputStream in) { - String line = getLine(in, "id=pre_story_links", 0); - if (line != null) { - int pos = line.lastIndexOf('"'); - if (pos >= 1) { - line = line.substring(pos + 1); - pos = line.indexOf('<'); - if (pos >= 0) { - return StringUtils.unhtml(line.substring(0, pos)).trim(); - } - } - } - - return null; - } - - private List getTags(InputStream in) { - List tags = new ArrayList(); - - String key = "title=\"Send Private Message\""; - String line = getLine(in, key, 2); - if (line != null) { - key = "Rated:"; - int pos = line.indexOf(key); - if (pos >= 0) { - line = line.substring(pos + key.length()); - key = "Chapters:"; - pos = line.indexOf(key); - if (pos >= 0) { - line = line.substring(0, pos); - line = StringUtils.unhtml(line).trim(); - if (line.endsWith("-")) { - line = line.substring(0, line.length() - 1); - } - - for (String tag : line.split("-")) { - tags.add(StringUtils.unhtml(tag).trim()); - } - } - } - } - - return tags; - } - - private String getTitle(InputStream in) { - int i = 0; - @SuppressWarnings("resource") - Scanner scan = new Scanner(in, "UTF-8"); - scan.useDelimiter("\\n"); - while (scan.hasNext()) { - String line = scan.next(); - if (line.contains("xcontrast_txt")) { - if ((++i) == 2) { - line = StringUtils.unhtml(line).trim(); - if (line.startsWith("Follow/Fav")) { - line = line.substring("Follow/Fav".length()).trim(); - } - - return StringUtils.unhtml(line).trim(); - } - } - } - - return ""; - } - - private String getAuthor(InputStream in) { - String author = null; - - int i = 0; - @SuppressWarnings("resource") - Scanner scan = new Scanner(in, "UTF-8"); - scan.useDelimiter("\\n"); - while (scan.hasNext()) { - String line = scan.next(); - if (line.contains("xcontrast_txt")) { - if ((++i) == 3) { - author = StringUtils.unhtml(line).trim(); - break; - } - } - } - - return bsHelper.fixAuthor(author); - } - - private String getDate(InputStream in) { - String key = "Published: = 0) { - line = line.substring(0, pos).trim(); - try { - SimpleDateFormat sdf = new SimpleDateFormat( - "yyyy-MM-dd"); - return sdf - .format(new Date(1000 * Long.parseLong(line))); - } catch (NumberFormatException e) { - Instance.getInstance().getTraceHandler() - .error(new IOException("Cannot convert publication date: " + line, e)); - } - } - } - } - - return null; - } - - @Override - protected String getDesc(URL source, InputStream in) { - return getLine(in, "title=\"Send Private Message\"", 1); - } - - private Image getCover(URL url, InputStream in) { - String key = "class='cimage"; - String line = getLine(in, key, 0); - if (line != null) { - int pos = line.indexOf(key); - if (pos >= 0) { - line = line.substring(pos + key.length()); - key = "src='"; - pos = line.indexOf(key); - if (pos >= 0) { - line = line.substring(pos + key.length()); - pos = line.indexOf('\''); - if (pos >= 0) { - line = line.substring(0, pos); - if (line.startsWith("//")) { - line = url.getProtocol() + "://" - + line.substring(2); - } else if (line.startsWith("//")) { - line = url.getProtocol() + "://" + url.getHost() - + "/" + line.substring(1); - } else { - line = url.getProtocol() + "://" + url.getHost() - + "/" + url.getPath() + "/" + line; - } - - return getImage(this, null, line); - } - } - } - } - - return null; - } - - @Override - protected List> getChapters(URL source, InputStream in, - Progress pg) { - List> urls = new ArrayList>(); - - String base = source.toString(); - int pos = base.lastIndexOf('/'); - String suffix = base.substring(pos); // including '/' at start - base = base.substring(0, pos); - if (base.endsWith("/1")) { - base = base.substring(0, base.length() - 1); // including '/' at end - } - - String line = getLine(in, "id=chap_select", 0); - String key = "