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