+ support Movie disk folders in movie mode
+ improved handling for derivate files (files with the same name but different extensions) in movie mode
This commit is contained in:
parent
67fe97c345
commit
cc5845b2a0
|
@ -109,8 +109,8 @@
|
||||||
<include name="org/slf4j/**" />
|
<include name="org/slf4j/**" />
|
||||||
</zipfileset>
|
</zipfileset>
|
||||||
|
|
||||||
<zipfileset src="${dir.lib}/guava.jar">
|
<zipfileset src="${dir.lib}/commons-io.jar">
|
||||||
<include name="com/google/common/**" />
|
<include name="org/apache/commons/io/**" />
|
||||||
</zipfileset>
|
</zipfileset>
|
||||||
|
|
||||||
<zipfileset src="${dir.lib}/jna.jar">
|
<zipfileset src="${dir.lib}/jna.jar">
|
||||||
|
|
|
@ -49,7 +49,7 @@
|
||||||
<jar href="mediainfo.jar" download="lazy" part="native" />
|
<jar href="mediainfo.jar" download="lazy" part="native" />
|
||||||
<jar href="nekohtml.jar" download="lazy" part="scraper" />
|
<jar href="nekohtml.jar" download="lazy" part="scraper" />
|
||||||
<jar href="xercesImpl.jar" download="lazy" part="scraper" />
|
<jar href="xercesImpl.jar" download="lazy" part="scraper" />
|
||||||
<jar href="guava.jar" download="lazy" />
|
<jar href="commons-io.jar" download="lazy" />
|
||||||
<jar href="junrar-custom.jar" download="lazy" />
|
<jar href="junrar-custom.jar" download="lazy" />
|
||||||
</resources>
|
</resources>
|
||||||
|
|
||||||
|
|
Binary file not shown.
BIN
lib/guava.jar
BIN
lib/guava.jar
Binary file not shown.
|
@ -3,7 +3,6 @@ package net.sourceforge.filebot.cli;
|
||||||
|
|
||||||
|
|
||||||
import static java.lang.String.*;
|
import static java.lang.String.*;
|
||||||
import static java.util.Arrays.*;
|
|
||||||
import static java.util.Collections.*;
|
import static java.util.Collections.*;
|
||||||
import static net.sourceforge.filebot.MediaTypes.*;
|
import static net.sourceforge.filebot.MediaTypes.*;
|
||||||
import static net.sourceforge.filebot.WebServices.*;
|
import static net.sourceforge.filebot.WebServices.*;
|
||||||
|
@ -47,6 +46,7 @@ import net.sourceforge.filebot.format.MediaBindingBean;
|
||||||
import net.sourceforge.filebot.hash.HashType;
|
import net.sourceforge.filebot.hash.HashType;
|
||||||
import net.sourceforge.filebot.hash.VerificationFileReader;
|
import net.sourceforge.filebot.hash.VerificationFileReader;
|
||||||
import net.sourceforge.filebot.hash.VerificationFileWriter;
|
import net.sourceforge.filebot.hash.VerificationFileWriter;
|
||||||
|
import net.sourceforge.filebot.media.ReleaseInfo;
|
||||||
import net.sourceforge.filebot.similarity.EpisodeMetrics;
|
import net.sourceforge.filebot.similarity.EpisodeMetrics;
|
||||||
import net.sourceforge.filebot.similarity.Match;
|
import net.sourceforge.filebot.similarity.Match;
|
||||||
import net.sourceforge.filebot.similarity.Matcher;
|
import net.sourceforge.filebot.similarity.Matcher;
|
||||||
|
@ -260,49 +260,68 @@ public class CmdlineOperations implements CmdlineInterface {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
public List<File> renameMovie(Collection<File> mediaFiles, String query, ExpressionFormat format, MovieIdentificationService service, Locale locale, boolean strict) throws Exception {
|
public List<File> renameMovie(Collection<File> files, String query, ExpressionFormat format, MovieIdentificationService service, 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
|
||||||
File[] movieFiles = filter(mediaFiles, VIDEO_FILES).toArray(new File[0]);
|
List<File> movieFiles = filter(files, VIDEO_FILES);
|
||||||
File[] subtitleFiles = filter(mediaFiles, SUBTITLE_FILES).toArray(new File[0]);
|
|
||||||
Movie[] movieByFileHash = new Movie[movieFiles.length];
|
|
||||||
|
|
||||||
if (movieFiles.length > 0 && query == null) {
|
Map<File, List<File>> derivatesByMovieFile = new HashMap<File, List<File>>();
|
||||||
// match movie hashes online
|
for (File movieFile : movieFiles) {
|
||||||
|
derivatesByMovieFile.put(movieFile, new ArrayList<File>());
|
||||||
|
}
|
||||||
|
for (File file : files) {
|
||||||
|
for (File movieFile : movieFiles) {
|
||||||
|
if (!file.equals(movieFile) && isDerived(file, movieFile)) {
|
||||||
|
derivatesByMovieFile.get(movieFile).add(file);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
List<File> standaloneFiles = new ArrayList<File>(files);
|
||||||
|
for (List<File> derivates : derivatesByMovieFile.values()) {
|
||||||
|
standaloneFiles.removeAll(derivates);
|
||||||
|
}
|
||||||
|
|
||||||
|
List<File> movieMatchFiles = new ArrayList<File>();
|
||||||
|
movieMatchFiles.addAll(movieFiles);
|
||||||
|
movieMatchFiles.addAll(filter(files, new ReleaseInfo().getDiskFolderFilter()));
|
||||||
|
movieMatchFiles.addAll(filter(standaloneFiles, SUBTITLE_FILES));
|
||||||
|
|
||||||
|
// map movies to (possibly multiple) files (in natural order)
|
||||||
|
Map<Movie, SortedSet<File>> filesByMovie = new HashMap<Movie, SortedSet<File>>();
|
||||||
|
|
||||||
|
// match movie hashes online
|
||||||
|
Map<File, Movie> movieByFile = new HashMap<File, Movie>();
|
||||||
|
if (query == null && movieFiles.size() > 0) {
|
||||||
try {
|
try {
|
||||||
CLILogger.fine(format("Looking up movie by filehash via [%s]", service.getName()));
|
CLILogger.fine(format("Looking up movie by filehash via [%s]", service.getName()));
|
||||||
movieByFileHash = service.getMovieDescriptors(movieFiles, locale);
|
Map<File, Movie> hashLookup = service.getMovieDescriptors(movieFiles, locale);
|
||||||
Analytics.trackEvent(service.getName(), "HashLookup", "Movie", movieByFileHash.length - frequency(asList(movieByFileHash), null)); // number of positive hash lookups
|
movieByFile.putAll(hashLookup);
|
||||||
|
Analytics.trackEvent(service.getName(), "HashLookup", "Movie", hashLookup.size()); // number of positive hash lookups
|
||||||
} catch (UnsupportedOperationException e) {
|
} catch (UnsupportedOperationException e) {
|
||||||
CLILogger.fine(format("%s: Hash lookup not supported", service.getName()));
|
CLILogger.fine(format("%s: Hash lookup not supported", service.getName()));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (subtitleFiles.length > 0 && movieFiles.length == 0) {
|
|
||||||
// special handling if there is only subtitle files
|
|
||||||
movieByFileHash = new Movie[subtitleFiles.length];
|
|
||||||
movieFiles = subtitleFiles;
|
|
||||||
subtitleFiles = new File[0];
|
|
||||||
}
|
|
||||||
|
|
||||||
if (query != null) {
|
if (query != null) {
|
||||||
CLILogger.fine(format("Looking up movie by query [%s]", query));
|
CLILogger.fine(format("Looking up movie by query [%s]", query));
|
||||||
Movie result = (Movie) selectSearchResult(query, service.searchMovie(query, locale), strict).get(0);
|
Movie result = (Movie) selectSearchResult(query, service.searchMovie(query, locale), strict).get(0);
|
||||||
fill(movieByFileHash, result);
|
// force all mappings
|
||||||
|
for (File file : movieMatchFiles) {
|
||||||
|
movieByFile.put(file, result);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// map movies to (possibly multiple) files (in natural order)
|
|
||||||
Map<Movie, SortedSet<File>> filesByMovie = new HashMap<Movie, SortedSet<File>>();
|
|
||||||
|
|
||||||
// map all files by movie
|
// map all files by movie
|
||||||
for (int i = 0; i < movieFiles.length; i++) {
|
for (File file : movieMatchFiles) {
|
||||||
Movie movie = movieByFileHash[i];
|
Movie movie = movieByFile.get(file);
|
||||||
|
|
||||||
// unknown hash, try via imdb id from nfo file
|
// unknown hash, try via imdb id from nfo file
|
||||||
if (movie == null) {
|
if (movie == null) {
|
||||||
CLILogger.fine(format("Auto-detect movie from context: [%s]", movieFiles[i]));
|
CLILogger.fine(format("Auto-detect movie from context: [%s]", file));
|
||||||
Collection<Movie> results = detectMovie(movieFiles[i], null, service, locale, strict);
|
Collection<Movie> results = detectMovie(file, null, service, locale, strict);
|
||||||
movie = (Movie) selectSearchResult(query, results, strict).get(0);
|
movie = (Movie) selectSearchResult(query, results, strict).get(0);
|
||||||
|
|
||||||
if (movie != null) {
|
if (movie != null) {
|
||||||
|
@ -320,7 +339,7 @@ public class CmdlineOperations implements CmdlineInterface {
|
||||||
filesByMovie.put(movie, movieParts);
|
filesByMovie.put(movie, movieParts);
|
||||||
}
|
}
|
||||||
|
|
||||||
movieParts.add(movieFiles[i]);
|
movieParts.add(file);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -341,17 +360,13 @@ public class CmdlineOperations implements CmdlineInterface {
|
||||||
}
|
}
|
||||||
|
|
||||||
matches.add(new Match<File, Movie>(file, part));
|
matches.add(new Match<File, Movie>(file, part));
|
||||||
}
|
|
||||||
}
|
// automatically add matches for derivates
|
||||||
|
List<File> derivates = derivatesByMovieFile.get(file);
|
||||||
// handle subtitle files
|
if (derivates != null) {
|
||||||
for (File subtitle : subtitleFiles) {
|
for (File derivate : derivates) {
|
||||||
// check if subtitle corresponds to a movie file (same name, different extension)
|
matches.add(new Match<File, Movie>(derivate, part));
|
||||||
for (Match<File, ?> movieMatch : matches) {
|
}
|
||||||
if (isDerived(subtitle, movieMatch.getValue())) {
|
|
||||||
matches.add(new Match<File, Object>(subtitle, movieMatch.getCandidate()));
|
|
||||||
// movie match found, we're done
|
|
||||||
break;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -387,7 +402,7 @@ 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 = renameFile(it.getKey(), it.getValue());
|
File destination = moveRename(it.getKey(), it.getValue());
|
||||||
CLILogger.info(format("Renamed [%s] to [%s]", it.getKey(), it.getValue()));
|
CLILogger.info(format("Renamed [%s] to [%s]", it.getKey(), it.getValue()));
|
||||||
|
|
||||||
// remember successfully renamed matches for history entry and possible revert
|
// remember successfully renamed matches for history entry and possible revert
|
||||||
|
|
|
@ -37,7 +37,7 @@ File.metaClass.hasExtension = { String... ext -> hasExtension(delegate, ext) }
|
||||||
File.metaClass.isDerived = { f -> isDerived(delegate, f) }
|
File.metaClass.isDerived = { f -> isDerived(delegate, f) }
|
||||||
File.metaClass.validateFileName = { validateFileName(delegate) }
|
File.metaClass.validateFileName = { validateFileName(delegate) }
|
||||||
File.metaClass.validateFilePath = { validateFilePath(delegate) }
|
File.metaClass.validateFilePath = { validateFilePath(delegate) }
|
||||||
File.metaClass.moveTo = { f -> renameFile(delegate, f) }
|
File.metaClass.moveTo = { f -> moveRename(delegate, f instanceof File ? f : new File(f.toString())) }
|
||||||
List.metaClass.mapByFolder = { mapByFolder(delegate) }
|
List.metaClass.mapByFolder = { mapByFolder(delegate) }
|
||||||
List.metaClass.mapByExtension = { mapByExtension(delegate) }
|
List.metaClass.mapByExtension = { mapByExtension(delegate) }
|
||||||
String.metaClass.getExtension = { getExtension(delegate) }
|
String.metaClass.getExtension = { getExtension(delegate) }
|
||||||
|
|
|
@ -8,6 +8,7 @@ import static net.sourceforge.filebot.similarity.Normalization.*;
|
||||||
import static net.sourceforge.tuned.FileUtilities.*;
|
import static net.sourceforge.tuned.FileUtilities.*;
|
||||||
|
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
|
import java.io.FileFilter;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.net.MalformedURLException;
|
import java.net.MalformedURLException;
|
||||||
import java.net.URL;
|
import java.net.URL;
|
||||||
|
@ -49,6 +50,11 @@ public class MediaDetection {
|
||||||
private static final ReleaseInfo releaseInfo = new ReleaseInfo();
|
private static final ReleaseInfo releaseInfo = new ReleaseInfo();
|
||||||
|
|
||||||
|
|
||||||
|
public static boolean isDiskFolder(File folder) {
|
||||||
|
return releaseInfo.getDiskFolderFilter().accept(folder);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
public static Map<Set<File>, Set<String>> mapSeriesNamesByFiles(Collection<File> files, Locale locale) throws Exception {
|
public static Map<Set<File>, Set<String>> mapSeriesNamesByFiles(Collection<File> files, Locale locale) throws Exception {
|
||||||
SortedMap<File, List<File>> filesByFolder = mapByFolder(filter(files, VIDEO_FILES, SUBTITLE_FILES));
|
SortedMap<File, List<File>> filesByFolder = mapByFolder(filter(files, VIDEO_FILES, SUBTITLE_FILES));
|
||||||
|
|
||||||
|
@ -156,8 +162,8 @@ public class MediaDetection {
|
||||||
Set<Movie> options = new LinkedHashSet<Movie>();
|
Set<Movie> options = new LinkedHashSet<Movie>();
|
||||||
|
|
||||||
// lookup by file hash
|
// lookup by file hash
|
||||||
if (hashLookupService != null) {
|
if (hashLookupService != null && movieFile.isFile()) {
|
||||||
for (Movie movie : hashLookupService.getMovieDescriptors(new File[] { movieFile }, locale)) {
|
for (Movie movie : hashLookupService.getMovieDescriptors(singleton(movieFile), locale).values()) {
|
||||||
if (movie != null) {
|
if (movie != null) {
|
||||||
options.add(movie);
|
options.add(movie);
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,13 +2,13 @@
|
||||||
package net.sourceforge.filebot.media;
|
package net.sourceforge.filebot.media;
|
||||||
|
|
||||||
|
|
||||||
import static java.util.Arrays.*;
|
|
||||||
import static java.util.ResourceBundle.*;
|
import static java.util.ResourceBundle.*;
|
||||||
import static java.util.regex.Pattern.*;
|
import static java.util.regex.Pattern.*;
|
||||||
import static net.sourceforge.filebot.similarity.Normalization.*;
|
import static net.sourceforge.filebot.similarity.Normalization.*;
|
||||||
import static net.sourceforge.tuned.StringUtilities.*;
|
import static net.sourceforge.tuned.StringUtilities.*;
|
||||||
|
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
|
import java.io.FileFilter;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.nio.ByteBuffer;
|
import java.nio.ByteBuffer;
|
||||||
import java.nio.charset.Charset;
|
import java.nio.charset.Charset;
|
||||||
|
@ -125,7 +125,7 @@ public class ReleaseInfo {
|
||||||
languageMap.put(locale.getISO3Language(), locale);
|
languageMap.put(locale.getISO3Language(), locale);
|
||||||
|
|
||||||
// map display language names for given locales
|
// map display language names for given locales
|
||||||
for (Locale language : asList(supportedLanguageName)) {
|
for (Locale language : supportedLanguageName) {
|
||||||
languageMap.put(locale.getDisplayLanguage(language), locale);
|
languageMap.put(locale.getDisplayLanguage(language), locale);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -174,6 +174,11 @@ public class ReleaseInfo {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public FileFilter getDiskFolderFilter() {
|
||||||
|
return new FolderEntryFilter(compile(getBundle(getClass().getName()).getString("pattern.diskfolder.entry")));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
// fetch release group names online and try to update the data every other day
|
// fetch release group names online and try to update the data every other day
|
||||||
protected final CachedResource<String[]> releaseGroupResource = new PatternResource(getBundle(getClass().getName()).getString("url.release-groups"));
|
protected final CachedResource<String[]> releaseGroupResource = new PatternResource(getBundle(getClass().getName()).getString("url.release-groups"));
|
||||||
protected final CachedResource<String[]> queryBlacklistResource = new PatternResource(getBundle(getClass().getName()).getString("url.query-blacklist"));
|
protected final CachedResource<String[]> queryBlacklistResource = new PatternResource(getBundle(getClass().getName()).getString("url.query-blacklist"));
|
||||||
|
@ -217,4 +222,28 @@ public class ReleaseInfo {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
protected static class FolderEntryFilter implements FileFilter {
|
||||||
|
|
||||||
|
private final Pattern entryPattern;
|
||||||
|
|
||||||
|
|
||||||
|
public FolderEntryFilter(Pattern entryPattern) {
|
||||||
|
this.entryPattern = entryPattern;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean accept(File dir) {
|
||||||
|
if (dir.isDirectory()) {
|
||||||
|
for (String entry : dir.list()) {
|
||||||
|
if (entryPattern.matcher(entry).matches()) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -12,3 +12,6 @@ url.query-blacklist: http://filebot.sourceforge.net/data/query-blacklist.txt
|
||||||
|
|
||||||
# list of all movies (id, name, year)
|
# list of all movies (id, name, year)
|
||||||
url.movie-list: http://filebot.sourceforge.net/data/movies.txt.gz
|
url.movie-list: http://filebot.sourceforge.net/data/movies.txt.gz
|
||||||
|
|
||||||
|
# disk folder matcher
|
||||||
|
pattern.diskfolder.entry: ^BDMV$|^HVDVD_TS$|^VIDEO_TS$|^AUDIO_TS$|^VCD$
|
||||||
|
|
|
@ -2,11 +2,14 @@
|
||||||
package net.sourceforge.filebot.ui.rename;
|
package net.sourceforge.filebot.ui.rename;
|
||||||
|
|
||||||
|
|
||||||
import static net.sourceforge.tuned.FileUtilities.*;
|
import static java.util.Arrays.*;
|
||||||
|
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.LinkedList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
|
import net.sourceforge.filebot.media.MediaDetection;
|
||||||
import net.sourceforge.filebot.ui.transfer.FileTransferablePolicy;
|
import net.sourceforge.filebot.ui.transfer.FileTransferablePolicy;
|
||||||
import net.sourceforge.tuned.FastFile;
|
import net.sourceforge.tuned.FastFile;
|
||||||
|
|
||||||
|
@ -15,30 +18,46 @@ class FilesListTransferablePolicy extends FileTransferablePolicy {
|
||||||
|
|
||||||
private final List<File> model;
|
private final List<File> model;
|
||||||
|
|
||||||
|
|
||||||
public FilesListTransferablePolicy(List<File> model) {
|
public FilesListTransferablePolicy(List<File> model) {
|
||||||
this.model = model;
|
this.model = model;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected boolean accept(List<File> files) {
|
protected boolean accept(List<File> files) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void clear() {
|
protected void clear() {
|
||||||
model.clear();
|
model.clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void load(List<File> files) {
|
protected void load(List<File> files) {
|
||||||
model.addAll(FastFile.foreach(flatten(files, 5, false)));
|
List<File> entries = new ArrayList<File>();
|
||||||
|
LinkedList<File> queue = new LinkedList<File>(files);
|
||||||
|
|
||||||
|
while (queue.size() > 0) {
|
||||||
|
File f = queue.removeFirst();
|
||||||
|
|
||||||
|
if (f.isHidden())
|
||||||
|
continue;
|
||||||
|
|
||||||
|
if (f.isFile() || MediaDetection.isDiskFolder(f)) {
|
||||||
|
entries.add(f);
|
||||||
|
} else {
|
||||||
|
queue.addAll(0, asList(f.listFiles()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
model.addAll(FastFile.foreach(entries));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String getFileFilterDescription() {
|
public String getFileFilterDescription() {
|
||||||
return "files and folders";
|
return "files and folders";
|
||||||
|
|
|
@ -27,8 +27,8 @@ import java.util.EnumSet;
|
||||||
import java.util.LinkedHashMap;
|
import java.util.LinkedHashMap;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.Set;
|
|
||||||
import java.util.Map.Entry;
|
import java.util.Map.Entry;
|
||||||
|
import java.util.Set;
|
||||||
import java.util.logging.Level;
|
import java.util.logging.Level;
|
||||||
import java.util.logging.Logger;
|
import java.util.logging.Logger;
|
||||||
import java.util.regex.Pattern;
|
import java.util.regex.Pattern;
|
||||||
|
@ -50,8 +50,8 @@ import javax.swing.JTable;
|
||||||
import javax.swing.JTextField;
|
import javax.swing.JTextField;
|
||||||
import javax.swing.ListSelectionModel;
|
import javax.swing.ListSelectionModel;
|
||||||
import javax.swing.RowFilter;
|
import javax.swing.RowFilter;
|
||||||
import javax.swing.SortOrder;
|
|
||||||
import javax.swing.RowSorter.SortKey;
|
import javax.swing.RowSorter.SortKey;
|
||||||
|
import javax.swing.SortOrder;
|
||||||
import javax.swing.border.CompoundBorder;
|
import javax.swing.border.CompoundBorder;
|
||||||
import javax.swing.border.TitledBorder;
|
import javax.swing.border.TitledBorder;
|
||||||
import javax.swing.event.DocumentEvent;
|
import javax.swing.event.DocumentEvent;
|
||||||
|
@ -94,7 +94,7 @@ class HistoryDialog extends JDialog {
|
||||||
|
|
||||||
private final JTable elementTable = createTable(elementModel);
|
private final JTable elementTable = createTable(elementModel);
|
||||||
|
|
||||||
|
|
||||||
public HistoryDialog(Window owner) {
|
public HistoryDialog(Window owner) {
|
||||||
super(owner, "Rename History", ModalityType.DOCUMENT_MODAL);
|
super(owner, "Rename History", ModalityType.DOCUMENT_MODAL);
|
||||||
|
|
||||||
|
@ -179,7 +179,7 @@ class HistoryDialog extends JDialog {
|
||||||
|
|
||||||
private final DateFormat format = DateFormat.getDateTimeInstance(DateFormat.MEDIUM, DateFormat.SHORT);
|
private final DateFormat format = DateFormat.getDateTimeInstance(DateFormat.MEDIUM, DateFormat.SHORT);
|
||||||
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected, boolean hasFocus, int row, int column) {
|
public Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected, boolean hasFocus, int row, int column) {
|
||||||
return super.getTableCellRendererComponent(table, format.format(value), isSelected, hasFocus, row, column);
|
return super.getTableCellRendererComponent(table, format.format(value), isSelected, hasFocus, row, column);
|
||||||
|
@ -244,7 +244,7 @@ class HistoryDialog extends JDialog {
|
||||||
setSize(580, 640);
|
setSize(580, 640);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
public void setModel(History history) {
|
public void setModel(History history) {
|
||||||
// update table model
|
// update table model
|
||||||
sequenceModel.setData(history.sequences());
|
sequenceModel.setData(history.sequences());
|
||||||
|
@ -261,17 +261,17 @@ class HistoryDialog extends JDialog {
|
||||||
initializeInfoLabel();
|
initializeInfoLabel();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
public History getModel() {
|
public History getModel() {
|
||||||
return new History(sequenceModel.getData());
|
return new History(sequenceModel.getData());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
public JLabel getInfoLabel() {
|
public JLabel getInfoLabel() {
|
||||||
return infoLabel;
|
return infoLabel;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
private void initializeInfoLabel() {
|
private void initializeInfoLabel() {
|
||||||
int count = 0;
|
int count = 0;
|
||||||
Date since = new Date();
|
Date since = new Date();
|
||||||
|
@ -286,7 +286,7 @@ class HistoryDialog extends JDialog {
|
||||||
infoLabel.setText(String.format("A total of %,d files have been renamed since %s.", count, DateFormat.getDateInstance().format(since)));
|
infoLabel.setText(String.format("A total of %,d files have been renamed since %s.", count, DateFormat.getDateInstance().format(since)));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
private JScrollPane createScrollPaneGroup(String title, JComponent component) {
|
private JScrollPane createScrollPaneGroup(String title, JComponent component) {
|
||||||
JScrollPane scrollPane = new JScrollPane(component);
|
JScrollPane scrollPane = new JScrollPane(component);
|
||||||
scrollPane.setBorder(new CompoundBorder(new TitledBorder(title), scrollPane.getBorder()));
|
scrollPane.setBorder(new CompoundBorder(new TitledBorder(title), scrollPane.getBorder()));
|
||||||
|
@ -294,7 +294,7 @@ class HistoryDialog extends JDialog {
|
||||||
return scrollPane;
|
return scrollPane;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
private JTable createTable(TableModel model) {
|
private JTable createTable(TableModel model) {
|
||||||
JTable table = new JTable(model);
|
JTable table = new JTable(model);
|
||||||
table.setBackground(Color.white);
|
table.setBackground(Color.white);
|
||||||
|
@ -312,7 +312,7 @@ class HistoryDialog extends JDialog {
|
||||||
return table;
|
return table;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
private final Action closeAction = new AbstractAction("Close") {
|
private final Action closeAction = new AbstractAction("Close") {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -337,13 +337,13 @@ class HistoryDialog extends JDialog {
|
||||||
maybeShowPopup(e);
|
maybeShowPopup(e);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void mouseReleased(MouseEvent e) {
|
public void mouseReleased(MouseEvent e) {
|
||||||
maybeShowPopup(e);
|
maybeShowPopup(e);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
private void maybeShowPopup(MouseEvent e) {
|
private void maybeShowPopup(MouseEvent e) {
|
||||||
if (e.isPopupTrigger()) {
|
if (e.isPopupTrigger()) {
|
||||||
JTable table = (JTable) e.getSource();
|
JTable table = (JTable) e.getSource();
|
||||||
|
@ -383,30 +383,30 @@ class HistoryDialog extends JDialog {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
private static class RevertAction extends AbstractAction {
|
private static class RevertAction extends AbstractAction {
|
||||||
|
|
||||||
public static final String ELEMENTS = "elements";
|
public static final String ELEMENTS = "elements";
|
||||||
public static final String PARENT = "parent";
|
public static final String PARENT = "parent";
|
||||||
|
|
||||||
|
|
||||||
public RevertAction(Collection<Element> elements, HistoryDialog parent) {
|
public RevertAction(Collection<Element> elements, HistoryDialog parent) {
|
||||||
putValue(NAME, "Revert...");
|
putValue(NAME, "Revert...");
|
||||||
putValue(ELEMENTS, elements.toArray(new Element[0]));
|
putValue(ELEMENTS, elements.toArray(new Element[0]));
|
||||||
putValue(PARENT, parent);
|
putValue(PARENT, parent);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
public Element[] elements() {
|
public Element[] elements() {
|
||||||
return (Element[]) getValue(ELEMENTS);
|
return (Element[]) getValue(ELEMENTS);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
public HistoryDialog parent() {
|
public HistoryDialog parent() {
|
||||||
return (HistoryDialog) getValue(PARENT);
|
return (HistoryDialog) getValue(PARENT);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
private enum Option {
|
private enum Option {
|
||||||
Rename {
|
Rename {
|
||||||
|
|
||||||
|
@ -431,7 +431,7 @@ class HistoryDialog extends JDialog {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void actionPerformed(ActionEvent e) {
|
public void actionPerformed(ActionEvent e) {
|
||||||
// use default directory
|
// use default directory
|
||||||
|
@ -501,13 +501,13 @@ class HistoryDialog extends JDialog {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
private void rename(File directory) {
|
private void rename(File directory) {
|
||||||
int count = 0;
|
int count = 0;
|
||||||
|
|
||||||
for (Entry<File, File> entry : getRenameMap(directory).entrySet()) {
|
for (Entry<File, File> entry : getRenameMap(directory).entrySet()) {
|
||||||
try {
|
try {
|
||||||
renameFile(entry.getKey(), entry.getValue());
|
moveRename(entry.getKey(), entry.getValue());
|
||||||
count++;
|
count++;
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
Logger.getLogger(HistoryDialog.class.getName()).log(Level.SEVERE, e.getMessage(), e);
|
Logger.getLogger(HistoryDialog.class.getName()).log(Level.SEVERE, e.getMessage(), e);
|
||||||
|
@ -528,7 +528,7 @@ class HistoryDialog extends JDialog {
|
||||||
parent().repaint();
|
parent().repaint();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
private Map<File, File> getRenameMap(File directory) {
|
private Map<File, File> getRenameMap(File directory) {
|
||||||
Map<File, File> renameMap = new LinkedHashMap<File, File>();
|
Map<File, File> renameMap = new LinkedHashMap<File, File>();
|
||||||
|
|
||||||
|
@ -552,7 +552,7 @@ class HistoryDialog extends JDialog {
|
||||||
return renameMap;
|
return renameMap;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
private List<File> getMissingFiles(File directory) {
|
private List<File> getMissingFiles(File directory) {
|
||||||
List<File> missingFiles = new ArrayList<File>();
|
List<File> missingFiles = new ArrayList<File>();
|
||||||
|
|
||||||
|
@ -565,7 +565,7 @@ class HistoryDialog extends JDialog {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
private final FileTransferablePolicy importHandler = new FileTransferablePolicy() {
|
private final FileTransferablePolicy importHandler = new FileTransferablePolicy() {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -573,13 +573,13 @@ class HistoryDialog extends JDialog {
|
||||||
return FileUtilities.containsOnly(files, new ExtensionFileFilter("xml"));
|
return FileUtilities.containsOnly(files, new ExtensionFileFilter("xml"));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void clear() {
|
protected void clear() {
|
||||||
setModel(new History());
|
setModel(new History());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void load(List<File> files) throws IOException {
|
protected void load(List<File> files) throws IOException {
|
||||||
History history = getModel();
|
History history = getModel();
|
||||||
|
@ -594,7 +594,7 @@ class HistoryDialog extends JDialog {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String getFileFilterDescription() {
|
public String getFileFilterDescription() {
|
||||||
return "history files (.xml)";
|
return "history files (.xml)";
|
||||||
|
@ -609,30 +609,30 @@ class HistoryDialog extends JDialog {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void export(File file) throws IOException {
|
public void export(File file) throws IOException {
|
||||||
History.exportHistory(getModel(), file);
|
History.exportHistory(getModel(), file);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String getDefaultFileName() {
|
public String getDefaultFileName() {
|
||||||
return "history.xml";
|
return "history.xml";
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
private static class HistoryFilter extends RowFilter<Object, Integer> {
|
private static class HistoryFilter extends RowFilter<Object, Integer> {
|
||||||
|
|
||||||
private final Pattern filter;
|
private final Pattern filter;
|
||||||
|
|
||||||
|
|
||||||
public HistoryFilter(String filter) {
|
public HistoryFilter(String filter) {
|
||||||
this.filter = compile(quote(filter), CASE_INSENSITIVE | UNICODE_CASE | CANON_EQ);
|
this.filter = compile(quote(filter), CASE_INSENSITIVE | UNICODE_CASE | CANON_EQ);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean include(Entry<?, ? extends Integer> entry) {
|
public boolean include(Entry<?, ? extends Integer> entry) {
|
||||||
// sequence model
|
// sequence model
|
||||||
|
@ -658,23 +658,23 @@ class HistoryDialog extends JDialog {
|
||||||
throw new IllegalArgumentException("Illegal model: " + entry.getModel());
|
throw new IllegalArgumentException("Illegal model: " + entry.getModel());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
private boolean include(Element element) {
|
private boolean include(Element element) {
|
||||||
return include(element.to()) || include(element.from()) || include(element.dir().getPath());
|
return include(element.to()) || include(element.from()) || include(element.dir().getPath());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
private boolean include(String value) {
|
private boolean include(String value) {
|
||||||
return filter.matcher(value).find();
|
return filter.matcher(value).find();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
private static class SequenceTableModel extends AbstractTableModel {
|
private static class SequenceTableModel extends AbstractTableModel {
|
||||||
|
|
||||||
private List<Sequence> data = emptyList();
|
private List<Sequence> data = emptyList();
|
||||||
|
|
||||||
|
|
||||||
public void setData(List<Sequence> data) {
|
public void setData(List<Sequence> data) {
|
||||||
this.data = new ArrayList<Sequence>(data);
|
this.data = new ArrayList<Sequence>(data);
|
||||||
|
|
||||||
|
@ -682,12 +682,12 @@ class HistoryDialog extends JDialog {
|
||||||
fireTableDataChanged();
|
fireTableDataChanged();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
public List<Sequence> getData() {
|
public List<Sequence> getData() {
|
||||||
return unmodifiableList(data);
|
return unmodifiableList(data);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String getColumnName(int column) {
|
public String getColumnName(int column) {
|
||||||
switch (column) {
|
switch (column) {
|
||||||
|
@ -704,19 +704,19 @@ class HistoryDialog extends JDialog {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public int getColumnCount() {
|
public int getColumnCount() {
|
||||||
return 4;
|
return 4;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public int getRowCount() {
|
public int getRowCount() {
|
||||||
return data.size();
|
return data.size();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Class<?> getColumnClass(int column) {
|
public Class<?> getColumnClass(int column) {
|
||||||
switch (column) {
|
switch (column) {
|
||||||
|
@ -733,7 +733,7 @@ class HistoryDialog extends JDialog {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Object getValueAt(int row, int column) {
|
public Object getValueAt(int row, int column) {
|
||||||
switch (column) {
|
switch (column) {
|
||||||
|
@ -750,12 +750,12 @@ class HistoryDialog extends JDialog {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
public Sequence getRow(int row) {
|
public Sequence getRow(int row) {
|
||||||
return data.get(row);
|
return data.get(row);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
private String getName(Sequence sequence) {
|
private String getName(Sequence sequence) {
|
||||||
StringBuilder sb = new StringBuilder();
|
StringBuilder sb = new StringBuilder();
|
||||||
|
|
||||||
|
@ -775,12 +775,12 @@ class HistoryDialog extends JDialog {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
private static class ElementTableModel extends AbstractTableModel {
|
private static class ElementTableModel extends AbstractTableModel {
|
||||||
|
|
||||||
private List<Element> data = emptyList();
|
private List<Element> data = emptyList();
|
||||||
|
|
||||||
|
|
||||||
public void setData(List<Element> data) {
|
public void setData(List<Element> data) {
|
||||||
this.data = new ArrayList<Element>(data);
|
this.data = new ArrayList<Element>(data);
|
||||||
|
|
||||||
|
@ -788,7 +788,7 @@ class HistoryDialog extends JDialog {
|
||||||
fireTableDataChanged();
|
fireTableDataChanged();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String getColumnName(int column) {
|
public String getColumnName(int column) {
|
||||||
switch (column) {
|
switch (column) {
|
||||||
|
@ -805,19 +805,19 @@ class HistoryDialog extends JDialog {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public int getColumnCount() {
|
public int getColumnCount() {
|
||||||
return 4;
|
return 4;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public int getRowCount() {
|
public int getRowCount() {
|
||||||
return data.size();
|
return data.size();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Class<?> getColumnClass(int column) {
|
public Class<?> getColumnClass(int column) {
|
||||||
switch (column) {
|
switch (column) {
|
||||||
|
@ -834,7 +834,7 @@ class HistoryDialog extends JDialog {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Object getValueAt(int row, int column) {
|
public Object getValueAt(int row, int column) {
|
||||||
switch (column) {
|
switch (column) {
|
||||||
|
@ -851,12 +851,12 @@ class HistoryDialog extends JDialog {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
public Element getRow(int row) {
|
public Element getRow(int row) {
|
||||||
return data.get(row);
|
return data.get(row);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
public boolean isBroken(int row) {
|
public boolean isBroken(int row) {
|
||||||
Element element = data.get(row);
|
Element element = data.get(row);
|
||||||
|
|
||||||
|
|
|
@ -2,8 +2,6 @@
|
||||||
package net.sourceforge.filebot.ui.rename;
|
package net.sourceforge.filebot.ui.rename;
|
||||||
|
|
||||||
|
|
||||||
import static java.util.Arrays.*;
|
|
||||||
import static java.util.Collections.*;
|
|
||||||
import static net.sourceforge.filebot.MediaTypes.*;
|
import static net.sourceforge.filebot.MediaTypes.*;
|
||||||
import static net.sourceforge.filebot.media.MediaDetection.*;
|
import static net.sourceforge.filebot.media.MediaDetection.*;
|
||||||
import static net.sourceforge.tuned.FileUtilities.*;
|
import static net.sourceforge.tuned.FileUtilities.*;
|
||||||
|
@ -37,6 +35,7 @@ import javax.swing.Action;
|
||||||
import javax.swing.SwingUtilities;
|
import javax.swing.SwingUtilities;
|
||||||
|
|
||||||
import net.sourceforge.filebot.Analytics;
|
import net.sourceforge.filebot.Analytics;
|
||||||
|
import net.sourceforge.filebot.media.ReleaseInfo;
|
||||||
import net.sourceforge.filebot.similarity.Match;
|
import net.sourceforge.filebot.similarity.Match;
|
||||||
import net.sourceforge.filebot.similarity.NameSimilarityMetric;
|
import net.sourceforge.filebot.similarity.NameSimilarityMetric;
|
||||||
import net.sourceforge.filebot.similarity.SimilarityMetric;
|
import net.sourceforge.filebot.similarity.SimilarityMetric;
|
||||||
|
@ -59,24 +58,30 @@ class MovieHashMatcher implements AutoCompleteMatcher {
|
||||||
@Override
|
@Override
|
||||||
public List<Match<File, ?>> match(final List<File> files, final Locale locale, final boolean autodetect, final Component parent) throws Exception {
|
public List<Match<File, ?>> match(final List<File> files, final Locale locale, final boolean autodetect, final Component parent) throws Exception {
|
||||||
// handle movie files
|
// handle movie files
|
||||||
File[] movieFiles = filter(files, VIDEO_FILES).toArray(new File[0]);
|
List<File> movieFiles = filter(files, VIDEO_FILES);
|
||||||
File[] subtitleFiles = filter(files, SUBTITLE_FILES).toArray(new File[0]);
|
|
||||||
Movie[] movieByFileHash = null;
|
|
||||||
|
|
||||||
if (movieFiles.length > 0) {
|
Map<File, List<File>> derivatesByMovieFile = new HashMap<File, List<File>>();
|
||||||
// match movie hashes online
|
for (File movieFile : movieFiles) {
|
||||||
try {
|
derivatesByMovieFile.put(movieFile, new ArrayList<File>());
|
||||||
movieByFileHash = service.getMovieDescriptors(movieFiles, locale);
|
|
||||||
Analytics.trackEvent(service.getName(), "HashLookup", "Movie", movieByFileHash.length - frequency(asList(movieByFileHash), null)); // number of positive hash lookups
|
|
||||||
} catch (UnsupportedOperationException e) {
|
|
||||||
movieByFileHash = new Movie[movieFiles.length];
|
|
||||||
}
|
|
||||||
} else if (subtitleFiles.length > 0) {
|
|
||||||
// special handling if there is only subtitle files
|
|
||||||
movieByFileHash = new Movie[subtitleFiles.length];
|
|
||||||
movieFiles = subtitleFiles;
|
|
||||||
subtitleFiles = new File[0];
|
|
||||||
}
|
}
|
||||||
|
for (File file : files) {
|
||||||
|
for (File movieFile : movieFiles) {
|
||||||
|
if (!file.equals(movieFile) && isDerived(file, movieFile)) {
|
||||||
|
derivatesByMovieFile.get(movieFile).add(file);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
List<File> standaloneFiles = new ArrayList<File>(files);
|
||||||
|
for (List<File> derivates : derivatesByMovieFile.values()) {
|
||||||
|
standaloneFiles.removeAll(derivates);
|
||||||
|
}
|
||||||
|
|
||||||
|
List<File> movieMatchFiles = new ArrayList<File>();
|
||||||
|
movieMatchFiles.addAll(movieFiles);
|
||||||
|
movieMatchFiles.addAll(filter(files, new ReleaseInfo().getDiskFolderFilter()));
|
||||||
|
movieMatchFiles.addAll(filter(standaloneFiles, SUBTITLE_FILES));
|
||||||
|
|
||||||
// map movies to (possibly multiple) files (in natural order)
|
// map movies to (possibly multiple) files (in natural order)
|
||||||
Map<Movie, SortedSet<File>> filesByMovie = new HashMap<Movie, SortedSet<File>>();
|
Map<Movie, SortedSet<File>> filesByMovie = new HashMap<Movie, SortedSet<File>>();
|
||||||
|
@ -84,10 +89,21 @@ class MovieHashMatcher implements AutoCompleteMatcher {
|
||||||
// match remaining movies file by file in parallel
|
// match remaining movies file by file in parallel
|
||||||
List<Callable<Entry<File, Movie>>> grabMovieJobs = new ArrayList<Callable<Entry<File, Movie>>>();
|
List<Callable<Entry<File, Movie>>> grabMovieJobs = new ArrayList<Callable<Entry<File, Movie>>>();
|
||||||
|
|
||||||
|
// match movie hashes online
|
||||||
|
Map<File, Movie> movieByFile = new HashMap<File, Movie>();
|
||||||
|
if (movieFiles.size() > 0) {
|
||||||
|
try {
|
||||||
|
Map<File, Movie> hashLookup = service.getMovieDescriptors(movieFiles, locale);
|
||||||
|
movieByFile.putAll(hashLookup);
|
||||||
|
Analytics.trackEvent(service.getName(), "HashLookup", "Movie", hashLookup.size()); // number of positive hash lookups
|
||||||
|
} catch (UnsupportedOperationException e) {
|
||||||
|
// ignore
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// map all files by movie
|
// map all files by movie
|
||||||
for (int i = 0; i < movieFiles.length; i++) {
|
for (final File file : movieMatchFiles) {
|
||||||
final Movie movie = movieByFileHash[i];
|
final Movie movie = movieByFile.get(file);
|
||||||
final File file = movieFiles[i];
|
|
||||||
grabMovieJobs.add(new Callable<Entry<File, Movie>>() {
|
grabMovieJobs.add(new Callable<Entry<File, Movie>>() {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -146,17 +162,13 @@ class MovieHashMatcher implements AutoCompleteMatcher {
|
||||||
}
|
}
|
||||||
|
|
||||||
matches.add(new Match<File, Movie>(file, part));
|
matches.add(new Match<File, Movie>(file, part));
|
||||||
}
|
|
||||||
}
|
// automatically add matches for derivates
|
||||||
|
List<File> derivates = derivatesByMovieFile.get(file);
|
||||||
// handle subtitle files
|
if (derivates != null) {
|
||||||
for (File subtitle : subtitleFiles) {
|
for (File derivate : derivates) {
|
||||||
// check if subtitle corresponds to a movie file (same name, different extension)
|
matches.add(new Match<File, Movie>(derivate, part));
|
||||||
for (Match<File, ?> movieMatch : matches) {
|
}
|
||||||
if (isDerived(subtitle, movieMatch.getValue())) {
|
|
||||||
matches.add(new Match<File, Object>(subtitle, movieMatch.getCandidate()));
|
|
||||||
// movie match found, we're done
|
|
||||||
break;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -13,16 +13,16 @@ import java.awt.event.ActionEvent;
|
||||||
import java.beans.PropertyChangeEvent;
|
import java.beans.PropertyChangeEvent;
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
import java.util.AbstractList;
|
import java.util.AbstractList;
|
||||||
|
import java.util.AbstractMap.SimpleEntry;
|
||||||
import java.util.ArrayList;
|
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.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
import java.util.Map.Entry;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
import java.util.TreeSet;
|
import java.util.TreeSet;
|
||||||
import java.util.AbstractMap.SimpleEntry;
|
|
||||||
import java.util.Map.Entry;
|
|
||||||
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;
|
||||||
|
@ -34,15 +34,15 @@ 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.ProgressDialog;
|
||||||
import net.sourceforge.tuned.ui.SwingWorkerPropertyChangeAdapter;
|
|
||||||
import net.sourceforge.tuned.ui.ProgressDialog.Cancellable;
|
import net.sourceforge.tuned.ui.ProgressDialog.Cancellable;
|
||||||
|
import net.sourceforge.tuned.ui.SwingWorkerPropertyChangeAdapter;
|
||||||
|
|
||||||
|
|
||||||
class RenameAction extends AbstractAction {
|
class RenameAction extends AbstractAction {
|
||||||
|
|
||||||
private final RenameModel model;
|
private final RenameModel model;
|
||||||
|
|
||||||
|
|
||||||
public RenameAction(RenameModel model) {
|
public RenameAction(RenameModel model) {
|
||||||
this.model = model;
|
this.model = model;
|
||||||
|
|
||||||
|
@ -51,7 +51,7 @@ class RenameAction extends AbstractAction {
|
||||||
putValue(SHORT_DESCRIPTION, "Rename files");
|
putValue(SHORT_DESCRIPTION, "Rename files");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
public void actionPerformed(ActionEvent evt) {
|
public void actionPerformed(ActionEvent evt) {
|
||||||
if (model.getRenameMap().isEmpty()) {
|
if (model.getRenameMap().isEmpty()) {
|
||||||
return;
|
return;
|
||||||
|
@ -85,7 +85,7 @@ class RenameAction extends AbstractAction {
|
||||||
window.setCursor(Cursor.getDefaultCursor());
|
window.setCursor(Cursor.getDefaultCursor());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
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>();
|
||||||
|
@ -117,7 +117,7 @@ class RenameAction extends AbstractAction {
|
||||||
return renameMap;
|
return renameMap;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
private List<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());
|
||||||
|
|
||||||
|
@ -132,13 +132,13 @@ class RenameAction extends AbstractAction {
|
||||||
return source.get(index).getValue();
|
return source.get(index).getValue();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public File set(int index, File name) {
|
public File set(int index, File name) {
|
||||||
return source.get(index).setValue(name);
|
return source.get(index).setValue(name);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public int size() {
|
public int size() {
|
||||||
return source.size();
|
return source.size();
|
||||||
|
@ -154,7 +154,7 @@ class RenameAction extends AbstractAction {
|
||||||
return emptyList();
|
return emptyList();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
protected ProgressDialog createProgressDialog(Window parent, final RenameJob job) {
|
protected ProgressDialog createProgressDialog(Window parent, final RenameJob job) {
|
||||||
final ProgressDialog dialog = new ProgressDialog(parent, job);
|
final ProgressDialog dialog = new ProgressDialog(parent, job);
|
||||||
|
|
||||||
|
@ -175,7 +175,7 @@ class RenameAction extends AbstractAction {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void done(PropertyChangeEvent evt) {
|
protected void done(PropertyChangeEvent evt) {
|
||||||
dialog.close();
|
dialog.close();
|
||||||
|
@ -185,19 +185,19 @@ class RenameAction extends AbstractAction {
|
||||||
return dialog;
|
return dialog;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
protected class RenameJob extends SwingWorker<Map<File, File>, Void> implements Cancellable {
|
protected class RenameJob extends SwingWorker<Map<File, File>, Void> implements Cancellable {
|
||||||
|
|
||||||
private final Map<File, File> renameMap;
|
private final Map<File, File> renameMap;
|
||||||
private final Map<File, File> renameLog;
|
private final Map<File, File> renameLog;
|
||||||
|
|
||||||
|
|
||||||
public RenameJob(Map<File, File> renameMap) {
|
public RenameJob(Map<File, File> renameMap) {
|
||||||
this.renameMap = synchronizedMap(renameMap);
|
this.renameMap = synchronizedMap(renameMap);
|
||||||
this.renameLog = synchronizedMap(new LinkedHashMap<File, File>());
|
this.renameLog = synchronizedMap(new LinkedHashMap<File, File>());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected Map<File, File> doInBackground() throws Exception {
|
protected Map<File, File> doInBackground() throws Exception {
|
||||||
for (Entry<File, File> mapping : renameMap.entrySet()) {
|
for (Entry<File, File> mapping : renameMap.entrySet()) {
|
||||||
|
@ -209,7 +209,7 @@ class RenameAction extends AbstractAction {
|
||||||
firePropertyChange("currentFile", mapping.getKey(), mapping.getValue());
|
firePropertyChange("currentFile", mapping.getKey(), mapping.getValue());
|
||||||
|
|
||||||
// rename file, throw exception on failure
|
// rename file, throw exception on failure
|
||||||
renameFile(mapping.getKey(), mapping.getValue());
|
moveRename(mapping.getKey(), mapping.getValue());
|
||||||
|
|
||||||
// remember successfully renamed matches for history entry and possible revert
|
// remember successfully renamed matches for history entry and possible revert
|
||||||
renameLog.put(mapping.getKey(), mapping.getValue());
|
renameLog.put(mapping.getKey(), mapping.getValue());
|
||||||
|
@ -218,7 +218,7 @@ class RenameAction extends AbstractAction {
|
||||||
return renameLog;
|
return renameLog;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void done() {
|
protected void done() {
|
||||||
try {
|
try {
|
||||||
|
@ -251,7 +251,7 @@ class RenameAction extends AbstractAction {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean cancel() {
|
public boolean cancel() {
|
||||||
return cancel(true);
|
return cancel(true);
|
||||||
|
|
|
@ -10,8 +10,10 @@ import java.io.IOException;
|
||||||
import java.net.URL;
|
import java.net.URL;
|
||||||
import java.net.URLConnection;
|
import java.net.URLConnection;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
|
import java.util.Collection;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Locale;
|
import java.util.Locale;
|
||||||
|
import java.util.Map;
|
||||||
import java.util.Scanner;
|
import java.util.Scanner;
|
||||||
import java.util.logging.Level;
|
import java.util.logging.Level;
|
||||||
import java.util.logging.Logger;
|
import java.util.logging.Logger;
|
||||||
|
@ -122,7 +124,7 @@ public class IMDbClient implements MovieIdentificationService {
|
||||||
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Movie[] getMovieDescriptors(File[] movieFiles, Locale locale) throws Exception {
|
public Map<File, Movie> getMovieDescriptors(Collection<File> movieFiles, Locale locale) throws Exception {
|
||||||
throw new UnsupportedOperationException();
|
throw new UnsupportedOperationException();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -3,8 +3,10 @@ package net.sourceforge.filebot.web;
|
||||||
|
|
||||||
|
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
|
import java.util.Collection;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Locale;
|
import java.util.Locale;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
import javax.swing.Icon;
|
import javax.swing.Icon;
|
||||||
|
|
||||||
|
@ -13,16 +15,16 @@ public interface MovieIdentificationService {
|
||||||
|
|
||||||
public String getName();
|
public String getName();
|
||||||
|
|
||||||
|
|
||||||
public Icon getIcon();
|
public Icon getIcon();
|
||||||
|
|
||||||
|
|
||||||
public List<Movie> searchMovie(String query, Locale locale) throws Exception;
|
public List<Movie> searchMovie(String query, Locale locale) throws Exception;
|
||||||
|
|
||||||
|
|
||||||
public Movie getMovieDescriptor(int imdbid, Locale locale) throws Exception;
|
public Movie getMovieDescriptor(int imdbid, Locale locale) throws Exception;
|
||||||
|
|
||||||
|
|
||||||
public Movie[] getMovieDescriptors(File[] movieFiles, Locale locale) throws Exception;
|
public Map<File, Movie> getMovieDescriptors(Collection<File> movieFiles, Locale locale) throws Exception;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,6 +4,7 @@ package net.sourceforge.filebot.web;
|
||||||
|
|
||||||
import static java.lang.Math.*;
|
import static java.lang.Math.*;
|
||||||
import static java.util.Arrays.*;
|
import static java.util.Arrays.*;
|
||||||
|
import static java.util.Collections.*;
|
||||||
import static net.sourceforge.filebot.web.OpenSubtitlesHasher.*;
|
import static net.sourceforge.filebot.web.OpenSubtitlesHasher.*;
|
||||||
|
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
|
@ -12,6 +13,7 @@ import java.net.URI;
|
||||||
import java.security.MessageDigest;
|
import java.security.MessageDigest;
|
||||||
import java.security.NoSuchAlgorithmException;
|
import java.security.NoSuchAlgorithmException;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
|
import java.util.Collection;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.LinkedList;
|
import java.util.LinkedList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
@ -184,37 +186,36 @@ public class OpenSubtitlesClient implements SubtitleProvider, VideoHashSubtitleS
|
||||||
|
|
||||||
|
|
||||||
public Movie getMovieDescriptor(File movieFile, Locale locale) throws Exception {
|
public Movie getMovieDescriptor(File movieFile, Locale locale) throws Exception {
|
||||||
return getMovieDescriptors(new File[] { movieFile }, locale)[0];
|
return getMovieDescriptors(singleton(movieFile), locale).get(movieFile);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Movie[] getMovieDescriptors(File[] movieFiles, Locale locale) throws Exception {
|
public Map<File, Movie> getMovieDescriptors(Collection<File> movieFiles, Locale locale) throws Exception {
|
||||||
// create result array
|
// create result array
|
||||||
Movie[] result = new Movie[movieFiles.length];
|
Map<File, Movie> result = new HashMap<File, Movie>();
|
||||||
|
|
||||||
// compute movie hashes
|
// compute movie hashes
|
||||||
Map<String, Integer> indexMap = new HashMap<String, Integer>(movieFiles.length);
|
Map<String, File> hashMap = new HashMap<String, File>(movieFiles.size());
|
||||||
|
|
||||||
for (int i = 0; i < movieFiles.length; i++) {
|
for (File file : movieFiles) {
|
||||||
if (movieFiles[i].length() > HASH_CHUNK_SIZE) {
|
if (file.length() > HASH_CHUNK_SIZE) {
|
||||||
indexMap.put(computeHash(movieFiles[i]), i); // remember original index
|
hashMap.put(computeHash(file), file); // map file by hash
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (indexMap.size() > 0) {
|
if (hashMap.size() > 0) {
|
||||||
// require login
|
// require login
|
||||||
login();
|
login();
|
||||||
|
|
||||||
// dispatch query for all hashes
|
// dispatch query for all hashes
|
||||||
List<String> hashes = new ArrayList<String>(indexMap.keySet());
|
List<String> hashes = new ArrayList<String>(hashMap.keySet());
|
||||||
int batchSize = 50;
|
int batchSize = 50;
|
||||||
for (int bn = 0; bn < ceil((float) hashes.size() / batchSize); bn++) {
|
for (int bn = 0; bn < ceil((float) hashes.size() / batchSize); bn++) {
|
||||||
List<String> batch = hashes.subList(bn * batchSize, min((bn * batchSize) + batchSize, hashes.size()));
|
List<String> batch = hashes.subList(bn * batchSize, min((bn * batchSize) + batchSize, hashes.size()));
|
||||||
|
|
||||||
for (Entry<String, Movie> entry : xmlrpc.checkMovieHash(batch).entrySet()) {
|
for (Entry<String, Movie> entry : xmlrpc.checkMovieHash(batch).entrySet()) {
|
||||||
int index = indexMap.get(entry.getKey());
|
result.put(hashMap.get(entry.getKey()), entry.getValue());
|
||||||
result[index] = entry.getValue();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -13,6 +13,7 @@ import java.io.Serializable;
|
||||||
import java.net.MalformedURLException;
|
import java.net.MalformedURLException;
|
||||||
import java.net.URL;
|
import java.net.URL;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
|
import java.util.Collection;
|
||||||
import java.util.EnumMap;
|
import java.util.EnumMap;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Locale;
|
import java.util.Locale;
|
||||||
|
@ -70,11 +71,6 @@ public class TMDbClient implements MovieIdentificationService {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
public List<Movie> searchMovie(File file, Locale locale) throws IOException, SAXException {
|
|
||||||
throw new UnsupportedOperationException();
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
public List<Movie> searchMovie(String hash, long bytesize, Locale locale) throws IOException, SAXException {
|
public List<Movie> searchMovie(String hash, long bytesize, Locale locale) throws IOException, SAXException {
|
||||||
return getMovies("Media.getInfo", hash + "/" + bytesize, locale);
|
return getMovies("Media.getInfo", hash + "/" + bytesize, locale);
|
||||||
}
|
}
|
||||||
|
@ -96,17 +92,8 @@ public class TMDbClient implements MovieIdentificationService {
|
||||||
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Movie[] getMovieDescriptors(File[] movieFiles, Locale locale) throws Exception {
|
public Map<File, Movie> getMovieDescriptors(Collection<File> movieFiles, Locale locale) throws Exception {
|
||||||
Movie[] movies = new Movie[movieFiles.length];
|
throw new UnsupportedOperationException();
|
||||||
|
|
||||||
for (int i = 0; i < movies.length; i++) {
|
|
||||||
List<Movie> options = searchMovie(movieFiles[i], locale);
|
|
||||||
|
|
||||||
// just use first result, if possible
|
|
||||||
movies[i] = options.isEmpty() ? null : options.get(0);
|
|
||||||
}
|
|
||||||
|
|
||||||
return movies;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -13,43 +13,36 @@ public class FastFile extends File {
|
||||||
private Long length;
|
private Long length;
|
||||||
private Boolean isDirectory;
|
private Boolean isDirectory;
|
||||||
private Boolean isFile;
|
private Boolean isFile;
|
||||||
private Boolean exists;
|
|
||||||
|
|
||||||
|
|
||||||
public FastFile(String path) {
|
public FastFile(String path) {
|
||||||
super(path);
|
super(path);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
public FastFile(File parent, String child) {
|
public FastFile(File parent, String child) {
|
||||||
super(parent, child);
|
super(parent, child);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public long length() {
|
public long length() {
|
||||||
return length != null ? length : (length = super.length());
|
return length != null ? length : (length = super.length());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean exists() {
|
|
||||||
return exists != null ? exists : (exists = super.exists());
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean isDirectory() {
|
public boolean isDirectory() {
|
||||||
return isDirectory != null ? isDirectory : (isDirectory = super.isDirectory());
|
return isDirectory != null ? isDirectory : (isDirectory = super.isDirectory());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean isFile() {
|
public boolean isFile() {
|
||||||
return isFile != null ? isFile : (isFile = super.isFile());
|
return isFile != null ? isFile : (isFile = super.isFile());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public File[] listFiles() {
|
public File[] listFiles() {
|
||||||
String[] names = list();
|
String[] names = list();
|
||||||
|
@ -62,12 +55,12 @@ public class FastFile extends File {
|
||||||
return files;
|
return files;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
public static List<FastFile> foreach(File... files) {
|
public static List<FastFile> foreach(File... files) {
|
||||||
return foreach(Arrays.asList(files));
|
return foreach(Arrays.asList(files));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
public static List<FastFile> foreach(final List<File> files) {
|
public static List<FastFile> foreach(final List<File> files) {
|
||||||
List<FastFile> result = new ArrayList<FastFile>(files.size());
|
List<FastFile> result = new ArrayList<FastFile>(files.size());
|
||||||
|
|
||||||
|
|
|
@ -42,7 +42,7 @@ import com.ibm.icu.text.CharsetMatch;
|
||||||
|
|
||||||
public final class FileUtilities {
|
public final class FileUtilities {
|
||||||
|
|
||||||
public static File renameFile(File source, File destination) throws IOException {
|
public static File moveRename(File source, File destination) throws IOException {
|
||||||
// resolve destination
|
// resolve destination
|
||||||
if (!destination.isAbsolute()) {
|
if (!destination.isAbsolute()) {
|
||||||
// same folder, different name
|
// same folder, different name
|
||||||
|
@ -57,25 +57,37 @@ public final class FileUtilities {
|
||||||
throw new IOException("Failed to create folder: " + destinationFolder);
|
throw new IOException("Failed to create folder: " + destinationFolder);
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
if (source.isDirectory()) { // move folder
|
||||||
renameFileNIO2(source, destination);
|
moveFolderIO(source, destination);
|
||||||
} catch (LinkageError e) {
|
} else { // move file
|
||||||
renameFileIO(source, destination);
|
try {
|
||||||
|
moveFileNIO2(source, destination);
|
||||||
|
} catch (LinkageError e) {
|
||||||
|
moveFileIO(source, destination);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return destination;
|
return destination;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
private static void renameFileNIO2(File source, File destination) throws IOException {
|
private static void moveFileNIO2(File source, File destination) throws IOException {
|
||||||
java.nio.file.Files.move(source.toPath(), destination.toPath());
|
java.nio.file.Files.move(source.toPath(), destination.toPath());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
private static void renameFileIO(File source, File destination) throws IOException {
|
private static void moveFileIO(File source, File destination) throws IOException {
|
||||||
if (!source.renameTo(destination)) {
|
if (!source.renameTo(destination)) {
|
||||||
// try using Guava IO utilities, that'll just copy files if renameTo() fails
|
// use "copy and delete" as fallback if standard rename fails
|
||||||
com.google.common.io.Files.move(source, destination);
|
org.apache.commons.io.FileUtils.moveFile(source, destination);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private static void moveFolderIO(File source, File destination) throws IOException {
|
||||||
|
if (!source.renameTo(destination)) {
|
||||||
|
// use "copy and delete" as fallback if standard move/rename fails
|
||||||
|
org.apache.commons.io.FileUtils.moveDirectory(source, destination);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue