mirror of
https://github.com/mitb-archive/filebot
synced 2024-11-04 16:35:08 -05:00
* progress dialog for move/rename job
This commit is contained in:
parent
5184e4d98d
commit
47ac797ec3
@ -82,7 +82,7 @@ public class NotificationLogging extends Handler {
|
||||
protected String getMessage(LogRecord record) {
|
||||
String message = record.getMessage();
|
||||
|
||||
if (message == null || message.isEmpty()) {
|
||||
if ((message == null || message.isEmpty()) && record.getThrown() != null) {
|
||||
// if message is empty, display exception string
|
||||
return ExceptionUtilities.getMessage(record.getThrown());
|
||||
}
|
||||
|
@ -7,22 +7,36 @@ import static net.sourceforge.filebot.ui.NotificationLogging.*;
|
||||
import static net.sourceforge.tuned.FileUtilities.*;
|
||||
import static net.sourceforge.tuned.ui.TunedUtilities.*;
|
||||
|
||||
import java.awt.Cursor;
|
||||
import java.awt.Window;
|
||||
import java.awt.event.ActionEvent;
|
||||
import java.beans.PropertyChangeEvent;
|
||||
import java.io.File;
|
||||
import java.util.AbstractList;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
import java.util.ListIterator;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.TreeSet;
|
||||
import java.util.AbstractMap.SimpleEntry;
|
||||
import java.util.Map.Entry;
|
||||
import java.util.concurrent.CancellationException;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.concurrent.TimeoutException;
|
||||
import java.util.logging.Level;
|
||||
|
||||
import javax.swing.AbstractAction;
|
||||
import javax.swing.Icon;
|
||||
import javax.swing.SwingWorker;
|
||||
|
||||
import net.sourceforge.filebot.Analytics;
|
||||
import net.sourceforge.filebot.ResourceManager;
|
||||
import net.sourceforge.tuned.ui.ProgressDialog;
|
||||
import net.sourceforge.tuned.ui.SwingWorkerPropertyChangeAdapter;
|
||||
import net.sourceforge.tuned.ui.ProgressDialog.Cancellable;
|
||||
|
||||
|
||||
class RenameAction extends AbstractAction {
|
||||
@ -40,69 +54,71 @@ class RenameAction extends AbstractAction {
|
||||
|
||||
|
||||
public void actionPerformed(ActionEvent evt) {
|
||||
List<Entry<File, File>> renameLog = new ArrayList<Entry<File, File>>();
|
||||
if (model.getRenameMap().isEmpty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
for (Entry<File, File> mapping : validate(model.getRenameMap(), getWindow(evt.getSource()))) {
|
||||
// rename file, throw exception on failure
|
||||
renameFile(mapping.getKey(), mapping.getValue());
|
||||
|
||||
// remember successfully renamed matches for history entry and possible revert
|
||||
renameLog.add(mapping);
|
||||
}
|
||||
Window window = getWindow(evt.getSource());
|
||||
Map<File, File> renameMap = checkRenamePlan(validate(model.getRenameMap(), window));
|
||||
|
||||
// renamed all matches successfully
|
||||
if (renameLog.size() > 0) {
|
||||
UILogger.info(String.format("%d files renamed.", renameLog.size()));
|
||||
window.setCursor(Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR));
|
||||
RenameJob renameJob = new RenameJob(renameMap);
|
||||
renameJob.execute();
|
||||
|
||||
try {
|
||||
// wait a for little while (renaming might finish in less than a second)
|
||||
renameJob.get(2, TimeUnit.SECONDS);
|
||||
} catch (TimeoutException ex) {
|
||||
// move/renaming will probably take a while
|
||||
ProgressDialog dialog = createProgressDialog(window, renameJob);
|
||||
dialog.setLocation(getOffsetLocation(dialog.getOwner()));
|
||||
|
||||
// display progress dialog and stop blocking EDT
|
||||
window.setCursor(Cursor.getDefaultCursor());
|
||||
dialog.setVisible(true);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
// could not rename one of the files, revert all changes
|
||||
UILogger.warning(e.getMessage());
|
||||
|
||||
// revert rename operations in reverse order
|
||||
for (ListIterator<Entry<File, File>> iterator = renameLog.listIterator(renameLog.size()); iterator.hasPrevious();) {
|
||||
Entry<File, File> mapping = iterator.previous();
|
||||
|
||||
// revert rename
|
||||
File original = mapping.getKey();
|
||||
File current = new File(original.getParentFile(), mapping.getValue().getPath());
|
||||
|
||||
if (current.renameTo(original)) {
|
||||
// remove reverted rename operation from log
|
||||
iterator.remove();
|
||||
} else {
|
||||
// failed to revert rename operation
|
||||
UILogger.severe("Failed to revert file: " + mapping.getValue());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// collect renamed types
|
||||
List<Class> types = new ArrayList<Class>();
|
||||
|
||||
// remove renamed matches
|
||||
for (Entry<File, ?> entry : renameLog) {
|
||||
// find index of source file
|
||||
int index = model.files().indexOf(entry.getKey());
|
||||
types.add(model.values().get(index).getClass());
|
||||
|
||||
// remove complete match
|
||||
model.matches().remove(index);
|
||||
}
|
||||
|
||||
// update history
|
||||
if (renameLog.size() > 0) {
|
||||
HistorySpooler.getInstance().append(renameLog);
|
||||
|
||||
for (Class it : new HashSet<Class>(types)) {
|
||||
Analytics.trackEvent("GUI", "Rename", it.getSimpleName(), frequency(types, it));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private Iterable<Entry<File, File>> validate(Map<File, String> renameMap, Window parent) {
|
||||
private Map<File, File> checkRenamePlan(List<Entry<File, File>> renamePlan) {
|
||||
// build rename map and perform some sanity checks
|
||||
Map<File, File> renameMap = new HashMap<File, File>();
|
||||
Set<File> destinationSet = new TreeSet<File>();
|
||||
|
||||
for (Entry<File, File> mapping : renamePlan) {
|
||||
File source = mapping.getKey();
|
||||
File destination = mapping.getValue();
|
||||
|
||||
// resolve destination
|
||||
if (!destination.isAbsolute()) {
|
||||
// same folder, different name
|
||||
destination = new File(source.getParentFile(), destination.getPath());
|
||||
}
|
||||
|
||||
if (renameMap.containsKey(source))
|
||||
throw new IllegalArgumentException("Duplicate source file: " + source);
|
||||
|
||||
if (destinationSet.contains(destination))
|
||||
throw new IllegalArgumentException("Conflict detected: " + destination);
|
||||
|
||||
if (destination.exists() && !source.equals(destination))
|
||||
throw new IllegalArgumentException("File already exists: " + destination);
|
||||
|
||||
renameMap.put(mapping.getKey(), mapping.getValue());
|
||||
}
|
||||
|
||||
return renameMap;
|
||||
}
|
||||
|
||||
|
||||
private List<Entry<File, File>> validate(Map<File, String> renameMap, Window parent) {
|
||||
final List<Entry<File, File>> source = new ArrayList<Entry<File, File>>(renameMap.size());
|
||||
|
||||
for (Entry<File, String> entry : renameMap.entrySet()) {
|
||||
source.add(new SimpleEntry<File, File>(entry.getKey(), new File(entry.getValue())));
|
||||
}
|
||||
@ -135,4 +151,113 @@ class RenameAction extends AbstractAction {
|
||||
// return empty list if validation was cancelled
|
||||
return emptyList();
|
||||
}
|
||||
|
||||
|
||||
protected ProgressDialog createProgressDialog(Window parent, final RenameJob job) {
|
||||
final ProgressDialog dialog = new ProgressDialog(parent, job);
|
||||
|
||||
// configure dialog
|
||||
dialog.setTitle("Renaming...");
|
||||
dialog.setTitle("Moving files...");
|
||||
dialog.setIcon((Icon) getValue(SMALL_ICON));
|
||||
|
||||
// close progress dialog when worker is finished
|
||||
job.addPropertyChangeListener(new SwingWorkerPropertyChangeAdapter() {
|
||||
|
||||
@Override
|
||||
protected void event(String name, Object oldValue, Object newValue) {
|
||||
if (name.equals("currentFile")) {
|
||||
int i = job.renameLog.size();
|
||||
int n = job.renameMap.size();
|
||||
dialog.setProgress(i, n);
|
||||
dialog.setNote(String.format("%d of %d", i + 1, n));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
protected void done(PropertyChangeEvent evt) {
|
||||
dialog.close();
|
||||
}
|
||||
});
|
||||
|
||||
return dialog;
|
||||
}
|
||||
|
||||
|
||||
protected class RenameJob extends SwingWorker<Map<File, File>, Void> implements Cancellable {
|
||||
|
||||
private final Map<File, File> renameMap;
|
||||
private final Map<File, File> renameLog;
|
||||
|
||||
|
||||
public RenameJob(Map<File, File> renameMap) {
|
||||
this.renameMap = synchronizedMap(renameMap);
|
||||
this.renameLog = synchronizedMap(new LinkedHashMap<File, File>());
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
protected Map<File, File> doInBackground() throws Exception {
|
||||
for (Entry<File, File> mapping : renameMap.entrySet()) {
|
||||
|
||||
if (isCancelled())
|
||||
return renameLog;
|
||||
|
||||
// update progress dialog
|
||||
firePropertyChange("currentFile", mapping.getKey(), mapping.getValue());
|
||||
|
||||
// rename file, throw exception on failure
|
||||
renameFile(mapping.getKey(), mapping.getValue());
|
||||
|
||||
// remember successfully renamed matches for history entry and possible revert
|
||||
renameLog.put(mapping.getKey(), mapping.getValue());
|
||||
}
|
||||
|
||||
return renameLog;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
protected void done() {
|
||||
try {
|
||||
get(); // check exceptions
|
||||
} catch (CancellationException e) {
|
||||
// ignore
|
||||
} catch (Exception e) {
|
||||
UILogger.log(Level.SEVERE, e.getMessage(), e);
|
||||
}
|
||||
|
||||
// collect renamed types
|
||||
List<Class> types = new ArrayList<Class>();
|
||||
|
||||
// remove renamed matches
|
||||
for (File source : renameLog.keySet()) {
|
||||
// find index of source file
|
||||
int index = model.files().indexOf(source);
|
||||
types.add(model.values().get(index).getClass());
|
||||
|
||||
// remove complete match
|
||||
model.matches().remove(index);
|
||||
}
|
||||
|
||||
if (renameLog.size() > 0) {
|
||||
UILogger.info(String.format("%d files renamed.", renameLog.size()));
|
||||
HistorySpooler.getInstance().append(renameLog.entrySet());
|
||||
|
||||
// count global statistics
|
||||
for (Class it : new HashSet<Class>(types)) {
|
||||
Analytics.trackEvent("GUI", "Rename", it.getSimpleName(), frequency(types, it));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public boolean cancel() {
|
||||
return cancel(true);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -56,6 +56,14 @@ public class ProgressDialog extends JDialog {
|
||||
}
|
||||
|
||||
|
||||
public void setProgress(int value, int max) {
|
||||
progressBar.setIndeterminate(false);
|
||||
progressBar.setMinimum(0);
|
||||
progressBar.setValue(value);
|
||||
progressBar.setMaximum(max);
|
||||
}
|
||||
|
||||
|
||||
public void setNote(String text) {
|
||||
progressBar.setString(text);
|
||||
}
|
||||
@ -68,11 +76,6 @@ public class ProgressDialog extends JDialog {
|
||||
}
|
||||
|
||||
|
||||
public JProgressBar getProgressBar() {
|
||||
return progressBar;
|
||||
}
|
||||
|
||||
|
||||
public void close() {
|
||||
setVisible(false);
|
||||
dispose();
|
||||
@ -83,6 +86,7 @@ public class ProgressDialog extends JDialog {
|
||||
|
||||
@Override
|
||||
public void actionPerformed(ActionEvent e) {
|
||||
cancelAction.setEnabled(false);
|
||||
cancellable.cancel();
|
||||
}
|
||||
};
|
||||
|
@ -11,10 +11,13 @@ import javax.swing.SwingWorker.StateValue;
|
||||
public abstract class SwingWorkerPropertyChangeAdapter implements PropertyChangeListener {
|
||||
|
||||
public void propertyChange(PropertyChangeEvent evt) {
|
||||
if (evt.getPropertyName().equals("progress"))
|
||||
if (evt.getPropertyName().equals("progress")) {
|
||||
progress(evt);
|
||||
else if (evt.getPropertyName().equals("state"))
|
||||
} else if (evt.getPropertyName().equals("state")) {
|
||||
state(evt);
|
||||
} else {
|
||||
event(evt.getPropertyName(), evt.getOldValue(), evt.getNewValue());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -41,4 +44,8 @@ public abstract class SwingWorkerPropertyChangeAdapter implements PropertyChange
|
||||
protected void done(PropertyChangeEvent evt) {
|
||||
}
|
||||
|
||||
|
||||
protected void event(String name, Object oldValue, Object newValue) {
|
||||
}
|
||||
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user