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