use a .summary file
[fanfix.git] / src / be / nikiroo / fanfix / library / LocalLibrary.java
CommitLineData
e42573a0 1package be.nikiroo.fanfix.library;
68e2c6d2 2
68e2c6d2
NR
3import java.io.File;
4import java.io.FileFilter;
14b57448 5import java.io.FileInputStream;
e1de8087 6import java.io.FileNotFoundException;
68e2c6d2 7import java.io.IOException;
14b57448 8import java.io.InputStream;
68e2c6d2
NR
9import java.util.ArrayList;
10import java.util.HashMap;
11import java.util.List;
12import java.util.Map;
13
e42573a0 14import be.nikiroo.fanfix.Instance;
68e2c6d2 15import be.nikiroo.fanfix.bundles.Config;
d66deb8d 16import be.nikiroo.fanfix.bundles.ConfigBundle;
68e2c6d2
NR
17import be.nikiroo.fanfix.data.MetaData;
18import be.nikiroo.fanfix.data.Story;
19import be.nikiroo.fanfix.output.BasicOutput;
20import be.nikiroo.fanfix.output.BasicOutput.OutputType;
21import be.nikiroo.fanfix.output.InfoCover;
22import be.nikiroo.fanfix.supported.InfoReader;
f70bcacf 23import be.nikiroo.utils.HashUtils;
68e2c6d2 24import be.nikiroo.utils.IOUtils;
16a81ef7 25import be.nikiroo.utils.Image;
68e2c6d2
NR
26import be.nikiroo.utils.Progress;
27
28/**
29 * This {@link BasicLibrary} will store the stories locally on disk.
30 *
31 * @author niki
32 */
33public class LocalLibrary extends BasicLibrary {
34 private int lastId;
dc919036 35 private Object lock = new Object();
68e2c6d2 36 private Map<MetaData, File[]> stories; // Files: [ infoFile, TargetFile ]
16a81ef7 37 private Map<String, Image> sourceCovers;
3989dfc5 38 private Map<String, Image> authorCovers;
68e2c6d2
NR
39
40 private File baseDir;
41 private OutputType text;
42 private OutputType image;
43
e604986c
NR
44 /**
45 * Create a new {@link LocalLibrary} with the given back-end directory.
46 *
dc919036
NR
47 * @param baseDir
48 * the directory where to find the {@link Story} objects
49 * @param config
50 * the configuration used to know which kind of default
51 * {@link OutputType} to use for images and non-images stories
e604986c 52 */
d66deb8d
NR
53 public LocalLibrary(File baseDir, ConfigBundle config) {
54 this(baseDir, //
55 config.getString(Config.FILE_FORMAT_NON_IMAGES_DOCUMENT_TYPE),
dc919036
NR
56 config.getString(Config.FILE_FORMAT_IMAGES_DOCUMENT_TYPE),
57 false);
e604986c
NR
58 }
59
60 /**
61 * Create a new {@link LocalLibrary} with the given back-end directory.
62 *
63 * @param baseDir
64 * the directory where to find the {@link Story} objects
ff05b828
NR
65 * @param text
66 * the {@link OutputType} to use for non-image documents
67 * @param image
68 * the {@link OutputType} to use for image documents
69 * @param defaultIsHtml
70 * if the given text or image is invalid, use HTML by default (if
71 * not, it will be INFO_TEXT/CBZ by default)
e604986c
NR
72 */
73 public LocalLibrary(File baseDir, String text, String image,
74 boolean defaultIsHtml) {
dc919036
NR
75 this(baseDir,
76 OutputType.valueOfAllOkUC(text,
77 defaultIsHtml ? OutputType.HTML : OutputType.INFO_TEXT),
ff05b828 78 OutputType.valueOfAllOkUC(image,
e604986c
NR
79 defaultIsHtml ? OutputType.HTML : OutputType.CBZ));
80 }
81
68e2c6d2
NR
82 /**
83 * Create a new {@link LocalLibrary} with the given back-end directory.
84 *
85 * @param baseDir
86 * the directory where to find the {@link Story} objects
87 * @param text
e604986c 88 * the {@link OutputType} to use for non-image documents
68e2c6d2 89 * @param image
e604986c 90 * the {@link OutputType} to use for image documents
68e2c6d2
NR
91 */
92 public LocalLibrary(File baseDir, OutputType text, OutputType image) {
93 this.baseDir = baseDir;
94 this.text = text;
95 this.image = image;
96
97 this.lastId = 0;
98 this.stories = null;
b56c9d60 99 this.sourceCovers = null;
68e2c6d2
NR
100
101 baseDir.mkdirs();
102 }
103
104 @Override
dc919036 105 protected List<MetaData> getMetas(Progress pg) {
68e2c6d2
NR
106 return new ArrayList<MetaData>(getStories(pg).keySet());
107 }
108
109 @Override
0bb51c9c 110 public File getFile(String luid, Progress pg) throws IOException {
dc919036
NR
111 Instance.getInstance().getTraceHandler().trace(
112 this.getClass().getSimpleName() + ": get file for " + luid);
9e2fad36
NR
113
114 File file = null;
115 String mess = "no file found for ";
116
117 MetaData meta = getInfo(luid);
dc919036
NR
118 if (meta != null) {
119 File[] files = getStories(pg).get(meta);
120 if (files != null) {
121 mess = "file retrieved for ";
122 file = files[1];
123 }
68e2c6d2
NR
124 }
125
d66deb8d 126 Instance.getInstance().getTraceHandler()
dc919036
NR
127 .trace(this.getClass().getSimpleName() + ": " + mess + luid
128 + " (" + meta.getTitle() + ")");
9e2fad36
NR
129
130 return file;
68e2c6d2
NR
131 }
132
133 @Override
0bb51c9c 134 public Image getCover(String luid) throws IOException {
68e2c6d2
NR
135 MetaData meta = getInfo(luid);
136 if (meta != null) {
3ab45a6e
NR
137 if (meta.getCover() != null) {
138 return meta.getCover();
139 }
140
68e2c6d2
NR
141 File[] files = getStories(null).get(meta);
142 if (files != null) {
143 File infoFile = files[0];
144
145 try {
146 meta = InfoReader.readMeta(infoFile, true);
147 return meta.getCover();
148 } catch (IOException e) {
d66deb8d 149 Instance.getInstance().getTraceHandler().error(e);
68e2c6d2
NR
150 }
151 }
152 }
153
154 return null;
155 }
156
157 @Override
dc919036 158 protected void updateInfo(MetaData meta) {
c8d48938 159 invalidateInfo();
efa3c511
NR
160 }
161
162 @Override
c8d48938 163 protected void invalidateInfo(String luid) {
dc919036
NR
164 synchronized (lock) {
165 stories = null;
166 sourceCovers = null;
167 }
68e2c6d2
NR
168 }
169
170 @Override
f70bcacf 171 protected String getNextId() {
14b57448 172 getStories(null); // make sure lastId is set
dc919036
NR
173
174 synchronized (lock) {
f70bcacf 175 return String.format("%03d", ++lastId);
dc919036 176 }
68e2c6d2
NR
177 }
178
179 @Override
180 protected void doDelete(String luid) throws IOException {
181 for (File file : getRelatedFiles(luid)) {
182 // TODO: throw an IOException if we cannot delete the files?
183 IOUtils.deltree(file);
e272f05f 184 file.getParentFile().delete();
68e2c6d2
NR
185 }
186 }
187
188 @Override
189 protected Story doSave(Story story, Progress pg) throws IOException {
190 MetaData meta = story.getMeta();
191
192 File expectedTarget = getExpectedFile(meta);
193 expectedTarget.getParentFile().mkdirs();
194
925298fd 195 BasicOutput it = BasicOutput.getOutput(getOutputType(meta), true, true);
68e2c6d2
NR
196 it.process(story, expectedTarget.getPath(), pg);
197
198 return story;
199 }
200
201 @Override
202 protected synchronized void saveMeta(MetaData meta, Progress pg)
203 throws IOException {
204 File newDir = getExpectedDir(meta.getSource());
205 if (!newDir.exists()) {
3a0605e6 206 newDir.mkdirs();
68e2c6d2
NR
207 }
208
209 List<File> relatedFiles = getRelatedFiles(meta.getLuid());
210 for (File relatedFile : relatedFiles) {
211 // TODO: this is not safe at all.
212 // We should copy all the files THEN delete them
213 // Maybe also adding some rollback cleanup if possible
214 if (relatedFile.getName().endsWith(".info")) {
215 try {
dc919036
NR
216 String name = relatedFile.getName().replaceFirst("\\.info$",
217 "");
68e2c6d2 218 relatedFile.delete();
c8d48938 219 InfoCover.writeInfo(newDir, name, meta);
e272f05f 220 relatedFile.getParentFile().delete();
68e2c6d2 221 } catch (IOException e) {
d66deb8d 222 Instance.getInstance().getTraceHandler().error(e);
68e2c6d2
NR
223 }
224 } else {
225 relatedFile.renameTo(new File(newDir, relatedFile.getName()));
e272f05f 226 relatedFile.getParentFile().delete();
68e2c6d2
NR
227 }
228 }
229
ef98466f 230 updateInfo(meta);
68e2c6d2
NR
231 }
232
14b57448 233 @Override
dc919036
NR
234 public Image getCustomSourceCover(String source) {
235 synchronized (lock) {
236 if (sourceCovers == null) {
237 sourceCovers = new HashMap<String, Image>();
238 }
e1de8087
NR
239 }
240
dc919036
NR
241 synchronized (lock) {
242 Image img = sourceCovers.get(source);
243 if (img != null) {
244 return img;
245 }
b56c9d60
NR
246 }
247
3a0605e6 248 File coverDir = getExpectedDir(source);
e1de8087
NR
249 if (coverDir.isDirectory()) {
250 File cover = new File(coverDir, ".cover.png");
a09ef2bb
NR
251 if (cover.exists()) {
252 InputStream in;
e1de8087 253 try {
a09ef2bb
NR
254 in = new FileInputStream(cover);
255 try {
dc919036 256 synchronized (lock) {
0a264fbe
NR
257 Image img = new Image(in);
258 if (img.getSize() == 0) {
259 img.close();
260 throw new IOException(
261 "Empty image not accepted");
262 }
263 sourceCovers.put(source, img);
dc919036 264 }
a09ef2bb
NR
265 } finally {
266 in.close();
267 }
268 } catch (FileNotFoundException e) {
269 e.printStackTrace();
270 } catch (IOException e) {
d66deb8d 271 Instance.getInstance().getTraceHandler()
dc919036
NR
272 .error(new IOException(
273 "Cannot load the existing custom source cover: "
274 + cover,
275 e));
e1de8087 276 }
e1de8087 277 }
14b57448
NR
278 }
279
dc919036
NR
280 synchronized (lock) {
281 return sourceCovers.get(source);
282 }
14b57448
NR
283 }
284
3989dfc5 285 @Override
dc919036
NR
286 public Image getCustomAuthorCover(String author) {
287 synchronized (lock) {
288 if (authorCovers == null) {
289 authorCovers = new HashMap<String, Image>();
290 }
c956ff52
NR
291 }
292
dc919036
NR
293 synchronized (lock) {
294 Image img = authorCovers.get(author);
295 if (img != null) {
296 return img;
297 }
c956ff52
NR
298 }
299
3989dfc5
NR
300 File cover = getAuthorCoverFile(author);
301 if (cover.exists()) {
302 InputStream in;
303 try {
304 in = new FileInputStream(cover);
305 try {
dc919036 306 synchronized (lock) {
0a264fbe
NR
307 Image img = new Image(in);
308 if (img.getSize() == 0) {
309 img.close();
310 throw new IOException(
311 "Empty image not accepted");
312 }
313 authorCovers.put(author, img);
dc919036 314 }
3989dfc5
NR
315 } finally {
316 in.close();
317 }
318 } catch (FileNotFoundException e) {
319 e.printStackTrace();
320 } catch (IOException e) {
d66deb8d 321 Instance.getInstance().getTraceHandler()
dc919036
NR
322 .error(new IOException(
323 "Cannot load the existing custom author cover: "
324 + cover,
325 e));
3989dfc5
NR
326 }
327 }
328
dc919036
NR
329 synchronized (lock) {
330 return authorCovers.get(author);
331 }
3989dfc5
NR
332 }
333
14b57448 334 @Override
0bb51c9c 335 public void setSourceCover(String source, String luid) throws IOException {
e1de8087
NR
336 setSourceCover(source, getCover(luid));
337 }
b56c9d60 338
3989dfc5 339 @Override
0bb51c9c 340 public void setAuthorCover(String author, String luid) throws IOException {
3989dfc5
NR
341 setAuthorCover(author, getCover(luid));
342 }
343
e1de8087 344 /**
3989dfc5 345 * Set the source cover to the given story cover.
e1de8087
NR
346 *
347 * @param source
348 * the source to change
349 * @param coverImage
350 * the cover image
351 */
dc919036 352 void setSourceCover(String source, Image coverImage) {
cf45a4c4
NR
353 File dir = getExpectedDir(source);
354 dir.mkdirs();
355 File cover = new File(dir, ".cover");
14b57448 356 try {
dc919036
NR
357 Instance.getInstance().getCache().saveAsImage(coverImage, cover,
358 true);
359 synchronized (lock) {
360 if (sourceCovers != null) {
361 sourceCovers.put(source, coverImage);
362 }
e1de8087 363 }
14b57448 364 } catch (IOException e) {
d66deb8d 365 Instance.getInstance().getTraceHandler().error(e);
14b57448
NR
366 }
367 }
368
3989dfc5
NR
369 /**
370 * Set the author cover to the given story cover.
371 *
372 * @param author
373 * the author to change
374 * @param coverImage
375 * the cover image
376 */
dc919036 377 void setAuthorCover(String author, Image coverImage) {
3989dfc5
NR
378 File cover = getAuthorCoverFile(author);
379 cover.getParentFile().mkdirs();
380 try {
dc919036
NR
381 Instance.getInstance().getCache().saveAsImage(coverImage, cover,
382 true);
383 synchronized (lock) {
384 if (authorCovers != null) {
385 authorCovers.put(author, coverImage);
386 }
3989dfc5
NR
387 }
388 } catch (IOException e) {
d66deb8d 389 Instance.getInstance().getTraceHandler().error(e);
3989dfc5
NR
390 }
391 }
392
b89dfb6e
NR
393 @Override
394 public void imprt(BasicLibrary other, String luid, Progress pg)
395 throws IOException {
396 if (pg == null) {
397 pg = new Progress();
398 }
399
ff05b828 400 // Check if we can simply copy the files instead of the whole process
b89dfb6e 401 if (other instanceof LocalLibrary) {
ff05b828 402 LocalLibrary otherLocalLibrary = (LocalLibrary) other;
e604986c 403
e604986c
NR
404 MetaData meta = otherLocalLibrary.getInfo(luid);
405 String expectedType = ""
406 + (meta != null && meta.isImageDocument() ? image : text);
407 if (meta != null && meta.getType().equals(expectedType)) {
408 File from = otherLocalLibrary.getExpectedDir(meta.getSource());
b89dfb6e 409 File to = this.getExpectedDir(meta.getSource());
3a0605e6
NR
410 List<File> relatedFiles = otherLocalLibrary
411 .getRelatedFiles(luid);
412 if (!relatedFiles.isEmpty()) {
413 pg.setMinMax(0, relatedFiles.size());
b89dfb6e
NR
414 }
415
3a0605e6
NR
416 for (File relatedFile : relatedFiles) {
417 File target = new File(relatedFile.getAbsolutePath()
418 .replace(from.getAbsolutePath(),
419 to.getAbsolutePath()));
420 if (!relatedFile.equals(target)) {
e604986c 421 target.getParentFile().mkdirs();
b89dfb6e
NR
422 InputStream in = null;
423 try {
3a0605e6 424 in = new FileInputStream(relatedFile);
b89dfb6e
NR
425 IOUtils.write(in, target);
426 } catch (IOException e) {
427 if (in != null) {
428 try {
429 in.close();
430 } catch (Exception ee) {
431 }
432 }
433
434 pg.done();
435 throw e;
436 }
437 }
438
439 pg.add(1);
440 }
441
c8d48938 442 invalidateInfo();
b89dfb6e
NR
443 pg.done();
444 return;
445 }
446 }
447
448 super.imprt(other, luid, pg);
449 }
450
68e2c6d2
NR
451 /**
452 * Return the {@link OutputType} for this {@link Story}.
453 *
454 * @param meta
455 * the {@link Story} {@link MetaData}
456 *
457 * @return the type
458 */
459 private OutputType getOutputType(MetaData meta) {
460 if (meta != null && meta.isImageDocument()) {
461 return image;
68e2c6d2 462 }
d4449e96 463
211f7ddb 464 return text;
68e2c6d2 465 }
d4449e96 466
585ae2b8
N
467 /**
468 * Return the default {@link OutputType} for this kind of {@link Story}.
469 *
470 * @param imageDocument
d4449e96 471 * TRUE for images document, FALSE for text documents
585ae2b8
N
472 *
473 * @return the type
474 */
475 public String getOutputType(boolean imageDocument) {
476 if (imageDocument) {
477 return image.toString();
478 }
479
480 return text.toString();
481 }
68e2c6d2
NR
482
483 /**
484 * Get the target {@link File} related to the given <tt>.info</tt>
485 * {@link File} and {@link MetaData}.
486 *
487 * @param meta
488 * the meta
489 * @param infoFile
490 * the <tt>.info</tt> {@link File}
491 *
492 * @return the target {@link File}
493 */
494 private File getTargetFile(MetaData meta, File infoFile) {
495 // Replace .info with whatever is needed:
496 String path = infoFile.getPath();
497 path = path.substring(0, path.length() - ".info".length());
498 String newExt = getOutputType(meta).getDefaultExtension(true);
499
500 return new File(path + newExt);
501 }
502
503 /**
504 * The target (full path) where the {@link Story} related to this
505 * {@link MetaData} should be located on disk for a new {@link Story}.
506 *
507 * @param key
508 * the {@link Story} {@link MetaData}
509 *
510 * @return the target
511 */
512 private File getExpectedFile(MetaData key) {
513 String title = key.getTitle();
514 if (title == null) {
515 title = "";
516 }
517 title = title.replaceAll("[^a-zA-Z0-9._+-]", "_");
3989dfc5
NR
518 if (title.length() > 40) {
519 title = title.substring(0, 40);
520 }
dc919036
NR
521 return new File(getExpectedDir(key.getSource()),
522 key.getLuid() + "_" + title);
68e2c6d2
NR
523 }
524
525 /**
526 * The directory (full path) where the new {@link Story} related to this
527 * {@link MetaData} should be located on disk.
528 *
085a2f9a 529 * @param source
68e2c6d2
NR
530 * the type (source)
531 *
532 * @return the target directory
533 */
085a2f9a 534 private File getExpectedDir(String source) {
3a0605e6
NR
535 String sanitizedSource = source.replaceAll("[^a-zA-Z0-9._+/-]", "_");
536
3989dfc5
NR
537 while (sanitizedSource.startsWith("/")
538 || sanitizedSource.startsWith("_")) {
3a0605e6
NR
539 if (sanitizedSource.length() > 1) {
540 sanitizedSource = sanitizedSource.substring(1);
541 } else {
542 sanitizedSource = "";
543 }
544 }
545
546 sanitizedSource = sanitizedSource.replace("/", File.separator);
547
548 if (sanitizedSource.isEmpty()) {
3989dfc5 549 sanitizedSource = "_EMPTY";
3a0605e6
NR
550 }
551
085a2f9a 552 return new File(baseDir, sanitizedSource);
68e2c6d2
NR
553 }
554
3989dfc5
NR
555 /**
556 * Return the full path to the file to use for the custom cover of this
557 * author.
558 * <p>
559 * One or more of the parent directories <b>MAY</b> not exist.
560 *
561 * @param author
562 * the author
563 *
564 * @return the custom cover file
565 */
566 private File getAuthorCoverFile(String author) {
567 File aDir = new File(baseDir, "_AUTHORS");
f70bcacf 568 String hash = HashUtils.md5(author);
dc919036
NR
569 String ext = Instance.getInstance().getConfig()
570 .getString(Config.FILE_FORMAT_IMAGE_FORMAT_COVER);
3989dfc5
NR
571 return new File(aDir, hash + "." + ext.toLowerCase());
572 }
573
68e2c6d2
NR
574 /**
575 * Return the list of files/directories on disk for this {@link Story}.
576 * <p>
577 * If the {@link Story} is not found, and empty list is returned.
578 *
579 * @param luid
580 * the {@link Story} LUID
581 *
582 * @return the list of {@link File}s
583 *
584 * @throws IOException
585 * if the {@link Story} was not found
586 */
587 private List<File> getRelatedFiles(String luid) throws IOException {
588 List<File> files = new ArrayList<File>();
589
590 MetaData meta = getInfo(luid);
591 if (meta == null) {
592 throw new IOException("Story not found: " + luid);
211f7ddb 593 }
68e2c6d2 594
211f7ddb
NR
595 File infoFile = getStories(null).get(meta)[0];
596 File targetFile = getStories(null).get(meta)[1];
68e2c6d2 597
211f7ddb
NR
598 files.add(infoFile);
599 files.add(targetFile);
68e2c6d2 600
211f7ddb
NR
601 String readerExt = getOutputType(meta).getDefaultExtension(true);
602 String fileExt = getOutputType(meta).getDefaultExtension(false);
68e2c6d2 603
211f7ddb
NR
604 String path = targetFile.getAbsolutePath();
605 if (readerExt != null && !readerExt.equals(fileExt)) {
606 path = path.substring(0, path.length() - readerExt.length())
607 + fileExt;
608 File relatedFile = new File(path);
68e2c6d2 609
211f7ddb
NR
610 if (relatedFile.exists()) {
611 files.add(relatedFile);
68e2c6d2 612 }
211f7ddb 613 }
68e2c6d2 614
dc919036
NR
615 String coverExt = "." + Instance.getInstance().getConfig()
616 .getString(Config.FILE_FORMAT_IMAGE_FORMAT_COVER).toLowerCase();
211f7ddb
NR
617 File coverFile = new File(path + coverExt);
618 if (!coverFile.exists()) {
dc919036
NR
619 coverFile = new File(
620 path.substring(0, path.length() - fileExt.length())
621 + coverExt);
211f7ddb
NR
622 }
623
624 if (coverFile.exists()) {
625 files.add(coverFile);
68e2c6d2
NR
626 }
627
34f8780d
NR
628 String summaryExt = ".summary";
629 File summaryFile = new File(path + summaryExt);
630 if (!summaryFile.exists()) {
631 summaryFile = new File(
632 path.substring(0, path.length() - fileExt.length())
633 + summaryExt);
634 }
635
636 if (summaryFile.exists()) {
637 files.add(summaryFile);
638 }
639
68e2c6d2
NR
640 return files;
641 }
642
643 /**
644 * Fill the list of stories by reading the content of the local directory
645 * {@link LocalLibrary#baseDir}.
646 * <p>
647 * Will use a cached list when possible (see
c8d48938 648 * {@link BasicLibrary#invalidateInfo()}).
68e2c6d2
NR
649 *
650 * @param pg
651 * the optional {@link Progress}
652 *
9b863b20
NR
653 * @return the list of stories (for each item, the first {@link File} is the
654 * info file, the second file is the target {@link File})
68e2c6d2 655 */
dc919036 656 private Map<MetaData, File[]> getStories(Progress pg) {
68e2c6d2
NR
657 if (pg == null) {
658 pg = new Progress();
659 } else {
660 pg.setMinMax(0, 100);
661 }
662
dc919036 663 Map<MetaData, File[]> stories = this.stories;
8e7a5d0e
NR
664 if (stories == null) {
665 stories = getStoriesDo(pg);
666 synchronized (lock) {
667 if (this.stories == null)
668 this.stories = stories;
669 else
670 stories = this.stories;
dc919036
NR
671 }
672 }
68e2c6d2 673
dc919036
NR
674 pg.done();
675 return stories;
68e2c6d2 676
dc919036 677 }
68e2c6d2 678
dc919036
NR
679 /**
680 * Actually do the work of {@link LocalLibrary#getStories(Progress)} (i.e.,
681 * do not retrieve the cache).
682 *
683 * @param pg
684 * the optional {@link Progress}
685 *
686 * @return the list of stories (for each item, the first {@link File} is the
687 * info file, the second file is the target {@link File})
688 */
689 private synchronized Map<MetaData, File[]> getStoriesDo(Progress pg) {
690 if (pg == null) {
691 pg = new Progress();
692 } else {
693 pg.setMinMax(0, 100);
694 }
b4f9071c 695
dc919036 696 Map<MetaData, File[]> stories = new HashMap<MetaData, File[]>();
b4f9071c 697
dc919036
NR
698 File[] dirs = baseDir.listFiles(new FileFilter() {
699 @Override
700 public boolean accept(File file) {
701 return file != null && file.isDirectory();
702 }
703 });
68e2c6d2 704
dc919036
NR
705 if (dirs != null) {
706 Progress pgDirs = new Progress(0, 100 * dirs.length);
707 pg.addProgress(pgDirs, 100);
14b57448 708
dc919036
NR
709 for (File dir : dirs) {
710 Progress pgFiles = new Progress();
711 pgDirs.addProgress(pgFiles, 100);
712 pgDirs.setName("Loading from: " + dir.getName());
713
714 addToStories(stories, pgFiles, dir);
715
716 pgFiles.setName(null);
68e2c6d2 717 }
dc919036
NR
718
719 pgDirs.setName("Loading directories");
68e2c6d2
NR
720 }
721
b4f9071c 722 pg.done();
dc919036 723
68e2c6d2
NR
724 return stories;
725 }
3a0605e6 726
dc919036
NR
727 private void addToStories(Map<MetaData, File[]> stories, Progress pgFiles,
728 File dir) {
3a0605e6
NR
729 File[] infoFilesAndSubdirs = dir.listFiles(new FileFilter() {
730 @Override
731 public boolean accept(File file) {
732 boolean info = file != null && file.isFile()
733 && file.getPath().toLowerCase().endsWith(".info");
734 boolean dir = file != null && file.isDirectory();
efdbabcd
NR
735 boolean isExpandedHtml = new File(file, "index.html").isFile();
736 return info || (dir && !isExpandedHtml);
3a0605e6
NR
737 }
738 });
739
740 if (pgFiles != null) {
741 pgFiles.setMinMax(0, infoFilesAndSubdirs.length);
742 }
743
744 for (File infoFileOrSubdir : infoFilesAndSubdirs) {
3a0605e6 745 if (infoFileOrSubdir.isDirectory()) {
dc919036 746 addToStories(stories, null, infoFileOrSubdir);
3a0605e6
NR
747 } else {
748 try {
dc919036
NR
749 MetaData meta = InfoReader.readMeta(infoFileOrSubdir,
750 false);
3a0605e6
NR
751 try {
752 int id = Integer.parseInt(meta.getLuid());
753 if (id > lastId) {
754 lastId = id;
755 }
756
757 stories.put(meta, new File[] { infoFileOrSubdir,
758 getTargetFile(meta, infoFileOrSubdir) });
759 } catch (Exception e) {
760 // not normal!!
761 throw new IOException("Cannot understand the LUID of "
762 + infoFileOrSubdir + ": " + meta.getLuid(), e);
763 }
764 } catch (IOException e) {
765 // We should not have not-supported files in the
766 // library
dc919036
NR
767 Instance.getInstance().getTraceHandler().error(
768 new IOException("Cannot load file from library: "
769 + infoFileOrSubdir, e));
3a0605e6
NR
770 }
771 }
772
773 if (pgFiles != null) {
774 pgFiles.add(1);
775 }
776 }
777 }
68e2c6d2 778}