Merge commit '7ce18848c8327967ce27b90abf2e280953530b5f'
[nikiroo-utils.git] / src / be / nikiroo / fanfix / Instance.java
CommitLineData
08fe2e33
NR
1package be.nikiroo.fanfix;
2
3import java.io.File;
4import java.io.IOException;
b42117f1 5import java.util.Date;
08fe2e33
NR
6
7import be.nikiroo.fanfix.bundles.Config;
8import be.nikiroo.fanfix.bundles.ConfigBundle;
99ccbdf6 9import be.nikiroo.fanfix.bundles.StringId;
08fe2e33 10import be.nikiroo.fanfix.bundles.StringIdBundle;
5bc9573b 11import be.nikiroo.fanfix.bundles.StringIdGuiBundle;
b4dc6ab5
NR
12import be.nikiroo.fanfix.bundles.UiConfig;
13import be.nikiroo.fanfix.bundles.UiConfigBundle;
e42573a0 14import be.nikiroo.fanfix.library.BasicLibrary;
5895a958 15import be.nikiroo.fanfix.library.CacheLibrary;
e42573a0 16import be.nikiroo.fanfix.library.LocalLibrary;
e023483b 17import be.nikiroo.fanfix.library.RemoteLibrary;
581d42c0 18import be.nikiroo.utils.Cache;
b42117f1 19import be.nikiroo.utils.IOUtils;
12443642 20import be.nikiroo.utils.Image;
dddfac1d 21import be.nikiroo.utils.Proxy;
2aac79c7 22import be.nikiroo.utils.TempFiles;
581d42c0 23import be.nikiroo.utils.TraceHandler;
08fe2e33
NR
24import be.nikiroo.utils.resources.Bundles;
25
26/**
27 * Global state for the program (services and singletons).
28 *
29 * @author niki
30 */
31public class Instance {
d66deb8d
NR
32 static private Instance instance;
33 static private Object instancelock = new Object();
34
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;
ee9b7083
NR
47
48 /**
49 * Initialise the instance -- if already initialised, nothing will happen.
50 * <p>
4ce96da3
NR
51 * Before calling this method, you may call
52 * {@link Bundles#setDirectory(String)} if wanted.
53 * <p>
54 * Note that this method will honour some environment variables, the 3 most
55 * important ones probably being:
56 * <ul>
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>
63 * </ul>
ee9b7083
NR
64 */
65 static public void init() {
6a211e41
NR
66 init(false);
67 }
68
69 /**
d66deb8d
NR
70 * Initialise the instance -- if already initialised, nothing will happen unless
71 * you pass TRUE to <tt>force</tt>.
6a211e41 72 * <p>
d66deb8d
NR
73 * Before calling this method, you may call {@link Bundles#setDirectory(String)}
74 * if wanted.
6a211e41 75 * <p>
d66deb8d
NR
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.
6a211e41 81 *
d66deb8d 82 * @param force force the initialisation even if already initialised
6a211e41
NR
83 */
84 static public void init(boolean force) {
d66deb8d
NR
85 synchronized (instancelock) {
86 if (instance == null || force) {
87 instance = new Instance();
88 }
ee9b7083
NR
89 }
90
d66deb8d
NR
91 }
92
93 /**
94 * Force-initialise the {@link Instance} to a known value.
95 * <p>
96 * Usually for DEBUG/Test purposes.
97 *
98 * @param instance the actual Instance to use
99 */
100 static public void init(Instance instance) {
101 Instance.instance = instance;
102 }
103
104 /**
105 * The (mostly unique) instance of this {@link Instance}.
106 *
107 * @return the (mostly unique) instance
108 */
109 public static Instance getInstance() {
110 return instance;
111 }
ee9b7083 112
d66deb8d
NR
113 /**
114 * Actually initialise the instance.
115 * <p>
116 * Before calling this method, you may call {@link Bundles#setDirectory(String)}
117 * if wanted.
118 */
119 protected Instance() {
62c63b07 120 // Before we can configure it:
a3c35586
NR
121 Boolean debug = checkEnv("DEBUG");
122 boolean trace = debug != null && debug;
123 tracer = new TraceHandler(true, trace, trace);
62c63b07 124
9f705f1a
NR
125 // config dir:
126 configDir = getConfigDir();
b42117f1
NR
127 if (!new File(configDir).exists()) {
128 new File(configDir).mkdirs();
fe999aa4
NR
129 }
130
9f705f1a 131 // Most of the rest is dependent upon this:
38febea9 132 createConfigs(configDir, false);
a3c35586 133
4ff0b1a9 134 // Proxy support
d66deb8d 135 Proxy.use(config.getString(Config.NETWORK_PROXY));
2206ef66 136
9f705f1a 137 // update tracer:
a3c35586 138 if (debug == null) {
d66deb8d
NR
139 debug = config.getBoolean(Config.DEBUG_ERR, false);
140 trace = config.getBoolean(Config.DEBUG_TRACE, false);
d0114000
NR
141 }
142
62c63b07 143 tracer = new TraceHandler(true, debug, trace);
581d42c0 144
9f705f1a
NR
145 // default Library
146 remoteDir = new File(configDir, "remote");
147 lib = createDefaultLibrary(remoteDir);
778d8d85 148
12443642 149 // create cache and TMP
0275353d 150 File tmp = getFile(Config.CACHE_DIR, configDir, "tmp");
7cd006eb
NR
151 Image.setTemporaryFilesRoot(new File(tmp.getParent(), "tmp.images"));
152
13fdb89a 153 String ua = config.getString(Config.NETWORK_USER_AGENT, "");
08fe2e33 154 try {
b7cd9db8
NR
155 int hours = config.getInteger(Config.CACHE_MAX_TIME_CHANGING, 0);
156 int hoursLarge = config.getInteger(Config.CACHE_MAX_TIME_STABLE, 0);
f1fb834c 157 cache = new DataLoader(tmp, ua, hours, hoursLarge);
08fe2e33 158 } catch (IOException e) {
d66deb8d 159 tracer.error(new IOException("Cannot create cache (will continue without cache)", e));
ae78e517 160 cache = new DataLoader(ua);
08fe2e33 161 }
ae78e517
NR
162
163 cache.setTraceHandler(tracer);
9f705f1a
NR
164
165 // readerTmp / coverDir
0275353d
NR
166 readerTmp = getFile(UiConfig.CACHE_DIR_LOCAL_READER, configDir, "tmp-reader");
167 coverDir = getFile(Config.DEFAULT_COVERS_DIR, configDir, "covers");
7cd006eb 168 coverDir.mkdirs();
2aac79c7
NR
169
170 try {
171 tempFiles = new TempFiles("fanfix");
172 } catch (IOException e) {
173 tracer.error(new IOException("Cannot create temporary directory", e));
174 }
08fe2e33
NR
175 }
176
581d42c0
NR
177 /**
178 * The traces handler for this {@link Cache}.
62c63b07
NR
179 * <p>
180 * It is never NULL.
581d42c0 181 *
62c63b07 182 * @return the traces handler (never NULL)
581d42c0 183 */
d66deb8d 184 public TraceHandler getTraceHandler() {
581d42c0
NR
185 return tracer;
186 }
187
188 /**
189 * The traces handler for this {@link Cache}.
190 *
d66deb8d 191 * @param tracer the new traces handler or NULL
581d42c0 192 */
d66deb8d 193 public void setTraceHandler(TraceHandler tracer) {
62c63b07
NR
194 if (tracer == null) {
195 tracer = new TraceHandler(false, false, false);
196 }
197
d66deb8d 198 this.tracer = tracer;
ae78e517 199 cache.setTraceHandler(tracer);
581d42c0
NR
200 }
201
08fe2e33
NR
202 /**
203 * Get the (unique) configuration service for the program.
204 *
205 * @return the configuration service
206 */
d66deb8d 207 public ConfigBundle getConfig() {
08fe2e33
NR
208 return config;
209 }
210
b4dc6ab5
NR
211 /**
212 * Get the (unique) UI configuration service for the program.
213 *
214 * @return the configuration service
215 */
d66deb8d 216 public UiConfigBundle getUiConfig() {
b4dc6ab5
NR
217 return uiconfig;
218 }
219
ae78e517
NR
220 /**
221 * Reset the configuration.
222 *
d66deb8d 223 * @param resetTrans also reset the translation files
ae78e517 224 */
d66deb8d 225 public void resetConfig(boolean resetTrans) {
ae78e517
NR
226 String dir = Bundles.getDirectory();
227 Bundles.setDirectory(null);
228 try {
229 try {
230 ConfigBundle config = new ConfigBundle();
231 config.updateFile(configDir);
232 } catch (IOException e) {
233 tracer.error(e);
234 }
235 try {
236 UiConfigBundle uiconfig = new UiConfigBundle();
237 uiconfig.updateFile(configDir);
238 } catch (IOException e) {
239 tracer.error(e);
240 }
241
242 if (resetTrans) {
243 try {
244 StringIdBundle trans = new StringIdBundle(null);
245 trans.updateFile(configDir);
246 } catch (IOException e) {
247 tracer.error(e);
248 }
249 }
250 } finally {
251 Bundles.setDirectory(dir);
252 }
253 }
254
08fe2e33 255 /**
f1fb834c 256 * Get the (unique) {@link DataLoader} for the program.
08fe2e33 257 *
f1fb834c 258 * @return the {@link DataLoader}
08fe2e33 259 */
d66deb8d 260 public DataLoader getCache() {
08fe2e33
NR
261 return cache;
262 }
263
264 /**
265 * Get the (unique) {link StringIdBundle} for the program.
5bc9573b
NR
266 * <p>
267 * This is used for the translations of the core parts of Fanfix.
39c3c689 268 *
08fe2e33
NR
269 * @return the {link StringIdBundle}
270 */
d66deb8d 271 public StringIdBundle getTrans() {
08fe2e33
NR
272 return trans;
273 }
274
5bc9573b
NR
275 /**
276 * Get the (unique) {link StringIdGuiBundle} for the program.
277 * <p>
278 * This is used for the translations of the GUI parts of Fanfix.
279 *
280 * @return the {link StringIdGuiBundle}
281 */
d66deb8d 282 public StringIdGuiBundle getTransGui() {
5bc9573b
NR
283 return transGui;
284 }
285
08fe2e33 286 /**
d7367179 287 * Get the (unique) {@link BasicLibrary} for the program.
08fe2e33 288 *
d7367179 289 * @return the {@link BasicLibrary}
08fe2e33 290 */
d66deb8d 291 public BasicLibrary getLibrary() {
778d8d85
NR
292 if (lib == null) {
293 throw new NullPointerException("We don't have a library to return");
294 }
295
08fe2e33
NR
296 return lib;
297 }
298
d7367179
NR
299 /**
300 * Change the default {@link BasicLibrary} for this program.
301 * <p>
302 * Be careful.
303 *
304 * @param lib
305 * the new {@link BasicLibrary}
306 */
307 public void setLibrary(BasicLibrary lib) {
308 this.lib = lib;
309 }
310
08fe2e33
NR
311 /**
312 * Return the directory where to look for default cover pages.
313 *
314 * @return the default covers directory
315 */
d66deb8d 316 public File getCoverDir() {
08fe2e33
NR
317 return coverDir;
318 }
319
3727aae2
NR
320 /**
321 * Return the directory where to store temporary files for the local reader.
322 *
323 * @return the directory
324 */
d66deb8d 325 public File getReaderDir() {
3727aae2
NR
326 return readerTmp;
327 }
328
b0e88ebd
NR
329 /**
330 * Return the directory where to store temporary files for the remote
68e2c6d2 331 * {@link LocalLibrary}.
b0e88ebd 332 *
d66deb8d 333 * @param host the remote for this host
b0e88ebd
NR
334 *
335 * @return the directory
336 */
d66deb8d 337 public File getRemoteDir(String host) {
9f705f1a
NR
338 return getRemoteDir(remoteDir, host);
339 }
340
341 /**
342 * Return the directory where to store temporary files for the remote
343 * {@link LocalLibrary}.
344 *
d66deb8d
NR
345 * @param remoteDir the base remote directory
346 * @param host the remote for this host
9f705f1a
NR
347 *
348 * @return the directory
349 */
d66deb8d 350 private File getRemoteDir(File remoteDir, String host) {
b0e88ebd
NR
351 remoteDir.mkdirs();
352
353 if (host != null) {
354 return new File(remoteDir, host);
355 }
356
357 return remoteDir;
358 }
359
b42117f1
NR
360 /**
361 * Check if we need to check that a new version of Fanfix is available.
362 *
363 * @return TRUE if we need to
364 */
d66deb8d 365 public boolean isVersionCheckNeeded() {
b42117f1 366 try {
d66deb8d 367 long wait = config.getInteger(Config.NETWORK_UPDATE_INTERVAL, 0) * 24 * 60 * 60 * 1000;
b42117f1 368 if (wait >= 0) {
d66deb8d
NR
369 String lastUpString = IOUtils.readSmallFile(new File(configDir, "LAST_UPDATE"));
370 long delay = new Date().getTime() - Long.parseLong(lastUpString);
b42117f1
NR
371 if (delay > wait) {
372 return true;
373 }
374 } else {
375 return false;
376 }
377 } catch (Exception e) {
378 // No file or bad file:
379 return true;
380 }
381
382 return false;
383 }
384
385 /**
386 * Notify that we checked for a new version of Fanfix.
387 */
d66deb8d 388 public void setVersionChecked() {
b42117f1 389 try {
d66deb8d 390 IOUtils.writeSmallFile(new File(configDir), "LAST_UPDATE", Long.toString(new Date().getTime()));
b42117f1 391 } catch (IOException e) {
581d42c0 392 tracer.error(e);
08fe2e33
NR
393 }
394 }
395
2aac79c7
NR
396 /**
397 * The facility to use temporary files in this program.
398 * <p>
399 * <b>MUST</b> be closed at end of program.
400 *
401 * @return the facility
402 */
d66deb8d 403 public TempFiles getTempFiles() {
2aac79c7
NR
404 return tempFiles;
405 }
406
9f705f1a 407 /**
d66deb8d
NR
408 * The configuration directory (will check, in order of preference, the system
409 * properties, the environment and then defaults to
f466a217 410 * {@link Instance#getHome()}/.fanfix).
9f705f1a
NR
411 *
412 * @return the config directory
413 */
d66deb8d 414 private String getConfigDir() {
f466a217 415 String configDir = System.getProperty("CONFIG_DIR");
9f705f1a
NR
416
417 if (configDir == null) {
418 configDir = System.getenv("CONFIG_DIR");
419 }
420
421 if (configDir == null) {
422 configDir = new File(getHome(), ".fanfix").getPath();
423 }
424
425 return configDir;
426 }
427
428 /**
429 * Create the config variables ({@link Instance#config},
5bc9573b
NR
430 * {@link Instance#uiconfig}, {@link Instance#trans} and
431 * {@link Instance#transGui}).
9f705f1a 432 *
d66deb8d
NR
433 * @param configDir the directory where to find the configuration files
434 * @param refresh TRUE to reset the configuration files from the default
435 * included ones
9f705f1a 436 */
d66deb8d 437 private void createConfigs(String configDir, boolean refresh) {
9f705f1a
NR
438 if (!refresh) {
439 Bundles.setDirectory(configDir);
440 }
441
442 try {
443 config = new ConfigBundle();
444 config.updateFile(configDir);
445 } catch (IOException e) {
446 tracer.error(e);
447 }
448
449 try {
450 uiconfig = new UiConfigBundle();
451 uiconfig.updateFile(configDir);
452 } catch (IOException e) {
453 tracer.error(e);
454 }
455
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());
5bc9573b 459 transGui = new StringIdGuiBundle(getLang());
9f705f1a
NR
460
461 // Fix an old bug (we used to store custom translation files by
462 // default):
463 if (trans.getString(StringId.INPUT_DESC_CBZ) == null) {
464 trans.deleteFile(configDir);
465 }
466
a3c35586
NR
467 Boolean noutf = checkEnv("NOUTF");
468 if (noutf != null && noutf) {
9f705f1a 469 trans.setUnicode(false);
5bc9573b 470 transGui.setUnicode(false);
9f705f1a
NR
471 }
472
473 Bundles.setDirectory(configDir);
474 }
475
476 /**
477 * Create the default library as specified by the config.
478 *
d66deb8d 479 * @param remoteDir the base remote directory if needed
9f705f1a
NR
480 *
481 * @return the default {@link BasicLibrary}
482 */
d66deb8d 483 private BasicLibrary createDefaultLibrary(File remoteDir) {
9f705f1a
NR
484 BasicLibrary lib = null;
485
d66deb8d 486 boolean useRemote = config.getBoolean(Config.REMOTE_LIBRARY_ENABLED, false);
459b4c28
NR
487 if (useRemote) {
488 String host = null;
489 int port = -1;
490 try {
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);
494
495 tracer.trace("Selecting remote library " + host + ":" + port);
496 lib = new RemoteLibrary(key, host, port);
d66deb8d 497 lib = new CacheLibrary(getRemoteDir(remoteDir, host), lib, uiconfig);
459b4c28 498 } catch (Exception e) {
d66deb8d 499 tracer.error(new IOException("Cannot create remote library for: " + host + ":" + port, e));
459b4c28
NR
500 }
501 } else {
7cd006eb 502 String libDir = System.getenv("BOOKS_DIR");
9f705f1a 503 if (libDir == null || libDir.isEmpty()) {
0275353d 504 libDir = getFile(Config.LIBRARY_DIR, configDir, "$HOME/Books").getPath();
9f705f1a
NR
505 }
506 try {
0275353d 507 lib = new LocalLibrary(new File(libDir), config);
9f705f1a 508 } catch (Exception e) {
0275353d 509 tracer.error(new IOException("Cannot create library for directory: " + libDir, e));
9f705f1a 510 }
9f705f1a
NR
511 }
512
513 return lib;
514 }
515
08fe2e33
NR
516 /**
517 * Return a path, but support the special $HOME variable.
518 *
d66deb8d 519 * @param id the key for the path, which may contain "$HOME"
0275353d
NR
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)
d66deb8d 522 * @return the path, with expanded "$HOME" if needed
08fe2e33 523 */
0275353d
NR
524 protected File getFile(Config id, String configDir, String def) {
525 String path = config.getString(id, def);
526 return getFile(path, configDir);
b4dc6ab5
NR
527 }
528
529 /**
530 * Return a path, but support the special $HOME variable.
531 *
d66deb8d 532 * @param id the key for the path, which may contain "$HOME"
0275353d
NR
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)
d66deb8d 535 * @return the path, with expanded "$HOME" if needed
b4dc6ab5 536 */
0275353d
NR
537 protected File getFile(UiConfig id, String configDir, String def) {
538 String path = uiconfig.getString(id, def);
539 return getFile(path, configDir);
b4dc6ab5
NR
540 }
541
542 /**
543 * Return a path, but support the special $HOME variable.
544 *
d66deb8d 545 * @param path the path, which may contain "$HOME"
0275353d 546 * @param configDir the directory to use as base if not absolute
d66deb8d 547 * @return the path, with expanded "$HOME" if needed
b4dc6ab5 548 */
0275353d 549 protected File getFile(String path, String configDir) {
08fe2e33 550 File file = null;
08fe2e33
NR
551 if (path != null && !path.isEmpty()) {
552 path = path.replace('/', File.separatorChar);
553 if (path.contains("$HOME")) {
ae78e517 554 path = path.replace("$HOME", getHome());
0275353d
NR
555 } else if (!path.startsWith("/")) {
556 path = new File(configDir, path).getPath();
08fe2e33
NR
557 }
558
559 file = new File(path);
560 }
561
562 return file;
563 }
564
ae78e517 565 /**
7cd006eb
NR
566 * Return the home directory from the environment (FANFIX_DIR) or the system
567 * properties.
568 * <p>
569 * The environment variable is tested first. Then, the custom property
d66deb8d
NR
570 * "fanfix.home" is tried, followed by the usual "user.home" then "java.io.tmp"
571 * if nothing else is found.
ae78e517
NR
572 *
573 * @return the home
574 */
d66deb8d 575 protected String getHome() {
7cd006eb 576 String home = System.getenv("FANFIX_DIR");
b4f9071c
NR
577 if (home != null && new File(home).isFile()) {
578 home = null;
579 }
580
7cd006eb
NR
581 if (home == null || home.trim().isEmpty()) {
582 home = System.getProperty("fanfix.home");
583 if (home != null && new File(home).isFile()) {
584 home = null;
585 }
586 }
587
b4f9071c
NR
588 if (home == null || home.trim().isEmpty()) {
589 home = System.getProperty("user.home");
590 if (!new File(home).isDirectory()) {
591 home = null;
592 }
593 }
594
ae78e517
NR
595 if (home == null || home.trim().isEmpty()) {
596 home = System.getProperty("java.io.tmpdir");
b4f9071c
NR
597 if (!new File(home).isDirectory()) {
598 home = null;
599 }
ae78e517
NR
600 }
601
602 if (home == null) {
603 home = "";
604 }
605
606 return home;
607 }
608
08fe2e33
NR
609 /**
610 * The language to use for the application (NULL = default system language).
611 *
612 * @return the language
613 */
d66deb8d 614 protected String getLang() {
08fe2e33
NR
615 String lang = config.getString(Config.LANG);
616
f83510cf 617 if (lang == null || lang.isEmpty()) {
d66deb8d 618 if (System.getenv("LANG") != null && !System.getenv("LANG").isEmpty()) {
fee80815
NR
619 lang = System.getenv("LANG");
620 }
08fe2e33
NR
621 }
622
623 if (lang != null && lang.isEmpty()) {
624 lang = null;
625 }
626
627 return lang;
628 }
d0114000
NR
629
630 /**
631 * Check that the given environment variable is "enabled".
632 *
d66deb8d 633 * @param key the variable to check
d0114000
NR
634 *
635 * @return TRUE if it is
636 */
d66deb8d 637 protected Boolean checkEnv(String key) {
d0114000
NR
638 String value = System.getenv(key);
639 if (value != null) {
640 value = value.trim().toLowerCase();
d66deb8d 641 if ("yes".equals(value) || "true".equals(value) || "on".equals(value) || "1".equals(value)
d0114000
NR
642 || "y".equals(value)) {
643 return true;
644 }
a3c35586
NR
645
646 return false;
d0114000
NR
647 }
648
a3c35586 649 return null;
d0114000 650 }
08fe2e33 651}