1 package be
.nikiroo
.fanfix
;
3 import java
.awt
.image
.BufferedImage
;
5 import java
.io
.FileFilter
;
6 import java
.io
.IOException
;
7 import java
.util
.ArrayList
;
8 import java
.util
.HashMap
;
12 import be
.nikiroo
.fanfix
.bundles
.Config
;
13 import be
.nikiroo
.fanfix
.data
.MetaData
;
14 import be
.nikiroo
.fanfix
.data
.Story
;
15 import be
.nikiroo
.fanfix
.output
.BasicOutput
;
16 import be
.nikiroo
.fanfix
.output
.BasicOutput
.OutputType
;
17 import be
.nikiroo
.fanfix
.output
.InfoCover
;
18 import be
.nikiroo
.fanfix
.supported
.InfoReader
;
19 import be
.nikiroo
.utils
.IOUtils
;
20 import be
.nikiroo
.utils
.Progress
;
23 * This {@link BasicLibrary} will store the stories locally on disk.
27 public class LocalLibrary
extends BasicLibrary
{
29 private Map
<MetaData
, File
[]> stories
; // Files: [ infoFile, TargetFile ]
32 private OutputType text
;
33 private OutputType image
;
36 * Create a new {@link LocalLibrary} with the given back-end directory.
39 * the directory where to find the {@link Story} objects
41 * the {@link OutputType} to save the text-focused stories into
43 * the {@link OutputType} to save the images-focused stories into
45 public LocalLibrary(File baseDir
, OutputType text
, OutputType image
) {
46 this.baseDir
= baseDir
;
57 protected List
<MetaData
> getMetas(Progress pg
) {
58 return new ArrayList
<MetaData
>(getStories(pg
).keySet());
62 public File
getFile(String luid
) {
63 File
[] files
= getStories(null).get(getInfo(luid
));
72 public BufferedImage
getCover(String luid
) {
73 MetaData meta
= getInfo(luid
);
75 File
[] files
= getStories(null).get(meta
);
77 File infoFile
= files
[0];
80 meta
= InfoReader
.readMeta(infoFile
, true);
81 return meta
.getCover();
82 } catch (IOException e
) {
92 protected void clearCache() {
97 protected synchronized int getNextId() {
102 protected void doDelete(String luid
) throws IOException
{
103 for (File file
: getRelatedFiles(luid
)) {
104 // TODO: throw an IOException if we cannot delete the files?
105 IOUtils
.deltree(file
);
110 protected Story
doSave(Story story
, Progress pg
) throws IOException
{
111 MetaData meta
= story
.getMeta();
113 File expectedTarget
= getExpectedFile(meta
);
114 expectedTarget
.getParentFile().mkdirs();
116 BasicOutput it
= BasicOutput
.getOutput(getOutputType(meta
), true);
117 it
.process(story
, expectedTarget
.getPath(), pg
);
123 protected synchronized void saveMeta(MetaData meta
, Progress pg
)
125 File newDir
= getExpectedDir(meta
.getSource());
126 if (!newDir
.exists()) {
130 List
<File
> relatedFiles
= getRelatedFiles(meta
.getLuid());
131 for (File relatedFile
: relatedFiles
) {
132 // TODO: this is not safe at all.
133 // We should copy all the files THEN delete them
134 // Maybe also adding some rollback cleanup if possible
135 if (relatedFile
.getName().endsWith(".info")) {
137 String name
= relatedFile
.getName().replaceFirst(
139 InfoCover
.writeInfo(newDir
, name
, meta
);
140 relatedFile
.delete();
141 } catch (IOException e
) {
145 relatedFile
.renameTo(new File(newDir
, relatedFile
.getName()));
153 * Return the {@link OutputType} for this {@link Story}.
156 * the {@link Story} {@link MetaData}
160 private OutputType
getOutputType(MetaData meta
) {
161 if (meta
!= null && meta
.isImageDocument()) {
169 * Get the target {@link File} related to the given <tt>.info</tt>
170 * {@link File} and {@link MetaData}.
175 * the <tt>.info</tt> {@link File}
177 * @return the target {@link File}
179 private File
getTargetFile(MetaData meta
, File infoFile
) {
180 // Replace .info with whatever is needed:
181 String path
= infoFile
.getPath();
182 path
= path
.substring(0, path
.length() - ".info".length());
183 String newExt
= getOutputType(meta
).getDefaultExtension(true);
185 return new File(path
+ newExt
);
189 * The target (full path) where the {@link Story} related to this
190 * {@link MetaData} should be located on disk for a new {@link Story}.
193 * the {@link Story} {@link MetaData}
197 private File
getExpectedFile(MetaData key
) {
198 String title
= key
.getTitle();
202 title
= title
.replaceAll("[^a-zA-Z0-9._+-]", "_");
203 return new File(getExpectedDir(key
.getSource()), key
.getLuid() + "_"
208 * The directory (full path) where the new {@link Story} related to this
209 * {@link MetaData} should be located on disk.
214 * @return the target directory
216 private File
getExpectedDir(String type
) {
217 String source
= type
.replaceAll("[^a-zA-Z0-9._+-]", "_");
218 return new File(baseDir
, source
);
222 * Return the list of files/directories on disk for this {@link Story}.
224 * If the {@link Story} is not found, and empty list is returned.
227 * the {@link Story} LUID
229 * @return the list of {@link File}s
231 * @throws IOException
232 * if the {@link Story} was not found
234 private List
<File
> getRelatedFiles(String luid
) throws IOException
{
235 List
<File
> files
= new ArrayList
<File
>();
237 MetaData meta
= getInfo(luid
);
239 throw new IOException("Story not found: " + luid
);
241 File infoFile
= getStories(null).get(meta
)[0];
242 File targetFile
= getStories(null).get(meta
)[1];
245 files
.add(targetFile
);
247 String readerExt
= getOutputType(meta
).getDefaultExtension(true);
248 String fileExt
= getOutputType(meta
).getDefaultExtension(false);
250 String path
= targetFile
.getAbsolutePath();
251 if (readerExt
!= null && !readerExt
.equals(fileExt
)) {
252 path
= path
.substring(0, path
.length() - readerExt
.length())
254 File relatedFile
= new File(path
);
256 if (relatedFile
.exists()) {
257 files
.add(relatedFile
);
261 String coverExt
= "."
262 + Instance
.getConfig().getString(Config
.IMAGE_FORMAT_COVER
);
263 File coverFile
= new File(path
+ coverExt
);
264 if (!coverFile
.exists()) {
265 coverFile
= new File(path
.substring(0,
266 path
.length() - fileExt
.length())
270 if (coverFile
.exists()) {
271 files
.add(coverFile
);
279 * Fill the list of stories by reading the content of the local directory
280 * {@link LocalLibrary#baseDir}.
282 * Will use a cached list when possible (see
283 * {@link BasicLibrary#clearCache()}).
286 * the optional {@link Progress}
288 * @return the list of stories
290 private synchronized Map
<MetaData
, File
[]> getStories(Progress pg
) {
294 pg
.setMinMax(0, 100);
297 if (stories
== null) {
298 stories
= new HashMap
<MetaData
, File
[]>();
302 File
[] dirs
= baseDir
.listFiles(new FileFilter() {
303 public boolean accept(File file
) {
304 return file
!= null && file
.isDirectory();
308 Progress pgDirs
= new Progress(0, 100 * dirs
.length
);
309 pg
.addProgress(pgDirs
, 100);
311 for (File dir
: dirs
) {
312 File
[] infoFiles
= dir
.listFiles(new FileFilter() {
313 public boolean accept(File file
) {
315 && file
.getPath().toLowerCase()
320 Progress pgFiles
= new Progress(0, infoFiles
.length
);
321 pgDirs
.addProgress(pgFiles
, 100);
322 pgDirs
.setName("Loading from: " + dir
.getName());
324 for (File infoFile
: infoFiles
) {
325 pgFiles
.setName(infoFile
.getName());
327 MetaData meta
= InfoReader
.readMeta(infoFile
, false);
329 int id
= Integer
.parseInt(meta
.getLuid());
334 stories
.put(meta
, new File
[] { infoFile
,
335 getTargetFile(meta
, infoFile
) });
336 } catch (Exception e
) {
338 throw new IOException(
339 "Cannot understand the LUID of "
342 + (meta
== null ?
"[meta is NULL]"
343 : meta
.getLuid()), e
);
345 } catch (IOException e
) {
346 // We should not have not-supported files in the
348 Instance
.syserr(new IOException(
349 "Cannot load file from library: " + infoFile
, e
));
354 pgFiles
.setName(null);
357 pgDirs
.setName("Loading directories");