584bc7cb3b8f8c64a4586fe5dba4ebbe17935fc0
[fanfix.git] / src / be / nikiroo / fanfix / reader / BasicReader.java
1 package be.nikiroo.fanfix.reader;
2
3 import java.io.File;
4 import java.io.IOException;
5 import java.net.MalformedURLException;
6 import java.net.URL;
7 import java.text.ParseException;
8 import java.text.SimpleDateFormat;
9 import java.util.Date;
10 import java.util.Map;
11 import java.util.TreeMap;
12
13 import be.nikiroo.fanfix.Instance;
14 import be.nikiroo.fanfix.bundles.Config;
15 import be.nikiroo.fanfix.bundles.UiConfig;
16 import be.nikiroo.fanfix.data.MetaData;
17 import be.nikiroo.fanfix.data.Story;
18 import be.nikiroo.fanfix.library.BasicLibrary;
19 import be.nikiroo.fanfix.library.LocalLibrary;
20 import be.nikiroo.fanfix.supported.BasicSupport;
21 import be.nikiroo.utils.Progress;
22 import be.nikiroo.utils.StringUtils;
23 import be.nikiroo.utils.serial.SerialUtils;
24
25 /**
26 * The class that handles the different {@link Story} readers you can use.
27 * <p>
28 * All the readers should be accessed via {@link BasicReader#getReader()}.
29 *
30 * @author niki
31 */
32 public abstract class BasicReader implements Reader {
33 private static BasicLibrary defaultLibrary = Instance.getLibrary();
34 private static ReaderType defaultType = ReaderType.GUI;
35
36 private BasicLibrary lib;
37 private MetaData meta;
38 private Story story;
39 private int chapter;
40
41 /**
42 * Take the default reader type configuration from the config file.
43 */
44 static {
45 String typeString = Instance.getConfig().getString(Config.READER_TYPE);
46 if (typeString != null && !typeString.isEmpty()) {
47 try {
48 ReaderType type = ReaderType.valueOf(typeString.toUpperCase());
49 defaultType = type;
50 } catch (IllegalArgumentException e) {
51 // Do nothing
52 }
53 }
54 }
55
56 @Override
57 public synchronized Story getStory(Progress pg) {
58 if (story == null) {
59 story = getLibrary().getStory(meta.getLuid(), pg);
60 }
61
62 return story;
63 }
64
65 @Override
66 public BasicLibrary getLibrary() {
67 if (lib == null) {
68 lib = defaultLibrary;
69 }
70
71 return lib;
72 }
73
74 @Override
75 public void setLibrary(BasicLibrary lib) {
76 this.lib = lib;
77 }
78
79 @Override
80 public synchronized MetaData getMeta() {
81 return meta;
82 }
83
84 @Override
85 public synchronized void setMeta(MetaData meta) throws IOException {
86 setMeta(meta == null ? null : meta.getLuid()); // must check the library
87 }
88
89 @Override
90 public synchronized void setMeta(String luid) throws IOException {
91 story = null;
92 meta = getLibrary().getInfo(luid);
93
94 if (meta == null) {
95 throw new IOException("Cannot retrieve story from library: " + luid);
96 }
97 }
98
99 @Override
100 public synchronized void setMeta(URL url, Progress pg) throws IOException {
101 BasicSupport support = BasicSupport.getSupport(url);
102 if (support == null) {
103 throw new IOException("URL not supported: " + url.toString());
104 }
105
106 story = support.process(pg);
107 if (story == null) {
108 throw new IOException(
109 "Cannot retrieve story from external source: "
110 + url.toString());
111 }
112
113 meta = story.getMeta();
114 }
115
116 @Override
117 public int getChapter() {
118 return chapter;
119 }
120
121 @Override
122 public void setChapter(int chapter) {
123 this.chapter = chapter;
124 }
125
126 /**
127 * Return a new {@link BasicReader} ready for use if one is configured.
128 * <p>
129 * Can return NULL if none are configured.
130 *
131 * @return a {@link BasicReader}, or NULL if none configured
132 */
133 public static Reader getReader() {
134 try {
135 if (defaultType != null) {
136 return (Reader) SerialUtils.createObject(defaultType
137 .getTypeName());
138 }
139 } catch (Exception e) {
140 Instance.getTraceHandler().error(
141 new Exception("Cannot create a reader of type: "
142 + defaultType + " (Not compiled in?)", e));
143 }
144
145 return null;
146 }
147
148 /**
149 * The default {@link Reader.ReaderType} used when calling
150 * {@link BasicReader#getReader()}.
151 *
152 * @return the default type
153 */
154 public static ReaderType getDefaultReaderType() {
155 return defaultType;
156 }
157
158 /**
159 * The default {@link Reader.ReaderType} used when calling
160 * {@link BasicReader#getReader()}.
161 *
162 * @param defaultType
163 * the new default type
164 */
165 public static void setDefaultReaderType(ReaderType defaultType) {
166 BasicReader.defaultType = defaultType;
167 }
168
169 /**
170 * Change the default {@link LocalLibrary} to open with the
171 * {@link BasicReader}s.
172 *
173 * @param lib
174 * the new {@link LocalLibrary}
175 */
176 public static void setDefaultLibrary(BasicLibrary lib) {
177 BasicReader.defaultLibrary = lib;
178 }
179
180 /**
181 * Return an {@link URL} from this {@link String}, be it a file path or an
182 * actual {@link URL}.
183 *
184 * @param sourceString
185 * the source
186 *
187 * @return the corresponding {@link URL}
188 *
189 * @throws MalformedURLException
190 * if this is neither a file nor a conventional {@link URL}
191 */
192 public static URL getUrl(String sourceString) throws MalformedURLException {
193 if (sourceString == null || sourceString.isEmpty()) {
194 throw new MalformedURLException("Empty url");
195 }
196
197 URL source = null;
198 try {
199 source = new URL(sourceString);
200 } catch (MalformedURLException e) {
201 File sourceFile = new File(sourceString);
202 source = sourceFile.toURI().toURL();
203 }
204
205 return source;
206 }
207
208 /**
209 * Describe a {@link Story} from its {@link MetaData} and return a list of
210 * title/value that represent this {@link Story}.
211 *
212 * @param meta
213 * the {@link MetaData} to represent
214 *
215 * @return the information
216 */
217 public static Map<String, String> getMetaDesc(MetaData meta) {
218 Map<String, String> metaDesc = new TreeMap<String, String>();
219
220 // TODO: i18n
221
222 StringBuilder tags = new StringBuilder();
223 for (String tag : meta.getTags()) {
224 if (tags.length() > 0) {
225 tags.append(", ");
226 }
227 tags.append(tag);
228 }
229
230 // TODO: i18n
231 metaDesc.put("Author", meta.getAuthor());
232 metaDesc.put("Publication date", formatDate(meta.getDate()));
233 metaDesc.put("Published on", meta.getPublisher());
234 metaDesc.put("URL", meta.getUrl());
235 String count = "";
236 if (meta.getWords() > 0) {
237 count = StringUtils.formatNumber(meta.getWords());
238 }
239 if (meta.isImageDocument()) {
240 metaDesc.put("Number of images", count);
241 } else {
242 metaDesc.put("Number of words", count);
243 }
244 metaDesc.put("Source", meta.getSource());
245 metaDesc.put("Subject", meta.getSubject());
246 metaDesc.put("Language", meta.getLang());
247 metaDesc.put("Tags", tags.toString());
248
249 return metaDesc;
250 }
251
252 /**
253 * Open the {@link Story} with an external reader (the program will be
254 * passed the main file associated with this {@link Story}).
255 *
256 * @param lib
257 * the {@link BasicLibrary} to select the {@link Story} from
258 * @param luid
259 * the {@link Story} LUID
260 * @param sync
261 * execute the process synchronously (wait until it is terminated
262 * before returning)
263 *
264 * @throws IOException
265 * in case of I/O error
266 */
267 @Override
268 public void openExternal(BasicLibrary lib, String luid, boolean sync)
269 throws IOException {
270 MetaData meta = lib.getInfo(luid);
271 File target = lib.getFile(luid, null);
272
273 openExternal(meta, target, sync);
274 }
275
276 /**
277 * Open the {@link Story} with an external reader (the program will be
278 * passed the given target file).
279 *
280 * @param meta
281 * the {@link Story} to load
282 * @param target
283 * the target {@link File}
284 * @param sync
285 * execute the process synchronously (wait until it is terminated
286 * before returning)
287 *
288 * @throws IOException
289 * in case of I/O error
290 */
291 protected void openExternal(MetaData meta, File target, boolean sync)
292 throws IOException {
293 String program = null;
294 if (meta.isImageDocument()) {
295 program = Instance.getUiConfig().getString(
296 UiConfig.IMAGES_DOCUMENT_READER);
297 } else {
298 program = Instance.getUiConfig().getString(
299 UiConfig.NON_IMAGES_DOCUMENT_READER);
300 }
301
302 if (program != null && program.trim().isEmpty()) {
303 program = null;
304 }
305
306 start(target, program, sync);
307 }
308
309 /**
310 * Start a file and open it with the given program if given or the first
311 * default system starter we can find.
312 *
313 * @param target
314 * the target to open
315 * @param program
316 * the program to use or NULL for the default system starter
317 * @param sync
318 * execute the process synchronously (wait until it is terminated
319 * before returning)
320 *
321 * @throws IOException
322 * in case of I/O error
323 */
324 protected void start(File target, String program, boolean sync)
325 throws IOException {
326
327 Process proc = null;
328 if (program == null) {
329 boolean ok = false;
330 for (String starter : new String[] { "xdg-open", "open", "see",
331 "start", "run" }) {
332 try {
333 Instance.getTraceHandler().trace(
334 "starting external program");
335 proc = Runtime.getRuntime().exec(
336 new String[] { starter, target.getAbsolutePath() });
337 ok = true;
338 break;
339 } catch (IOException e) {
340 }
341 }
342 if (!ok) {
343 throw new IOException("Cannot find a program to start the file");
344 }
345 } else {
346 Instance.getTraceHandler().trace("starting external program");
347 proc = Runtime.getRuntime().exec(
348 new String[] { program, target.getAbsolutePath() });
349 }
350
351 if (proc != null && sync) {
352 try {
353 proc.waitFor();
354 } catch (InterruptedException e) {
355 }
356 }
357 }
358
359 static private String formatDate(String date) {
360 long ms = 0;
361
362 if (date != null && !date.isEmpty()) {
363 try {
364 ms = StringUtils.toTime(date);
365 } catch (ParseException e) {
366 }
367
368 if (ms <= 0) {
369 SimpleDateFormat sdf = new SimpleDateFormat(
370 "yyyy-MM-dd'T'HH:mm:ssSSS");
371 try {
372 ms = sdf.parse(date).getTime();
373 } catch (ParseException e) {
374 }
375 }
376
377 if (ms > 0) {
378 SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
379 return sdf.format(new Date(ms));
380 }
381 }
382
383 if (date == null) {
384 date = "";
385 }
386
387 // :(
388 return date;
389 }
390 }