Prepare work on RemoteLibrary
[fanfix.git] / src / be / nikiroo / fanfix / library / BasicLibrary.java
CommitLineData
e42573a0 1package be.nikiroo.fanfix.library;
68e2c6d2
NR
2
3import java.awt.image.BufferedImage;
4import java.io.File;
5import java.io.IOException;
6import java.net.URL;
7import java.util.ArrayList;
8import java.util.Collections;
9import java.util.List;
10
e42573a0 11import be.nikiroo.fanfix.Instance;
68e2c6d2
NR
12import be.nikiroo.fanfix.data.MetaData;
13import be.nikiroo.fanfix.data.Story;
14import be.nikiroo.fanfix.output.BasicOutput;
15import be.nikiroo.fanfix.output.BasicOutput.OutputType;
16import be.nikiroo.fanfix.supported.BasicSupport;
17import be.nikiroo.fanfix.supported.BasicSupport.SupportType;
18import be.nikiroo.utils.Progress;
19
20/**
21 * Manage a library of Stories: import, export, list, modify.
22 * <p>
23 * Each {@link Story} object will be associated with a (local to the library)
24 * unique ID, the LUID, which will be used to identify the {@link Story}.
25 * <p>
26 * Most of the {@link BasicLibrary} functions work on a partial (cover
27 * <b>MAY</b> not be included) {@link MetaData} object.
28 *
29 * @author niki
30 */
31abstract public class BasicLibrary {
32 /**
33 * Retrieve the main {@link File} corresponding to the given {@link Story},
34 * which can be passed to an external reader or instance.
35 * <p>
36 * Do <b>NOT</b> alter this file.
37 *
38 * @param luid
39 * the Library UID of the story
40 *
41 * @return the corresponding {@link Story}
42 */
43 public abstract File getFile(String luid);
44
45 /**
46 * Return the cover image associated to this story.
47 *
48 * @param luid
49 * the Library UID of the story
50 *
51 * @return the cover image
52 */
53 public abstract BufferedImage getCover(String luid);
54
14b57448
NR
55 /**
56 * Return the cover image associated to this source.
57 * <p>
58 * By default, return the cover of the first story with this source.
59 *
60 * @param source
61 * the source
62 *
63 * @return the cover image or NULL
64 */
65 public BufferedImage getSourceCover(String source) {
66 List<MetaData> metas = getListBySource(source);
67 if (metas.size() > 0) {
68 return getCover(metas.get(0).getLuid());
69 }
70
71 return null;
72 }
73
74 /**
75 * Fix the source cover to the given story cover.
76 *
77 * @param source
78 * the source to change
79 * @param luid
80 * the story LUID
81 */
82 public abstract void setSourceCover(String source, String luid);
83
68e2c6d2
NR
84 /**
85 * Return the list of stories (represented by their {@link MetaData}, which
86 * <b>MAY</b> not have the cover included).
87 *
88 * @param pg
89 * the optional {@link Progress}
90 *
91 * @return the list (can be empty but not NULL)
92 */
93 protected abstract List<MetaData> getMetas(Progress pg);
94
95 /**
96 * Invalidate the {@link Story} cache (when the content should be re-read
97 * because it was changed).
98 */
99 protected abstract void clearCache();
100
101 /**
102 * Return the next LUID that can be used.
103 *
104 * @return the next luid
105 */
106 protected abstract int getNextId();
107
108 /**
109 * Delete the target {@link Story}.
110 *
111 * @param luid
112 * the LUID of the {@link Story}
113 *
114 * @throws IOException
115 * in case of I/O error or if the {@link Story} wa not found
116 */
117 protected abstract void doDelete(String luid) throws IOException;
118
119 /**
120 * Actually save the story to the back-end.
121 *
122 * @param story
123 * the {@link Story} to save
124 * @param pg
125 * the optional {@link Progress}
126 *
127 * @return the saved {@link Story} (which may have changed, especially
128 * regarding the {@link MetaData})
129 *
130 * @throws IOException
131 * in case of I/O error
132 */
133 protected abstract Story doSave(Story story, Progress pg)
134 throws IOException;
135
136 /**
137 * Refresh the {@link BasicLibrary}, that is, make sure all stories are
138 * loaded.
139 *
140 * @param full
141 * force the full content of the stories to be loaded, not just
142 * the {@link MetaData}
143 *
144 * @param pg
145 * the optional progress reporter
146 */
147 public void refresh(boolean full, Progress pg) {
148 if (full) {
149 // TODO: progress
150 List<MetaData> metas = getMetas(pg);
151 for (MetaData meta : metas) {
152 getStory(meta.getLuid(), null);
153 }
154 } else {
155 getMetas(pg);
156 }
157 }
158
159 /**
160 * List all the known types (sources) of stories.
161 *
162 * @return the sources
163 */
164 public synchronized List<String> getSources() {
165 List<String> list = new ArrayList<String>();
166 for (MetaData meta : getMetas(null)) {
167 String storySource = meta.getSource();
168 if (!list.contains(storySource)) {
169 list.add(storySource);
170 }
171 }
172
173 Collections.sort(list);
174 return list;
175 }
176
177 /**
178 * List all the known authors of stories.
179 *
180 * @return the authors
181 */
182 public synchronized List<String> getAuthors() {
183 List<String> list = new ArrayList<String>();
184 for (MetaData meta : getMetas(null)) {
185 String storyAuthor = meta.getAuthor();
186 if (!list.contains(storyAuthor)) {
187 list.add(storyAuthor);
188 }
189 }
190
191 Collections.sort(list);
192 return list;
193 }
194
195 /**
196 * List all the stories in the {@link BasicLibrary}.
197 * <p>
198 * Cover images not included.
199 *
200 * @return the stories
201 */
202 public synchronized List<MetaData> getList() {
203 return getMetas(null);
204 }
205
206 /**
207 * List all the stories of the given source type in the {@link BasicLibrary}
208 * , or all the stories if NULL is passed as a type.
209 * <p>
210 * Cover images not included.
211 *
212 * @param type
213 * the type of story to retrieve, or NULL for all
214 *
215 * @return the stories
216 */
217 public synchronized List<MetaData> getListBySource(String type) {
218 List<MetaData> list = new ArrayList<MetaData>();
219 for (MetaData meta : getMetas(null)) {
220 String storyType = meta.getSource();
221 if (type == null || type.equalsIgnoreCase(storyType)) {
222 list.add(meta);
223 }
224 }
225
226 Collections.sort(list);
227 return list;
228 }
229
230 /**
231 * List all the stories of the given author in the {@link BasicLibrary}, or
232 * all the stories if NULL is passed as an author.
233 * <p>
234 * Cover images not included.
235 *
236 * @param author
237 * the author of the stories to retrieve, or NULL for all
238 *
239 * @return the stories
240 */
241 public synchronized List<MetaData> getListByAuthor(String author) {
242 List<MetaData> list = new ArrayList<MetaData>();
243 for (MetaData meta : getMetas(null)) {
244 String storyAuthor = meta.getAuthor();
245 if (author == null || author.equalsIgnoreCase(storyAuthor)) {
246 list.add(meta);
247 }
248 }
249
250 Collections.sort(list);
251 return list;
252 }
253
254 /**
255 * Retrieve a {@link MetaData} corresponding to the given {@link Story},
256 * cover image <b>MAY</b> not be included.
257 *
258 * @param luid
259 * the Library UID of the story
260 *
261 * @return the corresponding {@link Story}
262 */
263 public synchronized MetaData getInfo(String luid) {
264 if (luid != null) {
265 for (MetaData meta : getMetas(null)) {
266 if (luid.equals(meta.getLuid())) {
267 return meta;
268 }
269 }
270 }
271
272 return null;
273 }
274
275 /**
276 * Retrieve a specific {@link Story}.
277 *
278 * @param luid
279 * the Library UID of the story
280 * @param pg
281 * the optional progress reporter
282 *
283 * @return the corresponding {@link Story} or NULL if not found
284 */
285 public synchronized Story getStory(String luid, Progress pg) {
286 // TODO: pg
287 if (pg == null) {
288 pg = new Progress();
289 }
290
291 Story story = null;
292 for (MetaData meta : getMetas(null)) {
293 if (meta.getLuid().equals(luid)) {
294 File file = getFile(luid);
295 try {
296 SupportType type = SupportType.valueOfAllOkUC(meta
297 .getType());
298 URL url = file.toURI().toURL();
299 if (type != null) {
300 story = BasicSupport.getSupport(type).process(url, pg);
301 } else {
302 throw new IOException("Unknown type: " + meta.getType());
303 }
304 } catch (IOException e) {
305 // We should not have not-supported files in the
306 // library
307 Instance.syserr(new IOException(
308 "Cannot load file from library: " + file, e));
309 } finally {
310 pg.done();
311 }
312
313 break;
314 }
315 }
316
317 return story;
318 }
319
320 /**
321 * Import the {@link Story} at the given {@link URL} into the
322 * {@link BasicLibrary}.
323 *
324 * @param url
325 * the {@link URL} to import
326 * @param pg
327 * the optional progress reporter
328 *
329 * @return the imported {@link Story}
330 *
331 * @throws IOException
332 * in case of I/O error
333 */
334 public Story imprt(URL url, Progress pg) throws IOException {
335 BasicSupport support = BasicSupport.getSupport(url);
336 if (support == null) {
337 throw new IOException("URL not supported: " + url.toString());
338 }
339
340 return save(support.process(url, pg), null);
341 }
342
b89dfb6e
NR
343 /**
344 * Import the story from one library to another, and keep the same LUID.
345 *
346 * @param other
347 * the other library to import from
348 * @param luid
349 * the Library UID
350 * @param pg
351 * the optional progress reporter
352 *
353 * @throws IOException
354 * in case of I/O error
355 */
356 public void imprt(BasicLibrary other, String luid, Progress pg)
357 throws IOException {
358 Progress pgGetStory = new Progress();
359 Progress pgSave = new Progress();
360 if (pg == null) {
361 pg = new Progress();
362 }
363
364 pg.setMinMax(0, 2);
365 pg.addProgress(pgGetStory, 1);
366 pg.addProgress(pgSave, 1);
367
368 Story story = other.getStory(luid, pgGetStory);
369 if (story != null) {
370 story = this.save(story, luid, pgSave);
371 pg.done();
372 } else {
373 pg.done();
374 throw new IOException("Cannot find story in Library: " + luid);
375 }
376 }
377
68e2c6d2
NR
378 /**
379 * Export the {@link Story} to the given target in the given format.
380 *
381 * @param luid
382 * the {@link Story} ID
383 * @param type
384 * the {@link OutputType} to transform it to
385 * @param target
386 * the target to save to
387 * @param pg
388 * the optional progress reporter
389 *
390 * @return the saved resource (the main saved {@link File})
391 *
392 * @throws IOException
393 * in case of I/O error
394 */
395 public File export(String luid, OutputType type, String target, Progress pg)
396 throws IOException {
397 Progress pgGetStory = new Progress();
398 Progress pgOut = new Progress();
399 if (pg != null) {
400 pg.setMax(2);
401 pg.addProgress(pgGetStory, 1);
402 pg.addProgress(pgOut, 1);
403 }
404
fa4dcafe 405 BasicOutput out = BasicOutput.getOutput(type, false);
68e2c6d2
NR
406 if (out == null) {
407 throw new IOException("Output type not supported: " + type);
408 }
409
410 Story story = getStory(luid, pgGetStory);
411 if (story == null) {
412 throw new IOException("Cannot find story to export: " + luid);
413 }
414
415 return out.process(story, target, pgOut);
416 }
417
418 /**
419 * Save a {@link Story} to the {@link BasicLibrary}.
420 *
421 * @param story
422 * the {@link Story} to save
423 * @param pg
424 * the optional progress reporter
425 *
426 * @return the same {@link Story}, whose LUID may have changed
427 *
428 * @throws IOException
429 * in case of I/O error
430 */
431 public Story save(Story story, Progress pg) throws IOException {
432 return save(story, null, pg);
433 }
434
435 /**
436 * Save a {@link Story} to the {@link BasicLibrary} -- the LUID <b>must</b>
437 * be correct, or NULL to get the next free one.
438 * <p>
439 * Will override any previous {@link Story} with the same LUID.
440 *
441 * @param story
442 * the {@link Story} to save
443 * @param luid
444 * the <b>correct</b> LUID or NULL to get the next free one
445 * @param pg
446 * the optional progress reporter
447 *
448 * @return the same {@link Story}, whose LUID may have changed
449 *
450 * @throws IOException
451 * in case of I/O error
452 */
453 public synchronized Story save(Story story, String luid, Progress pg)
454 throws IOException {
455 // Do not change the original metadata, but change the original story
456 MetaData meta = story.getMeta().clone();
457 story.setMeta(meta);
458
459 if (luid == null || luid.isEmpty()) {
460 meta.setLuid(String.format("%03d", getNextId()));
461 } else {
462 meta.setLuid(luid);
463 }
464
465 if (getInfo(luid) != null) {
466 delete(luid);
467 }
14b57448 468
68e2c6d2
NR
469 doSave(story, pg);
470
471 clearCache();
472
473 return story;
474 }
475
476 /**
477 * Delete the given {@link Story} from this {@link BasicLibrary}.
478 *
479 * @param luid
480 * the LUID of the target {@link Story}
481 *
482 * @throws IOException
483 * in case of I/O error
484 */
485 public synchronized void delete(String luid) throws IOException {
486 doDelete(luid);
487 clearCache();
488 }
489
490 /**
491 * Change the type (source) of the given {@link Story}.
492 *
493 * @param luid
494 * the {@link Story} LUID
495 * @param newSource
496 * the new source
497 * @param pg
498 * the optional progress reporter
499 *
500 * @throws IOException
501 * in case of I/O error or if the {@link Story} was not found
502 */
503 public synchronized void changeSource(String luid, String newSource,
504 Progress pg) throws IOException {
505 MetaData meta = getInfo(luid);
506 if (meta == null) {
507 throw new IOException("Story not found: " + luid);
508 }
509
510 meta.setSource(newSource);
511 saveMeta(meta, pg);
512 }
513
514 /**
515 * Save back the current state of the {@link MetaData} (LUID <b>MUST NOT</b>
516 * change) for this {@link Story}.
517 * <p>
518 * By default, delete the old {@link Story} then recreate a new
519 * {@link Story}.
520 * <p>
521 * Note that this behaviour can lead to data loss.
522 *
523 * @param meta
524 * the new {@link MetaData} (LUID <b>MUST NOT</b> change)
525 * @param pg
526 * the optional {@link Progress}
527 *
528 * @throws IOException
529 * in case of I/O error or if the {@link Story} was not found
530 */
531 protected synchronized void saveMeta(MetaData meta, Progress pg)
532 throws IOException {
533 if (pg == null) {
534 pg = new Progress();
535 }
536
537 Progress pgGet = new Progress();
538 Progress pgSet = new Progress();
539 pg.addProgress(pgGet, 50);
540 pg.addProgress(pgSet, 50);
541
542 Story story = getStory(meta.getLuid(), pgGet);
543 if (story == null) {
544 throw new IOException("Story not found: " + meta.getLuid());
545 }
546
547 delete(meta.getLuid());
548
549 story.setMeta(meta);
550 save(story, meta.getLuid(), pgSet);
551
552 pg.done();
553 }
554}