From 793f1071fae48daed3b545a03a286c85e527d244 Mon Sep 17 00:00:00 2001 From: Niki Roo Date: Sat, 4 Mar 2017 11:56:36 +0100 Subject: [PATCH] Wordcount (including UI), date of creation - Remember the date of creation (.info) - Remember the word count (.info) - UI: show either author or word count below the book title - Note: those new informations requires a redownload --- changelog.md | 5 ++ src/be/nikiroo/fanfix/data/Chapter.java | 20 +++++ src/be/nikiroo/fanfix/data/MetaData.java | 40 +++++++++ src/be/nikiroo/fanfix/data/Paragraph.java | 52 ++++++++---- src/be/nikiroo/fanfix/output/InfoCover.java | 3 + .../fanfix/reader/LocalReaderBook.java | 22 +++-- .../fanfix/reader/LocalReaderFrame.java | 27 ++++++- .../fanfix/reader/LocalReaderGroup.java | 10 ++- .../fanfix/supported/BasicSupport.java | 42 ++++++++-- .../nikiroo/fanfix/supported/InfoReader.java | 6 ++ .../nikiroo/fanfix/test/BasicSupportTest.java | 81 ++++++++++++++++++- 11 files changed, 275 insertions(+), 33 deletions(-) diff --git a/changelog.md b/changelog.md index edecf23..70b64c7 100644 --- a/changelog.md +++ b/changelog.md @@ -1,5 +1,10 @@ # Fanfix +## Version (in progress) + +- Remember the word count and the date of creation of Fanfix stories +- UI: option to show the word count instead of the author below the book title + ## Version 1.3.1 - UI: can now display books by Author diff --git a/src/be/nikiroo/fanfix/data/Chapter.java b/src/be/nikiroo/fanfix/data/Chapter.java index 5516063..839d67b 100644 --- a/src/be/nikiroo/fanfix/data/Chapter.java +++ b/src/be/nikiroo/fanfix/data/Chapter.java @@ -14,6 +14,7 @@ public class Chapter implements Iterable { private int number; private List paragraphs = new ArrayList(); private List empty = new ArrayList(); + private long words; /** * Create a new {@link Chapter} with the given information. @@ -92,6 +93,25 @@ public class Chapter implements Iterable { return paragraphs == null ? empty.iterator() : paragraphs.iterator(); } + /** + * The number of words in this {@link Chapter}. + * + * @return the number of words + */ + public long getWords() { + return words; + } + + /** + * The number of words in this {@link Chapter}. + * + * @param words + * the number of words to set + */ + public void setWords(long words) { + this.words = words; + } + /** * Display a DEBUG {@link String} representation of this object. */ diff --git a/src/be/nikiroo/fanfix/data/MetaData.java b/src/be/nikiroo/fanfix/data/MetaData.java index 153638c..06cf625 100644 --- a/src/be/nikiroo/fanfix/data/MetaData.java +++ b/src/be/nikiroo/fanfix/data/MetaData.java @@ -25,6 +25,8 @@ public class MetaData implements Cloneable, Comparable { private String publisher; private String type; private boolean imageDocument; + private long words; + private String creationDate; /** * The title of the story. @@ -315,6 +317,44 @@ public class MetaData implements Cloneable, Comparable { this.imageDocument = imageDocument; } + /** + * The number of words in the related {@link Story}. + * + * @return the number of words + */ + public long getWords() { + return words; + } + + /** + * The number of words in the related {@link Story}. + * + * @param words + * the number of words to set + */ + public void setWords(long words) { + this.words = words; + } + + /** + * The (Fanfix) {@link Story} creation date. + * + * @return the creationDate + */ + public String getCreationDate() { + return creationDate; + } + + /** + * The (Fanfix) {@link Story} creation date. + * + * @param creationDate + * the creationDate to set + */ + public void setCreationDate(String creationDate) { + this.creationDate = creationDate; + } + public int compareTo(MetaData o) { String oUuid = o == null ? null : o.getUuid(); return getUuid().compareTo(oUuid); diff --git a/src/be/nikiroo/fanfix/data/Paragraph.java b/src/be/nikiroo/fanfix/data/Paragraph.java index feb949c..e409c28 100644 --- a/src/be/nikiroo/fanfix/data/Paragraph.java +++ b/src/be/nikiroo/fanfix/data/Paragraph.java @@ -28,19 +28,7 @@ public class Paragraph { private ParagraphType type; private String content; - - /** - * Create a new {@link Paragraph} with the given values. - * - * @param type - * the {@link ParagraphType} - * @param content - * the content of this paragraph - */ - public Paragraph(ParagraphType type, String content) { - this.type = type; - this.content = content; - } + private long words; /** * Create a new {@link Paragraph} with the given image. @@ -52,8 +40,23 @@ public class Paragraph { * the content image of this paragraph */ public Paragraph(URL imageUrl) { - this.type = ParagraphType.IMAGE; - this.content = imageUrl.toString(); + this(ParagraphType.IMAGE, imageUrl.toString(), 0); + } + + /** + * Create a new {@link Paragraph} with the given values. + * + * @param type + * the {@link ParagraphType} + * @param content + * the content of this paragraph + * @param words + * the number of words + */ + public Paragraph(ParagraphType type, String content, long words) { + this.type = type; + this.content = content; + this.words = words; } /** @@ -94,6 +97,25 @@ public class Paragraph { this.content = content; } + /** + * The number of words in this {@link Paragraph}. + * + * @return the number of words + */ + public long getWords() { + return words; + } + + /** + * The number of words in this {@link Paragraph}. + * + * @param words + * the number of words to set + */ + public void setWords(long words) { + this.words = words; + } + /** * Display a DEBUG {@link String} representation of this object. */ diff --git a/src/be/nikiroo/fanfix/output/InfoCover.java b/src/be/nikiroo/fanfix/output/InfoCover.java index 1a63dce..7db2a1c 100644 --- a/src/be/nikiroo/fanfix/output/InfoCover.java +++ b/src/be/nikiroo/fanfix/output/InfoCover.java @@ -54,6 +54,9 @@ class InfoCover { } writeMeta(infoWriter, "EPUBCREATOR", BasicOutput.EPUB_CREATOR); writeMeta(infoWriter, "PUBLISHER", meta.getPublisher()); + writeMeta(infoWriter, "WORDCOUNT", + Long.toString(meta.getWords())); + writeMeta(infoWriter, "CREATION_DATE", meta.getCreationDate()); } finally { infoWriter.close(); } diff --git a/src/be/nikiroo/fanfix/reader/LocalReaderBook.java b/src/be/nikiroo/fanfix/reader/LocalReaderBook.java index 9a4290d..8c47078 100644 --- a/src/be/nikiroo/fanfix/reader/LocalReaderBook.java +++ b/src/be/nikiroo/fanfix/reader/LocalReaderBook.java @@ -95,14 +95,26 @@ class LocalReaderBook extends JPanel { * the story {@code}link MetaData} * @param cached * TRUE if it is locally cached + * @param seeWordcount + * TRUE to see word counts, FALSE to see authors */ - public LocalReaderBook(MetaData meta, boolean cached) { + public LocalReaderBook(MetaData meta, boolean cached, boolean seeWordCount) { this.cached = cached; this.meta = meta; - String optAuthor = meta.getAuthor(); - if (optAuthor != null && !optAuthor.isEmpty()) { - optAuthor = "(" + optAuthor + ")"; + String optSecondary = meta.getAuthor(); + if (seeWordCount) { + if (meta.getWords() >= 4000) { + optSecondary = (meta.getWords() / 1000) + "k words"; + } else if (meta.getWords() > 0) { + optSecondary = meta.getWords() + " words"; + } else { + optSecondary = "empty"; + } + } + + if (optSecondary != null && !optSecondary.isEmpty()) { + optSecondary = "(" + optSecondary + ")"; } icon = new JLabel(generateCoverIcon(meta.getCover())); @@ -114,7 +126,7 @@ class LocalReaderBook extends JPanel { + "%s" + "
" + "" + "%s" + "" + "" + "", TEXT_WIDTH, TEXT_HEIGHT, meta.getTitle(), AUTHOR_COLOR, - optAuthor)); + optSecondary)); setLayout(new BorderLayout(10, 10)); add(icon, BorderLayout.CENTER); diff --git a/src/be/nikiroo/fanfix/reader/LocalReaderFrame.java b/src/be/nikiroo/fanfix/reader/LocalReaderFrame.java index d50669d..a90360e 100644 --- a/src/be/nikiroo/fanfix/reader/LocalReaderFrame.java +++ b/src/be/nikiroo/fanfix/reader/LocalReaderFrame.java @@ -61,6 +61,7 @@ class LocalReaderFrame extends JFrame { private ProgressBar pgBar; private JMenuBar bar; private LocalReaderBook selectedBook; + private boolean words; // words or authors (secondary info on books) /** * Create a new {@link LocalReaderFrame}. @@ -191,13 +192,13 @@ class LocalReaderFrame extends JFrame { for (LocalReaderGroup group : booksByType.keySet()) { List stories = Instance.getLibrary().getListByType( booksByType.get(group)); - group.refreshBooks(stories); + group.refreshBooks(stories, words); } for (LocalReaderGroup group : booksByAuthor.keySet()) { List stories = Instance.getLibrary().getListByAuthor( booksByAuthor.get(group)); - group.refreshBooks(stories); + group.refreshBooks(stories, words); } pane.repaint(); @@ -255,6 +256,28 @@ class LocalReaderFrame extends JFrame { bar.add(edit); + JMenu view = new JMenu("View"); + view.setMnemonic(KeyEvent.VK_V); + JMenuItem vauthors = new JMenuItem("Author"); + vauthors.setMnemonic(KeyEvent.VK_A); + vauthors.addActionListener(new ActionListener() { + public void actionPerformed(ActionEvent e) { + words = false; + refreshBooks(); + } + }); + view.add(vauthors); + JMenuItem vwords = new JMenuItem("Word count"); + vwords.setMnemonic(KeyEvent.VK_W); + vwords.addActionListener(new ActionListener() { + public void actionPerformed(ActionEvent e) { + words = true; + refreshBooks(); + } + }); + view.add(vwords); + bar.add(view); + JMenu sources = new JMenu("Sources"); sources.setMnemonic(KeyEvent.VK_S); diff --git a/src/be/nikiroo/fanfix/reader/LocalReaderGroup.java b/src/be/nikiroo/fanfix/reader/LocalReaderGroup.java index 991aaee..8667ffd 100644 --- a/src/be/nikiroo/fanfix/reader/LocalReaderGroup.java +++ b/src/be/nikiroo/fanfix/reader/LocalReaderGroup.java @@ -27,6 +27,7 @@ public class LocalReaderGroup extends JPanel { private List stories; private List books; private JPanel pane; + private boolean words; // words or authors (secondary info on books) /** * Create a new {@link LocalReaderGroup}. @@ -78,7 +79,7 @@ public class LocalReaderGroup extends JPanel { */ public void setActionListener(BookActionListener action) { this.action = action; - refreshBooks(stories); + refreshBooks(stories, words); } /** @@ -86,9 +87,12 @@ public class LocalReaderGroup extends JPanel { * * @param stories * the stories + * @param seeWordcount + * TRUE to see word counts, FALSE to see authors */ - public void refreshBooks(List stories) { + public void refreshBooks(List stories, boolean seeWordcount) { this.stories = stories; + this.words = seeWordcount; books = new ArrayList(); invalidate(); @@ -98,7 +102,7 @@ public class LocalReaderGroup extends JPanel { if (stories != null) { for (MetaData meta : stories) { LocalReaderBook book = new LocalReaderBook(meta, - reader.isCached(meta.getLuid())); + reader.isCached(meta.getLuid()), seeWordcount); if (backgroundColor != null) { book.setBackground(backgroundColor); } diff --git a/src/be/nikiroo/fanfix/supported/BasicSupport.java b/src/be/nikiroo/fanfix/supported/BasicSupport.java index b528cac..f255d5e 100644 --- a/src/be/nikiroo/fanfix/supported/BasicSupport.java +++ b/src/be/nikiroo/fanfix/supported/BasicSupport.java @@ -10,6 +10,7 @@ import java.io.InputStreamReader; 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; @@ -333,6 +334,10 @@ public abstract class BasicSupport { Story story = new Story(); MetaData meta = getMeta(url, getInput()); + if (meta.getCreationDate() == null + || meta.getCreationDate().isEmpty()) { + meta.setCreationDate(StringUtils.fromTime(new Date().getTime())); + } story.setMeta(meta); if (meta != null && meta.getCover() == null) { @@ -408,14 +413,19 @@ public abstract class BasicSupport { Progress pgChaps = new Progress(0, chapters.size()); pg.addProgress(pgChaps, 80); + long words = 0; for (Entry chap : chapters) { setCurrentReferer(chap.getValue()); InputStream chapIn = Instance.getCache().open( chap.getValue(), this, true); try { - story.getChapters().add( - makeChapter(url, i, chap.getKey(), - getChapterContent(url, chapIn, i))); + Chapter cc = makeChapter(url, i, chap.getKey(), + getChapterContent(url, chapIn, i)); + words += cc.getWords(); + story.getChapters().add(cc); + if (story.getMeta() != null) { + story.getMeta().setWords(words); + } } finally { chapIn.close(); } @@ -554,7 +564,13 @@ public abstract class BasicSupport { Chapter chap = new Chapter(number, chapterName); if (content != null) { - chap.setParagraphs(makeParagraphs(source, content)); + List paras = makeParagraphs(source, content); + long words = 0; + for (Paragraph para : paras) { + words += para.getWords(); + } + chap.setParagraphs(paras); + chap.setWords(words); } return chap; @@ -929,7 +945,8 @@ public abstract class BasicSupport { if (!singleQ && !doubleQ) { line = openDoubleQuote + line + closeDoubleQuote; - newParas.add(new Paragraph(ParagraphType.QUOTE, line)); + newParas.add(new Paragraph(ParagraphType.QUOTE, line, para + .getWords())); } else { char open = singleQ ? openQuote : openDoubleQuote; char close = singleQ ? closeQuote : closeDoubleQuote; @@ -952,7 +969,13 @@ public abstract class BasicSupport { if (posDot >= 0) { String rest = line.substring(posDot + 1).trim(); line = line.substring(0, posDot + 1).trim(); - newParas.add(new Paragraph(ParagraphType.QUOTE, line)); + long words = 1; + for (char car : line.toCharArray()) { + if (car == ' ') { + words++; + } + } + newParas.add(new Paragraph(ParagraphType.QUOTE, line, words)); if (!rest.isEmpty()) { newParas.addAll(requotify(processPara(rest))); } @@ -986,6 +1009,7 @@ public abstract class BasicSupport { boolean tentativeCloseQuote = false; char prev = '\0'; int dashCount = 0; + long words = 1; StringBuilder builder = new StringBuilder(); for (char car : line.toCharArray()) { @@ -1019,6 +1043,10 @@ public abstract class BasicSupport { case '\t': case '\n': // just in case case '\r': // just in case + if (builder.length() > 0 + && builder.charAt(builder.length() - 1) != ' ') { + words++; + } builder.append(' '); break; @@ -1171,7 +1199,7 @@ public abstract class BasicSupport { type = ParagraphType.QUOTE; } - return new Paragraph(type, line); + return new Paragraph(type, line, words); } /** diff --git a/src/be/nikiroo/fanfix/supported/InfoReader.java b/src/be/nikiroo/fanfix/supported/InfoReader.java index c230240..9372edd 100644 --- a/src/be/nikiroo/fanfix/supported/InfoReader.java +++ b/src/be/nikiroo/fanfix/supported/InfoReader.java @@ -54,6 +54,12 @@ public class InfoReader { meta.setImageDocument(getInfoTagBoolean(in, "IMAGES_DOCUMENT", false)); meta.setCover(BasicSupport.getImage(null, sourceInfoFile, getInfoTag(in, "COVER"))); + try { + meta.setWords(Long.parseLong(getInfoTag(in, "WORDCOUNT"))); + } catch (NumberFormatException e) { + meta.setWords(0); + } + meta.setCreationDate(getInfoTag(in, "CREATION_DATE")); if (meta.getCover() == null) { meta.setCover(BasicSupport.getDefaultCover(meta.getSubject())); diff --git a/src/be/nikiroo/fanfix/test/BasicSupportTest.java b/src/be/nikiroo/fanfix/test/BasicSupportTest.java index d8565cb..f989663 100644 --- a/src/be/nikiroo/fanfix/test/BasicSupportTest.java +++ b/src/be/nikiroo/fanfix/test/BasicSupportTest.java @@ -50,7 +50,7 @@ public class BasicSupportTest extends TestLauncher { } @Override - protected List requotify(Paragraph para) { + public List requotify(Paragraph para) { List paras = new ArrayList( 1); paras.add(para); @@ -223,6 +223,79 @@ public class BasicSupportTest extends TestLauncher { assertEquals(text, para.getContent()); } }); + + addTest(new TestCase("BasicSupport.processPara() words count") { + @Override + public void test() throws Exception { + BasicSupportEmpty support = new BasicSupportEmpty() { + @Override + protected boolean isHtml() { + return true; + } + }; + + Paragraph para; + + para = support.processPara("«Yes, my Lord!»"); + assertEquals(3, para.getWords()); + + para = support.processPara("One, twee, trois."); + assertEquals(3, para.getWords()); + } + }); + + addTest(new TestCase("BasicSupport.requotify() words count") { + @Override + public void test() throws Exception { + BasicSupportEmpty support = new BasicSupportEmpty(); + + char openDoubleQuote = Instance.getTrans().getChar( + StringId.OPEN_DOUBLE_QUOTE); + char closeDoubleQuote = Instance.getTrans().getChar( + StringId.CLOSE_DOUBLE_QUOTE); + + String content = null; + Paragraph para = null; + List paras = null; + long words = 0; + + content = "One, twee, trois."; + para = new Paragraph(ParagraphType.NORMAL, content, + content.split(" ").length); + paras = support.requotify(para); + words = 0; + for (Paragraph p : paras) { + words += p.getWords(); + } + assertEquals("Bad words count in a single paragraph", + para.getWords(), words); + + content = "Such WoW! So Web2.0! With Colours!"; + para = new Paragraph(ParagraphType.NORMAL, content, + content.split(" ").length); + paras = support.requotify(para); + words = 0; + for (Paragraph p : paras) { + words += p.getWords(); + } + assertEquals("Bad words count in a single paragraph", + para.getWords(), words); + + content = openDoubleQuote + "Such a good idea!" + + closeDoubleQuote + + ", she said. This ought to be a new para."; + para = new Paragraph(ParagraphType.QUOTE, content, + content.split(" ").length); + paras = support.requotify(para); + words = 0; + for (Paragraph p : paras) { + words += p.getWords(); + } + assertEquals( + "Bad words count in a requotified paragraph", + para.getWords(), words); + } + }); } }); @@ -372,5 +445,11 @@ public class BasicSupportTest extends TestLauncher { public Paragraph processPara(String line) { return super.processPara(line); } + + @Override + // and make it public! + public List requotify(Paragraph para) { + return super.requotify(para); + } } } -- 2.27.0