mirror of
https://github.com/mitb-archive/filebot
synced 2025-01-11 05:48:01 -05:00
+ Auto-delete left behind empty folders when moving files into a new structure
This commit is contained in:
parent
3fd7d34647
commit
cfe7fc69a3
@ -11,7 +11,7 @@ import static net.filebot.util.ExceptionUtilities.*;
|
|||||||
import static net.filebot.util.FileUtilities.*;
|
import static net.filebot.util.FileUtilities.*;
|
||||||
import static net.filebot.util.ui.SwingUI.*;
|
import static net.filebot.util.ui.SwingUI.*;
|
||||||
|
|
||||||
import java.awt.Cursor;
|
import java.awt.Dialog.ModalityType;
|
||||||
import java.awt.Dimension;
|
import java.awt.Dimension;
|
||||||
import java.awt.Window;
|
import java.awt.Window;
|
||||||
import java.awt.event.ActionEvent;
|
import java.awt.event.ActionEvent;
|
||||||
@ -25,11 +25,11 @@ import java.util.ArrayList;
|
|||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.HashSet;
|
import java.util.HashSet;
|
||||||
import java.util.LinkedHashMap;
|
import java.util.LinkedHashMap;
|
||||||
import java.util.LinkedHashSet;
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.Map.Entry;
|
import java.util.Map.Entry;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
|
import java.util.TreeSet;
|
||||||
import java.util.concurrent.CancellationException;
|
import java.util.concurrent.CancellationException;
|
||||||
import java.util.concurrent.ExecutionException;
|
import java.util.concurrent.ExecutionException;
|
||||||
import java.util.concurrent.Semaphore;
|
import java.util.concurrent.Semaphore;
|
||||||
@ -75,104 +75,115 @@ class RenameAction extends AbstractAction {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void actionPerformed(ActionEvent evt) {
|
public void actionPerformed(ActionEvent evt) {
|
||||||
Window window = getWindow(evt.getSource());
|
|
||||||
try {
|
try {
|
||||||
if (model.files().isEmpty() || model.values().isEmpty()) {
|
Window window = getWindow(evt.getSource());
|
||||||
log.info("Nothing to rename. Please add some files and fetch naming data first.");
|
withWaitCursor(window, () -> {
|
||||||
return;
|
if (model.files().isEmpty() || model.values().isEmpty()) {
|
||||||
}
|
log.info("Nothing to rename. Please add some files and fetch naming data first.");
|
||||||
|
return;
|
||||||
Map<File, File> renameMap = checkRenamePlan(validate(model.getRenameMap(), window), window);
|
|
||||||
if (renameMap.isEmpty()) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// start processing
|
|
||||||
List<Match<Object, File>> matches = new ArrayList<Match<Object, File>>(model.matches());
|
|
||||||
StandardRenameAction action = (StandardRenameAction) getValue(RENAME_ACTION);
|
|
||||||
|
|
||||||
window.setCursor(Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR));
|
|
||||||
|
|
||||||
if (useNativeShell() && isNativeActionSupported(action)) {
|
|
||||||
RenameJob renameJob = new NativeRenameJob(renameMap, NativeRenameAction.valueOf(action.name()));
|
|
||||||
renameJob.execute();
|
|
||||||
try {
|
|
||||||
renameJob.get(); // wait for native operation to finish or be cancelled
|
|
||||||
} catch (CancellationException e) {
|
|
||||||
// ignore
|
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
RenameJob renameJob = new RenameJob(renameMap, action);
|
|
||||||
renameJob.execute();
|
|
||||||
|
|
||||||
try {
|
Map<File, File> renameMap = checkRenamePlan(validate(model.getRenameMap(), window), window);
|
||||||
// wait a for little while (renaming might finish in less than a second)
|
if (renameMap.isEmpty()) {
|
||||||
renameJob.get(2, TimeUnit.SECONDS);
|
return;
|
||||||
} catch (TimeoutException ex) {
|
|
||||||
// move/renaming will probably take a while
|
|
||||||
ProgressDialog dialog = createProgressDialog(window, renameJob);
|
|
||||||
dialog.setLocation(getOffsetLocation(dialog.getOwner()));
|
|
||||||
dialog.setIndeterminate(true);
|
|
||||||
|
|
||||||
// display progress dialog and stop blocking EDT
|
|
||||||
window.setCursor(Cursor.getDefaultCursor());
|
|
||||||
dialog.setVisible(true);
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
// store xattr
|
// start processing
|
||||||
storeMetaInfo(renameMap, matches);
|
List<Match<Object, File>> matches = new ArrayList<Match<Object, File>>(model.matches());
|
||||||
|
StandardRenameAction action = (StandardRenameAction) getValue(RENAME_ACTION);
|
||||||
|
|
||||||
// delete empty folders
|
if (useNativeShell() && isNativeActionSupported(action)) {
|
||||||
if (action == StandardRenameAction.MOVE) {
|
RenameJob renameJob = new NativeRenameJob(renameMap, NativeRenameAction.valueOf(action.name()));
|
||||||
deleteEmptyFolders(renameMap);
|
renameJob.execute();
|
||||||
}
|
|
||||||
|
// wait for native operation to finish or be cancelled
|
||||||
|
try {
|
||||||
|
renameJob.get();
|
||||||
|
} catch (CancellationException e) {
|
||||||
|
debug.finest(e::toString);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
RenameJob renameJob = new RenameJob(renameMap, action);
|
||||||
|
renameJob.execute();
|
||||||
|
|
||||||
|
// wait a little while (renaming might finish in less than a second)
|
||||||
|
try {
|
||||||
|
renameJob.get(2, TimeUnit.SECONDS);
|
||||||
|
} catch (TimeoutException e) {
|
||||||
|
// display progress dialog because move/rename might take a while
|
||||||
|
ProgressDialog dialog = createProgressDialog(window, renameJob);
|
||||||
|
dialog.setModalityType(ModalityType.APPLICATION_MODAL);
|
||||||
|
dialog.setLocation(getOffsetLocation(dialog.getOwner()));
|
||||||
|
dialog.setIndeterminate(true);
|
||||||
|
dialog.setVisible(true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// store xattr
|
||||||
|
storeMetaInfo(renameMap, matches);
|
||||||
|
|
||||||
|
// delete empty folders
|
||||||
|
if (action == StandardRenameAction.MOVE) {
|
||||||
|
deleteEmptyFolders(renameMap);
|
||||||
|
}
|
||||||
|
});
|
||||||
} catch (ExecutionException e) {
|
} catch (ExecutionException e) {
|
||||||
// ignore, handled in rename worker
|
// ignore, handled in rename worker
|
||||||
} catch (Throwable e) {
|
} catch (Throwable e) {
|
||||||
log.log(Level.WARNING, e.getMessage(), e);
|
log.log(Level.WARNING, e.getMessage(), e);
|
||||||
}
|
}
|
||||||
|
|
||||||
window.setCursor(Cursor.getDefaultCursor());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void storeMetaInfo(Map<File, File> renameMap, List<Match<Object, File>> matches) {
|
private void storeMetaInfo(Map<File, File> renameMap, List<Match<Object, File>> matches) {
|
||||||
// write metadata into xattr if xattr is enabled
|
// write metadata into xattr if xattr is enabled
|
||||||
for (Match<Object, File> match : matches) {
|
for (Match<Object, File> match : matches) {
|
||||||
File file = match.getCandidate();
|
File file = match.getCandidate();
|
||||||
Object meta = match.getValue();
|
Object info = match.getValue();
|
||||||
if (renameMap.containsKey(file) && meta != null) {
|
File destination = renameMap.get(file);
|
||||||
File destination = resolve(file, renameMap.get(file));
|
if (info != null && destination != null) {
|
||||||
|
destination = resolve(file, destination);
|
||||||
if (destination.isFile()) {
|
if (destination.isFile()) {
|
||||||
xattr.setMetaInfo(destination, meta, file.getName());
|
String original = file.getName();
|
||||||
|
debug.finest(format("Store xattr: [%s, %s] => %s", info, original, destination));
|
||||||
|
xattr.setMetaInfo(destination, info, original);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void deleteEmptyFolders(Map<File, File> renameMap) {
|
private void deleteEmptyFolders(Map<File, File> renameMap) {
|
||||||
// collect newly empty folders
|
// collect empty folders and files in reverse order
|
||||||
Set<File> deleteSet = new LinkedHashSet<File>();
|
Set<File> empty = new TreeSet<File>(reverseOrder());
|
||||||
|
|
||||||
renameMap.forEach((s, d) -> {
|
renameMap.forEach((s, d) -> {
|
||||||
File sourceFolder = s.getParentFile();
|
File sourceFolder = s.getParentFile();
|
||||||
File destinationFolder = resolve(s, d).getParentFile();
|
File destinationFolder = resolve(s, d).getParentFile();
|
||||||
|
|
||||||
if (!destinationFolder.getPath().startsWith(sourceFolder.getPath())) {
|
// destination folder is the source, or is inside the source folder
|
||||||
int relativePathSize = listPath(d).size() - 1;
|
if (destinationFolder.getPath().startsWith(sourceFolder.getPath())) {
|
||||||
for (int i = 0; i < relativePathSize && sourceFolder != null && !isVolumeRoot(sourceFolder); sourceFolder = sourceFolder.getParentFile(), i++) {
|
return;
|
||||||
File[] children = sourceFolder.listFiles();
|
}
|
||||||
if (children == null || !stream(children).allMatch(f -> f.isHidden() || deleteSet.contains(f))) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
stream(children).forEach(deleteSet::add);
|
// guess affected folder depth
|
||||||
deleteSet.add(sourceFolder);
|
int relativePathSize = 0;
|
||||||
|
try {
|
||||||
|
relativePathSize = listStructurePathTail(s).size();
|
||||||
|
} catch (Exception e) {
|
||||||
|
debug.warning(e::toString);
|
||||||
|
}
|
||||||
|
|
||||||
|
for (int i = 0; i < relativePathSize && !isVolumeRoot(sourceFolder); sourceFolder = sourceFolder.getParentFile(), i++) {
|
||||||
|
File[] children = sourceFolder.listFiles();
|
||||||
|
if (children == null || !stream(children).allMatch(f -> empty.contains(f) || f.isHidden())) {
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
stream(children).forEach(empty::add);
|
||||||
|
empty.add(sourceFolder);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
for (File f : deleteSet) {
|
for (File f : empty) {
|
||||||
try {
|
try {
|
||||||
debug.finest(format("Delete empty folder: %s", f));
|
debug.finest(format("Delete empty folder: %s", f));
|
||||||
Files.delete(f.toPath());
|
Files.delete(f.toPath());
|
||||||
|
Loading…
Reference in New Issue
Block a user