1 package be
.nikiroo
.fanfix
.supported
;
4 import java
.io
.FileNotFoundException
;
5 import java
.io
.IOException
;
6 import java
.io
.InputStream
;
8 import java
.util
.ArrayList
;
10 import java
.util
.Scanner
;
12 import be
.nikiroo
.fanfix
.Instance
;
13 import be
.nikiroo
.fanfix
.bundles
.Config
;
14 import be
.nikiroo
.fanfix
.data
.Chapter
;
15 import be
.nikiroo
.fanfix
.data
.MetaData
;
16 import be
.nikiroo
.utils
.IOUtils
;
17 import be
.nikiroo
.utils
.Image
;
18 import be
.nikiroo
.utils
.streams
.MarkableFileInputStream
;
20 public class InfoReader
{
21 static protected BasicSupportHelper bsHelper
= new BasicSupportHelper();
22 static protected BasicSupportImages bsImages
= new BasicSupportImages();
23 static protected BasicSupportPara bsPara
= new BasicSupportPara(
24 new BasicSupportHelper(), new BasicSupportImages());
26 public static MetaData
readMeta(File infoFile
, boolean withCover
)
28 if (infoFile
== null) {
29 throw new IOException("File is null");
34 if (infoFile
.exists()) {
35 InputStream in
= new MarkableFileInputStream(infoFile
);
37 meta
= createMeta(infoFile
.toURI().toURL(), in
,
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());
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(),
52 String base
= infoFile
.getPath();
53 if (base
.endsWith(".info")) {
54 base
= base
.substring(0,
55 base
.length() - ".info".length());
57 File textFile
= new File(base
);
58 if (!textFile
.exists()) {
59 textFile
= new File(base
+ ".txt");
61 if (!textFile
.exists()) {
62 textFile
= new File(base
+ ".text");
65 completeMeta(textFile
, meta
);
76 File summaryFile
= new File(infoFile
.getAbsolutePath()
77 .replaceFirst("\\.info$", ".summary"));
78 InputStream in
= new MarkableFileInputStream(summaryFile
);
80 String content
= IOUtils
.readSmallStream(in
);
81 Chapter desc
= bsPara
.makeChapter(null, null, 0,
82 "Description", content
, false, null);
87 } catch (IOException e
) {
88 // ignore absent or bad summary
94 throw new FileNotFoundException(
95 "File given as argument does not exists: "
96 + infoFile
.getAbsolutePath());
100 * Complete the given {@link MetaData} with the original text file if needed
104 * the original text file
106 * the {@link MetaData} to complete if needed and possible
108 * @throws IOException
109 * in case of I/O errors
111 static public void completeMeta(File textFile
,
112 MetaData meta
) throws IOException
{
113 // TODO: not nice, would be better to do it properly...
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();
122 public SupportType
getType() {
123 return SupportType
.TEXT
;
126 protected boolean loadDocument() throws IOException
{
127 loadDocument(source
);
128 superMetaA
[0] = getMeta();
133 protected Image
getCover(File sourceFile
) {
138 MetaData superMeta
= superMetaA
[0];
139 if (!hasIt(meta
.getTitle())) {
140 meta
.setTitle(superMeta
.getTitle());
142 if (!hasIt(meta
.getAuthor())) {
143 meta
.setAuthor(superMeta
.getAuthor());
145 if (!hasIt(meta
.getDate())) {
146 meta
.setDate(superMeta
.getDate());
148 if (!hasIt(meta
.getUrl())) {
149 meta
.setUrl(superMeta
.getUrl());
155 * Check if we have non-empty values for all the given {@link String}s.
158 * the values to check
160 * @return TRUE if none of them was NULL or empty
162 static private boolean hasIt(String
... values
) {
163 for (String value
: values
) {
164 if (value
== null || value
.trim().isEmpty()) {
172 private static MetaData
createMeta(URL sourceInfoFile
, InputStream in
,
173 boolean withCover
) throws IOException
{
174 MetaData meta
= new MetaData();
176 meta
.setTitle(getInfoTag(in
, "TITLE"));
177 meta
.setAuthor(getInfoTag(in
, "AUTHOR"));
178 meta
.setDate(bsHelper
.formatDate(getInfoTag(in
, "DATE")));
179 meta
.setTags(getInfoTagList(in
, "TAGS", ","));
180 meta
.setSource(getInfoTag(in
, "SOURCE"));
181 meta
.setUrl(getInfoTag(in
, "URL"));
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));
190 String infoTag
= getInfoTag(in
, "COVER");
191 if (infoTag
!= null && !infoTag
.trim().isEmpty()) {
192 meta
.setCover(bsHelper
.getImage(null, sourceInfoFile
, infoTag
));
194 if (meta
.getCover() == null) {
195 // Second chance: try to check for a cover next to the info file
196 meta
.setCover(getCoverByName(sourceInfoFile
));
200 meta
.setWords(Long
.parseLong(getInfoTag(in
, "WORDCOUNT")));
201 } catch (NumberFormatException e
) {
204 meta
.setCreationDate(
205 bsHelper
.formatDate(getInfoTag(in
, "CREATION_DATE")));
206 meta
.setFakeCover(Boolean
.parseBoolean(getInfoTag(in
, "FAKE_COVER")));
208 if (withCover
&& meta
.getCover() == null) {
209 meta
.setCover(bsHelper
.getDefaultCover(meta
.getSubject()));
216 * Return the cover image if it is next to the source file.
218 * @param sourceInfoFile
221 * @return the cover if present, NULL if not
223 public static Image
getCoverByName(URL sourceInfoFile
) {
226 File basefile
= new File(sourceInfoFile
.getFile());
228 String ext
= "." + Instance
.getInstance().getConfig()
229 .getString(Config
.FILE_FORMAT_IMAGE_FORMAT_COVER
).toLowerCase();
231 // Without removing ext
232 cover
= bsHelper
.getImage(null, sourceInfoFile
,
233 basefile
.getAbsolutePath() + ext
);
236 String name
= basefile
.getName();
237 int pos
= name
.lastIndexOf(".");
238 if (cover
== null && pos
> 0) {
239 name
= name
.substring(0, pos
);
240 basefile
= new File(basefile
.getParent(), name
);
242 cover
= bsHelper
.getImage(null, sourceInfoFile
,
243 basefile
.getAbsolutePath() + ext
);
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
;
255 private static Boolean
getInfoTagBoolean(InputStream in
, String key
)
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");
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
);
272 for (String tag
: tt
.split(separator
)) {
273 list
.add(tag
.trim());
281 * Return the value of the given tag in the <tt>.info</tt> file if present.
286 * @return the value or NULL
288 * @throws IOException
289 * in case of I/O error
291 private static String
getInfoTag(InputStream in
, String key
)
293 key
= "^" + key
+ "=";
297 String value
= getLine(in
, key
, 0);
298 if (value
!= null && !value
.isEmpty()) {
299 value
= value
.trim().substring(key
.length() - 1).trim();
300 if (value
.length() > 1 && //
301 (value
.startsWith("'") && value
.endsWith("'")
302 || value
.startsWith("\"")
303 && value
.endsWith("\""))) {
304 value
= value
.substring(1, value
.length() - 1).trim();
307 // Some old files ended up with TITLE="'xxxxx'"
308 if ("^TITLE=".equals(key
)) {
309 if (value
.startsWith("'") && value
.endsWith("'")
310 && value
.length() > 1) {
311 value
= value
.substring(1, value
.length() - 1).trim();
323 * Return the first line from the given input which correspond to the given
329 * a string that must be found inside the target line (also
330 * supports "^" at start to say "only if it starts with" the
332 * @param relativeLine
333 * the line to return based upon the target line position (-1 =
334 * the line before, 0 = the target line...)
338 static private String
getLine(InputStream in
, String needle
,
340 return getLine(in
, needle
, relativeLine
, true);
344 * Return a line from the given input which correspond to the given
350 * a string that must be found inside the target line (also
351 * supports "^" at start to say "only if it starts with" the
353 * @param relativeLine
354 * the line to return based upon the target line position (-1 =
355 * the line before, 0 = the target line...)
357 * takes the first result (as opposed to the last one, which will
358 * also always spend the input)
362 static private String
getLine(InputStream in
, String needle
,
363 int relativeLine
, boolean first
) {
366 List
<String
> lines
= new ArrayList
<String
>();
367 @SuppressWarnings("resource")
368 Scanner scan
= new Scanner(in
, "UTF-8");
370 scan
.useDelimiter("\\n");
371 while (scan
.hasNext()) {
372 lines
.add(scan
.next());
375 if (needle
.startsWith("^")) {
376 if (lines
.get(lines
.size() - 1)
377 .startsWith(needle
.substring(1))) {
378 index
= lines
.size() - 1;
382 if (lines
.get(lines
.size() - 1).contains(needle
)) {
383 index
= lines
.size() - 1;
388 if (index
>= 0 && index
+ relativeLine
< lines
.size()) {
389 rep
= lines
.get(index
+ relativeLine
);