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