* better handling of move/rename operations (display validation dialog, create folders if necessary, working revert)
This commit is contained in:
parent
472ed8aac0
commit
46764f7d63
|
@ -11,7 +11,6 @@ import java.util.Arrays;
|
||||||
import java.util.Collection;
|
import java.util.Collection;
|
||||||
import java.util.Date;
|
import java.util.Date;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map.Entry;
|
|
||||||
|
|
||||||
import javax.xml.bind.JAXBContext;
|
import javax.xml.bind.JAXBContext;
|
||||||
import javax.xml.bind.JAXBException;
|
import javax.xml.bind.JAXBException;
|
||||||
|
@ -93,8 +92,15 @@ class History {
|
||||||
private String to;
|
private String to;
|
||||||
|
|
||||||
|
|
||||||
private Element() {
|
public Element() {
|
||||||
// hide constructor
|
// used by JAXB
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public Element(String from, String to, File dir) {
|
||||||
|
this.from = from;
|
||||||
|
this.to = to;
|
||||||
|
this.dir = dir;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -136,29 +142,10 @@ class History {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
public void add(Iterable<Entry<File, File>> elements) {
|
public void add(Collection<Element> elements) {
|
||||||
Sequence sequence = new Sequence();
|
Sequence sequence = new Sequence();
|
||||||
|
|
||||||
sequence.date = new Date();
|
sequence.date = new Date();
|
||||||
sequence.elements = new ArrayList<Element>();
|
sequence.elements = new ArrayList<Element>(elements);
|
||||||
|
|
||||||
for (Entry<File, File> entry : elements) {
|
|
||||||
File from = entry.getKey();
|
|
||||||
File to = entry.getValue();
|
|
||||||
|
|
||||||
// sanity check, parent folder must be the same for both files
|
|
||||||
if (!from.getParentFile().equals(to.getParentFile())) {
|
|
||||||
throw new IllegalArgumentException(String.format("Illegal entry: ", entry));
|
|
||||||
}
|
|
||||||
|
|
||||||
Element element = new Element();
|
|
||||||
|
|
||||||
element.dir = from.getParentFile();
|
|
||||||
element.from = from.getName();
|
|
||||||
element.to = to.getName();
|
|
||||||
|
|
||||||
sequence.elements.add(element);
|
|
||||||
}
|
|
||||||
|
|
||||||
add(sequence);
|
add(sequence);
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,10 +6,14 @@ import static net.sourceforge.filebot.ui.panel.rename.History.*;
|
||||||
|
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
import java.util.Map.Entry;
|
import java.util.Map.Entry;
|
||||||
import java.util.logging.Level;
|
import java.util.logging.Level;
|
||||||
import java.util.logging.Logger;
|
import java.util.logging.Logger;
|
||||||
|
|
||||||
|
import net.sourceforge.filebot.ui.panel.rename.History.Element;
|
||||||
|
|
||||||
|
|
||||||
final class HistorySpooler {
|
final class HistorySpooler {
|
||||||
|
|
||||||
|
@ -45,9 +49,15 @@ final class HistorySpooler {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
public synchronized void append(Iterable<Entry<File, File>> elements) {
|
public synchronized void append(Iterable<Entry<File, String>> elements) {
|
||||||
|
List<Element> sequence = new ArrayList<Element>();
|
||||||
|
|
||||||
|
for (Entry<File, String> element : elements) {
|
||||||
|
sequence.add(new Element(element.getKey().getName(), element.getValue(), element.getKey().getParentFile()));
|
||||||
|
}
|
||||||
|
|
||||||
// append to session history
|
// append to session history
|
||||||
sessionHistory.add(elements);
|
sessionHistory.add(sequence);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -37,13 +37,12 @@ class RenameAction extends AbstractAction {
|
||||||
|
|
||||||
|
|
||||||
public void actionPerformed(ActionEvent evt) {
|
public void actionPerformed(ActionEvent evt) {
|
||||||
List<Entry<File, File>> renameLog = new ArrayList<Entry<File, File>>();
|
List<Entry<File, String>> renameLog = new ArrayList<Entry<File, String>>();
|
||||||
|
|
||||||
try {
|
try {
|
||||||
for (Entry<File, File> mapping : validate(model.getRenameMap(), getWindow(evt.getSource()))) {
|
for (Entry<File, String> mapping : validate(model.getRenameMap(), getWindow(evt.getSource()))) {
|
||||||
// rename file
|
// rename file, throw exception on failure
|
||||||
if (!mapping.getKey().renameTo(mapping.getValue()))
|
rename(mapping.getKey(), mapping.getValue());
|
||||||
throw new IOException(String.format("Failed to rename file: \"%s\".", mapping.getKey().getName()));
|
|
||||||
|
|
||||||
// remember successfully renamed matches for history entry and possible revert
|
// remember successfully renamed matches for history entry and possible revert
|
||||||
renameLog.add(mapping);
|
renameLog.add(mapping);
|
||||||
|
@ -58,21 +57,27 @@ class RenameAction extends AbstractAction {
|
||||||
Logger.getLogger("ui").warning(e.getMessage());
|
Logger.getLogger("ui").warning(e.getMessage());
|
||||||
|
|
||||||
// revert rename operations in reverse order
|
// revert rename operations in reverse order
|
||||||
for (ListIterator<Entry<File, File>> iterator = renameLog.listIterator(renameLog.size()); iterator.hasPrevious();) {
|
for (ListIterator<Entry<File, String>> iterator = renameLog.listIterator(renameLog.size()); iterator.hasPrevious();) {
|
||||||
Entry<File, File> mapping = iterator.previous();
|
Entry<File, String> mapping = iterator.previous();
|
||||||
|
|
||||||
|
try {
|
||||||
|
File source = mapping.getKey();
|
||||||
|
File destination = new File(source.getParentFile(), mapping.getValue());
|
||||||
|
|
||||||
|
// revert rename
|
||||||
|
rename(destination, source.getName());
|
||||||
|
|
||||||
if (mapping.getValue().renameTo(mapping.getKey())) {
|
|
||||||
// remove reverted rename operation from log
|
// remove reverted rename operation from log
|
||||||
iterator.remove();
|
iterator.remove();
|
||||||
} else {
|
} catch (IOException ioe) {
|
||||||
// failed to revert rename operation
|
// failed to revert rename operation
|
||||||
Logger.getLogger("ui").severe(String.format("Failed to revert file: \"%s\".", mapping.getValue().getName()));
|
Logger.getLogger("ui").severe(String.format("Failed to revert file: \"%s\".", mapping.getValue()));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// remove renamed matches
|
// remove renamed matches
|
||||||
for (Entry<File, File> entry : renameLog) {
|
for (Entry<File, ?> entry : renameLog) {
|
||||||
// find index of source file
|
// find index of source file
|
||||||
int index = model.files().indexOf(entry.getKey());
|
int index = model.files().indexOf(entry.getKey());
|
||||||
|
|
||||||
|
@ -87,26 +92,40 @@ class RenameAction extends AbstractAction {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
private Iterable<Entry<File, File>> validate(Map<File, File> renameMap, Window parent) {
|
private File rename(File file, String name) throws IOException {
|
||||||
final List<Entry<File, File>> source = new ArrayList<Entry<File, File>>(renameMap.entrySet());
|
// same folder, different name
|
||||||
|
File destination = new File(file.getParentFile(), name);
|
||||||
|
File destinationFolder = destination.getParentFile();
|
||||||
|
|
||||||
|
// create parent folder if necessary
|
||||||
|
if (!destinationFolder.isDirectory()) {
|
||||||
|
if (!destinationFolder.mkdirs()) {
|
||||||
|
throw new IOException("Failed to create folder: " + destinationFolder);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!file.renameTo(destination)) {
|
||||||
|
throw new IOException("Failed to rename file: " + file.getName());
|
||||||
|
}
|
||||||
|
|
||||||
|
return destination;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private Iterable<Entry<File, String>> validate(Map<File, String> renameMap, Window parent) {
|
||||||
|
final List<Entry<File, String>> source = new ArrayList<Entry<File, String>>(renameMap.entrySet());
|
||||||
|
|
||||||
List<String> destinationFileNameView = new AbstractList<String>() {
|
List<String> destinationFileNameView = new AbstractList<String>() {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String get(int index) {
|
public String get(int index) {
|
||||||
return source.get(index).getValue().getName();
|
return source.get(index).getValue();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String set(int index, String name) {
|
public String set(int index, String name) {
|
||||||
Entry<File, File> entry = source.get(index);
|
return source.get(index).setValue(name);
|
||||||
File old = entry.getValue();
|
|
||||||
|
|
||||||
// update name
|
|
||||||
entry.setValue(new File(old.getParent(), name));
|
|
||||||
|
|
||||||
return old.getName();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -78,8 +78,8 @@ public class RenameModel extends MatchModel<Object, File> {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
public Map<File, File> getRenameMap() {
|
public Map<File, String> getRenameMap() {
|
||||||
Map<File, File> map = new LinkedHashMap<File, File>();
|
Map<File, String> map = new LinkedHashMap<File, String>();
|
||||||
|
|
||||||
for (int i = 0; i < names.size(); i++) {
|
for (int i = 0; i < names.size(); i++) {
|
||||||
if (hasComplement(i)) {
|
if (hasComplement(i)) {
|
||||||
|
@ -108,11 +108,8 @@ public class RenameModel extends MatchModel<Object, File> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// same parent, different name
|
|
||||||
File newFile = new File(originalFile.getParentFile(), nameBuilder.toString());
|
|
||||||
|
|
||||||
// insert mapping
|
// insert mapping
|
||||||
if (map.put(originalFile, newFile) != null) {
|
if (map.put(originalFile, nameBuilder.toString()) != null) {
|
||||||
throw new IllegalStateException(String.format("Duplicate file entry: \"%s\"", originalFile.getName()));
|
throw new IllegalStateException(String.format("Duplicate file entry: \"%s\"", originalFile.getName()));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -91,7 +91,7 @@ public class RenamePanel extends JComponent {
|
||||||
filesList.getListComponent().setCellRenderer(cellrenderer);
|
filesList.getListComponent().setCellRenderer(cellrenderer);
|
||||||
|
|
||||||
EventSelectionModel<Match<Object, File>> selectionModel = new EventSelectionModel<Match<Object, File>>(renameModel.matches());
|
EventSelectionModel<Match<Object, File>> selectionModel = new EventSelectionModel<Match<Object, File>>(renameModel.matches());
|
||||||
selectionModel.setSelectionMode(ListSelection.MULTIPLE_INTERVAL_SELECTION_DEFENSIVE);
|
selectionModel.setSelectionMode(ListSelection.SINGLE_SELECTION);
|
||||||
|
|
||||||
// use the same selection model for both lists to synchronize selection
|
// use the same selection model for both lists to synchronize selection
|
||||||
namesList.getListComponent().setSelectionModel(selectionModel);
|
namesList.getListComponent().setSelectionModel(selectionModel);
|
||||||
|
|
Loading…
Reference in New Issue