1 package be
.nikiroo
.fanfix
;
4 import java
.io
.FileFilter
;
5 import java
.io
.IOException
;
7 import java
.util
.ArrayList
;
8 import java
.util
.Collections
;
9 import java
.util
.HashMap
;
10 import java
.util
.List
;
12 import java
.util
.Map
.Entry
;
14 import be
.nikiroo
.fanfix
.bundles
.Config
;
15 import be
.nikiroo
.fanfix
.data
.MetaData
;
16 import be
.nikiroo
.fanfix
.data
.Story
;
17 import be
.nikiroo
.fanfix
.output
.BasicOutput
;
18 import be
.nikiroo
.fanfix
.output
.BasicOutput
.OutputType
;
19 import be
.nikiroo
.fanfix
.supported
.BasicSupport
;
20 import be
.nikiroo
.fanfix
.supported
.BasicSupport
.SupportType
;
21 import be
.nikiroo
.fanfix
.supported
.InfoReader
;
22 import be
.nikiroo
.utils
.IOUtils
;
23 import be
.nikiroo
.utils
.Progress
;
26 * Manage a library of Stories: import, export, list.
28 * Each {@link Story} object will be associated with a (local to the library)
29 * unique ID, the LUID, which will be used to identify the {@link Story}.
33 public class Library
{
35 private Map
<MetaData
, File
> stories
;
37 private OutputType text
;
38 private OutputType image
;
41 * Create a new {@link Library} with the given backend directory.
44 * the directory where to find the {@link Story} objects
46 * the {@link OutputType} to save the text-focused stories into
48 * the {@link OutputType} to save the images-focused stories into
50 public Library(File dir
, OutputType text
, OutputType image
) {
52 this.stories
= new HashMap
<MetaData
, File
>();
61 * Refresh the {@link Library}, that is, make sure all stories are loaded.
64 * the optional progress reporter
66 public void refresh(Progress pg
) {
71 * List all the known types of stories.
75 public synchronized List
<String
> getTypes() {
76 List
<String
> list
= new ArrayList
<String
>();
77 for (Entry
<MetaData
, File
> entry
: getStories(null).entrySet()) {
78 String storyType
= entry
.getKey().getSource();
79 if (!list
.contains(storyType
)) {
84 Collections
.sort(list
);
89 * List all the known authors of stories.
93 public synchronized List
<String
> getAuthors() {
94 List
<String
> list
= new ArrayList
<String
>();
95 for (Entry
<MetaData
, File
> entry
: getStories(null).entrySet()) {
96 String storyAuthor
= entry
.getKey().getAuthor();
97 if (!list
.contains(storyAuthor
)) {
98 list
.add(storyAuthor
);
102 Collections
.sort(list
);
107 * List all the stories of the given author in the {@link Library}, or all
108 * the stories if NULL is passed as an author.
111 * the author of the stories to retrieve, or NULL for all
113 * @return the stories
115 public synchronized List
<MetaData
> getListByAuthor(String author
) {
116 List
<MetaData
> list
= new ArrayList
<MetaData
>();
117 for (Entry
<MetaData
, File
> entry
: getStories(null).entrySet()) {
118 String storyAuthor
= entry
.getKey().getAuthor();
119 if (author
== null || author
.equalsIgnoreCase(storyAuthor
)) {
120 list
.add(entry
.getKey());
124 Collections
.sort(list
);
129 * List all the stories of the given source type in the {@link Library}, or
130 * all the stories if NULL is passed as a type.
133 * the type of story to retrieve, or NULL for all
135 * @return the stories
137 public synchronized List
<MetaData
> getListByType(String type
) {
138 List
<MetaData
> list
= new ArrayList
<MetaData
>();
139 for (Entry
<MetaData
, File
> entry
: getStories(null).entrySet()) {
140 String storyType
= entry
.getValue().getParentFile().getName();
141 if (type
== null || type
.equalsIgnoreCase(storyType
)) {
142 list
.add(entry
.getKey());
146 Collections
.sort(list
);
151 * Retrieve a {@link File} corresponding to the given {@link Story}.
154 * the Library UID of the story
156 * @return the corresponding {@link Story}
158 public synchronized MetaData
getInfo(String luid
) {
160 for (Entry
<MetaData
, File
> entry
: getStories(null).entrySet()) {
161 if (luid
.equals(entry
.getKey().getLuid())) {
162 return entry
.getKey();
171 * Retrieve a {@link File} corresponding to the given {@link Story}.
174 * the Library UID of the story
176 * @return the corresponding {@link Story}
178 public synchronized File
getFile(String luid
) {
180 for (Entry
<MetaData
, File
> entry
: getStories(null).entrySet()) {
181 if (luid
.equals(entry
.getKey().getLuid())) {
182 return entry
.getValue();
191 * Retrieve a specific {@link Story}.
194 * the Library UID of the story
196 * the optional progress reporter
198 * @return the corresponding {@link Story} or NULL if not found
200 public synchronized Story
getStory(String luid
, Progress pg
) {
202 for (Entry
<MetaData
, File
> entry
: getStories(null).entrySet()) {
203 if (luid
.equals(entry
.getKey().getLuid())) {
205 SupportType type
= SupportType
.valueOfAllOkUC(entry
206 .getKey().getType());
207 URL url
= entry
.getValue().toURI().toURL();
209 return BasicSupport
.getSupport(type
).process(url
,
212 throw new IOException("Unknown type: "
213 + entry
.getKey().getType());
215 } catch (IOException e
) {
216 // We should not have not-supported files in the
218 Instance
.syserr(new IOException(
219 "Cannot load file from library: "
220 + entry
.getValue().getPath(), e
));
235 * Import the {@link Story} at the given {@link URL} into the
239 * the {@link URL} to import
241 * the optional progress reporter
243 * @return the imported {@link Story}
245 * @throws IOException
246 * in case of I/O error
248 public Story
imprt(URL url
, Progress pg
) throws IOException
{
249 BasicSupport support
= BasicSupport
.getSupport(url
);
250 if (support
== null) {
251 throw new IOException("URL not supported: " + url
.toString());
254 return save(support
.process(url
, pg
), null);
258 * Export the {@link Story} to the given target in the given format.
261 * the {@link Story} ID
263 * the {@link OutputType} to transform it to
265 * the target to save to
267 * the optional progress reporter
269 * @return the saved resource (the main saved {@link File})
271 * @throws IOException
272 * in case of I/O error
274 public File
export(String luid
, OutputType type
, String target
, Progress pg
)
276 Progress pgGetStory
= new Progress();
277 Progress pgOut
= new Progress();
280 pg
.addProgress(pgGetStory
, 1);
281 pg
.addProgress(pgOut
, 1);
284 BasicOutput out
= BasicOutput
.getOutput(type
, true);
286 throw new IOException("Output type not supported: " + type
);
289 Story story
= getStory(luid
, pgGetStory
);
291 throw new IOException("Cannot find story to export: " + luid
);
294 return out
.process(story
, target
, pgOut
);
298 * Save a {@link Story} to the {@link Library}.
301 * the {@link Story} to save
303 * the optional progress reporter
305 * @return the same {@link Story}, whose LUID may have changed
307 * @throws IOException
308 * in case of I/O error
310 public Story
save(Story story
, Progress pg
) throws IOException
{
311 return save(story
, null, pg
);
315 * Save a {@link Story} to the {@link Library} -- the LUID <b>must</b> be
316 * correct, or NULL to get the next free one.
319 * the {@link Story} to save
321 * the <b>correct</b> LUID or NULL to get the next free one
323 * the optional progress reporter
325 * @return the same {@link Story}, whose LUID may have changed
327 * @throws IOException
328 * in case of I/O error
330 public synchronized Story
save(Story story
, String luid
, Progress pg
)
332 // Do not change the original metadata, but change the original story
333 MetaData key
= story
.getMeta().clone();
336 if (luid
== null || luid
.isEmpty()) {
337 getStories(null); // refresh lastId if needed
338 key
.setLuid(String
.format("%03d", (++lastId
)));
343 getDir(key
).mkdirs();
344 if (!getDir(key
).exists()) {
345 throw new IOException("Cannot create library dir");
349 if (key
!= null && key
.isImageDocument()) {
355 BasicOutput it
= BasicOutput
.getOutput(out
, true);
356 it
.process(story
, getFile(key
).getPath(), pg
);
365 * Delete the given {@link Story} from this {@link Library}.
368 * the LUID of the target {@link Story}
370 * @return TRUE if it was deleted
372 public synchronized boolean delete(String luid
) {
375 MetaData meta
= getInfo(luid
);
376 File file
= getStories(null).get(meta
);
380 String readerExt
= getOutputType(meta
)
381 .getDefaultExtension(true);
382 String fileExt
= getOutputType(meta
).getDefaultExtension(false);
384 String path
= file
.getAbsolutePath();
385 if (readerExt
!= null && !readerExt
.equals(fileExt
)) {
387 .substring(0, path
.length() - readerExt
.length())
389 file
= new File(path
);
390 IOUtils
.deltree(file
);
393 File infoFile
= new File(path
+ ".info");
394 if (!infoFile
.exists()) {
395 infoFile
= new File(path
.substring(0, path
.length()
401 String coverExt
= "."
402 + Instance
.getConfig().getString(
403 Config
.IMAGE_FORMAT_COVER
);
404 File coverFile
= new File(path
+ coverExt
);
405 if (!coverFile
.exists()) {
406 coverFile
= new File(path
.substring(0, path
.length()
407 - fileExt
.length()));
422 * The directory (full path) where the {@link Story} related to this
423 * {@link MetaData} should be located on disk.
426 * the {@link Story} {@link MetaData}
428 * @return the target directory
430 private File
getDir(MetaData key
) {
431 String source
= key
.getSource().replaceAll("[^a-zA-Z0-9._+-]", "_");
432 return new File(baseDir
, source
);
436 * The target (full path) where the {@link Story} related to this
437 * {@link MetaData} should be located on disk.
440 * the {@link Story} {@link MetaData}
444 private File
getFile(MetaData key
) {
445 String title
= key
.getTitle();
449 title
= title
.replaceAll("[^a-zA-Z0-9._+-]", "_");
450 return new File(getDir(key
), key
.getLuid() + "_" + title
);
454 * Return all the known stories in this {@link Library} object.
457 * the optional progress reporter
459 * @return the stories
461 private synchronized Map
<MetaData
, File
> getStories(Progress pg
) {
465 pg
.setMinMax(0, 100);
468 if (stories
.isEmpty()) {
471 File
[] dirs
= baseDir
.listFiles(new FileFilter() {
472 public boolean accept(File file
) {
473 return file
!= null && file
.isDirectory();
477 Progress pgDirs
= new Progress(0, 100 * dirs
.length
);
478 pg
.addProgress(pgDirs
, 100);
480 final String ext
= ".info";
481 for (File dir
: dirs
) {
482 File
[] files
= dir
.listFiles(new FileFilter() {
483 public boolean accept(File file
) {
485 && file
.getPath().toLowerCase().endsWith(ext
);
489 Progress pgFiles
= new Progress(0, files
.length
);
490 pgDirs
.addProgress(pgFiles
, 100);
491 pgDirs
.setName("Loading from: " + dir
.getName());
493 for (File file
: files
) {
495 pgFiles
.setName(file
.getName());
496 MetaData meta
= InfoReader
.readMeta(file
);
498 int id
= Integer
.parseInt(meta
.getLuid());
503 // Replace .info with whatever is needed:
504 String path
= file
.getPath();
505 path
= path
.substring(0,
506 path
.length() - ext
.length());
508 String newExt
= getOutputType(meta
)
509 .getDefaultExtension(true);
511 file
= new File(path
+ newExt
);
514 stories
.put(meta
, file
);
516 } catch (Exception e
) {
518 Instance
.syserr(new IOException(
519 "Cannot understand the LUID of "
520 + file
.getPath() + ": "
521 + meta
.getLuid(), e
));
523 } catch (IOException e
) {
524 // We should not have not-supported files in the
526 Instance
.syserr(new IOException(
527 "Cannot load file from library: "
528 + file
.getPath(), e
));
530 pgFiles
.setProgress(pgFiles
.getProgress() + 1);
532 System
.out
.println("files: " + pgFiles
.getProgress()
533 + "/" + pgFiles
.getMax());
534 System
.out
.println("dirs : " + pgDirs
.getProgress()
535 + "/" + pgDirs
.getMax());
539 pgFiles
.setName(null);
542 pgDirs
.setName("Loading directories");
549 * Return the {@link OutputType} for this {@link Story}.
552 * the {@link Story} {@link MetaData}
556 private OutputType
getOutputType(MetaData meta
) {
557 if (meta
!= null && meta
.isImageDocument()) {