List files in human order (Original Files area only)
@see https://www.filebot.net/forums/viewtopic.php?f=10&t=4174 @see http://stackoverflow.com/questions/12640280/looking-for-a-combination-of-alphabetical-and-natural-order-aka-user-sane-sort
This commit is contained in:
parent
477aa7019c
commit
b1a30e4bc3
|
@ -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);
|
||||
|
|
|
@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
||||
}
|
|
@ -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.
|
||||
|
|
Loading…
Reference in New Issue