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