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