From 10d558d2429c984327f9e5a16933fefe5cc37314 Mon Sep 17 00:00:00 2001 From: Niki Roo Date: Mon, 20 Feb 2017 23:37:24 +0100 Subject: [PATCH] Version 1.2.0: better UI, some fixes --- README.md | 12 +- VERSION | 2 +- src/be/nikiroo/fanfix/Library.java | 71 ++++++- src/be/nikiroo/fanfix/output/BasicOutput.java | 18 +- src/be/nikiroo/fanfix/output/Cbz.java | 4 +- src/be/nikiroo/fanfix/output/Epub.java | 4 +- src/be/nikiroo/fanfix/output/Html.java | 12 +- src/be/nikiroo/fanfix/output/InfoText.java | 2 +- src/be/nikiroo/fanfix/output/LaTeX.java | 4 +- src/be/nikiroo/fanfix/output/Text.java | 4 +- src/be/nikiroo/fanfix/reader/LocalReader.java | 13 ++ .../fanfix/reader/LocalReaderBook.java | 35 +++- .../fanfix/reader/LocalReaderFrame.java | 194 +++++++++++++----- 13 files changed, 293 insertions(+), 82 deletions(-) diff --git a/README.md b/README.md index 3076590..4e21622 100644 --- a/README.md +++ b/README.md @@ -89,8 +89,9 @@ Currently missing, but either in progress or planned: - [x] Make one - [x] Make it run when no args passed - [x] Fix the UI, it is ugly - - [ ] Work on the UI thread is BAD + - [x] Work on the UI thread is BAD - [ ] Allow export + - [x] Allow delete/refresh - [ ] Show a list of types - [x] ..in the menu - [ ] ..as a screen view @@ -100,14 +101,15 @@ Currently missing, but either in progress or planned: - [ ] Translations - [x] i18n system in place - [x] Make use of it - - [x] Use it for all user output (some WIP remains) + - [x] Use it for most user ouput + - [ ] Use it for all user output - [ ] French translation - [ ] Allow lauching a custom application instead of Desktop.start ? - [ ] Make a wrapper for firefox to create a new, empty profile ? -- [ ] Install a mechanism to handle stories import/export progress update +- [x] Install a mechanism to handle stories import/export progress update - [x] Progress system - [x] in support classes (import) - - [ ] in output classes (export) + - [x] in output classes (export) - [x] CLI usage of such - - [ ] GUI usage of such + - [x] GUI usage of such diff --git a/VERSION b/VERSION index 9084fa2..26aaba0 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -1.1.0 +1.2.0 diff --git a/src/be/nikiroo/fanfix/Library.java b/src/be/nikiroo/fanfix/Library.java index 8822849..cae2d7d 100644 --- a/src/be/nikiroo/fanfix/Library.java +++ b/src/be/nikiroo/fanfix/Library.java @@ -9,6 +9,7 @@ import java.util.List; import java.util.Map; import java.util.Map.Entry; +import be.nikiroo.fanfix.bundles.Config; import be.nikiroo.fanfix.data.MetaData; import be.nikiroo.fanfix.data.Story; import be.nikiroo.fanfix.output.BasicOutput; @@ -58,7 +59,7 @@ public class Library { * * @return the types */ - public List getTypes() { + public synchronized List getTypes() { List list = new ArrayList(); for (Entry entry : getStories().entrySet()) { String storyType = entry.getValue().getParentFile().getName(); @@ -79,7 +80,7 @@ public class Library { * * @return the stories */ - public List getList(String type) { + public synchronized List getList(String type) { List list = new ArrayList(); for (Entry entry : getStories().entrySet()) { String storyType = entry.getValue().getParentFile().getName(); @@ -99,7 +100,7 @@ public class Library { * * @return the corresponding {@link Story} */ - public MetaData getInfo(String luid) { + public synchronized MetaData getInfo(String luid) { if (luid != null) { for (Entry entry : getStories().entrySet()) { if (luid.equals(entry.getKey().getLuid())) { @@ -119,7 +120,7 @@ public class Library { * * @return the corresponding {@link Story} */ - public File getFile(String luid) { + public synchronized File getFile(String luid) { if (luid != null) { for (Entry entry : getStories().entrySet()) { if (luid.equals(entry.getKey().getLuid())) { @@ -141,7 +142,7 @@ public class Library { * * @return the corresponding {@link Story} or NULL if not found */ - public Story getStory(String luid, Progress pg) { + public synchronized Story getStory(String luid, Progress pg) { if (luid != null) { for (Entry entry : getStories().entrySet()) { if (luid.equals(entry.getKey().getLuid())) { @@ -271,7 +272,8 @@ public class Library { * @throws IOException * in case of I/O error */ - public Story save(Story story, String luid, Progress pg) throws IOException { + public synchronized Story save(Story story, String luid, Progress pg) + throws IOException { // Do not change the original metadata, but change the original story MetaData key = story.getMeta().clone(); story.setMeta(key); @@ -296,12 +298,61 @@ public class Library { } BasicOutput it = BasicOutput.getOutput(out, true); - File file = it.process(story, getFile(key).getPath(), pg); - getStories().put(story.getMeta(), file); + it.process(story, getFile(key).getPath(), pg); + + // empty cache + stories.clear(); return story; } + /** + * Delete the given {@link Story} from this {@link Library}. + * + * @param luid + * the LUID of the target {@link Story} + * + * @return TRUE if it was deleted + */ + public synchronized boolean delete(String luid) { + boolean ok = false; + + MetaData meta = getInfo(luid); + File file = getStories().get(meta); + + if (file != null) { + if (file.delete()) { + String newExt = getOutputType(meta).getDefaultExtension(false); + + String path = file.getAbsolutePath(); + File infoFile = new File(path + ".info"); + if (!infoFile.exists()) { + infoFile = new File(path.substring(0, path.length() + - newExt.length()) + + ".info"); + } + infoFile.delete(); + + String coverExt = "." + + Instance.getConfig().getString( + Config.IMAGE_FORMAT_COVER); + File coverFile = new File(path + coverExt); + if (!coverFile.exists()) { + coverFile = new File(path.substring(0, path.length() + - newExt.length())); + } + coverFile.delete(); + + ok = true; + } + + // clear cache + stories.clear(); + } + + return ok; + } + /** * The directory (full path) where the {@link Story} related to this * {@link MetaData} should be located on disk. @@ -335,7 +386,7 @@ public class Library { * * @return the stories */ - private Map getStories() { + private synchronized Map getStories() { if (stories.isEmpty()) { lastId = 0; @@ -358,7 +409,7 @@ public class Library { - ext.length()); String newExt = getOutputType(meta) - .getDefaultExtension(); + .getDefaultExtension(true); file = new File(path + newExt); // diff --git a/src/be/nikiroo/fanfix/output/BasicOutput.java b/src/be/nikiroo/fanfix/output/BasicOutput.java index 5e82f25..a21ee97 100644 --- a/src/be/nikiroo/fanfix/output/BasicOutput.java +++ b/src/be/nikiroo/fanfix/output/BasicOutput.java @@ -69,12 +69,17 @@ public abstract class BasicOutput { /** * The default extension to add to the output files. * + * @param readerTarget + * the target to point to to read the {@link Story} (for + * instance, the main entry point if this {@link Story} is in + * a directory bundle) + * * @return the extension */ - public String getDefaultExtension() { + public String getDefaultExtension(boolean readerTarget) { BasicOutput output = BasicOutput.getOutput(this, false); if (output != null) { - return output.getDefaultExtension(); + return output.getDefaultExtension(readerTarget); } return null; @@ -166,7 +171,7 @@ public abstract class BasicOutput { File targetDir = new File(target).getParentFile(); String targetName = new File(target).getName(); - String ext = getDefaultExtension(); + String ext = getDefaultExtension(false); if (ext != null && !ext.isEmpty()) { if (targetName.toLowerCase().endsWith(ext)) { targetName = targetName.substring(0, @@ -239,9 +244,14 @@ public abstract class BasicOutput { /** * The default extension to add to the output files. * + * @param readerTarget + * the target to point to to read the {@link Story} (for + * instance, the main entry point if this {@link Story} is in a + * directory bundle) + * * @return the extension */ - public String getDefaultExtension() { + public String getDefaultExtension(boolean readerTarget) { return ""; } diff --git a/src/be/nikiroo/fanfix/output/Cbz.java b/src/be/nikiroo/fanfix/output/Cbz.java index 2c9dbc3..c1d5955 100644 --- a/src/be/nikiroo/fanfix/output/Cbz.java +++ b/src/be/nikiroo/fanfix/output/Cbz.java @@ -18,7 +18,7 @@ class Cbz extends BasicOutput { public File process(Story story, File targetDir, String targetName) throws IOException { String targetNameOrig = targetName; - targetName += getDefaultExtension(); + targetName += getDefaultExtension(false); File target = new File(targetDir, targetName); @@ -45,7 +45,7 @@ class Cbz extends BasicOutput { } @Override - public String getDefaultExtension() { + public String getDefaultExtension(boolean readerTarget) { return ".cbz"; } diff --git a/src/be/nikiroo/fanfix/output/Epub.java b/src/be/nikiroo/fanfix/output/Epub.java index 3e875aa..de55d4c 100644 --- a/src/be/nikiroo/fanfix/output/Epub.java +++ b/src/be/nikiroo/fanfix/output/Epub.java @@ -32,7 +32,7 @@ class Epub extends BasicOutput { public File process(Story story, File targetDir, String targetName) throws IOException { String targetNameOrig = targetName; - targetName += getDefaultExtension(); + targetName += getDefaultExtension(false); tmpDir = File.createTempFile("fanfic-reader-epub_", ".wip"); tmpDir.delete(); @@ -60,7 +60,7 @@ class Epub extends BasicOutput { } @Override - public String getDefaultExtension() { + public String getDefaultExtension(boolean readerTarget) { return ".epub"; } diff --git a/src/be/nikiroo/fanfix/output/Html.java b/src/be/nikiroo/fanfix/output/Html.java index 3ed52ad..1ec2a08 100644 --- a/src/be/nikiroo/fanfix/output/Html.java +++ b/src/be/nikiroo/fanfix/output/Html.java @@ -34,9 +34,7 @@ class Html extends BasicOutput { target.mkdir(); dir = target; - targetName += getDefaultExtension(); - - target = new File(targetDir, targetName); + target = new File(targetDir, targetName + getDefaultExtension(true)); writer = new BufferedWriter(new OutputStreamWriter( new FileOutputStream(target), "UTF-8")); @@ -58,8 +56,12 @@ class Html extends BasicOutput { } @Override - public String getDefaultExtension() { - return File.separator + "index.html"; + public String getDefaultExtension(boolean readerTarget) { + if (readerTarget) { + return File.separator + "index.html"; + } else { + return ""; + } } @Override diff --git a/src/be/nikiroo/fanfix/output/InfoText.java b/src/be/nikiroo/fanfix/output/InfoText.java index dc00b8a..66df947 100644 --- a/src/be/nikiroo/fanfix/output/InfoText.java +++ b/src/be/nikiroo/fanfix/output/InfoText.java @@ -19,7 +19,7 @@ class InfoText extends Text { StringId.CLOSE_DOUBLE_QUOTE); @Override - public String getDefaultExtension() { + public String getDefaultExtension(boolean readerTarget) { return ""; } diff --git a/src/be/nikiroo/fanfix/output/LaTeX.java b/src/be/nikiroo/fanfix/output/LaTeX.java index d322e4a..de3bd05 100644 --- a/src/be/nikiroo/fanfix/output/LaTeX.java +++ b/src/be/nikiroo/fanfix/output/LaTeX.java @@ -32,7 +32,7 @@ class LaTeX extends BasicOutput { public File process(Story story, File targetDir, String targetName) throws IOException { String targetNameOrig = targetName; - targetName += getDefaultExtension(); + targetName += getDefaultExtension(false); File target = new File(targetDir, targetName); @@ -49,7 +49,7 @@ class LaTeX extends BasicOutput { } @Override - public String getDefaultExtension() { + public String getDefaultExtension(boolean readerTarget) { return ".tex"; } diff --git a/src/be/nikiroo/fanfix/output/Text.java b/src/be/nikiroo/fanfix/output/Text.java index 4acfa76..28f3894 100644 --- a/src/be/nikiroo/fanfix/output/Text.java +++ b/src/be/nikiroo/fanfix/output/Text.java @@ -23,7 +23,7 @@ class Text extends BasicOutput { public File process(Story story, File targetDir, String targetName) throws IOException { String targetNameOrig = targetName; - targetName += getDefaultExtension(); + targetName += getDefaultExtension(false); this.targetDir = targetDir; @@ -42,7 +42,7 @@ class Text extends BasicOutput { } @Override - public String getDefaultExtension() { + public String getDefaultExtension(boolean readerTarget) { return ".txt"; } diff --git a/src/be/nikiroo/fanfix/reader/LocalReader.java b/src/be/nikiroo/fanfix/reader/LocalReader.java index 7c50b03..6a66c19 100644 --- a/src/be/nikiroo/fanfix/reader/LocalReader.java +++ b/src/be/nikiroo/fanfix/reader/LocalReader.java @@ -104,6 +104,10 @@ class LocalReader extends BasicReader { return file; } + public boolean isCached(String luid) { + return lib.getInfo(luid) != null; + } + @Override public void start(String type) { final String typeFinal = type; @@ -114,4 +118,13 @@ class LocalReader extends BasicReader { } }); } + + void refresh(String luid) { + lib.delete(luid); + } + + void delete(String luid) { + lib.delete(luid); + Instance.getLibrary().delete(luid); + } } diff --git a/src/be/nikiroo/fanfix/reader/LocalReaderBook.java b/src/be/nikiroo/fanfix/reader/LocalReaderBook.java index a9b24a2..c716bd3 100644 --- a/src/be/nikiroo/fanfix/reader/LocalReaderBook.java +++ b/src/be/nikiroo/fanfix/reader/LocalReaderBook.java @@ -68,8 +68,13 @@ class LocalReaderBook extends JPanel { private Date lastClick; private long doubleClickDelay = 200; // in ms private List listeners; + private String luid; + private boolean cached; + + public LocalReaderBook(MetaData meta, boolean cached) { + this.luid = meta.getLuid(); + this.cached = cached; - public LocalReaderBook(MetaData meta) { if (meta.getCover() != null) { BufferedImage resizedImage = new BufferedImage(SPINE_WIDTH + COVER_WIDTH, SPINE_HEIGHT + COVER_HEIGHT + HOFFSET, @@ -179,6 +184,29 @@ class LocalReaderBook extends JPanel { listeners.add(listener); } + public String getLuid() { + return luid; + } + + /** + * This boos is cached into the {@link LocalReader} library. + * + * @return the cached + */ + public boolean isCached() { + return cached; + } + + /** + * This boos is cached into the {@link LocalReader} library. + * + * @param cached + * the cached to set + */ + public void setCached(boolean cached) { + this.cached = cached; + } + @Override public void paint(Graphics g) { super.paint(g); @@ -211,6 +239,11 @@ class LocalReaderBook extends JPanel { } Rectangle clip = g.getClipBounds(); + if (cached) { + g.setColor(Color.green); + g.fillOval(clip.x + clip.width - 30, 10, 20, 20); + } + g.setColor(color); g.fillRect(clip.x, clip.y, clip.width, clip.height); } diff --git a/src/be/nikiroo/fanfix/reader/LocalReaderFrame.java b/src/be/nikiroo/fanfix/reader/LocalReaderFrame.java index 9d39017..98530b5 100644 --- a/src/be/nikiroo/fanfix/reader/LocalReaderFrame.java +++ b/src/be/nikiroo/fanfix/reader/LocalReaderFrame.java @@ -12,6 +12,7 @@ import java.io.IOException; import java.util.ArrayList; import java.util.List; +import javax.swing.JFileChooser; import javax.swing.JFrame; import javax.swing.JMenu; import javax.swing.JMenuBar; @@ -39,6 +40,7 @@ class LocalReaderFrame extends JFrame { private Color color; private ProgressBar pgBar; private JMenuBar bar; + private LocalReaderBook selectedBook; public LocalReaderFrame(LocalReader reader, String type) { super("Fanfix Library"); @@ -78,7 +80,8 @@ class LocalReaderFrame extends JFrame { books.clear(); bookPane.removeAll(); for (MetaData meta : stories) { - LocalReaderBook book = new LocalReaderBook(meta); + LocalReaderBook book = new LocalReaderBook(meta, + reader.isCached(meta.getLuid())); if (color != null) { book.setBackground(color); } @@ -87,18 +90,20 @@ class LocalReaderFrame extends JFrame { final String luid = meta.getLuid(); book.addActionListener(new BookActionListener() { public void select(LocalReaderBook book) { + selectedBook = book; for (LocalReaderBook abook : books) { abook.setSelected(abook == book); } } - public void action(LocalReaderBook book) { + public void action(final LocalReaderBook book) { final Progress pg = new Progress(); outOfUi(pg, new Runnable() { public void run() { try { File target = LocalReaderFrame.this.reader .getTarget(luid, pg); + book.setCached(true); // TODO: allow custom programs, with // Desktop/xdg-open fallback try { @@ -146,41 +151,58 @@ class LocalReaderFrame extends JFrame { bar = new JMenuBar(); JMenu file = new JMenu("File"); + file.setMnemonic(KeyEvent.VK_F); - JMenuItem imprt = new JMenuItem("Import", KeyEvent.VK_I); + JMenuItem imprt = new JMenuItem("Import URL", KeyEvent.VK_U); imprt.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { - final String url = JOptionPane.showInputDialog( - LocalReaderFrame.this, "url of the story to import?", - "Importing from URL", JOptionPane.QUESTION_MESSAGE); - if (url != null && !url.isEmpty()) { - final Progress pg = new Progress("Importing " + url); - outOfUi(pg, new Runnable() { - public void run() { - Exception ex = null; - try { - Instance.getLibrary().imprt( - BasicReader.getUrl(url), pg); - } catch (IOException e) { - ex = e; - } + imprt(true); + } + }); + JMenuItem imprtF = new JMenuItem("Import File", KeyEvent.VK_F); + imprtF.addActionListener(new ActionListener() { + public void actionPerformed(ActionEvent e) { + imprt(false); + } + }); + JMenuItem exit = new JMenuItem("Exit", KeyEvent.VK_X); + exit.addActionListener(new ActionListener() { + public void actionPerformed(ActionEvent e) { + LocalReaderFrame.this.dispatchEvent(new WindowEvent( + LocalReaderFrame.this, WindowEvent.WINDOW_CLOSING)); + } + }); - final Exception e = ex; + file.add(imprt); + file.add(imprtF); + file.addSeparator(); + file.add(exit); - final boolean ok = (e == null); + bar.add(file); + + JMenu edit = new JMenu("Edit"); + edit.setMnemonic(KeyEvent.VK_E); + + final String notYet = "[TODO] Show not ready yet, but you can do it on command line, see: fanfix --help"; + + JMenuItem export = new JMenuItem("Export", KeyEvent.VK_E); + export.addActionListener(new ActionListener() { + public void actionPerformed(ActionEvent e) { + JOptionPane.showMessageDialog(LocalReaderFrame.this, notYet); + } + }); + + JMenuItem refresh = new JMenuItem("Refresh", KeyEvent.VK_R); + refresh.addActionListener(new ActionListener() { + public void actionPerformed(ActionEvent e) { + if (selectedBook != null) { + outOfUi(null, new Runnable() { + public void run() { + reader.refresh(selectedBook.getLuid()); + selectedBook.setCached(false); SwingUtilities.invokeLater(new Runnable() { public void run() { - if (!ok) { - JOptionPane.showMessageDialog( - LocalReaderFrame.this, - "Cannot import: " + url, - e.getMessage(), - JOptionPane.ERROR_MESSAGE); - - setAllEnabled(true); - } else { - refreshBooks(type); - } + selectedBook.repaint(); } }); } @@ -188,32 +210,53 @@ class LocalReaderFrame extends JFrame { } } }); - JMenu types = new JMenu("Type"); + + JMenuItem delete = new JMenuItem("Delete", KeyEvent.VK_D); + delete.addActionListener(new ActionListener() { + public void actionPerformed(ActionEvent e) { + if (selectedBook != null) { + outOfUi(null, new Runnable() { + public void run() { + reader.delete(selectedBook.getLuid()); + selectedBook = null; + SwingUtilities.invokeLater(new Runnable() { + public void run() { + refreshBooks(type); + } + }); + } + }); + } + } + }); + + edit.add(export); + edit.add(refresh); + edit.add(delete); + + bar.add(edit); + + JMenu view = new JMenu("View"); + view.setMnemonic(KeyEvent.VK_V); + List tt = Instance.getLibrary().getTypes(); tt.add(0, null); for (final String type : tt) { - JMenuItem item = new JMenuItem(type == null ? "[all]" : type); + + JMenuItem item = new JMenuItem(type == null ? "All books" : type); item.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { refreshBooks(type); } }); - types.add(item); - } - JMenuItem exit = new JMenuItem("Exit", KeyEvent.VK_X); - exit.addActionListener(new ActionListener() { - public void actionPerformed(ActionEvent e) { - LocalReaderFrame.this.dispatchEvent(new WindowEvent( - LocalReaderFrame.this, WindowEvent.WINDOW_CLOSING)); - } - }); + view.add(item); - file.add(imprt); - file.add(types); - file.addSeparator(); - file.add(exit); + if (type == null) { + view.addSeparator(); + } + } - bar.add(file); + bar.add(view); return bar; } @@ -236,23 +279,80 @@ class LocalReaderFrame extends JFrame { new Thread(new Runnable() { public void run() { run.run(); - if (!pg.isDone()) { + if (pg == null) { + SwingUtilities.invokeLater(new Runnable() { + public void run() { + setAllEnabled(true); + } + }); + } else if (!pg.isDone()) { pg.setProgress(pg.getMax()); } } }).start(); } + private void imprt(boolean askUrl) { + JFileChooser fc = new JFileChooser(); + + final String url; + if (askUrl) { + url = JOptionPane.showInputDialog(LocalReaderFrame.this, + "url of the story to import?", "Importing from URL", + JOptionPane.QUESTION_MESSAGE); + } else if (fc.showOpenDialog(this) != JFileChooser.CANCEL_OPTION) { + url = fc.getSelectedFile().getAbsolutePath(); + } else { + url = null; + } + + if (url != null && !url.isEmpty()) { + final Progress pg = new Progress("Importing " + url); + outOfUi(pg, new Runnable() { + public void run() { + Exception ex = null; + try { + Instance.getLibrary() + .imprt(BasicReader.getUrl(url), pg); + } catch (IOException e) { + ex = e; + } + + final Exception e = ex; + + final boolean ok = (e == null); + SwingUtilities.invokeLater(new Runnable() { + public void run() { + if (!ok) { + JOptionPane.showMessageDialog( + LocalReaderFrame.this, + "Cannot import: " + url, + e.getMessage(), + JOptionPane.ERROR_MESSAGE); + + setAllEnabled(true); + } else { + refreshBooks(type); + } + } + }); + } + }); + } + } + public void setAllEnabled(boolean enabled) { for (LocalReaderBook book : books) { book.setEnabled(enabled); book.validate(); book.repaint(); } + bar.setEnabled(enabled); bookPane.setEnabled(enabled); bookPane.validate(); bookPane.repaint(); + setEnabled(enabled); validate(); repaint(); -- 2.27.0