From 1cbdc1f32b264447c11b2c36211c3595f6eea6b3 Mon Sep 17 00:00:00 2001 From: Reinhard Pointner Date: Tue, 26 Mar 2013 08:43:02 +0000 Subject: [PATCH] * properly lock history.xml file for reading and writing * revise donation reminders --- source/net/sourceforge/filebot/History.java | 12 +- .../sourceforge/filebot/HistorySpooler.java | 148 ++++++++---------- source/net/sourceforge/filebot/Main.java | 83 +++++----- .../filebot/ui/rename/HistoryDialog.java | 6 +- .../filebot/ui/rename/RenamePanel.java | 25 ++- 5 files changed, 127 insertions(+), 147 deletions(-) diff --git a/source/net/sourceforge/filebot/History.java b/source/net/sourceforge/filebot/History.java index d955fb7a..830952df 100644 --- a/source/net/sourceforge/filebot/History.java +++ b/source/net/sourceforge/filebot/History.java @@ -6,6 +6,8 @@ import static java.util.Collections.*; import java.io.File; import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; @@ -204,23 +206,21 @@ public class History { } - public static void exportHistory(History history, File file) throws IOException { + public static void exportHistory(History history, OutputStream output) throws IOException { try { Marshaller marshaller = JAXBContext.newInstance(History.class).createMarshaller(); marshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, Boolean.TRUE); - - marshaller.marshal(history, file); + marshaller.marshal(history, output); } catch (JAXBException e) { throw new IOException(e); } } - public static History importHistory(File file) throws IOException { + public static History importHistory(InputStream stream) throws IOException { try { Unmarshaller unmarshaller = JAXBContext.newInstance(History.class).createUnmarshaller(); - - return ((History) unmarshaller.unmarshal(file)); + return ((History) unmarshaller.unmarshal(stream)); } catch (JAXBException e) { throw new IOException(e); } diff --git a/source/net/sourceforge/filebot/HistorySpooler.java b/source/net/sourceforge/filebot/HistorySpooler.java index 990ba4bf..6022cbde 100644 --- a/source/net/sourceforge/filebot/HistorySpooler.java +++ b/source/net/sourceforge/filebot/HistorySpooler.java @@ -2,10 +2,13 @@ package net.sourceforge.filebot; -import static net.sourceforge.filebot.History.*; +import static net.sourceforge.filebot.Settings.*; import java.io.File; import java.io.IOException; +import java.io.RandomAccessFile; +import java.nio.channels.FileChannel; +import java.nio.channels.FileLock; import java.util.ArrayList; import java.util.List; import java.util.Map.Entry; @@ -13,6 +16,8 @@ import java.util.logging.Level; import java.util.logging.Logger; import net.sourceforge.filebot.History.Element; +import net.sourceforge.tuned.ByteBufferInputStream; +import net.sourceforge.tuned.ByteBufferOutputStream; public final class HistorySpooler { @@ -24,31 +29,72 @@ public final class HistorySpooler { return instance; } - private HistoryStorage persistentHistory = null; + private File persistentHistoryFile = new File(getApplicationFolder(), "history.xml"); + private int persistentHistoryTotalSize = -1; + private History sessionHistory = new History(); - public synchronized History getCompleteHistory() { - History history = new History(); - - // add persistent history - try { - if (getPersistentHistory() != null) { - history.addAll(getPersistentHistory().read().sequences()); - } - } catch (IOException e) { - Logger.getLogger(getClass().getName()).log(Level.SEVERE, "Failed to load history", e); + public synchronized History getCompleteHistory() throws IOException { + if (persistentHistoryFile.length() <= 0) { + return new History(); } - // add session history - history.addAll(sessionHistory.sequences()); - - return history; + RandomAccessFile f = new RandomAccessFile(persistentHistoryFile, "rw"); + FileChannel channel = f.getChannel(); + FileLock lock = channel.lock(); + try { + ByteBufferOutputStream data = new ByteBufferOutputStream(f.length()); + data.transferFully(channel); + + History history = History.importHistory(new ByteBufferInputStream(data.getByteBuffer())); + history.addAll(sessionHistory.sequences()); + return history; + } finally { + lock.release(); + channel.close(); + f.close(); + } } - public History getSessionHistory() { - return sessionHistory; + public synchronized void commit() { + if (sessionHistory.sequences().isEmpty()) { + return; + } + + try { + if (persistentHistoryFile.length() <= 0) { + persistentHistoryFile.createNewFile(); + } + RandomAccessFile f = new RandomAccessFile(persistentHistoryFile, "rw"); + FileChannel channel = f.getChannel(); + FileLock lock = channel.lock(); + try { + ByteBufferOutputStream data = new ByteBufferOutputStream(f.length()); + int read = data.transferFully(channel); + + History history = read > 0 ? History.importHistory(new ByteBufferInputStream(data.getByteBuffer())) : new History(); + history.addAll(sessionHistory.sequences()); + + data.rewind(); + History.exportHistory(history, data); + + channel.position(0); + channel.write(data.getByteBuffer()); + + sessionHistory.clear(); + persistentHistoryTotalSize = history.totalSize(); + } catch (Exception e) { + e.printStackTrace(); + } finally { + lock.release(); + channel.close(); + f.close(); + } + } catch (Exception e) { + Logger.getLogger(getClass().getName()).log(Level.SEVERE, "Failed to write rename history.", e); + } } @@ -66,71 +112,13 @@ public final class HistorySpooler { } - public synchronized void commit(History history) { - try { - if (getPersistentHistory() != null) { - getPersistentHistory().write(history); - } - - // clear session history - sessionHistory.clear(); - } catch (IOException e) { - Logger.getLogger(getClass().getName()).log(Level.SEVERE, "Failed to store history", e); - } + public History getSessionHistory() { + return sessionHistory; } - public synchronized void commit() { - // check if session history is not empty - if (sessionHistory.sequences().size() > 0) { - commit(getCompleteHistory()); - } - } - - - public synchronized void setPersistentHistory(HistoryStorage persistentHistory) { - this.persistentHistory = persistentHistory; - } - - - public synchronized HistoryStorage getPersistentHistory() { - return persistentHistory; - } - - - public static interface HistoryStorage { - - History read() throws IOException; - - - void write(History history) throws IOException; - } - - - public static class HistoryFileStorage implements HistoryStorage { - - private final File file; - - - public HistoryFileStorage(File file) { - this.file = file; - } - - - @Override - public History read() throws IOException { - if (file.exists()) { - return importHistory(file); - } else { - return new History(); - } - } - - - @Override - public void write(History history) throws IOException { - exportHistory(history, file); - } + public int getPersistentHistoryTotalSize() { + return persistentHistoryTotalSize; } } diff --git a/source/net/sourceforge/filebot/Main.java b/source/net/sourceforge/filebot/Main.java index 7ba66588..d74b2a0a 100644 --- a/source/net/sourceforge/filebot/Main.java +++ b/source/net/sourceforge/filebot/Main.java @@ -32,7 +32,6 @@ import java.util.Collections; import java.util.List; import java.util.Locale; import java.util.Properties; -import java.util.Random; import java.util.logging.Level; import java.util.logging.Logger; @@ -49,8 +48,6 @@ import javax.xml.parsers.DocumentBuilderFactory; import net.miginfocom.swing.MigLayout; import net.sf.ehcache.CacheManager; -import net.sourceforge.filebot.HistorySpooler.HistoryFileStorage; -import net.sourceforge.filebot.HistorySpooler.HistoryStorage; import net.sourceforge.filebot.cli.ArgumentBean; import net.sourceforge.filebot.cli.ArgumentProcessor; import net.sourceforge.filebot.cli.CmdlineOperations; @@ -98,7 +95,6 @@ public class Main { // initialize this stuff before anything else initializeCache(); initializeSecurityManager(); - HistorySpooler.getInstance().setPersistentHistory(new HistoryFileStorage(new File(getApplicationFolder(), "history.xml"))); if (args.clearUserData()) { System.out.println("Reset preferences"); @@ -207,48 +203,6 @@ public class Main { Logger.getLogger(Main.class.getName()).log(Level.WARNING, "Failed to check for updates", e); } } - - // hook donation reminder into rename history - if (useDonationReminder()) { - final HistoryStorage fileStorage = HistorySpooler.getInstance().getPersistentHistory(); - HistorySpooler.getInstance().setPersistentHistory(new HistoryStorage() { - - @Override - public void write(History history) throws IOException { - // store history - fileStorage.write(history); - - // display donation reminder - try { - PreferencesEntry persistentDonateLv = Settings.forPackage(Main.class).entry("donate.lv").defaultValue("0"); - int donateLv = Integer.parseInt(persistentDonateLv.getValue()); - int donateStep = 1000; - int usage = history.totalSize(); - - if (usage / donateStep > donateLv || new Random().nextInt(100) == 42) { - persistentDonateLv.setValue(String.valueOf(Math.max(donateLv + 1, usage / donateStep))); - - String message = String.format(Locale.ROOT, "

Thank you for using FileBot!


It has taken many nights to develop this application. If you enjoy using it,
please consider a donation to the author of this software. It will help to
make FileBot even better!

You've renamed %,d files.


", history.totalSize()); - String[] actions = new String[] { "Donate!", "Later" }; - JOptionPane pane = new JOptionPane(message, INFORMATION_MESSAGE, YES_NO_OPTION, ResourceManager.getIcon("message.donate"), actions, actions[0]); - pane.createDialog(null, "Please Donate").setVisible(true); - if (pane.getValue() == actions[0]) { - Desktop.getDesktop().browse(URI.create(getApplicationProperty("donate.url"))); - } - Analytics.trackEvent("GUI", "Donate", "Lv " + donateLv, pane.getValue() == actions[0] ? 1 : 0); - } - } catch (Exception e) { - Logger.getLogger(Main.class.getName()).log(Level.WARNING, e.getMessage(), e); - } - } - - - @Override - public History read() throws IOException { - return fileStorage.read(); - } - }); - } } catch (CmdLineException e) { // illegal arguments => just print CLI error message and stop System.err.println(e.getMessage()); @@ -276,6 +230,11 @@ public class Main { public void windowClosing(WindowEvent e) { e.getWindow().setVisible(false); HistorySpooler.getInstance().commit(); + + if (useDonationReminder()) { + showDonationReminder(); + } + System.exit(0); } }); @@ -371,6 +330,38 @@ public class Main { } + private static void showDonationReminder() { + int renameCount = HistorySpooler.getInstance().getPersistentHistoryTotalSize(); + if (renameCount <= 0) + return; + + PreferencesEntry donation = Settings.forPackage(Main.class).entry("donation").defaultValue("0"); + int donationRev = Integer.parseInt(donation.getValue()); + int currentRev = getApplicationRevisionNumber(); + if (donationRev >= currentRev) { + return; + } + + String message = String.format(Locale.ROOT, "

Thank you for using FileBot!


It has taken many nights to develop this application. If you enjoy using it,
please consider a donation to the author of this software. It will help to
make FileBot even better!

You've renamed %,d files.


", renameCount); + String[] actions = new String[] { "Donate!", "Later" }; + JOptionPane pane = new JOptionPane(message, INFORMATION_MESSAGE, YES_NO_OPTION, ResourceManager.getIcon("message.donate"), actions, actions[0]); + pane.createDialog(null, "Please Donate").setVisible(true); + if (pane.getValue() == actions[0]) { + try { + Desktop.getDesktop().browse(URI.create(getApplicationProperty("donate.url"))); + donation.setValue(String.valueOf(currentRev)); + } catch (Exception e) { + Logger.getLogger(Main.class.getName()).log(Level.SEVERE, "Failed to open URL.", e); + } + } else { + if (donationRev > 0 && donationRev < currentRev) { + donation.setValue(String.valueOf(currentRev)); + } + } + Analytics.trackEvent("GUI", "Donate", "r" + currentRev, pane.getValue() == actions[0] ? 1 : 0); + } + + private static void warmupCachedResources() { Thread warmup = new Thread("warmupCachedResources") { diff --git a/source/net/sourceforge/filebot/ui/rename/HistoryDialog.java b/source/net/sourceforge/filebot/ui/rename/HistoryDialog.java index 3f772666..c3976f1f 100644 --- a/source/net/sourceforge/filebot/ui/rename/HistoryDialog.java +++ b/source/net/sourceforge/filebot/ui/rename/HistoryDialog.java @@ -17,6 +17,8 @@ import java.awt.event.MouseAdapter; import java.awt.event.MouseEvent; import java.awt.event.MouseListener; import java.io.File; +import java.io.FileInputStream; +import java.io.FileOutputStream; import java.io.IOException; import java.text.DateFormat; import java.util.ArrayList; @@ -635,7 +637,7 @@ class HistoryDialog extends JDialog { try { for (File file : files) { - history.merge(History.importHistory(file)); + history.merge(History.importHistory(new FileInputStream(file))); } } finally { // update view @@ -661,7 +663,7 @@ class HistoryDialog extends JDialog { @Override public void export(File file) throws IOException { - History.exportHistory(getModel(), file); + History.exportHistory(getModel(), new FileOutputStream(file)); } diff --git a/source/net/sourceforge/filebot/ui/rename/RenamePanel.java b/source/net/sourceforge/filebot/ui/rename/RenamePanel.java index a35b0820..cc827b87 100644 --- a/source/net/sourceforge/filebot/ui/rename/RenamePanel.java +++ b/source/net/sourceforge/filebot/ui/rename/RenamePanel.java @@ -407,19 +407,18 @@ public class RenamePanel extends JComponent { protected final Action openHistoryAction = new AbstractAction("Open History", ResourceManager.getIcon("action.report")) { @Override - public void actionPerformed(ActionEvent e) { - History model = HistorySpooler.getInstance().getCompleteHistory(); - - HistoryDialog dialog = new HistoryDialog(getWindow(RenamePanel.this)); - dialog.setLocationRelativeTo(RenamePanel.this); - dialog.setModel(model); - - // show and block - dialog.setVisible(true); - - if (!model.equals(dialog.getModel())) { - // model was changed, switch to the new model - HistorySpooler.getInstance().commit(dialog.getModel()); + public void actionPerformed(ActionEvent evt) { + try { + History model = HistorySpooler.getInstance().getCompleteHistory(); + + HistoryDialog dialog = new HistoryDialog(getWindow(RenamePanel.this)); + dialog.setLocationRelativeTo(RenamePanel.this); + dialog.setModel(model); + + // show and block + dialog.setVisible(true); + } catch (Exception e) { + UILogger.log(Level.WARNING, String.format("%s: %s", getRootCause(e).getClass().getSimpleName(), getRootCauseMessage(e)), e); } } };