* 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) {
|
protected String getMessage(LogRecord record) {
|
||||||
String message = record.getMessage();
|
String message = record.getMessage();
|
||||||
|
|
||||||
if (message == null || message.isEmpty()) {
|
if ((message == null || message.isEmpty()) && record.getThrown() != null) {
|
||||||
// if message is empty, display exception string
|
// if message is empty, display exception string
|
||||||
return ExceptionUtilities.getMessage(record.getThrown());
|
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.FileUtilities.*;
|
||||||
import static net.sourceforge.tuned.ui.TunedUtilities.*;
|
import static net.sourceforge.tuned.ui.TunedUtilities.*;
|
||||||
|
|
||||||
|
import java.awt.Cursor;
|
||||||
import java.awt.Window;
|
import java.awt.Window;
|
||||||
import java.awt.event.ActionEvent;
|
import java.awt.event.ActionEvent;
|
||||||
|
import java.beans.PropertyChangeEvent;
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
import java.util.AbstractList;
|
import java.util.AbstractList;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
|
import java.util.HashMap;
|
||||||
import java.util.HashSet;
|
import java.util.HashSet;
|
||||||
|
import java.util.LinkedHashMap;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.ListIterator;
|
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
import java.util.Set;
|
||||||
|
import java.util.TreeSet;
|
||||||
import java.util.AbstractMap.SimpleEntry;
|
import java.util.AbstractMap.SimpleEntry;
|
||||||
import java.util.Map.Entry;
|
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.AbstractAction;
|
||||||
|
import javax.swing.Icon;
|
||||||
|
import javax.swing.SwingWorker;
|
||||||
|
|
||||||
import net.sourceforge.filebot.Analytics;
|
import net.sourceforge.filebot.Analytics;
|
||||||
import net.sourceforge.filebot.ResourceManager;
|
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 {
|
class RenameAction extends AbstractAction {
|
||||||
|
@ -40,69 +54,71 @@ class RenameAction extends AbstractAction {
|
||||||
|
|
||||||
|
|
||||||
public void actionPerformed(ActionEvent evt) {
|
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);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// renamed all matches successfully
|
try {
|
||||||
if (renameLog.size() > 0) {
|
Window window = getWindow(evt.getSource());
|
||||||
UILogger.info(String.format("%d files renamed.", renameLog.size()));
|
Map<File, File> renameMap = checkRenamePlan(validate(model.getRenameMap(), window));
|
||||||
|
|
||||||
|
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) {
|
} catch (Exception e) {
|
||||||
// could not rename one of the files, revert all changes
|
// could not rename one of the files, revert all changes
|
||||||
UILogger.warning(e.getMessage());
|
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());
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
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());
|
||||||
}
|
}
|
||||||
|
|
||||||
// collect renamed types
|
if (renameMap.containsKey(source))
|
||||||
List<Class> types = new ArrayList<Class>();
|
throw new IllegalArgumentException("Duplicate source file: " + source);
|
||||||
|
|
||||||
// remove renamed matches
|
if (destinationSet.contains(destination))
|
||||||
for (Entry<File, ?> entry : renameLog) {
|
throw new IllegalArgumentException("Conflict detected: " + destination);
|
||||||
// find index of source file
|
|
||||||
int index = model.files().indexOf(entry.getKey());
|
|
||||||
types.add(model.values().get(index).getClass());
|
|
||||||
|
|
||||||
// remove complete match
|
if (destination.exists() && !source.equals(destination))
|
||||||
model.matches().remove(index);
|
throw new IllegalArgumentException("File already exists: " + destination);
|
||||||
|
|
||||||
|
renameMap.put(mapping.getKey(), mapping.getValue());
|
||||||
}
|
}
|
||||||
|
|
||||||
// update history
|
return renameMap;
|
||||||
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 List<Entry<File, File>> validate(Map<File, String> renameMap, Window parent) {
|
||||||
final List<Entry<File, File>> source = new ArrayList<Entry<File, File>>(renameMap.size());
|
final List<Entry<File, File>> source = new ArrayList<Entry<File, File>>(renameMap.size());
|
||||||
|
|
||||||
for (Entry<File, String> entry : renameMap.entrySet()) {
|
for (Entry<File, String> entry : renameMap.entrySet()) {
|
||||||
source.add(new SimpleEntry<File, File>(entry.getKey(), new File(entry.getValue())));
|
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 empty list if validation was cancelled
|
||||||
return emptyList();
|
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) {
|
public void setNote(String text) {
|
||||||
progressBar.setString(text);
|
progressBar.setString(text);
|
||||||
}
|
}
|
||||||
|
@ -68,11 +76,6 @@ public class ProgressDialog extends JDialog {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
public JProgressBar getProgressBar() {
|
|
||||||
return progressBar;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
public void close() {
|
public void close() {
|
||||||
setVisible(false);
|
setVisible(false);
|
||||||
dispose();
|
dispose();
|
||||||
|
@ -83,6 +86,7 @@ public class ProgressDialog extends JDialog {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void actionPerformed(ActionEvent e) {
|
public void actionPerformed(ActionEvent e) {
|
||||||
|
cancelAction.setEnabled(false);
|
||||||
cancellable.cancel();
|
cancellable.cancel();
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
|
@ -11,10 +11,13 @@ import javax.swing.SwingWorker.StateValue;
|
||||||
public abstract class SwingWorkerPropertyChangeAdapter implements PropertyChangeListener {
|
public abstract class SwingWorkerPropertyChangeAdapter implements PropertyChangeListener {
|
||||||
|
|
||||||
public void propertyChange(PropertyChangeEvent evt) {
|
public void propertyChange(PropertyChangeEvent evt) {
|
||||||
if (evt.getPropertyName().equals("progress"))
|
if (evt.getPropertyName().equals("progress")) {
|
||||||
progress(evt);
|
progress(evt);
|
||||||
else if (evt.getPropertyName().equals("state"))
|
} else if (evt.getPropertyName().equals("state")) {
|
||||||
state(evt);
|
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 done(PropertyChangeEvent evt) {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
protected void event(String name, Object oldValue, Object newValue) {
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue