- [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
- [ ] 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
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;
*
* @return the types
*/
- public List<String> getTypes() {
+ public synchronized List<String> getTypes() {
List<String> list = new ArrayList<String>();
for (Entry<MetaData, File> entry : getStories().entrySet()) {
String storyType = entry.getValue().getParentFile().getName();
*
* @return the stories
*/
- public List<MetaData> getList(String type) {
+ public synchronized List<MetaData> getList(String type) {
List<MetaData> list = new ArrayList<MetaData>();
for (Entry<MetaData, File> entry : getStories().entrySet()) {
String storyType = entry.getValue().getParentFile().getName();
*
* @return the corresponding {@link Story}
*/
- public MetaData getInfo(String luid) {
+ public synchronized MetaData getInfo(String luid) {
if (luid != null) {
for (Entry<MetaData, File> entry : getStories().entrySet()) {
if (luid.equals(entry.getKey().getLuid())) {
*
* @return the corresponding {@link Story}
*/
- public File getFile(String luid) {
+ public synchronized File getFile(String luid) {
if (luid != null) {
for (Entry<MetaData, File> entry : getStories().entrySet()) {
if (luid.equals(entry.getKey().getLuid())) {
*
* @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<MetaData, File> entry : getStories().entrySet()) {
if (luid.equals(entry.getKey().getLuid())) {
* @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);
}
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.
*
* @return the stories
*/
- private Map<MetaData, File> getStories() {
+ private synchronized Map<MetaData, File> getStories() {
if (stories.isEmpty()) {
lastId = 0;
- ext.length());
String newExt = getOutputType(meta)
- .getDefaultExtension();
+ .getDefaultExtension(true);
file = new File(path + newExt);
//
/**
* 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;
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,
/**
* 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 "";
}
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);
}
@Override
- public String getDefaultExtension() {
+ public String getDefaultExtension(boolean readerTarget) {
return ".cbz";
}
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();
}
@Override
- public String getDefaultExtension() {
+ public String getDefaultExtension(boolean readerTarget) {
return ".epub";
}
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"));
}
@Override
- public String getDefaultExtension() {
- return File.separator + "index.html";
+ public String getDefaultExtension(boolean readerTarget) {
+ if (readerTarget) {
+ return File.separator + "index.html";
+ } else {
+ return "";
+ }
}
@Override
StringId.CLOSE_DOUBLE_QUOTE);
@Override
- public String getDefaultExtension() {
+ public String getDefaultExtension(boolean readerTarget) {
return "";
}
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);
}
@Override
- public String getDefaultExtension() {
+ public String getDefaultExtension(boolean readerTarget) {
return ".tex";
}
public File process(Story story, File targetDir, String targetName)
throws IOException {
String targetNameOrig = targetName;
- targetName += getDefaultExtension();
+ targetName += getDefaultExtension(false);
this.targetDir = targetDir;
}
@Override
- public String getDefaultExtension() {
+ public String getDefaultExtension(boolean readerTarget) {
return ".txt";
}
return file;
}
+ public boolean isCached(String luid) {
+ return lib.getInfo(luid) != null;
+ }
+
@Override
public void start(String type) {
final String typeFinal = type;
}
});
}
+
+ void refresh(String luid) {
+ lib.delete(luid);
+ }
+
+ void delete(String luid) {
+ lib.delete(luid);
+ Instance.getLibrary().delete(luid);
+ }
}
private Date lastClick;
private long doubleClickDelay = 200; // in ms
private List<BookActionListener> 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,
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);
}
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);
}
import java.util.ArrayList;
import java.util.List;
+import javax.swing.JFileChooser;
import javax.swing.JFrame;
import javax.swing.JMenu;
import javax.swing.JMenuBar;
private Color color;
private ProgressBar pgBar;
private JMenuBar bar;
+ private LocalReaderBook selectedBook;
public LocalReaderFrame(LocalReader reader, String type) {
super("Fanfix Library");
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);
}
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 {
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();
}
});
}
}
}
});
- 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<String> 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;
}
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();