* properly lock history.xml file for reading and writing
* revise donation reminders
This commit is contained in:
parent
3d6578cbaf
commit
1cbdc1f32b
|
@ -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);
|
||||||
}
|
}
|
||||||
|
|
|
@ -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);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -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") {
|
||||||
|
|
||||||
|
|
|
@ -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));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -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());
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
Loading…
Reference in New Issue