From: Niki Roo Date: Wed, 3 Jul 2024 11:54:59 +0000 (+0200) Subject: fix changelog headers X-Git-Url: https://git.nikiroo.be/?a=commitdiff_plain;h=refs%2Fheads%2Fmaster;hp=379a497e6fd2b959c57b2ff4023413e2daf36232;p=fanfix.git fix changelog headers --- diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 0000000..c48e253 --- /dev/null +++ b/.gitmodules @@ -0,0 +1,8 @@ +[submodule "src/be/nikiroo/fanfix"] + path = src/be/nikiroo/fanfix + url = ../fanfix + branch = subtree +[submodule "src/be/nikiroo/utils"] + path = src/be/nikiroo/utils + url = ../nikiroo-utils + branch = subtree diff --git a/DataLoader.java b/DataLoader.java deleted file mode 100644 index 901e8da..0000000 --- a/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/Doxyfile b/Doxyfile new file mode 100644 index 0000000..f855cfe --- /dev/null +++ b/Doxyfile @@ -0,0 +1,2469 @@ +# Doxyfile 1.8.13 + +# Minimum required fields: +PROJECT_NAME = "Fanfix" +PROJECT_BRIEF = "Download stories and render them offline" +INPUT = src/be/nikiroo +STRIP_FROM_PATH = src +OPTIMIZE_OUTPUT_FOR_C = NO +OPTIMIZE_OUTPUT_JAVA = YES +FILE_PATTERNS = *.java + +# This file describes the settings to be used by the documentation system +# doxygen (www.doxygen.org) for a project. +# +# All text after a double hash (##) is considered a comment and is placed in +# front of the TAG it is preceding. +# +# All text after a single hash (#) is considered a comment and will be ignored. +# The format is: +# TAG = value [value, ...] +# For lists, items can also be appended using: +# TAG += value [value, ...] +# Values that contain spaces should be placed between quotes (\" \"). + +#--------------------------------------------------------------------------- +# Project related configuration options +#--------------------------------------------------------------------------- + +# This tag specifies the encoding used for all characters in the config file +# that follow. The default is UTF-8 which is also the encoding used for all text +# before the first occurrence of this tag. Doxygen uses libiconv (or the iconv +# built into libc) for the transcoding. See http://www.gnu.org/software/libiconv +# for the list of possible encodings. +# The default value is: UTF-8. +# +#DOXYFILE_ENCODING = UTF-8 + +# The PROJECT_NAME tag is a single word (or a sequence of words surrounded by +# double-quotes, unless you are using Doxywizard) that should identify the +# project for which the documentation is generated. This name is used in the +# title of most generated pages and in a few other places. +# The default value is: My Project. +# +# +#PROJECT_NAME = "My Project" + +# The PROJECT_NUMBER tag can be used to enter a project or revision number. This +# could be handy for archiving the generated documentation or if some version +# control system is used. +# + +# Please do not use, or do not call with make file (it will automatically +# add the version number from ./VERSION) +# +#PROJECT_NUMBER = 1.0 + +# Using the PROJECT_BRIEF tag one can provide an optional one line description +# for a project that appears at the top of each page and should give viewer a +# quick idea about the purpose of the project. Keep the description short. +# + +#PROJECT_BRIEF = "Small description" + +# With the PROJECT_LOGO tag one can specify a logo or an icon that is included +# in the documentation. The maximum height of the logo should not exceed 55 +# pixels and the maximum width should not exceed 200 pixels. Doxygen will copy +# the logo to the output directory. +# + +PROJECT_LOGO = logo.png + +# The OUTPUT_DIRECTORY tag is used to specify the (relative or absolute) path +# into which the generated documentation will be written. If a relative path is +# entered, it will be relative to the location where doxygen was started. If +# left blank the current directory will be used. + +OUTPUT_DIRECTORY = doc + +# If the CREATE_SUBDIRS tag is set to YES then doxygen will create 4096 sub- +# directories (in 2 levels) under the output directory of each output format and +# will distribute the generated files over these directories. Enabling this +# option can be useful when feeding doxygen a huge amount of source files, where +# putting all generated files in the same directory would otherwise causes +# performance problems for the file system. +# The default value is: NO. +# +#CREATE_SUBDIRS = NO + +# If the ALLOW_UNICODE_NAMES tag is set to YES, doxygen will allow non-ASCII +# characters to appear in the names of generated files. If set to NO, non-ASCII +# characters will be escaped, for example _xE3_x81_x84 will be used for Unicode +# U+3044. +# The default value is: NO. +# +#ALLOW_UNICODE_NAMES = NO + +# The OUTPUT_LANGUAGE tag is used to specify the language in which all +# documentation generated by doxygen is written. Doxygen will use this +# information to generate all constant output in the proper language. +# Possible values are: Afrikaans, Arabic, Armenian, Brazilian, Catalan, Chinese, +# Chinese-Traditional, Croatian, Czech, Danish, Dutch, English (United States), +# Esperanto, Farsi (Persian), Finnish, French, German, Greek, Hungarian, +# Indonesian, Italian, Japanese, Japanese-en (Japanese with English messages), +# Korean, Korean-en (Korean with English messages), Latvian, Lithuanian, +# Macedonian, Norwegian, Persian (Farsi), Polish, Portuguese, Romanian, Russian, +# Serbian, Serbian-Cyrillic, Slovak, Slovene, Spanish, Swedish, Turkish, +# Ukrainian and Vietnamese. +# The default value is: English. +# +#OUTPUT_LANGUAGE = English + +# If the BRIEF_MEMBER_DESC tag is set to YES, doxygen will include brief member +# descriptions after the members that are listed in the file and class +# documentation (similar to Javadoc). Set to NO to disable this. +# The default value is: YES. +# +#BRIEF_MEMBER_DESC = YES + +# If the REPEAT_BRIEF tag is set to YES, doxygen will prepend the brief +# description of a member or function before the detailed description +# +# Note: If both HIDE_UNDOC_MEMBERS and BRIEF_MEMBER_DESC are set to NO, the +# brief descriptions will be completely suppressed. +# The default value is: YES. +# +#REPEAT_BRIEF = YES + +# This tag implements a quasi-intelligent brief description abbreviator that is +# used to form the text in various listings. Each string in this list, if found +# as the leading text of the brief description, will be stripped from the text +# and the result, after processing the whole list, is used as the annotated +# text. Otherwise, the brief description is used as-is. If left blank, the +# following values are used ($name is automatically replaced with the name of +# the entity):The $name class, The $name widget, The $name file, is, provides, +# specifies, contains, represents, a, an and the. +# +#ABBREVIATE_BRIEF = "The $name class" \ +# "The $name widget" \ +# "The $name file" \ +# is \ +# provides \ +# specifies \ +# contains \ +# represents \ +# a \ +# an \ +# the + +# If the ALWAYS_DETAILED_SEC and REPEAT_BRIEF tags are both set to YES then +# doxygen will generate a detailed section even if there is only a brief +# description. +# The default value is: NO. +# +#ALWAYS_DETAILED_SEC = NO + +# If the INLINE_INHERITED_MEMB tag is set to YES, doxygen will show all +# inherited members of a class in the documentation of that class as if those +# members were ordinary class members. Constructors, destructors and assignment +# operators of the base classes will not be shown. +# The default value is: NO. +# +#INLINE_INHERITED_MEMB = NO + +# If the FULL_PATH_NAMES tag is set to YES, doxygen will prepend the full path +# before files name in the file list and in the header files. If set to NO the +# shortest path that makes the file name unique will be used +# The default value is: YES. +# +#FULL_PATH_NAMES = YES + +# The STRIP_FROM_PATH tag can be used to strip a user-defined part of the path. +# Stripping is only done if one of the specified strings matches the left-hand +# part of the path. The tag can be used to show relative paths in the file list. +# If left blank the directory from which doxygen is run is used as the path to +# strip. +# +# Note that you can specify absolute paths here, but also relative paths, which +# will be relative from the directory where doxygen is started. +# This tag requires that the tag FULL_PATH_NAMES is set to YES. +# +#STRIP_FROM_PATH = + +# The STRIP_FROM_INC_PATH tag can be used to strip a user-defined part of the +# path mentioned in the documentation of a class, which tells the reader which +# header file to include in order to use a class. If left blank only the name of +# the header file containing the class definition is used. Otherwise one should +# specify the list of include paths that are normally passed to the compiler +# using the -I flag. +# +#STRIP_FROM_INC_PATH = + +# If the SHORT_NAMES tag is set to YES, doxygen will generate much shorter (but +# less readable) file names. This can be useful is your file systems doesn't +# support long names like on DOS, Mac, or CD-ROM. +# The default value is: NO. +# +#SHORT_NAMES = NO + +# If the JAVADOC_AUTOBRIEF tag is set to YES then doxygen will interpret the +# first line (until the first dot) of a Javadoc-style comment as the brief +# description. If set to NO, the Javadoc-style will behave just like regular Qt- +# style comments (thus requiring an explicit @brief command for a brief +# description.) +# The default value is: NO. +# +#JAVADOC_AUTOBRIEF = NO + +JAVADOC_AUTOBRIEF = YES + +# If the QT_AUTOBRIEF tag is set to YES then doxygen will interpret the first +# line (until the first dot) of a Qt-style comment as the brief description. If +# set to NO, the Qt-style will behave just like regular Qt-style comments (thus +# requiring an explicit \brief command for a brief description.) +# The default value is: NO. +# +#QT_AUTOBRIEF = NO + +# The MULTILINE_CPP_IS_BRIEF tag can be set to YES to make doxygen treat a +# multi-line C++ special comment block (i.e. a block of //! or /// comments) as +# a brief description. This used to be the default behavior. The new default is +# to treat a multi-line C++ comment block as a detailed description. Set this +# tag to YES if you prefer the old behavior instead. +# +# Note that setting this tag to YES also means that rational rose comments are +# not recognized any more. +# The default value is: NO. +# +#MULTILINE_CPP_IS_BRIEF = NO + +# If the INHERIT_DOCS tag is set to YES then an undocumented member inherits the +# documentation from any documented member that it re-implements. +# The default value is: YES. +# +#INHERIT_DOCS = YES + +# If the SEPARATE_MEMBER_PAGES tag is set to YES then doxygen will produce a new +# page for each member. If set to NO, the documentation of a member will be part +# of the file/class/namespace that contains it. +# The default value is: NO. +# +#SEPARATE_MEMBER_PAGES = NO + +# The TAB_SIZE tag can be used to set the number of spaces in a tab. Doxygen +# uses this value to replace tabs by spaces in code fragments. +# Minimum value: 1, maximum value: 16, default value: 4. +# +#TAB_SIZE = 4 + +# This tag can be used to specify a number of aliases that act as commands in +# the documentation. An alias has the form: +# name=value +# For example adding +# "sideeffect=@par Side Effects:\n" +# will allow you to put the command \sideeffect (or @sideeffect) in the +# documentation, which will result in a user-defined paragraph with heading +# "Side Effects:". You can put \n's in the value part of an alias to insert +# newlines. +# +#ALIASES = + +# This tag can be used to specify a number of word-keyword mappings (TCL only). +# A mapping has the form "name=value". For example adding "class=itcl::class" +# will allow you to use the command class in the itcl::class meaning. +# +#TCL_SUBST = + +# Set the OPTIMIZE_OUTPUT_FOR_C tag to YES if your project consists of C sources +# only. Doxygen will then generate output that is more tailored for C. For +# instance, some of the names that are used will be different. The list of all +# members will be omitted, etc. +# The default value is: NO. +# +#OPTIMIZE_OUTPUT_FOR_C = NO + +# Set the OPTIMIZE_OUTPUT_JAVA tag to YES if your project consists of Java or +# Python sources only. Doxygen will then generate output that is more tailored +# for that language. For instance, namespaces will be presented as packages, +# qualified scopes will look different, etc. +# The default value is: NO. +# +#OPTIMIZE_OUTPUT_JAVA = NO + +# Set the OPTIMIZE_FOR_FORTRAN tag to YES if your project consists of Fortran +# sources. Doxygen will then generate output that is tailored for Fortran. +# The default value is: NO. +# +#OPTIMIZE_FOR_FORTRAN = NO + +# Set the OPTIMIZE_OUTPUT_VHDL tag to YES if your project consists of VHDL +# sources. Doxygen will then generate output that is tailored for VHDL. +# The default value is: NO. +# +#OPTIMIZE_OUTPUT_VHDL = NO + +# Doxygen selects the parser to use depending on the extension of the files it +# parses. With this tag you can assign which parser to use for a given +# extension. Doxygen has a built-in mapping, but you can override or extend it +# using this tag. The format is ext=language, where ext is a file extension, and +# language is one of the parsers supported by doxygen: IDL, Java, Javascript, +# C#, C, C++, D, PHP, Objective-C, Python, Fortran (fixed format Fortran: +# FortranFixed, free formatted Fortran: FortranFree, unknown formatted Fortran: +# Fortran. In the later case the parser tries to guess whether the code is fixed +# or free formatted code, this is the default for Fortran type files), VHDL. For +# instance to make doxygen treat .inc files as Fortran files (default is PHP), +# and .f files as C (default is Fortran), use: inc=Fortran f=C. +# +# Note: For files without extension you can use no_extension as a placeholder. +# +# Note that for custom extensions you also need to set FILE_PATTERNS otherwise +# the files are not read by doxygen. +# +#EXTENSION_MAPPING = + +# If the MARKDOWN_SUPPORT tag is enabled then doxygen pre-processes all comments +# according to the Markdown format, which allows for more readable +# documentation. See http://daringfireball.net/projects/markdown/ for details. +# The output of markdown processing is further processed by doxygen, so you can +# mix doxygen, HTML, and XML commands with Markdown formatting. Disable only in +# case of backward compatibilities issues. +# The default value is: YES. +# +#MARKDOWN_SUPPORT = YES + +# When the TOC_INCLUDE_HEADINGS tag is set to a non-zero value, all headings up +# to that level are automatically included in the table of contents, even if +# they do not have an id attribute. +# Note: This feature currently applies only to Markdown headings. +# Minimum value: 0, maximum value: 99, default value: 0. +# This tag requires that the tag MARKDOWN_SUPPORT is set to YES. +# +#TOC_INCLUDE_HEADINGS = 0 + +# When enabled doxygen tries to link words that correspond to documented +# classes, or namespaces to their corresponding documentation. Such a link can +# be prevented in individual cases by putting a % sign in front of the word or +# globally by setting AUTOLINK_SUPPORT to NO. +# The default value is: YES. +# +#AUTOLINK_SUPPORT = YES + +# If you use STL classes (i.e. std::string, std::vector, etc.) but do not want +# to include (a tag file for) the STL sources as input, then you should set this +# tag to YES in order to let doxygen match functions declarations and +# definitions whose arguments contain STL classes (e.g. func(std::string); +# versus func(std::string) {}). This also make the inheritance and collaboration +# diagrams that involve STL classes more complete and accurate. +# The default value is: NO. +# +#BUILTIN_STL_SUPPORT = NO + +# If you use Microsoft's C++/CLI language, you should set this option to YES to +# enable parsing support. +# The default value is: NO. +# +#CPP_CLI_SUPPORT = NO + +# Set the SIP_SUPPORT tag to YES if your project consists of sip (see: +# http://www.riverbankcomputing.co.uk/software/sip/intro) sources only. Doxygen +# will parse them like normal C++ but will assume all classes use public instead +# of private inheritance when no explicit protection keyword is present. +# The default value is: NO. +# +#SIP_SUPPORT = NO + +# For Microsoft's IDL there are propget and propput attributes to indicate +# getter and setter methods for a property. Setting this option to YES will make +# doxygen to replace the get and set methods by a property in the documentation. +# This will only work if the methods are indeed getting or setting a simple +# type. If this is not the case, or you want to show the methods anyway, you +# should set this option to NO. +# The default value is: YES. +# +#IDL_PROPERTY_SUPPORT = YES + +# If member grouping is used in the documentation and the DISTRIBUTE_GROUP_DOC +# tag is set to YES then doxygen will reuse the documentation of the first +# member in the group (if any) for the other members of the group. By default +# all members of a group must be documented explicitly. +# The default value is: NO. +# +#DISTRIBUTE_GROUP_DOC = NO + +# If one adds a struct or class to a group and this option is enabled, then also +# any nested class or struct is added to the same group. By default this option +# is disabled and one has to add nested compounds explicitly via \ingroup. +# The default value is: NO. +# +#GROUP_NESTED_COMPOUNDS = NO + +# Set the SUBGROUPING tag to YES to allow class member groups of the same type +# (for instance a group of public functions) to be put as a subgroup of that +# type (e.g. under the Public Functions section). Set it to NO to prevent +# subgrouping. Alternatively, this can be done per class using the +# \nosubgrouping command. +# The default value is: YES. +# +#SUBGROUPING = YES + +# When the INLINE_GROUPED_CLASSES tag is set to YES, classes, structs and unions +# are shown inside the group in which they are included (e.g. using \ingroup) +# instead of on a separate page (for HTML and Man pages) or section (for LaTeX +# and RTF). +# +# Note that this feature does not work in combination with +# SEPARATE_MEMBER_PAGES. +# The default value is: NO. +# +#INLINE_GROUPED_CLASSES = NO + +# When the INLINE_SIMPLE_STRUCTS tag is set to YES, structs, classes, and unions +# with only public data fields or simple typedef fields will be shown inline in +# the documentation of the scope in which they are defined (i.e. file, +# namespace, or group documentation), provided this scope is documented. If set +# to NO, structs, classes, and unions are shown on a separate page (for HTML and +# Man pages) or section (for LaTeX and RTF). +# The default value is: NO. +# +#INLINE_SIMPLE_STRUCTS = NO + +# When TYPEDEF_HIDES_STRUCT tag is enabled, a typedef of a struct, union, or +# enum is documented as struct, union, or enum with the name of the typedef. So +# typedef struct TypeS {} TypeT, will appear in the documentation as a struct +# with name TypeT. When disabled the typedef will appear as a member of a file, +# namespace, or class. And the struct will be named TypeS. This can typically be +# useful for C code in case the coding convention dictates that all compound +# types are typedef'ed and only the typedef is referenced, never the tag name. +# The default value is: NO. +# +#TYPEDEF_HIDES_STRUCT = NO + +# The size of the symbol lookup cache can be set using LOOKUP_CACHE_SIZE. This +# cache is used to resolve symbols given their name and scope. Since this can be +# an expensive process and often the same symbol appears multiple times in the +# code, doxygen keeps a cache of pre-resolved symbols. If the cache is too small +# doxygen will become slower. If the cache is too large, memory is wasted. The +# cache size is given by this formula: 2^(16+LOOKUP_CACHE_SIZE). The valid range +# is 0..9, the default is 0, corresponding to a cache size of 2^16=65536 +# symbols. At the end of a run doxygen will report the cache usage and suggest +# the optimal cache size from a speed point of view. +# Minimum value: 0, maximum value: 9, default value: 0. +# +#LOOKUP_CACHE_SIZE = 0 + +#--------------------------------------------------------------------------- +# Build related configuration options +#--------------------------------------------------------------------------- + +# If the EXTRACT_ALL tag is set to YES, doxygen will assume all entities in +# documentation are documented, even if no documentation was available. Private +# class members and static file members will be hidden unless the +# EXTRACT_PRIVATE respectively EXTRACT_STATIC tags are set to YES. +# Note: This will also disable the warnings about undocumented members that are +# normally produced when WARNINGS is set to YES. +# The default value is: NO. +# +#EXTRACT_ALL = NO + +# If the EXTRACT_PRIVATE tag is set to YES, all private members of a class will +# be included in the documentation. +# The default value is: NO. +# +#EXTRACT_PRIVATE = NO + +# If the EXTRACT_PACKAGE tag is set to YES, all members with package or internal +# scope will be included in the documentation. +# The default value is: NO. +# +#EXTRACT_PACKAGE = NO + +# If the EXTRACT_STATIC tag is set to YES, all static members of a file will be +# included in the documentation. +# The default value is: NO. +# +#EXTRACT_STATIC = NO + +# If the EXTRACT_LOCAL_CLASSES tag is set to YES, classes (and structs) defined +# locally in source files will be included in the documentation. If set to NO, +# only classes defined in header files are included. Does not have any effect +# for Java sources. +# The default value is: YES. +# +#EXTRACT_LOCAL_CLASSES = YES + +# This flag is only useful for Objective-C code. If set to YES, local methods, +# which are defined in the implementation section but not in the interface are +# included in the documentation. If set to NO, only methods in the interface are +# included. +# The default value is: NO. +# +#EXTRACT_LOCAL_METHODS = NO + +# If this flag is set to YES, the members of anonymous namespaces will be +# extracted and appear in the documentation as a namespace called +# 'anonymous_namespace{file}', where file will be replaced with the base name of +# the file that contains the anonymous namespace. By default anonymous namespace +# are hidden. +# The default value is: NO. +# +#EXTRACT_ANON_NSPACES = NO + +# If the HIDE_UNDOC_MEMBERS tag is set to YES, doxygen will hide all +# undocumented members inside documented classes or files. If set to NO these +# members will be included in the various overviews, but no documentation +# section is generated. This option has no effect if EXTRACT_ALL is enabled. +# The default value is: NO. +# +#HIDE_UNDOC_MEMBERS = NO + +# If the HIDE_UNDOC_CLASSES tag is set to YES, doxygen will hide all +# undocumented classes that are normally visible in the class hierarchy. If set +# to NO, these classes will be included in the various overviews. This option +# has no effect if EXTRACT_ALL is enabled. +# The default value is: NO. +# +#HIDE_UNDOC_CLASSES = NO + +# If the HIDE_FRIEND_COMPOUNDS tag is set to YES, doxygen will hide all friend +# (class|struct|union) declarations. If set to NO, these declarations will be +# included in the documentation. +# The default value is: NO. +# +#HIDE_FRIEND_COMPOUNDS = NO + +# If the HIDE_IN_BODY_DOCS tag is set to YES, doxygen will hide any +# documentation blocks found inside the body of a function. If set to NO, these +# blocks will be appended to the function's detailed documentation block. +# The default value is: NO. +# +#HIDE_IN_BODY_DOCS = NO + +# The INTERNAL_DOCS tag determines if documentation that is typed after a +# \internal command is included. If the tag is set to NO then the documentation +# will be excluded. Set it to YES to include the internal documentation. +# The default value is: NO. +# +#INTERNAL_DOCS = NO + +# If the CASE_SENSE_NAMES tag is set to NO then doxygen will only generate file +# names in lower-case letters. If set to YES, upper-case letters are also +# allowed. This is useful if you have classes or files whose names only differ +# in case and if your file system supports case sensitive file names. Windows +# and Mac users are advised to set this option to NO. +# The default value is: system dependent. +# +#CASE_SENSE_NAMES = NO + +# If the HIDE_SCOPE_NAMES tag is set to NO then doxygen will show members with +# their full class and namespace scopes in the documentation. If set to YES, the +# scope will be hidden. +# The default value is: NO. +# +#HIDE_SCOPE_NAMES = NO + +# If the HIDE_COMPOUND_REFERENCE tag is set to NO (default) then doxygen will +# append additional text to a page's title, such as Class Reference. If set to +# YES the compound reference will be hidden. +# The default value is: NO. +# +#HIDE_COMPOUND_REFERENCE= NO + +# If the SHOW_INCLUDE_FILES tag is set to YES then doxygen will put a list of +# the files that are included by a file in the documentation of that file. +# The default value is: YES. +# +#SHOW_INCLUDE_FILES = YES + +# If the SHOW_GROUPED_MEMB_INC tag is set to YES then Doxygen will add for each +# grouped member an include statement to the documentation, telling the reader +# which file to include in order to use the member. +# The default value is: NO. +# +#SHOW_GROUPED_MEMB_INC = NO + +# If the FORCE_LOCAL_INCLUDES tag is set to YES then doxygen will list include +# files with double quotes in the documentation rather than with sharp brackets. +# The default value is: NO. +# +#FORCE_LOCAL_INCLUDES = NO + +# If the INLINE_INFO tag is set to YES then a tag [inline] is inserted in the +# documentation for inline members. +# The default value is: YES. +# +#INLINE_INFO = YES + +# If the SORT_MEMBER_DOCS tag is set to YES then doxygen will sort the +# (detailed) documentation of file and class members alphabetically by member +# name. If set to NO, the members will appear in declaration order. +# The default value is: YES. +# +#SORT_MEMBER_DOCS = YES + +# If the SORT_BRIEF_DOCS tag is set to YES then doxygen will sort the brief +# descriptions of file, namespace and class members alphabetically by member +# name. If set to NO, the members will appear in declaration order. Note that +# this will also influence the order of the classes in the class list. +# The default value is: NO. +# +#SORT_BRIEF_DOCS = NO + +# If the SORT_MEMBERS_CTORS_1ST tag is set to YES then doxygen will sort the +# (brief and detailed) documentation of class members so that constructors and +# destructors are listed first. If set to NO the constructors will appear in the +# respective orders defined by SORT_BRIEF_DOCS and SORT_MEMBER_DOCS. +# Note: If SORT_BRIEF_DOCS is set to NO this option is ignored for sorting brief +# member documentation. +# Note: If SORT_MEMBER_DOCS is set to NO this option is ignored for sorting +# detailed member documentation. +# The default value is: NO. +# +#SORT_MEMBERS_CTORS_1ST = NO + +# If the SORT_GROUP_NAMES tag is set to YES then doxygen will sort the hierarchy +# of group names into alphabetical order. If set to NO the group names will +# appear in their defined order. +# The default value is: NO. +# +#SORT_GROUP_NAMES = NO + +# If the SORT_BY_SCOPE_NAME tag is set to YES, the class list will be sorted by +# fully-qualified names, including namespaces. If set to NO, the class list will +# be sorted only by class name, not including the namespace part. +# Note: This option is not very useful if HIDE_SCOPE_NAMES is set to YES. +# Note: This option applies only to the class list, not to the alphabetical +# list. +# The default value is: NO. +# +#SORT_BY_SCOPE_NAME = NO + +# If the STRICT_PROTO_MATCHING option is enabled and doxygen fails to do proper +# type resolution of all parameters of a function it will reject a match between +# the prototype and the implementation of a member function even if there is +# only one candidate or it is obvious which candidate to choose by doing a +# simple string match. By disabling STRICT_PROTO_MATCHING doxygen will still +# accept a match between prototype and implementation in such cases. +# The default value is: NO. +# +#STRICT_PROTO_MATCHING = NO + +# The GENERATE_TODOLIST tag can be used to enable (YES) or disable (NO) the todo +# list. This list is created by putting \todo commands in the documentation. +# The default value is: YES. +# +#GENERATE_TODOLIST = YES + +# The GENERATE_TESTLIST tag can be used to enable (YES) or disable (NO) the test +# list. This list is created by putting \test commands in the documentation. +# The default value is: YES. +# +#GENERATE_TESTLIST = YES + +# The GENERATE_BUGLIST tag can be used to enable (YES) or disable (NO) the bug +# list. This list is created by putting \bug commands in the documentation. +# The default value is: YES. +# +#GENERATE_BUGLIST = YES + +# The GENERATE_DEPRECATEDLIST tag can be used to enable (YES) or disable (NO) +# the deprecated list. This list is created by putting \deprecated commands in +# the documentation. +# The default value is: YES. +# +#GENERATE_DEPRECATEDLIST= YES + +# The ENABLED_SECTIONS tag can be used to enable conditional documentation +# sections, marked by \if ... \endif and \cond +# ... \endcond blocks. +# +#ENABLED_SECTIONS = + +# The MAX_INITIALIZER_LINES tag determines the maximum number of lines that the +# initial value of a variable or macro / define can have for it to appear in the +# documentation. If the initializer consists of more lines than specified here +# it will be hidden. Use a value of 0 to hide initializers completely. The +# appearance of the value of individual variables and macros / defines can be +# controlled using \showinitializer or \hideinitializer command in the +# documentation regardless of this setting. +# Minimum value: 0, maximum value: 10000, default value: 30. +# +#MAX_INITIALIZER_LINES = 30 + +# Set the SHOW_USED_FILES tag to NO to disable the list of files generated at +# the bottom of the documentation of classes and structs. If set to YES, the +# list will mention the files that were used to generate the documentation. +# The default value is: YES. +# +#SHOW_USED_FILES = YES + +# Set the SHOW_FILES tag to NO to disable the generation of the Files page. This +# will remove the Files entry from the Quick Index and from the Folder Tree View +# (if specified). +# The default value is: YES. +# +#SHOW_FILES = YES + +# Set the SHOW_NAMESPACES tag to NO to disable the generation of the Namespaces +# page. This will remove the Namespaces entry from the Quick Index and from the +# Folder Tree View (if specified). +# The default value is: YES. +# +#SHOW_NAMESPACES = YES + +# The FILE_VERSION_FILTER tag can be used to specify a program or script that +# doxygen should invoke to get the current version for each file (typically from +# the version control system). Doxygen will invoke the program by executing (via +# popen()) the command command input-file, where command is the value of the +# FILE_VERSION_FILTER tag, and input-file is the name of an input file provided +# by doxygen. Whatever the program writes to standard output is used as the file +# version. For an example see the documentation. + +FILE_VERSION_FILTER = + +# The LAYOUT_FILE tag can be used to specify a layout file which will be parsed +# by doxygen. The layout file controls the global structure of the generated +# output files in an output format independent way. To create the layout file +# that represents doxygen's defaults, run doxygen with the -l option. You can +# optionally specify a file name after the option, if omitted DoxygenLayout.xml +# will be used as the name of the layout file. +# +# Note that if you run doxygen from a directory containing a file called +# DoxygenLayout.xml, doxygen will parse it automatically even if the LAYOUT_FILE +# tag is left empty. +# +#LAYOUT_FILE = + +# The CITE_BIB_FILES tag can be used to specify one or more bib files containing +# the reference definitions. This must be a list of .bib files. The .bib +# extension is automatically appended if omitted. This requires the bibtex tool +# to be installed. See also http://en.wikipedia.org/wiki/BibTeX for more info. +# For LaTeX the style of the bibliography can be controlled using +# LATEX_BIB_STYLE. To use this feature you need bibtex and perl available in the +# search path. See also \cite for info how to create references. +# +#CITE_BIB_FILES = + +#--------------------------------------------------------------------------- +# Configuration options related to warning and progress messages +#--------------------------------------------------------------------------- + +# The QUIET tag can be used to turn on/off the messages that are generated to +# standard output by doxygen. If QUIET is set to YES this implies that the +# messages are off. +# The default value is: NO. +# +#QUIET = NO + +# The WARNINGS tag can be used to turn on/off the warning messages that are +# generated to standard error (stderr) by doxygen. If WARNINGS is set to YES +# this implies that the warnings are on. +# +# Tip: Turn warnings on while writing the documentation. +# The default value is: YES. +# +#WARNINGS = YES + +# If the WARN_IF_UNDOCUMENTED tag is set to YES then doxygen will generate +# warnings for undocumented members. If EXTRACT_ALL is set to YES then this flag +# will automatically be disabled. +# The default value is: YES. +# +#WARN_IF_UNDOCUMENTED = YES + +# If the WARN_IF_DOC_ERROR tag is set to YES, doxygen will generate warnings for +# potential errors in the documentation, such as not documenting some parameters +# in a documented function, or documenting parameters that don't exist or using +# markup commands wrongly. +# The default value is: YES. +# +#WARN_IF_DOC_ERROR = YES + +# This WARN_NO_PARAMDOC option can be enabled to get warnings for functions that +# are documented, but have no documentation for their parameters or return +# value. If set to NO, doxygen will only warn about wrong or incomplete +# parameter documentation, but not about the absence of documentation. +# The default value is: NO. +# +#WARN_NO_PARAMDOC = NO + +# If the WARN_AS_ERROR tag is set to YES then doxygen will immediately stop when +# a warning is encountered. +# The default value is: NO. +# +#WARN_AS_ERROR = NO + +# The WARN_FORMAT tag determines the format of the warning messages that doxygen +# can produce. The string should contain the $file, $line, and $text tags, which +# will be replaced by the file and line number from which the warning originated +# and the warning text. Optionally the format may contain $version, which will +# be replaced by the version of the file (if it could be obtained via +# FILE_VERSION_FILTER) +# The default value is: $file:$line: $text. +# +#WARN_FORMAT = "$file:$line: $text" + +# The WARN_LOGFILE tag can be used to specify a file to which warning and error +# messages should be written. If left blank the output is written to standard +# error (stderr). +# +#WARN_LOGFILE = + +#--------------------------------------------------------------------------- +# Configuration options related to the input files +#--------------------------------------------------------------------------- + +# The INPUT tag is used to specify the files and/or directories that contain +# documented source files. You may enter file names like myfile.cpp or +# directories like /usr/src/myproject. Separate the files or directories with +# spaces. See also FILE_PATTERNS and EXTENSION_MAPPING +# Note: If this tag is empty the current directory is searched. +# +#INPUT = + +# This tag can be used to specify the character encoding of the source files +# that doxygen parses. Internally doxygen uses the UTF-8 encoding. Doxygen uses +# libiconv (or the iconv built into libc) for the transcoding. See the libiconv +# documentation (see: http://www.gnu.org/software/libiconv) for the list of +# possible encodings. +# The default value is: UTF-8. +# +#INPUT_ENCODING = UTF-8 + +# If the value of the INPUT tag contains directories, you can use the +# FILE_PATTERNS tag to specify one or more wildcard patterns (like *.cpp and +# *.h) to filter out the source-files in the directories. +# +# Note that for custom extensions or not directly supported extensions you also +# need to set EXTENSION_MAPPING for the extension otherwise the files are not +# read by doxygen. +# +# If left blank the following patterns are tested:*.c, *.cc, *.cxx, *.cpp, +# *.c++, *.java, *.ii, *.ixx, *.ipp, *.i++, *.inl, *.idl, *.ddl, *.odl, *.h, +# *.hh, *.hxx, *.hpp, *.h++, *.cs, *.d, *.php, *.php4, *.php5, *.phtml, *.inc, +# *.m, *.markdown, *.md, *.mm, *.dox, *.py, *.pyw, *.f90, *.f95, *.f03, *.f08, +# *.f, *.for, *.tcl, *.vhd, *.vhdl, *.ucf and *.qsf. +# +#FILE_PATTERNS = + +# The RECURSIVE tag can be used to specify whether or not subdirectories should +# be searched for input files as well. +# The default value is: NO. + +RECURSIVE = YES + +# The EXCLUDE tag can be used to specify files and/or directories that should be +# excluded from the INPUT source files. This way you can easily exclude a +# subdirectory from a directory tree whose root is specified with the INPUT tag. +# +# Note that relative paths are relative to the directory from which doxygen is +# run. +# +#EXCLUDE = + +# The EXCLUDE_SYMLINKS tag can be used to select whether or not files or +# directories that are symbolic links (a Unix file system feature) are excluded +# from the input. +# The default value is: NO. +# +#EXCLUDE_SYMLINKS = NO + +# If the value of the INPUT tag contains directories, you can use the +# EXCLUDE_PATTERNS tag to specify one or more wildcard patterns to exclude +# certain files from those directories. +# +# Note that the wildcards are matched against the file with absolute path, so to +# exclude all test directories for example use the pattern */test/* +# +#EXCLUDE_PATTERNS = + +# The EXCLUDE_SYMBOLS tag can be used to specify one or more symbol names +# (namespaces, classes, functions, etc.) that should be excluded from the +# output. The symbol name can be a fully qualified name, a word, or if the +# wildcard * is used, a substring. Examples: ANamespace, AClass, +# AClass::ANamespace, ANamespace::*Test +# +# Note that the wildcards are matched against the file with absolute path, so to +# exclude all test directories use the pattern */test/* +# +#EXCLUDE_SYMBOLS = + +# The EXAMPLE_PATH tag can be used to specify one or more files or directories +# that contain example code fragments that are included (see the \include +# command). +# +#EXAMPLE_PATH = + +# If the value of the EXAMPLE_PATH tag contains directories, you can use the +# EXAMPLE_PATTERNS tag to specify one or more wildcard pattern (like *.cpp and +# *.h) to filter out the source-files in the directories. If left blank all +# files are included. +# +#EXAMPLE_PATTERNS = * + +# If the EXAMPLE_RECURSIVE tag is set to YES then subdirectories will be +# searched for input files to be used with the \include or \dontinclude commands +# irrespective of the value of the RECURSIVE tag. +# The default value is: NO. +# +#EXAMPLE_RECURSIVE = NO + +# The IMAGE_PATH tag can be used to specify one or more files or directories +# that contain images that are to be included in the documentation (see the +# \image command). +# +#IMAGE_PATH = + +# The INPUT_FILTER tag can be used to specify a program that doxygen should +# invoke to filter for each input file. Doxygen will invoke the filter program +# by executing (via popen()) the command: +# +# +# +# where is the value of the INPUT_FILTER tag, and is the +# name of an input file. Doxygen will then use the output that the filter +# program writes to standard output. If FILTER_PATTERNS is specified, this tag +# will be ignored. +# +# Note that the filter must not add or remove lines; it is applied before the +# code is scanned, but not when the output code is generated. If lines are added +# or removed, the anchors will not be placed correctly. +# +# Note that for custom extensions or not directly supported extensions you also +# need to set EXTENSION_MAPPING for the extension otherwise the files are not +# properly processed by doxygen. +# +#INPUT_FILTER = + +# The FILTER_PATTERNS tag can be used to specify filters on a per file pattern +# basis. Doxygen will compare the file name with each pattern and apply the +# filter if there is a match. The filters are a list of the form: pattern=filter +# (like *.cpp=my_cpp_filter). See INPUT_FILTER for further information on how +# filters are used. If the FILTER_PATTERNS tag is empty or if none of the +# patterns match the file name, INPUT_FILTER is applied. +# +# Note that for custom extensions or not directly supported extensions you also +# need to set EXTENSION_MAPPING for the extension otherwise the files are not +# properly processed by doxygen. + +FILTER_PATTERNS = + +# If the FILTER_SOURCE_FILES tag is set to YES, the input filter (if set using +# INPUT_FILTER) will also be used to filter the input files that are used for +# producing the source files to browse (i.e. when SOURCE_BROWSER is set to YES). +# The default value is: NO. + +FILTER_SOURCE_FILES = NO + +# The FILTER_SOURCE_PATTERNS tag can be used to specify source filters per file +# pattern. A pattern will override the setting for FILTER_PATTERN (if any) and +# it is also possible to disable source filtering for a specific pattern using +# *.ext= (so without naming a filter). +# This tag requires that the tag FILTER_SOURCE_FILES is set to YES. + +FILTER_SOURCE_PATTERNS = + +# If the USE_MDFILE_AS_MAINPAGE tag refers to the name of a markdown file that +# is part of the input, its contents will be placed on the main page +# (index.html). This can be useful if you have a project on for instance GitHub +# and want to reuse the introduction page also for the doxygen output. + +USE_MDFILE_AS_MAINPAGE = + +#--------------------------------------------------------------------------- +# Configuration options related to source browsing +#--------------------------------------------------------------------------- + +# If the SOURCE_BROWSER tag is set to YES then a list of source files will be +# generated. Documented entities will be cross-referenced with these sources. +# +# Note: To get rid of all source code in the generated output, make sure that +# also VERBATIM_HEADERS is set to NO. +# The default value is: NO. + +SOURCE_BROWSER = YES + +# Setting the INLINE_SOURCES tag to YES will include the body of functions, +# classes and enums directly into the documentation. +# The default value is: NO. + +INLINE_SOURCES = NO + +# Setting the STRIP_CODE_COMMENTS tag to YES will instruct doxygen to hide any +# special comment blocks from generated source code fragments. Normal C, C++ and +# Fortran comments will always remain visible. +# The default value is: YES. + +STRIP_CODE_COMMENTS = NO + +# If the REFERENCED_BY_RELATION tag is set to YES then for each documented +# function all documented functions referencing it will be listed. +# The default value is: NO. + +REFERENCED_BY_RELATION = NO + +# If the REFERENCES_RELATION tag is set to YES then for each documented function +# all documented entities called/used by that function will be listed. +# The default value is: NO. + +REFERENCES_RELATION = NO + +# If the REFERENCES_LINK_SOURCE tag is set to YES and SOURCE_BROWSER tag is set +# to YES then the hyperlinks from functions in REFERENCES_RELATION and +# REFERENCED_BY_RELATION lists will link to the source code. Otherwise they will +# link to the documentation. +# The default value is: YES. + +REFERENCES_LINK_SOURCE = YES + +# If SOURCE_TOOLTIPS is enabled (the default) then hovering a hyperlink in the +# source code will show a tooltip with additional information such as prototype, +# brief description and links to the definition and documentation. Since this +# will make the HTML file larger and loading of large files a bit slower, you +# can opt to disable this feature. +# The default value is: YES. +# This tag requires that the tag SOURCE_BROWSER is set to YES. + +SOURCE_TOOLTIPS = YES + +# If the USE_HTAGS tag is set to YES then the references to source code will +# point to the HTML generated by the htags(1) tool instead of doxygen built-in +# source browser. The htags tool is part of GNU's global source tagging system +# (see http://www.gnu.org/software/global/global.html). You will need version +# 4.8.6 or higher. +# +# To use it do the following: +# - Install the latest version of global +# - Enable SOURCE_BROWSER and USE_HTAGS in the config file +# - Make sure the INPUT points to the root of the source tree +# - Run doxygen as normal +# +# Doxygen will invoke htags (and that will in turn invoke gtags), so these +# tools must be available from the command line (i.e. in the search path). +# +# The result: instead of the source browser generated by doxygen, the links to +# source code will now point to the output of htags. +# The default value is: NO. +# This tag requires that the tag SOURCE_BROWSER is set to YES. + +USE_HTAGS = NO + +# If the VERBATIM_HEADERS tag is set the YES then doxygen will generate a +# verbatim copy of the header file for each class for which an include is +# specified. Set to NO to disable this. +# See also: Section \class. +# The default value is: YES. + +VERBATIM_HEADERS = YES + +# If the CLANG_ASSISTED_PARSING tag is set to YES then doxygen will use the +# clang parser (see: http://clang.llvm.org/) for more accurate parsing at the +# cost of reduced performance. This can be particularly helpful with template +# rich C++ code for which doxygen's built-in parser lacks the necessary type +# information. +# Note: The availability of this option depends on whether or not doxygen was +# generated with the -Duse-libclang=ON option for CMake. +# The default value is: NO. + +CLANG_ASSISTED_PARSING = NO + +# If clang assisted parsing is enabled you can provide the compiler with command +# line options that you would normally use when invoking the compiler. Note that +# the include paths will already be set by doxygen for the files and directories +# specified with INPUT and INCLUDE_PATH. +# This tag requires that the tag CLANG_ASSISTED_PARSING is set to YES. + +CLANG_OPTIONS = + +#--------------------------------------------------------------------------- +# Configuration options related to the alphabetical class index +#--------------------------------------------------------------------------- + +# If the ALPHABETICAL_INDEX tag is set to YES, an alphabetical index of all +# compounds will be generated. Enable this if the project contains a lot of +# classes, structs, unions or interfaces. +# The default value is: YES. + +ALPHABETICAL_INDEX = YES + +# The COLS_IN_ALPHA_INDEX tag can be used to specify the number of columns in +# which the alphabetical index list will be split. +# Minimum value: 1, maximum value: 20, default value: 5. +# This tag requires that the tag ALPHABETICAL_INDEX is set to YES. + +COLS_IN_ALPHA_INDEX = 5 + +# In case all classes in a project start with a common prefix, all classes will +# be put under the same header in the alphabetical index. The IGNORE_PREFIX tag +# can be used to specify a prefix (or a list of prefixes) that should be ignored +# while generating the index headers. +# This tag requires that the tag ALPHABETICAL_INDEX is set to YES. + +IGNORE_PREFIX = + +#--------------------------------------------------------------------------- +# Configuration options related to the HTML output +#--------------------------------------------------------------------------- + +# If the GENERATE_HTML tag is set to YES, doxygen will generate HTML output +# The default value is: YES. + +GENERATE_HTML = YES + +# The HTML_OUTPUT tag is used to specify where the HTML docs will be put. If a +# relative path is entered the value of OUTPUT_DIRECTORY will be put in front of +# it. +# The default directory is: html. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_OUTPUT = html + +# The HTML_FILE_EXTENSION tag can be used to specify the file extension for each +# generated HTML page (for example: .htm, .php, .asp). +# The default value is: .html. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_FILE_EXTENSION = .html + +# The HTML_HEADER tag can be used to specify a user-defined HTML header file for +# each generated HTML page. If the tag is left blank doxygen will generate a +# standard header. +# +# To get valid HTML the header file that includes any scripts and style sheets +# that doxygen needs, which is dependent on the configuration options used (e.g. +# the setting GENERATE_TREEVIEW). It is highly recommended to start with a +# default header using +# doxygen -w html new_header.html new_footer.html new_stylesheet.css +# YourConfigFile +# and then modify the file new_header.html. See also section "Doxygen usage" +# for information on how to generate the default header that doxygen normally +# uses. +# Note: The header is subject to change so you typically have to regenerate the +# default header when upgrading to a newer version of doxygen. For a description +# of the possible markers and block names see the documentation. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_HEADER = + +# The HTML_FOOTER tag can be used to specify a user-defined HTML footer for each +# generated HTML page. If the tag is left blank doxygen will generate a standard +# footer. See HTML_HEADER for more information on how to generate a default +# footer and what special commands can be used inside the footer. See also +# section "Doxygen usage" for information on how to generate the default footer +# that doxygen normally uses. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_FOOTER = + +# The HTML_STYLESHEET tag can be used to specify a user-defined cascading style +# sheet that is used by each HTML page. It can be used to fine-tune the look of +# the HTML output. If left blank doxygen will generate a default style sheet. +# See also section "Doxygen usage" for information on how to generate the style +# sheet that doxygen normally uses. +# Note: It is recommended to use HTML_EXTRA_STYLESHEET instead of this tag, as +# it is more robust and this tag (HTML_STYLESHEET) will in the future become +# obsolete. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_STYLESHEET = + +# The HTML_EXTRA_STYLESHEET tag can be used to specify additional user-defined +# cascading style sheets that are included after the standard style sheets +# created by doxygen. Using this option one can overrule certain style aspects. +# This is preferred over using HTML_STYLESHEET since it does not replace the +# standard style sheet and is therefore more robust against future updates. +# Doxygen will copy the style sheet files to the output directory. +# Note: The order of the extra style sheet files is of importance (e.g. the last +# style sheet in the list overrules the setting of the previous ones in the +# list). For an example see the documentation. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_EXTRA_STYLESHEET = + +# The HTML_EXTRA_FILES tag can be used to specify one or more extra images or +# other source files which should be copied to the HTML output directory. Note +# that these files will be copied to the base HTML output directory. Use the +# $relpath^ marker in the HTML_HEADER and/or HTML_FOOTER files to load these +# files. In the HTML_STYLESHEET file, use the file name only. Also note that the +# files will be copied as-is; there are no commands or markers available. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_EXTRA_FILES = + +# The HTML_COLORSTYLE_HUE tag controls the color of the HTML output. Doxygen +# will adjust the colors in the style sheet and background images according to +# this color. Hue is specified as an angle on a colorwheel, see +# http://en.wikipedia.org/wiki/Hue for more information. For instance the value +# 0 represents red, 60 is yellow, 120 is green, 180 is cyan, 240 is blue, 300 +# purple, and 360 is red again. +# Minimum value: 0, maximum value: 359, default value: 220. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_COLORSTYLE_HUE = 220 + +# The HTML_COLORSTYLE_SAT tag controls the purity (or saturation) of the colors +# in the HTML output. For a value of 0 the output will use grayscales only. A +# value of 255 will produce the most vivid colors. +# Minimum value: 0, maximum value: 255, default value: 100. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_COLORSTYLE_SAT = 100 + +# The HTML_COLORSTYLE_GAMMA tag controls the gamma correction applied to the +# luminance component of the colors in the HTML output. Values below 100 +# gradually make the output lighter, whereas values above 100 make the output +# darker. The value divided by 100 is the actual gamma applied, so 80 represents +# a gamma of 0.8, The value 220 represents a gamma of 2.2, and 100 does not +# change the gamma. +# Minimum value: 40, maximum value: 240, default value: 80. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_COLORSTYLE_GAMMA = 80 + +# If the HTML_TIMESTAMP tag is set to YES then the footer of each generated HTML +# page will contain the date and time when the page was generated. Setting this +# to YES can help to show when doxygen was last run and thus if the +# documentation is up to date. +# The default value is: NO. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_TIMESTAMP = NO + +# If the HTML_DYNAMIC_SECTIONS tag is set to YES then the generated HTML +# documentation will contain sections that can be hidden and shown after the +# page has loaded. +# The default value is: NO. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_DYNAMIC_SECTIONS = NO + +# With HTML_INDEX_NUM_ENTRIES one can control the preferred number of entries +# shown in the various tree structured indices initially; the user can expand +# and collapse entries dynamically later on. Doxygen will expand the tree to +# such a level that at most the specified number of entries are visible (unless +# a fully collapsed tree already exceeds this amount). So setting the number of +# entries 1 will produce a full collapsed tree by default. 0 is a special value +# representing an infinite number of entries and will result in a full expanded +# tree by default. +# Minimum value: 0, maximum value: 9999, default value: 100. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_INDEX_NUM_ENTRIES = 100 + +# If the GENERATE_DOCSET tag is set to YES, additional index files will be +# generated that can be used as input for Apple's Xcode 3 integrated development +# environment (see: http://developer.apple.com/tools/xcode/), introduced with +# OSX 10.5 (Leopard). To create a documentation set, doxygen will generate a +# Makefile in the HTML output directory. Running make will produce the docset in +# that directory and running make install will install the docset in +# ~/Library/Developer/Shared/Documentation/DocSets so that Xcode will find it at +# startup. See http://developer.apple.com/tools/creatingdocsetswithdoxygen.html +# for more information. +# The default value is: NO. +# This tag requires that the tag GENERATE_HTML is set to YES. + +GENERATE_DOCSET = NO + +# This tag determines the name of the docset feed. A documentation feed provides +# an umbrella under which multiple documentation sets from a single provider +# (such as a company or product suite) can be grouped. +# The default value is: Doxygen generated docs. +# This tag requires that the tag GENERATE_DOCSET is set to YES. + +DOCSET_FEEDNAME = "Doxygen generated docs" + +# This tag specifies a string that should uniquely identify the documentation +# set bundle. This should be a reverse domain-name style string, e.g. +# com.mycompany.MyDocSet. Doxygen will append .docset to the name. +# The default value is: org.doxygen.Project. +# This tag requires that the tag GENERATE_DOCSET is set to YES. + +DOCSET_BUNDLE_ID = org.doxygen.Project + +# The DOCSET_PUBLISHER_ID tag specifies a string that should uniquely identify +# the documentation publisher. This should be a reverse domain-name style +# string, e.g. com.mycompany.MyDocSet.documentation. +# The default value is: org.doxygen.Publisher. +# This tag requires that the tag GENERATE_DOCSET is set to YES. + +DOCSET_PUBLISHER_ID = org.doxygen.Publisher + +# The DOCSET_PUBLISHER_NAME tag identifies the documentation publisher. +# The default value is: Publisher. +# This tag requires that the tag GENERATE_DOCSET is set to YES. + +DOCSET_PUBLISHER_NAME = Publisher + +# If the GENERATE_HTMLHELP tag is set to YES then doxygen generates three +# additional HTML index files: index.hhp, index.hhc, and index.hhk. The +# index.hhp is a project file that can be read by Microsoft's HTML Help Workshop +# (see: http://www.microsoft.com/en-us/download/details.aspx?id=21138) on +# Windows. +# +# The HTML Help Workshop contains a compiler that can convert all HTML output +# generated by doxygen into a single compiled HTML file (.chm). Compiled HTML +# files are now used as the Windows 98 help format, and will replace the old +# Windows help format (.hlp) on all Windows platforms in the future. Compressed +# HTML files also contain an index, a table of contents, and you can search for +# words in the documentation. The HTML workshop also contains a viewer for +# compressed HTML files. +# The default value is: NO. +# This tag requires that the tag GENERATE_HTML is set to YES. + +GENERATE_HTMLHELP = YES + +# The CHM_FILE tag can be used to specify the file name of the resulting .chm +# file. You can add a path in front of the file if the result should not be +# written to the html output directory. +# This tag requires that the tag GENERATE_HTMLHELP is set to YES. + +CHM_FILE = + +# The HHC_LOCATION tag can be used to specify the location (absolute path +# including file name) of the HTML help compiler (hhc.exe). If non-empty, +# doxygen will try to run the HTML help compiler on the generated index.hhp. +# The file has to be specified with full path. +# This tag requires that the tag GENERATE_HTMLHELP is set to YES. + +HHC_LOCATION = + +# The GENERATE_CHI flag controls if a separate .chi index file is generated +# (YES) or that it should be included in the master .chm file (NO). +# The default value is: NO. +# This tag requires that the tag GENERATE_HTMLHELP is set to YES. + +GENERATE_CHI = NO + +# The CHM_INDEX_ENCODING is used to encode HtmlHelp index (hhk), content (hhc) +# and project file content. +# This tag requires that the tag GENERATE_HTMLHELP is set to YES. + +CHM_INDEX_ENCODING = + +# The BINARY_TOC flag controls whether a binary table of contents is generated +# (YES) or a normal table of contents (NO) in the .chm file. Furthermore it +# enables the Previous and Next buttons. +# The default value is: NO. +# This tag requires that the tag GENERATE_HTMLHELP is set to YES. + +BINARY_TOC = NO + +# The TOC_EXPAND flag can be set to YES to add extra items for group members to +# the table of contents of the HTML help documentation and to the tree view. +# The default value is: NO. +# This tag requires that the tag GENERATE_HTMLHELP is set to YES. + +TOC_EXPAND = NO + +# If the GENERATE_QHP tag is set to YES and both QHP_NAMESPACE and +# QHP_VIRTUAL_FOLDER are set, an additional index file will be generated that +# can be used as input for Qt's qhelpgenerator to generate a Qt Compressed Help +# (.qch) of the generated HTML documentation. +# The default value is: NO. +# This tag requires that the tag GENERATE_HTML is set to YES. + +GENERATE_QHP = NO + +# If the QHG_LOCATION tag is specified, the QCH_FILE tag can be used to specify +# the file name of the resulting .qch file. The path specified is relative to +# the HTML output folder. +# This tag requires that the tag GENERATE_QHP is set to YES. + +QCH_FILE = + +# The QHP_NAMESPACE tag specifies the namespace to use when generating Qt Help +# Project output. For more information please see Qt Help Project / Namespace +# (see: http://qt-project.org/doc/qt-4.8/qthelpproject.html#namespace). +# The default value is: org.doxygen.Project. +# This tag requires that the tag GENERATE_QHP is set to YES. + +QHP_NAMESPACE = org.doxygen.Project + +# The QHP_VIRTUAL_FOLDER tag specifies the namespace to use when generating Qt +# Help Project output. For more information please see Qt Help Project / Virtual +# Folders (see: http://qt-project.org/doc/qt-4.8/qthelpproject.html#virtual- +# folders). +# The default value is: doc. +# This tag requires that the tag GENERATE_QHP is set to YES. + +QHP_VIRTUAL_FOLDER = doc + +# If the QHP_CUST_FILTER_NAME tag is set, it specifies the name of a custom +# filter to add. For more information please see Qt Help Project / Custom +# Filters (see: http://qt-project.org/doc/qt-4.8/qthelpproject.html#custom- +# filters). +# This tag requires that the tag GENERATE_QHP is set to YES. + +QHP_CUST_FILTER_NAME = + +# The QHP_CUST_FILTER_ATTRS tag specifies the list of the attributes of the +# custom filter to add. For more information please see Qt Help Project / Custom +# Filters (see: http://qt-project.org/doc/qt-4.8/qthelpproject.html#custom- +# filters). +# This tag requires that the tag GENERATE_QHP is set to YES. + +QHP_CUST_FILTER_ATTRS = + +# The QHP_SECT_FILTER_ATTRS tag specifies the list of the attributes this +# project's filter section matches. Qt Help Project / Filter Attributes (see: +# http://qt-project.org/doc/qt-4.8/qthelpproject.html#filter-attributes). +# This tag requires that the tag GENERATE_QHP is set to YES. + +QHP_SECT_FILTER_ATTRS = + +# The QHG_LOCATION tag can be used to specify the location of Qt's +# qhelpgenerator. If non-empty doxygen will try to run qhelpgenerator on the +# generated .qhp file. +# This tag requires that the tag GENERATE_QHP is set to YES. + +QHG_LOCATION = + +# If the GENERATE_ECLIPSEHELP tag is set to YES, additional index files will be +# generated, together with the HTML files, they form an Eclipse help plugin. To +# install this plugin and make it available under the help contents menu in +# Eclipse, the contents of the directory containing the HTML and XML files needs +# to be copied into the plugins directory of eclipse. The name of the directory +# within the plugins directory should be the same as the ECLIPSE_DOC_ID value. +# After copying Eclipse needs to be restarted before the help appears. +# The default value is: NO. +# This tag requires that the tag GENERATE_HTML is set to YES. + +GENERATE_ECLIPSEHELP = NO + +# A unique identifier for the Eclipse help plugin. When installing the plugin +# the directory name containing the HTML and XML files should also have this +# name. Each documentation set should have its own identifier. +# The default value is: org.doxygen.Project. +# This tag requires that the tag GENERATE_ECLIPSEHELP is set to YES. + +ECLIPSE_DOC_ID = org.doxygen.Project + +# If you want full control over the layout of the generated HTML pages it might +# be necessary to disable the index and replace it with your own. The +# DISABLE_INDEX tag can be used to turn on/off the condensed index (tabs) at top +# of each HTML page. A value of NO enables the index and the value YES disables +# it. Since the tabs in the index contain the same information as the navigation +# tree, you can set this option to YES if you also set GENERATE_TREEVIEW to YES. +# The default value is: NO. +# This tag requires that the tag GENERATE_HTML is set to YES. + +DISABLE_INDEX = YES + +# The GENERATE_TREEVIEW tag is used to specify whether a tree-like index +# structure should be generated to display hierarchical information. If the tag +# value is set to YES, a side panel will be generated containing a tree-like +# index structure (just like the one that is generated for HTML Help). For this +# to work a browser that supports JavaScript, DHTML, CSS and frames is required +# (i.e. any modern browser). Windows users are probably better off using the +# HTML help feature. Via custom style sheets (see HTML_EXTRA_STYLESHEET) one can +# further fine-tune the look of the index. As an example, the default style +# sheet generated by doxygen has an example that shows how to put an image at +# the root of the tree instead of the PROJECT_NAME. Since the tree basically has +# the same information as the tab index, you could consider setting +# DISABLE_INDEX to YES when enabling this option. +# The default value is: NO. +# This tag requires that the tag GENERATE_HTML is set to YES. + +GENERATE_TREEVIEW = YES + +# The ENUM_VALUES_PER_LINE tag can be used to set the number of enum values that +# doxygen will group on one line in the generated HTML documentation. +# +# Note that a value of 0 will completely suppress the enum values from appearing +# in the overview section. +# Minimum value: 0, maximum value: 20, default value: 4. +# This tag requires that the tag GENERATE_HTML is set to YES. + +ENUM_VALUES_PER_LINE = 4 + +# If the treeview is enabled (see GENERATE_TREEVIEW) then this tag can be used +# to set the initial width (in pixels) of the frame in which the tree is shown. +# Minimum value: 0, maximum value: 1500, default value: 250. +# This tag requires that the tag GENERATE_HTML is set to YES. + +TREEVIEW_WIDTH = 250 + +# If the EXT_LINKS_IN_WINDOW option is set to YES, doxygen will open links to +# external symbols imported via tag files in a separate window. +# The default value is: NO. +# This tag requires that the tag GENERATE_HTML is set to YES. + +EXT_LINKS_IN_WINDOW = NO + +# Use this tag to change the font size of LaTeX formulas included as images in +# the HTML documentation. When you change the font size after a successful +# doxygen run you need to manually remove any form_*.png images from the HTML +# output directory to force them to be regenerated. +# Minimum value: 8, maximum value: 50, default value: 10. +# This tag requires that the tag GENERATE_HTML is set to YES. + +FORMULA_FONTSIZE = 10 + +# Use the FORMULA_TRANPARENT tag to determine whether or not the images +# generated for formulas are transparent PNGs. Transparent PNGs are not +# supported properly for IE 6.0, but are supported on all modern browsers. +# +# Note that when changing this option you need to delete any form_*.png files in +# the HTML output directory before the changes have effect. +# The default value is: YES. +# This tag requires that the tag GENERATE_HTML is set to YES. + +FORMULA_TRANSPARENT = YES + +# Enable the USE_MATHJAX option to render LaTeX formulas using MathJax (see +# http://www.mathjax.org) which uses client side Javascript for the rendering +# instead of using pre-rendered bitmaps. Use this if you do not have LaTeX +# installed or if you want to formulas look prettier in the HTML output. When +# enabled you may also need to install MathJax separately and configure the path +# to it using the MATHJAX_RELPATH option. +# The default value is: NO. +# This tag requires that the tag GENERATE_HTML is set to YES. + +USE_MATHJAX = NO + +# When MathJax is enabled you can set the default output format to be used for +# the MathJax output. See the MathJax site (see: +# http://docs.mathjax.org/en/latest/output.html) for more details. +# Possible values are: HTML-CSS (which is slower, but has the best +# compatibility), NativeMML (i.e. MathML) and SVG. +# The default value is: HTML-CSS. +# This tag requires that the tag USE_MATHJAX is set to YES. + +MATHJAX_FORMAT = HTML-CSS + +# When MathJax is enabled you need to specify the location relative to the HTML +# output directory using the MATHJAX_RELPATH option. The destination directory +# should contain the MathJax.js script. For instance, if the mathjax directory +# is located at the same level as the HTML output directory, then +# MATHJAX_RELPATH should be ../mathjax. The default value points to the MathJax +# Content Delivery Network so you can quickly see the result without installing +# MathJax. However, it is strongly recommended to install a local copy of +# MathJax from http://www.mathjax.org before deployment. +# The default value is: http://cdn.mathjax.org/mathjax/latest. +# This tag requires that the tag USE_MATHJAX is set to YES. + +MATHJAX_RELPATH = http://cdn.mathjax.org/mathjax/latest + +# The MATHJAX_EXTENSIONS tag can be used to specify one or more MathJax +# extension names that should be enabled during MathJax rendering. For example +# MATHJAX_EXTENSIONS = TeX/AMSmath TeX/AMSsymbols +# This tag requires that the tag USE_MATHJAX is set to YES. + +MATHJAX_EXTENSIONS = + +# The MATHJAX_CODEFILE tag can be used to specify a file with javascript pieces +# of code that will be used on startup of the MathJax code. See the MathJax site +# (see: http://docs.mathjax.org/en/latest/output.html) for more details. For an +# example see the documentation. +# This tag requires that the tag USE_MATHJAX is set to YES. + +MATHJAX_CODEFILE = + +# When the SEARCHENGINE tag is enabled doxygen will generate a search box for +# the HTML output. The underlying search engine uses javascript and DHTML and +# should work on any modern browser. Note that when using HTML help +# (GENERATE_HTMLHELP), Qt help (GENERATE_QHP), or docsets (GENERATE_DOCSET) +# there is already a search function so this one should typically be disabled. +# For large projects the javascript based search engine can be slow, then +# enabling SERVER_BASED_SEARCH may provide a better solution. It is possible to +# search using the keyboard; to jump to the search box use + S +# (what the is depends on the OS and browser, but it is typically +# , /

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

- * Note that this method will honour some environment variables, the 3 most - * important ones probably being: - *

- */ - 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, configDir, "tmp"); - 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, configDir, - "tmp-reader"); - coverDir = getFile(Config.DEFAULT_COVERS_DIR, 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 BasicLibrary} for the program. - * - * @return the {@link BasicLibrary} - */ - public BasicLibrary getLibrary() { - if (lib == null) { - throw new NullPointerException("We don't have a library to return"); - } - - return lib; - } - - /** - * Change the default {@link BasicLibrary} for this program. - *

- * Be careful. - * - * @param lib - * the new {@link BasicLibrary} - */ - public void setLibrary(BasicLibrary lib) { - this.lib = 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) { - host = host.replace("fanfix://", ""); - host = host.replace("http://", ""); - host = host.replace("https://", ""); - host = host.replaceAll("[^a-zA-Z0-9=+.-]", "_"); - - 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, - "fanfix://localhost"); - port = config.getInteger(Config.REMOTE_LIBRARY_PORT, -1); - String key = config.getString(Config.REMOTE_LIBRARY_KEY); - - if (!host.startsWith("http://") && !host.startsWith("https://") - && !host.startsWith("fanfix://")) { - host = "fanfix://" + host; - } - - tracer.trace("Selecting remote library " + host + ":" + port); - - if (host.startsWith("fanfix://")) { - lib = new RemoteLibrary(key, host, port); - } else { - lib = new WebLibrary(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 = getFile(Config.LIBRARY_DIR, configDir, "$HOME/Books") - .getPath(); - } - try { - lib = new LocalLibrary(new File(libDir), config); - } catch (Exception e) { - tracer.error(new IOException( - "Cannot create library for directory: " + 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 configDir - * the directory to use as base if not absolute - * @param def - * the default value if none (will be configDir-rooted if needed) - * @return the path, with expanded "$HOME" if needed - */ - protected File getFile(Config id, String configDir, String def) { - String path = config.getString(id, def); - return getFile(path, configDir); - } - - /** - * Return a path, but support the special $HOME variable. - * - * @param id - * the key for the path, which may contain "$HOME" - * @param configDir - * the directory to use as base if not absolute - * @param def - * the default value if none (will be configDir-rooted if needed) - * @return the path, with expanded "$HOME" if needed - */ - protected File getFile(UiConfig id, String configDir, String def) { - String path = uiconfig.getString(id, def); - return getFile(path, configDir); - } - - /** - * Return a path, but support the special $HOME variable. - * - * @param path - * the path, which may contain "$HOME" - * @param configDir - * the directory to use as base if not absolute - * @return the path, with expanded "$HOME" if needed - */ - protected File getFile(String path, String configDir) { - File file = null; - if (path != null && !path.isEmpty()) { - path = path.replace('/', File.separatorChar); - if (path.contains("$HOME")) { - path = path.replace("$HOME", getHome()); - } else if (!path.startsWith("/")) { - path = new File(configDir, path).getPath(); - } - - 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/LICENSE b/LICENSE index 9cecc1d..f288702 100644 --- a/LICENSE +++ b/LICENSE @@ -1,7 +1,7 @@ GNU GENERAL PUBLIC LICENSE Version 3, 29 June 2007 - Copyright (C) 2007 Free Software Foundation, Inc. + Copyright (C) 2007 Free Software Foundation, Inc. Everyone is permitted to copy and distribute verbatim copies of this license document, but changing it is not allowed. @@ -631,8 +631,8 @@ to attach them to the start of each source file to most effectively state the exclusion of warranty; and each file should have at least the "copyright" line and a pointer to where the full notice is found. - {one line to give the program's name and a brief idea of what it does.} - Copyright (C) {year} {name of author} + + Copyright (C) This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -645,14 +645,14 @@ the "copyright" line and a pointer to where the full notice is found. GNU General Public License for more details. You should have received a copy of the GNU General Public License - along with this program. If not, see . + along with this program. If not, see . Also add information on how to contact you by electronic and paper mail. If the program does terminal interaction, make it output a short notice like this when it starts in an interactive mode: - {project} Copyright (C) {year} {fullname} + Copyright (C) This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. This is free software, and you are welcome to redistribute it under certain conditions; type `show c' for details. @@ -664,11 +664,11 @@ might be different; for a GUI interface, you would use an "about box". You should also get your employer (if you work as a programmer) or school, if any, to sign a "copyright disclaimer" for the program, if necessary. For more information on this, and how to apply and follow the GNU GPL, see -. +. The GNU General Public License does not permit incorporating your program into proprietary programs. If your program is a subroutine library, you may consider it more useful to permit linking proprietary applications with the library. If this is what you want to do, use the GNU Lesser General Public License instead of this License. But first, please read -. +. diff --git a/Main.java b/Main.java deleted file mode 100644 index 3536544..0000000 --- a/Main.java +++ /dev/null @@ -1,1141 +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.library.WebLibrary; -import be.nikiroo.fanfix.library.WebLibraryServer; -import be.nikiroo.fanfix.output.BasicOutput; -import be.nikiroo.fanfix.output.BasicOutput.OutputType; -import be.nikiroo.fanfix.reader.BasicReader; -import be.nikiroo.fanfix.reader.CliReader; -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.VersionCheck; - -/** - * Main program entry point. - * - * @author niki - */ -public class Main { - private enum MainAction { - IMPORT, EXPORT, CONVERT, READ, READ_URL, LIST, HELP, 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) { - new Main().start(args); - } - - /** - * Start the default handling for the application. - *

- * If specific actions were asked (with correct parameters), they will be - * forwarded to the different protected methods that you can override. - *

- * At the end of the method, {@link Main#exit(int)} will be called; by - * default, it calls {@link System#exit(int)} if the status is not 0. - * - * @param args - * the arguments received from the system - */ - public void start(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++) { - if (args[i] == null) - continue; - - // 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 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; - if (host.startsWith("http://") - || host.startsWith("https://")) { - lib = new WebLibrary(key, host, port); - } else { - lib = new RemoteLibrary(key, host, port); - } - - lib = new CacheLibrary( - Instance.getInstance().getRemoteDir(host), lib, - Instance.getInstance().getUiConfig()); - - Instance.getInstance().setLibrary(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 = checkUpdates(); - - if (exitCode == 0) { - switch (action) { - case IMPORT: - if (updates != null) { - // we consider it read - Instance.getInstance().setVersionChecked(); - } - - try { - exitCode = imprt(BasicReader.getUrl(urlString), pg); - } catch (MalformedURLException e) { - Instance.getInstance().getTraceHandler().error(e); - exitCode = 1; - } - - break; - case EXPORT: - if (updates != null) { - // we consider it read - Instance.getInstance().setVersionChecked(); - } - - OutputType exportType = OutputType.valueOfNullOkUC(sourceString, null); - if (exportType == null) { - Instance.getInstance().getTraceHandler().error(new Exception(trans(StringId.OUTPUT_DESC, sourceString))); - exitCode = 1; - break; - } - - exitCode = export(luid, exportType, target, pg); - - break; - case CONVERT: - if (updates != null) { - // we consider it read - Instance.getInstance().setVersionChecked(); - } - - OutputType convertType = OutputType.valueOfAllOkUC(sourceString, null); - if (convertType == null) { - Instance.getInstance().getTraceHandler() - .error(new IOException(trans(StringId.ERR_BAD_OUTPUT_TYPE, sourceString))); - - exitCode = 2; - break; - } - - exitCode = convert(urlString, convertType, target, - plusInfo == null ? false : plusInfo, pg); - - break; - case LIST: - 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 (luid == null || luid.isEmpty()) { - syntax(false); - exitCode = 255; - break; - } - - try { - Integer chap = null; - if (chapString != null) { - try { - chap = Integer.parseInt(chapString); - } catch (NumberFormatException e) { - Instance.getInstance().getTraceHandler().error(new IOException( - "Chapter number cannot be parsed: " + chapString, e)); - exitCode = 2; - break; - } - } - - BasicLibrary lib = Instance.getInstance().getLibrary(); - exitCode = read(lib.getStory(luid, null), chap); - } catch (IOException e) { - Instance.getInstance().getTraceHandler() - .error(new IOException("Failed to read book", e)); - exitCode = 2; - } - - break; - case READ_URL: - if (urlString == null || urlString.isEmpty()) { - syntax(false); - exitCode = 255; - break; - } - - try { - Integer chap = null; - if (chapString != null) { - try { - chap = Integer.parseInt(chapString); - } catch (NumberFormatException e) { - Instance.getInstance().getTraceHandler().error(new IOException( - "Chapter number cannot be parsed: " + chapString, e)); - exitCode = 2; - break; - } - } - - BasicSupport support = BasicSupport - .getSupport(BasicReader.getUrl(urlString)); - if (support == null) { - Instance.getInstance().getTraceHandler() - .error("URL not supported: " + urlString); - exitCode = 2; - break; - } - - exitCode = read(support.process(null), chap); - } catch (IOException e) { - Instance.getInstance().getTraceHandler() - .error(new IOException("Failed to read book", e)); - exitCode = 2; - } - - 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 (searchOn == null) { - try { - search(); - } catch (IOException e) { - Instance.getInstance().getTraceHandler().error(e); - exitCode = 1; - } - } else if (search != null) { - try { - searchKeywords(searchOn, search, page, item); - } catch (IOException e) { - Instance.getInstance().getTraceHandler().error(e); - exitCode = 20; - } - } else { - exitCode = 255; - } - - 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; - } - - try { - searchTags(searchOn, page, item, - tags.toArray(new Integer[] {})); - } catch (IOException e) { - Instance.getInstance().getTraceHandler().error(e); - } - - break; - case HELP: - syntax(true); - exitCode = 0; - break; - case VERSION: - if (updates != null) { - // we consider it read - Instance.getInstance().setVersionChecked(); - } - - System.out - .println(String.format("Fanfix version %s" - + "%nhttps://github.com/nikiroo/fanfix/" - + "%n\tWritten by Nikiroo", - Version.getCurrentVersion())); - break; - case START: - try { - start(); - } catch (IOException e) { - Instance.getInstance().getTraceHandler().error(e); - exitCode = 66; - } - break; - case SERVER: - try { - startServer(); - } catch (IOException e) { - Instance.getInstance().getTraceHandler().error(e); - } - - break; - case STOP_SERVER: - // Can be given via "--remote XX XX XX" - if (key == null) { - key = Instance.getInstance().getConfig() - .getString(Config.SERVER_KEY); - - // If a subkey in RW mode exists, use it - for (String subkey : Instance.getInstance().getConfig() - .getList(Config.SERVER_ALLOWED_SUBKEYS, - new ArrayList())) { - if ((subkey + "|").contains("|rw|")) { - key = key + "|" + subkey; - break; - } - } - } - - if (port == null) { - port = Instance.getInstance().getConfig().getInteger(Config.SERVER_PORT); - } - - if (host == null) { - String mode = Instance.getInstance().getConfig() - .getString(Config.SERVER_MODE, "fanfix"); - if ("http".equals(mode)) { - host = "http://localhost"; - } else if ("https".equals(mode)) { - host = "https://localhost"; - } else if ("fanfix".equals(mode)) { - host = "fanfix://localhost"; - } - } - - if (port == null) { - System.err.println("No port given nor configured in the config file"); - exitCode = 15; - break; - } - try { - stopServer(key, host, port); - } 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); - } - - exit(exitCode); - } - - /** - * A normal invocation of the program (without parameters or at least - * without "action" parameters). - *

- * You will probably want to override that one if you offer a user - * interface. - * - * @throws IOException - * in case of I/O error - */ - protected void start() throws IOException { - new CliReader().listBooks(null); - } - - /** - * Will check if updates are available, synchronously. - *

- * For this, it will simply forward the call to - * {@link Main#checkUpdates(String)} with a value of "nikiroo/fanfix". - *

- * You may want to override it so you call the forward method with the right - * parameters (or also if you want it to be asynchronous). - * - * @return the newer version information or NULL if nothing new - */ - protected VersionCheck checkUpdates() { - return checkUpdates("nikiroo/fanfix"); - } - - /** - * Will check if updates are available on a specific GitHub project. - *

- * Will be called by {@link Main#checkUpdates()}, but if you override that - * one you mall call it with another project. - * - * @param githubProject - * the GitHub project, for instance "nikiroo/fanfix" - * - * @return the newer version information or NULL if nothing new - */ - protected VersionCheck checkUpdates(String githubProject) { - try { - VersionCheck updates = VersionCheck.check(githubProject, - Instance.getInstance().getTrans().getLocale()); - if (updates.isNewVersionAvailable()) { - notifyUpdates(updates); - return updates; - } - } catch (IOException e) { - // Maybe no internet. Do not report any update. - } - - return null; - } - - /** - * Notify the user about available updates. - *

- * Will only be called when a version is available. - *

- * Note that you can call {@link Instance#setVersionChecked()} on it if the - * user has read the information (by default, it is marked read only on - * certain other actions). - * - * @param updates - * the new version information - */ - protected void notifyUpdates(VersionCheck updates) { - // 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(""); - } - } - - /** - * Import the given resource into the {@link LocalLibrary}. - * - * @param url - * the resource to import - * @param pg - * the optional progress reporter - * - * @return the exit return code (0 = success) - */ - protected static int imprt(URL url, Progress pg) { - try { - MetaData meta = Instance.getInstance().getLibrary().imprt(url, 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 type - * the {@link OutputType} to use - * @param target - * the target - * @param pg - * the optional progress reporter - * - * @return the exit return code (0 = success) - */ - protected static int export(String luid, OutputType type, String target, - Progress pg) { - 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) - */ - protected int list(String source) { - try { - new CliReader().listBooks(source); - } catch (IOException e) { - Instance.getInstance().getTraceHandler().error(e); - return 66; - } - - return 0; - } - - /** - * Start the current reader for this {@link Story}. - * - * @param story - * the story to read - * @param chap - * which {@link Chapter} to read (starting at 1), or NULL to get - * the {@link Story} description - * - * @return the exit return code (0 = success) - */ - protected int read(Story story, Integer chap) { - if (story != null) { - try { - if (chap == null) { - new CliReader().listChapters(story); - } else { - new CliReader().printChapter(story, chap); - } - } catch (IOException e) { - Instance.getInstance().getTraceHandler() - .error(new IOException("Failed to read book", e)); - return 2; - } - } else { - Instance.getInstance().getTraceHandler() - .error("Cannot find book: " + story); - return 2; - } - - return 0; - } - - /** - * Convert the {@link Story} into another format. - * - * @param urlString - * the source {@link Story} to convert - * @param type - * 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) - */ - protected int convert(String urlString, OutputType type, - 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()); - } - - 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; - } - - /** - * 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" - */ - protected 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)); - } - } - - /** - * Starts a search operation (i.e., list the available web sites we can - * search on). - * - * @throws IOException - * in case of I/O errors - */ - protected void search() throws IOException { - new CliReader().listSearchables(); - } - - /** - * Search for books by keywords on the given supported web site. - * - * @param searchOn - * the web site to search on - * @param search - * the keyword to look for - * @param page - * the page of results to get, or 0 to inquire about the number - * of pages - * @param item - * the index of the book we are interested by, or 0 to query - * about how many books are in that page of results - * - * @throws IOException - * in case of I/O error - */ - protected void searchKeywords(SupportType searchOn, String search, - int page, Integer item) throws IOException { - new CliReader().searchBooksByKeyword(searchOn, search, page, item); - } - - /** - * Search for books by tags on the given supported web site. - * - * @param searchOn - * the web site to search on - * @param page - * the page of results to get, or 0 to inquire about the number - * of pages - * @param item - * the index of the book we are interested by, or 0 to query - * about how many books are in that page of results - * @param tags - * the tags to look for - * - * @throws IOException - * in case of I/O error - */ - protected void searchTags(SupportType searchOn, Integer page, Integer item, - Integer[] tags) throws IOException { - new CliReader().searchBooksByTag(searchOn, page, item, tags); - } - - /** - * Start a Fanfix server. - * - * @throws IOException - * in case of I/O errors - * @throws SSLException - * when the key was not accepted - */ - private void startServer() throws IOException { - String mode = Instance.getInstance().getConfig() - .getString(Config.SERVER_MODE, "fanfix"); - if (mode.equals("fanfix")) { - RemoteLibraryServer server = new RemoteLibraryServer(); - server.setTraceHandler(Instance.getInstance().getTraceHandler()); - server.run(); - } else if (mode.equals("http")) { - WebLibraryServer server = new WebLibraryServer(false); - server.setTraceHandler(Instance.getInstance().getTraceHandler()); - server.run(); - } else if (mode.equals("https")) { - WebLibraryServer server = new WebLibraryServer(true); - server.setTraceHandler(Instance.getInstance().getTraceHandler()); - server.run(); - } else { - throw new IOException("Unknown server mode: " + mode); - } - } - - /** - * Stop a running Fanfix server. - * - * @param key - * the key to contact the Fanfix server - * @param host - * the host on which it runs - * @param port - * the port on which it runs - * - * @throws IOException - * in case of I/O errors - * @throws SSLException - * when the key was not accepted - */ - private void stopServer(String key, String host, int port) - throws IOException, SSLException { - if (host.startsWith("http://") || host.startsWith("https://")) { - new WebLibrary(key, host, port).stop(); - } else { - new RemoteLibrary(key, host, port).stop(); - } - } - - /** - * We are done and ready to exit. - *

- * By default, it will call {@link System#exit(int)} if the status is not 0. - * - * @param status - * the exit status - */ - protected void exit(int status) { - if (status != 0) { - System.exit(status); - } - } - - /** - * Simple shortcut method to call {link Instance#getTrans()#getString()}. - * - * @param id - * the ID to translate - * - * @return the translated result - */ - static private String trans(StringId id, Object... params) { - return Instance.getInstance().getTrans().getString(id, params); - } -} diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..d98c62e --- /dev/null +++ b/Makefile @@ -0,0 +1,284 @@ +# +# Java makefile +# > NAME: main program name (for manual, jar, doc...) +# > MAIN: main source to compile without the 'src/' prefix nor the '.java' ext +# > MORE: more sources to compile to generate the full program +# > TEST: list of all test programs to compile and run (same format as MORE) +# > JAR_FLAGS : list of paths to include in the jar file (`-C dir path`) +# > SJAR_FLAGS: list of paths to include in the source jar file (`-C dir path`) +# > PREFIX: the usual prefix to (un)install to -- you may of course override it +# +NAME = fanfix +MAIN = be/nikiroo/fanfix/Main +TEST = be/nikiroo/fanfix/tests/Test +JAR_MISC = -C ./ LICENSE -C ./ VERSION -C libs/ licenses +JAR_FLAGS += -C bin/ be -C bin/ org $(JAR_MISC) +SJAR_FLAGS += -C src/ be -C src/ org $(JAR_MISC) + +PREFIX = /usr/local + +# +# Special Options for this program: you can modify the previous var if needed +# > UI=android (or UI=awt by default) +# +ifeq ($(UI),android) +MORE += be/nikiroo/utils/android/ImageUtilsAndroid +TEST += be/nikiroo/utils/android/test/TestAndroid +else +MORE += be/nikiroo/utils/ui/ImageUtilsAwt +MORE += be/nikiroo/utils/ui/ImageTextAwt +TEST += be/nikiroo/utils/ui/test/TestUI +endif + +################################################################################ + +JAVAC = javac +JAVAC_FLAGS += -encoding UTF-8 -d ./bin/ -cp ./src/ +JAVA = java +JAVA_FLAGS += -cp ./bin/ +JAR = jar +RJAR = java +RJAR_FLAGS += -jar + +ifeq ($(DEBUG),1) +JAVAC_FLAGS += -Xlint:deprecation -g +endif + +.PHONY: all build run clean mrpropre mrpropre love debug doc man test run-test \ + check_time jar sjar resources test-resources libs + +all: build jar sjar + +check_time: + @echo + @echo ">>>>>>>>>> Checking lastest sources/build times..." + @cp -a `find src -type f -printf "%T@ %p\n" \ + | sort -n | cut -d' ' -f 2- | tail -n 1` latest_src + @cp -a `find libs -type f -printf "%T@ %p\n" \ + | sort -n | cut -d' ' -f 2- | tail -n 1` latest_lib + @cp -a `find . \( -wholename VERSION -o -name '*.md' -o \ + \( -wholename './src/*' -a ! -name '*.java' \ + -a ! -wholename '*/test/*' \)\ + \) \ + -type f -printf "%T@ %p\n" 2>/dev/null \ + | sort -n | cut -d' ' -f 2- | tail -n 1` latest_rsc + + @ls latest_??? \ + --time-style=+@%Y-%m-%d\ %H:%M:%S -lt | cut -f2- -d@ + +build: check_time resources latest_bin +latest_bin: latest_src + @echo + @echo ">>>>>>>>>> Building sources..." + $(JAVAC) $(JAVAC_FLAGS) "src/$(MAIN).java" + @[ "$(MORE)" = "" ] || for sup in $(MORE); do \ + echo $(JAVAC) $(JAVAC_FLAGS) "src/$$sup.java"; \ + $(JAVAC) $(JAVAC_FLAGS) "src/$$sup.java"; \ + done; + touch latest_bin + + +test: build test-resources latest_tst +latest_tst: latest_src + @echo + @echo ">>>>>>>>>> Building all tests: $(TEST)..." + @if [ "$(TEST)" = "" ]; then \ + echo No test sources defined.; \ + else \ + for tst in $(TEST); do \ + echo $(JAVAC) $(JAVAC_FLAGS) "src/$$tst.java"; \ + $(JAVAC) $(JAVAC_FLAGS) "src/$$tst.java"; \ + done; \ + fi; + touch latest_tst + +# Main buildables +jar: libs resources $(NAME).jar +$(NAME).jar: latest_bin + @echo + @echo ">>>>>>>>>> Generating $@..." + @if [ -d libs/bin/ ]; then \ + echo cp -r libs/bin/* bin/; \ + cp -r libs/bin/* bin/; \ + fi; + cp -r src/* bin/ + @echo "Main-Class: `echo "$(MAIN)" | sed 's:/:.:g'`" > bin/manifest + @echo >> bin/manifest + $(JAR) cfm $(NAME).jar bin/manifest -C ./ *.md $(JAR_FLAGS) + @if [ -e VERSION ]; then \ + echo cp $(NAME).jar "$(NAME)-`cat VERSION`.jar"; \ + cp $(NAME).jar "$(NAME)-`cat VERSION`.jar"; \ + fi; + +sjar: libs $(NAME)-sources.jar +$(NAME)-sources.jar: latest_src + @echo + @echo ">>>>>>>>>> Generating $@..." + @echo > bin/manifest + $(JAR) cfm $(NAME)-sources.jar \ + bin/manifest -C ./ *.md $(SJAR_FLAGS); + @if [ -e VERSION ]; then \ + echo cp $(NAME)-sources.jar \ + "$(NAME)-`cat VERSION`-sources.jar"; \ + cp $(NAME)-sources.jar \ + "$(NAME)-`cat VERSION`-sources.jar"; \ + fi; + +# Requisites +libs: check_time latest_lib +latest_lib: bin/libs +bin/libs: + @echo + @echo ">>>>>>>>>> Extracting sources from libs..." + @cd src && \ + for lib in ../libs/*-sources.jar \ + ../libs/*-sources.patch.jar; do \ + if [ "$$lib" != '../libs/*-sources.jar' \ + -a "$$lib" != '../libs/*-sources.patch.jar' ]; \ + then \ + echo cd src \&\& jar xf "$$lib"; \ + jar xf "$$lib"; \ + fi; \ + done; + mkdir -p bin + touch bin/libs + +resources: check_time libs latest_rsc +latest_rsc: bin/VERSION +bin/VERSION: + @echo + @echo ">>>>>>>>>> Copying resources and documentation into bin/..." + mkdir -p bin + @if ! ls *.md 2>/dev/null 2>&1; then \ + echo touch bin/no-documentation.md; \ + touch bin/no-documentation.md; \ + else \ + echo cp *.md bin/; \ + cp *.md bin/; \ + fi; + @cd src && find . | grep -v '\.java$$' \ + | grep -v '/test/' | while read -r ln; do \ + if [ -f "$$ln" ]; then \ + dir="`dirname "$$ln"`"; \ + mkdir -p "../bin/$$dir" ; \ + echo cp "$$ln" "../bin/$$ln" ; \ + cp "$$ln" "../bin/$$ln" ; \ + fi ; \ + done; + cp VERSION bin/ + +test-resources: check_time resources latest_tsc +latest_tsc: latest_ttt +latest_ttt: + @echo + @echo ">>>>>>>>>> Copying test resources into bin/..." + @cd src && find . | grep -v '\.java$$' \ + | grep '/test/' | while read -r ln; do \ + if [ -f "$$ln" ]; then \ + dir="`dirname "$$ln"`"; \ + mkdir -p "../bin/$$dir" ; \ + echo cp "$$ln" "../bin/$$ln" ; \ + cp "$$ln" "../bin/$$ln" ; \ + fi ; \ + done; + touch latest_ttt + +# Manual +man: + @echo + @echo ">>>>>>>>>> Manual of $(NAME): $(MAKECMDGOALS)..." + @$(MAKE) -f man.d $(MAKECMDGOALS) NAME=$(NAME) + +# Run +run: build + @echo + @echo ">>>>>>>>>> Running $(NAME)..." + $(JAVA) $(JAVA_FLAGS) $(MAIN) + +# Run main test +run-test: + @echo + @echo ">>>>>>>>>> Running tests: $(TEST)..." + @[ "$(TEST)" != "" ] || echo No test sources defined. + @if [ "`whereis tput`" = "tput:" ]; then \ + ok='"[ ok ]"'; \ + ko='"[ !! ]"'; \ + cols=80; \ + else \ + ok="`tput bold`[`tput setf 2` OK `tput init``tput bold`]`tput init`"; \ + ko="`tput bold`[`tput setf 4` !! `tput init``tput bold`]`tput init`"; \ + cols="`tput cols`"; \ + fi; \ + [ "$(TEST)" = "" ] || ( \ + clear; \ + for test in $(TEST); do \ + echo $(JAVA) $(JAVA_FLAGS) \ + "$$test" "$$cols" "$$ok" "$$ko"; \ + $(JAVA) $(JAVA_FLAGS) "$$test" "$$cols" "$$ok" "$$ko"; \ + done; \ + ); + +# Doc/misc +doc: + @echo + @echo ">>>>>>>>>> Generating documentation for $(NAME)..." + doxygen +love: + @echo " ...not war." +debug: + $(MAKE) $(MAKECMDGOALS) PREFIX=$(PREFIX) NAME=$(NAME) DEBUG=1 + +# Clean +clean: + @echo + @echo ">>>>>>>>>> Cleaning $(NAME)..." + rm -rf bin/ + @for lib in libs/*-sources.jar libs/*-sources.patch.jar; do \ + if [ "$$lib" != 'libs/*-sources.jar' \ + -a "$$lib" != 'libs/*-sources.patch.jar' ]; \ + then \ + echo Cleaning `basename "$$lib"`...; \ + jar tf "$$lib" | while read -r ln; do \ + [ -f "src/$$ln" ] && rm "src/$$ln"; \ + done; \ + jar tf "$$lib" | tac | while read -r ln; do \ + if [ -d "src/$$ln" ]; then \ + rmdir "src/$$ln" 2>/dev/null || true; \ + fi; \ + done; \ + fi; \ + done; + rm -f latest_??? + +mrproper: mrpropre +mrpropre: clean man + @echo + @echo ">>>>>>>>>> Calling Mr Propre..." + rm -f $(NAME).jar + rm -f $(NAME)-sources.jar + rm -f "$(NAME)-`cat VERSION`.jar" + rm -f "$(NAME)-`cat VERSION`-sources.jar" + rm -rf doc/html doc/latex doc/man + rmdir doc 2>/dev/null || true + +# Install/uninstall +install: jar man + @echo + @echo ">>>>>>>>>> Installing $(NAME) into $(PREFIX)..." + mkdir -p "$(PREFIX)/lib" "$(PREFIX)/bin" + cp $(NAME).jar "$(PREFIX)/lib/" + ( \ + echo "#!/bin/sh"; \ + echo "$(RJAR) $(RJAR_FLAGS) \"$(PREFIX)/lib/$(NAME).jar\" \"\$$@\"" \ + ) > "$(PREFIX)/bin/$(NAME)" + chmod a+rx "$(PREFIX)/bin/$(NAME)" + + +uninstall: man + @echo + @echo ">>>>>>>>>> Uninstalling $(NAME) from $(PREFIX)..." + rm -f "$(PREFIX)/bin/$(NAME)" + rm -f "$(PREFIX)/lib/$(NAME).jar" + rmdir "$(PREFIX)/bin" 2>/dev/null + rmdir "$(PREFIX)/lib" 2>/dev/null + diff --git a/README-fr.md b/README-fr.md new file mode 100644 index 0000000..c69bd4f --- /dev/null +++ b/README-fr.md @@ -0,0 +1,161 @@ +[English](README.md) Français + +# Fanfix + +Fanfix est un petit programme Java qui peut télécharger des histoires sur internet et les afficher hors ligne. + +## 🔴 Ceci est le programme serveur et command-line + +Vous pouvez aussi utiliser : +- le client graphique [Fanfix-swing](https://github.com/nikiroo/fanfix-swing/) +- le client en mode TUI [Fanfix-jexer](https://github.com/nikiroo/fanfix-jexer/) + +## Synopsis + +- ```fanfix``` --import [*URL*] +- ```fanfix``` --export [*id*] [*output_type*] [*target*] +- ```fanfix``` --convert [*URL*] [*output_type*] [*target*] (+info) +- ```fanfix``` --read [*id*] ([*chapter number*]) +- ```fanfix``` --read-url [*URL*] ([*chapter number*]) +- ```fanfix``` --search +- ```fanfix``` --search [*where*] [*keywords*] (page [*page*]) (item [*item*]) +- ```fanfix``` --search-tag +- ```fanfix``` --search-tag [*index 1*]... (page [*page*]) (item [*item*]) +- ```fanfix``` --list +- ```fanfix``` --server [*key*] [*port*] +- ```fanfix``` --stop-server [*key*] [*port*] +- ```fanfix``` --remote [*key*] [*host*] [*port*] +- ```fanfix``` --help + +## Description + +(Si vous voulez juste voir les derniers changements, vous pouvez regarder le [Changelog](changelog-fr.md) -- remarquez que le programme affiche le changelog si une version plus récente est détectée depuis la version 1.4.0.) + +Le fonctionnement du programme est assez simple : il converti une URL venant d'un site supporté en un fichier .epub pour les histoires ou .cbz pour les comics (d'autres options d'enregistrement sont disponibles, comme du texte simple, du HTML...) + +Pour vous aider à organiser vos histoires, il peut aussi servir de bibliothèque locale vous permettant : + +- d'importer une histoire depuis son URL (ou depuis un fichier) +- d'exporter une histoire dans un des formats supportés vers un fichier +- d'afficher une histoire en mode texte +- via [fanfix-swing](https://github.com/nikiroo/fanfix-swing/): d'afficher une histoire en mode GUI **lui-même** ([fanfix-swing](https://github.com/nikiroo/fanfix-swing/)) ou **en appelant un programme natif pour lire le fichier** (potentiellement converti en HTML avant, pour que n'importe quel navigateur web puisse l'afficher) + +### Sites supportés + +Pour le moment, les sites suivants sont supportés : + +- http://FimFiction.net/ : fanfictions dévouées à la série My Little Pony +- http://Fanfiction.net/ : 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 +- http://mangahub.io/ : un site répertoriant une quantité non négligeable de mangas (English) +- https://e621.net/ : un site Furry proposant des comics, y compris de MLP +- https://sofurry.com/ : même chose, mais orienté sur les histoires plutôt que les images +- https://e-hentai.org/ : support ajouté sur demande : n'hésitez pas à demander un site ! +- http://mangas-lecture-en-ligne.fr/ : un site proposant beaucoup de mangas, en français + +### Types de fichiers supportés + +Nous supportons les types de fichiers suivants (aussi bien en entrée qu'en sortie) : + +- epub : les fichiers .epub créés avec Fanfix (nous ne supportons pas les autres fichiers .epub, du moins pour le moment) +- text : les histoires enregistrées en texte (.txt), avec quelques règles spécifiques : + - le titre doit être sur la première ligne + - l'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)```) + - les chapitres doivent être déclarés avec ```Chapitre x``` ou ```Chapitre x: NOM DU CHAPTITRE```, où ```x``` est le numéro du chapitre + - une description de l'histoire doit être donnée en tant que chaptire 0 + - une image de couverture peut être présente avec le même nom de fichier que l'histoire, mais une extension .png, .jpeg ou .jpg +- 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) +- 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) +- 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 + +### Plateformes supportées + +Toute plateforme supportant Java 1.6 devrait suffire. + +Le programme a été testé sur Linux (Debian, Slackware et Ubuntu), MacOS X et Windows pour le moment, mais n'hésitez pas à nous informer si vous l'essayez sur un autre système. + +Si vous avez des difficultés pour le compiler avec une version supportée de Java (1.6+), contactez-nous. + +## Options + +Vous pouvez démarrer le programme de deux façons : + +- ```java -jar fanfix.jar``` +- ```fanfix``` (si vous avez utilisé *make install*) + +Les arguments suivants sont supportés : + +- ```--import [URL]```: importer une histoire dans la librairie +- ```--export [id] [output_type] [target]```: exporter l'histoire "id" vers le fichier donné +- ```--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é +- ```--read [id] ([chapter number])```: afficher l'histoire "id" +- ```--read-url [URL] ([chapter number])```: convertir l'histoire et la lire à la volée, sans la sauver +- ```--search```: liste les sites supportés (```where```) +- ```--search [where] [keywords] (page [page]) (item [item])```: lance une recherche et affiche les résultats de la page ```page``` (page 1 par défaut), et de l'item ```item``` spécifique si demandé +- ```--tag [where]```: liste tous les tags supportés par ce site web +- ```--tag [index 1]... (page [page]) (item [item])```: affine la recherche, tag par tag, et affiche si besoin les sous-tags, les histoires ou les infos précises de l'histoire demandée +- ```--list```: lister les histoires presentes dans la librairie et leurs IDs +- ```--server [key] [port]```: démarrer un serveur d'histoires sur ce port +- ```--stop-server [key] [port]```: arrêter le serveur distant sur ce port (key doit avoir la même valeur) +- ```--remote [key] [host] [port]```: contacter ce server au lieu de la librairie habituelle (key doit avoir la même valeur) +- ```--help```: afficher la liste des options disponibles +- ```--version```: retourne la version du programme + +### Environnement + +Certaines variables d'environnement sont reconnues par le programme : + +- ```LANG=en```: forcer la langue du programme en anglais +- ```CONFIG_DIR=$HOME/.fanfix```: utilise ce répertoire pour les fichiers de configuration du programme (et copie les fichiers de configuration par défaut si besoin) +- ```NOUTF=1```: essaye d'utiliser des caractères non-unicode quand possible (cela peut avoir un impact sur les fichiers générés, pas uniquement sur les messages à l'utilisateur) +- ```DEBUG=1```: force l'option ```DEBUG=true``` du fichier de configuration (pour afficher plus d'information en cas d'erreur) + +## Compilation + +```./configure.sh && make``` + +Vous pouvez aussi importer les sources java dans, par exemple, [Eclipse](https://eclipse.org/), et faire un JAR exécutable depuis celui-ci. + +Quelques tests unitaires sont disponibles : + +```./configure.sh && make build test run-test``` + +Si vous faites tourner les tests unitaires, sachez que certains fichiers flags peuvent les impacter: + +- ```test/VERBOSE``` : active le mode verbeux pour les erreurs +- ```test/OFFLINE``` : ne permet pas au programme de télécharger des données +- ```test/URLS``` : permet au programme de tester des URLs +- ```test/FORCE_REFRESH```: force le nettoyage du cache + +Notez que le répertoire ```test/CACHE``` peut rester en place; il contient tous les fichiers téléchargés au moins une fois depuis le réseau par les tests unitaires (si vous autorisez les tests d'URLs, lancez les tests au moins une fois pour peupler le CACHE, puis activez le mode OFFLINE, ça marchera toujours). + +Les fichiers de test seront: + +- ```test/*.url``` : des URLs à télécharger en fichier texte (le contenu du fichier est l'URL) +- ```test/*.story```: des histoires en mode texte (le contenu du fichier est l'histoire) + +### Librairies dépendantes (incluses) + +Nécessaires : + +- [```libs/unbescape-sources.jar```](https://github.com/unbescape/unbescape): une librairie sympathique pour convertir du texte depuis/vers beaucoup de formats ; utilisée ici pour la partie HTML +- [```libs/jsoup-sources.jar```](https://jsoup.org/): une libraririe pour parser du HTML +- [```libs/JSON-java-20190722-sources.jar```](https://github.com/stleary/JSON-java): une libraririe pour parser du JSON + +Optionnelles : + +- [```libs/jexer-sources.jar```](https://github.com/klamonte/jexer): une petite librairie qui offre des widgets en mode TUI +- [```pandoc```](http://pandoc.org/): pour générer les man pages depuis les fichiers README + +Submodules: + +- ```src/be/nikiroo/utils```: quelques utilitaires partagés depuis [https://github.com/nikiroo/nikiroo-utils.git](https://github.com/nikiroo/nikiroo-utils.git) -- branche ```subtree``` +- ```src/be/nikiroo/fanfix```: le code principal, sur le même dépôt mais dans la branche ```subtree``` + +Rien d'autre, si ce n'est Java 1.6+. + +À noter : ```make libs``` exporte ces librairies dans le répertoire src/. + +## Auteur + +Fanfix a été écrit par Niki Roo + diff --git a/README.md b/README.md new file mode 100644 index 0000000..129e69f --- /dev/null +++ b/README.md @@ -0,0 +1,162 @@ +English [Français](README-fr.md) + +# Fanfix + +Fanfix is a small Java program that can download stories from some supported websites and render them offline. + +## 🔴 This is the command line and server program + +You can also use: +- the graphical client [Fanfix-swing](https://github.com/nikiroo/fanfix-swing/) +- the TUI client [Fanfix-jexer](https://github.com/nikiroo/fanfix-jexer/) + +## Synopsis + +- ```fanfix``` --import [*URL*] +- ```fanfix``` --export [*id*] [*output_type*] [*target*] +- ```fanfix``` --convert [*URL*] [*output_type*] [*target*] (+info) +- ```fanfix``` --read [*id*] ([*chapter number*]) +- ```fanfix``` --read-url [*URL*] ([*chapter number*]) +- ```fanfix``` --search +- ```fanfix``` --search [*where*] [*keywords*] (page [*page*]) (item [*item*]) +- ```fanfix``` --search-tag +- ```fanfix``` --search-tag [*index 1*]... (page [*page*]) (item [*item*]) +- ```fanfix``` --list +- ```fanfix``` --server [*key*] [*port*] +- ```fanfix``` --stop-server [*key*] [*port*] +- ```fanfix``` --remote [*key*] [*host*] [*port*] +- ```fanfix``` --help + +## Description + +(If you are interested in the recent changes, please check the [Changelog](changelog.md) -- note that starting from version 1.4.0, the changelog is checked at startup.) + +This program will convert from a (supported) URL to an .epub file for stories or a .cbz file for comics (a few other output types are also available, like Plain Text, LaTeX, HTML...). + +To help organize your stories, it can also work as a local library so you can: + +- Import a story from its URL (or just from a file) +- Export a story to a file (in any of the supported output types) +- Display a story from the local library in text format in the console +- Via [fanfix-swing](https://github.com/nikiroo/fanfix-swing/): Display a story from the local library graphically **by itself** ([fanfix-swing](https://github.com/nikiroo/fanfix-swing/)) or **by calling a native program to handle it** (potentially converted into HTML before hand, so any browser can open it) + +### Supported websites + +Currently, the following websites are supported: + +- http://FimFiction.net/: fan fictions devoted to the My Little Pony show +- http://Fanfiction.net/: fan fictions of many, many different universes, from TV shows to novels to games +- http://mangahub.io/: a well filled repository of mangas (English) +- https://e621.net/: a Furry website supporting comics, including MLP +- https://sofurry.com/: same thing, but story-oriented +- https://e-hentai.org/: done upon request (so, feel free to ask for more websites!) +- http://mangas-lecture-en-ligne.fr/: a website offering a lot of mangas (in French) + +### Support file types + +We support a few file types for local story conversion (both as input and as output): + +- epub: .epub files created by this program (we do not support "all" .epub files, at least for now) +- text: local stories encoded in plain text format, with a few specific rules: + - the title must be on the first line + - the author (preceded by nothing, ```by ``` or ```©```) must be on the second line, possibly with the publication date in parenthesis (i.e., ```By Unknown (3rd October 1998)```) + - chapters must be declared with ```Chapter x``` or ```Chapter x: NAME OF THE CHAPTER```, where ```x``` is the chapter number + - a description of the story must be given as chapter number 0 + - a cover image may be present with the same filename as the story, but a .png, .jpeg or .jpg extension +- info_text: contains the same information as the text format, but with a companion .info file to store some metadata (the .info file is supposed to be created by Fanfix or compatible with it) +- cbz: .cbz (collection of images) files, preferably created with Fanfix (but any .cbz file is supported, though without most of Fanfix metadata, obviously) +- html: HTML files that you can open with any browser; note that it will create a directory structure with ```index.html``` as the main file -- we only support importing HTML files created by Fanfix + +### Supported platforms + +Any platform with at lest Java 1.6 on it should be ok. + +It has been tested on Linux (Debian, Slackware, Ubuntu), MacOS X and Windows for now, but feel free to inform us if you try it on another system. + +If you have any problems to compile it with a supported Java version (1.6+), please contact us. + +## Options + +You can start the program in two ways: + +- ```java -jar fanfix.jar``` +- ```fanfix``` (if you used *make install*) + +The following arguments are allowed: + +- ```--import [URL]```: import the story at URL into the local library +- ```--export [id] [output_type] [target]```: export the story denoted by ID to the target file +- ```--convert [URL] [output_type] [target] (+info)```: convert the story at URL into target, and force-add the .info and cover if +info is passed +- ```--read [id] ([chapter number])```: read the given story denoted by ID from the library +- ```--read-url [URL] ([chapter number])```: convert on the fly and read the story at URL, without saving it +- ```--search```: list the supported websites (```where```) +- ```--search [where] [keywords] (page [page]) (item [item])```: search on the supported website and display the given results page of stories it found, or the story details if asked +- ```--tag [where]```: list all the tags supported by this website +- ```--tag [index 1]... (page [page]) (item [item])```: search for the given stories or subtags, tag by tag, and display information about a specific page of results or about a specific item if requested +- ```--list```: list the stories present in the library and their associated IDs +- ```--server [key] [port]```: start a story server on this port +- ```--stop-server [key] [port]```: stop the remote server running on this port (key must be set to the same value) +- ```--remote [key] [host] [port]```: contact this server instead of the usual library (key must be set to the same value) +- ```--help```: display the available options +- ```--version```: return the version of the program + +### Environment + +Some environment variables are recognized by the program: + +- ```LANG=en```: force the language to English +- ```CONFIG_DIR=$HOME/.fanfix```: use the given directory as a config directory (and copy the default configuration if needed) +- ```NOUTF=1```: try to fallback to non-unicode values when possible (can have an impact on the resulting files, not only on user messages) +- ```DEBUG=1```: force the ```DEBUG=true``` option of the configuration file (to show more information on errors) + +## Compilation + +```./configure.sh && make``` + +You can also import the java sources into, say, [Eclipse](https://eclipse.org/), and create a runnable JAR file from there. + +There are some unit tests you can run, too: + +```./configure.sh && make build test run-test``` + +If you run the unit tests, note that some flag files can impact them: + +- ```test/VERBOSE``` : enable verbose mode +- ```test/OFFLINE``` : to forbid any downloading +- ```test/URLS``` : to allow testing URLs +- ```test/FORCE_REFRESH```: to force a clear of the cache + +Note that ```test/CACHE``` can be kept, as it will contain all internet related files you need (if you allow URLs, run the test once which will populate the CACHE then go OFFLINE, it will still work). + +The test files will be: + +- ```test/*.url``` : URL to download in text format, content = URL +- ```test/*.story```: text mode story, content = story + + +### Dependant libraries (included) + +Required: + +- [```libs/unbescape-sources.jar```](https://github.com/unbescape/unbescape): a nice library to escape/unescape a lot of text formats; used here for HTML +- [```libs/jsoup-sources.jar```](https://jsoup.org/): a library to parse HTML +- [```libs/JSON-java-20190722-sources.jar```](https://github.com/stleary/JSON-java): a library to parse JSON + +Optional: + +- [```libs/jexer-sources.jar```](https://github.com/klamonte/jexer): a small library that offers TUI widgets +- [```pandoc```](http://pandoc.org/): to generate the man pages from the README files + +Submodules: + +- ```src/be/nikiroo/utils```: some shared utility functions from [https://github.com/nikiroo/nikiroo-utils.git](https://github.com/nikiroo/nikiroo-utils.git) -- branch ```subtree``` +- ```src/be/nikiroo/fanfix```: the main code on the same repository but on branch ```subtree``` + +Nothing else but Java 1.6+. + +Note that calling ```make libs``` will export the libraries into the src/ directory. + +## Author + +Fanfix was written by Niki Roo + diff --git a/TODO.md b/TODO.md new file mode 100644 index 0000000..85f2deb --- /dev/null +++ b/TODO.md @@ -0,0 +1,106 @@ +My current planning for Fanfix (but not everything appears on this list): + +- [ ] Support new websites + - [x] YiffStar + - [ ] [Two Kinds](http://twokinds.keenspot.com/) + - [ ] [Slightly damned](http://www.sdamned.com/) + - [x] New API on FimFiction.net (faster) + - [x] Replace MangaFox which is causing issues all the time + - [ ] Others? Any ideas? I'm open for requests + - [x] [e-Hentai](https://e-hentai.org/) requested + - [x] Find some FR comics/manga websites + - [ ] Find more FR thingies +- [ ] Support videos (anime)? +- [x] A GUI library + - [x] Make one + - [x] Make it run when no args passed + - [x] Fix the UI, it is ugly + - [x] Work on the UI thread is BAD + - [x] Allow export + - [x] Allow delete/refresh + - [x] Show a list of types + - [x] ..in the menu + - [x] ..as a screen view + - [x] options screen + - [x] support progress events + - [x] Real menus + - [x] Store the long lists in [A-B], [BA-BB], ... +- [ ] A TUI library + - [x] Choose an output (Jexer) + - [x] Implement it from --set-reader to the actual window + - [x] List the stories + - [x] Fix the UI layout + - [x] Status bar + - [x] Real menus + - [ ] Store the long lists in [A-B], [BA-BB], ... + - [x] Open a story in the reader and/or natively + - [ ] Update the screenshots + - [ ] Remember the current chapter and current read status of stories + - [ ] Support progress events + - [x] Add a properties pages + - [ ] Deal with comics + - [x] properties page + - [x] external launcher + - [ ] jexer sixels? +- [ ] Move the GUI parts out of fanfix itself (see fanfix-swing) + - [x] Make new project: fanfix-swing + - [x] Fix the UI issues we had (UI thread) + - [x] Make it able to browse already downloaded stories + - [x] Make it able to download stories + - [ ] See what config options to use + - [ ] Import all previous menus end functions + - [ ] Feature parity with original GUI +- [ ] Move the TUI parts out of fanfix itself + - [ ] Make new project +- [x] Network support + - [x] A server that can send the stories + - [x] A network implementation of the Library + - [x] Write access to the library + - [x] Access rights (a simple "key") + - [x] More tests, especially with the GUI + - [x] ..even more + - [x] support progress events +- [x] Check if it can work on Android + - [x] First checks: it should work, but with changes + - [x] Adapt work on images :( + - [x] Partial/Conditional compilation + - [x] APK export +- [ ] Android + - [x] Android support + - [x] Show current stories + - [x] Download new stories + - [ ] Sort stories by Source/Author + - [ ] Fix UI + - [ ] support progress events + - [x] give up and ask a friend... +- [ ] Translations + - [x] i18n system in place + - [x] Make use of it in text + - [x] Make use of it in gui + - [ ] Make use of it in tui + - [ ] Use it for all user output + - [x] French translation + - [x] French manual/readme +- [x] Install a mechanism to handle stories import/export progress update + - [x] Progress system + - [x] in support classes (import) + - [x] in output classes (export) +- [x] Version + - [x] Use a version number + - [x] Show it in UI + - [x] A check-update feature + - [x] ..translated +- [ ] Improve GUI library + - [x] Allow launching a custom application instead of Desktop.start + - [ ] Add the resume next to the cover icon if available (as an option) + - [ ] Add the resume in the Properties page (maybe a second tab?) +- [ ] Bugs + - [x] Fix "Redownload also reset the source" + - [x] Fix "Redownload remote does not show the new item before restart of client app" + - [x] Fix eHentai "content warning" access (see 455) + - [ ] Fix the configuration system (for new or changed options, new or changed languages) + - [x] remote import also download the file in cache, why? + - [x] import file in remote mode tries to import remote file!! + - [ ] import file does not find author in cbz with SUMMARY file + - [x] import file:// creates a tmp without auto-deletion in /tmp/fanfic-... + diff --git a/bundles/Config.java b/bundles/Config.java deleted file mode 100644 index 86744b4..0000000 --- a/bundles/Config.java +++ /dev/null @@ -1,188 +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 { - - // Note: all hidden values are subject to be removed in a later version - - @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 = "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 environment 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 (fanfix://, http://, https:// -- if not specified, fanfix:// is assumed)",// - 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 = "Remote Server mode: you can use the fanfix protocol (which is encrypted), http (which is not) or https (which requires a keystore.jks file)",// - format = Format.FIXED_LIST, list = { "fanfix", "http", "https" }, def = "fanfix") - SERVER_MODE, - @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 = "A keystore.jks file, required to use HTTPS (the server will refuse to start in HTTPS mode without this file)", // - format = Format.STRING, def = "") - SERVER_SSL_KEYSTORE, - @Meta(description = "The pass phrase required to open the keystore.jks file (required for HTTPS mode)", // - format = Format.PASSWORD, def = "") - SERVER_SSL_KEYSTORE_PASS, - @Meta(description = "The encryption key for the server (NOT including a subkey), it cannot contain the pipe character \"|\" but can be empty -- is used to encrypt the traffic in fanfix mode (even if empty, traffic will be encrypted in fanfix mode), and used as a password for HTTP (clear text protocol) and HTTPS modes",// - 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 a WL subkey",// - array = true, format = Format.STRING, def = "") - SERVER_WHITELIST, // - @Meta(description = "Those sources will not be available for clients without a BL subkey",// - array = true, format = Format.STRING, def = "") - SERVER_BLACKLIST, // - @Meta(description = "The subkeys that the server will allow, including the modes\nA subkey is used as a login for HTTP (clear text protocol) and HTTPS modes", // - array = true, format = Format.STRING, def = "") - SERVER_ALLOWED_SUBKEYS, // - @Meta(description = "The maximum size of the cache, in MegaBytes, for HTTP and HTTPS servers", // - format = Format.INT, def = "100") - SERVER_MAX_CACHE_MB, - - @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",// - hidden = true, group = true) - CONF, // - @Meta(description = "LaTeX configuration",// - hidden = true, group = true) - CONF_LATEX_LANG, // - @Meta(description = "LaTeX output language (full name) for \"English\"",// - hidden = true, format = Format.STRING, def = "english") - CONF_LATEX_LANG_EN, // - @Meta(description = "LaTeX output language (full name) for \"French\"",// - hidden = true, format = Format.STRING, def = "french") - CONF_LATEX_LANG_FR, // - @Meta(description = "other 'by' prefixes before author name, used to identify the author",// - hidden = true, 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)", // - hidden = true, 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",// - hidden = true, format = Format.STRING, def = "Chapter") - CONF_CHAPTER_EN, // - @Meta(description = "Chapter identification string in French, used to identify a starting chapter in text mode",// - hidden = true, 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, // - - @Meta(description = "e621.net credentials\nYou can give your e621.net credentials here to have access to all the comics and ignore the default blacklist",// - group = true) - LOGIN_E621, // - @Meta(description = "Your e621.net login",// - format = Format.STRING) - LOGIN_E621_LOGIN, // - @Meta(description = "Your e621.net API KEY",// - format = Format.PASSWORD) - LOGIN_E621_APIKEY, // -} diff --git a/bundles/ConfigBundle.java b/bundles/ConfigBundle.java deleted file mode 100644 index ce72b3d..0000000 --- a/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/bundles/StringId.java b/bundles/StringId.java deleted file mode 100644 index 9772248..0000000 --- a/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/bundles/StringIdBundle.java b/bundles/StringIdBundle.java deleted file mode 100644 index b9a0d79..0000000 --- a/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/bundles/StringIdGui.java b/bundles/StringIdGui.java deleted file mode 100644 index c109f42..0000000 --- a/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 = "Prefetch 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/bundles/StringIdGuiBundle.java b/bundles/StringIdGuiBundle.java deleted file mode 100644 index c036381..0000000 --- a/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/bundles/Target.java b/bundles/Target.java deleted file mode 100644 index 64284c6..0000000 --- a/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/bundles/UiConfig.java b/bundles/UiConfig.java deleted file mode 100644 index 2122ccf..0000000 --- a/bundles/UiConfig.java +++ /dev/null @@ -1,59 +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 icon to use for the program",// - format = Format.FIXED_LIST, def = "default", list = { "default", "alternative", "magic-book", "pony-book", "pony-library" }) - PROGRAM_ICON, // - // - // GUI settings (hidden in config) - // - @Meta(description = "Show the side panel by default",// - hidden = true, format = Format.BOOLEAN, def = "true") - SHOW_SIDE_PANEL, // - @Meta(description = "Show the details panel by default",// - hidden = true, format = Format.BOOLEAN, def = "true") - SHOW_DETAILS_PANEL, // - @Meta(description = "Show thumbnails by default in the books view",// - hidden = true, format = Format.BOOLEAN, def = "false") - SHOW_THUMBNAILS, // - @Meta(description = "Show a words/images count instead of the author by default in the books view",// - hidden = true, format = Format.BOOLEAN, def = "false") - SHOW_WORDCOUNT, // - // - // Deprecated - // - @Meta(description = "The background colour of the library if you don't like the default system one",// - hidden = true, format = Format.COLOR) - @Deprecated - BACKGROUND_COLOR, // -} diff --git a/bundles/UiConfigBundle.java b/bundles/UiConfigBundle.java deleted file mode 100644 index 8b2c008..0000000 --- a/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/bundles/UiConfigBundleDesc.java b/bundles/UiConfigBundleDesc.java deleted file mode 100644 index da42950..0000000 --- a/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/bundles/package-info.java b/bundles/package-info.java deleted file mode 100644 index 80cdd15..0000000 --- a/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/bundles/resources_core.properties b/bundles/resources_core.properties deleted file mode 100644 index dc7881a..0000000 --- a/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/bundles/resources_core_fr.properties b/bundles/resources_core_fr.properties deleted file mode 100644 index a64a5a0..0000000 --- a/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/bundles/resources_gui.properties b/bundles/resources_gui.properties deleted file mode 100644 index 40be5eb..0000000 --- a/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 = Prefetch 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/bundles/resources_gui_fr.properties b/bundles/resources_gui_fr.properties deleted file mode 100644 index 25ff542..0000000 --- a/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 = Pré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/bundles/ui_description.properties b/bundles/ui_description.properties deleted file mode 100644 index c8def83..0000000 --- a/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 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: DIRECTORY) absolute path, $HOME variable supported, / is always accepted as dir separator -CACHE_DIR_LOCAL_READER = 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 -# 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/changelog-fr.md b/changelog-fr.md new file mode 100644 index 0000000..3fd1c66 --- /dev/null +++ b/changelog-fr.md @@ -0,0 +1,290 @@ +# Fanfix + +## Version WIP + +- new: bibliothèque http/https +- new: utilisation de submodules git +- new: remplacement du configure.sh par un Makefile + +## Version 3.1.2 + +- fix: date de publication/création formatée +- e621: correction sur l'ordre, encore +- e621: possibilité d'utiliser un Login/API key pour éviter les blacklists +- e621: meilleures meta-data + +## Version 3.1.1 + +- e621: correction pour les chapitres des pools dans l'ordre inverse +- fix: trie les histores par nom et plus par date +- fix: affiche le nom de l'histoire dans les barres de progrès + +## Version 3.1.0 + +- MangaHub: un nouvau site de manga (English) +- MangaFox: retrait du support (site désagréable) +- e621: correction pour la cover non sauvée + +## Version 3.0.1 + +- fix: en cas d'URL non supportée, n'affiche plus un message d'erreur relatif à "file://" +- e621: update pour e621 (et ce n'est plus un BasicSupport_Deprecated) + +## Version 3.0.0 + +- new: maintenant compatible Android (voir [companion project](https://gitlab.com/Rayman22/fanfix-android)) +- new: recherche d'histoires (pas encore toutes les sources) +- new: support d'un proxy +- fix: support des CBZ contenant du texte +- fix: correction de DEBUG=0 +- fix: correction des histoires importées qui n'arrivent pas immédiatement à l'affichage +- gui: correction pour le focus +- gui: fix pour la couleur d'arrière plan +- gui: fix pour la navigation au clavier (haut et bas) +- gui: configuration beaucoup plus facile +- gui: peut maintenant télécharger toutes les histoires d'un groupe en cache en une fois +- MangaLEL: site web changé +- search: supporte MangaLEL +- search: supporte Fanfiction.net +- FimFictionAPI: correction d'une NPE +- remote: changement du chiffrement because Google +- remote: incompatible avec 2.x +- remote: moins bonnes perfs mais meilleure utilisation de la mémoire +- remote: le log inclus maintenant la date des évènements +- remote: le mot de passe se configure maintenant dans le fichier de configuration + +## Version 2.0.3 + +SoFurry: correction pour les histoires disponibles uniquement aux utilisateurs inscrits sur le site + +## Version 2.0.2 + +- i18n: changer la langue dans les options fonctionne aussi quand $LANG existe +- gui: traduction en français +- gui: ReDownloader ne supprime plus le livre original +- fix: corrections pour le visionneur interne +- fix: quelques corrections pour les traductions + +## Version 2.0.1 + +- core: un changement de titre/source/author n'était pas toujours visible en runtime +- gui: ne recharger les histoires que quand nécessaire + +## Version 2.0.0 + +- new: les sources peuvent contenir "/" (et utiliseront des sous-répertoires en fonction) +- gui: nouvelle page pour voir les propriétés d'une histoire +- gui: renommer les histoires, changer l'auteur +- gui: permet de lister les auteurs ou les sources en mode "tout" ou "listing" +- gui: lecteur intégré pour les histoires (texte et images) +- tui: fonctionne maintenant assez bien que pour être déclaré stable +- cli: permet maintenant de changer la source, le titre ou l'auteur +- remote: fix de setSourceCover (ce n'était pas vu par le client) +- remote: on peut maintenant importer un fichier local +- remote: meilleures perfs +- remote: incompatible avec 1.x +- fix: deadlock dans certains cas rares (nikiroo-utils) +- fix: le résumé n'était pas visibe dans certains cas +- fix: update de nikiroo-utils, meilleures perfs pour le remote +- fix: eHentai content warning + +## Version 1.8.1 + +- e621: les images étaient rangées à l'envers pour les recherches (/post/) +- e621: correction pour /post/search/ +- remote: correction de certains problèmes de timeout +- remote: amélioration des perfs +- fix: permettre les erreurs I/O pour les CBZ (ignore l'image) +- fix: corriger le répertoire des covers par défaut + +## Version 1.8.0 + +- FimfictionAPI: les noms des chapitres sont maintenant triés correctement +- e621: supporte aussi les recherches (/post/) +- remote: la cover est maintenant envoyée au client pour les imports +- MangaLel: support pour MangaLel + +## Version 1.7.1 + +- GUI: fichiers tmp supprimés trop vite en mode GUI +- fix: une histoire sans cover pouvait planter le programme +- ePub: erreur d'import pour un EPUB local sans cover + +## Version 1.7.0 + +- new: utilisation de jsoup pour parser le HTML (pas encore partout) +- update: mise à jour de nikiroo-utils +- android: compatibilité Android +- MangaFox: fix après une mise-à-jour du site +- MangaFox: l'ordre des tomes n'était pas toujours bon +- ePub: correction pour la compatibilité avec certains lecteurs ePub +- remote: correction pour l'utilisation du cache +- fix: TYPE= était parfois mauvais dans l'info-file +- fix: les guillemets n'étaient pas toujours bien ordonnés +- fix: amélioration du lanceur externe (lecteur natif) +- test: plus de tests unitaires +- doc: changelog disponible en français +- doc: man pages (en, fr) +- doc: SysV init script + +## Version 1.6.3 + +- fix: corrections de bugs +- remote: notification de l'état de progression des actions +- remote: possibilité d'envoyer des histoires volumineuses +- remote: détection de l'état du serveur +- remote: import and change source on server +- CBZ: meilleur support de certains CBZ (si SUMMARY ou URL est présent dans le CBZ) +- Library: correction pour les pages de couvertures qui n'étaient pas toujours effacées quand l'histoire l'était +- fix: correction pour certains cas où les images ne pouvaient pas être sauvées (quand on demande un jpeg mais que l'image n'est pas supportée, nous essayons maintenant ensuite en png) +- remote: correction pour certaines images de couvertures qui n'étaient pas trouvées (nikiroo-utils) +- remote: correction pour les images de couvertures qui n'étaient pas transmises + +## Version 1.6.2 + +- GUI: amélioration des barres de progression +- GUI: meilleures performances pour l'ouverture d'une histoire si le type de l'histoire est déjà le type demandé pour l'ouverture (CBZ -> CBZ ou HTML -> HTML par exemple) + +## Version 1.6.1 + +- GUI: nouvelle option (désactivée par défaut) pour afficher un élément par source (type) sur la page de démarrage au lieu de tous les éléments triés par source (type) +- fix: correction de la source (type) qui était remis à zéro après un re-téléchargement +- GUI: affichage du nombre d'images présentes au lieu du nombre de mots pour les histoires en images + +## Version 1.6.0 + +- TUI: un nouveau TUI (mode texte mais avec des fenêtres et des menus en texte) -- cette option n'est pas compilée par défaut (configure.sh) +- remote: un serveur pour offrir les histoires téléchargées sur le réseau +- remote: une Library qui reçoit les histoires depuis un serveur distant +- update: mise à jour de nikiroo-utils +- FimFiction: support for the new API +- new: mise à jour du cache (effacer le cache actuel serait une bonne idée) +- GUI: correction pour le déplacement d'une histoire qui n'est pas encore dans le cache + +## Version 1.5.3 + +- FimFiction: correction pour les tags dans les metadata et la gestion des chapitres pour certaines histoires + +## Version 1.5.2 + +- FimFiction: correction pour les tags dans les metadata + +## Version 1.5.1 + +- FimFiction: mise à jour pour supporter FimFiction 4 +- eHentai: correction pour quelques metadata qui n'étaient pas reprises + +## Version 1.5.0 + +- eHentai: nouveau site supporté sur demande (n'hésitez pas !) : e-hentai.org +- Library: amélioration des performances quand on récupère une histoire (la page de couverture n'est plus chargée quand cela n'est pas nécessaire) +- Library: correction pour les pages de couvertures qui n'étaient pas toujours effacées quand l'histoire l'était +- GUI: amélioration des performances pour l'affichage des histoires (la page de couverture est re-dimensionnée en cache) +- GUI: on peut maintenant éditer la source d'une histoire ("Déplacer vers...") + +## Version 1.4.2 + +- GUI: nouveau menu Options pour configurer le programme (très minimaliste pour le moment) +- new: gestion de la progression des actions plus fluide et avec plus de détails +- fix: meilleur support des couvertures pour les fichiers en cache + +## Version 1.4.1 + +- fix: correction de UpdateChecker qui affichait les nouveautés de TOUTES les versions du programme au lieu de se limiter aux versions plus récentes +- fix: correction de la gestion de certains sauts de ligne pour le support HTML (entre autres, FanFiction.net) +- GUI: les barres de progrès fonctionnent maintenant correctement +- update: mise à jour de nikiroo-utils pour récupérer toutes les étapes dans les barres de progrès +- ( --Fin des nouveautés de la version 1.4.1-- ) + +## Version 1.4.0 + +- new: sauvegarde du nombre de mots et de la date de création des histoires dans les fichiers mêmes +- GUI: nouvelle option pour afficher le nombre de mots plutôt que le nom de l'auteur sous le nom de l'histoire +- CBZ: la première page n'est plus doublée sur les sites n'offrant pas de page de couverture +- GUI: recherche de mise à jour (le programme cherche maintenant si une mise à jour est disponible pour en informer l'utilisateur) + +## Version 1.3.1 + +- GUI: on peut maintenant trier les histoires par auteur + +## Version 1.3.0 + +- YiffStar: le site YiffStar (SoFurry.com) est maintenant supporté +- new: support des sites avec login/password +- GUI: les URLs copiées (ctrl+C) sont maintenant directement proposées par défaut quand on importe une histoire +- GUI: la version est maintenant visible (elle peut aussi être récupérée avec --version) + +## Version 1.2.4 + +- GUI: nouvelle option re-télécharger +- GUI: les histoires sont maintenant triées (et ne changeront plus d'ordre après chaque re-téléchargement) +- fix: corrections sur l'utilisation des guillemets +- fix: corrections sur la détection des chapitres +- new: de nouveaux tests unitaires + +## Version 1.2.3 + +- HTML: les fichiers originaux (info_text) sont maintenant rajoutés quand on sauve +- HTML: support d'un nouveau type de fichiers à l'import: HTML (si fait par Fanfix) + +## Version 1.2.2 + +- GUI: nouvelle option "Sauver sous..." +- GUI: corrections (rafraîchissement des icônes) +- fix: correction de la gestion du caractère TAB dans les messages utilisateurs +- GUI: LocalReader supporte maintenant "--read" +- ePub: corrections sur le CSS + +## Version 1.2.1 + +- GUI: de nouvelles fonctions ont été ajoutées dans le menu +- GUI: popup avec un clic droit sur les histoires +- GUI: corrections, particulièrement pour LocalLibrary +- GUI: nouvelle icône (un rond vert) pour dénoter qu'une histoire est "cachée" (dans le LocalReader) + +## Version 1.2.0 + +- GUI: système de notification de la progression des actions +- ePub: changements sur le CSS +- new: de nouveaux tests unitaires +- GUI: de nouvelles fonctions ont été ajoutées dans le menu (supprimer, rafraîchir, un bouton exporter qui ne fonctionne pas encore) + +## Version 1.1.0 + +- CLI: nouveau système de notification de la progression des actions +- e621: correction pour les "pending pools" qui ne fonctionnaient pas avant +- new: système de tests unitaires ajouté (pas encore de tests propres à Fanfix) + +## Version 1.0.0 + +- GUI: état acceptable pour une 1.0.0 (l'export n'est encore disponible qu'en CLI) +- fix: bugs fixés +- GUI: (forte) amélioration +- new: niveau fonctionnel acceptable pour une 1.0.0 + +## Version 0.9.5 + +- fix: bugs fixés +- new: compatibilité avec WIN32 (testé sur Windows 10) + +## Version 0.9.4 + +- fix: (beaucoup de) bugs fixés +- new: amélioration des performances +- new: moins de fichiers cache utilisés +- GUI: amélioration (pas encore parfait, mais utilisable) + +## Version 0.9.3 + +- fix: (beaucoup de) bugs fixés +- GUI: première implémentation graphique (laide et buggée) + +## Version 0.9.2 + +- new: version minimum de la JVM : Java 1.6 (tous les JAR binaires ont été compilés en Java 1.6) +- fix: bugs fixés + +## Version 0.9.1 + +- version initiale + diff --git a/changelog.md b/changelog.md new file mode 100644 index 0000000..d4dff94 --- /dev/null +++ b/changelog.md @@ -0,0 +1,290 @@ +# Fanfix + +## Version WIP + +- new: http/https remote library +- new: use git submodules instead of sub branches +- new: remove configure.sh, use simple Makefile + +## Version 3.1.2 + +- fix: publication/creation date formated +- e621: fix order again +- e621: option to use a Login/API key to evade blacklists +- e621: better metadata + +## Version 3.1.1 + +- e621: fix chapters in reverse order for pools +- fix: sort the stories by title and not by date +- fix: expose the story name on progress bars + +## Version 3.1.0 + +- MangaHub: a manga website (English) +- MangaFox: removed (too many unfriendly changes, bye) +- e621: fix cover not saved + +## Version 3.0.1 + +- fix: update for e621 (and it is no more a BasicSupport_Deprecated) +- fix: for unsupported URL, do not errors out with a "file://" related message + +## Version 3.0.0 + +- new: now Android-compatible (see [companion project](https://gitlab.com/Rayman22/fanfix-android)) +- new: story search (not all sources yet) +- new: proxy support +- fix: support hybrid CBZ (with text) +- fix: fix DEBUG=0 +- fix: fix imported stories that don't immediatly appear on screen +- gui: focus fix +- gui: bg colour fix +- gui: fix keyboard navigation support (up and down) +- gui: configuration is now much easier +- gui: can now prefetch to cache all the sories of a group at once +- MangaLEL: website has changed +- search: Fanfiction.net support +- search: MangaLEL support +- FimFictionAPI: fix NPE +- remote: encryption mode changed because Google +- remote: not compatible with 2.x +- remote: can now use password from config file +- remote: worse perfs but much better memory usage +- remote: log now includes the time of events + +## Version 2.0.3 + +SoFurry: fix for stories only available to registrated users + +## Version 2.0.2 + +- i18n: setting the language in the option panel now works even with $LANG set +- gui: translated into French +- gui: ReDownload does not delete the original book anymore +- gui: internal viewer fixes +- gui: some translation fixes + +## Version 2.0.1 + +- core: a change of title/source/author was not always visible at runtime +- gui: only reload the stoies when needed + +## Version 2.0.0 + +- new: sources can contain "/" (and will use subdirectories) +- gui: new Properties page for stories +- gui: rename stories, change author +- gui: allow "all" and "listing" modes for sources and authors +- gui: integrated viewer for stories (both text and images) +- tui: now working well enough to be considered stable +- cli: now allow changing the source, title and author +- remote: fix setSourceCover (was not seen by client) +- remote: can now import local files into a remote library +- remote: better perfs +- remote: not compatible with 1.x +- fix: deadlock in some rare cases (nikiroo-utils) +- fix: the resume was not visible in some cases +- fix: update nikiroo-utils, better remote perfs +- fix: eHentai content warning + +## Version 1.8.1 + +- e621: the images were stored in reverse order for searches (/post/) +- e621: fix for /post/search/ +- remote: fix some timeout issues +- remote: improve perfs +- fix: allow I/O errors on CBZ files (skip image) +- fix: fix the default covers directory + +## Version 1.8.0 + +- FimfictionAPI: chapter names are now correctly ordered +- e621: now supports searches (/post/) +- remote: cover is now sent over the network for imported stories +- MangaLel: new support for MangaLel + +## Version 1.7.1 + +- GUI: tmp files deleted too soon in GUI mode +- fix: unavailable cover could cause a crash +- ePub: local EPUB import error when no cover + +## Version 1.7.0 + +- new: use jsoup for parsing HTML (not everywhere yet) +- update nikiroo-utils +- android: Android compatibility +- MangaFox: fix after website update +- MangaFox: tomes order was not always correct +- ePub: fix for compatibility with some ePub viewers +- remote: cache usage fix +- fix: TYPE= not always correct in info-file +- fix: quotes error +- fix: improve external launcher (native viewer) +- test: more unit tests +- doc: changelog available in French +- doc: man pages (en, fr) +- doc: SysV init script + +## Version 1.6.3 + +- fix: bug fixes +- remote: progress report +- remote: can send large files +- remote: detect server state +- remote: import and change source on server +- CBZ: better support for some CBZ files (if SUMMARY or URL files are present in it) +- Library: fix cover images not deleted on story delete +- fix: some images not supported because not jpeg-able (now try again in png) +- remote: fix some covers not found over the wire (nikiroo-utils) +- remote: fix cover image files not sent over the wire + +## Version 1.6.2 + +- GUI: better progress bars +- GUI: can now open files much quicker if they are stored in both library and cache with the same output type + +## Version 1.6.1 + +- GUI: new option (disabled by default) to show one item per source type instead of one item per story when showing ALL sources (which is also the start page) +- fix: source/type reset when redownloading +- GUI: show the number of images instead of the number of words for images documents + +## Version 1.6.0 + +- TUI: new TUI (text with windows and menus) -- not compiled by default (configure.sh) +- remote: a server option to offer stories on the network +- remote: a remote library to get said stories from the network +- update to latest version of nikiroo-utils +- FimFiction: support for the new API +- new: cache update (you may want to clear your current cache) +- GUI: bug fixed (moving an unopened book does not fail any more) + +## Version 1.5.3 + +- FimFiction: Fix tags and chapter handling for some stories + +## Version 1.5.2 + +- FimFiction: Fix tags metadata on FimFiction 4 + +## Version 1.5.1 + +- FimFiction: Update to FimFiction 4 +- eHentai: Fix some meta data that were missing + +## Version 1.5.0 + +- eHentai: new website supported on request (do not hesitate!): e-hentai.org +- Library: perf improvement when retrieving the stories (cover not loaded when not needed) +- Library: fix the covers that were not always removed when deleting a story +- GUI: perf improvement when displaying books (cover resized then cached) +- GUI: sources are now editable ("Move to...") + +## Version 1.4.2 + +- GUI: new Options menu to configure the program (minimalist for now) +- new: improve progress reporting (smoother updates, more details) +- fix: better cover support for local files + +## Version 1.4.1 + +- fix: UpdateChecker which showed the changes of ALL versions instead of the newer ones only +- fix: some bad line breaks on HTML support (including FanFiction.net) +- GUI: progress bar now working correctly +- update: nikiroo-utils update to show all steps in the progress bars +- ( --End of changes for version 1.4.1-- ) + +## Version 1.4.0 + +- new: remember the word count and the date of creation of Fanfix stories +- GUI: option to show the word count instead of the author below the book title +- CBZ: do not include the first page twice anymore for no-cover websites +- GUI: update version check (we now check for new versions) + +## Version 1.3.1 + +- GUI: can now display books by Author + +## Version 1.3.0 + +- YiffStar: YiffStar (SoFurry.com) is now supported +- new: supports login/password websites +- GUI: copied URLs (ctrl+C) are selected by default when importing a URL +- GUI: version now visible (also with --version) + +## Version 1.2.4 + +- GUI: new option: Re-download +- GUI: books are now sorted (will not jump around after refresh/redownload) +- fix: quote character handling +- fix: chapter detection +- new: more tests included + +## Version 1.2.3 + +- HTML: include the original (info_text) files when saving +- HTML: new input type supported: HTML files made by Fanfix + +## Version 1.2.2 + +- GUI: new "Save as..." option +- GUI: fixes (icon refresh) +- fix: handling of TABs in user messages +- GUI: LocalReader can now be used with --read +- ePub: CSS style fixes + +## Version 1.2.1 + +- GUI: some menu functions added +- GUI: right-click popup menu added +- GUI: fixes, especially for the LocalReader library +- GUI: new green round icon to denote "cached" (into LocalReader library) files + +## Version 1.2.0 + +- GUI: progress reporting system +- ePub: CSS style changes +- new: unit tests added +- GUI: some menu functions added (delete, refresh, a place-holder for export) + +## Version 1.1.0 + +- CLI: new Progress reporting system +- e621: fix on "pending" pools, which were not downloaded before +- new: unit tests system added (but no test yet, as all tests were moved into nikiroo-utils) + +## Version 1.0.0 + +- GUI: it is now good enough to be released (export is still CLI-only though) +- fix: bug fixes +- GUI: improved (a lot) +- new: should be good enough for 1.0.0 + +## Version 0.9.5 + +- fix: bug fixes +- new: WIN32 compatibility (tested on Windows 10) + +## Version 0.9.4 + +- fix: bug fixes (lots of) +- new: perf improved +- new: use less cache files +- GUI: improvement (still not really OK, but OK enough I guess) + +## Version 0.9.3 + +- fix: bug fixes (lots of) +- GUI: first implementation (which is ugly and buggy -- the buggly GUI) + +## Version 0.9.2 + +- new: minimum JVM version: Java 1.6 (all binary JAR files will be released in 1.6) +- fix: bug fixes + +## Version 0.9.1 + +- initial version + diff --git a/data/Chapter.java b/data/Chapter.java deleted file mode 100644 index d490058..0000000 --- a/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/data/JsonIO.java b/data/JsonIO.java deleted file mode 100644 index 501a0d9..0000000 --- a/data/JsonIO.java +++ /dev/null @@ -1,431 +0,0 @@ -package be.nikiroo.fanfix.data; - -import java.util.ArrayList; -import java.util.List; - -import org.json.JSONArray; -import org.json.JSONException; -import org.json.JSONObject; - -import be.nikiroo.fanfix.data.Paragraph.ParagraphType; -import be.nikiroo.utils.Progress; - -public class JsonIO { - static public JSONObject toJson(MetaData meta) { - if (meta == null) { - return null; - } - - JSONObject json = new JSONObject(); - - put(json, "", MetaData.class.getName()); - put(json, "luid", meta.getLuid()); - put(json, "title", meta.getTitle()); - put(json, "author", meta.getAuthor()); - put(json, "source", meta.getSource()); - put(json, "url", meta.getUrl()); - put(json, "words", meta.getWords()); - put(json, "creation_date", meta.getCreationDate()); - put(json, "date", meta.getDate()); - put(json, "lang", meta.getLang()); - put(json, "publisher", meta.getPublisher()); - put(json, "subject", meta.getSubject()); - put(json, "type", meta.getType()); - put(json, "uuid", meta.getUuid()); - put(json, "fake_cover", meta.isFakeCover()); - put(json, "image_document", meta.isImageDocument()); - - put(json, "resume", toJson(meta.getResume())); - put(json, "tags", new JSONArray(meta.getTags())); - - return json; - } - - /** - * // no image - * - * @param json - * - * @return - * - * @throws JSONException - * when it cannot be converted - */ - static public MetaData toMetaData(JSONObject json) { - if (json == null) { - return null; - } - - MetaData meta = new MetaData(); - - meta.setLuid(getString(json, "luid")); - meta.setTitle(getString(json, "title")); - meta.setAuthor(getString(json, "author")); - meta.setSource(getString(json, "source")); - meta.setUrl(getString(json, "url")); - meta.setWords(getLong(json, "words", 0)); - meta.setCreationDate(getString(json, "creation_date")); - meta.setDate(getString(json, "date")); - meta.setLang(getString(json, "lang")); - meta.setPublisher(getString(json, "publisher")); - meta.setSubject(getString(json, "subject")); - meta.setType(getString(json, "type")); - meta.setUuid(getString(json, "uuid")); - meta.setFakeCover(getBoolean(json, "fake_cover", false)); - meta.setImageDocument(getBoolean(json, "image_document", false)); - - meta.setResume(toChapter(getJson(json, "resume"))); - meta.setTags(toListString(getJsonArr(json, "tags"))); - - return meta; - } - - static public JSONObject toJson(Story story) { - if (story == null) { - return null; - } - - JSONObject json = new JSONObject(); - put(json, "", Story.class.getName()); - put(json, "meta", toJson(story.getMeta())); - - List chapters = new ArrayList(); - for (Chapter chap : story) { - chapters.add(toJson(chap)); - } - put(json, "chapters", new JSONArray(chapters)); - - return json; - } - - /** - * - * @param json - * - * @return - * - * @throws JSONException - * when it cannot be converted - */ - static public Story toStory(JSONObject json) { - if (json == null) { - return null; - } - - Story story = new Story(); - story.setMeta(toMetaData(getJson(json, "meta"))); - story.setChapters(toListChapter(getJsonArr(json, "chapters"))); - - return story; - } - - static public JSONObject toJson(Chapter chap) { - if (chap == null) { - return null; - } - - JSONObject json = new JSONObject(); - put(json, "", Chapter.class.getName()); - put(json, "name", chap.getName()); - put(json, "number", chap.getNumber()); - put(json, "words", chap.getWords()); - - List paragraphs = new ArrayList(); - for (Paragraph para : chap) { - paragraphs.add(toJson(para)); - } - put(json, "paragraphs", new JSONArray(paragraphs)); - - return json; - } - - /** - * - * @param json - * - * @return - * - * @throws JSONException - * when it cannot be converted - */ - static public Chapter toChapter(JSONObject json) { - if (json == null) { - return null; - } - - Chapter chap = new Chapter(getInt(json, "number", 0), - getString(json, "name")); - chap.setWords(getLong(json, "words", 0)); - - chap.setParagraphs(toListParagraph(getJsonArr(json, "paragraphs"))); - - return chap; - } - - // no images - static public JSONObject toJson(Paragraph para) { - if (para == null) { - return null; - } - - JSONObject json = new JSONObject(); - - put(json, "", Paragraph.class.getName()); - put(json, "content", para.getContent()); - put(json, "words", para.getWords()); - - put(json, "type", para.getType().toString()); - - return json; - } - - /** - * // no images - * - * @param json - * - * @return - * - * @throws JSONException - * when it cannot be converted - */ - static public Paragraph toParagraph(JSONObject json) { - if (json == null) { - return null; - } - - Paragraph para = new Paragraph( - ParagraphType.valueOf(getString(json, "type")), - getString(json, "content"), getLong(json, "words", 0)); - - return para; - } - - // only supported option: a MetaData called "meta" - static public JSONObject toJson(Progress pg) { - return toJson(pg, null); - } - - // only supported option: a MetaData called "meta" - static private JSONObject toJson(Progress pg, Double weight) { - if (pg == null) { - return null; - } - - // Supported keys: meta (only keep the key on the main parent, where - // weight is NULL) - MetaData meta = null; - if (weight == null) { - Object ometa = pg.get("meta"); - if (ometa instanceof MetaData) { - meta = getMetaLight((MetaData) ometa); - } - } - // - - JSONObject json = new JSONObject(); - - put(json, "", Progress.class.getName()); - put(json, "name", pg.getName()); - put(json, "min", pg.getMin()); - put(json, "max", pg.getMax()); - put(json, "progress", pg.getRelativeProgress()); - put(json, "weight", weight); - put(json, "meta", meta); - - List children = new ArrayList(); - for (Progress child : pg.getChildren()) { - children.add(toJson(child, pg.getWeight(child))); - } - put(json, "children", new JSONArray(children)); - - return json; - } - - // only supported option: a MetaData called "meta" - static public Progress toProgress(JSONObject json) { - if (json == null) { - return null; - } - - Progress pg = new Progress( // - getString(json, "name"), // - getInt(json, "min", 0), // - getInt(json, "max", 100) // - ); - - pg.setRelativeProgress(getDouble(json, "progress", 0)); - - Object meta = getObject(json, "meta"); - if (meta != null) { - pg.put("meta", meta); - } - - JSONArray jchildren = getJsonArr(json, "children"); - for (int i = 0; i < jchildren.length(); i++) { - try { - JSONObject jchild = jchildren.getJSONObject(i); - Double weight = getDouble(jchild, "weight", 0); - pg.addProgress(toProgress(jchild), weight); - } catch (Exception e) { - } - } - - return pg; - } - - static public List toListString(JSONArray array) { - if (array != null) { - List values = new ArrayList(); - for (int i = 0; i < array.length(); i++) { - values.add(array.getString(i)); - } - return values; - } - - return null; - } - - static public List toListParagraph(JSONArray array) { - if (array != null) { - List values = new ArrayList(); - for (int i = 0; i < array.length(); i++) { - JSONObject value = array.getJSONObject(i); - values.add(toParagraph(value)); - } - return values; - } - - return null; - } - - static private List toListChapter(JSONArray array) { - if (array != null) { - List values = new ArrayList(); - for (int i = 0; i < array.length(); i++) { - JSONObject value = array.getJSONObject(i); - values.add(toChapter(value)); - } - return values; - } - - return null; - } - - static private void put(JSONObject json, String key, Object o) { - json.put(key, o == null ? JSONObject.NULL : o); - } - - static private Object getObject(JSONObject json, String key) { - if (json.has(key)) { - try { - return json.get(key); - } catch (Exception e) { - // Can fail if content was NULL! - } - } - - return null; - } - - static private String getString(JSONObject json, String key) { - Object o = getObject(json, key); - if (o instanceof String) - return (String) o; - - return null; - } - - static private long getLong(JSONObject json, String key, long def) { - Object o = getObject(json, key); - if (o instanceof Byte) - return (Byte) o; - if (o instanceof Short) - return (Short) o; - if (o instanceof Integer) - return (Integer) o; - if (o instanceof Long) - return (Long) o; - - return def; - } - - static private double getDouble(JSONObject json, String key, double def) { - Object o = getObject(json, key); - if (o instanceof Byte) - return (Byte) o; - if (o instanceof Short) - return (Short) o; - if (o instanceof Integer) - return (Integer) o; - if (o instanceof Long) - return (Long) o; - if (o instanceof Float) - return (Float) o; - if (o instanceof Double) - return (Double) o; - - return def; - } - - static private boolean getBoolean(JSONObject json, String key, - boolean def) { - Object o = getObject(json, key); - if (o instanceof Boolean) { - return (Boolean) o; - } - - return def; - } - - static private int getInt(JSONObject json, String key, int def) { - Object o = getObject(json, key); - if (o instanceof Byte) - return (Byte) o; - if (o instanceof Short) - return (Short) o; - if (o instanceof Integer) - return (Integer) o; - if (o instanceof Long) { - try { - return (int) (long) ((Long) o); - } catch (Exception e) { - } - } - - return def; - } - - static private JSONObject getJson(JSONObject json, String key) { - Object o = getObject(json, key); - if (o instanceof JSONObject) { - return (JSONObject) o; - } - - return null; - } - - static private JSONArray getJsonArr(JSONObject json, String key) { - Object o = getObject(json, key); - if (o instanceof JSONArray) { - return (JSONArray) o; - } - - return null; - } - - // null -> null - static private MetaData getMetaLight(MetaData meta) { - MetaData light = null; - if (meta != null) { - if (meta.getCover() == null) { - light = meta; - } else { - light = meta.clone(); - light.setCover(null); - } - } - - return light; - } -} diff --git a/data/MetaData.java b/data/MetaData.java deleted file mode 100644 index 5989604..0000000 --- a/data/MetaData.java +++ /dev/null @@ -1,576 +0,0 @@ -package be.nikiroo.fanfix.data; - -import java.io.Serializable; -import java.util.ArrayList; -import java.util.List; - -import be.nikiroo.fanfix.supported.SupportType; -import be.nikiroo.utils.Image; -import be.nikiroo.utils.StringUtils; - -/** - * The meta data associated to a {@link Story} object. - *

- * Note that some earlier version of the program did not save the resume as an - * external file; for those stories, the resume is not fetched until the story - * is. - *

- * The cover is never fetched until the story is. - * - * @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; - - /** - * Create an empty {@link MetaData}. - */ - public MetaData() { - } - - /** - * 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, we try to use "YYYY-mm-dd" when possible. - * - * @return the date - */ - public String getDate() { - return date; - } - - /** - * The story publication date, we try to use "YYYY-mm-dd" when possible. - * - * @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}. - *

- * Note that some earlier version of the program did not save the resume as - * an external file; for those stories, the resume is not fetched until the - * story is. - * - * @return the resume - */ - public Chapter getResume() { - return resume; - } - - /** - * The story resume (a.k.a. description). - *

- * Note that some earlier version of the program did not save the resume as - * an external file; for those stories, the resume is not fetched until the - * story is. - * - * @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). - *

- * The cover is not fetched until the story is. - * - * @return the cover - */ - public Image getCover() { - return cover; - } - - /** - * The cover image of the story, if any (can be NULL). - *

- * The cover is not fetched until the story is. - * - * @param cover - * the cover to set - */ - public void setCover(Image cover) { - this.cover = cover; - } - - /** - * 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...). - * - * @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 -- a very user-visible piece of data. - *

- * It is initialised with the same value as {@link MetaData#getPublisher()}, - * but the user is allowed to change it into any value -- this is a sort of - * 'category'. - * - * @return the source - */ - public String getSource() { - return source; - } - - /** - * The source of this story -- a very user-visible piece of data. - *

- * It is initialised with the same value as {@link MetaData#getPublisher()}, - * but the user is allowed to change it into any value -- this is a sort of - * 'category'. - * - * @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 (usually a - * numerical value 0-padded with a minimum size of 3; but this is subject to - * change and you can also obviously have more than 1000 stories -- - * a luid may potentially be anything else, including non-numeric - * characters). - *

- * A NULL or empty luid represents an incomplete, corrupted or fake - * {@link Story}. - * - * @return the luid - */ - public String getLuid() { - return luid; - } - - /** - * A unique value representing the story in the local library (usually a - * numerical value 0-padded with a minimum size of 3; but this is subject to - * change and you can also obviously have more than 1000 stories -- - * a luid may potentially be anything else, including non-numeric - * characters). - *

- * A NULL or empty luid represents an incomplete, corrupted or fake - * {@link Story}. - * - * @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 -- which is also the user representation of the - * output type this {@link Story} is in (see {@link SupportType}). - *

- * It allows you to know where the {@link Story} comes from, and is not - * supposed to change, even when re-imported. - *

- * It's the user representation of the enum - * ({@link SupportType#getSourceName()}, not - * {@link SupportType#toString()}). - * - * @return the publisher - */ - public String getPublisher() { - return publisher; - } - - /** - * The story publisher -- which is also the user representation of the - * output type this {@link Story} is in (see {@link SupportType}). - *

- * It allows you to know where the {@link Story} comes from, and is not - * supposed to change, even when re-imported. - *

- * It's the user representation of the enum - * ({@link SupportType#getSourceName()}, not - * {@link SupportType#toString()}). - * - * @param publisher - * the publisher to set - */ - public void setPublisher(String publisher) { - this.publisher = publisher; - } - - /** - * The output type this {@link Story} is in (see {@link SupportType}). - *

- * It allows you to know where the {@link Story} comes from, and is supposed - * to only change when it is imported anew. - *

- * It's the direct representation of the enum - * ({@link SupportType#toString()}, not - * {@link SupportType#getSourceName()}). - * - * @return the type the type - */ - public String getType() { - return type; - } - - /** - * The output type this {@link Story} is in (see {@link SupportType}). - *

- * It allows you to know where the {@link Story} comes from, and is supposed - * to only change when it is imported anew. - *

- * It's the direct representation of the enum - * ({@link SupportType#toString()}, not - * {@link SupportType#getSourceName()}). - * - * @param type - * the new type to set - */ - public void setType(String type) { - this.type = type; - } - - /** - * Document catering mostly to image files. - *

- * I.E., this is a comics or a manga, not a textual story with actual words. - *

- * In image documents, all the paragraphs are supposed to be images. - * - * @return the imageDocument state - */ - public boolean isImageDocument() { - return imageDocument; - } - - /** - * Document catering mostly to image files. - *

- * I.E., this is a comics or a manga, not a textual story with actual words. - *

- * In image documents, all the paragraphs are supposed to be images. - * - * @param imageDocument - * the imageDocument state to set - */ - public void setImageDocument(boolean imageDocument) { - this.imageDocument = imageDocument; - } - - /** - * The number of words (or images if this is an image document -- see - * {@link MetaData#isImageDocument()}) in the related {@link Story}. - * - * @return the number of words/images - */ - public long getWords() { - return words; - } - - /** - * The number of words (or images if this is an image document -- see - * {@link MetaData#isImageDocument()}) in the related {@link Story}. - * - * @param words - * the number of words/images to set - */ - public void setWords(long words) { - this.words = words; - } - - /** - * The (Fanfix) {@link Story} creation date, i.e., when the {@link Story} - * was fetched via Fanfix. - * - * @return the creation date - */ - public String getCreationDate() { - return creationDate; - } - - /** - * The (Fanfix) {@link Story} creation date, i.e., when the {@link Story} - * was fetched via Fanfix. - * - * @param creationDate - * the creation date to set - */ - public void setCreationDate(String creationDate) { - this.creationDate = creationDate; - } - - /** - * The cover in this {@link MetaData} object is "fake", in the sense 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 sense 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\tWord count: [%s]" - + "\n\tResume: [%s]\n\tCover: [%s]", - luid, title, getAuthor(), getDate(), tags.toString(), - "" + words, resume, cover); - } -} diff --git a/data/Paragraph.java b/data/Paragraph.java deleted file mode 100644 index d5a0f1c..0000000 --- a/data/Paragraph.java +++ /dev/null @@ -1,182 +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 content of this {@link Paragraph} if it is an image. - * - * @param contentImage - * the content - */ - public void setContentImage(Image contentImage) { - this.contentImage = 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/data/Story.java b/data/Story.java deleted file mode 100644 index fc3f909..0000000 --- a/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/data/package-info.java b/data/package-info.java deleted file mode 100644 index 57db36b..0000000 --- a/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/derename.sh b/derename.sh deleted file mode 100755 index 6c8cbff..0000000 --- a/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/doc.d b/doc.d new file mode 100644 index 0000000..a747524 --- /dev/null +++ b/doc.d @@ -0,0 +1,33 @@ +# Requires variable: NAME + +.PHONY: doc clean mrpropre mrproper + +doc: VERSION Doxyfile + @if doxygen -v >/dev/null 2>&1; then \ + echo Adding VERSION number to Doxyfile...; \ + tmp=`mktemp`; \ + grep -v '^PROJECT_NUMBER' Doxyfile > "$$tmp"; \ + cat "$$tmp" > Doxyfile; \ + rm -f "$$tmp"; \ + echo "PROJECT_NUMBER = `cat VERSION`" >> Doxyfile; \ + echo doxygen; \ + doxygen; \ + else \ + echo "man pages generation: pandoc required" >&2; \ + false; \ + fi; \ + +clean: + @( \ + echo Removing VERSION number from Doxyfile...; \ + tmp=`mktemp`; \ + grep -v '^PROJECT_NUMBER' Doxyfile > "$$tmp"; \ + cat "$$tmp" > Doxyfile; \ + rm -f "$$tmp"; \ + ); + +mrproper: mrpropre +mrpropre: + rm -rf doc/html doc/latex doc/man + rmdir doc 2>/dev/null || true + diff --git a/docs/android/android.md b/docs/android/android.md new file mode 100644 index 0000000..f3d2775 --- /dev/null +++ b/docs/android/android.md @@ -0,0 +1,289 @@ +# Android UI mock-up + +## Concepts + +### Story + +We have **Stories** in Fanfix, which represent a story (an "epub", ...) or a comics (a "CBZ" file, ...). + +A story is written by an author, comes from a source and is dentified by a LUID (Local Unique ID). +It can be a text story or an image story. + +The source can actually be changed by the user (this is the main sorting key). + +### Book + +We also have **Books**. + +Books can be used to display: + +- All the sources present in the library +- All the authors known in the lbrary +- Stories sharing a source or an author + +### All and Listing modes + +When representing sources or authors, books can be arranged in two modes: + +- "All" mode : all the sources/authors are represented by a book and displayed in the UI +- "Listing" mode : for each source/author, a group of books representing the corresponding story is present with the name of the source/author to group them (for instance, a JLabel on top of the group) + +Note: Listing mode can be left out of the Android version if needed (but the all mode is really needed). + +### Destination + +What I call here a destination is a specific group of books. + +Examples : + +- All the sources +- All the books of author "Tulipe, F." +- A listing of all the authors and their stories + +## Core + +### Library (main screen) + +![Main library](screens/main_lib.jpg) + +#### Header + +The header has a title, a navigation icon on the left and a search icon. + +Title can vary upon the current displayed books: + +- All sources +- Sources listing +- Source: xxx +- All authors +- Authors listing +- Author: xxx + +The navigation icon open the Navigation drawer. + +##### Search + +![Search/Filter](screens/search.jpg) + +The search icon is actually a filter: it will hide all the books that don't contain the given text (search on LUID, title and author). + +#### List + +This list will hold books. Each item will be represented by : + +- a cover image (which is provided by fanfix.jar) +- a main info, which can be: + - for stories, the story title + - for source books, the source name + - for author books, the author name +- a secondary info, which can vary via a setting (see the Options page) between: + - author name (for a book representing an author, it is left blank) + - a count (a word count for text stories, an image count for images stories, a stories count for sources and authors books) + +#### UI + +Material.IO: + +- Title, navigation icon, search icon : [App bar top](https://material.io/design/components/app-bars-top.html) +- List : [Cards](https://material.io/design/components/cards.html) + +A tap will open the target book in full-screen mode (i.e., the details about the card). + +On the detailed card, you will see the description (see Description Page) and 3 buttons : + +- Open +- Delete +- "..." for a menu + +### Navigation drawer + +![Navigation Drawer](screens/navigation.jpg) + +The navigation drawer will list 4 destinations: + +- All the sources +- Listing of the sources +- All the authors +- Listing of the authors +- By source + +...and 2 foldable "panels" with more destinations: + +- By source +- By author + +Those subpanels will either contain the sources/authors **or** sub-subpanels with sources/authors. +See fanfix.jar (BasicLibrary.getSourcesGrouped() and BasicLibrary.getAuthorsGrouped()). + +Note: if those last two cause problems, they can be removed; the first four options would be enough to cover the main use cases. + +#### UI + +Material.IO: + +- Navigation drawer: navigation drawer + +### Context menu + +![Context Menu](screens/menu.jpg) + +The context menu options are as follow for stories: + +- Open : open the book (= internal or external reader) +- Rename... : ask the user for a new title for the story (default is current name) +- Move to >> : show a "new source..." option as well as the current ones fo quick select (BasicLibrary.getSourcesGrouped() will return all the sources on only one level if their number is small enough) + - * + - [new source...] + - [A-H] + - Anima + - Fanfiction.NET + - [I-Z] + - MangaFox +- Change author to >> : show a "new author..." option as well as the current ones fo quick select (BasicLibrary.getAuthorsGrouped() will return all the authors on only one level if their number is small enough) + - * + - [new author...] + - [0-9] + - 5-stars + - [A-H] + - Albert + - Béatrice + - Hakan + - [I-Z] + - Irma + - Zoul +- Delete : delete the story +- Redownload : redownload the story (will **not** delete the original) +- Properties : open the properties page + +For other books (sources and authors): + +- Open: open the book (= go to the selected destination) + +#### UI + +Material.IO: + +- menu: [menu](https://developer.android.com/guide/topics/ui/menus.html) + +The menu will NOT use sublevels but link to a [list](https://material.io/design/components/lists.html) instead. + +### Description page + +![Description Page](screens/desc.jpg) + +#### Header + +Use the same cover image as the books, and the description key/values comes from BasicReader.getMetaDesc(MetaData). + +#### Description + +Simply display Story.getMeta().getResume(), without adding the chapter number (it is always 0 for description). + +An example can be seen in be.nikiroo.fanfix.ui.GuiReaderViewerTextOutput.java. + +### Options page + +![Options Page](screens/options.jpg) + +It consists of a "Remote Library" panel: + +- enable : an option to enable/disable the remote library (if disabled, use the local library instead) +- server : (only enabled if the remote library is) the remote server host +- port : (only enabled if the remote library is) the remote server port +- key : (only enabled if the remote library is) the remote server secret key + +...and 5 other options: + +- Open CBZ files internally : open CBZ files with the internal viewer +- Open epub files internally : open EPUB files with the internal viewer +- Show handles on image viewer : can hide the handles used as cues in the image viewer to know where to click +- Startup screen : select the destination to show on application startup +- Language : select the language to use + +#### Startup screen + +Can be: + +- Sources + - All + - Listing +- Authors + - All + - Listing + +...but will have to be presented in a better way to the user (i.e., better names). + +#### UI + +Material.IO: + +- the page itself : Temporary UI Region +- the options : Switch +- the languages : Exposed Dropdown Menu +- the text fields : the default for text fields +- the scret key field : the default for passwords (with * * * ) + +## Internal viewer + +The program will have an internal viewer that will be able to display the 2 kinds of stories (images and text). + +### Base viewer + +This is common to both of the viewer (this is **not** an architectural directives, I only speak about the concept here). + +![Base Viewer](screens/viewer.jpg) + +#### Header + +The title is the title of the story, shortened with "..." if too long. + +#### Content + +This area will host the text viewer or the image viewer. + +#### Navigator + +It contains 4 action buttons (first, previous, next and last chapter) and the title of the current chapter: + +- Descripton : for the properties page (same layout as the actual Properties page) +- Chapter X/Y: title : for the normal chapters (note that "Chapter X/Y" should be bold, and "X" should be coloured) + +#### UI + +Matrial.IO: + +- Header : Header +- Navigator : [Sheets bottom](https://material.io/design/components/sheets-bottom.html) + +### Text viewer + +![Text Viewer](screens/viewer-text.jpg) + +It will contain the content of the current chapter (Story.getChapters().get(index - 1)). + +Same layout as the Properties page uses for the resume, with just a small difference: the chapter name is now prefixed by "Chaper X: ". + +### Image viewer + +![Image Viewer](screens/viewer-image.jpg) + +#### Image + +Auto-zoom and fit (keep aspect ratio). + +#### Image counter + +Just display "Image X/Y" + +#### Handles + +This is a simple cue to show the user where to click. + +It can be hidden via the option "Show handles on image viewer" from the Options page. + +#### UI + +Pinch & Zoom should be allowed. + +Drag-to-pan should be allowed. + diff --git a/docs/android/screens/desc.jpg b/docs/android/screens/desc.jpg new file mode 100755 index 0000000..766b746 Binary files /dev/null and b/docs/android/screens/desc.jpg differ diff --git a/docs/android/screens/main_lib.jpg b/docs/android/screens/main_lib.jpg new file mode 100755 index 0000000..e105824 Binary files /dev/null and b/docs/android/screens/main_lib.jpg differ diff --git a/docs/android/screens/menu.jpg b/docs/android/screens/menu.jpg new file mode 100755 index 0000000..ea67163 Binary files /dev/null and b/docs/android/screens/menu.jpg differ diff --git a/docs/android/screens/navigation.jpg b/docs/android/screens/navigation.jpg new file mode 100755 index 0000000..997cb32 Binary files /dev/null and b/docs/android/screens/navigation.jpg differ diff --git a/docs/android/screens/options.jpg b/docs/android/screens/options.jpg new file mode 100755 index 0000000..b4ad836 Binary files /dev/null and b/docs/android/screens/options.jpg differ diff --git a/docs/android/screens/search.jpg b/docs/android/screens/search.jpg new file mode 100755 index 0000000..f32257e Binary files /dev/null and b/docs/android/screens/search.jpg differ diff --git a/docs/android/screens/viewer-image.jpg b/docs/android/screens/viewer-image.jpg new file mode 100755 index 0000000..8f5c742 Binary files /dev/null and b/docs/android/screens/viewer-image.jpg differ diff --git a/docs/android/screens/viewer-text.jpg b/docs/android/screens/viewer-text.jpg new file mode 100755 index 0000000..574f688 Binary files /dev/null and b/docs/android/screens/viewer-text.jpg differ diff --git a/docs/android/screens/viewer.jpg b/docs/android/screens/viewer.jpg new file mode 100755 index 0000000..d8b130a Binary files /dev/null and b/docs/android/screens/viewer.jpg differ diff --git a/fanfix.sysv b/fanfix.sysv new file mode 100755 index 0000000..5ab6912 --- /dev/null +++ b/fanfix.sysv @@ -0,0 +1,91 @@ +#!/bin/sh +# +# fanfix This starts the Fanfix remote service. +# +# description: Starts the Fanfix remote service +# +### BEGIN INIT INFO +# Default-Start: 3 4 5 +# Short-Description: Fanfix service +# Description: Starts the Fanfix remote service +### END INIT INFO + +ENABLED=true +USER=fanfix +JAR=/path/to/fanfix.jar + +FPID=/tmp/fanfix.pid +OUT=/var/log/fanfix +ERR=/var/log/fanfix.err + +if [ "$ENABLED" != true ]; then + [ "$1" != status ] + exit $? +fi + +if [ ! -e "$JAR" ]; then + echo "Canot find main jar file: $JAR" >&2 + exit 4 +fi + +case "$1" in +start) + if sh "$0" status --quiet; then + echo "Fanfix is already running." >&2 + false + else + [ -e "$OUT" ] && mv "$OUT" "$OUT".previous + [ -e "$ERR" ] && mv "$ERR" "$ERR".previous + sudo -u "$USER" -- java -jar "$JAR" --server > "$OUT" 2> "$ERR" & + echo $! > "$FPID" + fi + + sleep 0.1 + sh "$0" status --quiet +;; +stop) + if sh "$0" status --quiet; then + sudo -u "$USER" -- java -jar "$JAR" --stop-server + fi + + i=1 + while [ $i -lt 100 ]; do + if sh "$0" status --quiet; then + echo -n . >&2 + sleep 1 + fi + i=`expr $i + 1` + done + echo >&2 + + if sh "$0" status --quiet; then + echo "Process not responding, killing it..." >&2 + kill "`cat "$FPID"`" + sleep 10 + kill -9 "`cat "$FPID"`" 2>/dev/null + fi + + rm -f "$FPID" +;; +restart) + sh "$0" stop + sh "$0" start +;; +status) + if [ -e "$FPID" ]; then + if [ "$2" = "--quiet" ]; then + ps "`cat "$FPID"`" >/dev/null + else + ps "`cat "$FPID"`" >/dev/null \ + && echo service is running >&2 + fi + else + false + fi +;; +*) + echo $"Usage: $0 {start|stop|status|restart}" >&2 + false +;; +esac + diff --git a/library/web/icon_alternative.png b/icons/fanfix-alt.png similarity index 100% rename from library/web/icon_alternative.png rename to icons/fanfix-alt.png diff --git a/library/web/icon_default.png b/icons/fanfix.png similarity index 100% rename from library/web/icon_default.png rename to icons/fanfix.png diff --git a/library/web/icon_magic_book.png b/icons/mlpfim-icons.deviantart.com/janswer.deviantart.com/fanfix-d.png similarity index 100% rename from library/web/icon_magic_book.png rename to icons/mlpfim-icons.deviantart.com/janswer.deviantart.com/fanfix-d.png diff --git a/library/web/icon_pony_book.png b/icons/mlpfim-icons.deviantart.com/laceofthemoon.deviantart.com/fanfix-e.png similarity index 100% rename from library/web/icon_pony_book.png rename to icons/mlpfim-icons.deviantart.com/laceofthemoon.deviantart.com/fanfix-e.png diff --git a/library/web/icon_pony_library.png b/icons/mlpfim-icons.deviantart.com/pink618.deviantart.com/fanfix-c.png similarity index 100% rename from library/web/icon_pony_library.png rename to icons/mlpfim-icons.deviantart.com/pink618.deviantart.com/fanfix-c.png diff --git a/library/BasicLibrary.java b/library/BasicLibrary.java deleted file mode 100644 index f77d0ed..0000000 --- a/library/BasicLibrary.java +++ /dev/null @@ -1,998 +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.LinkedHashMap; -import java.util.List; -import java.util.Map; - -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, - /** You are not allowed to access this library. */ - UNAUTHORIZED, - /** The library is invalid, and will never work as is. */ - INVALID, - /** The library is currently out of commission, but may work later. */ - 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; - - /** - * Retrieve the list of {@link MetaData} known by this {@link BasicLibrary} - * in a easy-to-filter version. - * - * @param pg - * the optional {@link Progress} - * @return the list of {@link MetaData} as a {@link MetaResultList} you can - * query - * @throws IOException - * in case of I/O eror - */ - public MetaResultList getList(Progress pg) throws IOException { - // TODO: ensure it is the main used interface - - return new MetaResultList(getMetas(pg)); - } - - // TODO: make something for (normal and custom) non-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 String 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(@SuppressWarnings("unused") 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 - */ - @SuppressWarnings("unused") - public void clearFromCache(String luid) throws IOException { - // By default, this is a noop. - } - - /** - * @return the same as getList() - * @throws IOException - * in case of I/O error - * @deprecated please use {@link BasicLibrary#getList()} and - * {@link MetaResultList#getSources()} instead. - */ - @Deprecated - public List getSources() throws IOException { - return getList().getSources(); - } - - /** - * @return the same as getList() - * @throws IOException - * in case of I/O error - * @deprecated please use {@link BasicLibrary#getList()} and - * {@link MetaResultList#getSourcesGrouped()} instead. - */ - @Deprecated - public Map> getSourcesGrouped() throws IOException { - return getList().getSourcesGrouped(); - } - - /** - * @return the same as getList() - * @throws IOException - * in case of I/O error - * @deprecated please use {@link BasicLibrary#getList()} and - * {@link MetaResultList#getAuthors()} instead. - */ - @Deprecated - public List getAuthors() throws IOException { - return getList().getAuthors(); - } - - /** - * @return the same as getList() - * @throws IOException - * in case of I/O error - * @deprecated please use {@link BasicLibrary#getList()} and - * {@link MetaResultList#getAuthorsGrouped()} instead. - */ - @Deprecated - public Map> getAuthorsGrouped() throws IOException { - return getList().getAuthorsGrouped(); - } - - /** - * 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}. - *

- * Note that it will update both the cover and the resume in meta. - * - * @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 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}. - *

- * Note that it will update both the cover and the resume in meta. - * - * @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 { - return imprt(url, null, pg); - } - - /** - * Import the {@link Story} at the given {@link URL} into the - * {@link BasicLibrary}. - * - * @param url - * the {@link URL} to import - * @param luid - * the LUID to use - * @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 - */ - MetaData imprt(URL url, String luid, 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), luid, pgSave); - 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(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(); - } - - /** - * 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, translated and sorted - */ - static public Map getMetaDesc(MetaData meta) { - Map metaDesc = new LinkedHashMap(); - - // 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("Published on", meta.getPublisher()); - metaDesc.put("Publication date", meta.getDate()); - metaDesc.put("Creation date", meta.getCreationDate()); - 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()); - metaDesc.put("URL", meta.getUrl()); - - return metaDesc; - } -} diff --git a/library/CacheLibrary.java b/library/CacheLibrary.java deleted file mode 100644 index e184c1b..0000000 --- a/library/CacheLibrary.java +++ /dev/null @@ -1,435 +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(); - } - - List copy; - 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); - } - } - } - - copy = new ArrayList(metasMixed); - } - - pg.done(); - return copy; - } - - @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 synchronized 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); - metasMixed = null; - - clearFromCache(meta.getLuid()); - - pg.done(); - return meta; - } - - // All the following methods are only used by Save and Delete in - // BasicLibrary: - - @Override - protected String 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/library/LocalLibrary.java b/library/LocalLibrary.java deleted file mode 100644 index 25f2ec9..0000000 --- a/library/LocalLibrary.java +++ /dev/null @@ -1,778 +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.HashUtils; -import be.nikiroo.utils.IOUtils; -import be.nikiroo.utils.Image; -import be.nikiroo.utils.Progress; - -/** - * 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 String getNextId() { - getStories(null); // make sure lastId is set - - synchronized (lock) { - return String.format("%03d", ++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) { - Image img = new Image(in); - if (img.getSize() == 0) { - img.close(); - throw new IOException( - "Empty image not accepted"); - } - sourceCovers.put(source, img); - } - } 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) { - Image img = new Image(in); - if (img.getSize() == 0) { - img.close(); - throw new IOException( - "Empty image not accepted"); - } - authorCovers.put(author, img); - } - } 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 = HashUtils.md5(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); - } - - String summaryExt = ".summary"; - File summaryFile = new File(path + summaryExt); - if (!summaryFile.exists()) { - summaryFile = new File( - path.substring(0, path.length() - fileExt.length()) - + summaryExt); - } - - if (summaryFile.exists()) { - files.add(summaryFile); - } - - 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; - if (stories == null) { - stories = getStoriesDo(pg); - synchronized (lock) { - if (this.stories == null) - this.stories = stories; - else - stories = this.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 (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/library/MetaResultList.java b/library/MetaResultList.java deleted file mode 100644 index 8b8a167..0000000 --- a/library/MetaResultList.java +++ /dev/null @@ -1,419 +0,0 @@ -package be.nikiroo.fanfix.library; - -import java.io.IOException; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Collections; -import java.util.Comparator; -import java.util.List; -import java.util.Map; -import java.util.TreeMap; - -import be.nikiroo.fanfix.data.MetaData; -import be.nikiroo.utils.StringUtils; - -public class MetaResultList { - /** Max number of items before splitting in [A-B] etc. for eligible items */ - static private final int MAX = 20; - - 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; - } - - /** - * 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: - *

- * - * @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; - } - - 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; - } - - /** - * Return the list of authors, grouped by starting letter(s) if needed. - *

- * If the number of authors 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 author 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 { - return group(getAuthors()); - } - - 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; - } - - /** - * Return the list of tags, grouped by starting letter(s) if needed. - *

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

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

    - *
  • *: any tag which name doesn't contain letters nor numbers - *
  • - *
  • 0-9: any tag which name starts with a number
  • - *
  • A-C (for instance): any tag which 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 tags' names, grouped by letter(s) - * - * @throws IOException - * in case of IOException - */ - public Map> getTagsGrouped() throws IOException { - return group(getTags()); - } - - // 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; - } - - /** - * Return the list of values, grouped by starting letter(s) if needed. - *

- * If the number of values is not too high, only one group with an empty - * name and all the values will be returned (see - * {@link MetaResultList#MAX}). - *

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

    - *
  • *: any value which name doesn't contain letters nor numbers - *
  • - *
  • 0-9: any value which name starts with a number
  • - *
  • A-C (for instance): any value which 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). - * - * @param values - * the values to group - * - * @return the values, grouped by letter(s) - * - * @throws IOException - * in case of IOException - */ - private Map> group(List values) - throws IOException { - Map> groups = new TreeMap>(); - - // If all authors fit the max, just report them as is - if (values.size() <= MAX) { - groups.put("", values); - return groups; - } - - // Create groups A to Z, which can be empty here - for (char car = 'A'; car <= 'Z'; car++) { - groups.put(Character.toString(car), find(values, 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("*", find(values, '*')); - groups.put("0-9", find(values, '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 values - * 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 find(List values, char car) { - List accepted = new ArrayList(); - for (String value : values) { - char first = '*'; - for (int i = 0; first == '*' && i < value.length(); i++) { - String san = StringUtils.sanitize(value, 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(value); - } - } - - return accepted; - } - - /** - * 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/library/RemoteLibrary.java b/library/RemoteLibrary.java deleted file mode 100644 index 3e0e192..0000000 --- a/library/RemoteLibrary.java +++ /dev/null @@ -1,589 +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. - *

- * This remote library uses a custom fanfix:// protocol. - * - * @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)(|bl)(|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
  • - *
  • bl: flag to bypass the blacklist (if it exists)
  • - *
  • wl: flag to bypass the whitelist if it exists
  • - *
- *

- * Some examples: - *

    - *
  • my_key: normal connection, will take the default server - * options
  • - *
  • my_key|agzyzz|wl|bl: will ask to bypass the black list and 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 = ""; - } - - if (host.startsWith("fanfix://")) { - host = host.substring("fanfix://".length()); - } - - this.host = host; - this.port = port; - } - - @Override - public String getLibraryName() { - return (rw ? "[READ-ONLY] " : "") + "fanfix://" + 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] = null; - 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 errors - * @throws SSLException - * when the key was not accepted - */ - public void stop() throws IOException, SSLException { - 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 String 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/library/RemoteLibraryException.java b/library/RemoteLibraryException.java deleted file mode 100644 index 4cbb631..0000000 --- a/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/library/RemoteLibraryServer.java b/library/RemoteLibraryServer.java deleted file mode 100644 index 59819bb..0000000 --- a/library/RemoteLibraryServer.java +++ /dev/null @@ -1,596 +0,0 @@ -package be.nikiroo.fanfix.library; - -import java.io.IOException; -import java.net.URL; -import java.util.ArrayList; -import java.util.Arrays; -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 bls = 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). - * - * @throws IOException - * in case of I/O error - */ - public RemoteLibraryServer() throws IOException { - super("Fanfix remote library", - Instance.getInstance().getConfig() - .getInteger(Config.SERVER_PORT), - Instance.getInstance().getConfig() - .getString(Config.SERVER_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; - boolean bl = 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(); - } - List blacklist = Instance.getInstance().getConfig() - .getList(Config.SERVER_BLACKLIST); - if (blacklist == null) { - blacklist = 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(); - } - if ((subkey + "|").contains("|bl|")) { - bl = false; // |bl| = bypass blacklist - blacklist = new ArrayList(); - } - } - } - - String mode = display(wl, bl, 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, blacklist); - } catch (IOException e) { - rep = new RemoteLibraryException(e, true); - } - - commands.put(id, command); - wls.put(id, wl); - bls.put(id, bl); - rws.put(id, rw); - times.put(id, (new Date().getTime() - start)); - - return rep; - } - - private String display(boolean whitelist, boolean blacklist, boolean rw) { - String mode = ""; - if (!rw) { - mode += "RO: "; - } - if (whitelist) { - mode += "WL: "; - } - if (blacklist) { - mode += "BL: "; - } - - return mode; - } - - @Override - protected void onRequestDone(long id, long bytesReceived, long bytesSent) { - boolean whitelist = wls.get(id); - boolean blacklist = bls.get(id); - boolean rw = rws.get(id); - wls.remove(id); - bls.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, blacklist, 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, - List blacklist) 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)) { - metas.add(removeCover(meta)); - } - - 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); - } - - for (int i = 0; i < metas.size(); i++) { - if (!isAllowed(metas.get(i), whitelist, blacklist)) { - 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 || !isAllowed(meta, whitelist, blacklist)) { - 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) { - boolean updateProgress = false; - if (rep instanceof Integer[] && ((Integer[]) rep).length == 3) - updateProgress = true; - if (rep instanceof Object[] && ((Object[]) rep).length >= 5 - && "UPDATE".equals(((Object[]) rep)[0])) - updateProgress = true; - - if (updateProgress) { - Object[] a = (Object[]) rep; - - int offset = 0; - if (a[0] instanceof String) { - offset = 1; - } - - int min = (Integer) a[0 + offset]; - int max = (Integer) a[1 + offset]; - int progress = (Integer) a[2 + offset]; - - Object meta = null; - if (a.length > (3 + offset)) { - meta = a[3 + offset]; - } - - String name = null; - if (a.length > (4 + offset)) { - name = a[4 + offset] == null ? "" : a[4 + offset].toString(); - } - - if (min >= 0 && min <= max) { - pg.setName(name); - pg.setMinMax(min, max); - pg.setProgress(progress); - if (meta != null) { - pg.put("meta", meta); - } - - 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 Object[] pMeta = new MetaData[1]; - final String[] pName = new String[1]; - final Long[] lastTime = new Long[] { new Date().getTime() }; - pg.addProgressListener(new ProgressListener() { - @Override - public void progress(Progress progress, String name) { - Object meta = pg.get("meta"); - if (meta instanceof MetaData) { - meta = removeCover((MetaData) meta); - } - - int min = pg.getMin(); - int max = pg.getMax(); - int rel = min + (int) Math - .round(pg.getRelativeProgress() * (max - min)); - - boolean samePg = p[0] == min && p[1] == max && p[2] == rel; - - // Do not re-send the same value twice over the wire, - // unless more than 2 seconds have elapsed (to maintain the - // connection) - if (!samePg || !same(pMeta[0], meta) || !same(pName[0], name) // - || (new Date().getTime() - lastTime[0] > 2000)) { - p[0] = min; - p[1] = max; - p[2] = rel; - pMeta[0] = meta; - pName[0] = name; - - try { - action.send(new Object[] { "UPDATE", min, max, rel, - meta, name }); - action.rec(); - } catch (Exception e) { - getTraceHandler().error(e); - } - - lastTime[0] = new Date().getTime(); - } - - isDoneForwarded[0] = (pg.getProgress() >= pg.getMax()); - } - }); - - return pg; - } - - private boolean same(Object obj1, Object obj2) { - if (obj1 == null || obj2 == null) - return obj1 == null && obj2 == null; - - return obj1.equals(obj2); - } - - // 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); - } - } - } - - private MetaData removeCover(MetaData meta) { - MetaData light = null; - if (meta != null) { - if (meta.getCover() == null) { - light = meta; - } else { - light = meta.clone(); - light.setCover(null); - } - } - - return light; - } - - private boolean isAllowed(MetaData meta, List whitelist, - List blacklist) { - MetaResultList one = new MetaResultList(Arrays.asList(meta)); - if (!whitelist.isEmpty()) { - if (one.filter(whitelist, null, null).isEmpty()) { - return false; - } - } - if (!blacklist.isEmpty()) { - if (!one.filter(blacklist, null, null).isEmpty()) { - return false; - } - } - - return true; - } -} diff --git a/library/Template.java b/library/Template.java deleted file mode 100644 index a116ef6..0000000 --- a/library/Template.java +++ /dev/null @@ -1,105 +0,0 @@ -package be.nikiroo.fanfix.library; - -import java.io.IOException; -import java.io.InputStream; -import java.util.HashMap; -import java.util.List; -import java.util.Map; - -import be.nikiroo.utils.IOUtils; -import be.nikiroo.utils.streams.ReplaceInputStream; - -public class Template { - private Class location; - private String name; - - private Map values = new HashMap(); - private Map valuesTemplate = new HashMap(); - private Map> valuesTemplateList = new HashMap>(); - - public Template(Class location, String name) { - this.location = location; - this.name = name; - } - - public synchronized InputStream read() throws IOException { - - String from[] = new String[values.size() + valuesTemplate.size() - + valuesTemplateList.size()]; - String to[] = new String[from.length]; - - int i = 0; - - for (String key : values.keySet()) { - from[i] = "${" + key + "}"; - to[i] = values.get(key); - - i++; - } - for (String key : valuesTemplate.keySet()) { - InputStream value = valuesTemplate.get(key).read(); - try { - from[i] = "${" + key + "}"; - to[i] = IOUtils.readSmallStream(value); - } finally { - value.close(); - } - - i++; - } - for (String key : valuesTemplateList.keySet()) { - List