X-Git-Url: http://git.nikiroo.be/?a=blobdiff_plain;f=src%2Fbe%2Fnikiroo%2Ffanfix%2Fsupported%2FBasicSupport.java;h=900bcf9d5c2f59cb34b6879416b0e52565db769b;hb=55d4513cabcb14396c5500613e94dee93415c7d9;hp=c47d05e608dd43de574aa69cc96ee677168eccee;hpb=92fb0719f84f5b6734b51e528332546d78e9ccec;p=nikiroo-utils.git diff --git a/src/be/nikiroo/fanfix/supported/BasicSupport.java b/src/be/nikiroo/fanfix/supported/BasicSupport.java index c47d05e..900bcf9 100644 --- a/src/be/nikiroo/fanfix/supported/BasicSupport.java +++ b/src/be/nikiroo/fanfix/supported/BasicSupport.java @@ -1,30 +1,30 @@ package be.nikiroo.fanfix.supported; -import java.awt.image.BufferedImage; -import java.io.ByteArrayInputStream; -import java.io.File; import java.io.IOException; import java.io.InputStream; import java.net.MalformedURLException; import java.net.URL; import java.util.ArrayList; +import java.util.Date; import java.util.HashMap; import java.util.List; import java.util.Map; -import java.util.Map.Entry; import java.util.Scanner; +import java.util.Map.Entry; + +import org.json.JSONObject; +import org.jsoup.helper.DataUtil; +import org.jsoup.nodes.Document; +import org.jsoup.nodes.Element; +import org.jsoup.nodes.Node; import be.nikiroo.fanfix.Instance; -import be.nikiroo.fanfix.bundles.Config; import be.nikiroo.fanfix.bundles.StringId; import be.nikiroo.fanfix.data.Chapter; import be.nikiroo.fanfix.data.MetaData; -import be.nikiroo.fanfix.data.Paragraph; -import be.nikiroo.fanfix.data.Paragraph.ParagraphType; import be.nikiroo.fanfix.data.Story; -import be.nikiroo.utils.IOUtils; +import be.nikiroo.utils.Progress; import be.nikiroo.utils.StringUtils; -import be.nikiroo.utils.ui.Progress; /** * This class is the base class used by the other support classes. It can be @@ -36,134 +36,14 @@ import be.nikiroo.utils.ui.Progress; * @author niki */ public abstract class BasicSupport { - /** - * The supported input types for which we can get a {@link BasicSupport} - * object. - * - * @author niki - */ - public enum SupportType { - /** EPUB files created with this program */ - EPUB, - /** Pure text file with some rules */ - TEXT, - /** TEXT but with associated .info file */ - INFO_TEXT, - /** My Little Pony fanfictions */ - FIMFICTION, - /** Fanfictions from a lot of different universes */ - FANFICTION, - /** Website with lots of Mangas */ - MANGAFOX, - /** Furry website with comics support */ - E621, - /** CBZ files */ - CBZ; - - /** - * A description of this support type (more information than the - * {@link BasicSupport#getSourceName()}). - * - * @return the description - */ - public String getDesc() { - String desc = Instance.getTrans().getStringX(StringId.INPUT_DESC, - this.name()); - - if (desc == null) { - desc = Instance.getTrans().getString(StringId.INPUT_DESC, this); - } - - return desc; - } - - /** - * The name of this support type (a short version). - * - * @return the name - */ - public String getSourceName() { - BasicSupport support = BasicSupport.getSupport(this); - if (support != null) { - return support.getSourceName(); - } - - return null; - } - - @Override - public String toString() { - return super.toString().toLowerCase(); - } - - /** - * Call {@link SupportType#valueOf(String.toUpperCase())}. - * - * @param typeName - * the possible type name - * - * @return NULL or the type - */ - public static SupportType valueOfUC(String typeName) { - return SupportType.valueOf(typeName == null ? null : typeName - .toUpperCase()); - } - - /** - * Call {@link SupportType#valueOf(String.toUpperCase())} but return - * NULL for NULL instead of raising exception. - * - * @param typeName - * the possible type name - * - * @return NULL or the type - */ - public static SupportType valueOfNullOkUC(String typeName) { - if (typeName == null) { - return null; - } - - return SupportType.valueOfUC(typeName); - } - - /** - * Call {@link SupportType#valueOf(String.toUpperCase())} but return - * NULL in case of error instead of raising an exception. - * - * @param typeName - * the possible type name - * - * @return NULL or the type - */ - public static SupportType valueOfAllOkUC(String typeName) { - try { - return SupportType.valueOfUC(typeName); - } catch (Exception e) { - return null; - } - } - } - - private InputStream in; + private Document sourceNode; + private URL source; private SupportType type; - private URL currentReferer; // with on 'r', as in 'HTTP'... - - // quote chars - private char openQuote = Instance.getTrans().getChar( - StringId.OPEN_SINGLE_QUOTE); - private char closeQuote = Instance.getTrans().getChar( - StringId.CLOSE_SINGLE_QUOTE); - private char openDoubleQuote = Instance.getTrans().getChar( - StringId.OPEN_DOUBLE_QUOTE); - private char closeDoubleQuote = Instance.getTrans().getChar( - StringId.CLOSE_DOUBLE_QUOTE); - - /** - * The name of this support class. - * - * @return the name - */ - protected abstract String getSourceName(); + private URL currentReferer; // with only one 'r', as in 'HTTP'... + + static protected BasicSupportHelper bsHelper = new BasicSupportHelper(); + static protected BasicSupportImages bsImages = new BasicSupportImages(); + static protected BasicSupportPara bsPara = new BasicSupportPara(new BasicSupportHelper(), new BasicSupportImages()); /** * Check if the given resource is supported by this {@link BasicSupport}. @@ -183,59 +63,60 @@ public abstract class BasicSupport { */ protected abstract boolean isHtml(); - protected abstract MetaData getMeta(URL source, InputStream in) - throws IOException; + /** + * Return the {@link MetaData} of this story. + * + * @return the associated {@link MetaData}, never NULL + * + * @throws IOException + * in case of I/O error + */ + protected abstract MetaData getMeta() throws IOException; /** * Return the story description. * - * @param source - * the source of the story - * @param in - * the input (the main resource) - * * @return the description * * @throws IOException * in case of I/O error */ - protected abstract String getDesc(URL source, InputStream in) - throws IOException; + protected abstract String getDesc() throws IOException; /** * Return the list of chapters (name and resource). + *
+ * Can be NULL if this {@link BasicSupport} do no use chapters.
*
- * @param source
- * the source of the story
- * @param in
- * the input (the main resource)
+ * @param pg
+ * the optional progress reporter
*
- * @return the chapters
+ * @return the chapters or NULL
*
* @throws IOException
* in case of I/O error
*/
- protected abstract List
+ * Can return NULL, in which case you are supposed to work without a source
+ * node.
*
* @param source
- * the source of the story
- * @param in
- * the input (the main resource)
+ * the source {@link URL}
+ *
+ * @return the {@link InputStream}
*
* @throws IOException
- * on I/O error
+ * in case of I/O error
*/
- protected void preprocess(URL source, InputStream in) throws IOException {
+ protected Document loadDocument(URL source) throws IOException {
+ String url = getCanonicalUrl(source).toString();
+ return DataUtil.load(Instance.getInstance().getCache().open(source, this, false), "UTF-8", url.toString());
}
/**
- * Now that we have processed the {@link Story}, close the resources if any.
+ * Log into the support (can be a no-op depending upon the support).
*
* @throws IOException
- * on I/O error
+ * in case of I/O error
+ */
+ protected void login() throws IOException {
+ }
+
+ /**
+ * Now that we have processed the {@link Story}, close the resources if any.
*/
- protected void close() throws IOException {
+ protected void close() {
+ setCurrentReferer(null);
}
/**
- * Create a {@link Chapter} object from the given information, formatting
- * the content as it should be.
+ * Process the given story resource into a partially filled {@link Story}
+ * object containing the name and metadata.
*
- * @param number
- * the chapter number
- * @param name
- * the chapter name
- * @param content
- * the chapter content
+ * @param getDesc
+ * retrieve the description of the story, or not
+ * @param pg
+ * the optional progress reporter
*
- * @return the {@link Chapter}
+ * @return the {@link Story}, never NULL
*
* @throws IOException
* in case of I/O error
*/
- protected Chapter makeChapter(URL source, int number, String name,
- String content) throws IOException {
- // Chapter name: process it correctly, then remove the possible
- // redundant "Chapter x: " in front of it
- String chapterName = processPara(name).getContent().trim();
- for (String lang : Instance.getConfig().getString(Config.CHAPTER)
- .split(",")) {
- String chapterWord = Instance.getConfig().getStringX(
- Config.CHAPTER, lang);
- if (chapterName.startsWith(chapterWord)) {
- chapterName = chapterName.substring(chapterWord.length())
- .trim();
- break;
- }
+ protected Story processMeta(boolean getDesc, Progress pg)
+ throws IOException {
+ if (pg == null) {
+ pg = new Progress();
+ } else {
+ pg.setMinMax(0, 100);
}
- if (chapterName.startsWith(Integer.toString(number))) {
- chapterName = chapterName.substring(
- Integer.toString(number).length()).trim();
- }
+ pg.setProgress(30);
- if (chapterName.startsWith(":")) {
- chapterName = chapterName.substring(1).trim();
+ Story story = new Story();
+ MetaData meta = getMeta();
+ if (meta.getCreationDate() == null || meta.getCreationDate().isEmpty()) {
+ meta.setCreationDate(StringUtils.fromTime(new Date().getTime()));
}
- //
+ story.setMeta(meta);
+ pg.put("meta", meta);
- Chapter chap = new Chapter(number, chapterName);
+ pg.setProgress(50);
- if (content == null) {
- return chap;
+ if (meta.getCover() == null) {
+ meta.setCover(bsHelper.getDefaultCover(meta.getSubject()));
}
- if (isHtml()) {
- // Special
processing:
- content = content.replaceAll("(
]*>)|(
)|(
)",
- "\n* * *\n");
- }
+ pg.setProgress(60);
- InputStream in = new ByteArrayInputStream(content.getBytes("UTF-8"));
- try {
- @SuppressWarnings("resource")
- Scanner scan = new Scanner(in, "UTF-8");
- scan.useDelimiter("(\\n|
+ * Note that this method expects small JSON files (everything is copied into + * memory at least twice). * - * @param source - * the story source - * @param line - * the resource to check + * @param url + * the URL to parse + * @param stable + * TRUE for more stable resources, FALSE when they often change * - * @return the image URL if found, or NULL + * @return the JSON object * + * @throws IOException + * in case of I/O error */ - static URL getImageUrl(BasicSupport support, URL source, String line) { - URL url = null; - - if (line != null) { - // try for files - String path = null; - if (source != null) { - path = new File(source.getFile()).getParent(); - try { - String basePath = new File(new File(path), line.trim()) - .getAbsolutePath(); - for (String ext : getImageExt(true)) { - if (new File(basePath + ext).exists()) { - url = new File(basePath + ext).toURI().toURL(); - } - } - } catch (Exception e) { - // Nothing to do here - } - } - - if (url == null) { - // try for URLs - try { - for (String ext : getImageExt(true)) { - if (Instance.getCache().check(new URL(line + ext))) { - url = new URL(line + ext); - break; - } - } - - // try out of cache - if (url == null) { - for (String ext : getImageExt(true)) { - try { - url = new URL(line + ext); - Instance.getCache().refresh(url, support, true); - break; - } catch (IOException e) { - // no image with this ext - url = null; - } - } - } - } catch (MalformedURLException e) { - // Not an url - } - } - - // refresh the cached file - if (url != null) { - try { - Instance.getCache().refresh(url, support, true); - } catch (IOException e) { - // woops, broken image - url = null; - } - } - } - - return url; - } - - protected InputStream reset(InputStream in) { + protected JSONObject getJson(String url, boolean stable) + throws IOException { try { - in.reset(); - } catch (IOException e) { + return getJson(new URL(url), stable); + } catch (MalformedURLException e) { + throw new IOException("Malformed URL: " + url, e); } - return in; } /** - * Reset then return {@link BasicSupport#in}. + * Utility method to convert the given URL into a JSON object. + *
+ * Note that this method expects small JSON files (everything is copied into
+ * memory at least twice).
*
- * @return {@link BasicSupport#in}
- */
- protected InputStream getInput() {
- return reset(in);
- }
-
- /**
- * Fix the author name if it is prefixed with some "by" {@link String}.
+ * @param url
+ * the URL to parse
+ * @param stable
+ * TRUE for more stable resources, FALSE when they often change
*
- * @param author
- * the author with a possible prefix
+ * @return the JSON object
*
- * @return the author without prefixes
+ * @throws IOException
+ * in case of I/O error
*/
- protected String fixAuthor(String author) {
- if (author != null) {
- for (String suffix : new String[] { " ", ":" }) {
- for (String byString : Instance.getConfig()
- .getString(Config.BYS).split(",")) {
- byString += suffix;
- if (author.toUpperCase().startsWith(byString.toUpperCase())) {
- author = author.substring(byString.length()).trim();
- }
- }
- }
-
- // Special case (without suffix):
- if (author.startsWith("©")) {
- author = author.substring(1);
+ protected JSONObject getJson(URL url, boolean stable) throws IOException {
+ InputStream in = Instance.getInstance().getCache().open(url, null,
+ stable);
+ try {
+ Scanner scan = new Scanner(in);
+ scan.useDelimiter("\0");
+ try {
+ return new JSONObject(scan.next());
+ } finally {
+ scan.close();
}
+ } finally {
+ in.close();
}
-
- return author;
}
/**
- * Check quotes for bad format (i.e., quotes with normal paragraphs inside)
- * and requotify them (i.e., separate them into QUOTE paragraphs and other
- * paragraphs (quotes or not)).
+ * Process the given story resource into a fully filled {@link Story}
+ * object.
*
- * @param para
- * the paragraph to requotify (not necessaraly a quote)
+ * @param pg
+ * the optional progress reporter
+ *
+ * @return the {@link Story}, never NULL
*
- * @return the correctly (or so we hope) quotified paragraphs
+ * @throws IOException
+ * in case of I/O error
*/
- private List
- * Will also fix quotes and HTML encoding if needed.
+ * Will convert the story resource into a fully filled {@link Story} object.
+ *
+ * @param pg
+ * the optional progress reporter
*
- * @param line
- * the raw line
+ * @return the {@link Story}, never NULL
*
- * @return the processed {@link Paragraph}
+ * @throws IOException
+ * in case of I/O error
*/
- private Paragraph processPara(String line) {
- line = ifUnhtml(line).trim();
-
- boolean space = true;
- boolean brk = true;
- boolean quote = false;
- boolean tentativeCloseQuote = false;
- char prev = '\0';
- int dashCount = 0;
-
- StringBuilder builder = new StringBuilder();
- for (char car : line.toCharArray()) {
- if (car != '-') {
- if (dashCount > 0) {
- // dash, ndash and mdash: - â â
- // currently: always use mdash
- builder.append(dashCount == 1 ? '-' : 'â');
- }
- dashCount = 0;
- }
-
- if (tentativeCloseQuote) {
- tentativeCloseQuote = false;
- if ((car >= 'a' && car <= 'z') || (car >= 'A' && car <= 'Z')
- || (car >= '0' && car <= '9')) {
- builder.append("'");
- } else {
- builder.append(closeQuote);
- }
- }
-
- switch (car) {
- case 'Â ': // note: unbreakable space
- case ' ':
- case '\t':
- case '\n': // just in case
- case '\r': // just in case
- builder.append(' ');
- break;
-
- case '\'':
- if (space || (brk && quote)) {
- quote = true;
- builder.append(openQuote);
- } else if (prev == ' ') {
- builder.append(openQuote);
- } else {
- // it is a quote ("I'm off") or a 'quote' ("This
- // 'good' restaurant"...)
- tentativeCloseQuote = true;
- }
- break;
-
- case '"':
- if (space || (brk && quote)) {
- quote = true;
- builder.append(openDoubleQuote);
- } else if (prev == ' ') {
- builder.append(openDoubleQuote);
- } else {
- builder.append(closeDoubleQuote);
- }
- break;
-
- case '-':
- if (space) {
- quote = true;
- } else {
- dashCount++;
- }
- space = false;
- break;
-
- case '*':
- case '~':
- case '/':
- case '\\':
- case '<':
- case '>':
- case '=':
- case '+':
- case '_':
- case 'â':
- case 'â':
- space = false;
- builder.append(car);
- break;
+ protected Story doProcess(Progress pg) throws IOException {
+ if (pg == null) {
+ pg = new Progress();
+ } else {
+ pg.setMinMax(0, 100);
+ }
+
+ pg.setName("Initialising");
- case 'â':
- case '`':
- case 'â¹':
- case 'ï¹':
- case 'ã':
- case 'ã':
- if (space || (brk && quote)) {
- quote = true;
- builder.append(openQuote);
- } else {
- builder.append(openQuote);
+ pg.setProgress(1);
+ Progress pgMeta = new Progress();
+ pg.addProgress(pgMeta, 10);
+ Story story = processMeta(true, pgMeta);
+ pgMeta.done(); // 10%
+ pg.put("meta", story.getMeta());
+
+ Progress pgGetChapters = new Progress();
+ pg.addProgress(pgGetChapters, 10);
+ story.setChapters(new ArrayList