+ added support for rename --action move|copy|symlink|hardlink|test
This commit is contained in:
parent
96fdea340d
commit
8ed996bcd4
|
@ -31,6 +31,9 @@ public class ArgumentBean {
|
||||||
@Option(name = "--order", usage = "Episode order", metaVar = "[Airdate, Absolute, DVD]")
|
@Option(name = "--order", usage = "Episode order", metaVar = "[Airdate, Absolute, DVD]")
|
||||||
public String order = "Airdate";
|
public String order = "Airdate";
|
||||||
|
|
||||||
|
@Option(name = "--action", usage = "Rename action", metaVar = "[move, copy, symlink, test]")
|
||||||
|
public String action = "move";
|
||||||
|
|
||||||
@Option(name = "--format", usage = "Episode naming scheme", metaVar = "expression")
|
@Option(name = "--format", usage = "Episode naming scheme", metaVar = "expression")
|
||||||
public String format;
|
public String format;
|
||||||
|
|
||||||
|
|
|
@ -73,7 +73,7 @@ public class ArgumentProcessor {
|
||||||
}
|
}
|
||||||
|
|
||||||
if (args.rename) {
|
if (args.rename) {
|
||||||
cli.rename(files, args.query, args.output, args.format, args.db, args.order, args.lang, !args.nonStrict);
|
cli.rename(files, args.action, args.output, args.format, args.db, args.query, args.order, args.lang, !args.nonStrict);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (args.check) {
|
if (args.check) {
|
||||||
|
|
|
@ -9,7 +9,7 @@ import java.util.List;
|
||||||
|
|
||||||
public interface CmdlineInterface {
|
public interface CmdlineInterface {
|
||||||
|
|
||||||
List<File> rename(Collection<File> files, String query, String output, String format, String db, String sortOrder, String lang, boolean strict) throws Exception;
|
List<File> rename(Collection<File> files, String action, String output, String format, String db, String query, String sortOrder, String lang, boolean strict) throws Exception;
|
||||||
|
|
||||||
|
|
||||||
List<File> getSubtitles(Collection<File> files, String query, String lang, String output, String encoding, boolean strict) throws Exception;
|
List<File> getSubtitles(Collection<File> files, String query, String lang, String output, String encoding, boolean strict) throws Exception;
|
||||||
|
|
|
@ -22,10 +22,10 @@ import java.util.ArrayList;
|
||||||
import java.util.Collection;
|
import java.util.Collection;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
|
import java.util.HashSet;
|
||||||
import java.util.LinkedHashMap;
|
import java.util.LinkedHashMap;
|
||||||
import java.util.LinkedHashSet;
|
import java.util.LinkedHashSet;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.ListIterator;
|
|
||||||
import java.util.Locale;
|
import java.util.Locale;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.Map.Entry;
|
import java.util.Map.Entry;
|
||||||
|
@ -80,10 +80,11 @@ import net.sourceforge.tuned.FileUtilities.FolderFilter;
|
||||||
public class CmdlineOperations implements CmdlineInterface {
|
public class CmdlineOperations implements CmdlineInterface {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public List<File> rename(Collection<File> files, String query, String output, String expression, String db, String sortOrder, String languageName, boolean strict) throws Exception {
|
public List<File> rename(Collection<File> files, String action, String output, String expression, String db, String query, String sortOrder, String lang, boolean strict) throws Exception {
|
||||||
ExpressionFormat format = (expression != null) ? new ExpressionFormat(expression) : null;
|
ExpressionFormat format = (expression != null) ? new ExpressionFormat(expression) : null;
|
||||||
Locale locale = getLanguage(languageName).toLocale();
|
|
||||||
File outputDir = (output != null && output.length() > 0) ? new File(output) : null;
|
File outputDir = (output != null && output.length() > 0) ? new File(output) : null;
|
||||||
|
Locale locale = getLanguage(lang).toLocale();
|
||||||
|
RenameAction renameAction = StandardRenameAction.forName(action);
|
||||||
|
|
||||||
List<File> mediaFiles = filter(files, VIDEO_FILES, SUBTITLE_FILES);
|
List<File> mediaFiles = filter(files, VIDEO_FILES, SUBTITLE_FILES);
|
||||||
if (mediaFiles.isEmpty()) {
|
if (mediaFiles.isEmpty()) {
|
||||||
|
@ -92,12 +93,12 @@ public class CmdlineOperations implements CmdlineInterface {
|
||||||
|
|
||||||
if (getEpisodeListProvider(db) != null) {
|
if (getEpisodeListProvider(db) != null) {
|
||||||
// tv series mode
|
// tv series mode
|
||||||
return renameSeries(files, query, outputDir, format, getEpisodeListProvider(db), SortOrder.forName(sortOrder), locale, strict);
|
return renameSeries(files, renameAction, outputDir, format, getEpisodeListProvider(db), query, SortOrder.forName(sortOrder), locale, strict);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (getMovieIdentificationService(db) != null) {
|
if (getMovieIdentificationService(db) != null) {
|
||||||
// movie mode
|
// movie mode
|
||||||
return renameMovie(files, query, outputDir, format, getMovieIdentificationService(db), locale, strict);
|
return renameMovie(files, renameAction, outputDir, format, getMovieIdentificationService(db), query, locale, strict);
|
||||||
}
|
}
|
||||||
|
|
||||||
// auto-determine mode
|
// auto-determine mode
|
||||||
|
@ -128,14 +129,14 @@ public class CmdlineOperations implements CmdlineInterface {
|
||||||
|
|
||||||
CLILogger.finest(format("Filename pattern: [%.02f] SxE, [%.02f] CWS", sxe / max, cws / max));
|
CLILogger.finest(format("Filename pattern: [%.02f] SxE, [%.02f] CWS", sxe / max, cws / max));
|
||||||
if (sxe >= (max * 0.65) || cws >= (max * 0.65)) {
|
if (sxe >= (max * 0.65) || cws >= (max * 0.65)) {
|
||||||
return renameSeries(files, query, outputDir, format, getEpisodeListProviders()[0], SortOrder.forName(sortOrder), locale, strict); // use default episode db
|
return renameSeries(files, renameAction, outputDir, format, getEpisodeListProviders()[0], query, SortOrder.forName(sortOrder), locale, strict); // use default episode db
|
||||||
} else {
|
} else {
|
||||||
return renameMovie(files, query, outputDir, format, getMovieIdentificationServices()[0], locale, strict); // use default movie db
|
return renameMovie(files, renameAction, outputDir, format, getMovieIdentificationServices()[0], query, locale, strict); // use default movie db
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
public List<File> renameSeries(Collection<File> files, String query, File outputDir, ExpressionFormat format, EpisodeListProvider db, SortOrder sortOrder, Locale locale, boolean strict) throws Exception {
|
public List<File> renameSeries(Collection<File> files, RenameAction renameAction, File outputDir, ExpressionFormat format, EpisodeListProvider db, String query, SortOrder sortOrder, Locale locale, boolean strict) throws Exception {
|
||||||
CLILogger.config(format("Rename episodes using [%s]", db.getName()));
|
CLILogger.config(format("Rename episodes using [%s]", db.getName()));
|
||||||
List<File> mediaFiles = filter(files, VIDEO_FILES, SUBTITLE_FILES);
|
List<File> mediaFiles = filter(files, VIDEO_FILES, SUBTITLE_FILES);
|
||||||
|
|
||||||
|
@ -198,7 +199,7 @@ public class CmdlineOperations implements CmdlineInterface {
|
||||||
|
|
||||||
// rename episodes
|
// rename episodes
|
||||||
Analytics.trackEvent("CLI", "Rename", "Episode", renameMap.size());
|
Analytics.trackEvent("CLI", "Rename", "Episode", renameMap.size());
|
||||||
return renameAll(renameMap);
|
return renameAll(renameMap, renameAction);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -271,7 +272,7 @@ public class CmdlineOperations implements CmdlineInterface {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
public List<File> renameMovie(Collection<File> files, String query, File outputDir, ExpressionFormat format, MovieIdentificationService service, Locale locale, boolean strict) throws Exception {
|
public List<File> renameMovie(Collection<File> files, RenameAction renameAction, File outputDir, ExpressionFormat format, MovieIdentificationService service, String query, Locale locale, boolean strict) throws Exception {
|
||||||
CLILogger.config(format("Rename movies using [%s]", service.getName()));
|
CLILogger.config(format("Rename movies using [%s]", service.getName()));
|
||||||
|
|
||||||
// handle movie files
|
// handle movie files
|
||||||
|
@ -421,11 +422,33 @@ public class CmdlineOperations implements CmdlineInterface {
|
||||||
|
|
||||||
// rename movies
|
// rename movies
|
||||||
Analytics.trackEvent("CLI", "Rename", "Movie", renameMap.size());
|
Analytics.trackEvent("CLI", "Rename", "Movie", renameMap.size());
|
||||||
return renameAll(renameMap);
|
return renameAll(renameMap, renameAction);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
public List<File> renameAll(Map<File, File> renameMap) throws Exception {
|
public List<File> renameAll(Map<File, File> renameMap, RenameAction renameAction) throws Exception {
|
||||||
|
// perform some sanity checks
|
||||||
|
Set<File> destinationSet = new HashSet<File>();
|
||||||
|
|
||||||
|
for (Entry<File, File> mapping : renameMap.entrySet()) {
|
||||||
|
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 (destinationSet.contains(destination))
|
||||||
|
throw new IllegalArgumentException("Conflict detected: " + mapping.getValue());
|
||||||
|
|
||||||
|
if (destination.exists() && !source.equals(destination))
|
||||||
|
throw new IllegalArgumentException("File already exists: " + mapping.getValue());
|
||||||
|
|
||||||
|
destinationSet.add(destination);
|
||||||
|
}
|
||||||
|
|
||||||
// rename files
|
// rename files
|
||||||
final List<Entry<File, File>> renameLog = new ArrayList<Entry<File, File>>();
|
final List<Entry<File, File>> renameLog = new ArrayList<Entry<File, File>>();
|
||||||
|
|
||||||
|
@ -433,35 +456,16 @@ public class CmdlineOperations implements CmdlineInterface {
|
||||||
for (Entry<File, File> it : renameMap.entrySet()) {
|
for (Entry<File, File> it : renameMap.entrySet()) {
|
||||||
try {
|
try {
|
||||||
// rename file, throw exception on failure
|
// rename file, throw exception on failure
|
||||||
File destination = moveRename(it.getKey(), it.getValue());
|
File destination = renameAction.rename(it.getKey(), it.getValue());
|
||||||
CLILogger.info(format("Renamed [%s] to [%s]", it.getKey(), it.getValue()));
|
CLILogger.info(format("[%s] Renamed [%s] to [%s]", renameAction, it.getKey(), it.getValue()));
|
||||||
|
|
||||||
// remember successfully renamed matches for history entry and possible revert
|
// remember successfully renamed matches for history entry and possible revert
|
||||||
renameLog.add(new SimpleImmutableEntry<File, File>(it.getKey(), destination));
|
renameLog.add(new SimpleImmutableEntry<File, File>(it.getKey(), destination));
|
||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
CLILogger.warning(format("Failed to rename [%s]", it.getKey()));
|
CLILogger.warning(format("[%s] Failed to rename [%s]", renameAction, it.getKey()));
|
||||||
throw e;
|
throw e;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} catch (Exception e) {
|
|
||||||
// could not rename one of the files, revert all changes
|
|
||||||
CLILogger.severe(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
|
|
||||||
if (mapping.getValue().renameTo(mapping.getKey())) {
|
|
||||||
// remove reverted rename operation from log
|
|
||||||
CLILogger.info("Reverted filename: " + mapping.getKey());
|
|
||||||
} else {
|
|
||||||
// failed to revert rename operation
|
|
||||||
CLILogger.severe("Failed to revert filename: " + mapping.getValue());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
throw new Exception("Renaming failed", e);
|
|
||||||
} finally {
|
} finally {
|
||||||
if (renameLog.size() > 0) {
|
if (renameLog.size() > 0) {
|
||||||
// update rename history
|
// update rename history
|
||||||
|
|
|
@ -0,0 +1,12 @@
|
||||||
|
|
||||||
|
package net.sourceforge.filebot.cli;
|
||||||
|
|
||||||
|
|
||||||
|
import java.io.File;
|
||||||
|
|
||||||
|
|
||||||
|
public interface RenameAction {
|
||||||
|
|
||||||
|
File rename(File from, File to) throws Exception;
|
||||||
|
|
||||||
|
}
|
|
@ -187,7 +187,7 @@ List.metaClass.sortBySimilarity = { prime, Closure toStringFunction = { obj -> o
|
||||||
// CLI bindings
|
// CLI bindings
|
||||||
def rename(args) { args = _defaults(args)
|
def rename(args) { args = _defaults(args)
|
||||||
synchronized (_cli) {
|
synchronized (_cli) {
|
||||||
_guarded { _cli.rename(_files(args), args.query, args.output, args.format, args.db, args.order, args.lang, args.strict) }
|
_guarded { _cli.rename(_files(args), args.action, args.output, args.format, args.db, args.query, args.order, args.lang, args.strict) }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -251,6 +251,7 @@ def _files(args) {
|
||||||
* Fill in default values from cmdline arguments
|
* Fill in default values from cmdline arguments
|
||||||
*/
|
*/
|
||||||
def _defaults(args) {
|
def _defaults(args) {
|
||||||
|
args.action = args.action ?: _args.action
|
||||||
args.query = args.query ?: _args.query
|
args.query = args.query ?: _args.query
|
||||||
args.format = args.format ?: _args.format
|
args.format = args.format ?: _args.format
|
||||||
args.db = args.db ?: _args.db
|
args.db = args.db ?: _args.db
|
||||||
|
|
|
@ -0,0 +1,79 @@
|
||||||
|
|
||||||
|
package net.sourceforge.filebot.cli;
|
||||||
|
|
||||||
|
|
||||||
|
import java.io.File;
|
||||||
|
import java.io.IOException;
|
||||||
|
|
||||||
|
import net.sourceforge.tuned.FileUtilities;
|
||||||
|
|
||||||
|
|
||||||
|
public enum StandardRenameAction implements RenameAction {
|
||||||
|
|
||||||
|
MOVE {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public File rename(File from, File to) throws Exception {
|
||||||
|
return FileUtilities.moveRename(from, to);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
COPY {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public File rename(File from, File to) throws Exception {
|
||||||
|
return FileUtilities.copyAs(from, to);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
SYMLINK {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public File rename(File from, File to) throws Exception {
|
||||||
|
File destionation = FileUtilities.resolveDestination(from, to);
|
||||||
|
|
||||||
|
// create symlink via NIO.2
|
||||||
|
try {
|
||||||
|
java.nio.file.Files.createSymbolicLink(destionation.toPath(), from.toPath());
|
||||||
|
} catch (LinkageError e) {
|
||||||
|
throw new Exception("Unsupported Operation: createSymbolicLink");
|
||||||
|
}
|
||||||
|
|
||||||
|
return destionation;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
HARDLINK {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public File rename(File from, File to) throws Exception {
|
||||||
|
File destionation = FileUtilities.resolveDestination(from, to);
|
||||||
|
|
||||||
|
// create hardlink via NIO.2
|
||||||
|
try {
|
||||||
|
java.nio.file.Files.createLink(destionation.toPath(), from.toPath());
|
||||||
|
} catch (LinkageError e) {
|
||||||
|
throw new Exception("Unsupported Operation: createLink");
|
||||||
|
}
|
||||||
|
|
||||||
|
return destionation;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
TEST {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public File rename(File from, File to) throws IOException {
|
||||||
|
return FileUtilities.resolveDestination(from, to);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
public static StandardRenameAction forName(String action) {
|
||||||
|
for (StandardRenameAction it : values()) {
|
||||||
|
if (it.name().equalsIgnoreCase(action))
|
||||||
|
return it;
|
||||||
|
}
|
||||||
|
|
||||||
|
throw new IllegalArgumentException("Illegal rename action: " + action);
|
||||||
|
}
|
||||||
|
}
|
|
@ -22,7 +22,6 @@ 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.TimeUnit;
|
import java.util.concurrent.TimeUnit;
|
||||||
import java.util.concurrent.TimeoutException;
|
import java.util.concurrent.TimeoutException;
|
||||||
import java.util.logging.Level;
|
import java.util.logging.Level;
|
||||||
|
@ -90,7 +89,7 @@ class RenameAction extends AbstractAction {
|
||||||
private Map<File, File> checkRenamePlan(List<Entry<File, File>> renamePlan) {
|
private Map<File, File> checkRenamePlan(List<Entry<File, File>> renamePlan) {
|
||||||
// build rename map and perform some sanity checks
|
// build rename map and perform some sanity checks
|
||||||
Map<File, File> renameMap = new HashMap<File, File>();
|
Map<File, File> renameMap = new HashMap<File, File>();
|
||||||
Set<File> destinationSet = new TreeSet<File>();
|
Set<File> destinationSet = new HashSet<File>();
|
||||||
|
|
||||||
for (Entry<File, File> mapping : renamePlan) {
|
for (Entry<File, File> mapping : renamePlan) {
|
||||||
File source = mapping.getKey();
|
File source = mapping.getKey();
|
||||||
|
@ -113,6 +112,7 @@ class RenameAction extends AbstractAction {
|
||||||
|
|
||||||
// use original mapping values
|
// use original mapping values
|
||||||
renameMap.put(mapping.getKey(), mapping.getValue());
|
renameMap.put(mapping.getKey(), mapping.getValue());
|
||||||
|
destinationSet.add(destination);
|
||||||
}
|
}
|
||||||
|
|
||||||
return renameMap;
|
return renameMap;
|
||||||
|
|
|
@ -44,18 +44,7 @@ public final class FileUtilities {
|
||||||
|
|
||||||
public static File moveRename(File source, File destination) throws IOException {
|
public static File moveRename(File source, File destination) throws IOException {
|
||||||
// resolve destination
|
// resolve destination
|
||||||
if (!destination.isAbsolute()) {
|
destination = resolveDestination(source, destination);
|
||||||
// same folder, different name
|
|
||||||
destination = new File(source.getParentFile(), destination.getPath());
|
|
||||||
}
|
|
||||||
|
|
||||||
// make sure we that we can create the destination folder structure
|
|
||||||
File destinationFolder = destination.getParentFile();
|
|
||||||
|
|
||||||
// create parent folder if necessary
|
|
||||||
if (!destinationFolder.isDirectory() && !destinationFolder.mkdirs()) {
|
|
||||||
throw new IOException("Failed to create folder: " + destinationFolder);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (source.isDirectory()) { // move folder
|
if (source.isDirectory()) { // move folder
|
||||||
moveFolderIO(source, destination);
|
moveFolderIO(source, destination);
|
||||||
|
@ -89,6 +78,20 @@ public final class FileUtilities {
|
||||||
|
|
||||||
|
|
||||||
public static File copyAs(File source, File destination) throws IOException {
|
public static File copyAs(File source, File destination) throws IOException {
|
||||||
|
// resolve destination
|
||||||
|
destination = resolveDestination(source, destination);
|
||||||
|
|
||||||
|
if (source.isDirectory()) { // copy folder
|
||||||
|
org.apache.commons.io.FileUtils.copyDirectory(source, destination);
|
||||||
|
} else { // copy file
|
||||||
|
org.apache.commons.io.FileUtils.copyFile(source, destination);
|
||||||
|
}
|
||||||
|
|
||||||
|
return destination;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public static File resolveDestination(File source, File destination) throws IOException {
|
||||||
// resolve destination
|
// resolve destination
|
||||||
if (!destination.isAbsolute()) {
|
if (!destination.isAbsolute()) {
|
||||||
// same folder, different name
|
// same folder, different name
|
||||||
|
@ -103,12 +106,6 @@ public final class FileUtilities {
|
||||||
throw new IOException("Failed to create folder: " + destinationFolder);
|
throw new IOException("Failed to create folder: " + destinationFolder);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (source.isDirectory()) { // copy folder
|
|
||||||
org.apache.commons.io.FileUtils.copyDirectory(source, destination);
|
|
||||||
} else { // copy file
|
|
||||||
org.apache.commons.io.FileUtils.copyFile(source, destination);
|
|
||||||
}
|
|
||||||
|
|
||||||
return destination;
|
return destination;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue