Merge commit '9e7330d793887fe9ee378ca1413141d7761e76ca'
[nikiroo-utils.git] / src / be / nikiroo / fanfix / supported / InfoReader.java
CommitLineData
68686a37
NR
1package be.nikiroo.fanfix.supported;
2
3import java.io.File;
68686a37
NR
4import java.io.FileNotFoundException;
5import java.io.IOException;
6import java.io.InputStream;
333f0e7b 7import java.net.URL;
68686a37
NR
8import java.util.ArrayList;
9import java.util.List;
7445f856 10import java.util.Scanner;
68686a37 11
2d2a3222
NR
12import be.nikiroo.fanfix.Instance;
13import be.nikiroo.fanfix.bundles.Config;
34f8780d 14import be.nikiroo.fanfix.data.Chapter;
68686a37 15import be.nikiroo.fanfix.data.MetaData;
34f8780d 16import be.nikiroo.utils.IOUtils;
bb7021f2 17import be.nikiroo.utils.Image;
67837328 18import be.nikiroo.utils.streams.MarkableFileInputStream;
68686a37 19
68686a37 20public class InfoReader {
8d59ce07 21 static protected BasicSupportHelper bsHelper = new BasicSupportHelper();
34f8780d
NR
22 static protected BasicSupportImages bsImages = new BasicSupportImages();
23 static protected BasicSupportPara bsPara = new BasicSupportPara(
24 new BasicSupportHelper(), new BasicSupportImages());
8d59ce07 25
57f02339
NR
26 public static MetaData readMeta(File infoFile, boolean withCover)
27 throws IOException {
68686a37
NR
28 if (infoFile == null) {
29 throw new IOException("File is null");
30 }
34f8780d
NR
31
32 MetaData meta = null;
68686a37
NR
33
34 if (infoFile.exists()) {
67837328 35 InputStream in = new MarkableFileInputStream(infoFile);
68686a37 36 try {
34f8780d 37 meta = createMeta(infoFile.toURI().toURL(), in,
31e27ee3
NR
38 withCover);
39
076caecc
NR
40 // Some old .info files were using UUID for URL...
41 if (!hasIt(meta.getUrl()) && meta.getUuid() != null
42 && (meta.getUuid().startsWith("http://")
43 || meta.getUuid().startsWith("https://"))) {
44 meta.setUrl(meta.getUuid());
45 }
46
31e27ee3
NR
47 // Some old .info files don't have those now required fields...
48 // So we check if we can find the info in another way (many
49 // formats have a copy of the original text file)
50 if (!hasIt(meta.getTitle(), meta.getAuthor(), meta.getDate(),
51 meta.getUrl())) {
31e27ee3
NR
52 String base = infoFile.getPath();
53 if (base.endsWith(".info")) {
54 base = base.substring(0,
55 base.length() - ".info".length());
56 }
57 File textFile = new File(base);
58 if (!textFile.exists()) {
59 textFile = new File(base + ".txt");
60 }
61 if (!textFile.exists()) {
62 textFile = new File(base + ".text");
63 }
64
15da4d0a 65 completeMeta(textFile, meta);
31e27ee3
NR
66 }
67
34f8780d 68
68686a37
NR
69 } finally {
70 in.close();
68686a37 71 }
68686a37 72 }
211f7ddb 73
34f8780d
NR
74 if (meta != null) {
75 try {
76 File summaryFile = new File(infoFile.getAbsolutePath()
77 .replaceFirst("\\.info$", ".summary"));
78 InputStream in = new MarkableFileInputStream(summaryFile);
79 try {
80 String content = IOUtils.readSmallStream(in);
81 Chapter desc = bsPara.makeChapter(null, null, 0,
82 "Description", content, false, null);
83 meta.setResume(desc);
84 } finally {
85 in.close();
86 }
87 } catch (IOException e) {
88 // ignore absent or bad summary
89 }
90
91 return meta;
92 }
93
211f7ddb
NR
94 throw new FileNotFoundException(
95 "File given as argument does not exists: "
96 + infoFile.getAbsolutePath());
68686a37 97 }
15da4d0a
NR
98
99 /**
100 * Complete the given {@link MetaData} with the original text file if needed
101 * and possible.
102 *
103 * @param textFile
104 * the original text file
105 * @param meta
106 * the {@link MetaData} to complete if needed and possible
107 *
108 * @throws IOException
109 * in case of I/O errors
110 */
111 static public void completeMeta(File textFile,
112 MetaData meta) throws IOException {
34f8780d 113 // TODO: not nice, would be better to do it properly...
15da4d0a
NR
114 if (textFile != null && textFile.exists()) {
115 final URL source = textFile.toURI().toURL();
116 final MetaData[] superMetaA = new MetaData[1];
117 @SuppressWarnings("unused")
118 Text unused = new Text() {
119 private boolean loaded = loadDocument();
120
121 @Override
122 public SupportType getType() {
123 return SupportType.TEXT;
124 }
125
126 protected boolean loadDocument() throws IOException {
127 loadDocument(source);
128 superMetaA[0] = getMeta();
129 return true;
130 }
131
132 @Override
133 protected Image getCover(File sourceFile) {
134 return null;
135 }
136 };
137
138 MetaData superMeta = superMetaA[0];
139 if (!hasIt(meta.getTitle())) {
140 meta.setTitle(superMeta.getTitle());
141 }
142 if (!hasIt(meta.getAuthor())) {
143 meta.setAuthor(superMeta.getAuthor());
144 }
145 if (!hasIt(meta.getDate())) {
146 meta.setDate(superMeta.getDate());
147 }
148 if (!hasIt(meta.getUrl())) {
149 meta.setUrl(superMeta.getUrl());
150 }
151 }
152 }
68686a37 153
31e27ee3
NR
154 /**
155 * Check if we have non-empty values for all the given {@link String}s.
156 *
157 * @param values
158 * the values to check
159 *
160 * @return TRUE if none of them was NULL or empty
161 */
162 static private boolean hasIt(String... values) {
163 for (String value : values) {
164 if (value == null || value.trim().isEmpty()) {
165 return false;
166 }
167 }
168
169 return true;
170 }
171
57f02339
NR
172 private static MetaData createMeta(URL sourceInfoFile, InputStream in,
173 boolean withCover) throws IOException {
68686a37
NR
174 MetaData meta = new MetaData();
175
176 meta.setTitle(getInfoTag(in, "TITLE"));
177 meta.setAuthor(getInfoTag(in, "AUTHOR"));
bff19b54 178 meta.setDate(bsHelper.formatDate(getInfoTag(in, "DATE")));
68686a37
NR
179 meta.setTags(getInfoTagList(in, "TAGS", ","));
180 meta.setSource(getInfoTag(in, "SOURCE"));
2206ef66 181 meta.setUrl(getInfoTag(in, "URL"));
68686a37
NR
182 meta.setPublisher(getInfoTag(in, "PUBLISHER"));
183 meta.setUuid(getInfoTag(in, "UUID"));
184 meta.setLuid(getInfoTag(in, "LUID"));
185 meta.setLang(getInfoTag(in, "LANG"));
186 meta.setSubject(getInfoTag(in, "SUBJECT"));
187 meta.setType(getInfoTag(in, "TYPE"));
188 meta.setImageDocument(getInfoTagBoolean(in, "IMAGES_DOCUMENT", false));
57f02339 189 if (withCover) {
16a81ef7
NR
190 String infoTag = getInfoTag(in, "COVER");
191 if (infoTag != null && !infoTag.trim().isEmpty()) {
31e27ee3 192 meta.setCover(bsHelper.getImage(null, sourceInfoFile, infoTag));
16a81ef7 193 }
2d2a3222 194 if (meta.getCover() == null) {
bb7021f2
NR
195 // Second chance: try to check for a cover next to the info file
196 meta.setCover(getCoverByName(sourceInfoFile));
2d2a3222 197 }
57f02339 198 }
793f1071
NR
199 try {
200 meta.setWords(Long.parseLong(getInfoTag(in, "WORDCOUNT")));
201 } catch (NumberFormatException e) {
202 meta.setWords(0);
203 }
bff19b54
NR
204 meta.setCreationDate(
205 bsHelper.formatDate(getInfoTag(in, "CREATION_DATE")));
a9eb3f46 206 meta.setFakeCover(Boolean.parseBoolean(getInfoTag(in, "FAKE_COVER")));
68686a37 207
57f02339 208 if (withCover && meta.getCover() == null) {
8d59ce07 209 meta.setCover(bsHelper.getDefaultCover(meta.getSubject()));
68686a37
NR
210 }
211
212 return meta;
213 }
214
bb7021f2
NR
215 /**
216 * Return the cover image if it is next to the source file.
217 *
218 * @param sourceInfoFile
219 * the source file
220 *
221 * @return the cover if present, NULL if not
222 */
223 public static Image getCoverByName(URL sourceInfoFile) {
30478c5b
NR
224 Image cover = null;
225
226 File basefile = new File(sourceInfoFile.getFile());
227
31e27ee3
NR
228 String ext = "." + Instance.getInstance().getConfig()
229 .getString(Config.FILE_FORMAT_IMAGE_FORMAT_COVER).toLowerCase();
30478c5b
NR
230
231 // Without removing ext
8d59ce07 232 cover = bsHelper.getImage(null, sourceInfoFile,
30478c5b
NR
233 basefile.getAbsolutePath() + ext);
234
235 // Try without ext
236 String name = basefile.getName();
237 int pos = name.lastIndexOf(".");
238 if (cover == null && pos > 0) {
239 name = name.substring(0, pos);
9ac581ab 240 basefile = new File(basefile.getParent(), name);
30478c5b 241
8d59ce07 242 cover = bsHelper.getImage(null, sourceInfoFile,
30478c5b 243 basefile.getAbsolutePath() + ext);
bb7021f2
NR
244 }
245
30478c5b 246 return cover;
bb7021f2
NR
247 }
248
68686a37
NR
249 private static boolean getInfoTagBoolean(InputStream in, String key,
250 boolean def) throws IOException {
251 Boolean value = getInfoTagBoolean(in, key);
252 return value == null ? def : value;
253 }
254
255 private static Boolean getInfoTagBoolean(InputStream in, String key)
256 throws IOException {
257 String value = getInfoTag(in, key);
258 if (value != null && !value.trim().isEmpty()) {
259 value = value.toLowerCase().trim();
260 return value.equals("1") || value.equals("on")
261 || value.equals("true") || value.equals("yes");
262 }
263
264 return null;
265 }
266
267 private static List<String> getInfoTagList(InputStream in, String key,
268 String separator) throws IOException {
269 List<String> list = new ArrayList<String>();
270 String tt = getInfoTag(in, key);
271 if (tt != null) {
272 for (String tag : tt.split(separator)) {
273 list.add(tag.trim());
274 }
275 }
276
277 return list;
278 }
279
280 /**
281 * Return the value of the given tag in the <tt>.info</tt> file if present.
282 *
283 * @param key
284 * the tag key
285 *
286 * @return the value or NULL
287 *
288 * @throws IOException
289 * in case of I/O error
290 */
291 private static String getInfoTag(InputStream in, String key)
292 throws IOException {
293 key = "^" + key + "=";
294
295 if (in != null) {
296 in.reset();
7445f856 297 String value = getLine(in, key, 0);
68686a37
NR
298 if (value != null && !value.isEmpty()) {
299 value = value.trim().substring(key.length() - 1).trim();
70691e84
NR
300 if (value.length() > 1 && //
301 (value.startsWith("'") && value.endsWith("'")
302 || value.startsWith("\"")
303 && value.endsWith("\""))) {
68686a37
NR
304 value = value.substring(1, value.length() - 1).trim();
305 }
306
076caecc 307 // Some old files ended up with TITLE="'xxxxx'"
70691e84
NR
308 if ("^TITLE=".equals(key)) {
309 if (value.startsWith("'") && value.endsWith("'")
310 && value.length() > 1) {
076caecc
NR
311 value = value.substring(1, value.length() - 1).trim();
312 }
313 }
314
68686a37
NR
315 return value;
316 }
317 }
318
319 return null;
320 }
7445f856
NR
321
322 /**
323 * Return the first line from the given input which correspond to the given
324 * selectors.
325 *
326 * @param in
327 * the input
328 * @param needle
329 * a string that must be found inside the target line (also
330 * supports "^" at start to say "only if it starts with" the
331 * needle)
332 * @param relativeLine
333 * the line to return based upon the target line position (-1 =
334 * the line before, 0 = the target line...)
335 *
336 * @return the line
337 */
338 static private String getLine(InputStream in, String needle,
339 int relativeLine) {
340 return getLine(in, needle, relativeLine, true);
341 }
342
343 /**
344 * Return a line from the given input which correspond to the given
345 * selectors.
346 *
347 * @param in
348 * the input
349 * @param needle
350 * a string that must be found inside the target line (also
351 * supports "^" at start to say "only if it starts with" the
352 * needle)
353 * @param relativeLine
354 * the line to return based upon the target line position (-1 =
355 * the line before, 0 = the target line...)
356 * @param first
357 * takes the first result (as opposed to the last one, which will
358 * also always spend the input)
359 *
360 * @return the line
361 */
362 static private String getLine(InputStream in, String needle,
363 int relativeLine, boolean first) {
364 String rep = null;
365
366 List<String> lines = new ArrayList<String>();
367 @SuppressWarnings("resource")
368 Scanner scan = new Scanner(in, "UTF-8");
369 int index = -1;
370 scan.useDelimiter("\\n");
371 while (scan.hasNext()) {
372 lines.add(scan.next());
373
374 if (index == -1) {
375 if (needle.startsWith("^")) {
31e27ee3
NR
376 if (lines.get(lines.size() - 1)
377 .startsWith(needle.substring(1))) {
7445f856
NR
378 index = lines.size() - 1;
379 }
380
381 } else {
382 if (lines.get(lines.size() - 1).contains(needle)) {
383 index = lines.size() - 1;
384 }
385 }
386 }
387
388 if (index >= 0 && index + relativeLine < lines.size()) {
389 rep = lines.get(index + relativeLine);
390 if (first) {
391 break;
392 }
393 }
394 }
395
396 return rep;
397 }
68686a37 398}