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