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