1 package be
.nikiroo
.fanfix
;
4 import java
.io
.IOException
;
7 import be
.nikiroo
.fanfix
.bundles
.Config
;
8 import be
.nikiroo
.fanfix
.bundles
.ConfigBundle
;
9 import be
.nikiroo
.fanfix
.bundles
.StringId
;
10 import be
.nikiroo
.fanfix
.bundles
.StringIdBundle
;
11 import be
.nikiroo
.fanfix
.bundles
.StringIdGuiBundle
;
12 import be
.nikiroo
.fanfix
.bundles
.UiConfig
;
13 import be
.nikiroo
.fanfix
.bundles
.UiConfigBundle
;
14 import be
.nikiroo
.fanfix
.library
.BasicLibrary
;
15 import be
.nikiroo
.fanfix
.library
.CacheLibrary
;
16 import be
.nikiroo
.fanfix
.library
.LocalLibrary
;
17 import be
.nikiroo
.fanfix
.library
.RemoteLibrary
;
18 import be
.nikiroo
.fanfix
.library
.WebLibrary
;
19 import be
.nikiroo
.utils
.Cache
;
20 import be
.nikiroo
.utils
.IOUtils
;
21 import be
.nikiroo
.utils
.Image
;
22 import be
.nikiroo
.utils
.Proxy
;
23 import be
.nikiroo
.utils
.TempFiles
;
24 import be
.nikiroo
.utils
.TraceHandler
;
25 import be
.nikiroo
.utils
.resources
.Bundles
;
28 * Global state for the program (services and singletons).
32 public class Instance
{
33 static private Instance instance
;
34 static private Object instancelock
= new Object();
36 private ConfigBundle config
;
37 private UiConfigBundle uiconfig
;
38 private StringIdBundle trans
;
39 private DataLoader cache
;
40 private StringIdGuiBundle transGui
;
41 private BasicLibrary lib
;
42 private File coverDir
;
43 private File readerTmp
;
44 private File remoteDir
;
45 private String configDir
;
46 private TraceHandler tracer
;
47 private TempFiles tempFiles
;
50 * Initialise the instance -- if already initialised, nothing will happen.
52 * Before calling this method, you may call
53 * {@link Bundles#setDirectory(String)} if wanted.
55 * Note that this method will honour some environment variables, the 3 most
56 * important ones probably being:
58 * <li><tt>DEBUG</tt>: will enable DEBUG output if set to 1 (or Y or TRUE or
59 * ON, case insensitive)</li>
60 * <li><tt>CONFIG_DIR</tt>: will use this directory as configuration
61 * directory (supports $HOME notation, defaults to $HOME/.fanfix</li>
62 * <li><tt>BOOKS_DIR</tt>: will use this directory as library directory
63 * (supports $HOME notation, defaults to $HOME/Books</li>
66 static public void init() {
71 * Initialise the instance -- if already initialised, nothing will happen
72 * unless you pass TRUE to <tt>force</tt>.
74 * Before calling this method, you may call
75 * {@link Bundles#setDirectory(String)} if wanted.
77 * Note: forcing the initialisation can be dangerous, so make sure to only
78 * make it under controlled circumstances -- for instance, at the start of
79 * the program, you could call {@link Instance#init()}, change some settings
80 * because you want to force those settings (it will also forbid users to
81 * change them!) and then call {@link Instance#init(boolean)} with
82 * <tt>force</tt> set to TRUE.
85 * force the initialisation even if already initialised
87 static public void init(boolean force
) {
88 synchronized (instancelock
) {
89 if (instance
== null || force
) {
90 instance
= new Instance();
97 * Force-initialise the {@link Instance} to a known value.
99 * Usually for DEBUG/Test purposes.
102 * the actual Instance to use
104 static public void init(Instance instance
) {
105 Instance
.instance
= instance
;
109 * The (mostly unique) instance of this {@link Instance}.
111 * @return the (mostly unique) instance
113 public static Instance
getInstance() {
118 * Actually initialise the instance.
120 * Before calling this method, you may call
121 * {@link Bundles#setDirectory(String)} if wanted.
123 protected Instance() {
124 // Before we can configure it:
125 Boolean debug
= checkEnv("DEBUG");
126 boolean trace
= debug
!= null && debug
;
127 tracer
= new TraceHandler(true, trace
, trace
);
130 configDir
= getConfigDir();
131 if (!new File(configDir
).exists()) {
132 new File(configDir
).mkdirs();
135 // Most of the rest is dependent upon this:
136 createConfigs(configDir
, false);
139 Proxy
.use(config
.getString(Config
.NETWORK_PROXY
));
143 debug
= config
.getBoolean(Config
.DEBUG_ERR
, false);
144 trace
= config
.getBoolean(Config
.DEBUG_TRACE
, false);
147 tracer
= new TraceHandler(true, debug
, trace
);
150 remoteDir
= new File(configDir
, "remote");
151 lib
= createDefaultLibrary(remoteDir
);
153 // create cache and TMP
154 File tmp
= getFile(Config
.CACHE_DIR
, configDir
, "tmp");
155 Image
.setTemporaryFilesRoot(new File(tmp
.getParent(), "tmp.images"));
157 String ua
= config
.getString(Config
.NETWORK_USER_AGENT
, "");
159 int hours
= config
.getInteger(Config
.CACHE_MAX_TIME_CHANGING
, 0);
160 int hoursLarge
= config
.getInteger(Config
.CACHE_MAX_TIME_STABLE
, 0);
161 cache
= new DataLoader(tmp
, ua
, hours
, hoursLarge
);
162 } catch (IOException e
) {
163 tracer
.error(new IOException(
164 "Cannot create cache (will continue without cache)", e
));
165 cache
= new DataLoader(ua
);
168 cache
.setTraceHandler(tracer
);
170 // readerTmp / coverDir
171 readerTmp
= getFile(UiConfig
.CACHE_DIR_LOCAL_READER
, configDir
,
173 coverDir
= getFile(Config
.DEFAULT_COVERS_DIR
, configDir
, "covers");
177 tempFiles
= new TempFiles("fanfix");
178 } catch (IOException e
) {
180 new IOException("Cannot create temporary directory", e
));
185 * The traces handler for this {@link Cache}.
189 * @return the traces handler (never NULL)
191 public TraceHandler
getTraceHandler() {
196 * The traces handler for this {@link Cache}.
199 * the new traces handler or NULL
201 public void setTraceHandler(TraceHandler tracer
) {
202 if (tracer
== null) {
203 tracer
= new TraceHandler(false, false, false);
206 this.tracer
= tracer
;
207 cache
.setTraceHandler(tracer
);
211 * Get the (unique) configuration service for the program.
213 * @return the configuration service
215 public ConfigBundle
getConfig() {
220 * Get the (unique) UI configuration service for the program.
222 * @return the configuration service
224 public UiConfigBundle
getUiConfig() {
229 * Reset the configuration.
232 * also reset the translation files
234 public void resetConfig(boolean resetTrans
) {
235 String dir
= Bundles
.getDirectory();
236 Bundles
.setDirectory(null);
239 ConfigBundle config
= new ConfigBundle();
240 config
.updateFile(configDir
);
241 } catch (IOException e
) {
245 UiConfigBundle uiconfig
= new UiConfigBundle();
246 uiconfig
.updateFile(configDir
);
247 } catch (IOException e
) {
253 StringIdBundle trans
= new StringIdBundle(null);
254 trans
.updateFile(configDir
);
255 } catch (IOException e
) {
260 Bundles
.setDirectory(dir
);
265 * Get the (unique) {@link DataLoader} for the program.
267 * @return the {@link DataLoader}
269 public DataLoader
getCache() {
274 * Get the (unique) {link StringIdBundle} for the program.
276 * This is used for the translations of the core parts of Fanfix.
278 * @return the {link StringIdBundle}
280 public StringIdBundle
getTrans() {
285 * Get the (unique) {link StringIdGuiBundle} for the program.
287 * This is used for the translations of the GUI parts of Fanfix.
289 * @return the {link StringIdGuiBundle}
291 public StringIdGuiBundle
getTransGui() {
296 * Get the (unique) {@link BasicLibrary} for the program.
298 * @return the {@link BasicLibrary}
300 public BasicLibrary
getLibrary() {
302 throw new NullPointerException("We don't have a library to return");
309 * Change the default {@link BasicLibrary} for this program.
314 * the new {@link BasicLibrary}
316 public void setLibrary(BasicLibrary lib
) {
321 * Return the directory where to look for default cover pages.
323 * @return the default covers directory
325 public File
getCoverDir() {
330 * Return the directory where to store temporary files for the local reader.
332 * @return the directory
334 public File
getReaderDir() {
339 * Return the directory where to store temporary files for the remote
340 * {@link LocalLibrary}.
343 * the remote for this host
345 * @return the directory
347 public File
getRemoteDir(String host
) {
348 return getRemoteDir(remoteDir
, host
);
352 * Return the directory where to store temporary files for the remote
353 * {@link LocalLibrary}.
356 * the base remote directory
358 * the remote for this host
360 * @return the directory
362 private File
getRemoteDir(File remoteDir
, String host
) {
366 host
= host
.replace("fanfix://", "");
367 host
= host
.replace("http://", "");
368 host
= host
.replace("https://", "");
369 host
= host
.replaceAll("[^a-zA-Z0-9=+.-]", "_");
371 return new File(remoteDir
, host
);
378 * Check if we need to check that a new version of Fanfix is available.
380 * @return TRUE if we need to
382 public boolean isVersionCheckNeeded() {
384 long wait
= config
.getInteger(Config
.NETWORK_UPDATE_INTERVAL
, 0)
385 * 24 * 60 * 60 * 1000;
387 String lastUpString
= IOUtils
388 .readSmallFile(new File(configDir
, "LAST_UPDATE"));
389 long delay
= new Date().getTime()
390 - Long
.parseLong(lastUpString
);
397 } catch (Exception e
) {
398 // No file or bad file:
406 * Notify that we checked for a new version of Fanfix.
408 public void setVersionChecked() {
410 IOUtils
.writeSmallFile(new File(configDir
), "LAST_UPDATE",
411 Long
.toString(new Date().getTime()));
412 } catch (IOException e
) {
418 * The facility to use temporary files in this program.
420 * <b>MUST</b> be closed at end of program.
422 * @return the facility
424 public TempFiles
getTempFiles() {
429 * The configuration directory (will check, in order of preference, the
430 * system properties, the environment and then defaults to
431 * {@link Instance#getHome()}/.fanfix).
433 * @return the config directory
435 private String
getConfigDir() {
436 String configDir
= System
.getProperty("CONFIG_DIR");
438 if (configDir
== null) {
439 configDir
= System
.getenv("CONFIG_DIR");
442 if (configDir
== null) {
443 configDir
= new File(getHome(), ".fanfix").getPath();
450 * Create the config variables ({@link Instance#config},
451 * {@link Instance#uiconfig}, {@link Instance#trans} and
452 * {@link Instance#transGui}).
455 * the directory where to find the configuration files
457 * TRUE to reset the configuration files from the default
460 private void createConfigs(String configDir
, boolean refresh
) {
462 Bundles
.setDirectory(configDir
);
466 config
= new ConfigBundle();
467 config
.updateFile(configDir
);
468 } catch (IOException e
) {
473 uiconfig
= new UiConfigBundle();
474 uiconfig
.updateFile(configDir
);
475 } catch (IOException e
) {
479 // No updateFile for this one! (we do not want the user to have custom
480 // translations that won't accept updates from newer versions)
481 trans
= new StringIdBundle(getLang());
482 transGui
= new StringIdGuiBundle(getLang());
484 // Fix an old bug (we used to store custom translation files by
486 if (trans
.getString(StringId
.INPUT_DESC_CBZ
) == null) {
487 trans
.deleteFile(configDir
);
490 Boolean noutf
= checkEnv("NOUTF");
491 if (noutf
!= null && noutf
) {
492 trans
.setUnicode(false);
493 transGui
.setUnicode(false);
496 Bundles
.setDirectory(configDir
);
500 * Create the default library as specified by the config.
503 * the base remote directory if needed
505 * @return the default {@link BasicLibrary}
507 private BasicLibrary
createDefaultLibrary(File remoteDir
) {
508 BasicLibrary lib
= null;
510 boolean useRemote
= config
.getBoolean(Config
.REMOTE_LIBRARY_ENABLED
,
516 host
= config
.getString(Config
.REMOTE_LIBRARY_HOST
,
517 "fanfix://localhost");
518 port
= config
.getInteger(Config
.REMOTE_LIBRARY_PORT
, -1);
519 String key
= config
.getString(Config
.REMOTE_LIBRARY_KEY
);
521 if (!host
.startsWith("http://") && !host
.startsWith("https://")
522 && !host
.startsWith("fanfix://")) {
523 host
= "fanfix://" + host
;
526 tracer
.trace("Selecting remote library " + host
+ ":" + port
);
528 if (host
.startsWith("fanfix://")) {
529 lib
= new RemoteLibrary(key
, host
, port
);
531 lib
= new WebLibrary(key
, host
, port
);
534 lib
= new CacheLibrary(getRemoteDir(remoteDir
, host
), lib
,
536 } catch (Exception e
) {
538 new IOException("Cannot create remote library for: "
539 + host
+ ":" + port
, e
));
542 String libDir
= System
.getenv("BOOKS_DIR");
543 if (libDir
== null || libDir
.isEmpty()) {
544 libDir
= getFile(Config
.LIBRARY_DIR
, configDir
, "$HOME/Books")
548 lib
= new LocalLibrary(new File(libDir
), config
);
549 } catch (Exception e
) {
550 tracer
.error(new IOException(
551 "Cannot create library for directory: " + libDir
, e
));
559 * Return a path, but support the special $HOME variable.
562 * the key for the path, which may contain "$HOME"
564 * the directory to use as base if not absolute
566 * the default value if none (will be configDir-rooted if needed)
567 * @return the path, with expanded "$HOME" if needed
569 protected File
getFile(Config id
, String configDir
, String def
) {
570 String path
= config
.getString(id
, def
);
571 return getFile(path
, configDir
);
575 * Return a path, but support the special $HOME variable.
578 * the key for the path, which may contain "$HOME"
580 * the directory to use as base if not absolute
582 * the default value if none (will be configDir-rooted if needed)
583 * @return the path, with expanded "$HOME" if needed
585 protected File
getFile(UiConfig id
, String configDir
, String def
) {
586 String path
= uiconfig
.getString(id
, def
);
587 return getFile(path
, configDir
);
591 * Return a path, but support the special $HOME variable.
594 * the path, which may contain "$HOME"
596 * the directory to use as base if not absolute
597 * @return the path, with expanded "$HOME" if needed
599 protected File
getFile(String path
, String configDir
) {
601 if (path
!= null && !path
.isEmpty()) {
602 path
= path
.replace('/', File
.separatorChar
);
603 if (path
.contains("$HOME")) {
604 path
= path
.replace("$HOME", getHome());
605 } else if (!path
.startsWith("/")) {
606 path
= new File(configDir
, path
).getPath();
609 file
= new File(path
);
616 * Return the home directory from the environment (FANFIX_DIR) or the system
619 * The environment variable is tested first. Then, the custom property
620 * "fanfix.home" is tried, followed by the usual "user.home" then
621 * "java.io.tmp" if nothing else is found.
625 protected String
getHome() {
626 String home
= System
.getenv("FANFIX_DIR");
627 if (home
!= null && new File(home
).isFile()) {
631 if (home
== null || home
.trim().isEmpty()) {
632 home
= System
.getProperty("fanfix.home");
633 if (home
!= null && new File(home
).isFile()) {
638 if (home
== null || home
.trim().isEmpty()) {
639 home
= System
.getProperty("user.home");
640 if (!new File(home
).isDirectory()) {
645 if (home
== null || home
.trim().isEmpty()) {
646 home
= System
.getProperty("java.io.tmpdir");
647 if (!new File(home
).isDirectory()) {
660 * The language to use for the application (NULL = default system language).
662 * @return the language
664 protected String
getLang() {
665 String lang
= config
.getString(Config
.LANG
);
667 if (lang
== null || lang
.isEmpty()) {
668 if (System
.getenv("LANG") != null
669 && !System
.getenv("LANG").isEmpty()) {
670 lang
= System
.getenv("LANG");
674 if (lang
!= null && lang
.isEmpty()) {
682 * Check that the given environment variable is "enabled".
685 * the variable to check
687 * @return TRUE if it is
689 protected Boolean
checkEnv(String key
) {
690 String value
= System
.getenv(key
);
692 value
= value
.trim().toLowerCase();
693 if ("yes".equals(value
) || "true".equals(value
)
694 || "on".equals(value
) || "1".equals(value
)
695 || "y".equals(value
)) {