Merge branch 'master' of github.com:nikiroo/fanfix
authorNiki Roo <niki@nikiroo.be>
Thu, 19 Sep 2019 19:28:22 +0000 (21:28 +0200)
committerNiki Roo <niki@nikiroo.be>
Thu, 19 Sep 2019 19:28:22 +0000 (21:28 +0200)
23 files changed:
README-fr.md
README.md
TODO.md
changelog-fr.md
changelog.md
libs/nikiroo-utils-5.0.0-dev-sources.jar
src/be/nikiroo/fanfix/Main.java
src/be/nikiroo/fanfix/bundles/StringIdGui.java
src/be/nikiroo/fanfix/bundles/resources_core.properties
src/be/nikiroo/fanfix/bundles/resources_core_fr.properties
src/be/nikiroo/fanfix/bundles/resources_gui.properties
src/be/nikiroo/fanfix/bundles/resources_gui_fr.properties
src/be/nikiroo/fanfix/library/BasicLibrary.java
src/be/nikiroo/fanfix/library/CacheLibrary.java
src/be/nikiroo/fanfix/library/RemoteLibrary.java
src/be/nikiroo/fanfix/library/RemoteLibraryServer.java
src/be/nikiroo/fanfix/reader/ui/GuiReader.java
src/be/nikiroo/fanfix/reader/ui/GuiReaderBook.java
src/be/nikiroo/fanfix/reader/ui/GuiReaderBookInfo.java
src/be/nikiroo/fanfix/reader/ui/GuiReaderFrame.java
src/be/nikiroo/fanfix/reader/ui/GuiReaderMainPanel.java
src/be/nikiroo/fanfix/reader/ui/GuiReaderViewerPanel.java
src/be/nikiroo/fanfix/test/Test.java

index 5a0d7f8dadda047e57d5ae30a3000fceb216f903..777840a5beeb0ee1c9ae524f9cefb0bced2f4b3e 100644 (file)
@@ -120,6 +120,20 @@ 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 :
index 7e7c3b9e22e8ad15648b99d4de2dfb76c89bfeb0..43a0f40687d0fdd6caff18f7c4dd5c7eba3fa16b 100644 (file)
--- a/README.md
+++ b/README.md
@@ -120,6 +120,21 @@ 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:
diff --git a/TODO.md b/TODO.md
index f9f7692b8ad91dbcbdbc4e43eca038ab2ccd7950..af17b53ad0b7c12fd072aea2436a47956fc438df 100644 (file)
--- a/TODO.md
+++ b/TODO.md
@@ -85,10 +85,10 @@ My current planning for Fanfix (but not everything appears on this list):
     - [ ] Add the resume in the Properties page (maybe a second tab?)
 - [ ] Bugs
     - [x] Fix "Redownload also reset the source"
-    - [ ] Fix "Redownload remote does not show the new item before restart of client app"
+    - [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)
-    - [ ] remote import also download the file in cache, why?
+    - [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-...
index 5ea8c3294fbc3169bedb06730ec5bdc759017a22..d242cab2c67f9d4bfc9d7cea5189a687553466c4 100644 (file)
@@ -7,10 +7,12 @@
 - 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
index 195310b39edfc11e9a6134ae2f295a9242cc54d8..5173c25c0f1ae13ac78a2b9e4ea6f5645644e6cc 100644 (file)
@@ -7,10 +7,12 @@
 - 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
index 3a29484e8a9c38a3173f12d5c2423add1325178c..9f0b4a0a3e7bfb961487a4646fa501e1e28e1116 100644 (file)
Binary files a/libs/nikiroo-utils-5.0.0-dev-sources.jar and b/libs/nikiroo-utils-5.0.0-dev-sources.jar differ
index b3633612713996f8651bffda5f480fc2607b54be..3d69cad31358f794d874b0645a9e124e36bf7caa 100644 (file)
@@ -12,6 +12,7 @@ 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;
@@ -644,10 +645,10 @@ public class Main {
         */
        public static int imprt(String urlString, Progress pg) {
                try {
-                       Story story = Instance.getLibrary().imprt(
+                       MetaData meta = Instance.getLibrary().imprt(
                                        BasicReader.getUrl(urlString), pg);
-                       System.out.println(story.getMeta().getLuid() + ": \""
-                                       + story.getMeta().getTitle() + "\" imported.");
+                       System.out.println(meta.getLuid() + ": \"" + meta.getTitle()
+                                       + "\" imported.");
                } catch (IOException e) {
                        Instance.getTraceHandler().error(e);
                        return 1;
index 6bb774c1a0bab0e194f1c31da8e6b101f310e6ac..2c9d222a84d41e9d3bc50ad58d107f42dfaed64e 100644 (file)
@@ -109,6 +109,8 @@ public enum StringIdGui {
        MENU_FILE_OPEN, //
        @Meta(def = "Edit", format = Format.STRING, description = "the edit menu")
        MENU_EDIT, //
+       @Meta(def = "Download to cache", format = Format.STRING, description = "the edit/send to cache menu button, to download the story into the cache if not already done")
+       MENU_EDIT_DOWNLOAD_TO_CACHE, //
        @Meta(def = "Clear cache", format = Format.STRING, description = "the clear cache menu button, to clear the cache for a single book")
        MENU_EDIT_CLEAR_CACHE, //
        @Meta(def = "Redownload", format = Format.STRING, description = "the edit/redownload menu button, to download the latest version of the book")
index 1ebc3d5932a5f86ef8cf87b3a1143058ee172939..d656ed6626204c6c68bffd1511b579a7faded50e 100644 (file)
@@ -1,4 +1,4 @@
-# United Kingdom (en_GB) resources translation file (UTF-8)
+# 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
@@ -8,7 +8,7 @@
 
 
 # help message for the syntax
-# (FORMAT: STRING) %s = supported input, %s = supported output
+# (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\
@@ -48,52 +48,53 @@ Supported input types:\n\
 \n\
 Supported output types:\n\
 %s
-# syntax error message (FORMAT: STRING) 
+# syntax error message
+# (FORMAT: STRING) 
 ERR_SYNTAX = Syntax error (try "--help")
 # an input or output support type description
-# (FORMAT: STRING) %s = support name, %s = support desc
+# (FORMAT: STRING) 
 ERR_SYNTAX_TYPE = > %s: %s
 # Error when retrieving data
-# (FORMAT: STRING) %s = input string
+# (FORMAT: STRING) 
 ERR_LOADING = Error when retrieving data from: %s
 # Error when saving to given target
-# (FORMAT: STRING) %s = save target
+# (FORMAT: STRING) 
 ERR_SAVING = Error when saving to target: %s
 # Error when unknown output format
-# (FORMAT: STRING) %s = bad output format
+# (FORMAT: STRING) 
 ERR_BAD_OUTPUT_TYPE = Unknown output type: %s
 # Error when converting input to URL/File
-# (FORMAT: STRING) %s = input string
+# (FORMAT: STRING) 
 ERR_BAD_URL = Cannot understand file or protocol: %s
 # URL/File not supported
-# (FORMAT: STRING) %s = input url
+# (FORMAT: STRING) 
 ERR_NOT_SUPPORTED = URL not supported: %s
 # Failed to download cover : %s
-# (FORMAT: STRING) %s = cover URL
+# (FORMAT: STRING) 
 ERR_BS_NO_COVER = Failed to download cover: %s
 # Canonical OPEN SINGLE QUOTE char (for instance: ‘)
-# (FORMAT: STRING) single char
+# (FORMAT: STRING) 
 OPEN_SINGLE_QUOTE = ‘
 # Canonical CLOSE SINGLE QUOTE char (for instance: ’)
-# (FORMAT: STRING) single char
+# (FORMAT: STRING) 
 CLOSE_SINGLE_QUOTE = ’
 # Canonical OPEN DOUBLE QUOTE char (for instance: “)
-# (FORMAT: STRING) single char
+# (FORMAT: STRING) 
 OPEN_DOUBLE_QUOTE = “
 # Canonical CLOSE DOUBLE QUOTE char (for instance: ”)
-# (FORMAT: STRING) single char
+# (FORMAT: STRING) 
 CLOSE_DOUBLE_QUOTE = ”
 # Name of the description fake chapter
 # (FORMAT: STRING) 
 DESCRIPTION = Description
 # Name of a chapter with a name
-# (FORMAT: STRING) %d = number, %s = name
+# (FORMAT: STRING) 
 CHAPTER_NAMED = Chapter %d: %s
 # Name of a chapter without name
-# (FORMAT: STRING) %d = number, %s = name
+# (FORMAT: STRING) 
 CHAPTER_UNNAMED = Chapter %d
 # Default description when the type is not known by i18n
-# (FORMAT: STRING) %s = type
+# (FORMAT: STRING) 
 INPUT_DESC = Unknown type: %s
 # Description of this input type
 # (FORMAT: STRING) 
@@ -142,7 +143,7 @@ INPUT_DESC_CBZ = CBZ files coming from this very program
 # (FORMAT: STRING) 
 INPUT_DESC_HTML = HTML files coming from this very program
 # Default description when the type is not known by i18n
-# (FORMAT: STRING) %s = type
+# (FORMAT: STRING) 
 OUTPUT_DESC = Unknown type: %s
 # Description of this output type
 # (FORMAT: STRING) 
@@ -177,7 +178,7 @@ OUTPUT_DESC_LATEX = A LaTeX file using the "book" template
 # (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.
+# 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) 
@@ -201,7 +202,7 @@ OUTPUT_DESC_SHORT_SYSOUT = Console output
 # (FORMAT: STRING) 
 OUTPUT_DESC_SHORT_HTML = HTML files with resources (directory, .html)
 # Error message for unknown 2-letter LaTeX language code
-# (FORMAT: STRING) %s = the unknown 2-code language
+# (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) 
index e64651b3275d781953f4f8ba3ad9b66a993c1907..9bf3626304733e61586946b13762507dbb871842 100644 (file)
@@ -1,4 +1,4 @@
-# français (fr) resources translation file (UTF-8)
+# 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
@@ -8,7 +8,7 @@
 
 
 # help message for the syntax
-# (FORMAT: STRING) %s = supported input, %s = supported output
+# (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\
@@ -39,52 +39,53 @@ Types supportés en entrée :\n\
 \n\
 Types supportés en sortie :\n\
 %s
-# syntax error message (FORMAT: STRING) 
+# syntax error message
+# (FORMAT: STRING) 
 ERR_SYNTAX = Erreur de syntaxe (essayez "--help")
 # an input or output support type description
-# (FORMAT: STRING) %s = support name, %s = support desc
+# (FORMAT: STRING) 
 ERR_SYNTAX_TYPE = > %s : %s
 # Error when retrieving data
-# (FORMAT: STRING) %s = input string
+# (FORMAT: STRING) 
 ERR_LOADING = Erreur de récupération des données depuis : %s
 # Error when saving to given target
-# (FORMAT: STRING) %s = save target
+# (FORMAT: STRING) 
 ERR_SAVING = Erreur lors de la sauvegarde sur : %s
 # Error when unknown output format
-# (FORMAT: STRING) %s = bad output format
+# (FORMAT: STRING) 
 ERR_BAD_OUTPUT_TYPE = Type de sortie inconnu : %s
 # Error when converting input to URL/File
-# (FORMAT: STRING) %s = input string
+# (FORMAT: STRING) 
 ERR_BAD_URL = Protocole ou type de fichier inconnu : %s
 # URL/File not supported
-# (FORMAT: STRING) %s = input url
+# (FORMAT: STRING) 
 ERR_NOT_SUPPORTED = Site web non supporté : %s
 # Failed to download cover : %s
-# (FORMAT: STRING) %s = cover URL
+# (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) single char
+# (FORMAT: STRING) 
 OPEN_SINGLE_QUOTE = ‘
 # Canonical CLOSE SINGLE QUOTE char (for instance: ’)
-# (FORMAT: STRING) single char
+# (FORMAT: STRING) 
 CLOSE_SINGLE_QUOTE = ’
 # Canonical OPEN DOUBLE QUOTE char (for instance: “)
-# (FORMAT: STRING) single char
+# (FORMAT: STRING) 
 OPEN_DOUBLE_QUOTE = “
 # Canonical CLOSE DOUBLE QUOTE char (for instance: ”)
-# (FORMAT: STRING) single char
+# (FORMAT: STRING) 
 CLOSE_DOUBLE_QUOTE = ”
 # Name of the description fake chapter
 # (FORMAT: STRING) 
 DESCRIPTION = Description
 # Name of a chapter with a name
-# (FORMAT: STRING) %d = number, %s = name
+# (FORMAT: STRING) 
 CHAPTER_NAMED = Chapitre %d : %s
 # Name of a chapter without name
-# (FORMAT: STRING) %d = number, %s = name
+# (FORMAT: STRING) 
 CHAPTER_UNNAMED = Chapitre %d
 # Default description when the type is not known by i18n
-# (FORMAT: STRING) %s = type
+# (FORMAT: STRING) 
 INPUT_DESC = Type d'entrée inconnu : %s
 # Description of this input type
 # (FORMAT: STRING) 
@@ -125,7 +126,7 @@ INPUT_DESC_CBZ = Les fichiers .cbz (une collection d'images zipées), de préfé
 # (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) %s = type
+# (FORMAT: STRING) 
 OUTPUT_DESC = Type de sortie inconnu : %s
 # Description of this output type
 # (FORMAT: STRING) 
@@ -160,7 +161,7 @@ OUTPUT_DESC_LATEX = A LaTeX file using the "book" template
 # (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.
+# 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) 
@@ -184,7 +185,7 @@ OUTPUT_DESC_SHORT_SYSOUT = Console output
 # (FORMAT: STRING) 
 OUTPUT_DESC_SHORT_HTML = HTML files with resources (directory, .html)
 # Error message for unknown 2-letter LaTeX language code
-# (FORMAT: STRING) %s = the unknown 2-code language
+# (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) 
index de44c18c085b11663cfae8b7b4546f9c2d71dc10..6d46af41f5884c46e5af0eb83e4da3c062ed72cd 100644 (file)
@@ -1,4 +1,4 @@
-# United States (en_US) resources_gui translation file (UTF-8)
+# 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
@@ -8,10 +8,10 @@
 
 
 # the title of the main window of Fanfix, the library
-# (FORMAT: STRING) %s = current Fanfix version
+# (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) %s = current Fanfix version, %s = library name
+# (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) 
@@ -38,7 +38,7 @@ SUBTITLE_MOVE_TO = Move to:
 # (FORMAT: STRING) 
 TITLE_DELETE = Delete story
 # the subtitle of the 'delete' window of Fanfix
-# (FORMAT: STRING) %s = LUID of the story, %s = title of the story
+# (FORMAT: STRING) 
 SUBTITLE_DELETE = Delete %s: %s
 # the title of the 'library error' dialogue
 # (FORMAT: STRING) 
@@ -53,27 +53,28 @@ SUBTITLE_IMPORT_URL = URL of the story to import:
 # (FORMAT: STRING) 
 TITLE_ERROR = Error
 # the title of a story for the properties dialogue, the viewers...
-# (FORMAT: STRING) %s = LUID of the story, %s = title of the story
+# (FORMAT: STRING) 
 TITLE_STORY = %s: %s
 # HTML text used to notify of a new version
-# (FORMAT: STRING) %s = url link in HTML
+# (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) %s = the newer version number
+# (FORMAT: STRING) 
 NEW_VERSION_VERSION = Version %s
 # show the number of words of a book
-# (FORMAT: STRING) %s = the number
+# (FORMAT: STRING) 
 BOOK_COUNT_WORDS = %s words
 # show the number of images of a book
-# (FORMAT: STRING) %s = the number
+# (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) %s = the number
+# (FORMAT: STRING) 
 BOOK_COUNT_STORIES = %s stories
-# the file menu (FORMAT: STRING) 
+# the file menu
+# (FORMAT: STRING) 
 MENU_FILE = File
 # the file/exit menu button
 # (FORMAT: STRING) 
@@ -108,8 +109,12 @@ 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) 
+# the edit menu
+# (FORMAT: STRING) 
 MENU_EDIT = Edit
+# the edit/send to cache menu button, to download the story into the cache if not already done
+# (FORMAT: STRING) 
+MENU_EDIT_DOWNLOAD_TO_CACHE = Download to cache 
 # the clear cache menu button, to clear the cache for a single book
 # (FORMAT: STRING) 
 MENU_EDIT_CLEAR_CACHE = Clear cache
@@ -128,7 +133,8 @@ 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) 
+# 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) 
@@ -158,7 +164,7 @@ MENU_AUTHORS_UNKNOWN = [unknown]
 # (FORMAT: STRING) 
 PROGRESS_OUT_OF_UI_RELOAD_BOOKS = Reload books
 # progress bar caption for the 'change source' step of the ReDownload operation
-# (FORMAT: STRING) %s = new source name
+# (FORMAT: STRING) 
 PROGRESS_CHANGE_SOURCE = Change the source of the book to %s
 # default description if the error is not known
 # (FORMAT: STRING) 
@@ -176,18 +182,18 @@ ERROR_LIB_STATUS_UNAVAILABLE = Library currently unavailable
 # (FORMAT: STRING) 
 ERROR_CANNOT_OPEN = Cannot open the selected book
 # URL is not supported by Fanfix
-# (FORMAT: STRING) %s = URL
+# (FORMAT: STRING) 
 ERROR_URL_NOT_SUPPORTED = URL not supported: %s
 # cannot import the URL
-# (FORMAT: STRING) %s = URL, %s = reasons
+# (FORMAT: STRING) 
 ERROR_URL_IMPORT_FAILED = Failed to import %s:\n\
 %s
 # (html) the chapter progression value used on the viewers
-# (FORMAT: STRING) %d = chapter number, %d = total chapters
+# (FORMAT: STRING) 
 CHAPTER_HTML_UNNAMED = &nbsp;&nbsp;<B>Chapter <SPAN COLOR='#7777DD'>%d</SPAN>/%d</B>
 # (html) the chapter progression value used on the viewers
-# (FORMAT: STRING) %d = chapter number, %d = total chapters, %s = chapter name
+# (FORMAT: STRING) 
 CHAPTER_HTML_NAMED = &nbsp;&nbsp;<B>Chapter <SPAN COLOR='#7777DD'>%d</SPAN>/%d</B>: %s
 # (NO html) the chapter progression value used on the viewers
-# (FORMAT: STRING) %d = current image number, %d = total images
+# (FORMAT: STRING) 
 IMAGE_PROGRESSION = Image %d / %d
index 2b6d19200b0773404d03ee49e052cc58fe6ee1b2..ee7c8871ceb214f6081f2d8c59ca99b9caf30cf1 100644 (file)
@@ -8,10 +8,10 @@
 
 
 # the title of the main window of Fanfix, the library
-# (FORMAT: STRING) %s = current Fanfix version
+# (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) %s = current Fanfix version, %s = library name
+# (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) 
@@ -38,7 +38,7 @@ SUBTITLE_MOVE_TO = Déplacer vers :
 # (FORMAT: STRING) 
 TITLE_DELETE = Supprimer le livre
 # the subtitle of the 'delete' window of Fanfix
-# (FORMAT: STRING) %s = LUID of the story, %s = title of the story
+# (FORMAT: STRING) 
 SUBTITLE_DELETE = Supprimer %s : %s
 # the title of the 'library error' dialogue
 # (FORMAT: STRING) 
@@ -53,27 +53,28 @@ SUBTITLE_IMPORT_URL = L'URL du livre à importer
 # (FORMAT: STRING) 
 TITLE_ERROR = Error
 # the title of a story for the properties dialogue, the viewers...
-# (FORMAT: STRING) %s = LUID of the story, %s = title of the story
+# (FORMAT: STRING) 
 TITLE_STORY = %s: %s
 # HTML text used to notify of a new version
-# (FORMAT: STRING) %s = url link in HTML
+# (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) %s = the newer version number
+# (FORMAT: STRING) 
 NEW_VERSION_VERSION = Version %s
 # show the number of words of a book
-# (FORMAT: STRING) %s = the number
+# (FORMAT: STRING) 
 BOOK_COUNT_WORDS = %s mots
 # show the number of images of a book
-# (FORMAT: STRING) %s = the number
+# (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) %s = the number
+# (FORMAT: STRING) 
 BOOK_COUNT_STORIES = %s livres
-# the file menu (FORMAT: STRING) 
+# the file menu
+# (FORMAT: STRING) 
 MENU_FILE = Fichier
 # the file/exit menu button
 # (FORMAT: STRING) 
@@ -108,8 +109,12 @@ 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) 
+# the edit menu
+# (FORMAT: STRING) 
 MENU_EDIT = Edition
+# the edit/send to cache menu button, to download the story into the cache if not already done
+# (FORMAT: STRING) 
+MENU_EDIT_DOWNLOAD_TO_CACHE = Charger en cache 
 # the clear cache menu button, to clear the cache for a single book
 # (FORMAT: STRING) 
 MENU_EDIT_CLEAR_CACHE = Nettoyer le cache
@@ -128,7 +133,8 @@ 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) 
+# 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) 
@@ -158,7 +164,7 @@ MENU_AUTHORS_UNKNOWN = [inconnu]
 # (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) %s = new source name
+# (FORMAT: STRING) 
 PROGRESS_CHANGE_SOURCE = Change la source du livre en %s
 # default description if the error is not known
 # (FORMAT: STRING) 
@@ -176,18 +182,18 @@ ERROR_LIB_STATUS_UNAVAILABLE = Librairie indisponible
 # (FORMAT: STRING) 
 ERROR_CANNOT_OPEN = Impossible d'ouvrir le livre sélectionné
 # URL is not supported by Fanfix
-# (FORMAT: STRING) %s = URL
+# (FORMAT: STRING) 
 ERROR_URL_NOT_SUPPORTED = URL non supportée : %s
 # cannot import the URL
-# (FORMAT: STRING) %s = URL, %s = reasons
+# (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) %d = chapter number, %d = total chapters
+# (FORMAT: STRING) 
 CHAPTER_HTML_UNNAMED = &nbsp;&nbsp;<B>Chapitre <SPAN COLOR='#444466'>%d</SPAN>&nbsp;/&nbsp;%d</B>
 # (html) the chapter progression value used on the viewers
-# (FORMAT: STRING) %d = chapter number, %d = total chapters, %s = chapter name
+# (FORMAT: STRING) 
 CHAPTER_HTML_NAMED = &nbsp;&nbsp;<B>Chapitre <SPAN COLOR='#444466'>%d</SPAN>&nbsp;/&nbsp;%d</B>: %s
 # (NO html) the chapter progression value used on the viewers
-# (FORMAT: STRING) %d = current image number, %d = total images
+# (FORMAT: STRING) 
 IMAGE_PROGRESSION = Image %d / %d
index 380c5c988ca43dad853f7e4edb25e657a55ed1ba..099859dcfaffed075f30b3d716b790f644a43a09 100644 (file)
@@ -751,14 +751,14 @@ abstract public class BasicLibrary {
         * @param pg
         *            the optional progress reporter
         * 
-        * @return the imported {@link Story}
+        * @return the imported Story {@link MetaData}
         * 
         * @throws UnknownHostException
         *             if the host is not supported
         * @throws IOException
         *             in case of I/O error
         */
-       public Story imprt(URL url, Progress pg) throws IOException {
+       public MetaData imprt(URL url, Progress pg) throws IOException {
                if (pg == null)
                        pg = new Progress();
 
@@ -776,7 +776,7 @@ abstract public class BasicLibrary {
                Story story = save(support.process(pgProcess), pgSave);
                pg.done();
 
-               return story;
+               return story.getMeta();
        }
 
        /**
index 019acd210d1fb3b2263db468e2c21e6e6bfd1d69..e8743b63cd9d18ca0ef0ba3316aa204433b4bf34 100644 (file)
@@ -216,11 +216,17 @@ public class CacheLibrary extends BasicLibrary {
        @Override
        protected void updateInfo(MetaData meta) throws IOException {
                if (meta != null && metas != null) {
+                       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);
+                       }
                }
 
                cacheLib.updateInfo(meta);
@@ -345,7 +351,7 @@ public class CacheLibrary extends BasicLibrary {
        }
 
        @Override
-       public Story imprt(URL url, Progress pg) throws IOException {
+       public MetaData imprt(URL url, Progress pg) throws IOException {
                if (pg == null) {
                        pg = new Progress();
                }
@@ -356,13 +362,13 @@ public class CacheLibrary extends BasicLibrary {
                pg.addProgress(pgImprt, 7);
                pg.addProgress(pgCache, 3);
 
-               Story story = lib.imprt(url, pgImprt);
-               cacheLib.save(story, story.getMeta().getLuid(), pgCache);
-
-               updateInfo(story.getMeta());
-
+               MetaData meta = lib.imprt(url, pgImprt);
+               updateInfo(meta);
+               
+               clearFromCache(meta.getLuid());
+               
                pg.done();
-               return story;
+               return meta;
        }
 
        // All the following methods are only used by Save and Delete in
index a6c68546882c0cdd64bda6c7592976d228bdfb4d..ce4305aadad96bfe9446817d467234545ccfce7a 100644 (file)
@@ -78,7 +78,7 @@ public class RemoteLibrary extends BasicLibrary {
         * <li><b>wl</b>: flag to allow access to all the stories (bypassing the
         * whitelist if it exists)</li>
         * </ul>
-        * 
+        * <p>
         * Some examples:
         * <ul>
         * <li><b>my_key</b>: normal connection, will take the default server
@@ -352,8 +352,9 @@ public class RemoteLibrary extends BasicLibrary {
 
        @Override
        // Could work (more slowly) without it
-       public Story imprt(final URL url, Progress pg) throws IOException {
+       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);
                }
@@ -364,13 +365,7 @@ public class RemoteLibrary extends BasicLibrary {
                        pg = new Progress();
                }
 
-               pg.setMinMax(0, 2);
-               Progress pgImprt = new Progress();
-               Progress pgGet = new Progress();
-               pg.addProgress(pgImprt, 1);
-               pg.addProgress(pgGet, 1);
-
-               final Progress pgF = pgImprt;
+               final Progress pgF = pg;
                final String[] luid = new String[1];
 
                connectRemoteAction(new RemoteAction() {
@@ -399,11 +394,8 @@ public class RemoteLibrary extends BasicLibrary {
                        throw new IOException("Remote failure");
                }
 
-               Story story = getStory(luid[0], pgGet);
-               pgGet.done();
-
                pg.done();
-               return story;
+               return getInfo(luid[0]);
        }
 
        @Override
index 43f61b096cbb6e8d452a47b13853a1a2fc15af7c..dc9688c4de69c0348a1ee7dbac4665ec581d905a 100644 (file)
@@ -289,10 +289,10 @@ public class RemoteLibraryServer extends ServerObject {
                        }
 
                        Progress pg = createPgForwarder(action);
-                       Story story = Instance.getLibrary().imprt(
+                       MetaData meta = Instance.getLibrary().imprt(
                                        new URL((String) args[0]), pg);
                        forcePgDoneSent(pg);
-                       return story.getMeta().getLuid();
+                       return meta.getLuid();
                } else if ("DELETE_STORY".equals(command)) {
                        if (!rw) {
                                throw new RemoteLibraryException("Read-Only remote library: "
index b720af409250248312d3d0cc5a2a2da45c158ddb..0205e11489ba783b85102781c93d7932207489eb 100644 (file)
@@ -317,8 +317,6 @@ class GuiReader extends BasicReader {
        /**
         * "Open" the given {@link Story}. It usually involves starting an external
         * program adapted to the given file type.
-        * <p>
-        * Asynchronous method.
         * 
         * @param luid
         *            the luid of the {@link Story} to open
@@ -362,6 +360,24 @@ class GuiReader extends BasicReader {
                }
        }
 
+
+       /**
+        * "Prefetch" the given {@link Story}.
+        * <p>
+        * Synchronous method.
+        * 
+        * @param luid
+        *            the luid of the {@link Story} to prefetch
+        * @param pg
+        *            the optional progress (we may need to prepare the
+        *            {@link Story} for reading
+        * 
+        * @throws IOException
+        *             in case of I/O errors
+        */
+       void prefetch(String luid, Progress pg) throws IOException {
+               cacheLib.getFile(luid, pg);
+       }
        /**
         * Change the source of the given {@link Story} (the source is the main
         * information used to group the stories together).
index 47b462360eeb7e6f5cc634c2957866e0dde25a7b..73ccdaa0c1b34427a6c10f64046de8199062d329 100644 (file)
@@ -18,6 +18,8 @@ import be.nikiroo.fanfix.reader.Reader;
 
 /**
  * A book item presented in a {@link GuiReaderFrame}.
+ * <p>
+ * Can be a story, or a comic or... a group.
  * 
  * @author niki
  */
index f071be02e59d559991317a57c193ab8e04e2fb81..c163834f493f875755ac0e3d15af12263433d7a2 100644 (file)
@@ -53,6 +53,15 @@ public class GuiReaderBookInfo {
                this.id = id;
                this.value = value;
        }
+       
+       /**
+        * The type of {@link GuiReaderBookInfo}.
+        * 
+        * @return the type
+        */
+       public Type getType() {
+               return type;
+       }
 
        /**
         * Get the main info to display for this book (a title, an author, a
index c0e8ec6fc430444788f26f03263d696e580d7744..f3748218fcbbcd1b25b70816454cd0d56d2870ac 100644 (file)
@@ -36,7 +36,7 @@ import be.nikiroo.fanfix.library.LocalLibrary;
 import be.nikiroo.fanfix.output.BasicOutput.OutputType;
 import be.nikiroo.fanfix.reader.BasicReader;
 import be.nikiroo.fanfix.reader.ui.GuiReaderMainPanel.FrameHelper;
-import be.nikiroo.fanfix.reader.ui.GuiReaderMainPanel.StoryRunnable;
+import be.nikiroo.fanfix.reader.ui.GuiReaderMainPanel.MetaDataRunnable;
 import be.nikiroo.fanfix.searchable.BasicSearchable;
 import be.nikiroo.fanfix.supported.SupportType;
 import be.nikiroo.utils.Progress;
@@ -103,6 +103,7 @@ class GuiReaderFrame extends JFrame implements FrameHelper {
                        popup.add(createMenuItemSetCoverForSource());
                        popup.add(createMenuItemSetCoverForAuthor());
                }
+               popup.add(createMenuItemDownloadToCache());
                popup.add(createMenuItemClearCache());
                if (status.isWritable()) {
                        popup.add(createMenuItemRedownload());
@@ -184,6 +185,7 @@ class GuiReaderFrame extends JFrame implements FrameHelper {
 
                edit.add(createMenuItemSetCoverForSource());
                edit.add(createMenuItemSetCoverForAuthor());
+               edit.add(createMenuItemDownloadToCache());
                edit.add(createMenuItemClearCache());
                edit.add(createMenuItemRedownload());
                edit.addSeparator();
@@ -756,10 +758,9 @@ class GuiReaderFrame extends JFrame implements FrameHelper {
                                final GuiReaderBook selectedBook = mainPanel.getSelectedBook();
                                if (selectedBook != null) {
                                        final MetaData meta = selectedBook.getInfo().getMeta();
-                                       mainPanel.imprt(meta.getUrl(), new StoryRunnable() {
+                                       mainPanel.imprt(meta.getUrl(), new MetaDataRunnable() {
                                                @Override
-                                               public void run(Story story) {
-                                                       MetaData newMeta = story.getMeta();
+                                               public void run(MetaData newMeta) {
                                                        if (!newMeta.getSource().equals(meta.getSource())) {
                                                                reader.changeSource(newMeta.getLuid(),
                                                                                meta.getSource());
@@ -772,6 +773,29 @@ class GuiReaderFrame extends JFrame implements FrameHelper {
 
                return refresh;
        }
+       
+       /**
+        * Create the download to cache menu item.
+        * 
+        * @return the item
+        */
+       private JMenuItem createMenuItemDownloadToCache() {
+               JMenuItem refresh = new JMenuItem(
+                               GuiReader.trans(StringIdGui.MENU_EDIT_DOWNLOAD_TO_CACHE),
+                               KeyEvent.VK_T);
+               refresh.addActionListener(new ActionListener() {
+                       @Override
+                       public void actionPerformed(ActionEvent e) {
+                               final GuiReaderBook selectedBook = mainPanel.getSelectedBook();
+                               if (selectedBook != null) {
+                                       mainPanel.prefetchBook(selectedBook);
+                               }
+                       }
+               });
+
+               return refresh;
+       }
+
 
        /**
         * Create the delete menu item.
index 8593fe6471c816812220defeedf5775fe1757642..476e130b01bc6acd7eb4804ddd7d19ddc15db4c4 100644 (file)
@@ -17,6 +17,7 @@ import java.lang.reflect.InvocationTargetException;
 import java.net.URL;
 import java.net.UnknownHostException;
 import java.util.ArrayList;
+import java.util.LinkedList;
 import java.util.List;
 import java.util.Map;
 import java.util.TreeMap;
@@ -42,6 +43,7 @@ import be.nikiroo.fanfix.library.BasicLibrary.Status;
 import be.nikiroo.fanfix.library.LocalLibrary;
 import be.nikiroo.fanfix.reader.BasicReader;
 import be.nikiroo.fanfix.reader.ui.GuiReaderBook.BookActionListener;
+import be.nikiroo.fanfix.reader.ui.GuiReaderBookInfo.Type;
 import be.nikiroo.utils.Progress;
 import be.nikiroo.utils.ui.ProgressBar;
 
@@ -108,18 +110,18 @@ class GuiReaderMainPanel extends JPanel {
        }
 
        /**
-        * A {@link Runnable} with a {@link Story} parameter.
+        * A {@link Runnable} with a {@link MetaData} parameter.
         * 
         * @author niki
         */
-       public interface StoryRunnable {
+       public interface MetaDataRunnable {
                /**
                 * Run the action.
                 * 
-                * @param story
-                *            the story
+                * @param meta
+                *            the meta of the story
                 */
-               public void run(Story story);
+               public void run(MetaData meta);
        }
 
        /**
@@ -414,6 +416,70 @@ class GuiReaderMainPanel extends JPanel {
                        }
                });
        }
+       
+       /**
+        * Prefetch a {@link GuiReaderBook} item (which can be a group, in which
+        * case we prefetch all its members).
+        * 
+        * @param book
+        *            the {@link GuiReaderBook} to open
+        */
+       public void prefetchBook(final GuiReaderBook book) {
+               final List<String> luids = new LinkedList<String>();
+               try {
+                       switch (book.getInfo().getType()) {
+                       case STORY:
+                               luids.add(book.getInfo().getMeta().getLuid());
+                               break;
+                       case SOURCE:
+                               for (MetaData meta : helper.getReader().getLibrary()
+                                               .getListBySource(book.getInfo().getMainInfo())) {
+                                       luids.add(meta.getLuid());
+                               }
+                               break;
+                       case AUTHOR:
+                               for (MetaData meta : helper.getReader().getLibrary()
+                                               .getListByAuthor(book.getInfo().getMainInfo())) {
+                                       luids.add(meta.getLuid());
+                               }
+                               break;
+                       }
+               } catch (IOException e) {
+                       Instance.getTraceHandler().error(e);
+               }
+
+               final Progress pg = new Progress();
+               pg.setMax(luids.size());
+
+               outOfUi(pg, false, new Runnable() {
+                       @Override
+                       public void run() {
+                               try {
+                                       for (String luid : luids) {
+                                               Progress pgStep = new Progress();
+                                               pg.addProgress(pgStep, 1);
+
+                                               helper.getReader().prefetch(luid, pgStep);
+                                       }
+
+                                       // TODO: also set the green button on sources/authors?
+                                       // requires to do the same when all stories inside are green
+                                       if (book.getInfo().getType() == Type.STORY) {
+                                               SwingUtilities.invokeLater(new Runnable() {
+                                                       @Override
+                                                       public void run() {
+                                                               book.setCached(true);
+                                                       }
+                                               });
+                                       }
+                               } catch (IOException e) {
+                                       Instance.getTraceHandler().error(e);
+                                       error(GuiReader.trans(StringIdGui.ERROR_CANNOT_OPEN),
+                                                       GuiReader.trans(StringIdGui.TITLE_ERROR), e);
+                               }
+                       }
+               });
+       }
 
        /**
         * Process the given action out of the Swing UI thread and link the given
@@ -517,7 +583,8 @@ class GuiReaderMainPanel extends JPanel {
                                // No data will be handled
                        }
 
-                       if (clipboard == null || !clipboard.startsWith("http")) {
+                       if (clipboard == null || !(clipboard.startsWith("http://") || //
+                                       clipboard.startsWith("https://"))) {
                                clipboard = "";
                        }
 
@@ -548,7 +615,7 @@ class GuiReaderMainPanel extends JPanel {
         * @param onSuccessPgName
         *            the name to use for the onSuccess progress bar
         */
-       public void imprt(final String url, final StoryRunnable onSuccess,
+       public void imprt(final String url, final MetaDataRunnable onSuccess,
                        String onSuccessPgName) {
                final Progress pg = new Progress();
                final Progress pgImprt = new Progress();
@@ -560,9 +627,9 @@ class GuiReaderMainPanel extends JPanel {
                        @Override
                        public void run() {
                                Exception ex = null;
-                               Story story = null;
+                               MetaData meta = null;
                                try {
-                                       story = helper.getReader().getLibrary()
+                                       meta = helper.getReader().getLibrary()
                                                        .imprt(BasicReader.getUrl(url), pgImprt);
                                } catch (IOException e) {
                                        ex = e;
@@ -586,7 +653,7 @@ class GuiReaderMainPanel extends JPanel {
                                        }
                                } else {
                                        if (onSuccess != null) {
-                                               onSuccess.run(story);
+                                               onSuccess.run(meta);
                                        }
                                }
                                pgOnSuccess.done();
index 11f1d3870fdf1d6d4f81d9e4c7742d07991f06c5..4cc10b4dc54099a4511d5b06f207dc32148ee3f6 100644 (file)
@@ -16,7 +16,6 @@ import javax.swing.JPanel;
 import javax.swing.JProgressBar;
 import javax.swing.JScrollPane;
 import javax.swing.SwingConstants;
-import javax.swing.plaf.basic.BasicArrowButton;
 
 import be.nikiroo.fanfix.Instance;
 import be.nikiroo.fanfix.bundles.StringIdGui;
index 35fdec164c5ab8252a7f4a9aa05d96790c14aa71..d772561ad6ff008069ccecb6e98d894475ca6977 100644 (file)
@@ -18,11 +18,16 @@ import be.nikiroo.utils.test.TestLauncher;
  */
 public class Test extends TestLauncher {
        //
-       // 3 files can control the test:
+       // 4 files can control the test:
        // - 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
@@ -39,16 +44,18 @@ public class Test extends TestLauncher {
         * @param args
         *            the arguments to configure the number of columns and the ok/ko
         *            {@link String}s
+        * @param urlsAllowed
+        *            allow testing URLs (<tt>.url</tt> files)
         * 
         * @throws IOException
         */
-       public Test(String[] args) throws IOException {
+       public Test(String[] args, boolean urlsAllowed) throws IOException {
                super("Fanfix", args);
                Instance.setTraceHandler(null);
                addSeries(new BasicSupportUtilitiesTest(args));
                addSeries(new BasicSupportDeprecatedTest(args));
                addSeries(new LibraryTest(args));
-               
+
                File sources = new File("test/");
                if (sources.isDirectory()) {
                        for (File file : sources.listFiles()) {
@@ -62,7 +69,7 @@ public class Test extends TestLauncher {
                                                + file.getName()).getAbsolutePath();
 
                                String uri;
-                               if (file.getName().endsWith(".url")) {
+                               if (urlsAllowed && file.getName().endsWith(".url")) {
                                        uri = IOUtils.readSmallFile(file).trim();
                                } else if (file.getName().endsWith(".story")) {
                                        uri = file.getAbsolutePath();
@@ -93,19 +100,22 @@ public class Test extends TestLauncher {
                // Can force refresh
                boolean forceRefresh = new File("test/FORCE_REFRESH").exists();
 
+               // Allow URLs:
+               boolean urlsAllowed = new File("test/URLS").exists();
+
+               
                // Only download files if allowed:
                boolean offline = new File("test/OFFLINE").exists();
                Instance.getCache().setOffline(offline);
 
+
+               
                int result = 0;
                tempFiles = new TempFiles("fanfix-test");
                try {
                        File tmpConfig = tempFiles.createTempDir("fanfix-config");
                        File localCache = new File("test/CACHE");
-                       if (forceRefresh) {
-                               IOUtils.deltree(localCache);
-                       }
-                       localCache.mkdirs();
+                       prepareCache(localCache, forceRefresh);
 
                        ConfigBundle config = new ConfigBundle();
                        Bundles.setDirectory(tmpConfig.getAbsolutePath());
@@ -118,12 +128,13 @@ public class Test extends TestLauncher {
                        Instance.init(true);
                        Instance.getCache().setOffline(offline);
 
-                       TestLauncher tests = new Test(args);
+                       TestLauncher tests = new Test(args, urlsAllowed);
                        tests.setDetails(verbose);
 
                        result = tests.launch();
 
                        IOUtils.deltree(tmpConfig);
+                       prepareCache(localCache, forceRefresh);
                } finally {
                        // Test temp files
                        tempFiles.close();
@@ -134,4 +145,33 @@ public class Test extends TestLauncher {
 
                System.exit(result);
        }
+
+       /**
+        * Prepare the cache (or clean it up).
+        * <p>
+        * The cache directory will always exist if this method succeed
+        * 
+        * @param localCache
+        *            the cache directory
+        * @param forceRefresh
+        *            TRUE to force acache refresh (delete all files)
+        * 
+        * @throw IOException if the cache cannot be created
+        */
+       private static void prepareCache(File localCache, boolean forceRefresh)
+                       throws IOException {
+               // if needed
+               localCache.mkdirs();
+
+               if (!localCache.isDirectory()) {
+                       throw new IOException("Cannot get a cache");
+               }
+
+               // delete local cached files (_*) or all files if forceRefresh
+               for (File f : localCache.listFiles()) {
+                       if (forceRefresh || f.getName().startsWith("_")) {
+                               IOUtils.deltree(f);
+                       }
+               }
+       }
 }