Partial HTML implementation, partial GUI support
[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 Story story = getStory(luid);
198 if (story == null) {
199 throw new IOException("Cannot find story to export: " + luid);
200 }
201
202 return out.process(getStory(luid), target);
203 }
204
205 /**
206 * Save a {@link Story} to the {@link Library}.
207 *
208 * @param story
209 * the {@link Story} to save
210 *
211 * @return the same {@link Story}, whose LUID may have changed
212 *
213 * @throws IOException
214 * in case of I/O error
215 */
216 public Story save(Story story) throws IOException {
217 return save(story, null);
218 }
219
220 /**
221 * Save a {@link Story} to the {@link Library} -- the LUID <b>must</b> be
222 * correct, or NULL to get the next free one.
223 *
224 * @param story
225 * the {@link Story} to save
226 * @param luid
227 * the <b>correct</b> LUID or NULL to get the next free one
228 *
229 * @return the same {@link Story}, whose LUID may have changed
230 *
231 * @throws IOException
232 * in case of I/O error
233 */
234 private Story save(Story story, String luid) throws IOException {
235 // Do not change the original metadata, but change the original story
236 MetaData key = story.getMeta().clone();
237 story.setMeta(key);
238
239 if (luid == null || luid.isEmpty()) {
240 getStories(); // refresh lastId if needed
241 key.setLuid(String.format("%03d", (++lastId)));
242 } else {
243 key.setLuid(luid);
244 }
245
246 getDir(key).mkdirs();
247 if (!getDir(key).exists()) {
248 throw new IOException("Cannot create library dir");
249 }
250
251 OutputType out;
252 if (key != null && key.isImageDocument()) {
253 out = image;
254 } else {
255 out = text;
256 }
257
258 BasicOutput it = BasicOutput.getOutput(out, true);
259 File file = it.process(story, getFile(key).getPath());
260 getStories().put(story.getMeta(), file);
261
262 return story;
263 }
264
265 /**
266 * The directory (full path) where the {@link Story} related to this
267 * {@link MetaData} should be located on disk.
268 *
269 * @param key
270 * the {@link Story} {@link MetaData}
271 *
272 * @return the target directory
273 */
274 private File getDir(MetaData key) {
275 String source = key.getSource().replaceAll("[^a-zA-Z0-9._+-]", "_");
276 return new File(baseDir, source);
277 }
278
279 /**
280 * The target (full path) where the {@link Story} related to this
281 * {@link MetaData} should be located on disk.
282 *
283 * @param key
284 * the {@link Story} {@link MetaData}
285 *
286 * @return the target
287 */
288 private File getFile(MetaData key) {
289 String title = key.getTitle().replaceAll("[^a-zA-Z0-9._+-]", "_");
290 return new File(getDir(key), key.getLuid() + "_" + title);
291 }
292
293 /**
294 * Return all the known stories in this {@link Library} object.
295 *
296 * @return the stories
297 */
298 private Map<MetaData, File> getStories() {
299 if (stories.isEmpty()) {
300 lastId = 0;
301
302 String ext = ".info";
303 for (File dir : baseDir.listFiles()) {
304 if (dir.isDirectory()) {
305 for (File file : dir.listFiles()) {
306 try {
307 if (file.getPath().toLowerCase().endsWith(ext)) {
308 MetaData meta = InfoReader.readMeta(file);
309 try {
310 int id = Integer.parseInt(meta.getLuid());
311 if (id > lastId) {
312 lastId = id;
313 }
314
315 // Replace .info with whatever is needed:
316 String path = file.getPath();
317 path = path.substring(0, path.length()
318 - ext.length());
319
320 String newExt = getOutputType(meta)
321 .getDefaultExtension();
322
323 file = new File(path + newExt);
324 //
325
326 stories.put(meta, file);
327
328 } catch (Exception e) {
329 // not normal!!
330 Instance.syserr(new IOException(
331 "Cannot understand the LUID of "
332 + file.getPath() + ": "
333 + meta.getLuid(), e));
334 }
335 }
336 } catch (IOException e) {
337 // We should not have not-supported files in the
338 // library
339 Instance.syserr(new IOException(
340 "Cannot load file from library: "
341 + file.getPath(), e));
342 }
343 }
344 }
345 }
346 }
347
348 return stories;
349 }
350
351 /**
352 * Return the {@link OutputType} for this {@link Story}.
353 *
354 * @param meta
355 * the {@link Story} {@link MetaData}
356 *
357 * @return the type
358 */
359 private OutputType getOutputType(MetaData meta) {
360 if (meta != null && meta.isImageDocument()) {
361 return image;
362 } else {
363 return text;
364 }
365 }
366 }