1 package be
.nikiroo
.fanfix
.library
;
3 import java
.awt
.image
.BufferedImage
;
5 import java
.io
.FileFilter
;
6 import java
.io
.FileInputStream
;
7 import java
.io
.IOException
;
8 import java
.io
.InputStream
;
9 import java
.util
.ArrayList
;
10 import java
.util
.HashMap
;
11 import java
.util
.List
;
14 import javax
.imageio
.ImageIO
;
16 import be
.nikiroo
.fanfix
.Instance
;
17 import be
.nikiroo
.fanfix
.bundles
.Config
;
18 import be
.nikiroo
.fanfix
.data
.MetaData
;
19 import be
.nikiroo
.fanfix
.data
.Story
;
20 import be
.nikiroo
.fanfix
.output
.BasicOutput
;
21 import be
.nikiroo
.fanfix
.output
.BasicOutput
.OutputType
;
22 import be
.nikiroo
.fanfix
.output
.InfoCover
;
23 import be
.nikiroo
.fanfix
.supported
.InfoReader
;
24 import be
.nikiroo
.utils
.IOUtils
;
25 import be
.nikiroo
.utils
.ImageUtils
;
26 import be
.nikiroo
.utils
.MarkableFileInputStream
;
27 import be
.nikiroo
.utils
.Progress
;
30 * This {@link BasicLibrary} will store the stories locally on disk.
34 public class LocalLibrary
extends BasicLibrary
{
36 private Map
<MetaData
, File
[]> stories
; // Files: [ infoFile, TargetFile ]
37 private Map
<String
, BufferedImage
> sourceCovers
;
40 private OutputType text
;
41 private OutputType image
;
44 * Create a new {@link LocalLibrary} with the given back-end directory.
47 * the directory where to find the {@link Story} objects
49 * the {@link OutputType} to save the text-focused stories into
51 * the {@link OutputType} to save the images-focused stories into
53 public LocalLibrary(File baseDir
, OutputType text
, OutputType image
) {
54 this.baseDir
= baseDir
;
60 this.sourceCovers
= new HashMap
<String
, BufferedImage
>();
66 protected List
<MetaData
> getMetas(Progress pg
) {
67 return new ArrayList
<MetaData
>(getStories(pg
).keySet());
71 public File
getFile(String luid
) {
72 File
[] files
= getStories(null).get(getInfo(luid
));
81 public BufferedImage
getCover(String luid
) {
82 MetaData meta
= getInfo(luid
);
84 File
[] files
= getStories(null).get(meta
);
86 File infoFile
= files
[0];
89 meta
= InfoReader
.readMeta(infoFile
, true);
90 return meta
.getCover();
91 } catch (IOException e
) {
101 protected void clearCache() {
103 sourceCovers
= new HashMap
<String
, BufferedImage
>();
107 protected synchronized int getNextId() {
108 getStories(null); // make sure lastId is set
113 protected void doDelete(String luid
) throws IOException
{
114 for (File file
: getRelatedFiles(luid
)) {
115 // TODO: throw an IOException if we cannot delete the files?
116 IOUtils
.deltree(file
);
121 protected Story
doSave(Story story
, Progress pg
) throws IOException
{
122 MetaData meta
= story
.getMeta();
124 File expectedTarget
= getExpectedFile(meta
);
125 expectedTarget
.getParentFile().mkdirs();
127 BasicOutput it
= BasicOutput
.getOutput(getOutputType(meta
), true);
128 it
.process(story
, expectedTarget
.getPath(), pg
);
134 protected synchronized void saveMeta(MetaData meta
, Progress pg
)
136 File newDir
= getExpectedDir(meta
.getSource());
137 if (!newDir
.exists()) {
141 List
<File
> relatedFiles
= getRelatedFiles(meta
.getLuid());
142 for (File relatedFile
: relatedFiles
) {
143 // TODO: this is not safe at all.
144 // We should copy all the files THEN delete them
145 // Maybe also adding some rollback cleanup if possible
146 if (relatedFile
.getName().endsWith(".info")) {
148 String name
= relatedFile
.getName().replaceFirst(
150 InfoCover
.writeInfo(newDir
, name
, meta
);
151 relatedFile
.delete();
152 } catch (IOException e
) {
156 relatedFile
.renameTo(new File(newDir
, relatedFile
.getName()));
164 public BufferedImage
getSourceCover(String source
) {
165 if (!sourceCovers
.containsKey(source
)) {
166 sourceCovers
.put(source
, super.getSourceCover(source
));
169 return sourceCovers
.get(source
);
173 public void setSourceCover(String source
, String luid
) {
174 sourceCovers
.put(source
, getCover(luid
));
175 File cover
= new File(getExpectedDir(source
), ".cover.png");
177 ImageIO
.write(sourceCovers
.get(source
), "png", cover
);
178 } catch (IOException e
) {
180 sourceCovers
.remove(source
);
185 * Return the {@link OutputType} for this {@link Story}.
188 * the {@link Story} {@link MetaData}
192 private OutputType
getOutputType(MetaData meta
) {
193 if (meta
!= null && meta
.isImageDocument()) {
201 * Get the target {@link File} related to the given <tt>.info</tt>
202 * {@link File} and {@link MetaData}.
207 * the <tt>.info</tt> {@link File}
209 * @return the target {@link File}
211 private File
getTargetFile(MetaData meta
, File infoFile
) {
212 // Replace .info with whatever is needed:
213 String path
= infoFile
.getPath();
214 path
= path
.substring(0, path
.length() - ".info".length());
215 String newExt
= getOutputType(meta
).getDefaultExtension(true);
217 return new File(path
+ newExt
);
221 * The target (full path) where the {@link Story} related to this
222 * {@link MetaData} should be located on disk for a new {@link Story}.
225 * the {@link Story} {@link MetaData}
229 private File
getExpectedFile(MetaData key
) {
230 String title
= key
.getTitle();
234 title
= title
.replaceAll("[^a-zA-Z0-9._+-]", "_");
235 return new File(getExpectedDir(key
.getSource()), key
.getLuid() + "_"
240 * The directory (full path) where the new {@link Story} related to this
241 * {@link MetaData} should be located on disk.
246 * @return the target directory
248 private File
getExpectedDir(String type
) {
249 String source
= type
.replaceAll("[^a-zA-Z0-9._+-]", "_");
250 return new File(baseDir
, source
);
254 * Return the list of files/directories on disk for this {@link Story}.
256 * If the {@link Story} is not found, and empty list is returned.
259 * the {@link Story} LUID
261 * @return the list of {@link File}s
263 * @throws IOException
264 * if the {@link Story} was not found
266 private List
<File
> getRelatedFiles(String luid
) throws IOException
{
267 List
<File
> files
= new ArrayList
<File
>();
269 MetaData meta
= getInfo(luid
);
271 throw new IOException("Story not found: " + luid
);
274 File infoFile
= getStories(null).get(meta
)[0];
275 File targetFile
= getStories(null).get(meta
)[1];
278 files
.add(targetFile
);
280 String readerExt
= getOutputType(meta
).getDefaultExtension(true);
281 String fileExt
= getOutputType(meta
).getDefaultExtension(false);
283 String path
= targetFile
.getAbsolutePath();
284 if (readerExt
!= null && !readerExt
.equals(fileExt
)) {
285 path
= path
.substring(0, path
.length() - readerExt
.length())
287 File relatedFile
= new File(path
);
289 if (relatedFile
.exists()) {
290 files
.add(relatedFile
);
294 String coverExt
= "."
295 + Instance
.getConfig().getString(Config
.IMAGE_FORMAT_COVER
);
296 File coverFile
= new File(path
+ coverExt
);
297 if (!coverFile
.exists()) {
298 coverFile
= new File(path
.substring(0,
299 path
.length() - fileExt
.length())
303 if (coverFile
.exists()) {
304 files
.add(coverFile
);
311 * Fill the list of stories by reading the content of the local directory
312 * {@link LocalLibrary#baseDir}.
314 * Will use a cached list when possible (see
315 * {@link BasicLibrary#clearCache()}).
318 * the optional {@link Progress}
320 * @return the list of stories
322 private synchronized Map
<MetaData
, File
[]> getStories(Progress pg
) {
326 pg
.setMinMax(0, 100);
329 if (stories
== null) {
330 stories
= new HashMap
<MetaData
, File
[]>();
334 File
[] dirs
= baseDir
.listFiles(new FileFilter() {
336 public boolean accept(File file
) {
337 return file
!= null && file
.isDirectory();
341 Progress pgDirs
= new Progress(0, 100 * dirs
.length
);
342 pg
.addProgress(pgDirs
, 100);
344 for (File dir
: dirs
) {
345 File
[] infoFiles
= dir
.listFiles(new FileFilter() {
347 public boolean accept(File file
) {
349 && file
.getPath().toLowerCase()
354 Progress pgFiles
= new Progress(0, infoFiles
.length
);
355 pgDirs
.addProgress(pgFiles
, 100);
356 pgDirs
.setName("Loading from: " + dir
.getName());
358 String source
= null;
359 for (File infoFile
: infoFiles
) {
360 pgFiles
.setName(infoFile
.getName());
362 MetaData meta
= InfoReader
.readMeta(infoFile
, false);
363 source
= meta
.getSource();
365 int id
= Integer
.parseInt(meta
.getLuid());
370 stories
.put(meta
, new File
[] { infoFile
,
371 getTargetFile(meta
, infoFile
) });
372 } catch (Exception e
) {
374 throw new IOException(
375 "Cannot understand the LUID of "
378 + (meta
== null ?
"[meta is NULL]"
379 : meta
.getLuid()), e
);
381 } catch (IOException e
) {
382 // We should not have not-supported files in the
384 Instance
.syserr(new IOException(
385 "Cannot load file from library: " + infoFile
, e
));
390 File cover
= new File(dir
, ".cover.png");
391 if (cover
.exists()) {
393 InputStream in
= new MarkableFileInputStream(
394 new FileInputStream(cover
));
396 sourceCovers
.put(source
, ImageUtils
.fromStream(in
));
400 } catch (IOException e
) {
405 pgFiles
.setName(null);
408 pgDirs
.setName("Loading directories");