CLI search, step 1
[fanfix.git] / src / be / nikiroo / fanfix / reader / BasicReader.java
CommitLineData
89cb07a6
NR
1package be.nikiroo.fanfix.reader;
2
3b2b638f 3import java.io.File;
89cb07a6 4import java.io.IOException;
3b2b638f 5import java.net.MalformedURLException;
89cb07a6 6import java.net.URL;
df6e2d88
NR
7import java.text.ParseException;
8import java.text.SimpleDateFormat;
df6e2d88 9import java.util.Date;
a6c830bb
NR
10import java.util.Map;
11import java.util.TreeMap;
89cb07a6
NR
12
13import be.nikiroo.fanfix.Instance;
d0114000 14import be.nikiroo.fanfix.bundles.Config;
c1873e56
NR
15import be.nikiroo.fanfix.bundles.UiConfig;
16import be.nikiroo.fanfix.data.MetaData;
89cb07a6 17import be.nikiroo.fanfix.data.Story;
e42573a0
NR
18import be.nikiroo.fanfix.library.BasicLibrary;
19import be.nikiroo.fanfix.library.LocalLibrary;
89cb07a6 20import be.nikiroo.fanfix.supported.BasicSupport;
3b2b638f 21import be.nikiroo.utils.Progress;
df6e2d88 22import be.nikiroo.utils.StringUtils;
9119671d 23import be.nikiroo.utils.serial.SerialUtils;
89cb07a6
NR
24
25/**
dd56a893 26 * The class that handles the different {@link Story} readers you can use.
89cb07a6 27 * <p>
dd56a893 28 * All the readers should be accessed via {@link BasicReader#getReader()}.
89cb07a6
NR
29 *
30 * @author niki
31 */
e42573a0 32public abstract class BasicReader implements Reader {
68e2c6d2 33 private static BasicLibrary defaultLibrary = Instance.getLibrary();
c1873e56 34 private static ReaderType defaultType = ReaderType.GUI;
b0e88ebd 35
68e2c6d2 36 private BasicLibrary lib;
bc2ea776 37 private MetaData meta;
89cb07a6 38 private Story story;
bc2ea776 39 private int chapter;
3727aae2 40
d0114000
NR
41 /**
42 * Take the default reader type configuration from the config file.
43 */
3727aae2 44 static {
d0114000
NR
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 }
3727aae2
NR
54 }
55
211f7ddb 56 @Override
bc2ea776
NR
57 public synchronized Story getStory(Progress pg) {
58 if (story == null) {
59 story = getLibrary().getStory(meta.getLuid(), pg);
60 }
61
89cb07a6
NR
62 return story;
63 }
64
211f7ddb 65 @Override
68e2c6d2 66 public BasicLibrary getLibrary() {
b0e88ebd
NR
67 if (lib == null) {
68 lib = defaultLibrary;
69 }
70
71 return lib;
72 }
73
211f7ddb 74 @Override
bc2ea776 75 public void setLibrary(BasicLibrary lib) {
b0e88ebd
NR
76 this.lib = lib;
77 }
78
211f7ddb 79 @Override
9fe3f177 80 public synchronized MetaData getMeta() {
bc2ea776
NR
81 return meta;
82 }
83
211f7ddb 84 @Override
bc2ea776
NR
85 public synchronized void setMeta(MetaData meta) throws IOException {
86 setMeta(meta == null ? null : meta.getLuid()); // must check the library
87 }
88
211f7ddb 89 @Override
bc2ea776
NR
90 public synchronized void setMeta(String luid) throws IOException {
91 story = null;
92 meta = getLibrary().getInfo(luid);
93
94 if (meta == null) {
92fb0719 95 throw new IOException("Cannot retrieve story from library: " + luid);
89cb07a6
NR
96 }
97 }
98
211f7ddb 99 @Override
350bc060 100 public synchronized void setMeta(URL url, Progress pg) throws IOException {
e0fb1417 101 BasicSupport support = BasicSupport.getSupport(url);
89cb07a6 102 if (support == null) {
e0fb1417 103 throw new IOException("URL not supported: " + url.toString());
89cb07a6
NR
104 }
105
0ffa4754 106 story = support.process(pg);
89cb07a6
NR
107 if (story == null) {
108 throw new IOException(
109 "Cannot retrieve story from external source: "
e0fb1417 110 + url.toString());
89cb07a6 111 }
bc2ea776
NR
112
113 meta = story.getMeta();
114 }
115
211f7ddb 116 @Override
bc2ea776
NR
117 public int getChapter() {
118 return chapter;
89cb07a6
NR
119 }
120
211f7ddb 121 @Override
bc2ea776
NR
122 public void setChapter(int chapter) {
123 this.chapter = chapter;
6322ab64
NR
124 }
125
3727aae2 126 /**
d0114000
NR
127 * Return a new {@link BasicReader} ready for use if one is configured.
128 * <p>
129 * Can return NULL if none are configured.
3727aae2 130 *
d0114000 131 * @return a {@link BasicReader}, or NULL if none configured
3727aae2 132 */
e42573a0 133 public static Reader getReader() {
333f0e7b
NR
134 try {
135 if (defaultType != null) {
e42573a0
NR
136 return (Reader) SerialUtils.createObject(defaultType
137 .getTypeName());
d0114000 138 }
9119671d 139 } catch (Exception e) {
16a81ef7
NR
140 Instance.getTraceHandler().error(
141 new Exception("Cannot create a reader of type: "
142 + defaultType + " (Not compiled in?)", e));
3727aae2
NR
143 }
144
145 return null;
146 }
147
148 /**
bc2ea776 149 * The default {@link Reader.ReaderType} used when calling
3727aae2
NR
150 * {@link BasicReader#getReader()}.
151 *
152 * @return the default type
153 */
154 public static ReaderType getDefaultReaderType() {
155 return defaultType;
156 }
157
158 /**
bc2ea776 159 * The default {@link Reader.ReaderType} used when calling
3727aae2
NR
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 }
3b2b638f 168
b0e88ebd 169 /**
68e2c6d2
NR
170 * Change the default {@link LocalLibrary} to open with the
171 * {@link BasicReader}s.
b0e88ebd
NR
172 *
173 * @param lib
68e2c6d2 174 * the new {@link LocalLibrary}
b0e88ebd 175 */
68e2c6d2 176 public static void setDefaultLibrary(BasicLibrary lib) {
b0e88ebd
NR
177 BasicReader.defaultLibrary = lib;
178 }
179
3b2b638f
NR
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 }
c1873e56 207
df6e2d88
NR
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 */
a6c830bb
NR
217 public static Map<String, String> getMetaDesc(MetaData meta) {
218 Map<String, String> metaDesc = new TreeMap<String, String>();
df6e2d88
NR
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
a6c830bb
NR
230 metaDesc.put("Author", meta.getAuthor());
231 metaDesc.put("Publication date", formatDate(meta.getDate()));
232 metaDesc.put("Published on", meta.getPublisher());
233 metaDesc.put("URL", meta.getUrl());
a98e49ee
NR
234 if (meta.isImageDocument()) {
235 metaDesc.put("Number of images", format(meta.getWords()));
236 } else {
237 metaDesc.put("Number of words", format(meta.getWords()));
238 }
a6c830bb
NR
239 metaDesc.put("Source", meta.getSource());
240 metaDesc.put("Subject", meta.getSubject());
241 metaDesc.put("Language", meta.getLang());
242 metaDesc.put("Tags", tags.toString());
df6e2d88
NR
243
244 return metaDesc;
245 }
246
5dd985cf
NR
247 /**
248 * Open the {@link Story} with an external reader (the program will be
249 * passed the main file associated with this {@link Story}).
250 *
251 * @param lib
252 * the {@link BasicLibrary} to select the {@link Story} from
253 * @param luid
254 * the {@link Story} LUID
350bc060
NR
255 * @param sync
256 * execute the process synchronously (wait until it is terminated
257 * before returning)
5dd985cf
NR
258 *
259 * @throws IOException
260 * in case of I/O error
261 */
16a81ef7 262 @Override
350bc060
NR
263 public void openExternal(BasicLibrary lib, String luid, boolean sync)
264 throws IOException {
b0e88ebd 265 MetaData meta = lib.getInfo(luid);
ff05b828 266 File target = lib.getFile(luid, null);
c1873e56 267
350bc060 268 openExternal(meta, target, sync);
c1873e56
NR
269 }
270
5dd985cf
NR
271 /**
272 * Open the {@link Story} with an external reader (the program will be
273 * passed the given target file).
274 *
275 * @param meta
276 * the {@link Story} to load
277 * @param target
278 * the target {@link File}
350bc060
NR
279 * @param sync
280 * execute the process synchronously (wait until it is terminated
281 * before returning)
5dd985cf
NR
282 *
283 * @throws IOException
284 * in case of I/O error
285 */
350bc060
NR
286 protected void openExternal(MetaData meta, File target, boolean sync)
287 throws IOException {
c1873e56
NR
288 String program = null;
289 if (meta.isImageDocument()) {
290 program = Instance.getUiConfig().getString(
291 UiConfig.IMAGES_DOCUMENT_READER);
292 } else {
293 program = Instance.getUiConfig().getString(
294 UiConfig.NON_IMAGES_DOCUMENT_READER);
295 }
296
297 if (program != null && program.trim().isEmpty()) {
298 program = null;
299 }
300
350bc060 301 start(target, program, sync);
16a81ef7 302 }
c1873e56 303
16a81ef7
NR
304 /**
305 * Start a file and open it with the given program if given or the first
306 * default system starter we can find.
307 *
308 * @param target
309 * the target to open
310 * @param program
311 * the program to use or NULL for the default system starter
350bc060
NR
312 * @param sync
313 * execute the process synchronously (wait until it is terminated
314 * before returning)
16a81ef7
NR
315 *
316 * @throws IOException
317 * in case of I/O error
318 */
350bc060
NR
319 protected void start(File target, String program, boolean sync)
320 throws IOException {
321
322 Process proc = null;
16a81ef7
NR
323 if (program == null) {
324 boolean ok = false;
b4f9071c
NR
325 for (String starter : new String[] { "xdg-open", "open", "see",
326 "start", "run" }) {
16a81ef7 327 try {
9e2fad36
NR
328 Instance.getTraceHandler().trace(
329 "starting external program");
350bc060 330 proc = Runtime.getRuntime().exec(
16a81ef7
NR
331 new String[] { starter, target.getAbsolutePath() });
332 ok = true;
333 break;
334 } catch (IOException e) {
335 }
336 }
337 if (!ok) {
338 throw new IOException("Cannot find a program to start the file");
c1873e56
NR
339 }
340 } else {
9e2fad36 341 Instance.getTraceHandler().trace("starting external program");
350bc060 342 proc = Runtime.getRuntime().exec(
c1873e56
NR
343 new String[] { program, target.getAbsolutePath() });
344 }
350bc060
NR
345
346 if (proc != null && sync) {
9ce8d884
N
347 try {
348 proc.waitFor();
349 } catch (InterruptedException e) {
350bc060
NR
350 }
351 }
c1873e56 352 }
df6e2d88 353
91b82a5c
NR
354 /**
355 * @deprecated use StringUtils when updated
356 */
357 @Deprecated
df6e2d88 358 static private String format(long value) {
91b82a5c
NR
359 //TODO: use StringUtils
360
df6e2d88 361 String display = "";
a98e49ee
NR
362 String suffix = "";
363
364 if (value > 4000) {
365 value = value / 1000;
366 suffix = "k";
367 }
df6e2d88
NR
368
369 while (value > 0) {
370 if (!display.isEmpty()) {
371 display = "." + display;
372 }
373 display = (value % 1000) + display;
374 value = value / 1000;
375 }
376
a98e49ee 377 return display + suffix;
df6e2d88
NR
378 }
379
380 static private String formatDate(String date) {
381 long ms = 0;
382
383 try {
384 ms = StringUtils.toTime(date);
385 } catch (ParseException e) {
386 }
387
388 if (ms <= 0) {
389 SimpleDateFormat sdf = new SimpleDateFormat(
a4a94451 390 "yyyy-MM-dd'T'HH:mm:ssSSS");
df6e2d88
NR
391 try {
392 ms = sdf.parse(date).getTime();
393 } catch (ParseException e) {
394 }
395 }
396
397 if (ms > 0) {
398 SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
399 return sdf.format(new Date(ms));
400 }
401
402 // :(
403 return date;
404 }
89cb07a6 405}