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