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