1 package be
.nikiroo
.fanfix
;
4 import java
.io
.IOException
;
6 import java
.util
.ArrayList
;
7 import java
.util
.Collections
;
8 import java
.util
.HashMap
;
11 import java
.util
.Map
.Entry
;
13 import be
.nikiroo
.fanfix
.bundles
.Config
;
14 import be
.nikiroo
.fanfix
.data
.MetaData
;
15 import be
.nikiroo
.fanfix
.data
.Story
;
16 import be
.nikiroo
.fanfix
.output
.BasicOutput
;
17 import be
.nikiroo
.fanfix
.output
.BasicOutput
.OutputType
;
18 import be
.nikiroo
.fanfix
.supported
.BasicSupport
;
19 import be
.nikiroo
.fanfix
.supported
.BasicSupport
.SupportType
;
20 import be
.nikiroo
.fanfix
.supported
.InfoReader
;
21 import be
.nikiroo
.utils
.IOUtils
;
22 import be
.nikiroo
.utils
.Progress
;
25 * Manage a library of Stories: import, export, list.
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}.
32 public class Library
{
34 private Map
<MetaData
, File
> stories
;
36 private OutputType text
;
37 private OutputType image
;
40 * Create a new {@link Library} with the given backend directory.
43 * the directory where to find the {@link Story} objects
45 * the {@link OutputType} to save the text-focused stories into
47 * the {@link OutputType} to save the images-focused stories into
49 public Library(File dir
, OutputType text
, OutputType image
) {
51 this.stories
= new HashMap
<MetaData
, File
>();
60 * List all the known types of stories.
64 public synchronized List
<String
> getTypes() {
65 List
<String
> list
= new ArrayList
<String
>();
66 for (Entry
<MetaData
, File
> entry
: getStories().entrySet()) {
67 String storyType
= entry
.getKey().getSource();
68 if (!list
.contains(storyType
)) {
73 Collections
.sort(list
);
78 * List all the known authors of stories.
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
);
91 Collections
.sort(list
);
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.
100 * the author of the stories to retrieve, or NULL for all
102 * @return the stories
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());
113 Collections
.sort(list
);
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.
122 * the type of story to retrieve, or NULL for all
124 * @return the stories
126 public synchronized List
<MetaData
> getListByType(String type
) {
127 List
<MetaData
> list
= new ArrayList
<MetaData
>();
128 for (Entry
<MetaData
, File
> entry
: getStories().entrySet()) {
129 String storyType
= entry
.getValue().getParentFile().getName();
130 if (type
== null || type
.equalsIgnoreCase(storyType
)) {
131 list
.add(entry
.getKey());
135 Collections
.sort(list
);
140 * Retrieve a {@link File} corresponding to the given {@link Story}.
143 * the Library UID of the story
145 * @return the corresponding {@link Story}
147 public synchronized MetaData
getInfo(String luid
) {
149 for (Entry
<MetaData
, File
> entry
: getStories().entrySet()) {
150 if (luid
.equals(entry
.getKey().getLuid())) {
151 return entry
.getKey();
160 * Retrieve a {@link File} corresponding to the given {@link Story}.
163 * the Library UID of the story
165 * @return the corresponding {@link Story}
167 public synchronized File
getFile(String luid
) {
169 for (Entry
<MetaData
, File
> entry
: getStories().entrySet()) {
170 if (luid
.equals(entry
.getKey().getLuid())) {
171 return entry
.getValue();
180 * Retrieve a specific {@link Story}.
183 * the Library UID of the story
185 * the optional progress reporter
187 * @return the corresponding {@link Story} or NULL if not found
189 public synchronized Story
getStory(String luid
, Progress pg
) {
191 for (Entry
<MetaData
, File
> entry
: getStories().entrySet()) {
192 if (luid
.equals(entry
.getKey().getLuid())) {
194 SupportType type
= SupportType
.valueOfAllOkUC(entry
195 .getKey().getType());
196 URL url
= entry
.getValue().toURI().toURL();
198 return BasicSupport
.getSupport(type
).process(url
,
201 throw new IOException("Unknown type: "
202 + entry
.getKey().getType());
204 } catch (IOException e
) {
205 // We should not have not-supported files in the
207 Instance
.syserr(new IOException(
208 "Cannot load file from library: "
209 + entry
.getValue().getPath(), e
));
224 * Import the {@link Story} at the given {@link URL} into the
228 * the {@link URL} to import
230 * the optional progress reporter
232 * @return the imported {@link Story}
234 * @throws IOException
235 * in case of I/O error
237 public Story
imprt(URL url
, Progress pg
) throws IOException
{
238 BasicSupport support
= BasicSupport
.getSupport(url
);
239 if (support
== null) {
240 throw new IOException("URL not supported: " + url
.toString());
243 return save(support
.process(url
, pg
), null);
247 * Export the {@link Story} to the given target in the given format.
250 * the {@link Story} ID
252 * the {@link OutputType} to transform it to
254 * the target to save to
256 * the optional progress reporter
258 * @return the saved resource (the main saved {@link File})
260 * @throws IOException
261 * in case of I/O error
263 public File
export(String luid
, OutputType type
, String target
, Progress pg
)
265 Progress pgGetStory
= new Progress();
266 Progress pgOut
= new Progress();
269 pg
.addProgress(pgGetStory
, 1);
270 pg
.addProgress(pgOut
, 1);
273 BasicOutput out
= BasicOutput
.getOutput(type
, true);
275 throw new IOException("Output type not supported: " + type
);
278 Story story
= getStory(luid
, pgGetStory
);
280 throw new IOException("Cannot find story to export: " + luid
);
283 return out
.process(story
, target
, pgOut
);
287 * Save a {@link Story} to the {@link Library}.
290 * the {@link Story} to save
292 * the optional progress reporter
294 * @return the same {@link Story}, whose LUID may have changed
296 * @throws IOException
297 * in case of I/O error
299 public Story
save(Story story
, Progress pg
) throws IOException
{
300 return save(story
, null, pg
);
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.
308 * the {@link Story} to save
310 * the <b>correct</b> LUID or NULL to get the next free one
312 * the optional progress reporter
314 * @return the same {@link Story}, whose LUID may have changed
316 * @throws IOException
317 * in case of I/O error
319 public synchronized Story
save(Story story
, String luid
, Progress pg
)
321 // Do not change the original metadata, but change the original story
322 MetaData key
= story
.getMeta().clone();
325 if (luid
== null || luid
.isEmpty()) {
326 getStories(); // refresh lastId if needed
327 key
.setLuid(String
.format("%03d", (++lastId
)));
332 getDir(key
).mkdirs();
333 if (!getDir(key
).exists()) {
334 throw new IOException("Cannot create library dir");
338 if (key
!= null && key
.isImageDocument()) {
344 BasicOutput it
= BasicOutput
.getOutput(out
, true);
345 it
.process(story
, getFile(key
).getPath(), pg
);
354 * Delete the given {@link Story} from this {@link Library}.
357 * the LUID of the target {@link Story}
359 * @return TRUE if it was deleted
361 public synchronized boolean delete(String luid
) {
364 MetaData meta
= getInfo(luid
);
365 File file
= getStories().get(meta
);
369 String readerExt
= getOutputType(meta
)
370 .getDefaultExtension(true);
371 String fileExt
= getOutputType(meta
).getDefaultExtension(false);
373 String path
= file
.getAbsolutePath();
374 if (readerExt
!= null && !readerExt
.equals(fileExt
)) {
376 .substring(0, path
.length() - readerExt
.length())
378 file
= new File(path
);
379 IOUtils
.deltree(file
);
382 File infoFile
= new File(path
+ ".info");
383 if (!infoFile
.exists()) {
384 infoFile
= new File(path
.substring(0, path
.length()
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()
396 - fileExt
.length()));
411 * The directory (full path) where the {@link Story} related to this
412 * {@link MetaData} should be located on disk.
415 * the {@link Story} {@link MetaData}
417 * @return the target directory
419 private File
getDir(MetaData key
) {
420 String source
= key
.getSource().replaceAll("[^a-zA-Z0-9._+-]", "_");
421 return new File(baseDir
, source
);
425 * The target (full path) where the {@link Story} related to this
426 * {@link MetaData} should be located on disk.
429 * the {@link Story} {@link MetaData}
433 private File
getFile(MetaData key
) {
434 String title
= key
.getTitle();
438 title
= title
.replaceAll("[^a-zA-Z0-9._+-]", "_");
439 return new File(getDir(key
), key
.getLuid() + "_" + title
);
443 * Return all the known stories in this {@link Library} object.
445 * @return the stories
447 private synchronized Map
<MetaData
, File
> getStories() {
448 if (stories
.isEmpty()) {
451 String ext
= ".info";
452 for (File dir
: baseDir
.listFiles()) {
453 if (dir
.isDirectory()) {
454 for (File file
: dir
.listFiles()) {
456 if (file
.getPath().toLowerCase().endsWith(ext
)) {
457 MetaData meta
= InfoReader
.readMeta(file
);
459 int id
= Integer
.parseInt(meta
.getLuid());
464 // Replace .info with whatever is needed:
465 String path
= file
.getPath();
466 path
= path
.substring(0, path
.length()
469 String newExt
= getOutputType(meta
)
470 .getDefaultExtension(true);
472 file
= new File(path
+ newExt
);
475 stories
.put(meta
, file
);
477 } catch (Exception e
) {
479 Instance
.syserr(new IOException(
480 "Cannot understand the LUID of "
481 + file
.getPath() + ": "
482 + meta
.getLuid(), e
));
485 } catch (IOException e
) {
486 // We should not have not-supported files in the
488 Instance
.syserr(new IOException(
489 "Cannot load file from library: "
490 + file
.getPath(), e
));
501 * Return the {@link OutputType} for this {@link Story}.
504 * the {@link Story} {@link MetaData}
508 private OutputType
getOutputType(MetaData meta
) {
509 if (meta
!= null && meta
.isImageDocument()) {