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
.utils
.Cache
;
19 import be
.nikiroo
.utils
.IOUtils
;
20 import be
.nikiroo
.utils
.Image
;
21 import be
.nikiroo
.utils
.Proxy
;
22 import be
.nikiroo
.utils
.TempFiles
;
23 import be
.nikiroo
.utils
.TraceHandler
;
24 import be
.nikiroo
.utils
.resources
.Bundles
;
27 * Global state for the program (services and singletons).
31 public class Instance
{
32 static private Instance instance
;
33 static private Object instancelock
= new Object();
35 private ConfigBundle config
;
36 private UiConfigBundle uiconfig
;
37 private StringIdBundle trans
;
38 private DataLoader cache
;
39 private StringIdGuiBundle transGui
;
40 private BasicLibrary lib
;
41 private File coverDir
;
42 private File readerTmp
;
43 private File remoteDir
;
44 private String configDir
;
45 private TraceHandler tracer
;
46 private TempFiles tempFiles
;
49 * Initialise the instance -- if already initialised, nothing will happen.
51 * Before calling this method, you may call
52 * {@link Bundles#setDirectory(String)} if wanted.
54 * Note that this method will honour some environment variables, the 3 most
55 * important ones probably being:
57 * <li><tt>DEBUG</tt>: will enable DEBUG output if set to 1 (or Y or TRUE or
58 * ON, case insensitive)</li>
59 * <li><tt>CONFIG_DIR</tt>: will use this directory as configuration
60 * directory (supports $HOME notation, defaults to $HOME/.fanfix</li>
61 * <li><tt>BOOKS_DIR</tt>: will use this directory as library directory
62 * (supports $HOME notation, defaults to $HOME/Books</li>
65 static public void init() {
70 * Initialise the instance -- if already initialised, nothing will happen unless
71 * you pass TRUE to <tt>force</tt>.
73 * Before calling this method, you may call {@link Bundles#setDirectory(String)}
76 * Note: forcing the initialisation can be dangerous, so make sure to only make
77 * it under controlled circumstances -- for instance, at the start of the
78 * program, you could call {@link Instance#init()}, change some settings because
79 * you want to force those settings (it will also forbid users to change them!)
80 * and then call {@link Instance#init(boolean)} with <tt>force</tt> set to TRUE.
82 * @param force force the initialisation even if already initialised
84 static public void init(boolean force
) {
85 synchronized (instancelock
) {
86 if (instance
== null || force
) {
87 instance
= new Instance();
94 * Force-initialise the {@link Instance} to a known value.
96 * Usually for DEBUG/Test purposes.
98 * @param instance the actual Instance to use
100 static public void init(Instance instance
) {
101 Instance
.instance
= instance
;
105 * The (mostly unique) instance of this {@link Instance}.
107 * @return the (mostly unique) instance
109 public static Instance
getInstance() {
114 * Actually initialise the instance.
116 * Before calling this method, you may call {@link Bundles#setDirectory(String)}
119 protected Instance() {
120 // Before we can configure it:
121 Boolean debug
= checkEnv("DEBUG");
122 boolean trace
= debug
!= null && debug
;
123 tracer
= new TraceHandler(true, trace
, trace
);
126 configDir
= getConfigDir();
127 if (!new File(configDir
).exists()) {
128 new File(configDir
).mkdirs();
131 // Most of the rest is dependent upon this:
132 createConfigs(configDir
, false);
135 Proxy
.use(config
.getString(Config
.NETWORK_PROXY
));
139 debug
= config
.getBoolean(Config
.DEBUG_ERR
, false);
140 trace
= config
.getBoolean(Config
.DEBUG_TRACE
, false);
143 tracer
= new TraceHandler(true, debug
, trace
);
146 remoteDir
= new File(configDir
, "remote");
147 lib
= createDefaultLibrary(remoteDir
);
149 // create cache and TMP
150 File tmp
= getFile(Config
.CACHE_DIR
, configDir
, "tmp");
151 Image
.setTemporaryFilesRoot(new File(tmp
.getParent(), "tmp.images"));
153 String ua
= config
.getString(Config
.NETWORK_USER_AGENT
, "");
155 int hours
= config
.getInteger(Config
.CACHE_MAX_TIME_CHANGING
, 0);
156 int hoursLarge
= config
.getInteger(Config
.CACHE_MAX_TIME_STABLE
, 0);
157 cache
= new DataLoader(tmp
, ua
, hours
, hoursLarge
);
158 } catch (IOException e
) {
159 tracer
.error(new IOException("Cannot create cache (will continue without cache)", e
));
160 cache
= new DataLoader(ua
);
163 cache
.setTraceHandler(tracer
);
165 // readerTmp / coverDir
166 readerTmp
= getFile(UiConfig
.CACHE_DIR_LOCAL_READER
, configDir
, "tmp-reader");
167 coverDir
= getFile(Config
.DEFAULT_COVERS_DIR
, configDir
, "covers");
171 tempFiles
= new TempFiles("fanfix");
172 } catch (IOException e
) {
173 tracer
.error(new IOException("Cannot create temporary directory", e
));
178 * The traces handler for this {@link Cache}.
182 * @return the traces handler (never NULL)
184 public TraceHandler
getTraceHandler() {
189 * The traces handler for this {@link Cache}.
191 * @param tracer the new traces handler or NULL
193 public void setTraceHandler(TraceHandler tracer
) {
194 if (tracer
== null) {
195 tracer
= new TraceHandler(false, false, false);
198 this.tracer
= tracer
;
199 cache
.setTraceHandler(tracer
);
203 * Get the (unique) configuration service for the program.
205 * @return the configuration service
207 public ConfigBundle
getConfig() {
212 * Get the (unique) UI configuration service for the program.
214 * @return the configuration service
216 public UiConfigBundle
getUiConfig() {
221 * Reset the configuration.
223 * @param resetTrans also reset the translation files
225 public void resetConfig(boolean resetTrans
) {
226 String dir
= Bundles
.getDirectory();
227 Bundles
.setDirectory(null);
230 ConfigBundle config
= new ConfigBundle();
231 config
.updateFile(configDir
);
232 } catch (IOException e
) {
236 UiConfigBundle uiconfig
= new UiConfigBundle();
237 uiconfig
.updateFile(configDir
);
238 } catch (IOException e
) {
244 StringIdBundle trans
= new StringIdBundle(null);
245 trans
.updateFile(configDir
);
246 } catch (IOException e
) {
251 Bundles
.setDirectory(dir
);
256 * Get the (unique) {@link DataLoader} for the program.
258 * @return the {@link DataLoader}
260 public DataLoader
getCache() {
265 * Get the (unique) {link StringIdBundle} for the program.
267 * This is used for the translations of the core parts of Fanfix.
269 * @return the {link StringIdBundle}
271 public StringIdBundle
getTrans() {
276 * Get the (unique) {link StringIdGuiBundle} for the program.
278 * This is used for the translations of the GUI parts of Fanfix.
280 * @return the {link StringIdGuiBundle}
282 public StringIdGuiBundle
getTransGui() {
287 * Get the (unique) {@link BasicLibrary} for the program.
289 * @return the {@link BasicLibrary}
291 public BasicLibrary
getLibrary() {
293 throw new NullPointerException("We don't have a library to return");
300 * Change the default {@link BasicLibrary} for this program.
305 * the new {@link BasicLibrary}
307 public void setLibrary(BasicLibrary lib
) {
312 * Return the directory where to look for default cover pages.
314 * @return the default covers directory
316 public File
getCoverDir() {
321 * Return the directory where to store temporary files for the local reader.
323 * @return the directory
325 public File
getReaderDir() {
330 * Return the directory where to store temporary files for the remote
331 * {@link LocalLibrary}.
333 * @param host the remote for this host
335 * @return the directory
337 public File
getRemoteDir(String host
) {
338 return getRemoteDir(remoteDir
, host
);
342 * Return the directory where to store temporary files for the remote
343 * {@link LocalLibrary}.
345 * @param remoteDir the base remote directory
346 * @param host the remote for this host
348 * @return the directory
350 private File
getRemoteDir(File remoteDir
, String host
) {
354 return new File(remoteDir
, host
);
361 * Check if we need to check that a new version of Fanfix is available.
363 * @return TRUE if we need to
365 public boolean isVersionCheckNeeded() {
367 long wait
= config
.getInteger(Config
.NETWORK_UPDATE_INTERVAL
, 0) * 24 * 60 * 60 * 1000;
369 String lastUpString
= IOUtils
.readSmallFile(new File(configDir
, "LAST_UPDATE"));
370 long delay
= new Date().getTime() - Long
.parseLong(lastUpString
);
377 } catch (Exception e
) {
378 // No file or bad file:
386 * Notify that we checked for a new version of Fanfix.
388 public void setVersionChecked() {
390 IOUtils
.writeSmallFile(new File(configDir
), "LAST_UPDATE", Long
.toString(new Date().getTime()));
391 } catch (IOException e
) {
397 * The facility to use temporary files in this program.
399 * <b>MUST</b> be closed at end of program.
401 * @return the facility
403 public TempFiles
getTempFiles() {
408 * The configuration directory (will check, in order of preference, the system
409 * properties, the environment and then defaults to
410 * {@link Instance#getHome()}/.fanfix).
412 * @return the config directory
414 private String
getConfigDir() {
415 String configDir
= System
.getProperty("CONFIG_DIR");
417 if (configDir
== null) {
418 configDir
= System
.getenv("CONFIG_DIR");
421 if (configDir
== null) {
422 configDir
= new File(getHome(), ".fanfix").getPath();
429 * Create the config variables ({@link Instance#config},
430 * {@link Instance#uiconfig}, {@link Instance#trans} and
431 * {@link Instance#transGui}).
433 * @param configDir the directory where to find the configuration files
434 * @param refresh TRUE to reset the configuration files from the default
437 private void createConfigs(String configDir
, boolean refresh
) {
439 Bundles
.setDirectory(configDir
);
443 config
= new ConfigBundle();
444 config
.updateFile(configDir
);
445 } catch (IOException e
) {
450 uiconfig
= new UiConfigBundle();
451 uiconfig
.updateFile(configDir
);
452 } catch (IOException e
) {
456 // No updateFile for this one! (we do not want the user to have custom
457 // translations that won't accept updates from newer versions)
458 trans
= new StringIdBundle(getLang());
459 transGui
= new StringIdGuiBundle(getLang());
461 // Fix an old bug (we used to store custom translation files by
463 if (trans
.getString(StringId
.INPUT_DESC_CBZ
) == null) {
464 trans
.deleteFile(configDir
);
467 Boolean noutf
= checkEnv("NOUTF");
468 if (noutf
!= null && noutf
) {
469 trans
.setUnicode(false);
470 transGui
.setUnicode(false);
473 Bundles
.setDirectory(configDir
);
477 * Create the default library as specified by the config.
479 * @param remoteDir the base remote directory if needed
481 * @return the default {@link BasicLibrary}
483 private BasicLibrary
createDefaultLibrary(File remoteDir
) {
484 BasicLibrary lib
= null;
486 boolean useRemote
= config
.getBoolean(Config
.REMOTE_LIBRARY_ENABLED
, false);
491 host
= config
.getString(Config
.REMOTE_LIBRARY_HOST
);
492 port
= config
.getInteger(Config
.REMOTE_LIBRARY_PORT
, -1);
493 String key
= config
.getString(Config
.REMOTE_LIBRARY_KEY
);
495 tracer
.trace("Selecting remote library " + host
+ ":" + port
);
496 lib
= new RemoteLibrary(key
, host
, port
);
497 lib
= new CacheLibrary(getRemoteDir(remoteDir
, host
), lib
, uiconfig
);
498 } catch (Exception e
) {
499 tracer
.error(new IOException("Cannot create remote library for: " + host
+ ":" + port
, e
));
502 String libDir
= System
.getenv("BOOKS_DIR");
503 if (libDir
== null || libDir
.isEmpty()) {
504 libDir
= getFile(Config
.LIBRARY_DIR
, configDir
, "$HOME/Books").getPath();
507 lib
= new LocalLibrary(new File(libDir
), config
);
508 } catch (Exception e
) {
509 tracer
.error(new IOException("Cannot create library for directory: " + libDir
, e
));
517 * Return a path, but support the special $HOME variable.
519 * @param id the key for the path, which may contain "$HOME"
520 * @param configDir the directory to use as base if not absolute
521 * @param def the default value if none (will be configDir-rooted if needed)
522 * @return the path, with expanded "$HOME" if needed
524 protected File
getFile(Config id
, String configDir
, String def
) {
525 String path
= config
.getString(id
, def
);
526 return getFile(path
, configDir
);
530 * Return a path, but support the special $HOME variable.
532 * @param id the key for the path, which may contain "$HOME"
533 * @param configDir the directory to use as base if not absolute
534 * @param def the default value if none (will be configDir-rooted if needed)
535 * @return the path, with expanded "$HOME" if needed
537 protected File
getFile(UiConfig id
, String configDir
, String def
) {
538 String path
= uiconfig
.getString(id
, def
);
539 return getFile(path
, configDir
);
543 * Return a path, but support the special $HOME variable.
545 * @param path the path, which may contain "$HOME"
546 * @param configDir the directory to use as base if not absolute
547 * @return the path, with expanded "$HOME" if needed
549 protected File
getFile(String path
, String configDir
) {
551 if (path
!= null && !path
.isEmpty()) {
552 path
= path
.replace('/', File
.separatorChar
);
553 if (path
.contains("$HOME")) {
554 path
= path
.replace("$HOME", getHome());
555 } else if (!path
.startsWith("/")) {
556 path
= new File(configDir
, path
).getPath();
559 file
= new File(path
);
566 * Return the home directory from the environment (FANFIX_DIR) or the system
569 * The environment variable is tested first. Then, the custom property
570 * "fanfix.home" is tried, followed by the usual "user.home" then "java.io.tmp"
571 * if nothing else is found.
575 protected String
getHome() {
576 String home
= System
.getenv("FANFIX_DIR");
577 if (home
!= null && new File(home
).isFile()) {
581 if (home
== null || home
.trim().isEmpty()) {
582 home
= System
.getProperty("fanfix.home");
583 if (home
!= null && new File(home
).isFile()) {
588 if (home
== null || home
.trim().isEmpty()) {
589 home
= System
.getProperty("user.home");
590 if (!new File(home
).isDirectory()) {
595 if (home
== null || home
.trim().isEmpty()) {
596 home
= System
.getProperty("java.io.tmpdir");
597 if (!new File(home
).isDirectory()) {
610 * The language to use for the application (NULL = default system language).
612 * @return the language
614 protected String
getLang() {
615 String lang
= config
.getString(Config
.LANG
);
617 if (lang
== null || lang
.isEmpty()) {
618 if (System
.getenv("LANG") != null && !System
.getenv("LANG").isEmpty()) {
619 lang
= System
.getenv("LANG");
623 if (lang
!= null && lang
.isEmpty()) {
631 * Check that the given environment variable is "enabled".
633 * @param key the variable to check
635 * @return TRUE if it is
637 protected Boolean
checkEnv(String key
) {
638 String value
= System
.getenv(key
);
640 value
= value
.trim().toLowerCase();
641 if ("yes".equals(value
) || "true".equals(value
) || "on".equals(value
) || "1".equals(value
)
642 || "y".equals(value
)) {