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