Reinhard Pointner 2016-09-28 13:19:20 +08:00
parent 477aa7019c
commit b1a30e4bc3
5 changed files with 129 additions and 62 deletions

View File

@ -297,7 +297,7 @@ class EpisodeListMatcher implements AutoCompleteMatcher {
}
protected String getQueryInputMessage(String header, String message, Collection<File> files) throws Exception {
List<File> selection = files.stream().sorted(comparing(File::length).reversed()).limit(5).collect(toList());
List<File> selection = files.stream().sorted(comparing(File::length).reversed()).limit(5).sorted(HUMAN_ORDER).collect(toList());
if (selection.isEmpty()) {
return "";
}
@ -309,7 +309,7 @@ class EpisodeListMatcher implements AutoCompleteMatcher {
}
TextColorizer colorizer = new TextColorizer("<nobr>• ", "</nobr><br>");
for (File file : sortByUniquePath(selection)) {
for (File file : selection) {
File path = getStructurePathTail(file);
if (path == null) {
path = getRelativePathTail(file, 3);

View File

@ -5,14 +5,15 @@ import static java.util.stream.Collectors.*;
import static net.filebot.Logging.*;
import static net.filebot.MediaTypes.*;
import static net.filebot.util.FileUtilities.*;
import static net.filebot.util.RegularExpressions.*;
import java.awt.datatransfer.Transferable;
import java.io.File;
import java.util.Collection;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Objects;
import java.util.Set;
import java.util.TreeSet;
import java.util.logging.Level;
import net.filebot.media.MediaDetection;
@ -50,26 +51,21 @@ class FilesListTransferablePolicy extends BackgroundFileTransferablePolicy<File>
@Override
protected void load(List<File> files, TransferAction action) {
Set<File> fileset = new LinkedHashSet<File>();
Set<File> fileset = new TreeSet<File>(CASE_INSENSITIVE_ORDER);
// load files recursively by default
load(files, action != TransferAction.LINK, fileset);
// use fast file to minimize system calls like length(), isDirectory(), isFile(), ...
publish(fileset.stream().map(FastFile::new).toArray(File[]::new));
// use fast file to minimize system calls like length(), isDirectory(), isFile(), ... and list files in human order
publish(fileset.stream().sorted(HUMAN_ORDER).map(FastFile::new).toArray(File[]::new));
}
private void load(List<File> files, boolean recursive, Collection<File> sink) {
for (File f : files) {
// ignore hidden files
if (f.isHidden()) {
continue;
}
// load file paths from text files
if (recursive && LIST_FILES.accept(f)) {
try {
String[] lines = readTextFile(f).split("\\R");
String[] lines = NEWLINE.split(readTextFile(f));
List<File> paths = stream(lines).filter(s -> s.length() > 0).map(path -> {
try {
File file = new File(path);
@ -96,7 +92,7 @@ class FilesListTransferablePolicy extends BackgroundFileTransferablePolicy<File>
// load folders recursively
else if (f.isDirectory()) {
load(sortByUniquePath(getChildren(f)), true, sink); // FORCE NATURAL FILE ORDER
load(getChildren(f, NOT_HIDDEN), true, sink); // FORCE NATURAL FILE ORDER
}
}
}

View File

@ -96,7 +96,7 @@ class ChecksumTableTransferablePolicy extends BackgroundFileTransferablePolicy<C
// handle single folder drop
if (files.size() == 1 && containsOnly(files, FOLDERS)) {
for (File folder : files) {
for (File file : getChildren(folder, NOT_HIDDEN, CASE_INSENSITIVE_PATH)) {
for (File file : getChildren(folder, NOT_HIDDEN, CASE_INSENSITIVE_ORDER)) {
load(file, null, folder);
}
}
@ -179,7 +179,7 @@ class ChecksumTableTransferablePolicy extends BackgroundFileTransferablePolicy<C
if (absoluteFile.isDirectory()) {
// load all files in the file tree
for (File child : getChildren(absoluteFile, NOT_HIDDEN, CASE_INSENSITIVE_PATH)) {
for (File child : getChildren(absoluteFile, NOT_HIDDEN, CASE_INSENSITIVE_ORDER)) {
load(child, relativeFile, root);
}
} else {

View File

@ -0,0 +1,105 @@
package net.filebot.util;
import java.text.Collator;
import java.util.Comparator;
import java.util.Locale;
public class AlphanumComparator implements Comparator<String> {
protected Collator collator;
public AlphanumComparator(Collator collator) {
this.collator = collator;
}
public AlphanumComparator(Locale locale) {
this.collator = Collator.getInstance(locale);
this.collator.setDecomposition(Collator.FULL_DECOMPOSITION);
this.collator.setStrength(Collator.PRIMARY);
}
protected boolean isDigit(String s, int i) {
return Character.isDigit(s.charAt(i));
}
protected int getNumericValue(String s, int i) {
return Character.getNumericValue(s.charAt(i));
}
protected String getChunk(String s, int start) {
int index = start;
int length = s.length();
boolean mode = isDigit(s, index++);
while (index < length) {
if (mode != isDigit(s, index)) {
break;
}
++index;
}
return s.substring(start, index);
}
public int compare(String s1, String s2) {
int length1 = s1.length();
int length2 = s2.length();
int index1 = 0;
int index2 = 0;
int result = 0;
while (result == 0 && index1 < length1 && index2 < length2) {
String chunk1 = getChunk(s1, index1);
index1 += chunk1.length();
String chunk2 = getChunk(s2, index2);
index2 += chunk2.length();
if (isDigit(chunk1, 0) && isDigit(chunk2, 0)) {
int chunkLength1 = chunk1.length();
int chunkLength2 = chunk2.length();
// count and skip leading zeros
int zeroIndex1 = 0;
while (zeroIndex1 < chunkLength1 && getNumericValue(chunk1, zeroIndex1) == 0) {
++zeroIndex1;
}
// count and skip leading zeros
int zeroIndex2 = 0;
while (zeroIndex2 < chunkLength2 && getNumericValue(chunk2, zeroIndex2) == 0) {
++zeroIndex2;
}
// the longer run of non-zero digits is greater
result = (chunkLength1 - zeroIndex1) - (chunkLength2 - zeroIndex2);
// if the length is the same, the first differing digit decides
// which one is deemed greater.
int numberIndex1 = zeroIndex1;
int numberIndex2 = zeroIndex2;
while (result == 0 && numberIndex1 < chunkLength1 && numberIndex2 < chunkLength2) {
result = getNumericValue(chunk1, numberIndex1++) - getNumericValue(chunk2, numberIndex2++);
}
// if still no difference, the longer zeros-prefix is greater
if (result == 0) {
result = numberIndex1 - numberIndex2;
}
} else {
result = collator.compare(chunk1, chunk2);
}
}
// if there was no difference at all, let the longer one be the greater one
if (result == 0) {
result = length1 - length2;
}
// limit result to (-1, 0, or 1)
return Integer.signum(result);
}
}

View File

@ -3,6 +3,7 @@ package net.filebot.util;
import static java.nio.charset.StandardCharsets.*;
import static java.util.Arrays.*;
import static java.util.Collections.*;
import static java.util.Comparator.*;
import static java.util.stream.Collectors.*;
import static net.filebot.Logging.*;
import static net.filebot.util.RegularExpressions.*;
@ -39,6 +40,7 @@ import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.SortedMap;
import java.util.TreeMap;
@ -363,7 +365,7 @@ public final class FileUtilities {
public static List<File> sortByUniquePath(Collection<File> files) {
// sort by unique lower-case paths
TreeSet<File> sortedSet = new TreeSet<File>(CASE_INSENSITIVE_PATH);
TreeSet<File> sortedSet = new TreeSet<File>(CASE_INSENSITIVE_ORDER);
sortedSet.addAll(files);
return new ArrayList<File>(sortedSet);
@ -385,7 +387,7 @@ public final class FileUtilities {
}
public static FileFilter not(FileFilter filter) {
return new NotFileFilter(filter);
return f -> !filter.accept(f);
}
public static List<File> listPath(File file) {
@ -436,14 +438,14 @@ public final class FileUtilities {
return getChildren(folder, filter, null);
}
public static List<File> getChildren(File folder, FileFilter filter, Comparator<File> sorter) {
public static List<File> getChildren(File folder, FileFilter filter, Comparator<File> order) {
File[] files = filter == null ? folder.listFiles() : folder.listFiles(filter);
// children array may be null if folder permissions do not allow listing of files
if (files == null) {
files = new File[0];
} else if (sorter != null) {
sort(files, sorter);
} else if (order != null) {
sort(files, order);
}
return asList(files);
@ -667,21 +669,11 @@ public final class FileUtilities {
return String.format("%,d bytes", size);
}
public static final FileFilter FOLDERS = new FileFilter() {
public static final FileFilter FOLDERS = File::isDirectory;
@Override
public boolean accept(File file) {
return file.isDirectory();
}
};
public static final FileFilter FILES = File::isFile;
public static final FileFilter FILES = new FileFilter() {
@Override
public boolean accept(File file) {
return file.isFile();
}
};
public static final FileFilter NOT_HIDDEN = not(File::isHidden);
public static final FileFilter TEMPORARY = new FileFilter() {
@ -689,15 +681,7 @@ public final class FileUtilities {
@Override
public boolean accept(File file) {
return file.getAbsolutePath().startsWith(tmpdir);
}
};
public static final FileFilter NOT_HIDDEN = new FileFilter() {
@Override
public boolean accept(File file) {
return !file.isHidden();
return file.getPath().startsWith(tmpdir);
}
};
@ -801,27 +785,9 @@ public final class FileUtilities {
}
}
public static class NotFileFilter implements FileFilter {
public static final Comparator<File> CASE_INSENSITIVE_ORDER = comparing(File::getPath, String.CASE_INSENSITIVE_ORDER);
public FileFilter filter;
public NotFileFilter(FileFilter filter) {
this.filter = filter;
}
@Override
public boolean accept(File file) {
return !filter.accept(file);
}
}
public static final Comparator<File> CASE_INSENSITIVE_PATH = new Comparator<File>() {
@Override
public int compare(File o1, File o2) {
return o1.getPath().compareToIgnoreCase(o2.getPath());
}
};
public static final Comparator<File> HUMAN_ORDER = comparing(File::getPath, new AlphanumComparator(Locale.ENGLISH));
/**
* Dummy constructor to prevent instantiation.