+ 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:
Reinhard Pointner 2012-02-10 16:43:09 +00:00
parent 67fe97c345
commit cc5845b2a0
19 changed files with 290 additions and 209 deletions

View File

@ -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">

View File

@ -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>

BIN
lib/commons-io.jar Normal file

Binary file not shown.

Binary file not shown.

View File

@ -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

View File

@ -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) }

View File

@ -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);
} }

View File

@ -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;
}
}
} }

View File

@ -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$

View File

@ -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";

View File

@ -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);

View File

@ -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;
} }
} }
} }

View File

@ -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);

View File

@ -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();
} }

View File

@ -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;
} }

View File

@ -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();
} }
} }
} }

View File

@ -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;
} }

View File

@ -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());

View File

@ -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);
} }
} }