* properly lock history.xml file for reading and writing

* revise donation reminders
This commit is contained in:
Reinhard Pointner 2013-03-26 08:43:02 +00:00
parent 3d6578cbaf
commit 1cbdc1f32b
5 changed files with 127 additions and 147 deletions

View File

@ -6,6 +6,8 @@ import static java.util.Collections.*;
import java.io.File; import java.io.File;
import java.io.IOException; import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Arrays; import java.util.Arrays;
import java.util.Collection; 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 { try {
Marshaller marshaller = JAXBContext.newInstance(History.class).createMarshaller(); Marshaller marshaller = JAXBContext.newInstance(History.class).createMarshaller();
marshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, Boolean.TRUE); marshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, Boolean.TRUE);
marshaller.marshal(history, output);
marshaller.marshal(history, file);
} catch (JAXBException e) { } catch (JAXBException e) {
throw new IOException(e); throw new IOException(e);
} }
} }
public static History importHistory(File file) throws IOException { public static History importHistory(InputStream stream) throws IOException {
try { try {
Unmarshaller unmarshaller = JAXBContext.newInstance(History.class).createUnmarshaller(); Unmarshaller unmarshaller = JAXBContext.newInstance(History.class).createUnmarshaller();
return ((History) unmarshaller.unmarshal(stream));
return ((History) unmarshaller.unmarshal(file));
} catch (JAXBException e) { } catch (JAXBException e) {
throw new IOException(e); throw new IOException(e);
} }

View File

@ -2,10 +2,13 @@
package net.sourceforge.filebot; package net.sourceforge.filebot;
import static net.sourceforge.filebot.History.*; import static net.sourceforge.filebot.Settings.*;
import java.io.File; import java.io.File;
import java.io.IOException; 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.ArrayList;
import java.util.List; import java.util.List;
import java.util.Map.Entry; import java.util.Map.Entry;
@ -13,6 +16,8 @@ import java.util.logging.Level;
import java.util.logging.Logger; import java.util.logging.Logger;
import net.sourceforge.filebot.History.Element; import net.sourceforge.filebot.History.Element;
import net.sourceforge.tuned.ByteBufferInputStream;
import net.sourceforge.tuned.ByteBufferOutputStream;
public final class HistorySpooler { public final class HistorySpooler {
@ -24,31 +29,72 @@ public final class HistorySpooler {
return instance; return instance;
} }
private HistoryStorage persistentHistory = null; private File persistentHistoryFile = new File(getApplicationFolder(), "history.xml");
private int persistentHistoryTotalSize = -1;
private History sessionHistory = new History(); private History sessionHistory = new History();
public synchronized History getCompleteHistory() { public synchronized History getCompleteHistory() throws IOException {
History history = new History(); if (persistentHistoryFile.length() <= 0) {
return new History();
}
// add persistent history RandomAccessFile f = new RandomAccessFile(persistentHistoryFile, "rw");
FileChannel channel = f.getChannel();
FileLock lock = channel.lock();
try { try {
if (getPersistentHistory() != null) { ByteBufferOutputStream data = new ByteBufferOutputStream(f.length());
history.addAll(getPersistentHistory().read().sequences()); data.transferFully(channel);
History history = History.importHistory(new ByteBufferInputStream(data.getByteBuffer()));
history.addAll(sessionHistory.sequences());
return history;
} finally {
lock.release();
channel.close();
f.close();
} }
} catch (IOException e) {
Logger.getLogger(getClass().getName()).log(Level.SEVERE, "Failed to load history", e);
} }
// add session history
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()); history.addAll(sessionHistory.sequences());
return history; 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);
} }
public History getSessionHistory() {
return sessionHistory;
} }
@ -66,71 +112,13 @@ public final class HistorySpooler {
} }
public synchronized void commit(History history) { public History getSessionHistory() {
try { return sessionHistory;
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 synchronized void commit() { public int getPersistentHistoryTotalSize() {
// check if session history is not empty return persistentHistoryTotalSize;
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);
}
} }
} }

View File

@ -32,7 +32,6 @@ import java.util.Collections;
import java.util.List; import java.util.List;
import java.util.Locale; import java.util.Locale;
import java.util.Properties; import java.util.Properties;
import java.util.Random;
import java.util.logging.Level; import java.util.logging.Level;
import java.util.logging.Logger; import java.util.logging.Logger;
@ -49,8 +48,6 @@ import javax.xml.parsers.DocumentBuilderFactory;
import net.miginfocom.swing.MigLayout; import net.miginfocom.swing.MigLayout;
import net.sf.ehcache.CacheManager; 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.ArgumentBean;
import net.sourceforge.filebot.cli.ArgumentProcessor; import net.sourceforge.filebot.cli.ArgumentProcessor;
import net.sourceforge.filebot.cli.CmdlineOperations; import net.sourceforge.filebot.cli.CmdlineOperations;
@ -98,7 +95,6 @@ public class Main {
// initialize this stuff before anything else // initialize this stuff before anything else
initializeCache(); initializeCache();
initializeSecurityManager(); initializeSecurityManager();
HistorySpooler.getInstance().setPersistentHistory(new HistoryFileStorage(new File(getApplicationFolder(), "history.xml")));
if (args.clearUserData()) { if (args.clearUserData()) {
System.out.println("Reset preferences"); 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); 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<String> 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, "<html><p style='font-size:16pt; font-weight:bold'>Thank you for using FileBot!</p><br><p>It has taken many nights to develop this application. If you enjoy using it,<br>please consider a donation to the author of this software. It will help to<br>make FileBot even better!<p><p style='font-size:14pt; font-weight:bold'>You've renamed %,d files.</p><br><html>", 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) { } catch (CmdLineException e) {
// illegal arguments => just print CLI error message and stop // illegal arguments => just print CLI error message and stop
System.err.println(e.getMessage()); System.err.println(e.getMessage());
@ -276,6 +230,11 @@ public class Main {
public void windowClosing(WindowEvent e) { public void windowClosing(WindowEvent e) {
e.getWindow().setVisible(false); e.getWindow().setVisible(false);
HistorySpooler.getInstance().commit(); HistorySpooler.getInstance().commit();
if (useDonationReminder()) {
showDonationReminder();
}
System.exit(0); System.exit(0);
} }
}); });
@ -371,6 +330,38 @@ public class Main {
} }
private static void showDonationReminder() {
int renameCount = HistorySpooler.getInstance().getPersistentHistoryTotalSize();
if (renameCount <= 0)
return;
PreferencesEntry<String> 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, "<html><p style='font-size:16pt; font-weight:bold'>Thank you for using FileBot!</p><br><p>It has taken many nights to develop this application. If you enjoy using it,<br>please consider a donation to the author of this software. It will help to<br>make FileBot even better!<p><p style='font-size:14pt; font-weight:bold'>You've renamed %,d files.</p><br><html>", 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() { private static void warmupCachedResources() {
Thread warmup = new Thread("warmupCachedResources") { Thread warmup = new Thread("warmupCachedResources") {

View File

@ -17,6 +17,8 @@ import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent; import java.awt.event.MouseEvent;
import java.awt.event.MouseListener; import java.awt.event.MouseListener;
import java.io.File; import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException; import java.io.IOException;
import java.text.DateFormat; import java.text.DateFormat;
import java.util.ArrayList; import java.util.ArrayList;
@ -635,7 +637,7 @@ class HistoryDialog extends JDialog {
try { try {
for (File file : files) { for (File file : files) {
history.merge(History.importHistory(file)); history.merge(History.importHistory(new FileInputStream(file)));
} }
} finally { } finally {
// update view // update view
@ -661,7 +663,7 @@ class HistoryDialog extends JDialog {
@Override @Override
public void export(File file) throws IOException { public void export(File file) throws IOException {
History.exportHistory(getModel(), file); History.exportHistory(getModel(), new FileOutputStream(file));
} }

View File

@ -407,7 +407,8 @@ public class RenamePanel extends JComponent {
protected final Action openHistoryAction = new AbstractAction("Open History", ResourceManager.getIcon("action.report")) { protected final Action openHistoryAction = new AbstractAction("Open History", ResourceManager.getIcon("action.report")) {
@Override @Override
public void actionPerformed(ActionEvent e) { public void actionPerformed(ActionEvent evt) {
try {
History model = HistorySpooler.getInstance().getCompleteHistory(); History model = HistorySpooler.getInstance().getCompleteHistory();
HistoryDialog dialog = new HistoryDialog(getWindow(RenamePanel.this)); HistoryDialog dialog = new HistoryDialog(getWindow(RenamePanel.this));
@ -416,10 +417,8 @@ public class RenamePanel extends JComponent {
// show and block // show and block
dialog.setVisible(true); dialog.setVisible(true);
} catch (Exception e) {
if (!model.equals(dialog.getModel())) { UILogger.log(Level.WARNING, String.format("%s: %s", getRootCause(e).getClass().getSimpleName(), getRootCauseMessage(e)), e);
// model was changed, switch to the new model
HistorySpooler.getInstance().commit(dialog.getModel());
} }
} }
}; };