+# Makefile base template
+#
+# Version:
+# - 1.0.0: add a version comment
+# - 1.1.0: add help, sjar
+
# Required parameters (the commented out ones are supposed to change per project):
#MAIN = path to main java source to compile
RJAR = java
RJAR_FLAGS += -jar
-# Usual options:
-# make : to build the jar file
-# make libs : to update the libraries into src/
-# make build : to update the binaries (not the jar)
-# make test : to update the test binaries
-# make build jar : to update the binaries and jar file
-# make clean : to clean the directory of intermediate files
-# make mrpropre : to clean the directory of all outputs
-# make run : to run the program from the binaries
-# make run-test : to run the test program from the binaries
-# make jrun : to run the program from the jar file
-# make install : to install the application into $PREFIX
-
-# Note: build is actually slower than rebuild in most cases except when
-# small changes only are detected ; so we use rebuild by default
-
all: build jar
-.PHONY: all clean mrproper mrpropre build run jrun jar resources test-resources install libs love
+help:
+ @echo "Usual options:"
+ @echo "=============="
+ @echo " make : to build the jar file"
+ @echo " make help : to get this help screen"
+ @echo " make libs : to update the libraries into src/"
+ @echo " make build : to update the binaries (not the jar)"
+ @echo " make test : to update the test binaries"
+ @echo " make build jar : to update the binaries and jar file"
+ @echo " make sjar : to create the sources jar file"
+ @echo " make clean : to clean the directory of intermediate files"
+ @echo " make mrpropre : to clean the directory of all outputs"
+ @echo " make run : to run the program from the binaries"
+ @echo " make run-test : to run the test program from the binaries"
+ @echo " make jrun : to run the program from the jar file"
+ @echo " make install : to install the application into $PREFIX"
+
+.PHONY: all clean mrproper mrpropre build run jrun jar sjar resources test-resources install libs love
bin:
@mkdir -p bin
jar: $(NAME).jar
+sjar: $(NAME)-sources.jar
+
build: resources
@echo Compiling program...
@echo " src/$(MAIN)"
done )
@[ ! -d libs ] || touch bin/libs
-$(NAME).jar: resources
- @[ -e bin/$(MAIN).class ] || echo You need to build the sources
- @[ -e bin/$(MAIN).class ]
- @echo Making JAR file...
+$(NAME)-sources.jar: libs
+ @echo Making sources JAR file...
@echo > bin/manifest
@[ "$(SJAR_FLAGS)" = "" ] || echo Creating $(NAME)-sources.jar...
@[ "$(SJAR_FLAGS)" = "" ] || $(JAR) cfm $(NAME)-sources.jar bin/manifest $(SJAR_FLAGS)
@[ "$(SJAR_FLAGS)" = "" ] || [ ! -e VERSION ] || echo Copying to "$(NAME)-`cat VERSION`-sources.jar"...
@[ "$(SJAR_FLAGS)" = "" ] || [ ! -e VERSION ] || cp $(NAME)-sources.jar "$(NAME)-`cat VERSION`-sources.jar"
+
+$(NAME).jar: resources
+ @[ -e bin/$(MAIN).class ] || echo You need to build the sources
+ @[ -e bin/$(MAIN).class ]
@echo "Main-Class: `echo "$(MAIN)" | sed 's:/:.:g'`" > bin/manifest
@echo >> bin/manifest
$(JAR) cfm $(NAME).jar bin/manifest $(JAR_FLAGS)
# Fanfix
+# Version WIP
+
+- Update nikiroo-utils
+- Android compatibility
+
# Version 1.6.3
- Bug fixes
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.IOUtils;
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
* @author niki
*/
public class DataLoader {
- private Cache cache;
private Downloader downloader;
+ private Cache downloadCache;
+ private Cache cache;
/**
* Create a new {@link DataLoader} object.
*/
public DataLoader(File dir, String UA, int hoursChanging, int hoursStable)
throws IOException {
- cache = new Cache(dir, hoursChanging, hoursStable);
- cache.setTraceHandler(Instance.getTraceHandler());
downloader = new Downloader(UA);
- downloader.setTraceHandler(Instance.getTraceHandler());
+ downloadCache = new Cache(dir, hoursChanging, hoursStable);
+ cache = downloadCache;
+ }
+
+ /**
+ * 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);
+ downloadCache = null;
+ cache = new CacheMemory();
+ }
+
+ /**
+ * The traces handler for this {@link Cache}.
+ *
+ * @param tracer
+ * the new traces handler
+ */
+ public void setTraceHandler(TraceHandler tracer) {
+ downloader.setTraceHandler(tracer);
+ cache.setTraceHandler(tracer);
+ if (downloadCache != null) {
+ downloadCache.setTraceHandler(tracer);
+ }
+
}
/**
URL originalUrl) throws IOException {
// MUST NOT return null
try {
- InputStream in = cache.load(originalUrl, false, stable);
- Instance.getTraceHandler().trace(
- "Cache " + (in != null ? "hit" : "miss") + ": " + url);
+ InputStream in = null;
+
+ if (downloadCache != null) {
+ in = downloadCache.load(originalUrl, false, stable);
+ Instance.getTraceHandler().trace(
+ "Cache " + (in != null ? "hit" : "miss") + ": " + url);
+ }
if (in == null) {
try {
in = openNoCache(url, support, null, null, null);
- cache.save(in, originalUrl);
- // ..But we want a resetable stream
- in.close();
- in = cache.load(originalUrl, false, stable);
+ if (downloadCache != null) {
+ downloadCache.save(in, originalUrl);
+ // ..But we want a resetable stream
+ in.close();
+ in = downloadCache.load(originalUrl, false, stable);
+ } else {
+ InputStream resetIn = IOUtils.forceResetableStream(in);
+ if (resetIn != in) {
+ in.close();
+ in = resetIn;
+ }
+ }
} catch (IOException e) {
throw new IOException("Cannot save the url: "
+ (url == null ? "null" : url.toString()), e);
*/
public void refresh(URL url, BasicSupport support, boolean stable)
throws IOException {
- if (!cache.check(url, false, stable)) {
+ if (downloadCache != null && !downloadCache.check(url, false, stable)) {
open(url, support, stable).close();
}
}
*
*/
public boolean check(URL url, boolean stable) {
- return cache.check(url, false, stable);
+ return downloadCache != null && downloadCache.check(url, false, stable);
}
/**
public void saveAsImage(Image img, File target, String format)
throws IOException {
ImageUtils.getInstance().saveAsImage(img, target, format);
-
}
/**
* @param uniqueID
* a unique ID for this resource
*
- * @return the resulting {@link File}
*
* @throws IOException
* in case of I/O error
*/
- public File addToCache(InputStream in, String uniqueID) throws IOException {
- return cache.save(in, uniqueID);
+ public void addToCache(InputStream in, String uniqueID) throws IOException {
+ cache.save(in, uniqueID);
}
/**
static {
// Before we can configure it:
- tracer = new TraceHandler(true, checkEnv("DEBUG"), false);
+ tracer = new TraceHandler(true, checkEnv("DEBUG"), checkEnv("DEBUG"));
// Most of the rest is dependent upon this:
config = new ConfigBundle();
}
if (configDir == null) {
- configDir = new File(System.getProperty("user.home"), ".fanfix")
- .getPath();
+ configDir = new File(getHome(), ".fanfix").getPath();
}
if (!new File(configDir).exists()) {
// Fix an old bug (we used to store custom translation files by
// default):
if (trans.getString(StringId.INPUT_DESC_CBZ) == null) {
- // TODO: create the deleteFile method
- // trans.deleteFile(configDir);
+ trans.deleteFile(configDir);
}
Bundles.setDirectory(configDir);
if (checkEnv("DEBUG")) {
debug = true;
+ trace = true;
}
tracer = new TraceHandler(true, debug, trace);
if (lib == null) {
tracer.error(new IOException(
- "Cannot create remote library for: "
- + getFile(Config.DEFAULT_LIBRARY)));
+ "Cannot create remote library for: " + remoteLib));
}
}
coverDir = null;
}
+ String ua = config.getString(Config.USER_AGENT);
try {
- String ua = config.getString(Config.USER_AGENT);
int hours = config.getInteger(Config.CACHE_MAX_TIME_CHANGING, -1);
int hoursLarge = config
.getInteger(Config.CACHE_MAX_TIME_STABLE, -1);
-
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);
}
/**
}
Instance.tracer = tracer;
+ cache.setTraceHandler(tracer);
}
/**
return uiconfig;
}
+ /**
+ * Reset the configuration.
+ *
+ * @param resetTrans
+ * also reset the translation files
+ */
+ public static 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.
*
if (path != null && !path.isEmpty()) {
path = path.replace('/', File.separatorChar);
if (path.contains("$HOME")) {
- path = path.replace("$HOME",
- "" + System.getProperty("user.home"));
+ path = path.replace("$HOME", getHome());
}
file = new File(path);
return file;
}
+ /**
+ * Return the home directory from the system properties.
+ *
+ * @return the home
+ */
+ private static String getHome() {
+ String home = System.getProperty("user.home");
+ if (home == null || home.trim().isEmpty()) {
+ home = System.getProperty("java.io.tmpdir");
+ }
+
+ if (home == null) {
+ home = "";
+ }
+
+ return home;
+ }
+
/**
* The language to use for the application (NULL = default system language).
*
@Meta(description = "reader type (CLI = simple output to console, TUI = Text User Interface with menus and windows, GUI = a GUI with locally stored files)", format = Format.FIXED_LIST, list = {
"CLI", "GUI", "TUI" }, info = "Select the default reader to use to read stories")
READER_TYPE, //
- @Meta(format = Format.COMBO_LIST, list = { "INFO_TEXT", "EPUB", "HTML",
- "TEXT" }, info = "One of the known output type", description = "The type of output for the Local Reader for non-images documents")
+ @Meta(def = "INFO_TEXT", format = Format.COMBO_LIST, list = { "INFO_TEXT",
+ "EPUB", "HTML", "TEXT" }, info = "One of the known output type", description = "The type of output for the Local Reader for non-images documents")
NON_IMAGES_DOCUMENT_TYPE, //
- @Meta(format = Format.COMBO_LIST, list = { "CBZ", "HTML" }, description = "The type of output for the Local Reader for images documents")
+ @Meta(def = "CBZ", format = Format.COMBO_LIST, list = { "CBZ", "HTML" }, description = "The type of output for the Local Reader for images documents")
IMAGES_DOCUMENT_TYPE, //
@Meta(description = "absolute path, $HOME variable supported, / is always accepted as dir separator", format = Format.DIRECTORY, info = "The directory where to store temporary files, defaults to directory 'tmp' in the conig directory (usually $HOME/.fanfix)")
CACHE_DIR, //
- @Meta(description = "delay in hours, or 0 for no cache, or -1 for infinite time (default)", format = Format.INT, info = "The delay after which a cached resource that is thought to change ~often is considered too old and triggers a refresh")
+ @Meta(def = "24", description = "delay in hours, or 0 for no cache, or -1 for infinite time (default)", format = Format.INT, info = "The delay after which a cached resource that is thought to change ~often is considered too old and triggers a refresh")
CACHE_MAX_TIME_CHANGING, //
- @Meta(description = "delay in hours, or 0 for no cache, or -1 for infinite time (default)", format = Format.INT, info = "The delay after which a cached resource that is thought to change rarely is considered too old and triggers a refresh")
+ @Meta(def = "720", description = "delay in hours, or 0 for no cache, or -1 for infinite time (default)", format = Format.INT, info = "The delay after which a cached resource that is thought to change rarely is considered too old and triggers a refresh")
CACHE_MAX_TIME_STABLE, //
- @Meta(description = "string", info = "The user-agent to use to download files")
+ @Meta(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)", description = "string", info = "The user-agent to use to download files")
USER_AGENT, //
- @Meta(description = "absolute path, $HOME variable supported, / is always accepted as dir separator", format = Format.DIRECTORY, info = "The directory where to get the default story covers")
+ @Meta(def = "$HOME/bin/epub/", description = "absolute path, $HOME variable supported, / is always accepted as dir separator", format = Format.DIRECTORY, info = "The directory where to get the default story covers")
DEFAULT_COVERS_DIR, //
@Meta(description = "string", info = "The default library to use (KEY:SERVER:PORT), or empty for the local library")
DEFAULT_LIBRARY, //
- @Meta(description = "absolute path, $HOME variable supported, / is always accepted as dir separator", format = Format.DIRECTORY, info = "The directory where to store the library")
+ @Meta(def = "$HOME/Books", description = "absolute path, $HOME variable supported, / is always accepted as dir separator", format = Format.DIRECTORY, info = "The directory where to store the library")
LIBRARY_DIR, //
- @Meta(description = "boolean", format = Format.BOOLEAN, info = "Show debug information on errors")
+ @Meta(def = "false", description = "boolean", format = Format.BOOLEAN, info = "Show debug information on errors")
DEBUG_ERR, //
- @Meta(description = "boolean", format = Format.BOOLEAN, info = "Show debug trace information")
+ @Meta(def = "false", description = "boolean", format = Format.BOOLEAN, info = "Show debug trace information")
DEBUG_TRACE, //
- @Meta(description = "image format", format = Format.COMBO_LIST, list = {
+ @Meta(def = "PNG", description = "image format", format = Format.COMBO_LIST, list = {
"PNG", "JPG", "BMP" }, info = "Image format to use for cover images")
IMAGE_FORMAT_COVER, //
- @Meta(description = "image format", format = Format.COMBO_LIST, list = {
+ @Meta(def = "JPG", description = "image format", format = Format.COMBO_LIST, list = {
"PNG", "JPG", "BMP" }, info = "Image format to use for content images")
IMAGE_FORMAT_CONTENT, //
@Meta(group = true)
LATEX_LANG, //
- @Meta(description = "LaTeX output language: English", info = "LaTeX full name")
+ @Meta(def = "english", description = "LaTeX output language: English", info = "LaTeX full name")
LATEX_LANG_EN, //
- @Meta(description = "LaTeX output language: French", info = "LaTeX full name")
+ @Meta(def = "french", description = "LaTeX output language: French", info = "LaTeX full name")
LATEX_LANG_FR, //
- @Meta(description = "other 'by' prefixes before author name, used to identify the author", array = true)
+ @Meta(def = "by,par,de,©,(c)", description = "other 'by' prefixes before author name, used to identify the author", array = true)
BYS, //
- @Meta(description = "List of languages codes used for chapter identification (should not be changed)", array = true, info = "EN,FR")
+ @Meta(def = "EN,FR", description = "List of languages codes used for chapter identification (should not be changed)", array = true, info = "EN,FR")
CHAPTER, //
- @Meta(description = "Chapter identification String: English", info = "used to identify a starting chapter in text mode")
+ @Meta(def = "Chapter", description = "Chapter identification String: English", info = "used to identify a starting chapter in text mode")
CHAPTER_EN, //
- @Meta(description = "Chapter identification String: French", info = "used to identify a starting chapter in text mode")
+ @Meta(def = "Chapitre", description = "Chapter identification String: French", info = "used to identify a starting chapter in text mode")
CHAPTER_FR, //
@Meta(description = "Login information (username) for YiffStar to have access to all the stories (should not be necessary anymore)")
LOGIN_YIFFSTAR_USER, //
LOGIN_FIMFICTION_APIKEY_CLIENT_SECRET, //
@Meta(description = "Do not use the new API, even if we have a token, and force HTML scraping (default is false, use API if token or ID present)", format = Format.BOOLEAN)
LOGIN_FIMFICTION_APIKEY_FORCE_HTML, //
- @Meta(description = "A token is required to use the beta APIv2 from FimFiction (see APIKEY_CLIENT_*)", format = Format.PASSWORD)
+ @Meta(def = "Bearer WnZ5oHlzQoDocv1GcgHfcoqctHkSwL-D", description = "A token is required to use the beta APIv2 from FimFiction (see APIKEY_CLIENT_*)", format = Format.PASSWORD)
LOGIN_FIMFICTION_APIKEY_TOKEN, //
}
ERR_NOT_SUPPORTED, //
@Meta(info = "%s = cover URL", description = "Failed to download cover : %s")
ERR_BS_NO_COVER, //
- @Meta(info = "single char", description = "Canonical OPEN SINGLE QUOTE char (for instance: `)")
+ @Meta(def = "`", info = "single char", description = "Canonical OPEN SINGLE QUOTE char (for instance: `)")
OPEN_SINGLE_QUOTE, //
- @Meta(info = "single char", description = "Canonical CLOSE SINGLE QUOTE char (for instance: ‘)")
+ @Meta(def = "‘", info = "single char", description = "Canonical CLOSE SINGLE QUOTE char (for instance: ‘)")
CLOSE_SINGLE_QUOTE, //
- @Meta(info = "single char", description = "Canonical OPEN DOUBLE QUOTE char (for instance: “)")
+ @Meta(def = "“", info = "single char", description = "Canonical OPEN DOUBLE QUOTE char (for instance: “)")
OPEN_DOUBLE_QUOTE, //
- @Meta(info = "single char", description = "Canonical CLOSE DOUBLE QUOTE char (for instance: ”)")
+ @Meta(def = "”", info = "single char", description = "Canonical CLOSE DOUBLE QUOTE char (for instance: ”)")
CLOSE_DOUBLE_QUOTE, //
- @Meta(description = "Name of the description fake chapter")
+ @Meta(def = "Description", description = "Name of the description fake chapter")
DESCRIPTION, //
- @Meta(info = "%d = number, %s = name", description = "Name of a chapter with a name")
+ @Meta(def = "Chapter %d: %s", info = "%d = number, %s = name", description = "Name of a chapter with a name")
CHAPTER_NAMED, //
- @Meta(info = "%d = number, %s = name", description = "Name of a chapter without name")
+ @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, //
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(description = "'by' prefix before author name used to output the author, make sure it is covered by Config.BYS for input detection")
+ @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, //
;
*/
@SuppressWarnings("javadoc")
public enum UiConfig {
- @Meta(format = Format.DIRECTORY, info = "absolute path, $HOME variable supported, / is always accepted as dir separator", description = "The directory where to store temporary files, defaults to directory 'tmp.reader' in the conig directory (usually $HOME/.fanfix)")
+ @Meta(format = Format.DIRECTORY, info = "absolute path, $HOME variable supported, / is always accepted as dir separator", description = "The directory where to store temporary files, defaults to directory 'tmp.reader' in the config directory (usually $HOME/.fanfix)")
CACHE_DIR_LOCAL_READER, //
@Meta(format = Format.COMBO_LIST, list = { "INFO_TEXT", "EPUB", "HTML",
"TEXT" }, info = "One of the known output type", description = "The type of output for the GUI Reader for non-images documents")
DEBUG_TRACE = false
# image format (FORMAT: COMBO_LIST) Image format to use for cover images
# ALLOWED VALUES: "PNG" "JPG" "BMP"
-IMAGE_FORMAT_COVER = png
+IMAGE_FORMAT_COVER = PNG
# image format (FORMAT: COMBO_LIST) Image format to use for content images
# ALLOWED VALUES: "PNG" "JPG" "BMP"
-IMAGE_FORMAT_CONTENT = png
+IMAGE_FORMAT_CONTENT = JPG
# This item is used as a group, its content is not expected to be used.
LATEX_LANG =
# LaTeX output language: English
#
-# The directory where to store temporary files, defaults to directory 'tmp.reader' in the conig directory (usually $HOME/.fanfix)
+# The directory where to store temporary files, defaults to directory 'tmp.reader' in the config directory (usually $HOME/.fanfix)
# (FORMAT: DIRECTORY) absolute path, $HOME variable supported, / is always accepted as dir separator
CACHE_DIR_LOCAL_READER =
# The type of output for the GUI Reader for non-images documents