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