Version 1.3.1: UI: authors
[fanfix.git] / src / be / nikiroo / fanfix / Library.java
CommitLineData
08fe2e33
NR
1package be.nikiroo.fanfix;
2
3import java.io.File;
4import java.io.IOException;
5import java.net.URL;
6import java.util.ArrayList;
22848428 7import java.util.Collections;
08fe2e33
NR
8import java.util.HashMap;
9import java.util.List;
10import java.util.Map;
11import java.util.Map.Entry;
12
10d558d2 13import be.nikiroo.fanfix.bundles.Config;
08fe2e33
NR
14import be.nikiroo.fanfix.data.MetaData;
15import be.nikiroo.fanfix.data.Story;
16import be.nikiroo.fanfix.output.BasicOutput;
17import be.nikiroo.fanfix.output.BasicOutput.OutputType;
18import be.nikiroo.fanfix.supported.BasicSupport;
19import be.nikiroo.fanfix.supported.BasicSupport.SupportType;
68686a37 20import be.nikiroo.fanfix.supported.InfoReader;
9843a5e5 21import be.nikiroo.utils.IOUtils;
3b2b638f 22import be.nikiroo.utils.Progress;
08fe2e33
NR
23
24/**
25 * Manage a library of Stories: import, export, list.
26 * <p>
27 * Each {@link Story} object will be associated with a (local to the library)
28 * unique ID, the LUID, which will be used to identify the {@link Story}.
29 *
30 * @author niki
31 */
32public class Library {
33 private File baseDir;
34 private Map<MetaData, File> stories;
08fe2e33 35 private int lastId;
2206ef66
NR
36 private OutputType text;
37 private OutputType image;
08fe2e33
NR
38
39 /**
40 * Create a new {@link Library} with the given backend directory.
41 *
42 * @param dir
2206ef66
NR
43 * the directory where to find the {@link Story} objects
44 * @param text
45 * the {@link OutputType} to save the text-focused stories into
46 * @param image
47 * the {@link OutputType} to save the images-focused stories into
08fe2e33 48 */
2206ef66 49 public Library(File dir, OutputType text, OutputType image) {
08fe2e33
NR
50 this.baseDir = dir;
51 this.stories = new HashMap<MetaData, File>();
52 this.lastId = 0;
2206ef66
NR
53 this.text = text;
54 this.image = image;
08fe2e33
NR
55
56 dir.mkdirs();
57 }
58
333f0e7b
NR
59 /**
60 * List all the known types of stories.
61 *
62 * @return the types
63 */
10d558d2 64 public synchronized List<String> getTypes() {
333f0e7b
NR
65 List<String> list = new ArrayList<String>();
66 for (Entry<MetaData, File> entry : getStories().entrySet()) {
4310bae9 67 String storyType = entry.getKey().getSource();
333f0e7b
NR
68 if (!list.contains(storyType)) {
69 list.add(storyType);
70 }
71 }
72
4310bae9
NR
73 Collections.sort(list);
74 return list;
75 }
76
77 /**
78 * List all the known authors of stories.
79 *
80 * @return the authors
81 */
82 public synchronized List<String> getAuthors() {
83 List<String> list = new ArrayList<String>();
84 for (Entry<MetaData, File> entry : getStories().entrySet()) {
85 String storyAuthor = entry.getKey().getAuthor();
86 if (!list.contains(storyAuthor)) {
87 list.add(storyAuthor);
88 }
89 }
90
91 Collections.sort(list);
92 return list;
93 }
94
95 /**
96 * List all the stories of the given author in the {@link Library}, or all
97 * the stories if NULL is passed as an author.
98 *
99 * @param author
100 * the author of the stories to retrieve, or NULL for all
101 *
102 * @return the stories
103 */
104 public synchronized List<MetaData> getListByAuthor(String author) {
105 List<MetaData> list = new ArrayList<MetaData>();
106 for (Entry<MetaData, File> entry : getStories().entrySet()) {
107 String storyAuthor = entry.getKey().getAuthor();
108 if (author == null || author.equalsIgnoreCase(storyAuthor)) {
109 list.add(entry.getKey());
110 }
111 }
112
113 Collections.sort(list);
333f0e7b
NR
114 return list;
115 }
116
08fe2e33
NR
117 /**
118 * List all the stories of the given source type in the {@link Library}, or
119 * all the stories if NULL is passed as a type.
120 *
121 * @param type
122 * the type of story to retrieve, or NULL for all
123 *
124 * @return the stories
125 */
4310bae9 126 public synchronized List<MetaData> getListByType(String type) {
08fe2e33
NR
127 List<MetaData> list = new ArrayList<MetaData>();
128 for (Entry<MetaData, File> entry : getStories().entrySet()) {
129 String storyType = entry.getValue().getParentFile().getName();
333f0e7b 130 if (type == null || type.equalsIgnoreCase(storyType)) {
08fe2e33
NR
131 list.add(entry.getKey());
132 }
133 }
134
22848428 135 Collections.sort(list);
08fe2e33
NR
136 return list;
137 }
138
301791d3
NR
139 /**
140 * Retrieve a {@link File} corresponding to the given {@link Story}.
141 *
142 * @param luid
143 * the Library UID of the story
144 *
145 * @return the corresponding {@link Story}
146 */
10d558d2 147 public synchronized MetaData getInfo(String luid) {
301791d3
NR
148 if (luid != null) {
149 for (Entry<MetaData, File> entry : getStories().entrySet()) {
150 if (luid.equals(entry.getKey().getLuid())) {
151 return entry.getKey();
152 }
153 }
154 }
155
156 return null;
157 }
158
2206ef66
NR
159 /**
160 * Retrieve a {@link File} corresponding to the given {@link Story}.
161 *
162 * @param luid
163 * the Library UID of the story
164 *
165 * @return the corresponding {@link Story}
166 */
10d558d2 167 public synchronized File getFile(String luid) {
2206ef66
NR
168 if (luid != null) {
169 for (Entry<MetaData, File> entry : getStories().entrySet()) {
170 if (luid.equals(entry.getKey().getLuid())) {
171 return entry.getValue();
172 }
173 }
174 }
175
176 return null;
177 }
178
08fe2e33
NR
179 /**
180 * Retrieve a specific {@link Story}.
181 *
182 * @param luid
183 * the Library UID of the story
92fb0719
NR
184 * @param pg
185 * the optional progress reporter
08fe2e33 186 *
3d247bc3 187 * @return the corresponding {@link Story} or NULL if not found
08fe2e33 188 */
10d558d2 189 public synchronized Story getStory(String luid, Progress pg) {
08fe2e33
NR
190 if (luid != null) {
191 for (Entry<MetaData, File> entry : getStories().entrySet()) {
192 if (luid.equals(entry.getKey().getLuid())) {
193 try {
fe999aa4
NR
194 SupportType type = SupportType.valueOfAllOkUC(entry
195 .getKey().getType());
196 URL url = entry.getValue().toURI().toURL();
197 if (type != null) {
92fb0719
NR
198 return BasicSupport.getSupport(type).process(url,
199 pg);
fe999aa4
NR
200 } else {
201 throw new IOException("Unknown type: "
202 + entry.getKey().getType());
203 }
08fe2e33
NR
204 } catch (IOException e) {
205 // We should not have not-supported files in the
206 // library
207 Instance.syserr(new IOException(
208 "Cannot load file from library: "
209 + entry.getValue().getPath(), e));
210 }
211 }
212 }
213 }
214
92fb0719
NR
215 if (pg != null) {
216 pg.setMinMax(0, 1);
217 pg.setProgress(1);
218 }
219
08fe2e33
NR
220 return null;
221 }
222
223 /**
224 * Import the {@link Story} at the given {@link URL} into the
225 * {@link Library}.
226 *
227 * @param url
228 * the {@link URL} to import
92fb0719
NR
229 * @param pg
230 * the optional progress reporter
08fe2e33
NR
231 *
232 * @return the imported {@link Story}
233 *
234 * @throws IOException
235 * in case of I/O error
236 */
92fb0719 237 public Story imprt(URL url, Progress pg) throws IOException {
08fe2e33
NR
238 BasicSupport support = BasicSupport.getSupport(url);
239 if (support == null) {
240 throw new IOException("URL not supported: " + url.toString());
241 }
242
92fb0719 243 return save(support.process(url, pg), null);
08fe2e33
NR
244 }
245
246 /**
247 * Export the {@link Story} to the given target in the given format.
248 *
249 * @param luid
250 * the {@link Story} ID
251 * @param type
252 * the {@link OutputType} to transform it to
253 * @param target
254 * the target to save to
92fb0719
NR
255 * @param pg
256 * the optional progress reporter
08fe2e33
NR
257 *
258 * @return the saved resource (the main saved {@link File})
259 *
260 * @throws IOException
261 * in case of I/O error
262 */
92fb0719 263 public File export(String luid, OutputType type, String target, Progress pg)
08fe2e33 264 throws IOException {
bee7dffe
NR
265 Progress pgGetStory = new Progress();
266 Progress pgOut = new Progress();
267 if (pg != null) {
268 pg.setMax(2);
269 pg.addProgress(pgGetStory, 1);
270 pg.addProgress(pgOut, 1);
271 }
272
08fe2e33
NR
273 BasicOutput out = BasicOutput.getOutput(type, true);
274 if (out == null) {
275 throw new IOException("Output type not supported: " + type);
276 }
277
bee7dffe 278 Story story = getStory(luid, pgGetStory);
73ce17ef
NR
279 if (story == null) {
280 throw new IOException("Cannot find story to export: " + luid);
281 }
282
bee7dffe 283 return out.process(story, target, pgOut);
08fe2e33
NR
284 }
285
286 /**
2206ef66
NR
287 * Save a {@link Story} to the {@link Library}.
288 *
289 * @param story
290 * the {@link Story} to save
bee7dffe
NR
291 * @param pg
292 * the optional progress reporter
2206ef66
NR
293 *
294 * @return the same {@link Story}, whose LUID may have changed
295 *
296 * @throws IOException
297 * in case of I/O error
298 */
bee7dffe
NR
299 public Story save(Story story, Progress pg) throws IOException {
300 return save(story, null, pg);
2206ef66
NR
301 }
302
303 /**
304 * Save a {@link Story} to the {@link Library} -- the LUID <b>must</b> be
305 * correct, or NULL to get the next free one.
08fe2e33
NR
306 *
307 * @param story
308 * the {@link Story} to save
2206ef66
NR
309 * @param luid
310 * the <b>correct</b> LUID or NULL to get the next free one
bee7dffe
NR
311 * @param pg
312 * the optional progress reporter
2206ef66
NR
313 *
314 * @return the same {@link Story}, whose LUID may have changed
08fe2e33
NR
315 *
316 * @throws IOException
317 * in case of I/O error
318 */
10d558d2
NR
319 public synchronized Story save(Story story, String luid, Progress pg)
320 throws IOException {
a7d266e6 321 // Do not change the original metadata, but change the original story
301791d3 322 MetaData key = story.getMeta().clone();
a7d266e6 323 story.setMeta(key);
08fe2e33 324
2206ef66
NR
325 if (luid == null || luid.isEmpty()) {
326 getStories(); // refresh lastId if needed
327 key.setLuid(String.format("%03d", (++lastId)));
328 } else {
329 key.setLuid(luid);
330 }
331
08fe2e33
NR
332 getDir(key).mkdirs();
333 if (!getDir(key).exists()) {
334 throw new IOException("Cannot create library dir");
335 }
336
337 OutputType out;
08fe2e33 338 if (key != null && key.isImageDocument()) {
2206ef66 339 out = image;
08fe2e33 340 } else {
2206ef66 341 out = text;
08fe2e33 342 }
2206ef66 343
08fe2e33 344 BasicOutput it = BasicOutput.getOutput(out, true);
10d558d2
NR
345 it.process(story, getFile(key).getPath(), pg);
346
347 // empty cache
348 stories.clear();
2206ef66
NR
349
350 return story;
08fe2e33
NR
351 }
352
10d558d2
NR
353 /**
354 * Delete the given {@link Story} from this {@link Library}.
355 *
356 * @param luid
357 * the LUID of the target {@link Story}
358 *
359 * @return TRUE if it was deleted
360 */
361 public synchronized boolean delete(String luid) {
362 boolean ok = false;
363
364 MetaData meta = getInfo(luid);
365 File file = getStories().get(meta);
366
367 if (file != null) {
368 if (file.delete()) {
9843a5e5
NR
369 String readerExt = getOutputType(meta)
370 .getDefaultExtension(true);
371 String fileExt = getOutputType(meta).getDefaultExtension(false);
10d558d2
NR
372
373 String path = file.getAbsolutePath();
9843a5e5
NR
374 if (readerExt != null && !readerExt.equals(fileExt)) {
375 path = path
376 .substring(0, path.length() - readerExt.length())
377 + fileExt;
378 file = new File(path);
379 IOUtils.deltree(file);
380 }
381
10d558d2
NR
382 File infoFile = new File(path + ".info");
383 if (!infoFile.exists()) {
384 infoFile = new File(path.substring(0, path.length()
9843a5e5 385 - fileExt.length())
10d558d2
NR
386 + ".info");
387 }
388 infoFile.delete();
389
390 String coverExt = "."
391 + Instance.getConfig().getString(
392 Config.IMAGE_FORMAT_COVER);
393 File coverFile = new File(path + coverExt);
394 if (!coverFile.exists()) {
395 coverFile = new File(path.substring(0, path.length()
9843a5e5 396 - fileExt.length()));
10d558d2
NR
397 }
398 coverFile.delete();
399
400 ok = true;
401 }
402
403 // clear cache
404 stories.clear();
405 }
406
407 return ok;
408 }
409
08fe2e33
NR
410 /**
411 * The directory (full path) where the {@link Story} related to this
412 * {@link MetaData} should be located on disk.
413 *
414 * @param key
415 * the {@link Story} {@link MetaData}
416 *
417 * @return the target directory
418 */
419 private File getDir(MetaData key) {
420 String source = key.getSource().replaceAll("[^a-zA-Z0-9._+-]", "_");
421 return new File(baseDir, source);
422 }
423
424 /**
425 * The target (full path) where the {@link Story} related to this
426 * {@link MetaData} should be located on disk.
427 *
428 * @param key
429 * the {@link Story} {@link MetaData}
430 *
431 * @return the target
432 */
433 private File getFile(MetaData key) {
a4143cd7
NR
434 String title = key.getTitle();
435 if (title == null) {
436 title = "";
437 }
438 title = title.replaceAll("[^a-zA-Z0-9._+-]", "_");
08fe2e33
NR
439 return new File(getDir(key), key.getLuid() + "_" + title);
440 }
441
442 /**
443 * Return all the known stories in this {@link Library} object.
444 *
445 * @return the stories
446 */
10d558d2 447 private synchronized Map<MetaData, File> getStories() {
08fe2e33
NR
448 if (stories.isEmpty()) {
449 lastId = 0;
68686a37 450
2206ef66 451 String ext = ".info";
08fe2e33
NR
452 for (File dir : baseDir.listFiles()) {
453 if (dir.isDirectory()) {
454 for (File file : dir.listFiles()) {
455 try {
2206ef66 456 if (file.getPath().toLowerCase().endsWith(ext)) {
68686a37
NR
457 MetaData meta = InfoReader.readMeta(file);
458 try {
459 int id = Integer.parseInt(meta.getLuid());
460 if (id > lastId) {
461 lastId = id;
08fe2e33 462 }
68686a37 463
2206ef66
NR
464 // Replace .info with whatever is needed:
465 String path = file.getPath();
466 path = path.substring(0, path.length()
467 - ext.length());
468
469 String newExt = getOutputType(meta)
10d558d2 470 .getDefaultExtension(true);
2206ef66
NR
471
472 file = new File(path + newExt);
473 //
474
68686a37
NR
475 stories.put(meta, file);
476
477 } catch (Exception e) {
08fe2e33
NR
478 // not normal!!
479 Instance.syserr(new IOException(
68686a37
NR
480 "Cannot understand the LUID of "
481 + file.getPath() + ": "
482 + meta.getLuid(), e));
08fe2e33
NR
483 }
484 }
485 } catch (IOException e) {
486 // We should not have not-supported files in the
487 // library
488 Instance.syserr(new IOException(
489 "Cannot load file from library: "
490 + file.getPath(), e));
491 }
492 }
493 }
494 }
495 }
496
497 return stories;
498 }
2206ef66
NR
499
500 /**
501 * Return the {@link OutputType} for this {@link Story}.
502 *
503 * @param meta
504 * the {@link Story} {@link MetaData}
505 *
506 * @return the type
507 */
508 private OutputType getOutputType(MetaData meta) {
509 if (meta != null && meta.isImageDocument()) {
510 return image;
511 } else {
512 return text;
513 }
514 }
08fe2e33 515}