jdoc
[nikiroo-utils.git] / library / CacheLibrary.java
CommitLineData
ff05b828
NR
1package be.nikiroo.fanfix.library;
2
ff05b828
NR
3import java.io.File;
4import java.io.IOException;
edf79e5e 5import java.net.URL;
1f2a7d5f 6import java.util.ArrayList;
ff05b828 7import java.util.List;
ef98466f 8import java.util.TreeSet;
ff05b828
NR
9
10import be.nikiroo.fanfix.Instance;
11import be.nikiroo.fanfix.bundles.UiConfig;
d66deb8d 12import be.nikiroo.fanfix.bundles.UiConfigBundle;
ff05b828
NR
13import be.nikiroo.fanfix.data.MetaData;
14import be.nikiroo.fanfix.data.Story;
d66deb8d 15import be.nikiroo.fanfix.output.BasicOutput.OutputType;
16a81ef7 16import be.nikiroo.utils.Image;
ff05b828
NR
17import be.nikiroo.utils.Progress;
18
19/**
20 * This library will cache another pre-existing {@link BasicLibrary}.
21 *
22 * @author niki
23 */
24public class CacheLibrary extends BasicLibrary {
ef98466f
NR
25 private List<MetaData> metasReal;
26 private List<MetaData> metasMixed;
dc919036
NR
27 private Object metasLock = new Object();
28
ff05b828
NR
29 private BasicLibrary lib;
30 private LocalLibrary cacheLib;
31
32 /**
33 * Create a cache library around the given one.
34 * <p>
dc919036
NR
35 * It will return the same result, but those will be saved to disk at the
36 * same time to be fetched quicker the next time.
ff05b828 37 *
dc919036
NR
38 * @param cacheDir
39 * the cache directory where to save the files to disk
40 * @param lib
41 * the original library to wrap
42 * @param config
43 * the configuration used to know which kind of default
44 * {@link OutputType} to use for images and non-images stories
ff05b828 45 */
dc919036
NR
46 public CacheLibrary(File cacheDir, BasicLibrary lib,
47 UiConfigBundle config) {
d66deb8d
NR
48 this.cacheLib = new LocalLibrary(cacheDir, //
49 config.getString(UiConfig.GUI_NON_IMAGES_DOCUMENT_TYPE),
50 config.getString(UiConfig.GUI_IMAGES_DOCUMENT_TYPE), true);
ff05b828
NR
51 this.lib = lib;
52 }
53
54 @Override
55 public String getLibraryName() {
56 return lib.getLibraryName();
57 }
58
e6249b0f
NR
59 @Override
60 public Status getStatus() {
61 return lib.getStatus();
62 }
63
ff05b828 64 @Override
dc919036 65 protected List<MetaData> getMetas(Progress pg) throws IOException {
ff05b828
NR
66 if (pg == null) {
67 pg = new Progress();
68 }
69
2b5784f2 70 List<MetaData> copy;
dc919036
NR
71 synchronized (metasLock) {
72 // We make sure that cached metas have precedence
73 if (metasMixed == null) {
74 if (metasReal == null) {
75 metasReal = lib.getMetas(pg);
76 }
77
78 metasMixed = new ArrayList<MetaData>();
79 TreeSet<String> cachedLuids = new TreeSet<String>();
80 for (MetaData cachedMeta : cacheLib.getMetas(null)) {
81 metasMixed.add(cachedMeta);
82 cachedLuids.add(cachedMeta.getLuid());
83 }
84 for (MetaData realMeta : metasReal) {
85 if (!cachedLuids.contains(realMeta.getLuid())) {
86 metasMixed.add(realMeta);
87 }
ef98466f
NR
88 }
89 }
2b5784f2
NR
90
91 copy = new ArrayList<MetaData>(metasMixed);
ff05b828
NR
92 }
93
94 pg.done();
2b5784f2 95 return copy;
3828c808
NR
96 }
97
60f72311 98 @Override
dc919036
NR
99 public synchronized Story getStory(String luid, MetaData meta, Progress pg)
100 throws IOException {
ff05b828
NR
101 if (pg == null) {
102 pg = new Progress();
103 }
104
105 Progress pgImport = new Progress();
106 Progress pgGet = new Progress();
ff05b828 107
d4449e96 108 pg.setMinMax(0, 4);
ff05b828
NR
109 pg.addProgress(pgImport, 3);
110 pg.addProgress(pgGet, 1);
ff05b828
NR
111
112 if (!isCached(luid)) {
113 try {
114 cacheLib.imprt(lib, luid, pgImport);
ef98466f 115 updateMetaCache(metasMixed, cacheLib.getInfo(luid));
ff05b828 116 pgImport.done();
ff05b828 117 } catch (IOException e) {
d66deb8d 118 Instance.getInstance().getTraceHandler().error(e);
ff05b828
NR
119 }
120
121 pgImport.done();
122 pgGet.done();
123 }
124
d4449e96
NR
125 String type = cacheLib.getOutputType(meta.isImageDocument());
126 MetaData cachedMeta = meta.clone();
127 cachedMeta.setType(type);
128
129 return cacheLib.getStory(luid, cachedMeta, pg);
130 }
131
132 @Override
dc919036
NR
133 public synchronized File getFile(final String luid, Progress pg)
134 throws IOException {
d4449e96
NR
135 if (pg == null) {
136 pg = new Progress();
137 }
138
139 Progress pgGet = new Progress();
140 Progress pgRecall = new Progress();
141
142 pg.setMinMax(0, 5);
143 pg.addProgress(pgGet, 4);
144 pg.addProgress(pgRecall, 1);
145
146 if (!isCached(luid)) {
147 getStory(luid, pgGet);
148 pgGet.done();
149 }
150
ff05b828
NR
151 File file = cacheLib.getFile(luid, pgRecall);
152 pgRecall.done();
153
154 pg.done();
155 return file;
156 }
157
158 @Override
0bb51c9c 159 public Image getCover(final String luid) throws IOException {
ff05b828
NR
160 if (isCached(luid)) {
161 return cacheLib.getCover(luid);
162 }
163
085a2f9a 164 // We could update the cache here, but it's not easy
ff05b828
NR
165 return lib.getCover(luid);
166 }
167
085a2f9a 168 @Override
0bb51c9c 169 public Image getSourceCover(String source) throws IOException {
e1de8087
NR
170 Image custom = getCustomSourceCover(source);
171 if (custom != null) {
172 return custom;
173 }
174
cf45a4c4
NR
175 Image cached = cacheLib.getSourceCover(source);
176 if (cached != null) {
177 return cached;
178 }
179
180 return lib.getSourceCover(source);
e1de8087
NR
181 }
182
3989dfc5 183 @Override
0bb51c9c 184 public Image getAuthorCover(String author) throws IOException {
c956ff52 185 Image custom = getCustomAuthorCover(author);
3989dfc5
NR
186 if (custom != null) {
187 return custom;
188 }
189
c956ff52 190 Image cached = cacheLib.getAuthorCover(author);
3989dfc5
NR
191 if (cached != null) {
192 return cached;
193 }
194
c956ff52 195 return lib.getAuthorCover(author);
3989dfc5
NR
196 }
197
e1de8087 198 @Override
0bb51c9c 199 public Image getCustomSourceCover(String source) throws IOException {
e1de8087
NR
200 Image custom = cacheLib.getCustomSourceCover(source);
201 if (custom == null) {
202 custom = lib.getCustomSourceCover(source);
203 if (custom != null) {
204 cacheLib.setSourceCover(source, custom);
205 }
206 }
207
208 return custom;
085a2f9a 209 }
c956ff52 210
3989dfc5 211 @Override
0bb51c9c 212 public Image getCustomAuthorCover(String author) throws IOException {
3989dfc5
NR
213 Image custom = cacheLib.getCustomAuthorCover(author);
214 if (custom == null) {
215 custom = lib.getCustomAuthorCover(author);
216 if (custom != null) {
217 cacheLib.setAuthorCover(author, custom);
218 }
219 }
220
221 return custom;
222 }
085a2f9a
NR
223
224 @Override
0bb51c9c 225 public void setSourceCover(String source, String luid) throws IOException {
085a2f9a 226 lib.setSourceCover(source, luid);
0dc195de 227 cacheLib.setSourceCover(source, getCover(luid));
085a2f9a
NR
228 }
229
3989dfc5 230 @Override
0bb51c9c 231 public void setAuthorCover(String author, String luid) throws IOException {
3989dfc5
NR
232 lib.setAuthorCover(author, luid);
233 cacheLib.setAuthorCover(author, getCover(luid));
234 }
235
ef98466f
NR
236 /**
237 * Invalidate the {@link Story} cache (when the content has changed, but we
238 * already have it) with the new given meta.
239 * <p>
dc919036
NR
240 * <b>Make sure to always use {@link MetaData} from the cached library in
241 * priority, here.</b>
ef98466f
NR
242 *
243 * @param meta
244 * the {@link Story} to clear from the cache
245 *
246 * @throws IOException
247 * in case of IOException
248 */
ff05b828 249 @Override
ef98466f 250 @Deprecated
0bb51c9c 251 protected void updateInfo(MetaData meta) throws IOException {
ef98466f
NR
252 throw new IOException(
253 "This method is not supported in a CacheLibrary, please use updateMetaCache");
254 }
dc919036 255
ef98466f
NR
256 // relplace the meta in Metas by Meta, add it if needed
257 // return TRUE = added
258 private boolean updateMetaCache(List<MetaData> metas, MetaData meta) {
b56c9d60 259 if (meta != null && metas != null) {
dc919036
NR
260 synchronized (metasLock) {
261 boolean changed = false;
262 for (int i = 0; i < metas.size(); i++) {
263 if (metas.get(i).getLuid().equals(meta.getLuid())) {
264 metas.set(i, meta);
265 changed = true;
266 }
efa3c511 267 }
c747c1f2 268
dc919036
NR
269 if (!changed) {
270 metas.add(meta);
271 return true;
272 }
c747c1f2 273 }
efa3c511 274 }
dc919036 275
ef98466f 276 return false;
efa3c511
NR
277 }
278
279 @Override
c8d48938 280 protected void invalidateInfo(String luid) {
e272f05f 281 if (luid == null) {
dc919036
NR
282 synchronized (metasLock) {
283 metasReal = null;
284 metasMixed = null;
285 }
ef98466f
NR
286 } else {
287 invalidateInfo(metasReal, luid);
288 invalidateInfo(metasMixed, luid);
289 }
290
291 cacheLib.invalidateInfo(luid);
292 lib.invalidateInfo(luid);
293 }
294
295 // luid cannot be null
296 private void invalidateInfo(List<MetaData> metas, String luid) {
297 if (metas != null) {
dc919036
NR
298 synchronized (metasLock) {
299 for (int i = 0; i < metas.size(); i++) {
300 if (metas.get(i).getLuid().equals(luid)) {
301 metas.remove(i--);
302 }
e272f05f
NR
303 }
304 }
e272f05f 305 }
ff05b828
NR
306 }
307
308 @Override
dc919036
NR
309 public synchronized Story save(Story story, String luid, Progress pg)
310 throws IOException {
03c1cede
NR
311 Progress pgLib = new Progress();
312 Progress pgCacheLib = new Progress();
313
314 if (pg == null) {
315 pg = new Progress();
316 }
317
318 pg.setMinMax(0, 2);
319 pg.addProgress(pgLib, 1);
320 pg.addProgress(pgCacheLib, 1);
321
322 story = lib.save(story, luid, pgLib);
ef98466f 323 updateMetaCache(metasReal, story.getMeta());
dc919036 324
0fa0fe95 325 story = cacheLib.save(story, story.getMeta().getLuid(), pgCacheLib);
ef98466f 326 updateMetaCache(metasMixed, story.getMeta());
03c1cede 327
ff05b828
NR
328 return story;
329 }
330
331 @Override
541f433a 332 public synchronized void delete(String luid) throws IOException {
085a2f9a
NR
333 if (isCached(luid)) {
334 cacheLib.delete(luid);
335 }
ff05b828 336 lib.delete(luid);
e272f05f 337
3828c808 338 invalidateInfo(luid);
ff05b828
NR
339 }
340
ff05b828 341 @Override
dc919036
NR
342 protected synchronized void changeSTA(String luid, String newSource,
343 String newTitle, String newAuthor, Progress pg) throws IOException {
ff05b828
NR
344 if (pg == null) {
345 pg = new Progress();
346 }
347
348 Progress pgCache = new Progress();
349 Progress pgOrig = new Progress();
350 pg.setMinMax(0, 2);
351 pg.addProgress(pgCache, 1);
352 pg.addProgress(pgOrig, 1);
353
e272f05f
NR
354 MetaData meta = getInfo(luid);
355 if (meta == null) {
356 throw new IOException("Story not found: " + luid);
357 }
358
e06632ee 359 if (isCached(luid)) {
c8d48938 360 cacheLib.changeSTA(luid, newSource, newTitle, newAuthor, pgCache);
e06632ee 361 }
ff05b828 362 pgCache.done();
e272f05f 363
c8d48938 364 lib.changeSTA(luid, newSource, newTitle, newAuthor, pgOrig);
ff05b828
NR
365 pgOrig.done();
366
e272f05f 367 meta.setSource(newSource);
c8d48938
NR
368 meta.setTitle(newTitle);
369 meta.setAuthor(newAuthor);
ff05b828 370 pg.done();
3828c808 371
ef98466f
NR
372 if (isCached(luid)) {
373 updateMetaCache(metasMixed, meta);
374 updateMetaCache(metasReal, lib.getInfo(luid));
375 } else {
376 updateMetaCache(metasReal, meta);
377 }
ff05b828
NR
378 }
379
4452446c 380 @Override
ff05b828 381 public boolean isCached(String luid) {
0bb51c9c
NR
382 try {
383 return cacheLib.getInfo(luid) != null;
384 } catch (IOException e) {
385 return false;
386 }
ff05b828
NR
387 }
388
4452446c 389 @Override
ff05b828 390 public void clearFromCache(String luid) throws IOException {
e06632ee
NR
391 if (isCached(luid)) {
392 cacheLib.delete(luid);
e06632ee 393 }
ff05b828
NR
394 }
395
edf79e5e 396 @Override
dc919036 397 public MetaData imprt(URL url, Progress pg) throws IOException {
edf79e5e
NR
398 if (pg == null) {
399 pg = new Progress();
400 }
401
402 Progress pgImprt = new Progress();
403 Progress pgCache = new Progress();
404 pg.setMinMax(0, 10);
405 pg.addProgress(pgImprt, 7);
406 pg.addProgress(pgCache, 3);
407
b6b65795 408 MetaData meta = lib.imprt(url, pgImprt);
ef98466f 409 updateMetaCache(metasReal, meta);
2b5784f2 410 metasMixed = null;
dc919036 411
b6b65795 412 clearFromCache(meta.getLuid());
ef98466f 413
edf79e5e 414 pg.done();
b6b65795 415 return meta;
edf79e5e
NR
416 }
417
ff05b828
NR
418 // All the following methods are only used by Save and Delete in
419 // BasicLibrary:
420
421 @Override
422 protected int getNextId() {
423 throw new java.lang.InternalError("Should not have been called");
424 }
425
426 @Override
427 protected void doDelete(String luid) throws IOException {
428 throw new java.lang.InternalError("Should not have been called");
429 }
430
431 @Override
432 protected Story doSave(Story story, Progress pg) throws IOException {
433 throw new java.lang.InternalError("Should not have been called");
434 }
435}