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