diff --git a/source/net/sourceforge/filebot/cli/ArgumentBean.java b/source/net/sourceforge/filebot/cli/ArgumentBean.java index 782e8f30..3bb5dd4f 100644 --- a/source/net/sourceforge/filebot/cli/ArgumentBean.java +++ b/source/net/sourceforge/filebot/cli/ArgumentBean.java @@ -34,6 +34,9 @@ public class ArgumentBean { @Option(name = "--action", usage = "Rename action", metaVar = "[move, copy, keeplink, symlink, hardlink, test]") public String action = "move"; + @Option(name = "--conflict", usage = "Conflict resolution", metaVar = "[override, skip, fail]") + public String conflict = "skip"; + @Option(name = "--format", usage = "Episode naming scheme", metaVar = "expression") public String format; diff --git a/source/net/sourceforge/filebot/cli/ArgumentProcessor.java b/source/net/sourceforge/filebot/cli/ArgumentProcessor.java index 4ea86672..8d32d432 100644 --- a/source/net/sourceforge/filebot/cli/ArgumentProcessor.java +++ b/source/net/sourceforge/filebot/cli/ArgumentProcessor.java @@ -73,7 +73,7 @@ public class ArgumentProcessor { } if (args.rename) { - cli.rename(files, args.action, args.output, args.format, args.db, args.query, args.order, args.lang, !args.nonStrict); + cli.rename(files, args.action, args.conflict, args.output, args.format, args.db, args.query, args.order, args.lang, !args.nonStrict); } if (args.check) { diff --git a/source/net/sourceforge/filebot/cli/CmdlineInterface.java b/source/net/sourceforge/filebot/cli/CmdlineInterface.java index 24c01d10..343a9814 100644 --- a/source/net/sourceforge/filebot/cli/CmdlineInterface.java +++ b/source/net/sourceforge/filebot/cli/CmdlineInterface.java @@ -9,7 +9,7 @@ import java.util.List; public interface CmdlineInterface { - List rename(Collection files, String action, String output, String format, String db, String query, String sortOrder, String lang, boolean strict) throws Exception; + List rename(Collection files, String action, String conflict, String output, String format, String db, String query, String sortOrder, String lang, boolean strict) throws Exception; List getSubtitles(Collection files, String query, String lang, String output, String encoding, boolean strict) throws Exception; diff --git a/source/net/sourceforge/filebot/cli/CmdlineOperations.java b/source/net/sourceforge/filebot/cli/CmdlineOperations.java index af675a26..02887f97 100644 --- a/source/net/sourceforge/filebot/cli/CmdlineOperations.java +++ b/source/net/sourceforge/filebot/cli/CmdlineOperations.java @@ -22,7 +22,6 @@ import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.HashMap; -import java.util.HashSet; import java.util.LinkedHashMap; import java.util.LinkedHashSet; import java.util.List; @@ -80,11 +79,12 @@ import net.sourceforge.tuned.FileUtilities.FolderFilter; public class CmdlineOperations implements CmdlineInterface { @Override - public List rename(Collection files, String action, String output, String expression, String db, String query, String sortOrder, String lang, boolean strict) throws Exception { + public List rename(Collection files, String action, String conflict, String output, String expression, String db, String query, String sortOrder, String lang, boolean strict) throws Exception { ExpressionFormat format = (expression != null) ? new ExpressionFormat(expression) : null; File outputDir = (output != null && output.length() > 0) ? new File(output) : null; Locale locale = getLanguage(lang).toLocale(); RenameAction renameAction = StandardRenameAction.forName(action); + ConflictAction conflictAction = ConflictAction.forName(conflict); List mediaFiles = filter(files, VIDEO_FILES, SUBTITLE_FILES); if (mediaFiles.isEmpty()) { @@ -93,12 +93,12 @@ public class CmdlineOperations implements CmdlineInterface { if (getEpisodeListProvider(db) != null) { // tv series mode - return renameSeries(files, renameAction, outputDir, format, getEpisodeListProvider(db), query, SortOrder.forName(sortOrder), locale, strict); + return renameSeries(files, renameAction, conflictAction, outputDir, format, getEpisodeListProvider(db), query, SortOrder.forName(sortOrder), locale, strict); } if (getMovieIdentificationService(db) != null) { // movie mode - return renameMovie(files, renameAction, outputDir, format, getMovieIdentificationService(db), query, locale, strict); + return renameMovie(files, renameAction, conflictAction, outputDir, format, getMovieIdentificationService(db), query, locale, strict); } // auto-determine mode @@ -129,14 +129,15 @@ public class CmdlineOperations implements CmdlineInterface { CLILogger.finest(format("Filename pattern: [%.02f] SxE, [%.02f] CWS", sxe / max, cws / max)); if (sxe >= (max * 0.65) || cws >= (max * 0.65)) { - return renameSeries(files, renameAction, outputDir, format, getEpisodeListProviders()[0], query, SortOrder.forName(sortOrder), locale, strict); // use default episode db + return renameSeries(files, renameAction, conflictAction, outputDir, format, WebServices.TVRage, query, SortOrder.forName(sortOrder), locale, strict); // use default episode db } else { - return renameMovie(files, renameAction, outputDir, format, getMovieIdentificationServices()[0], query, locale, strict); // use default movie db + return renameMovie(files, renameAction, conflictAction, outputDir, format, WebServices.OpenSubtitles, query, locale, strict); // use default movie db } } - public List renameSeries(Collection files, RenameAction renameAction, File outputDir, ExpressionFormat format, EpisodeListProvider db, String query, SortOrder sortOrder, Locale locale, boolean strict) throws Exception { + public List renameSeries(Collection files, RenameAction renameAction, ConflictAction conflictAction, 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())); List mediaFiles = filter(files, VIDEO_FILES, SUBTITLE_FILES); @@ -199,7 +200,7 @@ public class CmdlineOperations implements CmdlineInterface { // rename episodes Analytics.trackEvent("CLI", "Rename", "Episode", renameMap.size()); - return renameAll(renameMap, renameAction); + return renameAll(renameMap, renameAction, conflictAction); } @@ -272,7 +273,8 @@ public class CmdlineOperations implements CmdlineInterface { } - public List renameMovie(Collection files, RenameAction renameAction, File outputDir, ExpressionFormat format, MovieIdentificationService service, String query, Locale locale, boolean strict) throws Exception { + public List renameMovie(Collection files, RenameAction renameAction, ConflictAction conflictAction, File outputDir, ExpressionFormat format, MovieIdentificationService service, String query, Locale locale, boolean strict) + throws Exception { CLILogger.config(format("Rename movies using [%s]", service.getName())); // handle movie files @@ -422,45 +424,48 @@ public class CmdlineOperations implements CmdlineInterface { // rename movies Analytics.trackEvent("CLI", "Rename", "Movie", renameMap.size()); - return renameAll(renameMap, renameAction); + return renameAll(renameMap, renameAction, conflictAction); } - public List renameAll(Map renameMap, RenameAction renameAction) throws Exception { - // perform some sanity checks - Set destinationSet = new HashSet(); - - for (Entry 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); - } - + public List renameAll(Map renameMap, RenameAction renameAction, ConflictAction conflictAction) throws Exception { // rename files final List> renameLog = new ArrayList>(); try { for (Entry it : renameMap.entrySet()) { try { + File source = it.getKey(); + File destination = it.getValue(); + + // resolve destination + if (!destination.isAbsolute()) { + // same folder, different name + destination = new File(source.getParentFile(), destination.getPath()); + } + + if (!destination.equals(source) && destination.exists()) { + if (conflictAction == ConflictAction.FAIL) { + throw new Exception("File already exists: " + destination); + } + + if (conflictAction == ConflictAction.OVERRIDE) { + if (!destination.delete()) { + throw new Exception("Failed to override file: " + destination); + } + } + } + // rename file, throw exception on failure - File destination = renameAction.rename(it.getKey(), it.getValue()); - CLILogger.info(format("[%s] Renamed [%s] to [%s]", renameAction, it.getKey(), it.getValue())); + if (!destination.equals(source) && !destination.exists()) { + destination = renameAction.rename(source, destination); + CLILogger.info(format("[%s] Renamed [%s] to [%s]", renameAction, it.getKey(), it.getValue())); + } else { + CLILogger.info(format("Skipped [%s] because [%s] already exists", source, destination)); + } // remember successfully renamed matches for history entry and possible revert - renameLog.add(new SimpleImmutableEntry(it.getKey(), destination)); + renameLog.add(new SimpleImmutableEntry(source, destination)); } catch (IOException e) { CLILogger.warning(format("[%s] Failed to rename [%s]", renameAction, it.getKey())); throw e; diff --git a/source/net/sourceforge/filebot/cli/ConflictAction.java b/source/net/sourceforge/filebot/cli/ConflictAction.java new file mode 100644 index 00000000..59d1ca89 --- /dev/null +++ b/source/net/sourceforge/filebot/cli/ConflictAction.java @@ -0,0 +1,20 @@ + +package net.sourceforge.filebot.cli; + + +public enum ConflictAction { + + OVERRIDE, + SKIP, + FAIL; + + public static ConflictAction forName(String action) { + for (ConflictAction it : values()) { + if (it.name().equalsIgnoreCase(action)) + return it; + } + + throw new IllegalArgumentException("Illegal conflict action: " + action); + } + +} diff --git a/source/net/sourceforge/filebot/cli/ScriptShell.lib.groovy b/source/net/sourceforge/filebot/cli/ScriptShell.lib.groovy index 99c00863..da5d43f5 100644 --- a/source/net/sourceforge/filebot/cli/ScriptShell.lib.groovy +++ b/source/net/sourceforge/filebot/cli/ScriptShell.lib.groovy @@ -187,7 +187,7 @@ List.metaClass.sortBySimilarity = { prime, Closure toStringFunction = { obj -> o // CLI bindings def rename(args) { args = _defaults(args) synchronized (_cli) { - _guarded { _cli.rename(_files(args), args.action, args.output, args.format, args.db, args.query, args.order, args.lang, args.strict) } + _guarded { _cli.rename(_files(args), args.action, args.conflict, args.output, args.format, args.db, args.query, args.order, args.lang, args.strict) } } } @@ -252,6 +252,7 @@ def _files(args) { */ def _defaults(args) { args.action = args.action ?: _args.action + args.conflict = args.conflict ?: _args.conflict args.query = args.query ?: _args.query args.format = args.format ?: _args.format args.db = args.db ?: _args.db