1 package be
.nikiroo
.fanfix
.library
;
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
.Instance
;
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
.output
.InfoCover
;
19 import be
.nikiroo
.fanfix
.supported
.InfoReader
;
20 import be
.nikiroo
.utils
.IOUtils
;
21 import be
.nikiroo
.utils
.Progress
;
24 * This {@link BasicLibrary} will store the stories locally on disk.
28 public class LocalLibrary
extends BasicLibrary
{
30 private Map
<MetaData
, File
[]> stories
; // Files: [ infoFile, TargetFile ]
33 private OutputType text
;
34 private OutputType image
;
37 * Create a new {@link LocalLibrary} with the given back-end directory.
40 * the directory where to find the {@link Story} objects
42 * the {@link OutputType} to save the text-focused stories into
44 * the {@link OutputType} to save the images-focused stories into
46 public LocalLibrary(File baseDir
, OutputType text
, OutputType image
) {
47 this.baseDir
= baseDir
;
58 protected List
<MetaData
> getMetas(Progress pg
) {
59 return new ArrayList
<MetaData
>(getStories(pg
).keySet());
63 public File
getFile(String luid
) {
64 File
[] files
= getStories(null).get(getInfo(luid
));
73 public BufferedImage
getCover(String luid
) {
74 MetaData meta
= getInfo(luid
);
76 File
[] files
= getStories(null).get(meta
);
78 File infoFile
= files
[0];
81 meta
= InfoReader
.readMeta(infoFile
, true);
82 return meta
.getCover();
83 } catch (IOException e
) {
93 protected void clearCache() {
98 protected synchronized int getNextId() {
103 protected void doDelete(String luid
) throws IOException
{
104 for (File file
: getRelatedFiles(luid
)) {
105 // TODO: throw an IOException if we cannot delete the files?
106 IOUtils
.deltree(file
);
111 protected Story
doSave(Story story
, Progress pg
) throws IOException
{
112 MetaData meta
= story
.getMeta();
114 File expectedTarget
= getExpectedFile(meta
);
115 expectedTarget
.getParentFile().mkdirs();
117 BasicOutput it
= BasicOutput
.getOutput(getOutputType(meta
), true);
118 it
.process(story
, expectedTarget
.getPath(), pg
);
124 protected synchronized void saveMeta(MetaData meta
, Progress pg
)
126 File newDir
= getExpectedDir(meta
.getSource());
127 if (!newDir
.exists()) {
131 List
<File
> relatedFiles
= getRelatedFiles(meta
.getLuid());
132 for (File relatedFile
: relatedFiles
) {
133 // TODO: this is not safe at all.
134 // We should copy all the files THEN delete them
135 // Maybe also adding some rollback cleanup if possible
136 if (relatedFile
.getName().endsWith(".info")) {
138 String name
= relatedFile
.getName().replaceFirst(
140 InfoCover
.writeInfo(newDir
, name
, meta
);
141 relatedFile
.delete();
142 } catch (IOException e
) {
146 relatedFile
.renameTo(new File(newDir
, relatedFile
.getName()));
154 * Return the {@link OutputType} for this {@link Story}.
157 * the {@link Story} {@link MetaData}
161 private OutputType
getOutputType(MetaData meta
) {
162 if (meta
!= null && meta
.isImageDocument()) {
170 * Get the target {@link File} related to the given <tt>.info</tt>
171 * {@link File} and {@link MetaData}.
176 * the <tt>.info</tt> {@link File}
178 * @return the target {@link File}
180 private File
getTargetFile(MetaData meta
, File infoFile
) {
181 // Replace .info with whatever is needed:
182 String path
= infoFile
.getPath();
183 path
= path
.substring(0, path
.length() - ".info".length());
184 String newExt
= getOutputType(meta
).getDefaultExtension(true);
186 return new File(path
+ newExt
);
190 * The target (full path) where the {@link Story} related to this
191 * {@link MetaData} should be located on disk for a new {@link Story}.
194 * the {@link Story} {@link MetaData}
198 private File
getExpectedFile(MetaData key
) {
199 String title
= key
.getTitle();
203 title
= title
.replaceAll("[^a-zA-Z0-9._+-]", "_");
204 return new File(getExpectedDir(key
.getSource()), key
.getLuid() + "_"
209 * The directory (full path) where the new {@link Story} related to this
210 * {@link MetaData} should be located on disk.
215 * @return the target directory
217 private File
getExpectedDir(String type
) {
218 String source
= type
.replaceAll("[^a-zA-Z0-9._+-]", "_");
219 return new File(baseDir
, source
);
223 * Return the list of files/directories on disk for this {@link Story}.
225 * If the {@link Story} is not found, and empty list is returned.
228 * the {@link Story} LUID
230 * @return the list of {@link File}s
232 * @throws IOException
233 * if the {@link Story} was not found
235 private List
<File
> getRelatedFiles(String luid
) throws IOException
{
236 List
<File
> files
= new ArrayList
<File
>();
238 MetaData meta
= getInfo(luid
);
240 throw new IOException("Story not found: " + luid
);
242 File infoFile
= getStories(null).get(meta
)[0];
243 File targetFile
= getStories(null).get(meta
)[1];
246 files
.add(targetFile
);
248 String readerExt
= getOutputType(meta
).getDefaultExtension(true);
249 String fileExt
= getOutputType(meta
).getDefaultExtension(false);
251 String path
= targetFile
.getAbsolutePath();
252 if (readerExt
!= null && !readerExt
.equals(fileExt
)) {
253 path
= path
.substring(0, path
.length() - readerExt
.length())
255 File relatedFile
= new File(path
);
257 if (relatedFile
.exists()) {
258 files
.add(relatedFile
);
262 String coverExt
= "."
263 + Instance
.getConfig().getString(Config
.IMAGE_FORMAT_COVER
);
264 File coverFile
= new File(path
+ coverExt
);
265 if (!coverFile
.exists()) {
266 coverFile
= new File(path
.substring(0,
267 path
.length() - fileExt
.length())
271 if (coverFile
.exists()) {
272 files
.add(coverFile
);
280 * Fill the list of stories by reading the content of the local directory
281 * {@link LocalLibrary#baseDir}.
283 * Will use a cached list when possible (see
284 * {@link BasicLibrary#clearCache()}).
287 * the optional {@link Progress}
289 * @return the list of stories
291 private synchronized Map
<MetaData
, File
[]> getStories(Progress pg
) {
295 pg
.setMinMax(0, 100);
298 if (stories
== null) {
299 stories
= new HashMap
<MetaData
, File
[]>();
303 File
[] dirs
= baseDir
.listFiles(new FileFilter() {
304 public boolean accept(File file
) {
305 return file
!= null && file
.isDirectory();
309 Progress pgDirs
= new Progress(0, 100 * dirs
.length
);
310 pg
.addProgress(pgDirs
, 100);
312 for (File dir
: dirs
) {
313 File
[] infoFiles
= dir
.listFiles(new FileFilter() {
314 public boolean accept(File file
) {
316 && file
.getPath().toLowerCase()
321 Progress pgFiles
= new Progress(0, infoFiles
.length
);
322 pgDirs
.addProgress(pgFiles
, 100);
323 pgDirs
.setName("Loading from: " + dir
.getName());
325 for (File infoFile
: infoFiles
) {
326 pgFiles
.setName(infoFile
.getName());
328 MetaData meta
= InfoReader
.readMeta(infoFile
, false);
330 int id
= Integer
.parseInt(meta
.getLuid());
335 stories
.put(meta
, new File
[] { infoFile
,
336 getTargetFile(meta
, infoFile
) });
337 } catch (Exception e
) {
339 throw new IOException(
340 "Cannot understand the LUID of "
343 + (meta
== null ?
"[meta is NULL]"
344 : meta
.getLuid()), e
);
346 } catch (IOException e
) {
347 // We should not have not-supported files in the
349 Instance
.syserr(new IOException(
350 "Cannot load file from library: " + infoFile
, e
));
355 pgFiles
.setName(null);
358 pgDirs
.setName("Loading directories");