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