Woopsie, forgot file
[fanfix.git] / src / be / nikiroo / fanfix / Library.java
CommitLineData
08fe2e33
NR
1package be.nikiroo.fanfix;
2
3import java.io.File;
4import java.io.IOException;
5import java.net.URL;
6import java.util.ArrayList;
7import java.util.HashMap;
8import java.util.List;
9import java.util.Map;
10import java.util.Map.Entry;
11
10d558d2 12import be.nikiroo.fanfix.bundles.Config;
08fe2e33
NR
13import be.nikiroo.fanfix.data.MetaData;
14import be.nikiroo.fanfix.data.Story;
15import be.nikiroo.fanfix.output.BasicOutput;
16import be.nikiroo.fanfix.output.BasicOutput.OutputType;
17import be.nikiroo.fanfix.supported.BasicSupport;
18import be.nikiroo.fanfix.supported.BasicSupport.SupportType;
68686a37 19import be.nikiroo.fanfix.supported.InfoReader;
9843a5e5 20import be.nikiroo.utils.IOUtils;
3b2b638f 21import be.nikiroo.utils.Progress;
08fe2e33
NR
22
23/**
24 * Manage a library of Stories: import, export, list.
25 * <p>
26 * Each {@link Story} object will be associated with a (local to the library)
27 * unique ID, the LUID, which will be used to identify the {@link Story}.
28 *
29 * @author niki
30 */
31public class Library {
32 private File baseDir;
33 private Map<MetaData, File> stories;
08fe2e33 34 private int lastId;
2206ef66
NR
35 private OutputType text;
36 private OutputType image;
08fe2e33
NR
37
38 /**
39 * Create a new {@link Library} with the given backend directory.
40 *
41 * @param dir
2206ef66
NR
42 * the directory where to find the {@link Story} objects
43 * @param text
44 * the {@link OutputType} to save the text-focused stories into
45 * @param image
46 * the {@link OutputType} to save the images-focused stories into
08fe2e33 47 */
2206ef66 48 public Library(File dir, OutputType text, OutputType image) {
08fe2e33
NR
49 this.baseDir = dir;
50 this.stories = new HashMap<MetaData, File>();
51 this.lastId = 0;
2206ef66
NR
52 this.text = text;
53 this.image = image;
08fe2e33
NR
54
55 dir.mkdirs();
56 }
57
333f0e7b
NR
58 /**
59 * List all the known types of stories.
60 *
61 * @return the types
62 */
10d558d2 63 public synchronized List<String> getTypes() {
333f0e7b
NR
64 List<String> list = new ArrayList<String>();
65 for (Entry<MetaData, File> entry : getStories().entrySet()) {
66 String storyType = entry.getValue().getParentFile().getName();
67 if (!list.contains(storyType)) {
68 list.add(storyType);
69 }
70 }
71
72 return list;
73 }
74
08fe2e33
NR
75 /**
76 * List all the stories of the given source type in the {@link Library}, or
77 * all the stories if NULL is passed as a type.
78 *
79 * @param type
80 * the type of story to retrieve, or NULL for all
81 *
82 * @return the stories
83 */
10d558d2 84 public synchronized List<MetaData> getList(String type) {
08fe2e33
NR
85 List<MetaData> list = new ArrayList<MetaData>();
86 for (Entry<MetaData, File> entry : getStories().entrySet()) {
87 String storyType = entry.getValue().getParentFile().getName();
333f0e7b 88 if (type == null || type.equalsIgnoreCase(storyType)) {
08fe2e33
NR
89 list.add(entry.getKey());
90 }
91 }
92
93 return list;
94 }
95
301791d3
NR
96 /**
97 * Retrieve a {@link File} corresponding to the given {@link Story}.
98 *
99 * @param luid
100 * the Library UID of the story
101 *
102 * @return the corresponding {@link Story}
103 */
10d558d2 104 public synchronized MetaData getInfo(String luid) {
301791d3
NR
105 if (luid != null) {
106 for (Entry<MetaData, File> entry : getStories().entrySet()) {
107 if (luid.equals(entry.getKey().getLuid())) {
108 return entry.getKey();
109 }
110 }
111 }
112
113 return null;
114 }
115
2206ef66
NR
116 /**
117 * Retrieve a {@link File} corresponding to the given {@link Story}.
118 *
119 * @param luid
120 * the Library UID of the story
121 *
122 * @return the corresponding {@link Story}
123 */
10d558d2 124 public synchronized File getFile(String luid) {
2206ef66
NR
125 if (luid != null) {
126 for (Entry<MetaData, File> entry : getStories().entrySet()) {
127 if (luid.equals(entry.getKey().getLuid())) {
128 return entry.getValue();
129 }
130 }
131 }
132
133 return null;
134 }
135
08fe2e33
NR
136 /**
137 * Retrieve a specific {@link Story}.
138 *
139 * @param luid
140 * the Library UID of the story
92fb0719
NR
141 * @param pg
142 * the optional progress reporter
08fe2e33 143 *
3d247bc3 144 * @return the corresponding {@link Story} or NULL if not found
08fe2e33 145 */
10d558d2 146 public synchronized Story getStory(String luid, Progress pg) {
08fe2e33
NR
147 if (luid != null) {
148 for (Entry<MetaData, File> entry : getStories().entrySet()) {
149 if (luid.equals(entry.getKey().getLuid())) {
150 try {
fe999aa4
NR
151 SupportType type = SupportType.valueOfAllOkUC(entry
152 .getKey().getType());
153 URL url = entry.getValue().toURI().toURL();
154 if (type != null) {
92fb0719
NR
155 return BasicSupport.getSupport(type).process(url,
156 pg);
fe999aa4
NR
157 } else {
158 throw new IOException("Unknown type: "
159 + entry.getKey().getType());
160 }
08fe2e33
NR
161 } catch (IOException e) {
162 // We should not have not-supported files in the
163 // library
164 Instance.syserr(new IOException(
165 "Cannot load file from library: "
166 + entry.getValue().getPath(), e));
167 }
168 }
169 }
170 }
171
92fb0719
NR
172 if (pg != null) {
173 pg.setMinMax(0, 1);
174 pg.setProgress(1);
175 }
176
08fe2e33
NR
177 return null;
178 }
179
180 /**
181 * Import the {@link Story} at the given {@link URL} into the
182 * {@link Library}.
183 *
184 * @param url
185 * the {@link URL} to import
92fb0719
NR
186 * @param pg
187 * the optional progress reporter
08fe2e33
NR
188 *
189 * @return the imported {@link Story}
190 *
191 * @throws IOException
192 * in case of I/O error
193 */
92fb0719 194 public Story imprt(URL url, Progress pg) throws IOException {
08fe2e33
NR
195 BasicSupport support = BasicSupport.getSupport(url);
196 if (support == null) {
197 throw new IOException("URL not supported: " + url.toString());
198 }
199
92fb0719 200 return save(support.process(url, pg), null);
08fe2e33
NR
201 }
202
203 /**
204 * Export the {@link Story} to the given target in the given format.
205 *
206 * @param luid
207 * the {@link Story} ID
208 * @param type
209 * the {@link OutputType} to transform it to
210 * @param target
211 * the target to save to
92fb0719
NR
212 * @param pg
213 * the optional progress reporter
08fe2e33
NR
214 *
215 * @return the saved resource (the main saved {@link File})
216 *
217 * @throws IOException
218 * in case of I/O error
219 */
92fb0719 220 public File export(String luid, OutputType type, String target, Progress pg)
08fe2e33 221 throws IOException {
bee7dffe
NR
222 Progress pgGetStory = new Progress();
223 Progress pgOut = new Progress();
224 if (pg != null) {
225 pg.setMax(2);
226 pg.addProgress(pgGetStory, 1);
227 pg.addProgress(pgOut, 1);
228 }
229
08fe2e33
NR
230 BasicOutput out = BasicOutput.getOutput(type, true);
231 if (out == null) {
232 throw new IOException("Output type not supported: " + type);
233 }
234
bee7dffe 235 Story story = getStory(luid, pgGetStory);
73ce17ef
NR
236 if (story == null) {
237 throw new IOException("Cannot find story to export: " + luid);
238 }
239
bee7dffe 240 return out.process(story, target, pgOut);
08fe2e33
NR
241 }
242
243 /**
2206ef66
NR
244 * Save a {@link Story} to the {@link Library}.
245 *
246 * @param story
247 * the {@link Story} to save
bee7dffe
NR
248 * @param pg
249 * the optional progress reporter
2206ef66
NR
250 *
251 * @return the same {@link Story}, whose LUID may have changed
252 *
253 * @throws IOException
254 * in case of I/O error
255 */
bee7dffe
NR
256 public Story save(Story story, Progress pg) throws IOException {
257 return save(story, null, pg);
2206ef66
NR
258 }
259
260 /**
261 * Save a {@link Story} to the {@link Library} -- the LUID <b>must</b> be
262 * correct, or NULL to get the next free one.
08fe2e33
NR
263 *
264 * @param story
265 * the {@link Story} to save
2206ef66
NR
266 * @param luid
267 * the <b>correct</b> LUID or NULL to get the next free one
bee7dffe
NR
268 * @param pg
269 * the optional progress reporter
2206ef66
NR
270 *
271 * @return the same {@link Story}, whose LUID may have changed
08fe2e33
NR
272 *
273 * @throws IOException
274 * in case of I/O error
275 */
10d558d2
NR
276 public synchronized Story save(Story story, String luid, Progress pg)
277 throws IOException {
a7d266e6 278 // Do not change the original metadata, but change the original story
301791d3 279 MetaData key = story.getMeta().clone();
a7d266e6 280 story.setMeta(key);
08fe2e33 281
2206ef66
NR
282 if (luid == null || luid.isEmpty()) {
283 getStories(); // refresh lastId if needed
284 key.setLuid(String.format("%03d", (++lastId)));
285 } else {
286 key.setLuid(luid);
287 }
288
08fe2e33
NR
289 getDir(key).mkdirs();
290 if (!getDir(key).exists()) {
291 throw new IOException("Cannot create library dir");
292 }
293
294 OutputType out;
08fe2e33 295 if (key != null && key.isImageDocument()) {
2206ef66 296 out = image;
08fe2e33 297 } else {
2206ef66 298 out = text;
08fe2e33 299 }
2206ef66 300
08fe2e33 301 BasicOutput it = BasicOutput.getOutput(out, true);
10d558d2
NR
302 it.process(story, getFile(key).getPath(), pg);
303
304 // empty cache
305 stories.clear();
2206ef66
NR
306
307 return story;
08fe2e33
NR
308 }
309
10d558d2
NR
310 /**
311 * Delete the given {@link Story} from this {@link Library}.
312 *
313 * @param luid
314 * the LUID of the target {@link Story}
315 *
316 * @return TRUE if it was deleted
317 */
318 public synchronized boolean delete(String luid) {
319 boolean ok = false;
320
321 MetaData meta = getInfo(luid);
322 File file = getStories().get(meta);
323
324 if (file != null) {
325 if (file.delete()) {
9843a5e5
NR
326 String readerExt = getOutputType(meta)
327 .getDefaultExtension(true);
328 String fileExt = getOutputType(meta).getDefaultExtension(false);
10d558d2
NR
329
330 String path = file.getAbsolutePath();
9843a5e5
NR
331 if (readerExt != null && !readerExt.equals(fileExt)) {
332 path = path
333 .substring(0, path.length() - readerExt.length())
334 + fileExt;
335 file = new File(path);
336 IOUtils.deltree(file);
337 }
338
10d558d2
NR
339 File infoFile = new File(path + ".info");
340 if (!infoFile.exists()) {
341 infoFile = new File(path.substring(0, path.length()
9843a5e5 342 - fileExt.length())
10d558d2
NR
343 + ".info");
344 }
345 infoFile.delete();
346
347 String coverExt = "."
348 + Instance.getConfig().getString(
349 Config.IMAGE_FORMAT_COVER);
350 File coverFile = new File(path + coverExt);
351 if (!coverFile.exists()) {
352 coverFile = new File(path.substring(0, path.length()
9843a5e5 353 - fileExt.length()));
10d558d2
NR
354 }
355 coverFile.delete();
356
357 ok = true;
358 }
359
360 // clear cache
361 stories.clear();
362 }
363
364 return ok;
365 }
366
08fe2e33
NR
367 /**
368 * The directory (full path) where the {@link Story} related to this
369 * {@link MetaData} should be located on disk.
370 *
371 * @param key
372 * the {@link Story} {@link MetaData}
373 *
374 * @return the target directory
375 */
376 private File getDir(MetaData key) {
377 String source = key.getSource().replaceAll("[^a-zA-Z0-9._+-]", "_");
378 return new File(baseDir, source);
379 }
380
381 /**
382 * The target (full path) where the {@link Story} related to this
383 * {@link MetaData} should be located on disk.
384 *
385 * @param key
386 * the {@link Story} {@link MetaData}
387 *
388 * @return the target
389 */
390 private File getFile(MetaData key) {
391 String title = key.getTitle().replaceAll("[^a-zA-Z0-9._+-]", "_");
392 return new File(getDir(key), key.getLuid() + "_" + title);
393 }
394
395 /**
396 * Return all the known stories in this {@link Library} object.
397 *
398 * @return the stories
399 */
10d558d2 400 private synchronized Map<MetaData, File> getStories() {
08fe2e33
NR
401 if (stories.isEmpty()) {
402 lastId = 0;
68686a37 403
2206ef66 404 String ext = ".info";
08fe2e33
NR
405 for (File dir : baseDir.listFiles()) {
406 if (dir.isDirectory()) {
407 for (File file : dir.listFiles()) {
408 try {
2206ef66 409 if (file.getPath().toLowerCase().endsWith(ext)) {
68686a37
NR
410 MetaData meta = InfoReader.readMeta(file);
411 try {
412 int id = Integer.parseInt(meta.getLuid());
413 if (id > lastId) {
414 lastId = id;
08fe2e33 415 }
68686a37 416
2206ef66
NR
417 // Replace .info with whatever is needed:
418 String path = file.getPath();
419 path = path.substring(0, path.length()
420 - ext.length());
421
422 String newExt = getOutputType(meta)
10d558d2 423 .getDefaultExtension(true);
2206ef66
NR
424
425 file = new File(path + newExt);
426 //
427
68686a37
NR
428 stories.put(meta, file);
429
430 } catch (Exception e) {
08fe2e33
NR
431 // not normal!!
432 Instance.syserr(new IOException(
68686a37
NR
433 "Cannot understand the LUID of "
434 + file.getPath() + ": "
435 + meta.getLuid(), e));
08fe2e33
NR
436 }
437 }
438 } catch (IOException e) {
439 // We should not have not-supported files in the
440 // library
441 Instance.syserr(new IOException(
442 "Cannot load file from library: "
443 + file.getPath(), e));
444 }
445 }
446 }
447 }
448 }
449
450 return stories;
451 }
2206ef66
NR
452
453 /**
454 * Return the {@link OutputType} for this {@link Story}.
455 *
456 * @param meta
457 * the {@link Story} {@link MetaData}
458 *
459 * @return the type
460 */
461 private OutputType getOutputType(MetaData meta) {
462 if (meta != null && meta.isImageDocument()) {
463 return image;
464 } else {
465 return text;
466 }
467 }
08fe2e33 468}