* Format Source
This commit is contained in:
parent
38a046bf2d
commit
8299e849aa
|
@ -6,7 +6,7 @@ import java.io.File;
|
|||
|
||||
|
||||
public interface RenameAction {
|
||||
|
||||
|
||||
File rename(File from, File to) throws Exception;
|
||||
|
||||
|
||||
}
|
||||
|
|
|
@ -56,7 +56,7 @@ public final class ResourceManager {
|
|||
|
||||
/**
|
||||
* Get the URL of an image resource in this jar. Image must be located in <code>resources/</code> and the file type is assumed to be png.
|
||||
*
|
||||
*
|
||||
* @param name
|
||||
* simple name of the resource (without extension)
|
||||
* @return URL of the resource or null if resource does not exist
|
||||
|
|
|
@ -35,6 +35,7 @@ public class ApacheVFS implements ArchiveExtractor, Closeable {
|
|||
this.archive = fsm.createFileSystem(fsm.toFileObject(file));
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<FileInfo> listFiles() throws Exception {
|
||||
List<FileInfo> paths = new ArrayList<FileInfo>();
|
||||
for (FileObject it : archive.findFiles(ALL_FILES)) {
|
||||
|
@ -46,10 +47,12 @@ public class ApacheVFS implements ArchiveExtractor, Closeable {
|
|||
return paths;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void extract(File outputDir) throws Exception {
|
||||
extract(outputDir, null);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void extract(File outputDir, FileFilter filter) throws Exception {
|
||||
fsm.toFileObject(outputDir).copyFrom(archive, filter == null ? ALL_FILES : new FileFilterSelector(filter));
|
||||
}
|
||||
|
|
|
@ -18,33 +18,34 @@ import net.sf.sevenzipjbinding.impl.RandomAccessFileInStream;
|
|||
|
||||
|
||||
class ArchiveOpenVolumeCallback implements IArchiveOpenVolumeCallback, IArchiveOpenCallback, Closeable {
|
||||
|
||||
|
||||
/**
|
||||
* Cache for opened file streams
|
||||
*/
|
||||
private Map<String, RandomAccessFile> openedRandomAccessFileList = new HashMap<String, RandomAccessFile>();
|
||||
|
||||
|
||||
/**
|
||||
* Name of the last volume returned by {@link #getStream(String)}
|
||||
*/
|
||||
private String name;
|
||||
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* This method should at least provide the name of the last
|
||||
* opened volume (propID=PropID.NAME).
|
||||
*
|
||||
*
|
||||
* @see IArchiveOpenVolumeCallback#getProperty(PropID)
|
||||
*/
|
||||
@Override
|
||||
public Object getProperty(PropID propID) throws SevenZipException {
|
||||
switch (propID) {
|
||||
case NAME:
|
||||
return name;
|
||||
case NAME:
|
||||
return name;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* The name of the required volume will be calculated out of the
|
||||
* name of the first volume and a volume index. In case of RAR file,
|
||||
|
@ -56,6 +57,7 @@ class ArchiveOpenVolumeCallback implements IArchiveOpenVolumeCallback, IArchiveO
|
|||
* <li>test.part001.rar - first part of a multi-part archive. "00" indicates, that at least 100 volumes must exist.</li>
|
||||
* </ul>
|
||||
*/
|
||||
@Override
|
||||
public IInStream getStream(String filename) throws SevenZipException {
|
||||
try {
|
||||
// We use caching of opened streams, so check cache first
|
||||
|
@ -64,19 +66,19 @@ class ArchiveOpenVolumeCallback implements IArchiveOpenVolumeCallback, IArchiveO
|
|||
// Move the file pointer back to the beginning
|
||||
// in order to emulating new stream
|
||||
randomAccessFile.seek(0);
|
||||
|
||||
|
||||
// Save current volume name in case getProperty() will be called
|
||||
name = filename;
|
||||
|
||||
|
||||
return new RandomAccessFileInStream(randomAccessFile);
|
||||
}
|
||||
|
||||
|
||||
// Nothing useful in cache. Open required volume.
|
||||
randomAccessFile = new RandomAccessFile(filename, "r");
|
||||
|
||||
|
||||
// Put new stream in the cache
|
||||
openedRandomAccessFileList.put(filename, randomAccessFile);
|
||||
|
||||
|
||||
// Save current volume name in case getProperty() will be called
|
||||
name = filename;
|
||||
return new RandomAccessFileInStream(randomAccessFile);
|
||||
|
@ -85,7 +87,7 @@ class ArchiveOpenVolumeCallback implements IArchiveOpenVolumeCallback, IArchiveO
|
|||
// 1. never exists. 7-Zip doesn't know how many volumes should
|
||||
// exist, so it have to try each volume.
|
||||
// 2. should be there, but doesn't. This is an error case.
|
||||
|
||||
|
||||
// Since normal and error cases are possible,
|
||||
// we can't throw an error message
|
||||
return null; // We return always null in this case
|
||||
|
@ -93,25 +95,26 @@ class ArchiveOpenVolumeCallback implements IArchiveOpenVolumeCallback, IArchiveO
|
|||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Close all opened streams
|
||||
*/
|
||||
@Override
|
||||
public void close() throws IOException {
|
||||
for (RandomAccessFile file : openedRandomAccessFileList.values()) {
|
||||
file.close();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
@Override
|
||||
public void setCompleted(Long files, Long bytes) throws SevenZipException {
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
@Override
|
||||
public void setTotal(Long files, Long bytes) throws SevenZipException {
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
|
|
@ -16,48 +16,51 @@ import net.sf.sevenzipjbinding.SevenZipException;
|
|||
|
||||
|
||||
class ExtractCallback implements IArchiveExtractCallback {
|
||||
|
||||
|
||||
private ISevenZipInArchive inArchive;
|
||||
private ExtractOutProvider extractOut;
|
||||
|
||||
|
||||
private ExtractOutStream output = null;
|
||||
|
||||
|
||||
|
||||
|
||||
public ExtractCallback(ISevenZipInArchive inArchive, ExtractOutProvider extractOut) {
|
||||
this.inArchive = inArchive;
|
||||
this.extractOut = extractOut;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
@Override
|
||||
public ISequentialOutStream getStream(int index, ExtractAskMode extractAskMode) throws SevenZipException {
|
||||
if (extractAskMode != ExtractAskMode.EXTRACT) {
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
boolean isFolder = (Boolean) inArchive.getProperty(index, PropID.IS_FOLDER);
|
||||
if (isFolder) {
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
String path = (String) inArchive.getProperty(index, PropID.PATH);
|
||||
try {
|
||||
OutputStream target = extractOut.getStream(new File(path));
|
||||
if (target == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
output = new ExtractOutStream(target);
|
||||
return output;
|
||||
} catch (IOException e) {
|
||||
throw new SevenZipException(e);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
@Override
|
||||
public void prepareOperation(ExtractAskMode extractAskMode) throws SevenZipException {
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
@Override
|
||||
public void setOperationResult(ExtractOperationResult extractOperationResult) throws SevenZipException {
|
||||
if (output != null) {
|
||||
try {
|
||||
|
@ -68,18 +71,20 @@ class ExtractCallback implements IArchiveExtractCallback {
|
|||
output = null;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
if (extractOperationResult != ExtractOperationResult.OK) {
|
||||
throw new SevenZipException("Extraction Error: " + extractOperationResult);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
@Override
|
||||
public void setCompleted(long completeValue) throws SevenZipException {
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
@Override
|
||||
public void setTotal(long total) throws SevenZipException {
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
|
|
@ -8,7 +8,7 @@ import java.io.OutputStream;
|
|||
|
||||
|
||||
public interface ExtractOutProvider {
|
||||
|
||||
|
||||
OutputStream getStream(File archivePath) throws IOException;
|
||||
|
||||
|
||||
}
|
||||
|
|
|
@ -11,15 +11,15 @@ import net.sf.sevenzipjbinding.SevenZipException;
|
|||
|
||||
|
||||
class ExtractOutStream implements ISequentialOutStream, Closeable {
|
||||
|
||||
|
||||
private OutputStream out;
|
||||
|
||||
|
||||
|
||||
|
||||
public ExtractOutStream(OutputStream out) {
|
||||
this.out = out;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
@Override
|
||||
public int write(byte[] data) throws SevenZipException {
|
||||
try {
|
||||
|
@ -29,11 +29,11 @@ class ExtractOutStream implements ISequentialOutStream, Closeable {
|
|||
}
|
||||
return data.length; // return amount of proceed data
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
@Override
|
||||
public void close() throws IOException {
|
||||
out.close();
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
|
|
@ -61,6 +61,7 @@ public class SevenZipExecutable implements ArchiveExtractor {
|
|||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<FileInfo> listFiles() throws IOException {
|
||||
List<FileInfo> paths = new ArrayList<FileInfo>();
|
||||
|
||||
|
@ -81,11 +82,13 @@ public class SevenZipExecutable implements ArchiveExtractor {
|
|||
return paths;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void extract(File outputDir) throws IOException {
|
||||
// e.g. 7z x -y -aos archive.7z
|
||||
execute(get7zCommand(), "x", "-y", "-aos", archive.getPath(), "-o" + outputDir.getCanonicalPath());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void extract(File outputDir, FileFilter filter) throws IOException {
|
||||
// e.g. 7z x -y -aos archive.7z file.txt image.png info.nfo
|
||||
Stream<String> command = Stream.of(get7zCommand(), "x", "-y", "-aos", archive.getPath(), "-o" + outputDir.getCanonicalPath());
|
||||
|
|
|
@ -14,15 +14,15 @@ import com.sun.jna.Platform;
|
|||
|
||||
|
||||
public class SevenZipLoader {
|
||||
|
||||
|
||||
private static boolean nativeLibrariesLoaded = false;
|
||||
|
||||
|
||||
|
||||
|
||||
private static synchronized void requireNativeLibraries() throws SevenZipNativeInitializationException {
|
||||
if (nativeLibrariesLoaded) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
// initialize 7z-JBinding native libs
|
||||
try {
|
||||
try {
|
||||
|
@ -32,7 +32,7 @@ public class SevenZipLoader {
|
|||
} catch (Throwable e) {
|
||||
Logger.getLogger(SevenZipLoader.class.getName()).warning("Failed to preload library: " + e);
|
||||
}
|
||||
|
||||
|
||||
System.loadLibrary("7-Zip-JBinding");
|
||||
SevenZip.initLoadedLibraries(); // NATIVE LIBS MUST BE LOADED WITH SYSTEM CLASSLOADER
|
||||
nativeLibrariesLoaded = true;
|
||||
|
@ -40,17 +40,17 @@ public class SevenZipLoader {
|
|||
throw new SevenZipNativeInitializationException("Failed to load 7z-JBinding: " + e.getMessage(), e);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
public static ISevenZipInArchive open(IInStream stream, IArchiveOpenCallback callback) throws Exception {
|
||||
// initialize 7-Zip-JBinding
|
||||
requireNativeLibraries();
|
||||
|
||||
|
||||
if (callback == null) {
|
||||
return SevenZip.openInArchive(null, stream);
|
||||
} else {
|
||||
return SevenZip.openInArchive(null, stream, callback);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
|
|
@ -60,6 +60,7 @@ public class SevenZipNativeBindings implements ArchiveExtractor, Closeable {
|
|||
return item;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<FileInfo> listFiles() throws SevenZipException {
|
||||
List<FileInfo> paths = new ArrayList<FileInfo>();
|
||||
|
||||
|
|
|
@ -13,66 +13,66 @@ import net.sf.sevenzipjbinding.SevenZipException;
|
|||
|
||||
|
||||
public class VolumedArchiveInStream implements IInStream {
|
||||
|
||||
|
||||
private static final String FIRST_VOLUME_POSTFIX = ".001";
|
||||
|
||||
|
||||
private long absoluteOffset;
|
||||
private long absoluteLength = -1;
|
||||
|
||||
|
||||
private int currentIndex = -1;
|
||||
private IInStream currentInStream;
|
||||
private long currentVolumeOffset;
|
||||
private long currentVolumeLength;
|
||||
private List<Long> volumePositions = new ArrayList<Long>();
|
||||
|
||||
|
||||
private final IArchiveOpenVolumeCallback archiveOpenVolumeCallback;
|
||||
private String cuttedVolumeFilename;
|
||||
|
||||
|
||||
|
||||
|
||||
public VolumedArchiveInStream(IArchiveOpenVolumeCallback archiveOpenVolumeCallback) throws SevenZipException {
|
||||
this((String) archiveOpenVolumeCallback.getProperty(PropID.NAME), archiveOpenVolumeCallback);
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
public VolumedArchiveInStream(String firstVolumeFilename, IArchiveOpenVolumeCallback archiveOpenVolumeCallback) throws SevenZipException {
|
||||
this.archiveOpenVolumeCallback = archiveOpenVolumeCallback;
|
||||
volumePositions.add(Long.valueOf(0));
|
||||
|
||||
|
||||
if (!firstVolumeFilename.endsWith(FIRST_VOLUME_POSTFIX)) {
|
||||
throw new SevenZipException("The first volume filename '" + firstVolumeFilename + "' don't ends with the postfix: '" + FIRST_VOLUME_POSTFIX + "'. Can't proceed");
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
cuttedVolumeFilename = firstVolumeFilename.substring(0, firstVolumeFilename.length() - 3);
|
||||
openVolume(1, true);
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
private void openVolume(int index, boolean seekToBegin) throws SevenZipException {
|
||||
if (currentIndex == index) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
for (int i = volumePositions.size(); i < index && absoluteLength == -1; i++) {
|
||||
openVolume(i, false);
|
||||
}
|
||||
|
||||
|
||||
if (absoluteLength != -1 && volumePositions.size() <= index) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
String volumeFilename = cuttedVolumeFilename + MessageFormat.format("{0,number,000}", Integer.valueOf(index));
|
||||
|
||||
|
||||
// Get new IInStream
|
||||
IInStream newInStream = archiveOpenVolumeCallback.getStream(volumeFilename);
|
||||
|
||||
|
||||
if (newInStream == null) {
|
||||
absoluteLength = volumePositions.get(volumePositions.size() - 1).longValue();
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
currentInStream = newInStream;
|
||||
|
||||
|
||||
if (volumePositions.size() == index) {
|
||||
// Determine volume size
|
||||
currentVolumeLength = currentInStream.seek(0, SEEK_END);
|
||||
|
@ -80,23 +80,23 @@ public class VolumedArchiveInStream implements IInStream {
|
|||
throw new RuntimeException("Volume " + index + " is empty");
|
||||
}
|
||||
volumePositions.add(Long.valueOf(volumePositions.get(index - 1).longValue() + currentVolumeLength));
|
||||
|
||||
|
||||
if (seekToBegin) {
|
||||
currentInStream.seek(0, SEEK_SET);
|
||||
}
|
||||
} else {
|
||||
currentVolumeLength = volumePositions.get(index).longValue() - volumePositions.get(index - 1).longValue();
|
||||
}
|
||||
|
||||
|
||||
if (seekToBegin) {
|
||||
currentVolumeOffset = 0;
|
||||
absoluteOffset = volumePositions.get(index - 1).longValue();
|
||||
}
|
||||
|
||||
|
||||
currentIndex = index;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
private void openVolumeToAbsoluteOffset() throws SevenZipException {
|
||||
int index = volumePositions.size() - 1;
|
||||
if (absoluteLength != -1 && absoluteOffset >= absoluteLength) {
|
||||
|
@ -105,80 +105,80 @@ public class VolumedArchiveInStream implements IInStream {
|
|||
while (volumePositions.get(index).longValue() > absoluteOffset) {
|
||||
index--;
|
||||
}
|
||||
|
||||
|
||||
if (index < volumePositions.size() - 1) {
|
||||
openVolume(index + 1, false);
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
do {
|
||||
index++;
|
||||
openVolume(index, false);
|
||||
} while ((absoluteLength == -1 || absoluteOffset < absoluteLength) && volumePositions.get(index).longValue() <= absoluteOffset);
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
@Override
|
||||
public long seek(long offset, int seekOrigin) throws SevenZipException {
|
||||
long newOffset;
|
||||
boolean proceedWithSeek = false;
|
||||
switch (seekOrigin) {
|
||||
case SEEK_SET:
|
||||
newOffset = offset;
|
||||
break;
|
||||
|
||||
case SEEK_CUR:
|
||||
newOffset = absoluteOffset + offset;
|
||||
break;
|
||||
|
||||
case SEEK_END:
|
||||
if (absoluteLength == -1) {
|
||||
openVolume(Integer.MAX_VALUE, false);
|
||||
proceedWithSeek = true;
|
||||
}
|
||||
newOffset = absoluteLength + offset;
|
||||
break;
|
||||
|
||||
default:
|
||||
throw new RuntimeException("Seek: unknown origin: " + seekOrigin);
|
||||
case SEEK_SET:
|
||||
newOffset = offset;
|
||||
break;
|
||||
|
||||
case SEEK_CUR:
|
||||
newOffset = absoluteOffset + offset;
|
||||
break;
|
||||
|
||||
case SEEK_END:
|
||||
if (absoluteLength == -1) {
|
||||
openVolume(Integer.MAX_VALUE, false);
|
||||
proceedWithSeek = true;
|
||||
}
|
||||
newOffset = absoluteLength + offset;
|
||||
break;
|
||||
|
||||
default:
|
||||
throw new RuntimeException("Seek: unknown origin: " + seekOrigin);
|
||||
}
|
||||
|
||||
|
||||
if (newOffset == absoluteOffset && !proceedWithSeek) {
|
||||
return newOffset;
|
||||
}
|
||||
absoluteOffset = newOffset;
|
||||
|
||||
|
||||
openVolumeToAbsoluteOffset();
|
||||
|
||||
|
||||
if (absoluteLength != -1 && absoluteLength <= absoluteOffset) {
|
||||
absoluteOffset = absoluteLength;
|
||||
return absoluteLength;
|
||||
}
|
||||
|
||||
|
||||
currentVolumeOffset = absoluteOffset - volumePositions.get(currentIndex - 1).longValue();
|
||||
currentInStream.seek(currentVolumeOffset, SEEK_SET);
|
||||
|
||||
|
||||
return newOffset;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
@Override
|
||||
public int read(byte[] data) throws SevenZipException {
|
||||
if (absoluteLength != -1 && absoluteOffset >= absoluteLength) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
int read = currentInStream.read(data);
|
||||
|
||||
|
||||
absoluteOffset += read;
|
||||
currentVolumeOffset += read;
|
||||
|
||||
|
||||
if (currentVolumeOffset >= currentVolumeLength) {
|
||||
openVolume(currentIndex + 1, true);
|
||||
}
|
||||
|
||||
|
||||
return read;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
|
|
@ -292,6 +292,7 @@ public class GroovyPad extends JFrame {
|
|||
|
||||
private class ConsoleOutputStream extends ByteArrayOutputStream {
|
||||
|
||||
@Override
|
||||
public void flush() {
|
||||
try {
|
||||
String message = this.toString("UTF-8");
|
||||
|
|
|
@ -57,7 +57,7 @@ public class ScriptShellMethods {
|
|||
}
|
||||
|
||||
public static List<File> listFiles(File self, Closure<?> closure) {
|
||||
return (List<File>) DefaultGroovyMethods.findAll(FileUtilities.getChildren(self), closure);
|
||||
return DefaultGroovyMethods.findAll(FileUtilities.getChildren(self), closure);
|
||||
}
|
||||
|
||||
public static boolean isVideo(File self) {
|
||||
|
@ -121,7 +121,7 @@ public class ScriptShellMethods {
|
|||
|
||||
List<File> files = FileUtilities.listFiles(roots);
|
||||
if (closure != null) {
|
||||
files = (List<File>) DefaultGroovyMethods.findAll(files, closure);
|
||||
files = DefaultGroovyMethods.findAll(files, closure);
|
||||
}
|
||||
|
||||
return FileUtilities.sortByUniquePath(files);
|
||||
|
@ -144,7 +144,7 @@ public class ScriptShellMethods {
|
|||
|
||||
List<File> folders = FileUtilities.listFolders(roots);
|
||||
if (closure != null) {
|
||||
folders = (List<File>) DefaultGroovyMethods.findAll(folders, closure);
|
||||
folders = DefaultGroovyMethods.findAll(folders, closure);
|
||||
}
|
||||
|
||||
return FileUtilities.sortByUniquePath(folders);
|
||||
|
|
|
@ -15,18 +15,18 @@ import java.util.TreeSet;
|
|||
|
||||
|
||||
public class AssociativeScriptObject extends GroovyObjectSupport implements Iterable<Entry<Object, Object>> {
|
||||
|
||||
|
||||
private final Map<Object, Object> properties;
|
||||
|
||||
|
||||
|
||||
|
||||
public AssociativeScriptObject(Map<?, ?> properties) {
|
||||
this.properties = new LenientLookup(properties);
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Get the property with the given name.
|
||||
*
|
||||
*
|
||||
* @param name
|
||||
* the property name
|
||||
* @param start
|
||||
|
@ -36,77 +36,77 @@ public class AssociativeScriptObject extends GroovyObjectSupport implements Iter
|
|||
public Object getProperty(String name) {
|
||||
return properties.get(name);
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
@Override
|
||||
public void setProperty(String name, Object value) {
|
||||
// ignore, object is immutable
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
@Override
|
||||
public Iterator<Entry<Object, Object>> iterator() {
|
||||
return properties.entrySet().iterator();
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
// all the properties in alphabetic order
|
||||
return new TreeSet<Object>(properties.keySet()).toString();
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Map allowing look-up of values by a fault-tolerant key as specified by the defining key.
|
||||
*
|
||||
*
|
||||
*/
|
||||
private static class LenientLookup extends AbstractMap<Object, Object> {
|
||||
|
||||
|
||||
private final Map<String, Entry<?, ?>> lookup = new HashMap<String, Entry<?, ?>>();
|
||||
|
||||
|
||||
|
||||
|
||||
public LenientLookup(Map<?, ?> source) {
|
||||
// populate lookup map
|
||||
for (Entry<?, ?> entry : source.entrySet()) {
|
||||
lookup.put(definingKey(entry.getKey()), entry);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
protected String definingKey(Object key) {
|
||||
// letters and digits are defining, everything else will be ignored
|
||||
return key.toString().replaceAll("[^\\p{Alnum}]", "").toLowerCase();
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
@Override
|
||||
public boolean containsKey(Object key) {
|
||||
return lookup.containsKey(definingKey(key));
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
@Override
|
||||
public Object get(Object key) {
|
||||
Entry<?, ?> entry = lookup.get(definingKey(key));
|
||||
|
||||
|
||||
if (entry != null)
|
||||
return entry.getValue();
|
||||
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
@Override
|
||||
public Set<Entry<Object, Object>> entrySet() {
|
||||
return new AbstractSet<Entry<Object, Object>>() {
|
||||
|
||||
|
||||
@Override
|
||||
public Iterator<Entry<Object, Object>> iterator() {
|
||||
return (Iterator) lookup.values().iterator();
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
@Override
|
||||
public int size() {
|
||||
return lookup.size();
|
||||
|
@ -114,5 +114,5 @@ public class AssociativeScriptObject extends GroovyObjectSupport implements Iter
|
|||
};
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
|
|
@ -12,8 +12,8 @@ import java.lang.annotation.Target;
|
|||
@Retention(RUNTIME)
|
||||
@Target(METHOD)
|
||||
public @interface Define {
|
||||
|
||||
|
||||
String[] value();
|
||||
|
||||
|
||||
static final String undefined = "";
|
||||
}
|
||||
|
|
|
@ -6,26 +6,26 @@ import javax.script.ScriptException;
|
|||
|
||||
|
||||
public class ExpressionException extends ScriptException {
|
||||
|
||||
|
||||
private final String message;
|
||||
|
||||
|
||||
|
||||
|
||||
public ExpressionException(String message, Exception cause) {
|
||||
super(cause);
|
||||
|
||||
|
||||
// can't set message via super constructor
|
||||
this.message = message;
|
||||
}
|
||||
|
||||
|
||||
|
||||
public ExpressionException(Exception e) {
|
||||
this(e.getMessage(), e);
|
||||
}
|
||||
|
||||
|
||||
|
||||
@Override
|
||||
public String getMessage() {
|
||||
return message;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
|
|
@ -32,7 +32,7 @@ public class ExpressionFormatMethods {
|
|||
|
||||
/**
|
||||
* Pad strings or numbers with given characters ('0' by default).
|
||||
*
|
||||
*
|
||||
* e.g. "1" -> "01"
|
||||
*/
|
||||
public static String pad(String self, int length, String padding) {
|
||||
|
@ -97,7 +97,7 @@ public class ExpressionFormatMethods {
|
|||
|
||||
/**
|
||||
* Replace space characters with a given characters.
|
||||
*
|
||||
*
|
||||
* e.g. "Doctor Who" -> "Doctor_Who"
|
||||
*/
|
||||
public static String space(String self, String replacement) {
|
||||
|
@ -106,7 +106,7 @@ public class ExpressionFormatMethods {
|
|||
|
||||
/**
|
||||
* Upper-case all initials.
|
||||
*
|
||||
*
|
||||
* e.g. "The Day a new Demon was born" -> "The Day A New Demon Was Born"
|
||||
*/
|
||||
public static String upperInitial(String self) {
|
||||
|
@ -131,7 +131,7 @@ public class ExpressionFormatMethods {
|
|||
|
||||
/**
|
||||
* Get acronym, i.e. first letter of each word.
|
||||
*
|
||||
*
|
||||
* e.g. "Deep Space 9" -> "DS9"
|
||||
*/
|
||||
public static String acronym(String self) {
|
||||
|
@ -148,7 +148,7 @@ public class ExpressionFormatMethods {
|
|||
|
||||
/**
|
||||
* Lower-case all letters that are not initials.
|
||||
*
|
||||
*
|
||||
* e.g. "Gundam SEED" -> "Gundam Seed"
|
||||
*/
|
||||
public static String lowerTrail(String self) {
|
||||
|
@ -220,7 +220,7 @@ public class ExpressionFormatMethods {
|
|||
|
||||
/**
|
||||
* Replace trailing parenthesis including any leading whitespace.
|
||||
*
|
||||
*
|
||||
* e.g. "The IT Crowd (UK)" -> "The IT Crowd"
|
||||
*/
|
||||
public static String replaceTrailingBrackets(String self) {
|
||||
|
@ -233,7 +233,7 @@ public class ExpressionFormatMethods {
|
|||
|
||||
/**
|
||||
* Replace 'part identifier'.
|
||||
*
|
||||
*
|
||||
* e.g. "Today Is the Day: Part 1" -> "Today Is the Day, Part 1" or "Today Is the Day (1)" -> "Today Is the Day, Part 1"
|
||||
*/
|
||||
public static String replacePart(String self) {
|
||||
|
@ -257,7 +257,7 @@ public class ExpressionFormatMethods {
|
|||
|
||||
/**
|
||||
* Apply ICU transliteration
|
||||
*
|
||||
*
|
||||
* @see http://userguide.icu-project.org/transforms/general
|
||||
*/
|
||||
public static String transliterate(String self, String transformIdentifier) {
|
||||
|
@ -266,7 +266,7 @@ public class ExpressionFormatMethods {
|
|||
|
||||
/**
|
||||
* Convert Unicode to ASCII as best as possible. Works with most alphabets/scripts used in the world.
|
||||
*
|
||||
*
|
||||
* e.g. "Österreich" -> "Osterreich" "カタカナ" -> "katakana"
|
||||
*/
|
||||
public static String ascii(String self) {
|
||||
|
@ -283,7 +283,7 @@ public class ExpressionFormatMethods {
|
|||
|
||||
/**
|
||||
* Replace multiple replacement pairs
|
||||
*
|
||||
*
|
||||
* e.g. replace('ä', 'ae', 'ö', 'oe', 'ü', 'ue')
|
||||
*/
|
||||
public static String replace(String self, String tr0, String tr1, String... tr) {
|
||||
|
|
|
@ -91,10 +91,10 @@ public class MediaBindingBean {
|
|||
return getMovie().getName();
|
||||
if (infoObject instanceof AudioTrack)
|
||||
return getAlbumArtist() != null ? getAlbumArtist() : getArtist();
|
||||
if (infoObject instanceof File)
|
||||
return FileUtilities.getName((File) infoObject);
|
||||
if (infoObject instanceof File)
|
||||
return FileUtilities.getName((File) infoObject);
|
||||
|
||||
return null;
|
||||
return null;
|
||||
}
|
||||
|
||||
@Define("y")
|
||||
|
@ -909,6 +909,7 @@ public class MediaBindingBean {
|
|||
|
||||
private AssociativeScriptObject createBindingObject(File file, Object info, Map<File, Object> context) {
|
||||
MediaBindingBean mediaBindingBean = new MediaBindingBean(info, file, context) {
|
||||
@Override
|
||||
@Define(undefined)
|
||||
public <T> T undefined(String name) {
|
||||
return null; // never throw exceptions for empty or null values
|
||||
|
|
|
@ -13,22 +13,22 @@ import java.security.PrivilegedExceptionAction;
|
|||
|
||||
|
||||
public final class PrivilegedInvocation implements InvocationHandler {
|
||||
|
||||
|
||||
private final Object object;
|
||||
private final AccessControlContext context;
|
||||
|
||||
|
||||
|
||||
private PrivilegedInvocation(Object object, AccessControlContext context) {
|
||||
this.object = object;
|
||||
this.context = context;
|
||||
}
|
||||
|
||||
|
||||
|
||||
@Override
|
||||
public Object invoke(final Object proxy, final Method method, final Object[] args) throws Throwable {
|
||||
try {
|
||||
return AccessController.doPrivileged(new PrivilegedExceptionAction<Object>() {
|
||||
|
||||
|
||||
@Override
|
||||
public Object run() throws Exception {
|
||||
return method.invoke(object, args);
|
||||
|
@ -36,23 +36,23 @@ public final class PrivilegedInvocation implements InvocationHandler {
|
|||
}, context);
|
||||
} catch (PrivilegedActionException e) {
|
||||
Throwable cause = e.getException();
|
||||
|
||||
|
||||
// the underlying method may have throw an exception
|
||||
if (cause instanceof InvocationTargetException) {
|
||||
// get actual cause
|
||||
cause = cause.getCause();
|
||||
}
|
||||
|
||||
|
||||
// forward cause
|
||||
throw cause;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
public static <I> I newProxy(Class<I> interfaceClass, I object, AccessControlContext context) {
|
||||
InvocationHandler invocationHandler = new PrivilegedInvocation(object, context);
|
||||
ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
|
||||
|
||||
|
||||
// create dynamic invocation proxy
|
||||
return interfaceClass.cast(Proxy.newProxyInstance(classLoader, new Class[] { interfaceClass }, invocationHandler));
|
||||
}
|
||||
|
|
|
@ -14,17 +14,17 @@ import java.util.TreeMap;
|
|||
* Used to create a map view of the properties of an Object
|
||||
*/
|
||||
public class PropertyBindings extends AbstractMap<String, Object> {
|
||||
|
||||
|
||||
private final Object object;
|
||||
private final Map<String, Object> properties = new TreeMap<String, Object>(String.CASE_INSENSITIVE_ORDER);
|
||||
|
||||
|
||||
private final Object defaultValue;
|
||||
|
||||
|
||||
|
||||
|
||||
public PropertyBindings(Object object, Object defaultValue) {
|
||||
this.object = object;
|
||||
this.defaultValue = defaultValue;
|
||||
|
||||
|
||||
// get method bindings
|
||||
for (Method method : object.getClass().getMethods()) {
|
||||
if (method.getReturnType() != void.class && method.getParameterTypes().length == 0 && !method.getDeclaringClass().getName().startsWith("java")) {
|
||||
|
@ -32,7 +32,7 @@ public class PropertyBindings extends AbstractMap<String, Object> {
|
|||
if (method.getName().length() > 3 && method.getName().substring(0, 3).equalsIgnoreCase("get")) {
|
||||
properties.put(method.getName().substring(3), method);
|
||||
}
|
||||
|
||||
|
||||
// boolean properties
|
||||
if (method.getName().length() > 2 && method.getName().substring(0, 3).equalsIgnoreCase("is")) {
|
||||
properties.put(method.getName().substring(2), method);
|
||||
|
@ -40,17 +40,17 @@ public class PropertyBindings extends AbstractMap<String, Object> {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
@Override
|
||||
public Object get(Object key) {
|
||||
Object value = properties.get(key);
|
||||
|
||||
|
||||
// evaluate method
|
||||
if (value instanceof Method) {
|
||||
try {
|
||||
value = ((Method) value).invoke(object);
|
||||
|
||||
|
||||
if (value == null) {
|
||||
value = defaultValue;
|
||||
}
|
||||
|
@ -58,74 +58,74 @@ public class PropertyBindings extends AbstractMap<String, Object> {
|
|||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
return value;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
@Override
|
||||
public Object put(String key, Object value) {
|
||||
return properties.put(key, value);
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
@Override
|
||||
public Object remove(Object key) {
|
||||
return properties.remove(key);
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
@Override
|
||||
public boolean containsKey(Object key) {
|
||||
return properties.containsKey(key);
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
@Override
|
||||
public Set<String> keySet() {
|
||||
return properties.keySet();
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
@Override
|
||||
public boolean isEmpty() {
|
||||
return properties.isEmpty();
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return properties.toString();
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
@Override
|
||||
public Set<Entry<String, Object>> entrySet() {
|
||||
Set<Entry<String, Object>> entrySet = new HashSet<Entry<String, Object>>();
|
||||
|
||||
|
||||
for (final String key : keySet()) {
|
||||
entrySet.add(new Entry<String, Object>() {
|
||||
|
||||
|
||||
@Override
|
||||
public String getKey() {
|
||||
return key;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
@Override
|
||||
public Object getValue() {
|
||||
return get(key);
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
@Override
|
||||
public Object setValue(Object value) {
|
||||
return put(key, value);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
return entrySet;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
|
|
@ -7,22 +7,22 @@ import com.sun.jna.Pointer;
|
|||
|
||||
|
||||
interface GIOLibrary extends Library {
|
||||
|
||||
|
||||
void g_type_init();
|
||||
|
||||
|
||||
|
||||
|
||||
Pointer g_vfs_get_default();
|
||||
|
||||
|
||||
|
||||
|
||||
Pointer g_vfs_get_file_for_uri(Pointer gvfs, String uri);
|
||||
|
||||
|
||||
|
||||
|
||||
Pointer g_file_get_path(Pointer gfile);
|
||||
|
||||
|
||||
|
||||
|
||||
void g_free(Pointer gpointer);
|
||||
|
||||
|
||||
|
||||
|
||||
void g_object_unref(Pointer gobject);
|
||||
|
||||
|
||||
}
|
||||
|
|
|
@ -10,31 +10,31 @@ import com.sun.jna.Pointer;
|
|||
|
||||
|
||||
public class GVFS {
|
||||
|
||||
|
||||
private static GIOLibrary lib;
|
||||
private static Pointer gvfs;
|
||||
|
||||
|
||||
|
||||
|
||||
private synchronized static GIOLibrary getLibrary() {
|
||||
if (lib == null) {
|
||||
lib = (GIOLibrary) Native.loadLibrary("gio-2.0", GIOLibrary.class);
|
||||
}
|
||||
return lib;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
public synchronized static Pointer getDefaultVFS() {
|
||||
if (gvfs == null) {
|
||||
gvfs = getLibrary().g_vfs_get_default();
|
||||
}
|
||||
return gvfs;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
public static File getPathForURI(URI uri) {
|
||||
Pointer gfile = getLibrary().g_vfs_get_file_for_uri(getDefaultVFS(), uri.toString());
|
||||
Pointer chars = getLibrary().g_file_get_path(gfile);
|
||||
|
||||
|
||||
try {
|
||||
if (chars != null)
|
||||
return new File(chars.getString(0));
|
||||
|
@ -45,5 +45,5 @@ public class GVFS {
|
|||
getLibrary().g_free(chars);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
|
|
@ -6,24 +6,24 @@ import java.util.zip.Checksum;
|
|||
|
||||
|
||||
public class ChecksumHash implements Hash {
|
||||
|
||||
|
||||
private final Checksum checksum;
|
||||
|
||||
|
||||
|
||||
|
||||
public ChecksumHash(Checksum checksum) {
|
||||
this.checksum = checksum;
|
||||
}
|
||||
|
||||
|
||||
|
||||
@Override
|
||||
public void update(byte[] bytes, int off, int len) {
|
||||
checksum.update(bytes, off, len);
|
||||
}
|
||||
|
||||
|
||||
|
||||
@Override
|
||||
public String digest() {
|
||||
return String.format("%08X", checksum.getValue());
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
|
|
@ -9,10 +9,10 @@ import jonelo.jacksum.algorithm.Edonkey;
|
|||
|
||||
|
||||
public class Ed2kHash implements Hash {
|
||||
|
||||
|
||||
private final Edonkey ed2k;
|
||||
|
||||
|
||||
|
||||
|
||||
public Ed2kHash() {
|
||||
try {
|
||||
this.ed2k = new Edonkey();
|
||||
|
@ -20,17 +20,17 @@ public class Ed2kHash implements Hash {
|
|||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
@Override
|
||||
public void update(byte[] bytes, int off, int len) {
|
||||
ed2k.update(bytes, off, len);
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
@Override
|
||||
public String digest() {
|
||||
return String.format("%0" + (ed2k.getByteArray().length * 2) + "x", new BigInteger(1, ed2k.getByteArray()));
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
|
|
@ -3,10 +3,10 @@ package net.filebot.hash;
|
|||
|
||||
|
||||
public interface Hash {
|
||||
|
||||
|
||||
public void update(byte[] bytes, int off, int len);
|
||||
|
||||
|
||||
|
||||
public String digest();
|
||||
|
||||
|
||||
}
|
||||
|
|
|
@ -8,10 +8,10 @@ import java.security.NoSuchAlgorithmException;
|
|||
|
||||
|
||||
public class MessageDigestHash implements Hash {
|
||||
|
||||
|
||||
private final MessageDigest md;
|
||||
|
||||
|
||||
|
||||
|
||||
public MessageDigestHash(String algorithm) {
|
||||
try {
|
||||
this.md = MessageDigest.getInstance(algorithm);
|
||||
|
@ -19,23 +19,23 @@ public class MessageDigestHash implements Hash {
|
|||
throw new IllegalArgumentException(e);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
public MessageDigestHash(MessageDigest md) {
|
||||
this.md = md;
|
||||
}
|
||||
|
||||
|
||||
|
||||
@Override
|
||||
public void update(byte[] bytes, int off, int len) {
|
||||
md.update(bytes, off, len);
|
||||
}
|
||||
|
||||
|
||||
|
||||
@Override
|
||||
public String digest() {
|
||||
// e.g. %032x (format for MD-5)
|
||||
return String.format("%0" + (md.getDigestLength() * 2) + "x", new BigInteger(1, md.digest()));
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
|
|
@ -10,17 +10,17 @@ import java.util.regex.Pattern;
|
|||
|
||||
|
||||
public class SfvFormat extends VerificationFormat {
|
||||
|
||||
|
||||
@Override
|
||||
public String format(String path, String hash) {
|
||||
// e.g folder/file.txt 970E4EF1
|
||||
return String.format("%s %s", path, hash);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Pattern used to parse the lines of a sfv file.
|
||||
*
|
||||
*
|
||||
* <pre>
|
||||
* Sample:
|
||||
* folder/file.txt 970E4EF1
|
||||
|
@ -28,17 +28,17 @@ public class SfvFormat extends VerificationFormat {
|
|||
* </pre>
|
||||
*/
|
||||
private final Pattern pattern = Pattern.compile("^(.+)\\s+(\\p{XDigit}{8})$");
|
||||
|
||||
|
||||
|
||||
@Override
|
||||
public Entry<File, String> parseObject(String line) throws ParseException {
|
||||
Matcher matcher = pattern.matcher(line);
|
||||
|
||||
|
||||
if (!matcher.matches()) {
|
||||
throw new ParseException("Illegal input pattern", 0);
|
||||
}
|
||||
|
||||
|
||||
return entry(matcher.group(1), matcher.group(2));
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
|
|
@ -15,21 +15,21 @@ import java.util.logging.Logger;
|
|||
|
||||
|
||||
public class VerificationFileReader implements Iterator<Entry<File, String>>, Closeable {
|
||||
|
||||
|
||||
private final Scanner scanner;
|
||||
|
||||
|
||||
private final VerificationFormat format;
|
||||
|
||||
|
||||
private Entry<File, String> buffer;
|
||||
|
||||
|
||||
private int lineNumber = 0;
|
||||
|
||||
|
||||
|
||||
public VerificationFileReader(Readable source, VerificationFormat format) {
|
||||
this.scanner = new Scanner(source);
|
||||
this.format = format;
|
||||
}
|
||||
|
||||
|
||||
|
||||
@Override
|
||||
public boolean hasNext() {
|
||||
|
@ -37,10 +37,10 @@ public class VerificationFileReader implements Iterator<Entry<File, String>>, Cl
|
|||
// cache next entry
|
||||
buffer = nextEntry();
|
||||
}
|
||||
|
||||
|
||||
return buffer != null;
|
||||
}
|
||||
|
||||
|
||||
|
||||
@Override
|
||||
public Entry<File, String> next() {
|
||||
|
@ -48,7 +48,7 @@ public class VerificationFileReader implements Iterator<Entry<File, String>>, Cl
|
|||
if (!hasNext()) {
|
||||
throw new NoSuchElementException();
|
||||
}
|
||||
|
||||
|
||||
try {
|
||||
return buffer;
|
||||
} finally {
|
||||
|
@ -56,15 +56,15 @@ public class VerificationFileReader implements Iterator<Entry<File, String>>, Cl
|
|||
buffer = null;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
protected Entry<File, String> nextEntry() {
|
||||
Entry<File, String> entry = null;
|
||||
|
||||
|
||||
// get next valid entry
|
||||
while (entry == null && scanner.hasNextLine()) {
|
||||
String line = scanner.nextLine().trim();
|
||||
|
||||
|
||||
// ignore comments
|
||||
if (!isComment(line)) {
|
||||
try {
|
||||
|
@ -74,33 +74,33 @@ public class VerificationFileReader implements Iterator<Entry<File, String>>, Cl
|
|||
Logger.getLogger(getClass().getName()).log(Level.WARNING, String.format("Illegal format on line %d: %s", lineNumber, line));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
lineNumber++;
|
||||
}
|
||||
|
||||
|
||||
return entry;
|
||||
}
|
||||
|
||||
|
||||
|
||||
public int getLineNumber() {
|
||||
return lineNumber;
|
||||
}
|
||||
|
||||
|
||||
|
||||
protected boolean isComment(String line) {
|
||||
return line.isEmpty() || line.startsWith(";");
|
||||
}
|
||||
|
||||
|
||||
|
||||
@Override
|
||||
public void close() throws IOException {
|
||||
scanner.close();
|
||||
}
|
||||
|
||||
|
||||
|
||||
@Override
|
||||
public void remove() {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
|
|
@ -12,40 +12,40 @@ import net.filebot.Settings;
|
|||
|
||||
|
||||
public class VerificationFileWriter implements Closeable {
|
||||
|
||||
|
||||
protected PrintWriter out;
|
||||
protected VerificationFormat format;
|
||||
|
||||
|
||||
|
||||
public VerificationFileWriter(File file, VerificationFormat format, String charset) throws IOException {
|
||||
this(new PrintWriter(file, charset), format, charset);
|
||||
}
|
||||
|
||||
|
||||
|
||||
public VerificationFileWriter(PrintWriter out, VerificationFormat format, String charset) {
|
||||
this.out = out;
|
||||
this.format = format;
|
||||
|
||||
|
||||
// start by printing the file header
|
||||
writeHeader(charset);
|
||||
}
|
||||
|
||||
|
||||
|
||||
protected void writeHeader(String charset) {
|
||||
out.format("; Generated by %s %s on %tF at %<tT%n", Settings.getApplicationName(), Settings.getApplicationVersion(), new Date());
|
||||
out.format("; charset=%s%n", charset);
|
||||
out.format(";%n");
|
||||
}
|
||||
|
||||
|
||||
|
||||
public void write(String path, String hash) {
|
||||
out.format("%s%n", format.format(path, hash));
|
||||
}
|
||||
|
||||
|
||||
|
||||
@Override
|
||||
public void close() throws IOException {
|
||||
out.close();
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
|
|
@ -14,74 +14,74 @@ import java.util.regex.Pattern;
|
|||
|
||||
|
||||
public class VerificationFormat extends Format {
|
||||
|
||||
|
||||
private final String hashTypeHint;
|
||||
|
||||
|
||||
|
||||
public VerificationFormat() {
|
||||
this.hashTypeHint = "";
|
||||
}
|
||||
|
||||
|
||||
|
||||
public VerificationFormat(String hashTypeHint) {
|
||||
this.hashTypeHint = hashTypeHint.isEmpty() ? "" : '?' + hashTypeHint.toUpperCase();
|
||||
}
|
||||
|
||||
|
||||
|
||||
@Override
|
||||
public StringBuffer format(Object obj, StringBuffer sb, FieldPosition pos) {
|
||||
@SuppressWarnings("unchecked")
|
||||
Entry<File, String> entry = (Entry<File, String>) obj;
|
||||
|
||||
|
||||
String path = entry.getKey().getPath();
|
||||
String hash = entry.getValue();
|
||||
|
||||
|
||||
return sb.append(format(path, hash));
|
||||
}
|
||||
|
||||
|
||||
|
||||
public String format(String path, String hash) {
|
||||
// e.g. 1a02a7c1e9ac91346d08829d5037b240f42ded07 ?SHA1*folder/file.txt
|
||||
return String.format("%s %s*%s", hash, hashTypeHint, path);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Pattern used to parse the lines of a md5 or sha1 file.
|
||||
*
|
||||
*
|
||||
* <pre>
|
||||
* Sample MD5:
|
||||
* 50e85fe18e17e3616774637a82968f4c *folder/file.txt
|
||||
* | Group 1 | Group 2 |
|
||||
*
|
||||
*
|
||||
* Sample SHA-1:
|
||||
* 1a02a7c1e9ac91346d08829d5037b240f42ded07 ?SHA1*folder/file.txt
|
||||
* | Group 1 | | Group 2 |
|
||||
* </pre>
|
||||
*/
|
||||
private final Pattern pattern = Pattern.compile("^(\\p{XDigit}+)\\s+(?:\\?\\w+)?\\*?(.+)$");
|
||||
|
||||
|
||||
|
||||
@Override
|
||||
public Entry<File, String> parseObject(String line) throws ParseException {
|
||||
Matcher matcher = pattern.matcher(line);
|
||||
|
||||
|
||||
if (!matcher.find()) {
|
||||
throw new ParseException("Illegal input pattern", 0);
|
||||
}
|
||||
|
||||
|
||||
return entry(matcher.group(2), matcher.group(1));
|
||||
}
|
||||
|
||||
|
||||
|
||||
@Override
|
||||
public Entry<File, String> parseObject(String line, ParsePosition pos) {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
|
||||
|
||||
protected Entry<File, String> entry(String path, String hash) {
|
||||
return new SimpleImmutableEntry<File, String>(new File(path), hash);
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
|
|
@ -284,6 +284,7 @@ public class DropToUnlock extends JList<File> {
|
|||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handleTransferable(Transferable tr, TransferAction action) throws Exception {
|
||||
List<File> files = FileTransferable.getFilesFromTransferable(tr);
|
||||
if (files != null) {
|
||||
|
@ -318,6 +319,7 @@ public class DropToUnlock extends JList<File> {
|
|||
|
||||
protected static class FileChooserAction extends MouseAdapter {
|
||||
|
||||
@Override
|
||||
public void mouseClicked(MouseEvent evt) {
|
||||
DropToUnlock list = (DropToUnlock) evt.getSource();
|
||||
if (evt.getClickCount() > 0) {
|
||||
|
|
|
@ -1,10 +1,10 @@
|
|||
/* Copyright (c) 2014 Reinhard Pointner, All Rights Reserved
|
||||
*
|
||||
*
|
||||
* This library is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Lesser General Public
|
||||
* License as published by the Free Software Foundation; either
|
||||
* version 2.1 of the License, or (at your option) any later version.
|
||||
*
|
||||
*
|
||||
* This library is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
|
@ -18,7 +18,7 @@ import com.sun.jna.Pointer;
|
|||
|
||||
/**
|
||||
* JNA wrapper for <sys/xattr.h>
|
||||
*
|
||||
*
|
||||
*/
|
||||
interface XAttr extends Library {
|
||||
|
||||
|
|
|
@ -33,6 +33,7 @@ public class SmartSeasonEpisodeMatcher extends SeasonEpisodeMatcher {
|
|||
return super.match(new File(clean(file.getPath())));
|
||||
}
|
||||
|
||||
@Override
|
||||
public String head(String name) {
|
||||
return super.head(clean(name));
|
||||
}
|
||||
|
|
|
@ -24,14 +24,14 @@ interface MediaInfoLibrary extends Library {
|
|||
|
||||
/**
|
||||
* Create a new handle.
|
||||
*
|
||||
*
|
||||
* @return handle
|
||||
*/
|
||||
Pointer New();
|
||||
|
||||
/**
|
||||
* Open a file and collect information about it (technical information and tags).
|
||||
*
|
||||
*
|
||||
* @param handle
|
||||
* @param file
|
||||
* full name of the file to open
|
||||
|
@ -41,7 +41,7 @@ interface MediaInfoLibrary extends Library {
|
|||
|
||||
/**
|
||||
* Configure or get information about MediaInfo.
|
||||
*
|
||||
*
|
||||
* @param handle
|
||||
* @param option
|
||||
* The name of option
|
||||
|
@ -53,7 +53,7 @@ interface MediaInfoLibrary extends Library {
|
|||
|
||||
/**
|
||||
* Get all details about a file.
|
||||
*
|
||||
*
|
||||
* @param handle
|
||||
* @return All details about a file in one string
|
||||
*/
|
||||
|
@ -61,7 +61,7 @@ interface MediaInfoLibrary extends Library {
|
|||
|
||||
/**
|
||||
* Get a piece of information about a file (parameter is a string).
|
||||
*
|
||||
*
|
||||
* @param handle
|
||||
* @param streamKind
|
||||
* Kind of stream (general, video, audio...)
|
||||
|
@ -79,7 +79,7 @@ interface MediaInfoLibrary extends Library {
|
|||
|
||||
/**
|
||||
* Get a piece of information about a file (parameter is an integer).
|
||||
*
|
||||
*
|
||||
* @param handle
|
||||
* @param streamKind
|
||||
* Kind of stream (general, video, audio...)
|
||||
|
@ -95,7 +95,7 @@ interface MediaInfoLibrary extends Library {
|
|||
|
||||
/**
|
||||
* Count of streams of a stream kind (StreamNumber not filled), or count of piece of information in this stream.
|
||||
*
|
||||
*
|
||||
* @param handle
|
||||
* @param streamKind
|
||||
* Kind of stream (general, video, audio...)
|
||||
|
@ -107,14 +107,14 @@ interface MediaInfoLibrary extends Library {
|
|||
|
||||
/**
|
||||
* Close a file opened before with Open().
|
||||
*
|
||||
*
|
||||
* @param handle
|
||||
*/
|
||||
void Close(Pointer handle);
|
||||
|
||||
/**
|
||||
* Dispose of a handle created with New().
|
||||
*
|
||||
*
|
||||
* @param handle
|
||||
*/
|
||||
void Delete(Pointer handle);
|
||||
|
|
|
@ -10,37 +10,37 @@ import net.filebot.format.PropertyBindings;
|
|||
|
||||
|
||||
public class CrossPropertyMetric implements SimilarityMetric {
|
||||
|
||||
|
||||
private SimilarityMetric metric;
|
||||
|
||||
|
||||
|
||||
|
||||
public CrossPropertyMetric(SimilarityMetric metric) {
|
||||
this.metric = metric;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
public CrossPropertyMetric() {
|
||||
this.metric = new StringEqualsMetric();
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
@Override
|
||||
public float getSimilarity(Object o1, Object o2) {
|
||||
Map<String, Object> m1 = getProperties(o1);
|
||||
if (m1.isEmpty())
|
||||
return 0;
|
||||
|
||||
|
||||
Map<String, Object> m2 = getProperties(o2);
|
||||
if (m2.isEmpty())
|
||||
return 0;
|
||||
|
||||
|
||||
// works with commons keys
|
||||
Set<String> keys = new TreeSet<String>(String.CASE_INSENSITIVE_ORDER);
|
||||
keys.addAll(m1.keySet());
|
||||
keys.retainAll(m2.keySet());
|
||||
if (keys.isEmpty())
|
||||
return 0;
|
||||
|
||||
|
||||
float feedback = 0;
|
||||
for (String k : keys) {
|
||||
try {
|
||||
|
@ -49,13 +49,13 @@ public class CrossPropertyMetric implements SimilarityMetric {
|
|||
// ignore
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
return feedback / keys.size();
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
protected Map<String, Object> getProperties(Object object) {
|
||||
return new PropertyBindings(object, null);
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
|
|
@ -8,28 +8,28 @@ import java.io.File;
|
|||
|
||||
|
||||
public class FileNameMetric implements SimilarityMetric {
|
||||
|
||||
|
||||
@Override
|
||||
public float getSimilarity(Object o1, Object o2) {
|
||||
String s1 = getFileName(o1);
|
||||
if (s1 == null || s1.isEmpty())
|
||||
return 0;
|
||||
|
||||
|
||||
String s2 = getFileName(o2);
|
||||
if (s2 == null || s2.isEmpty())
|
||||
return 0;
|
||||
|
||||
|
||||
return s1.startsWith(s2) || s2.startsWith(s1) ? 1 : 0;
|
||||
}
|
||||
|
||||
|
||||
|
||||
protected String getFileName(Object object) {
|
||||
if (object instanceof File) {
|
||||
// name without extension normalized to lower-case
|
||||
return getName((File) object).trim().toLowerCase();
|
||||
}
|
||||
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
|
|
@ -6,28 +6,28 @@ import java.io.File;
|
|||
|
||||
|
||||
public class FileSizeMetric implements SimilarityMetric {
|
||||
|
||||
|
||||
@Override
|
||||
public float getSimilarity(Object o1, Object o2) {
|
||||
long l1 = getLength(o1);
|
||||
if (l1 < 0)
|
||||
return 0;
|
||||
|
||||
|
||||
long l2 = getLength(o2);
|
||||
if (l2 < 0)
|
||||
return 0;
|
||||
|
||||
|
||||
// objects have the same non-negative length
|
||||
return l1 == l2 ? 1 : -1;
|
||||
}
|
||||
|
||||
|
||||
|
||||
protected long getLength(Object object) {
|
||||
if (object instanceof File) {
|
||||
return ((File) object).length();
|
||||
}
|
||||
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
|
|
@ -6,26 +6,26 @@ import java.util.Arrays;
|
|||
|
||||
|
||||
public class Match<Value, Candidate> {
|
||||
|
||||
|
||||
private final Value value;
|
||||
private final Candidate candidate;
|
||||
|
||||
|
||||
|
||||
public Match(Value value, Candidate candidate) {
|
||||
this.value = value;
|
||||
this.candidate = candidate;
|
||||
}
|
||||
|
||||
|
||||
|
||||
public Value getValue() {
|
||||
return value;
|
||||
}
|
||||
|
||||
|
||||
|
||||
public Candidate getCandidate() {
|
||||
return candidate;
|
||||
}
|
||||
|
||||
|
||||
|
||||
@Override
|
||||
public boolean equals(Object obj) {
|
||||
|
@ -33,20 +33,20 @@ public class Match<Value, Candidate> {
|
|||
Match<?, ?> other = (Match<?, ?>) obj;
|
||||
return value == other.value && candidate == other.candidate;
|
||||
}
|
||||
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return Arrays.hashCode(new Object[] { value, candidate });
|
||||
}
|
||||
|
||||
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return String.format("[%s, %s]", value, candidate);
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
|
|
@ -3,20 +3,20 @@ package net.filebot.similarity;
|
|||
|
||||
|
||||
public class MetricMin implements SimilarityMetric {
|
||||
|
||||
|
||||
private final SimilarityMetric metric;
|
||||
private final float minValue;
|
||||
|
||||
|
||||
|
||||
public MetricMin(SimilarityMetric metric, float minValue) {
|
||||
this.metric = metric;
|
||||
this.minValue = minValue;
|
||||
}
|
||||
|
||||
|
||||
|
||||
@Override
|
||||
public float getSimilarity(Object o1, Object o2) {
|
||||
return Math.max(metric.getSimilarity(o1, o2), minValue);
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
|
|
@ -11,43 +11,43 @@ import com.ibm.icu.text.Transliterator;
|
|||
|
||||
|
||||
public class NameSimilarityMetric implements SimilarityMetric {
|
||||
|
||||
|
||||
private final AbstractStringMetric metric;
|
||||
private final Transliterator transliterator;
|
||||
|
||||
|
||||
|
||||
|
||||
public NameSimilarityMetric() {
|
||||
// QGramsDistance with a QGram tokenizer seems to work best for similarity of names
|
||||
this(new QGramsDistance(new TokeniserQGram3()), Transliterator.getInstance("Any-Latin;Latin-ASCII;[:Diacritic:]remove"));
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
public NameSimilarityMetric(AbstractStringMetric metric, Transliterator transliterator) {
|
||||
this.metric = metric;
|
||||
this.transliterator = transliterator;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
@Override
|
||||
public float getSimilarity(Object o1, Object o2) {
|
||||
return metric.getSimilarity(normalize(o1), normalize(o2));
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
protected String normalize(Object object) {
|
||||
// use string representation
|
||||
String name = object.toString();
|
||||
|
||||
|
||||
// apply transliterator
|
||||
if (transliterator != null) {
|
||||
name = transliterator.transform(name);
|
||||
}
|
||||
|
||||
|
||||
// normalize separators
|
||||
name = normalizePunctuation(name);
|
||||
|
||||
|
||||
// normalize case and trim
|
||||
return name.toLowerCase();
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
|
|
@ -15,81 +15,81 @@ import uk.ac.shef.wit.simmetrics.wordhandlers.InterfaceTermHandler;
|
|||
|
||||
|
||||
public class NumericSimilarityMetric implements SimilarityMetric {
|
||||
|
||||
|
||||
private final AbstractStringMetric metric;
|
||||
|
||||
|
||||
|
||||
|
||||
public NumericSimilarityMetric() {
|
||||
// I don't exactly know why, but I get a good matching behavior
|
||||
// I don't exactly know why, but I get a good matching behavior
|
||||
// when using QGramsDistance or BlockDistance
|
||||
metric = new QGramsDistance(new NumberTokeniser());
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
@Override
|
||||
public float getSimilarity(Object o1, Object o2) {
|
||||
return metric.getSimilarity(normalize(o1), normalize(o2));
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
protected String normalize(Object object) {
|
||||
// no need to do anything special here, because we don't care about anything but number patterns anyway
|
||||
return object.toString();
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
private static class NumberTokeniser implements InterfaceTokeniser {
|
||||
|
||||
|
||||
private final String delimiter = "\\D+";
|
||||
|
||||
|
||||
|
||||
|
||||
@Override
|
||||
public ArrayList<String> tokenizeToArrayList(String input) {
|
||||
ArrayList<String> tokens = new ArrayList<String>();
|
||||
|
||||
|
||||
// scan for number patterns, use non-number pattern as delimiter
|
||||
Scanner scanner = new Scanner(input).useDelimiter(delimiter);
|
||||
|
||||
|
||||
while (scanner.hasNextInt()) {
|
||||
// remove leading zeros from number tokens by scanning for Integers
|
||||
tokens.add(String.valueOf(scanner.nextInt()));
|
||||
}
|
||||
|
||||
|
||||
return tokens;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
@Override
|
||||
public Set<String> tokenizeToSet(String input) {
|
||||
return new LinkedHashSet<String>(tokenizeToArrayList(input));
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
@Override
|
||||
public String getShortDescriptionString() {
|
||||
return getClass().getSimpleName();
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
@Override
|
||||
public String getDelimiters() {
|
||||
return delimiter;
|
||||
}
|
||||
|
||||
|
||||
private InterfaceTermHandler stopWordHandler = new DummyStopTermHandler();
|
||||
|
||||
|
||||
|
||||
|
||||
@Override
|
||||
public InterfaceTermHandler getStopWordHandler() {
|
||||
return stopWordHandler;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
@Override
|
||||
public void setStopWordHandler(InterfaceTermHandler stopWordHandler) {
|
||||
this.stopWordHandler = stopWordHandler;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
|
|
@ -154,7 +154,7 @@ public class SeasonEpisodeMatcher {
|
|||
|
||||
/**
|
||||
* Try to get season and episode numbers for the given string.
|
||||
*
|
||||
*
|
||||
* @param name
|
||||
* match this string against the a set of know patterns
|
||||
* @return the matches returned by the first pattern that returns any matches for this string, or null if no pattern returned any matches
|
||||
|
|
|
@ -110,7 +110,7 @@ public class SeriesNameMatcher {
|
|||
|
||||
/**
|
||||
* Try to match and verify all series names using known season episode patterns.
|
||||
*
|
||||
*
|
||||
* @param names
|
||||
* episode names
|
||||
* @return series names that have been matched one or multiple times depending on the threshold
|
||||
|
@ -154,7 +154,7 @@ public class SeriesNameMatcher {
|
|||
|
||||
/**
|
||||
* Try to match all common word sequences in the given list.
|
||||
*
|
||||
*
|
||||
* @param names
|
||||
* list of episode names
|
||||
* @return all common word sequences that have been found
|
||||
|
@ -184,7 +184,7 @@ public class SeriesNameMatcher {
|
|||
|
||||
/**
|
||||
* Try to match a series name from the given episode name using known season episode patterns.
|
||||
*
|
||||
*
|
||||
* @param name
|
||||
* episode name
|
||||
* @return a substring of the given name that ends before the first occurrence of a season episode pattern, or null if there is no such pattern
|
||||
|
@ -218,7 +218,7 @@ public class SeriesNameMatcher {
|
|||
|
||||
/**
|
||||
* Try to match a series name from the first common word sequence.
|
||||
*
|
||||
*
|
||||
* @param names
|
||||
* various episode names (at least two)
|
||||
* @return a word sequence all episode names have in common, or null
|
||||
|
|
|
@ -6,34 +6,34 @@ import java.util.Comparator;
|
|||
|
||||
|
||||
public class SimilarityComparator implements Comparator<Object> {
|
||||
|
||||
|
||||
protected SimilarityMetric metric;
|
||||
protected Object[] paragon;
|
||||
|
||||
|
||||
|
||||
|
||||
public SimilarityComparator(SimilarityMetric metric, Object[] paragon) {
|
||||
this.metric = metric;
|
||||
this.paragon = paragon;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
public SimilarityComparator(Object... paragon) {
|
||||
this(new NameSimilarityMetric(), paragon);
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
@Override
|
||||
public int compare(Object o1, Object o2) {
|
||||
float f1 = getMaxSimilarity(o1);
|
||||
float f2 = getMaxSimilarity(o2);
|
||||
|
||||
|
||||
if (f1 == f2)
|
||||
return 0;
|
||||
|
||||
|
||||
return f1 > f2 ? -1 : 1;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
public float getMaxSimilarity(Object obj) {
|
||||
float m = 0;
|
||||
for (Object it : paragon) {
|
||||
|
|
|
@ -3,7 +3,7 @@ package net.filebot.similarity;
|
|||
|
||||
|
||||
public interface SimilarityMetric {
|
||||
|
||||
|
||||
public float getSimilarity(Object o1, Object o2);
|
||||
|
||||
|
||||
}
|
||||
|
|
|
@ -10,22 +10,22 @@ import java.nio.file.attribute.BasicFileAttributes;
|
|||
|
||||
|
||||
public class TimeStampMetric implements SimilarityMetric {
|
||||
|
||||
|
||||
@Override
|
||||
public float getSimilarity(Object o1, Object o2) {
|
||||
long t1 = getTimeStamp(o1);
|
||||
long t2 = getTimeStamp(o2);
|
||||
|
||||
|
||||
if (t1 <= 0 || t2 <= 0)
|
||||
return -1;
|
||||
|
||||
|
||||
float min = min(t1, t2);
|
||||
float max = max(t1, t2);
|
||||
|
||||
|
||||
return min / max;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
public long getTimeStamp(Object obj) {
|
||||
if (obj instanceof File) {
|
||||
try {
|
||||
|
@ -43,7 +43,7 @@ public class TimeStampMetric implements SimilarityMetric {
|
|||
} else if (obj instanceof Number) {
|
||||
return ((Number) obj).longValue();
|
||||
}
|
||||
|
||||
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -9,61 +9,61 @@ import java.util.List;
|
|||
|
||||
|
||||
public class MicroDVDReader extends SubtitleReader {
|
||||
|
||||
|
||||
private double fps = 23.976;
|
||||
|
||||
|
||||
|
||||
public MicroDVDReader(Readable source) {
|
||||
super(source);
|
||||
}
|
||||
|
||||
|
||||
|
||||
@Override
|
||||
public SubtitleElement readNext() throws Exception {
|
||||
String line = scanner.nextLine();
|
||||
|
||||
|
||||
List<String> properties = new ArrayList<String>(2);
|
||||
int from = 0;
|
||||
|
||||
|
||||
while (from < line.length() && line.charAt(from) == '{') {
|
||||
int to = line.indexOf('}', from + 1);
|
||||
|
||||
|
||||
// no more properties
|
||||
if (to < from)
|
||||
break;
|
||||
|
||||
|
||||
// extract property
|
||||
properties.add(line.substring(from + 1, to));
|
||||
|
||||
|
||||
// skip property
|
||||
from = to + 1;
|
||||
}
|
||||
|
||||
|
||||
if (properties.size() < 2) {
|
||||
// ignore illegal lines
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
int startFrame = Integer.parseInt(properties.get(0));
|
||||
int endFrame = Integer.parseInt(properties.get(1));
|
||||
String text = line.substring(from).trim();
|
||||
|
||||
|
||||
// cancel format markers
|
||||
text = text.replaceAll("\\{[^\\}]*\\}", "");
|
||||
|
||||
|
||||
if (startFrame == 1 && endFrame == 1) {
|
||||
// override fps
|
||||
fps = Double.parseDouble(text);
|
||||
|
||||
|
||||
// ignore line
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
// translate '|' to new lines
|
||||
String[] lines = text.split("[|]");
|
||||
|
||||
// convert frame interval to time interval
|
||||
|
||||
// convert frame interval to time interval
|
||||
return new SubtitleElement(Math.round(startFrame * fps), Math.round(endFrame * fps), join(lines, "\n"));
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
|
|
@ -12,21 +12,21 @@ import java.util.TimeZone;
|
|||
|
||||
|
||||
public class SubRipWriter implements Closeable {
|
||||
|
||||
|
||||
private final DateFormat timeFormat;
|
||||
private final Formatter out;
|
||||
|
||||
|
||||
private int lineNumber = 0;
|
||||
|
||||
|
||||
|
||||
public SubRipWriter(Appendable out) {
|
||||
this.out = new Formatter(out, Locale.ROOT);
|
||||
|
||||
|
||||
// format used to create time stamps (e.g. 00:02:26,407 --> 00:02:31,356)
|
||||
timeFormat = new SimpleDateFormat("HH:mm:ss,SSS", Locale.ROOT);
|
||||
timeFormat.setTimeZone(TimeZone.getTimeZone("UTC"));
|
||||
}
|
||||
|
||||
|
||||
|
||||
public void write(SubtitleElement element) {
|
||||
// write a single subtitle in SubRip format, e.g.
|
||||
|
@ -37,11 +37,11 @@ public class SubRipWriter implements Closeable {
|
|||
out.format("%s --> %s%n", timeFormat.format(element.getStart()), timeFormat.format(element.getEnd()));
|
||||
out.format("%s%n%n", element.getText());
|
||||
}
|
||||
|
||||
|
||||
|
||||
@Override
|
||||
public void close() throws IOException {
|
||||
out.close();
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
|
|
@ -10,87 +10,87 @@ import java.util.regex.Pattern;
|
|||
|
||||
|
||||
public class SubStationAlphaReader extends SubtitleReader {
|
||||
|
||||
|
||||
private final DateFormat timeFormat = new SubtitleTimeFormat();
|
||||
private final Pattern newline = Pattern.compile(Pattern.quote("\\n"), Pattern.CASE_INSENSITIVE);
|
||||
private final Pattern tag = Pattern.compile("[{]\\\\[^}]+[}]");
|
||||
|
||||
|
||||
private String[] format;
|
||||
private int formatIndexStart;
|
||||
private int formatIndexEnd;
|
||||
private int formatIndexText;
|
||||
|
||||
|
||||
|
||||
public SubStationAlphaReader(Readable source) {
|
||||
super(source);
|
||||
}
|
||||
|
||||
|
||||
|
||||
private void readFormat() throws Exception {
|
||||
// read format line (e.g. Format: Marked, Start, End, Style, Name, MarginL, MarginR, MarginV, Effect, Text)
|
||||
String[] event = scanner.nextLine().split(":", 2);
|
||||
|
||||
|
||||
// sanity check
|
||||
if (!event[0].equals("Format"))
|
||||
throw new InputMismatchException("Illegal format header: " + Arrays.toString(event));
|
||||
|
||||
|
||||
// read columns
|
||||
format = event[1].split(",");
|
||||
|
||||
|
||||
// normalize column names
|
||||
for (int i = 0; i < format.length; i++) {
|
||||
format[i] = format[i].trim().toLowerCase();
|
||||
}
|
||||
|
||||
|
||||
List<String> lookup = Arrays.asList(format);
|
||||
formatIndexStart = lookup.indexOf("start");
|
||||
formatIndexEnd = lookup.indexOf("end");
|
||||
formatIndexText = lookup.indexOf("text");
|
||||
}
|
||||
|
||||
|
||||
|
||||
@Override
|
||||
public SubtitleElement readNext() throws Exception {
|
||||
if (format == null) {
|
||||
// move to [Events] sections
|
||||
boolean found = false;
|
||||
|
||||
|
||||
while (!found && scanner.hasNextLine()) {
|
||||
found = scanner.nextLine().equals("[Events]");
|
||||
}
|
||||
|
||||
|
||||
if (!found) {
|
||||
throw new InputMismatchException("Cannot find [Events] section");
|
||||
}
|
||||
|
||||
|
||||
// read format header
|
||||
readFormat();
|
||||
}
|
||||
|
||||
|
||||
// read next dialogue line
|
||||
String[] event = scanner.nextLine().split(":", 2);
|
||||
|
||||
|
||||
// sanity check
|
||||
if (!event[0].equals("Dialogue"))
|
||||
throw new InputMismatchException("Illegal dialogue event: " + Arrays.toString(event));
|
||||
|
||||
|
||||
// extract information
|
||||
String[] values = event[1].split(",", format.length);
|
||||
|
||||
|
||||
long start = timeFormat.parse(values[formatIndexStart].trim()).getTime();
|
||||
long end = timeFormat.parse(values[formatIndexEnd].trim()).getTime();
|
||||
String text = values[formatIndexText].trim();
|
||||
|
||||
|
||||
return new SubtitleElement(start, end, resolve(text));
|
||||
}
|
||||
|
||||
|
||||
|
||||
protected String resolve(String text) {
|
||||
// remove tags
|
||||
text = tag.matcher(text).replaceAll("");
|
||||
|
||||
// resolve line breaks
|
||||
|
||||
// resolve line breaks
|
||||
return newline.matcher(text).replaceAll("\n");
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
|
|
@ -12,33 +12,33 @@ import java.util.regex.Pattern;
|
|||
|
||||
|
||||
public class SubViewerReader extends SubtitleReader {
|
||||
|
||||
|
||||
private final DateFormat timeFormat = new SubtitleTimeFormat();
|
||||
private final Pattern newline = compile(quote("[br]"), CASE_INSENSITIVE);
|
||||
|
||||
|
||||
|
||||
public SubViewerReader(Readable source) {
|
||||
super(source);
|
||||
}
|
||||
|
||||
|
||||
|
||||
@Override
|
||||
protected SubtitleElement readNext() throws Exception {
|
||||
// element starts with interval (e.g. 00:42:16.33,00:42:19.39)
|
||||
String[] interval = scanner.nextLine().split(",", 2);
|
||||
|
||||
|
||||
if (interval.length < 2 || interval[0].startsWith("[")) {
|
||||
// ignore property lines
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
try {
|
||||
long t1 = timeFormat.parse(interval[0]).getTime();
|
||||
long t2 = timeFormat.parse(interval[1]).getTime();
|
||||
|
||||
|
||||
// translate [br] to new lines
|
||||
String[] lines = newline.split(scanner.nextLine());
|
||||
|
||||
|
||||
return new SubtitleElement(t1, t2, join(lines, "\n"));
|
||||
} catch (ParseException e) {
|
||||
// can't parse interval, ignore line
|
||||
|
|
|
@ -3,38 +3,38 @@ package net.filebot.subtitle;
|
|||
|
||||
|
||||
public class SubtitleElement {
|
||||
|
||||
|
||||
private final long start;
|
||||
private final long end;
|
||||
|
||||
|
||||
private final String text;
|
||||
|
||||
|
||||
|
||||
public SubtitleElement(long start, long end, String text) {
|
||||
this.start = start;
|
||||
this.end = end;
|
||||
this.text = text;
|
||||
}
|
||||
|
||||
|
||||
|
||||
public long getStart() {
|
||||
return start;
|
||||
}
|
||||
|
||||
|
||||
|
||||
public long getEnd() {
|
||||
return end;
|
||||
}
|
||||
|
||||
|
||||
|
||||
public String getText() {
|
||||
return text;
|
||||
}
|
||||
|
||||
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return String.format("[%d, %d] %s", start, end, text);
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
|
|
@ -7,44 +7,44 @@ import net.filebot.util.FileUtilities.ExtensionFileFilter;
|
|||
|
||||
|
||||
public enum SubtitleFormat {
|
||||
|
||||
|
||||
SubRip {
|
||||
|
||||
|
||||
@Override
|
||||
public SubtitleReader newReader(Readable readable) {
|
||||
return new SubRipReader(readable);
|
||||
}
|
||||
},
|
||||
|
||||
|
||||
MicroDVD {
|
||||
|
||||
|
||||
@Override
|
||||
public SubtitleReader newReader(Readable readable) {
|
||||
return new MicroDVDReader(readable);
|
||||
}
|
||||
},
|
||||
|
||||
|
||||
SubViewer {
|
||||
|
||||
|
||||
@Override
|
||||
public SubtitleReader newReader(Readable readable) {
|
||||
return new SubViewerReader(readable);
|
||||
}
|
||||
},
|
||||
|
||||
|
||||
SubStationAlpha {
|
||||
|
||||
|
||||
@Override
|
||||
public SubtitleReader newReader(Readable readable) {
|
||||
return new SubStationAlphaReader(readable);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
public abstract SubtitleReader newReader(Readable readable);
|
||||
|
||||
|
||||
|
||||
public ExtensionFileFilter getFilter() {
|
||||
return MediaTypes.getDefaultFilter("subtitle/" + this.name());
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
|
|
@ -88,6 +88,7 @@ public enum SubtitleMetrics implements SimilarityMetric {
|
|||
return 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected float similarity(String match, String s1, String s2) {
|
||||
return match.length() > 0 ? 1 : 0;
|
||||
}
|
||||
|
@ -153,10 +154,12 @@ public enum SubtitleMetrics implements SimilarityMetric {
|
|||
private final String FPS = "FPS";
|
||||
private final String SECONDS = "SECS";
|
||||
|
||||
@Override
|
||||
public float getSimilarity(Object o1, Object o2) {
|
||||
return o1 instanceof SubtitleDescriptor ? super.getSimilarity(o1, o2) : super.getSimilarity(o2, o1); // make sure that SubtitleDescriptor is o1
|
||||
};
|
||||
|
||||
@Override
|
||||
protected Map<String, Object> getProperties(Object object) {
|
||||
if (object instanceof OpenSubtitlesSubtitleDescriptor) {
|
||||
return getSubtitleProperties((OpenSubtitlesSubtitleDescriptor) object);
|
||||
|
|
|
@ -12,18 +12,18 @@ import java.util.logging.Logger;
|
|||
|
||||
|
||||
public abstract class SubtitleReader implements Iterator<SubtitleElement>, Closeable {
|
||||
|
||||
|
||||
protected final Scanner scanner;
|
||||
protected SubtitleElement current;
|
||||
|
||||
|
||||
|
||||
public SubtitleReader(Readable source) {
|
||||
this.scanner = new Scanner(source);
|
||||
}
|
||||
|
||||
|
||||
|
||||
protected abstract SubtitleElement readNext() throws Exception;
|
||||
|
||||
|
||||
|
||||
@Override
|
||||
public boolean hasNext() {
|
||||
|
@ -36,34 +36,34 @@ public abstract class SubtitleReader implements Iterator<SubtitleElement>, Close
|
|||
Logger.getLogger(getClass().getName()).log(Level.WARNING, "Illegal input: " + e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
return current != null;
|
||||
}
|
||||
|
||||
|
||||
|
||||
@Override
|
||||
public SubtitleElement next() {
|
||||
if (!hasNext()) {
|
||||
throw new NoSuchElementException();
|
||||
}
|
||||
|
||||
|
||||
try {
|
||||
return current;
|
||||
} finally {
|
||||
current = null;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
@Override
|
||||
public void close() throws IOException {
|
||||
scanner.close();
|
||||
}
|
||||
|
||||
|
||||
|
||||
@Override
|
||||
public void remove() {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
|
|
@ -13,45 +13,45 @@ import java.util.regex.Pattern;
|
|||
|
||||
|
||||
class SubtitleTimeFormat extends DateFormat {
|
||||
|
||||
|
||||
public SubtitleTimeFormat() {
|
||||
// calendar without any kind of special handling for time zone and daylight saving time
|
||||
calendar = Calendar.getInstance(TimeZone.getTimeZone("UTC"), Locale.ROOT);
|
||||
}
|
||||
|
||||
|
||||
|
||||
@Override
|
||||
public StringBuffer format(Date date, StringBuffer sb, FieldPosition pos) {
|
||||
// e.g. 1:42:52.42
|
||||
calendar.setTime(date);
|
||||
|
||||
|
||||
sb.append(String.format("%02d", calendar.get(Calendar.HOUR_OF_DAY)));
|
||||
sb.append(':').append(String.format("%02d", calendar.get(Calendar.MINUTE)));
|
||||
sb.append(':').append(String.format("%02d", calendar.get(Calendar.SECOND)));
|
||||
|
||||
|
||||
String millis = String.format("%03d", calendar.get(Calendar.MILLISECOND));
|
||||
sb.append('.').append(millis.substring(0, 2));
|
||||
|
||||
|
||||
return sb;
|
||||
}
|
||||
|
||||
|
||||
|
||||
private final Pattern delimiter = Pattern.compile("[:.]");
|
||||
|
||||
|
||||
|
||||
@Override
|
||||
public Date parse(String source, ParsePosition pos) {
|
||||
String[] split = delimiter.split(source, 4);
|
||||
|
||||
|
||||
// reset state
|
||||
calendar.clear();
|
||||
|
||||
|
||||
try {
|
||||
// handle hours:minutes:seconds
|
||||
calendar.set(Calendar.HOUR_OF_DAY, Integer.parseInt(split[0]));
|
||||
calendar.set(Calendar.MINUTE, Integer.parseInt(split[1]));
|
||||
calendar.set(Calendar.SECOND, Integer.parseInt(split[2]));
|
||||
|
||||
|
||||
// handle hundredth seconds
|
||||
calendar.set(Calendar.MILLISECOND, Integer.parseInt(split[3]) * 10);
|
||||
} catch (Exception e) {
|
||||
|
@ -59,7 +59,7 @@ class SubtitleTimeFormat extends DateFormat {
|
|||
pos.setErrorIndex(0);
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
// update position
|
||||
pos.setIndex(source.length());
|
||||
return calendar.getTime();
|
||||
|
|
|
@ -1,9 +1,9 @@
|
|||
/*
|
||||
* BeDecoder.java
|
||||
*
|
||||
*
|
||||
* Created on May 30, 2003, 2:44 PM
|
||||
* Copyright (C) 2003, 2004, 2005, 2006 Aelitis, All Rights Reserved.
|
||||
*
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public License
|
||||
* as published by the Free Software Foundation; either version 2
|
||||
|
@ -15,7 +15,7 @@
|
|||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, write to the Free Software
|
||||
* Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
|
||||
*
|
||||
*
|
||||
* AELITIS, SAS au capital de 46,603.30 euros
|
||||
* 8 Allee Lenotre, La Grille Royale, 78600 Le Mesnil le Roi, France.
|
||||
*/
|
||||
|
@ -37,190 +37,190 @@ import java.util.Map;
|
|||
/**
|
||||
* A set of utility methods to decode a bencoded array of byte into a Map. integer are
|
||||
* represented as Long, String as byte[], dictionnaries as Map, and list as List.
|
||||
*
|
||||
*
|
||||
* @author TdC_VgA
|
||||
*/
|
||||
class BDecoder {
|
||||
|
||||
|
||||
private static final Charset BINARY_CHARSET = Charset.forName("ISO-8859-1");
|
||||
|
||||
|
||||
|
||||
|
||||
public static Map<?, ?> decode(InputStream is) throws IOException {
|
||||
return (new BDecoder().decodeStream(is));
|
||||
}
|
||||
|
||||
|
||||
|
||||
public Map<?, ?> decodeStream(InputStream data) throws IOException {
|
||||
Object res = decodeInputStream(data, 0);
|
||||
|
||||
|
||||
if (res == null)
|
||||
throw (new IOException("BDecoder: zero length file"));
|
||||
else if (!(res instanceof Map))
|
||||
throw (new IOException("BDecoder: top level isn't a Map"));
|
||||
|
||||
|
||||
return ((Map<?, ?>) res);
|
||||
}
|
||||
|
||||
|
||||
|
||||
private Object decodeInputStream(InputStream bais, int nesting) throws IOException {
|
||||
if (!bais.markSupported())
|
||||
throw new IOException("InputStream must support the mark() method");
|
||||
|
||||
|
||||
// set a mark
|
||||
bais.mark(Integer.MAX_VALUE);
|
||||
|
||||
|
||||
// read a byte
|
||||
int tempByte = bais.read();
|
||||
|
||||
|
||||
// decide what to do
|
||||
switch (tempByte) {
|
||||
case 'd':
|
||||
// create a new dictionary object
|
||||
Map<String, Object> tempMap = new HashMap<String, Object>();
|
||||
|
||||
// get the key
|
||||
byte[] tempByteArray = null;
|
||||
|
||||
while ((tempByteArray = (byte[]) decodeInputStream(bais, nesting + 1)) != null) {
|
||||
|
||||
// decode some more
|
||||
|
||||
Object value = decodeInputStream(bais, nesting + 1);
|
||||
|
||||
// add the value to the map
|
||||
|
||||
CharBuffer cb = BINARY_CHARSET.decode(ByteBuffer.wrap(tempByteArray));
|
||||
|
||||
String key = new String(cb.array(), 0, cb.limit());
|
||||
|
||||
tempMap.put(key, value);
|
||||
}
|
||||
|
||||
if (bais.available() < nesting)
|
||||
throw (new IOException("BDecoder: invalid input data, 'e' missing from end of dictionary"));
|
||||
|
||||
// return the map
|
||||
return tempMap;
|
||||
|
||||
case 'l':
|
||||
// create the list
|
||||
List<Object> tempList = new ArrayList<Object>();
|
||||
|
||||
// create the key
|
||||
Object tempElement = null;
|
||||
while ((tempElement = decodeInputStream(bais, nesting + 1)) != null)
|
||||
// add the element
|
||||
tempList.add(tempElement);
|
||||
|
||||
if (bais.available() < nesting)
|
||||
throw (new IOException("BDecoder: invalid input data, 'e' missing from end of list"));
|
||||
|
||||
// return the list
|
||||
return tempList;
|
||||
|
||||
case 'e':
|
||||
case -1:
|
||||
return null;
|
||||
|
||||
case 'i':
|
||||
return new Long(getNumberFromStream(bais, 'e'));
|
||||
|
||||
case '0':
|
||||
case '1':
|
||||
case '2':
|
||||
case '3':
|
||||
case '4':
|
||||
case '5':
|
||||
case '6':
|
||||
case '7':
|
||||
case '8':
|
||||
case '9':
|
||||
// move back one
|
||||
bais.reset();
|
||||
// get the string
|
||||
return getByteArrayFromStream(bais);
|
||||
|
||||
default: {
|
||||
|
||||
int rem_len = bais.available();
|
||||
|
||||
if (rem_len > 256)
|
||||
rem_len = 256;
|
||||
|
||||
byte[] rem_data = new byte[rem_len];
|
||||
|
||||
bais.read(rem_data);
|
||||
|
||||
throw (new IOException("BDecoder: unknown command '" + tempByte + ", remainder = " + new String(rem_data)));
|
||||
case 'd':
|
||||
// create a new dictionary object
|
||||
Map<String, Object> tempMap = new HashMap<String, Object>();
|
||||
|
||||
// get the key
|
||||
byte[] tempByteArray = null;
|
||||
|
||||
while ((tempByteArray = (byte[]) decodeInputStream(bais, nesting + 1)) != null) {
|
||||
|
||||
// decode some more
|
||||
|
||||
Object value = decodeInputStream(bais, nesting + 1);
|
||||
|
||||
// add the value to the map
|
||||
|
||||
CharBuffer cb = BINARY_CHARSET.decode(ByteBuffer.wrap(tempByteArray));
|
||||
|
||||
String key = new String(cb.array(), 0, cb.limit());
|
||||
|
||||
tempMap.put(key, value);
|
||||
}
|
||||
|
||||
if (bais.available() < nesting)
|
||||
throw (new IOException("BDecoder: invalid input data, 'e' missing from end of dictionary"));
|
||||
|
||||
// return the map
|
||||
return tempMap;
|
||||
|
||||
case 'l':
|
||||
// create the list
|
||||
List<Object> tempList = new ArrayList<Object>();
|
||||
|
||||
// create the key
|
||||
Object tempElement = null;
|
||||
while ((tempElement = decodeInputStream(bais, nesting + 1)) != null)
|
||||
// add the element
|
||||
tempList.add(tempElement);
|
||||
|
||||
if (bais.available() < nesting)
|
||||
throw (new IOException("BDecoder: invalid input data, 'e' missing from end of list"));
|
||||
|
||||
// return the list
|
||||
return tempList;
|
||||
|
||||
case 'e':
|
||||
case -1:
|
||||
return null;
|
||||
|
||||
case 'i':
|
||||
return new Long(getNumberFromStream(bais, 'e'));
|
||||
|
||||
case '0':
|
||||
case '1':
|
||||
case '2':
|
||||
case '3':
|
||||
case '4':
|
||||
case '5':
|
||||
case '6':
|
||||
case '7':
|
||||
case '8':
|
||||
case '9':
|
||||
// move back one
|
||||
bais.reset();
|
||||
// get the string
|
||||
return getByteArrayFromStream(bais);
|
||||
|
||||
default: {
|
||||
|
||||
int rem_len = bais.available();
|
||||
|
||||
if (rem_len > 256)
|
||||
rem_len = 256;
|
||||
|
||||
byte[] rem_data = new byte[rem_len];
|
||||
|
||||
bais.read(rem_data);
|
||||
|
||||
throw (new IOException("BDecoder: unknown command '" + tempByte + ", remainder = " + new String(rem_data)));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
private long getNumberFromStream(InputStream bais, char parseChar) throws IOException {
|
||||
int length = 0;
|
||||
|
||||
|
||||
// place a mark
|
||||
bais.mark(Integer.MAX_VALUE);
|
||||
|
||||
|
||||
int tempByte = bais.read();
|
||||
while ((tempByte != parseChar) && (tempByte >= 0)) {
|
||||
tempByte = bais.read();
|
||||
length++;
|
||||
}
|
||||
|
||||
|
||||
// are we at the end of the stream?
|
||||
if (tempByte < 0)
|
||||
return -1;
|
||||
|
||||
|
||||
// reset the mark
|
||||
bais.reset();
|
||||
|
||||
|
||||
// get the length
|
||||
byte[] tempArray = new byte[length];
|
||||
int count = 0;
|
||||
int len = 0;
|
||||
|
||||
|
||||
// get the string
|
||||
while ((count != length) && ((len = bais.read(tempArray, count, length - count)) > 0))
|
||||
count += len;
|
||||
|
||||
|
||||
// jump ahead in the stream to compensate for the :
|
||||
bais.skip(1);
|
||||
|
||||
|
||||
// return the value
|
||||
CharBuffer cb = BINARY_CHARSET.decode(ByteBuffer.wrap(tempArray));
|
||||
|
||||
|
||||
String str_value = new String(cb.array(), 0, cb.limit());
|
||||
|
||||
|
||||
return Long.parseLong(str_value);
|
||||
}
|
||||
|
||||
|
||||
|
||||
private byte[] getByteArrayFromStream(InputStream bais) throws IOException {
|
||||
int length = (int) getNumberFromStream(bais, ':');
|
||||
|
||||
|
||||
if (length < 0)
|
||||
return null;
|
||||
|
||||
|
||||
// note that torrent hashes can be big (consider a 55GB file with 2MB
|
||||
// pieces
|
||||
// this generates a pieces hash of 1/2 meg
|
||||
if (length > 8 * 1024 * 1024)
|
||||
throw (new IOException("Byte array length too large (" + length + ")"));
|
||||
|
||||
|
||||
byte[] tempArray = new byte[length];
|
||||
int count = 0;
|
||||
int len = 0;
|
||||
|
||||
|
||||
// get the string
|
||||
while ((count != length) && ((len = bais.read(tempArray, count, length - count)) > 0))
|
||||
count += len;
|
||||
|
||||
|
||||
if (count != tempArray.length)
|
||||
throw (new IOException("BDecoder::getByteArrayFromStream: truncated"));
|
||||
|
||||
|
||||
return tempArray;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
|
|
@ -16,7 +16,7 @@ import java.util.Map;
|
|||
|
||||
|
||||
public class Torrent {
|
||||
|
||||
|
||||
private String name;
|
||||
private String encoding;
|
||||
private String createdBy;
|
||||
|
@ -24,188 +24,188 @@ public class Torrent {
|
|||
private String comment;
|
||||
private Long creationDate;
|
||||
private Long pieceLength;
|
||||
|
||||
|
||||
private List<Entry> files;
|
||||
private boolean singleFileTorrent;
|
||||
|
||||
|
||||
|
||||
|
||||
protected Torrent() {
|
||||
// used by serializer
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
public Torrent(File torrent) throws IOException {
|
||||
this(decodeTorrent(torrent));
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
public Torrent(Map<?, ?> torrentMap) {
|
||||
Charset charset = Charset.forName("UTF-8");
|
||||
encoding = decodeString(torrentMap.get("encoding"), charset);
|
||||
|
||||
|
||||
try {
|
||||
charset = Charset.forName(encoding);
|
||||
} catch (IllegalArgumentException e) {
|
||||
// invalid encoding, just keep using UTF-8
|
||||
}
|
||||
|
||||
|
||||
createdBy = decodeString(torrentMap.get("created by"), charset);
|
||||
announce = decodeString(torrentMap.get("announce"), charset);
|
||||
comment = decodeString(torrentMap.get("comment"), charset);
|
||||
creationDate = decodeLong(torrentMap.get("creation date"));
|
||||
|
||||
|
||||
Map<?, ?> infoMap = (Map<?, ?>) torrentMap.get("info");
|
||||
|
||||
|
||||
name = decodeString(infoMap.get("name"), charset);
|
||||
pieceLength = (Long) infoMap.get("piece length");
|
||||
|
||||
|
||||
if (infoMap.containsKey("files")) {
|
||||
// torrent contains multiple entries
|
||||
singleFileTorrent = false;
|
||||
|
||||
|
||||
List<Entry> entries = new ArrayList<Entry>();
|
||||
|
||||
|
||||
for (Object fileMapObject : (List<?>) infoMap.get("files")) {
|
||||
Map<?, ?> fileMap = (Map<?, ?>) fileMapObject;
|
||||
List<?> pathList = (List<?>) fileMap.get("path");
|
||||
|
||||
|
||||
StringBuilder path = new StringBuilder(80);
|
||||
|
||||
|
||||
Iterator<?> iterator = pathList.iterator();
|
||||
|
||||
|
||||
while (iterator.hasNext()) {
|
||||
// append path element
|
||||
path.append(decodeString(iterator.next(), charset));
|
||||
|
||||
|
||||
// append separator
|
||||
if (iterator.hasNext()) {
|
||||
path.append("/");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Long length = decodeLong(fileMap.get("length"));
|
||||
|
||||
|
||||
entries.add(new Entry(path.toString(), length));
|
||||
}
|
||||
|
||||
|
||||
files = Collections.unmodifiableList(entries);
|
||||
} else {
|
||||
// single file torrent
|
||||
singleFileTorrent = true;
|
||||
|
||||
|
||||
Long length = decodeLong(infoMap.get("length"));
|
||||
|
||||
|
||||
files = Collections.singletonList(new Entry(name, length));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
private static Map<?, ?> decodeTorrent(File torrent) throws IOException {
|
||||
InputStream in = new BufferedInputStream(new FileInputStream(torrent));
|
||||
|
||||
|
||||
try {
|
||||
return BDecoder.decode(in);
|
||||
} finally {
|
||||
in.close();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
private String decodeString(Object byteArray, Charset charset) {
|
||||
if (byteArray == null)
|
||||
return null;
|
||||
|
||||
|
||||
return new String((byte[]) byteArray, charset);
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
private Long decodeLong(Object number) {
|
||||
if (number == null)
|
||||
return null;
|
||||
|
||||
|
||||
return (Long) number;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
public String getAnnounce() {
|
||||
return announce;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
public String getComment() {
|
||||
return comment;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
public String getCreatedBy() {
|
||||
return createdBy;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
public Long getCreationDate() {
|
||||
return creationDate;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
public String getEncoding() {
|
||||
return encoding;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
public List<Entry> getFiles() {
|
||||
return files;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
public Long getPieceLength() {
|
||||
return pieceLength;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
public boolean isSingleFileTorrent() {
|
||||
return singleFileTorrent;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
public static class Entry {
|
||||
|
||||
|
||||
private final String path;
|
||||
|
||||
|
||||
private final long length;
|
||||
|
||||
|
||||
|
||||
|
||||
public Entry(String path, long length) {
|
||||
this.path = path;
|
||||
this.length = length;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
public String getPath() {
|
||||
return path;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
public String getName() {
|
||||
// the last element in the path is the filename
|
||||
// torrents don't contain directory entries, so there is always a non-empty name
|
||||
return path.substring(path.lastIndexOf("/") + 1);
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
public long getLength() {
|
||||
return length;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return getPath();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
|
|
@ -8,20 +8,20 @@ import net.filebot.ui.transfer.TextFileExportHandler;
|
|||
|
||||
|
||||
public class FileBotListExportHandler extends TextFileExportHandler {
|
||||
|
||||
|
||||
protected final FileBotList<?> list;
|
||||
|
||||
|
||||
|
||||
|
||||
public FileBotListExportHandler(FileBotList<?> list) {
|
||||
this.list = list;
|
||||
}
|
||||
|
||||
|
||||
|
||||
@Override
|
||||
public boolean canExport() {
|
||||
return list.getModel().size() > 0;
|
||||
}
|
||||
|
||||
|
||||
|
||||
@Override
|
||||
public void export(PrintWriter out) {
|
||||
|
@ -29,11 +29,11 @@ public class FileBotListExportHandler extends TextFileExportHandler {
|
|||
out.println(entry);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
@Override
|
||||
public String getDefaultFileName() {
|
||||
return list.getTitle() + ".txt";
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
|
|
@ -13,91 +13,91 @@ import javax.swing.SwingUtilities;
|
|||
|
||||
|
||||
public class FileBotTab<T extends JComponent> extends JComponent {
|
||||
|
||||
|
||||
private final FileBotTabComponent tabComponent = new FileBotTabComponent();
|
||||
|
||||
|
||||
private final T component;
|
||||
|
||||
|
||||
|
||||
|
||||
public FileBotTab(T component) {
|
||||
this.component = component;
|
||||
|
||||
|
||||
tabComponent.getCloseButton().addActionListener(closeAction);
|
||||
|
||||
|
||||
setLayout(new BorderLayout());
|
||||
add(component, BorderLayout.CENTER);
|
||||
}
|
||||
|
||||
|
||||
|
||||
public void addTo(JTabbedPane tabbedPane) {
|
||||
tabbedPane.addTab(this.getTitle(), this);
|
||||
tabbedPane.setTabComponentAt(tabbedPane.indexOfComponent(this), tabComponent);
|
||||
}
|
||||
|
||||
|
||||
|
||||
public void close() {
|
||||
if (!isClosed()) {
|
||||
getTabbedPane().remove(this);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
public boolean isClosed() {
|
||||
JTabbedPane tabbedPane = getTabbedPane();
|
||||
|
||||
|
||||
if (tabbedPane == null)
|
||||
return true;
|
||||
|
||||
|
||||
return getTabbedPane().indexOfComponent(this) < 0;
|
||||
}
|
||||
|
||||
|
||||
|
||||
private JTabbedPane getTabbedPane() {
|
||||
return (JTabbedPane) SwingUtilities.getAncestorOfClass(JTabbedPane.class, this);
|
||||
}
|
||||
|
||||
|
||||
|
||||
public T getComponent() {
|
||||
return component;
|
||||
}
|
||||
|
||||
|
||||
|
||||
public FileBotTabComponent getTabComponent() {
|
||||
return tabComponent;
|
||||
}
|
||||
|
||||
|
||||
|
||||
public void setTitle(String title) {
|
||||
tabComponent.setText(title);
|
||||
}
|
||||
|
||||
|
||||
|
||||
public String getTitle() {
|
||||
return tabComponent.getText();
|
||||
}
|
||||
|
||||
|
||||
|
||||
public void setIcon(Icon icon) {
|
||||
tabComponent.setIcon(icon);
|
||||
}
|
||||
|
||||
|
||||
|
||||
public Icon getIcon() {
|
||||
return tabComponent.getIcon();
|
||||
}
|
||||
|
||||
|
||||
|
||||
public void setLoading(boolean loading) {
|
||||
tabComponent.setLoading(loading);
|
||||
}
|
||||
|
||||
|
||||
private final ActionListener closeAction = new ActionListener() {
|
||||
|
||||
|
||||
@Override
|
||||
public void actionPerformed(ActionEvent e) {
|
||||
close();
|
||||
}
|
||||
|
||||
|
||||
};
|
||||
|
||||
|
||||
}
|
||||
|
|
|
@ -18,84 +18,84 @@ import net.miginfocom.swing.MigLayout;
|
|||
|
||||
|
||||
public class FileBotTabComponent extends JComponent {
|
||||
|
||||
|
||||
private ProgressIndicator progressIndicator = new ProgressIndicator();
|
||||
private JLabel textLabel = new JLabel();
|
||||
private JLabel iconLabel = new JLabel();
|
||||
private AbstractButton closeButton = createCloseButton();
|
||||
|
||||
|
||||
private boolean loading = false;
|
||||
|
||||
|
||||
|
||||
|
||||
public FileBotTabComponent() {
|
||||
iconLabel.setHorizontalAlignment(SwingConstants.CENTER);
|
||||
textLabel.setHorizontalAlignment(SwingConstants.LEFT);
|
||||
|
||||
|
||||
progressIndicator.setVisible(loading);
|
||||
progressIndicator.setMinimumSize(new Dimension(16, 16));
|
||||
|
||||
|
||||
setLayout(new MigLayout("nogrid, insets 0 0 1 3"));
|
||||
|
||||
|
||||
add(progressIndicator, "hidemode 3");
|
||||
add(iconLabel, "hidemode 3");
|
||||
add(textLabel, "gap rel, align left");
|
||||
add(closeButton, "gap unrel:push, hidemode 3, align center 45%");
|
||||
}
|
||||
|
||||
|
||||
|
||||
public void setLoading(boolean loading) {
|
||||
this.loading = loading;
|
||||
progressIndicator.setVisible(loading);
|
||||
iconLabel.setVisible(!loading);
|
||||
}
|
||||
|
||||
|
||||
|
||||
public boolean isLoading() {
|
||||
return loading;
|
||||
}
|
||||
|
||||
|
||||
|
||||
public void setIcon(Icon icon) {
|
||||
iconLabel.setIcon(icon);
|
||||
progressIndicator.setPreferredSize(icon != null ? SwingUI.getDimension(icon) : progressIndicator.getMinimumSize());
|
||||
}
|
||||
|
||||
|
||||
|
||||
public Icon getIcon() {
|
||||
return iconLabel.getIcon();
|
||||
}
|
||||
|
||||
|
||||
|
||||
public void setText(String text) {
|
||||
textLabel.setText(text);
|
||||
}
|
||||
|
||||
|
||||
|
||||
public String getText() {
|
||||
return textLabel.getText();
|
||||
}
|
||||
|
||||
|
||||
|
||||
public AbstractButton getCloseButton() {
|
||||
return closeButton;
|
||||
}
|
||||
|
||||
|
||||
|
||||
protected AbstractButton createCloseButton() {
|
||||
Icon icon = ResourceManager.getIcon("tab.close");
|
||||
Icon rolloverIcon = ResourceManager.getIcon("tab.close.hover");
|
||||
|
||||
|
||||
JButton button = new JButton(icon);
|
||||
button.setRolloverIcon(rolloverIcon);
|
||||
|
||||
|
||||
button.setPreferredSize(SwingUI.getDimension(rolloverIcon));
|
||||
button.setMaximumSize(button.getPreferredSize());
|
||||
|
||||
|
||||
button.setContentAreaFilled(false);
|
||||
button.setBorderPainted(false);
|
||||
button.setFocusable(false);
|
||||
button.setRolloverEnabled(true);
|
||||
|
||||
|
||||
return button;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -97,11 +97,13 @@ public class LanguageComboBoxModel extends AbstractListModel implements ComboBox
|
|||
return data.get(index);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean add(Language element) {
|
||||
// add first
|
||||
return addIfAbsent(0, element);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void add(int index, Language element) {
|
||||
addIfAbsent(index, element);
|
||||
}
|
||||
|
|
|
@ -125,10 +125,12 @@ public class MainFrame extends JFrame {
|
|||
GroovyPad pad = new GroovyPad();
|
||||
|
||||
pad.addWindowListener(new WindowAdapter() {
|
||||
@Override
|
||||
public void windowOpened(WindowEvent e) {
|
||||
MainFrame.this.setVisible(false);
|
||||
};
|
||||
|
||||
@Override
|
||||
public void windowClosing(WindowEvent e) {
|
||||
MainFrame.this.setVisible(true);
|
||||
};
|
||||
|
|
|
@ -23,52 +23,52 @@ import net.filebot.util.ui.notification.QueueNotificationLayout;
|
|||
|
||||
|
||||
public class NotificationLogging extends Handler {
|
||||
|
||||
|
||||
public static final Logger UILogger = createNotificationLogger("net.filebot.logger.ui");
|
||||
|
||||
|
||||
|
||||
|
||||
private static Logger createNotificationLogger(String name) {
|
||||
Logger log = Logger.getLogger(name);
|
||||
|
||||
|
||||
// don't use parent handlers
|
||||
log.setUseParentHandlers(false);
|
||||
|
||||
|
||||
// ui handler
|
||||
log.addHandler(new NotificationLogging());
|
||||
|
||||
|
||||
// console handler (for warnings and errors only)
|
||||
ConsoleHandler console = new ConsoleHandler();
|
||||
console.setLevel(Level.WARNING);
|
||||
log.addHandler(console);
|
||||
|
||||
|
||||
return log;
|
||||
}
|
||||
|
||||
|
||||
public final NotificationManager notificationManager;
|
||||
public final int timeout = 2500;
|
||||
|
||||
|
||||
|
||||
|
||||
public NotificationLogging() {
|
||||
this(new NotificationManager(new QueueNotificationLayout(NORTH, SOUTH)));
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
public NotificationLogging(NotificationManager notificationManager) {
|
||||
this.notificationManager = notificationManager;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
@Override
|
||||
public void publish(LogRecord record) {
|
||||
// fail gracefully on an headless machine
|
||||
if (GraphicsEnvironment.isHeadless())
|
||||
return;
|
||||
|
||||
|
||||
final Level level = record.getLevel();
|
||||
final String message = getMessage(record);
|
||||
|
||||
|
||||
SwingUtilities.invokeLater(new Runnable() {
|
||||
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
if (level == Level.INFO) {
|
||||
|
@ -81,34 +81,34 @@ public class NotificationLogging extends Handler {
|
|||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
protected String getMessage(LogRecord record) {
|
||||
String message = record.getMessage();
|
||||
|
||||
|
||||
if ((message == null || message.isEmpty()) && record.getThrown() != null) {
|
||||
// if message is empty, display exception string
|
||||
return ExceptionUtilities.getMessage(record.getThrown());
|
||||
}
|
||||
|
||||
|
||||
return message;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
protected void show(String message, Icon icon, int timeout) {
|
||||
notificationManager.show(new MessageNotification(getApplicationName(), message, icon, timeout));
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
@Override
|
||||
public void close() throws SecurityException {
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
@Override
|
||||
public void flush() {
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
|
|
@ -7,13 +7,13 @@ import javax.swing.JComponent;
|
|||
|
||||
|
||||
public interface PanelBuilder {
|
||||
|
||||
|
||||
public String getName();
|
||||
|
||||
|
||||
|
||||
public Icon getIcon();
|
||||
|
||||
|
||||
|
||||
public JComponent create();
|
||||
|
||||
|
||||
}
|
||||
|
|
|
@ -37,6 +37,7 @@ public class AnalyzePanel extends JComponent {
|
|||
|
||||
private final PropertyChangeListener filetreeListener = new PropertyChangeListener() {
|
||||
|
||||
@Override
|
||||
public void propertyChange(PropertyChangeEvent evt) {
|
||||
// stopped loading, refresh tools
|
||||
for (int i = 0; i < toolsPanel.getTabCount(); i++) {
|
||||
|
|
|
@ -10,22 +10,22 @@ import net.filebot.ui.PanelBuilder;
|
|||
|
||||
|
||||
public class AnalyzePanelBuilder implements PanelBuilder {
|
||||
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return "Analyze";
|
||||
}
|
||||
|
||||
|
||||
|
||||
@Override
|
||||
public Icon getIcon() {
|
||||
return ResourceManager.getIcon("panel.analyze");
|
||||
}
|
||||
|
||||
|
||||
|
||||
@Override
|
||||
public JComponent create() {
|
||||
return new AnalyzePanel();
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
|
|
@ -114,6 +114,7 @@ public class FileTree extends JTree {
|
|||
putValue("files", files);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void actionPerformed(ActionEvent event) {
|
||||
UserFiles.revealFiles((Collection<File>) getValue("files"));
|
||||
}
|
||||
|
|
|
@ -13,15 +13,15 @@ import net.filebot.util.ui.GradientStyle;
|
|||
|
||||
|
||||
public class FileTreeCellRenderer extends FancyTreeCellRenderer {
|
||||
|
||||
|
||||
public FileTreeCellRenderer() {
|
||||
super(GradientStyle.TOP_TO_BOTTOM);
|
||||
|
||||
|
||||
openIcon = ResourceManager.getIcon("tree.open");
|
||||
closedIcon = ResourceManager.getIcon("tree.closed");
|
||||
leafIcon = ResourceManager.getIcon("tree.leaf");
|
||||
}
|
||||
|
||||
|
||||
|
||||
@Override
|
||||
public Component getTreeCellRendererComponent(JTree tree, Object value, boolean sel, boolean expanded, boolean leaf, int row, boolean hasFocus) {
|
||||
|
@ -30,17 +30,17 @@ public class FileTreeCellRenderer extends FancyTreeCellRenderer {
|
|||
expanded = true;
|
||||
leaf = false;
|
||||
}
|
||||
|
||||
|
||||
super.getTreeCellRendererComponent(tree, value, sel, expanded, leaf, row, hasFocus);
|
||||
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
|
||||
|
||||
private boolean isFolder(Object value) {
|
||||
if (((TreeNode) value).getAllowsChildren())
|
||||
return true;
|
||||
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -7,7 +7,6 @@ import static net.filebot.util.FileUtilities.*;
|
|||
|
||||
import java.io.File;
|
||||
import java.io.FileFilter;
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
|
@ -15,8 +14,6 @@ import java.util.Map;
|
|||
import java.util.Map.Entry;
|
||||
import java.util.SortedMap;
|
||||
import java.util.TreeMap;
|
||||
import java.util.logging.Level;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
import javax.swing.BorderFactory;
|
||||
import javax.swing.JScrollPane;
|
||||
|
|
|
@ -10,22 +10,22 @@ import net.filebot.ui.PanelBuilder;
|
|||
|
||||
|
||||
public class EpisodeListPanelBuilder implements PanelBuilder {
|
||||
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return "Episodes";
|
||||
}
|
||||
|
||||
|
||||
|
||||
@Override
|
||||
public Icon getIcon() {
|
||||
return ResourceManager.getIcon("panel.episodelist");
|
||||
}
|
||||
|
||||
|
||||
|
||||
@Override
|
||||
public JComponent create() {
|
||||
return new EpisodeListPanel();
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
|
|
@ -6,47 +6,47 @@ import javax.swing.SpinnerNumberModel;
|
|||
|
||||
|
||||
class SeasonSpinnerModel extends SpinnerNumberModel {
|
||||
|
||||
|
||||
public static final int ALL_SEASONS = 0;
|
||||
|
||||
|
||||
public static final int MAX_VALUE = 99;
|
||||
|
||||
|
||||
private Number valueBeforeLock = null;
|
||||
|
||||
|
||||
|
||||
|
||||
public SeasonSpinnerModel() {
|
||||
super(ALL_SEASONS, ALL_SEASONS, MAX_VALUE, 1);
|
||||
}
|
||||
|
||||
|
||||
|
||||
public int getSeason() {
|
||||
return getNumber().intValue();
|
||||
}
|
||||
|
||||
|
||||
|
||||
@Override
|
||||
public Integer getMinimum() {
|
||||
return (Integer) super.getMinimum();
|
||||
}
|
||||
|
||||
|
||||
|
||||
@Override
|
||||
public Integer getMaximum() {
|
||||
return (Integer) super.getMaximum();
|
||||
}
|
||||
|
||||
|
||||
|
||||
public void spin(int steps) {
|
||||
int next = getSeason() + steps;
|
||||
|
||||
|
||||
if (next < getMinimum())
|
||||
next = getMinimum();
|
||||
else if (next > getMaximum())
|
||||
next = getMaximum();
|
||||
|
||||
|
||||
setValue(next);
|
||||
}
|
||||
|
||||
|
||||
|
||||
public void lock(int value) {
|
||||
valueBeforeLock = getNumber();
|
||||
|
@ -54,12 +54,12 @@ class SeasonSpinnerModel extends SpinnerNumberModel {
|
|||
setMaximum(value);
|
||||
setValue(value);
|
||||
}
|
||||
|
||||
|
||||
|
||||
public void unlock() {
|
||||
setMinimum(ALL_SEASONS);
|
||||
setMaximum(MAX_VALUE);
|
||||
|
||||
|
||||
if (valueBeforeLock != null) {
|
||||
setValue(valueBeforeLock);
|
||||
valueBeforeLock = null;
|
||||
|
|
|
@ -10,22 +10,22 @@ import net.filebot.ui.PanelBuilder;
|
|||
|
||||
|
||||
public class ListPanelBuilder implements PanelBuilder {
|
||||
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return "List";
|
||||
}
|
||||
|
||||
|
||||
|
||||
@Override
|
||||
public Icon getIcon() {
|
||||
return ResourceManager.getIcon("panel.list");
|
||||
}
|
||||
|
||||
|
||||
|
||||
@Override
|
||||
public JComponent create() {
|
||||
return new ListPanel();
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
|
|
@ -21,42 +21,42 @@ import net.filebot.util.ui.GradientStyle;
|
|||
|
||||
|
||||
class CharacterHighlightPainter implements Highlighter.HighlightPainter {
|
||||
|
||||
|
||||
private Color gradientBeginColor;
|
||||
private Color gradientEndColor;
|
||||
|
||||
|
||||
|
||||
|
||||
public CharacterHighlightPainter(Color gradientBeginColor, Color gradientEndColor) {
|
||||
this.gradientBeginColor = gradientBeginColor;
|
||||
this.gradientEndColor = gradientEndColor;
|
||||
}
|
||||
|
||||
|
||||
|
||||
@Override
|
||||
public void paint(Graphics g, int offset1, int offset2, Shape bounds, JTextComponent c) {
|
||||
Graphics2D g2d = (Graphics2D) g;
|
||||
|
||||
|
||||
try {
|
||||
// determine locations
|
||||
TextUI mapper = c.getUI();
|
||||
Rectangle p1 = mapper.modelToView(c, offset1);
|
||||
Rectangle p2 = mapper.modelToView(c, offset2);
|
||||
|
||||
|
||||
Rectangle r = p1.union(p2);
|
||||
|
||||
|
||||
float w = r.width + 1;
|
||||
float h = r.height;
|
||||
|
||||
|
||||
float x = r.x - 1;
|
||||
float y = r.y;
|
||||
|
||||
|
||||
float arch = 5f;
|
||||
|
||||
|
||||
RoundRectangle2D shape = new RoundRectangle2D.Float(x, y, w, h, arch, arch);
|
||||
|
||||
|
||||
g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
|
||||
g2d.setPaint(GradientStyle.TOP_TO_BOTTOM.getGradientPaint(shape, gradientBeginColor, gradientEndColor));
|
||||
|
||||
|
||||
g2d.fill(shape);
|
||||
} catch (BadLocationException e) {
|
||||
//should not happen
|
||||
|
|
|
@ -14,55 +14,55 @@ import net.filebot.similarity.Match;
|
|||
|
||||
|
||||
class ExpressionFormatter implements MatchFormatter {
|
||||
|
||||
|
||||
private final String expression;
|
||||
private ExpressionFormat format;
|
||||
|
||||
|
||||
private Format preview;
|
||||
private Class<?> target;
|
||||
|
||||
|
||||
|
||||
|
||||
public ExpressionFormatter(String expression, Format preview, Class<?> target) {
|
||||
if (expression == null || expression.isEmpty())
|
||||
throw new IllegalArgumentException("Expression must not be null or empty");
|
||||
|
||||
|
||||
this.expression = expression;
|
||||
this.preview = preview;
|
||||
this.target = target;
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
@Override
|
||||
public boolean canFormat(Match<?, ?> match) {
|
||||
// target object is required, file is optional
|
||||
return target.isInstance(match.getValue()) && (match.getCandidate() == null || match.getCandidate() instanceof File);
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
@Override
|
||||
public String preview(Match<?, ?> match) {
|
||||
return preview != null ? preview.format(match.getValue()) : match.getValue().toString();
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
@Override
|
||||
public synchronized String format(Match<?, ?> match, Map<?, ?> context) throws ScriptException {
|
||||
// lazy initialize script engine
|
||||
if (format == null) {
|
||||
format = new ExpressionFormat(expression);
|
||||
}
|
||||
|
||||
|
||||
// evaluate the expression using the given bindings
|
||||
Object bindingBean = new MediaBindingBean(match.getValue(), (File) match.getCandidate(), (Map<File, Object>) context);
|
||||
String result = format.format(bindingBean).trim();
|
||||
|
||||
|
||||
// if result is empty, check for script exceptions
|
||||
if (result.isEmpty() && format.caughtScriptException() != null) {
|
||||
throw format.caughtScriptException();
|
||||
}
|
||||
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
|
|
@ -11,47 +11,47 @@ import net.filebot.vfs.FileInfo;
|
|||
|
||||
|
||||
class FileNameFormatter implements MatchFormatter {
|
||||
|
||||
|
||||
private boolean preserveExtension;
|
||||
|
||||
|
||||
|
||||
|
||||
public FileNameFormatter(boolean preserveExtension) {
|
||||
this.preserveExtension = preserveExtension;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
@Override
|
||||
public boolean canFormat(Match<?, ?> match) {
|
||||
return match.getValue() instanceof File || match.getValue() instanceof FileInfo || match.getValue() instanceof String;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
@Override
|
||||
public String preview(Match<?, ?> match) {
|
||||
return format(match, null);
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
@Override
|
||||
public String format(Match<?, ?> match, Map<?, ?> context) {
|
||||
Object value = match.getValue();
|
||||
|
||||
|
||||
if (value instanceof File) {
|
||||
File file = (File) value;
|
||||
return preserveExtension ? FileUtilities.getName(file) : file.getName();
|
||||
}
|
||||
|
||||
|
||||
if (value instanceof FileInfo) {
|
||||
FileInfo file = (FileInfo) value;
|
||||
return preserveExtension ? file.getName() : file.getPath();
|
||||
}
|
||||
|
||||
|
||||
if (value instanceof String) {
|
||||
return preserveExtension ? FileUtilities.getNameWithoutExtension(value.toString()) : value.toString();
|
||||
}
|
||||
|
||||
|
||||
// cannot format value
|
||||
throw new IllegalArgumentException("Illegal value: " + value);
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
|
|
@ -24,45 +24,45 @@ import net.filebot.util.ui.SwingUI;
|
|||
|
||||
|
||||
class HighlightListCellRenderer extends AbstractFancyListCellRenderer {
|
||||
|
||||
|
||||
protected final JTextComponent textComponent = new JTextField();
|
||||
|
||||
|
||||
protected final Pattern pattern;
|
||||
protected final Highlighter.HighlightPainter highlightPainter;
|
||||
|
||||
|
||||
|
||||
public HighlightListCellRenderer(Pattern pattern, Highlighter.HighlightPainter highlightPainter, int padding) {
|
||||
super(new Insets(0, 0, 0, 0));
|
||||
|
||||
|
||||
this.pattern = pattern;
|
||||
this.highlightPainter = highlightPainter;
|
||||
|
||||
// pad the cell from inside the text component,
|
||||
|
||||
// pad the cell from inside the text component,
|
||||
// so the HighlightPainter may paint in this space as well
|
||||
textComponent.setBorder(new EmptyBorder(padding, padding, padding, padding));
|
||||
|
||||
// make text component transparent, should work for all LAFs (setOpaque(false) may not, e.g. Nimbus)
|
||||
|
||||
// make text component transparent, should work for all LAFs (setOpaque(false) may not, e.g. Nimbus)
|
||||
textComponent.setBackground(SwingUI.TRANSLUCENT);
|
||||
|
||||
|
||||
this.add(textComponent, BorderLayout.WEST);
|
||||
|
||||
|
||||
textComponent.getDocument().addDocumentListener(new HighlightUpdateListener());
|
||||
}
|
||||
|
||||
|
||||
|
||||
@Override
|
||||
protected void configureListCellRendererComponent(JList list, Object value, int index, boolean isSelected, boolean cellHasFocus) {
|
||||
super.configureListCellRendererComponent(list, value, index, isSelected, cellHasFocus);
|
||||
|
||||
|
||||
textComponent.setText(value.toString());
|
||||
}
|
||||
|
||||
|
||||
|
||||
protected void updateHighlighter() {
|
||||
textComponent.getHighlighter().removeAllHighlights();
|
||||
|
||||
|
||||
Matcher matcher = pattern.matcher(textComponent.getText());
|
||||
|
||||
|
||||
while (matcher.find()) {
|
||||
try {
|
||||
textComponent.getHighlighter().addHighlight(matcher.start(0), matcher.end(0), highlightPainter);
|
||||
|
@ -72,38 +72,38 @@ class HighlightListCellRenderer extends AbstractFancyListCellRenderer {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
@Override
|
||||
public void setForeground(Color fg) {
|
||||
super.setForeground(fg);
|
||||
|
||||
|
||||
// textComponent is null while in super constructor
|
||||
if (textComponent != null) {
|
||||
textComponent.setForeground(fg);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
private class HighlightUpdateListener implements DocumentListener {
|
||||
|
||||
|
||||
@Override
|
||||
public void changedUpdate(DocumentEvent e) {
|
||||
updateHighlighter();
|
||||
}
|
||||
|
||||
|
||||
|
||||
@Override
|
||||
public void insertUpdate(DocumentEvent e) {
|
||||
updateHighlighter();
|
||||
}
|
||||
|
||||
|
||||
|
||||
@Override
|
||||
public void removeUpdate(DocumentEvent e) {
|
||||
updateHighlighter();
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
|
|
@ -42,6 +42,7 @@ class MatchAction extends AbstractAction {
|
|||
putValue(SMALL_ICON, ResourceManager.getIcon(strict ? "action.match.strict" : "action.match"));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void actionPerformed(ActionEvent evt) {
|
||||
if (model.names().isEmpty() || model.files().isEmpty()) {
|
||||
return;
|
||||
|
|
|
@ -8,13 +8,13 @@ import net.filebot.similarity.Match;
|
|||
|
||||
|
||||
public interface MatchFormatter {
|
||||
|
||||
|
||||
public boolean canFormat(Match<?, ?> match);
|
||||
|
||||
|
||||
|
||||
|
||||
public String preview(Match<?, ?> match);
|
||||
|
||||
|
||||
|
||||
|
||||
public String format(Match<?, ?> match, Map<?, ?> context) throws Exception;
|
||||
|
||||
|
||||
}
|
||||
|
|
|
@ -16,48 +16,48 @@ import ca.odell.glazedlists.event.ListEvent;
|
|||
|
||||
|
||||
public class MatchModel<Value, Candidate> {
|
||||
|
||||
|
||||
private final EventList<Match<Value, Candidate>> source = new BasicEventList<Match<Value, Candidate>>();
|
||||
|
||||
|
||||
private final EventList<Value> values;
|
||||
|
||||
|
||||
private final EventList<Candidate> candidates;
|
||||
|
||||
|
||||
|
||||
|
||||
public MatchModel() {
|
||||
this.values = new MatchView<Value, Candidate>(source) {
|
||||
|
||||
|
||||
@Override
|
||||
public Value getElement(Match<Value, Candidate> match) {
|
||||
return match.getValue();
|
||||
}
|
||||
|
||||
|
||||
|
||||
@Override
|
||||
public Candidate getComplement(Match<Value, Candidate> match) {
|
||||
return match.getCandidate();
|
||||
}
|
||||
|
||||
|
||||
|
||||
@Override
|
||||
public Match<Value, Candidate> createMatch(Value element, Candidate complement) {
|
||||
return new Match<Value, Candidate>(element, complement);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
this.candidates = new MatchView<Candidate, Value>(source) {
|
||||
|
||||
|
||||
@Override
|
||||
public Candidate getElement(Match<Value, Candidate> match) {
|
||||
return match.getCandidate();
|
||||
}
|
||||
|
||||
|
||||
|
||||
@Override
|
||||
public Value getComplement(Match<Value, Candidate> match) {
|
||||
return match.getValue();
|
||||
}
|
||||
|
||||
|
||||
|
||||
@Override
|
||||
public Match<Value, Candidate> createMatch(Candidate element, Value complement) {
|
||||
|
@ -65,154 +65,154 @@ public class MatchModel<Value, Candidate> {
|
|||
}
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
|
||||
public void clear() {
|
||||
source.clear();
|
||||
}
|
||||
|
||||
|
||||
|
||||
public int size() {
|
||||
return source.size();
|
||||
}
|
||||
|
||||
|
||||
|
||||
public Match<Value, Candidate> getMatch(int index) {
|
||||
return source.get(index);
|
||||
}
|
||||
|
||||
|
||||
|
||||
public boolean hasComplement(int index) {
|
||||
if (index >= 0 && index < size()) {
|
||||
return source.get(index).getValue() != null && source.get(index).getCandidate() != null;
|
||||
}
|
||||
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
|
||||
public EventList<Match<Value, Candidate>> matches() {
|
||||
return source;
|
||||
}
|
||||
|
||||
|
||||
|
||||
public EventList<Value> values() {
|
||||
return values;
|
||||
}
|
||||
|
||||
|
||||
|
||||
public EventList<Candidate> candidates() {
|
||||
return candidates;
|
||||
}
|
||||
|
||||
|
||||
|
||||
public void addAll(Collection<Match<Value, Candidate>> matches) {
|
||||
source.addAll(matches);
|
||||
}
|
||||
|
||||
|
||||
|
||||
public void addAll(Collection<Value> values, Collection<Candidate> candidates) {
|
||||
if (this.values.size() != this.candidates.size())
|
||||
throw new IllegalStateException("Existing matches are not balanced");
|
||||
|
||||
|
||||
Iterator<Value> valueIterator = values.iterator();
|
||||
Iterator<Candidate> candidateIterator = candidates.iterator();
|
||||
|
||||
|
||||
while (valueIterator.hasNext() || candidateIterator.hasNext()) {
|
||||
Value value = valueIterator.hasNext() ? valueIterator.next() : null;
|
||||
Candidate candidate = candidateIterator.hasNext() ? candidateIterator.next() : null;
|
||||
|
||||
|
||||
source.add(new Match<Value, Candidate>(value, candidate));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
private abstract class MatchView<Element, Complement> extends TransformedList<Match<Value, Candidate>, Element> {
|
||||
|
||||
|
||||
public MatchView(EventList<Match<Value, Candidate>> source) {
|
||||
super(source);
|
||||
|
||||
|
||||
source.addListEventListener(this);
|
||||
}
|
||||
|
||||
|
||||
|
||||
public abstract Element getElement(Match<Value, Candidate> match);
|
||||
|
||||
|
||||
|
||||
public abstract Complement getComplement(Match<Value, Candidate> match);
|
||||
|
||||
|
||||
|
||||
public abstract Match<Value, Candidate> createMatch(Element element, Complement complement);
|
||||
|
||||
|
||||
|
||||
@Override
|
||||
public Element get(int index) {
|
||||
return getElement(index);
|
||||
}
|
||||
|
||||
|
||||
|
||||
public Element getElement(int index) {
|
||||
return getElement(source.get(index));
|
||||
}
|
||||
|
||||
|
||||
|
||||
public Complement getComplement(int index) {
|
||||
return getComplement(source.get(index));
|
||||
}
|
||||
|
||||
|
||||
|
||||
@Override
|
||||
public boolean addAll(Collection<? extends Element> values) {
|
||||
return put(size(), values);
|
||||
}
|
||||
|
||||
|
||||
|
||||
@Override
|
||||
public boolean add(Element value) {
|
||||
return put(size(), Collections.singleton(value));
|
||||
};
|
||||
|
||||
|
||||
|
||||
@Override
|
||||
public void add(int index, Element value) {
|
||||
List<Element> range = new ArrayList<Element>();
|
||||
|
||||
|
||||
range.add(value);
|
||||
range.addAll(subList(index, size()));
|
||||
|
||||
|
||||
put(index, range);
|
||||
}
|
||||
|
||||
|
||||
|
||||
@Override
|
||||
public Element remove(int index) {
|
||||
Element old = getElement(index);
|
||||
|
||||
|
||||
int lastIndex = size() - 1;
|
||||
|
||||
|
||||
// shift subsequent elements
|
||||
put(index, new ArrayList<Element>(subList(index + 1, lastIndex + 1)));
|
||||
|
||||
|
||||
// remove last element
|
||||
if (getComplement(lastIndex) == null) {
|
||||
source.remove(lastIndex);
|
||||
} else {
|
||||
set(lastIndex, null);
|
||||
}
|
||||
|
||||
|
||||
return old;
|
||||
}
|
||||
|
||||
|
||||
|
||||
@Override
|
||||
public Element set(int index, Element element) {
|
||||
Element old = getElement(index);
|
||||
|
||||
|
||||
source.set(index, createMatch(element, getComplement(index)));
|
||||
|
||||
|
||||
return old;
|
||||
}
|
||||
|
||||
|
||||
|
||||
@Override
|
||||
public void clear() {
|
||||
|
@ -220,7 +220,7 @@ public class MatchModel<Value, Candidate> {
|
|||
// exist at the and of the source model
|
||||
for (int i = size() - 1; i >= 0; i--) {
|
||||
Complement complement = getComplement(i);
|
||||
|
||||
|
||||
if (complement != null) {
|
||||
// replace original match with null match
|
||||
source.set(i, createMatch(null, complement));
|
||||
|
@ -230,7 +230,7 @@ public class MatchModel<Value, Candidate> {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
private boolean put(int index, Collection<? extends Element> elements) {
|
||||
for (Element element : elements) {
|
||||
|
@ -239,37 +239,37 @@ public class MatchModel<Value, Candidate> {
|
|||
} else {
|
||||
source.add(index, createMatch(element, null));
|
||||
}
|
||||
|
||||
|
||||
index++;
|
||||
}
|
||||
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
|
||||
@Override
|
||||
protected boolean isWritable() {
|
||||
// can't write to source directly
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
private int size = 0;
|
||||
|
||||
|
||||
|
||||
|
||||
@Override
|
||||
public int size() {
|
||||
return size;
|
||||
}
|
||||
|
||||
|
||||
|
||||
@Override
|
||||
public void listChanged(ListEvent<Match<Value, Candidate>> listChanges) {
|
||||
updates.beginEvent(true);
|
||||
|
||||
|
||||
while (listChanges.next()) {
|
||||
int index = listChanges.getIndex();
|
||||
int type = listChanges.getType();
|
||||
|
||||
|
||||
if (type == ListEvent.INSERT || type == ListEvent.UPDATE) {
|
||||
if (index < size) {
|
||||
if (index == size - 1 && getElement(index) == null) {
|
||||
|
@ -287,9 +287,9 @@ public class MatchModel<Value, Candidate> {
|
|||
size--;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
updates.commitEvent();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
|
|
@ -12,31 +12,31 @@ import net.filebot.web.MoviePart;
|
|||
|
||||
|
||||
class MovieFormatter implements MatchFormatter {
|
||||
|
||||
|
||||
@Override
|
||||
public boolean canFormat(Match<?, ?> match) {
|
||||
return match.getValue() instanceof MoviePart;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
@Override
|
||||
public String preview(Match<?, ?> match) {
|
||||
return format(match, null);
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
@Override
|
||||
public String format(Match<?, ?> match, Map<?, ?> context) {
|
||||
MoviePart video = (MoviePart) match.getValue();
|
||||
Formatter name = new Formatter(new StringBuilder());
|
||||
|
||||
|
||||
// format as single-file or multi-part movie
|
||||
name.format("%s (%d)", video.getName(), video.getYear());
|
||||
|
||||
|
||||
if (video.getPartCount() > 1) {
|
||||
name.format(".CD%d", video.getPartIndex());
|
||||
}
|
||||
|
||||
|
||||
// remove path separators if the name contains any / or \
|
||||
return replacePathSeparators(name.out().toString());
|
||||
}
|
||||
|
|
|
@ -237,6 +237,7 @@ public class PresetEditor extends JDialog {
|
|||
|
||||
private final ListCellRenderer<Object> parent = (ListCellRenderer<Object>) combo.getRenderer();
|
||||
|
||||
@Override
|
||||
public Component getListCellRendererComponent(JList<?> list, Object value, int index, boolean isSelected, boolean cellHasFocus) {
|
||||
JLabel label = (JLabel) parent.getListCellRendererComponent(list, value, index, isSelected, cellHasFocus);
|
||||
|
||||
|
@ -278,7 +279,7 @@ public class PresetEditor extends JDialog {
|
|||
JLabel label = (JLabel) parent.getListCellRendererComponent(list, value, index, isSelected, cellHasFocus);
|
||||
|
||||
if (value instanceof Language) {
|
||||
Language it = (Language) value;
|
||||
Language it = value;
|
||||
label.setText(it.getName());
|
||||
label.setIcon(ResourceManager.getFlagIcon(it.getCode()));
|
||||
}
|
||||
|
|
|
@ -126,6 +126,7 @@ class RenameList<E> extends FileBotList<E> {
|
|||
|
||||
private final AbstractAction upAction = new AbstractAction("Align Up", ResourceManager.getIcon("action.up")) {
|
||||
|
||||
@Override
|
||||
public void actionPerformed(ActionEvent e) {
|
||||
int index = getListComponent().getSelectedIndex();
|
||||
|
||||
|
@ -138,6 +139,7 @@ class RenameList<E> extends FileBotList<E> {
|
|||
|
||||
private final AbstractAction downAction = new AbstractAction("Align Down", ResourceManager.getIcon("action.down")) {
|
||||
|
||||
@Override
|
||||
public void actionPerformed(ActionEvent e) {
|
||||
int index = getListComponent().getSelectedIndex();
|
||||
|
||||
|
|
|
@ -274,7 +274,7 @@ public class RenamePanel extends JComponent {
|
|||
try {
|
||||
JList list = (JList) evt.getSource();
|
||||
if (list.getSelectedIndex() >= 0) {
|
||||
UserFiles.revealFiles((List<File>) list.getSelectedValuesList());
|
||||
UserFiles.revealFiles(list.getSelectedValuesList());
|
||||
}
|
||||
} catch (Exception e) {
|
||||
Logger.getLogger(RenamePanel.class.getName()).log(Level.WARNING, e.getMessage());
|
||||
|
@ -702,6 +702,7 @@ public class RenamePanel extends JComponent {
|
|||
this.preset = preset;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<File> getFiles(ActionEvent evt) {
|
||||
List<File> input = new ArrayList<File>();
|
||||
if (preset.getInputFolder() != null) {
|
||||
|
@ -725,10 +726,12 @@ public class RenamePanel extends JComponent {
|
|||
return input;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isStrict(ActionEvent evt) {
|
||||
return preset.getMatchMode() != null ? MATCH_MODE_STRICT.equals(preset.getMatchMode()) : super.isStrict(evt);
|
||||
}
|
||||
|
||||
@Override
|
||||
public SortOrder getSortOrder(ActionEvent evt) {
|
||||
return preset.getSortOrder() != null ? preset.getSortOrder() : super.getSortOrder(evt);
|
||||
}
|
||||
|
|
|
@ -10,22 +10,22 @@ import net.filebot.ui.PanelBuilder;
|
|||
|
||||
|
||||
public class RenamePanelBuilder implements PanelBuilder {
|
||||
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return "Rename";
|
||||
}
|
||||
|
||||
|
||||
|
||||
@Override
|
||||
public Icon getIcon() {
|
||||
return ResourceManager.getIcon("panel.rename");
|
||||
}
|
||||
|
||||
|
||||
|
||||
@Override
|
||||
public JComponent create() {
|
||||
return new RenamePanel();
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
|
|
@ -13,88 +13,88 @@ import javax.swing.event.ListDataListener;
|
|||
|
||||
|
||||
class ScrollPaneSynchronizer {
|
||||
|
||||
|
||||
private final RenameList[] components;
|
||||
|
||||
|
||||
|
||||
public ScrollPaneSynchronizer(RenameList... components) {
|
||||
this.components = components;
|
||||
|
||||
|
||||
// share vertical and horizontal scrollbar model
|
||||
BoundedRangeModel horizontalScrollBarModel = components[0].getListScrollPane().getHorizontalScrollBar().getModel();
|
||||
BoundedRangeModel verticalScrollBarModel = components[0].getListScrollPane().getVerticalScrollBar().getModel();
|
||||
|
||||
|
||||
// recalculate common size on change
|
||||
ListDataListener resizeListener = new ListDataListener() {
|
||||
|
||||
|
||||
private final Timer timer = new Timer(50, new ActionListener() {
|
||||
|
||||
|
||||
@Override
|
||||
public void actionPerformed(ActionEvent e) {
|
||||
updatePreferredSize();
|
||||
|
||||
|
||||
// fire only once
|
||||
timer.stop();
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
|
||||
@Override
|
||||
public void intervalAdded(ListDataEvent e) {
|
||||
timer.restart();
|
||||
}
|
||||
|
||||
|
||||
|
||||
@Override
|
||||
public void intervalRemoved(ListDataEvent e) {
|
||||
timer.restart();
|
||||
}
|
||||
|
||||
|
||||
|
||||
@Override
|
||||
public void contentsChanged(ListDataEvent e) {
|
||||
timer.restart();
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
// apply to all components
|
||||
for (RenameList<?> component : components) {
|
||||
component.getListScrollPane().getHorizontalScrollBar().setModel(horizontalScrollBarModel);
|
||||
component.getListScrollPane().getVerticalScrollBar().setModel(verticalScrollBarModel);
|
||||
|
||||
|
||||
component.getListComponent().getModel().addListDataListener(resizeListener);
|
||||
}
|
||||
|
||||
|
||||
// initial sync of component sizes
|
||||
updatePreferredSize();
|
||||
}
|
||||
|
||||
|
||||
|
||||
public void updatePreferredSize() {
|
||||
Dimension max = new Dimension();
|
||||
|
||||
|
||||
for (RenameList component : components) {
|
||||
// reset preferred size
|
||||
component.getListComponent().setPreferredSize(null);
|
||||
|
||||
|
||||
// calculate preferred size based on data and renderer
|
||||
Dimension preferred = component.getListComponent().getPreferredSize();
|
||||
|
||||
|
||||
// update maximum size
|
||||
if (preferred.width > max.width)
|
||||
max.width = preferred.width;
|
||||
if (preferred.height > max.height)
|
||||
max.height = preferred.height;
|
||||
}
|
||||
|
||||
|
||||
for (RenameList component : components) {
|
||||
// set fixed preferred size
|
||||
component.getListComponent().setPreferredSize(max);
|
||||
|
||||
|
||||
// update scrollbars
|
||||
component.getListComponent().revalidate();
|
||||
component.getListScrollPane().revalidate();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
|
|
@ -39,36 +39,36 @@ import net.miginfocom.swing.MigLayout;
|
|||
|
||||
|
||||
class ValidateDialog extends JDialog {
|
||||
|
||||
|
||||
private final JList list;
|
||||
|
||||
|
||||
private File[] model;
|
||||
|
||||
|
||||
private boolean cancelled = true;
|
||||
|
||||
|
||||
|
||||
|
||||
public ValidateDialog(Window owner, Collection<File> source) {
|
||||
super(owner, "Invalid Names", ModalityType.DOCUMENT_MODAL);
|
||||
|
||||
|
||||
model = source.toArray(new File[0]);
|
||||
|
||||
|
||||
list = new JList(model);
|
||||
list.setEnabled(false);
|
||||
|
||||
|
||||
list.setCellRenderer(new HighlightListCellRenderer(ILLEGAL_CHARACTERS, new CharacterHighlightPainter(new Color(0xFF4200), new Color(0xFF1200)), 4) {
|
||||
|
||||
|
||||
@Override
|
||||
protected void updateHighlighter() {
|
||||
textComponent.getHighlighter().removeAllHighlights();
|
||||
|
||||
|
||||
Matcher matcher = pattern.matcher(textComponent.getText());
|
||||
File file = new File(textComponent.getText());
|
||||
|
||||
|
||||
// highlight path components separately to ignore "illegal characters" that are either path separators or part of the drive letter (e.g. ':' in 'E:')
|
||||
for (File element : listPath(file)) {
|
||||
int limit = element.getPath().length();
|
||||
matcher.region(limit - element.getName().length(), limit);
|
||||
|
||||
|
||||
while (matcher.find()) {
|
||||
try {
|
||||
textComponent.getHighlighter().addHighlight(matcher.start(0), matcher.end(0), highlightPainter);
|
||||
|
@ -80,47 +80,47 @@ class ValidateDialog extends JDialog {
|
|||
}
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
JLabel label = new JLabel("Some names contain invalid characters:");
|
||||
|
||||
|
||||
JComponent content = (JComponent) getContentPane();
|
||||
|
||||
|
||||
content.setLayout(new MigLayout("insets dialog, nogrid, fill", "", "[pref!][fill][pref!]"));
|
||||
|
||||
|
||||
content.add(label, "wrap");
|
||||
content.add(new JScrollPane(list), "grow, wrap 2mm");
|
||||
|
||||
|
||||
content.add(new JButton(validateAction), "align center");
|
||||
content.add(new JButton(continueAction), "gap related");
|
||||
content.add(new JButton(cancelAction), "gap 12mm");
|
||||
|
||||
|
||||
installAction(content, KeyStroke.getKeyStroke(KeyEvent.VK_ESCAPE, 0), cancelAction);
|
||||
|
||||
|
||||
setDefaultCloseOperation(DISPOSE_ON_CLOSE);
|
||||
setMinimumSize(new Dimension(365, 280));
|
||||
pack();
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
public List<File> getModel() {
|
||||
return unmodifiableList(Arrays.asList(model));
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
public boolean isCancelled() {
|
||||
return cancelled;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
private void finish(boolean cancelled) {
|
||||
this.cancelled = cancelled;
|
||||
|
||||
|
||||
setVisible(false);
|
||||
dispose();
|
||||
}
|
||||
|
||||
|
||||
private final Action validateAction = new AbstractAction("Validate", ResourceManager.getIcon("dialog.continue")) {
|
||||
|
||||
|
||||
@Override
|
||||
public void actionPerformed(ActionEvent e) {
|
||||
// validate names
|
||||
|
@ -128,110 +128,110 @@ class ValidateDialog extends JDialog {
|
|||
// remove illegal characters
|
||||
model[i] = validateFilePath(model[i]);
|
||||
}
|
||||
|
||||
|
||||
// update view
|
||||
list.repaint();
|
||||
|
||||
|
||||
// switch icon
|
||||
continueAction.putValue(SMALL_ICON, getValue(SMALL_ICON));
|
||||
|
||||
|
||||
// disable this action
|
||||
setEnabled(false);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
private final Action continueAction = new AbstractAction("Continue", ResourceManager.getIcon("dialog.continue.invalid")) {
|
||||
|
||||
|
||||
@Override
|
||||
public void actionPerformed(ActionEvent e) {
|
||||
finish(false);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
private final Action cancelAction = new AbstractAction("Cancel", ResourceManager.getIcon("dialog.cancel")) {
|
||||
|
||||
|
||||
@Override
|
||||
public void actionPerformed(ActionEvent e) {
|
||||
finish(true);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
|
||||
|
||||
public static boolean validate(Component parent, List<File> source) {
|
||||
IndexView<File> invalidFilePaths = new IndexView<File>(source);
|
||||
|
||||
|
||||
for (int i = 0; i < source.size(); i++) {
|
||||
// invalid file names are also invalid file paths
|
||||
if (isInvalidFilePath(source.get(i)) && !isUnixFS()) {
|
||||
invalidFilePaths.addIndex(i);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// check if there is anything to do in the first place
|
||||
if (invalidFilePaths.isEmpty()) {
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
ValidateDialog dialog = new ValidateDialog(getWindow(parent), invalidFilePaths);
|
||||
dialog.setLocation(getOffsetLocation(dialog.getOwner()));
|
||||
|
||||
|
||||
// show and block
|
||||
dialog.setVisible(true);
|
||||
|
||||
|
||||
if (dialog.isCancelled()) {
|
||||
// no output
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
List<File> validatedFilePaths = dialog.getModel();
|
||||
|
||||
|
||||
// validate source list via index view
|
||||
for (int i = 0; i < invalidFilePaths.size(); i++) {
|
||||
invalidFilePaths.set(i, validatedFilePaths.get(i));
|
||||
}
|
||||
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
private static class IndexView<E> extends AbstractList<E> {
|
||||
|
||||
|
||||
private final List<Integer> mapping = new ArrayList<Integer>();
|
||||
|
||||
|
||||
private final List<E> source;
|
||||
|
||||
|
||||
|
||||
|
||||
public IndexView(List<E> source) {
|
||||
this.source = source;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
public boolean addIndex(int index) {
|
||||
return mapping.add(index);
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
@Override
|
||||
public E get(int index) {
|
||||
int sourceIndex = mapping.get(index);
|
||||
|
||||
|
||||
if (sourceIndex >= 0)
|
||||
return source.get(sourceIndex);
|
||||
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
@Override
|
||||
public E set(int index, E element) {
|
||||
return source.set(mapping.get(index), element);
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
@Override
|
||||
public int size() {
|
||||
return mapping.size();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
|
|
@ -22,55 +22,55 @@ import net.filebot.ResourceManager;
|
|||
|
||||
|
||||
public class ChecksumButton extends JToggleButton {
|
||||
|
||||
|
||||
private static final Icon contentArea = ResourceManager.getIcon("button.checksum");
|
||||
private static final Icon contentAreaSelected = ResourceManager.getIcon("button.checksum.selected");
|
||||
|
||||
|
||||
|
||||
public ChecksumButton(Action action) {
|
||||
super(action);
|
||||
|
||||
|
||||
setPreferredSize(new Dimension(max(contentAreaSelected.getIconWidth(), contentArea.getIconWidth()), max(contentAreaSelected.getIconHeight(), contentArea.getIconHeight())));
|
||||
setMinimumSize(getPreferredSize());
|
||||
setMaximumSize(getPreferredSize());
|
||||
|
||||
|
||||
setForeground(WHITE);
|
||||
setFont(new Font(DIALOG, PLAIN, 11));
|
||||
|
||||
|
||||
// as image button
|
||||
setBorderPainted(false);
|
||||
setContentAreaFilled(false);
|
||||
setFocusPainted(false);
|
||||
|
||||
|
||||
setEnabled(true);
|
||||
}
|
||||
|
||||
|
||||
|
||||
@Override
|
||||
public void setEnabled(boolean enabled) {
|
||||
super.setEnabled(enabled);
|
||||
|
||||
|
||||
// set appropriate cursor
|
||||
setCursor(getPredefinedCursor(enabled ? HAND_CURSOR : DEFAULT_CURSOR));
|
||||
}
|
||||
|
||||
|
||||
|
||||
@Override
|
||||
protected void paintComponent(Graphics g) {
|
||||
Graphics2D g2d = (Graphics2D) g;
|
||||
|
||||
|
||||
g2d.setRenderingHint(KEY_TEXT_ANTIALIASING, VALUE_TEXT_ANTIALIAS_ON);
|
||||
g2d.setRenderingHint(KEY_RENDERING, VALUE_RENDER_QUALITY);
|
||||
|
||||
|
||||
// paint background image in the center
|
||||
if (isSelected()) {
|
||||
contentAreaSelected.paintIcon(this, g2d, (int) round((getWidth() - contentAreaSelected.getIconWidth()) / (double) 2), (int) round((getHeight() - contentAreaSelected.getIconHeight()) / (double) 2));
|
||||
} else {
|
||||
contentArea.paintIcon(this, g2d, (int) round((getWidth() - contentArea.getIconWidth()) / (double) 2), (int) round((getHeight() - contentArea.getIconHeight()) / (double) 2));
|
||||
}
|
||||
|
||||
|
||||
Rectangle2D textBounds = g2d.getFontMetrics().getStringBounds(getText(), g2d);
|
||||
|
||||
|
||||
// draw text in the center
|
||||
g2d.drawString(getText(), round((getWidth() - textBounds.getWidth()) / 2) + 1, round(getHeight() / 2 - textBounds.getY() - textBounds.getHeight() / 2));
|
||||
}
|
||||
|
|
|
@ -17,132 +17,132 @@ import net.filebot.util.ExceptionUtilities;
|
|||
|
||||
|
||||
class ChecksumCell {
|
||||
|
||||
|
||||
private final String name;
|
||||
private final File root;
|
||||
|
||||
|
||||
private Map<HashType, String> hashes;
|
||||
private ChecksumComputationTask task;
|
||||
private Throwable error;
|
||||
|
||||
|
||||
|
||||
|
||||
public static enum State {
|
||||
PENDING,
|
||||
PROGRESS,
|
||||
READY,
|
||||
ERROR
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
public ChecksumCell(String name, File root, Map<HashType, String> hashes) {
|
||||
this.name = name;
|
||||
this.root = root;
|
||||
this.hashes = hashes;
|
||||
}
|
||||
|
||||
|
||||
|
||||
public ChecksumCell(String name, File root, ChecksumComputationTask task) {
|
||||
this.name = name;
|
||||
this.root = root;
|
||||
this.hashes = new EnumMap<HashType, String>(HashType.class);
|
||||
this.task = task;
|
||||
|
||||
|
||||
// forward property change events
|
||||
task.addPropertyChangeListener(taskListener);
|
||||
}
|
||||
|
||||
|
||||
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
|
||||
|
||||
public File getRoot() {
|
||||
return root;
|
||||
}
|
||||
|
||||
|
||||
|
||||
public String getChecksum(HashType hash) {
|
||||
return hashes.get(hash);
|
||||
}
|
||||
|
||||
|
||||
|
||||
public void putTask(ChecksumComputationTask computationTask) {
|
||||
if (task != null) {
|
||||
task.removePropertyChangeListener(taskListener);
|
||||
task.cancel(true);
|
||||
}
|
||||
|
||||
|
||||
task = computationTask;
|
||||
error = null;
|
||||
|
||||
|
||||
// forward property change events
|
||||
task.addPropertyChangeListener(taskListener);
|
||||
|
||||
|
||||
// state changed to PENDING
|
||||
pcs.firePropertyChange("state", null, getState());
|
||||
}
|
||||
|
||||
|
||||
|
||||
public ChecksumComputationTask getTask() {
|
||||
return task;
|
||||
}
|
||||
|
||||
|
||||
|
||||
public Throwable getError() {
|
||||
return error;
|
||||
}
|
||||
|
||||
|
||||
|
||||
public State getState() {
|
||||
if (task != null) {
|
||||
switch (task.getState()) {
|
||||
case PENDING:
|
||||
return State.PENDING;
|
||||
default:
|
||||
return State.PROGRESS;
|
||||
case PENDING:
|
||||
return State.PENDING;
|
||||
default:
|
||||
return State.PROGRESS;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
if (error != null) {
|
||||
return State.ERROR;
|
||||
}
|
||||
|
||||
|
||||
return State.READY;
|
||||
}
|
||||
|
||||
|
||||
|
||||
public void dispose() {
|
||||
// clear property change support
|
||||
for (PropertyChangeListener listener : pcs.getPropertyChangeListeners()) {
|
||||
pcs.removePropertyChangeListener(listener);
|
||||
}
|
||||
|
||||
|
||||
if (task != null) {
|
||||
task.removePropertyChangeListener(taskListener);
|
||||
task.cancel(true);
|
||||
}
|
||||
|
||||
|
||||
hashes = null;
|
||||
error = null;
|
||||
task = null;
|
||||
pcs = null;
|
||||
}
|
||||
|
||||
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return String.format("%s %s", name, hashes);
|
||||
}
|
||||
|
||||
|
||||
private final PropertyChangeListener taskListener = new PropertyChangeListener() {
|
||||
|
||||
|
||||
@Override
|
||||
public void propertyChange(PropertyChangeEvent evt) {
|
||||
if ("state".equals(evt.getPropertyName())) {
|
||||
if (evt.getNewValue() == StateValue.DONE)
|
||||
done(evt);
|
||||
|
||||
|
||||
// cell state changed because worker state changed
|
||||
pcs.firePropertyChange("state", null, getState());
|
||||
} else {
|
||||
|
@ -150,36 +150,36 @@ class ChecksumCell {
|
|||
pcs.firePropertyChange(evt.getPropertyName(), evt.getOldValue(), evt.getNewValue());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
protected void done(PropertyChangeEvent evt) {
|
||||
try {
|
||||
hashes.putAll(task.get());
|
||||
} catch (Exception e) {
|
||||
Throwable cause = ExceptionUtilities.getRootCause(e);
|
||||
|
||||
|
||||
// ignore cancellation
|
||||
if (cause instanceof CancellationException) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
error = cause;
|
||||
} finally {
|
||||
task = null;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
private SwingPropertyChangeSupport pcs = new SwingPropertyChangeSupport(this, true);
|
||||
|
||||
|
||||
|
||||
|
||||
public void addPropertyChangeListener(PropertyChangeListener listener) {
|
||||
pcs.addPropertyChangeListener(listener);
|
||||
}
|
||||
|
||||
|
||||
|
||||
public void removePropertyChangeListener(PropertyChangeListener listener) {
|
||||
pcs.removePropertyChangeListener(listener);
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
|
|
@ -18,37 +18,37 @@ import net.filebot.util.ExceptionUtilities;
|
|||
|
||||
|
||||
public class ChecksumCellRenderer extends DefaultTableCellRenderer {
|
||||
|
||||
|
||||
private final SwingWorkerCellRenderer progressRenderer = new SwingWorkerCellRenderer();
|
||||
|
||||
|
||||
private final Color verificationForeground = new Color(0x009900);
|
||||
|
||||
|
||||
|
||||
@Override
|
||||
public Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected, boolean hasFocus, int row, int column) {
|
||||
boolean pendingWorker = false;
|
||||
|
||||
|
||||
if (value instanceof SwingWorker) {
|
||||
if (((SwingWorker<?, ?>) value).getState() != StateValue.PENDING)
|
||||
return progressRenderer.getTableCellRendererComponent(table, value, isSelected, hasFocus, row, column);
|
||||
|
||||
|
||||
pendingWorker = true;
|
||||
}
|
||||
|
||||
|
||||
// ignore focus
|
||||
super.getTableCellRendererComponent(table, value, isSelected, false, row, column);
|
||||
|
||||
// check row state for ERROR
|
||||
|
||||
// check row state for ERROR
|
||||
boolean isError = (table.getValueAt(row, 0) == ChecksumRow.State.ERROR);
|
||||
|
||||
|
||||
// if row state is ERROR and if we are not selected use text color RED,
|
||||
// else use default table colors
|
||||
setForeground(isSelected ? table.getSelectionForeground() : isError ? Color.RED : isVerificationColumn(table, column) ? verificationForeground : table.getForeground());
|
||||
setBackground(isSelected ? table.getSelectionBackground() : table.getBackground());
|
||||
|
||||
|
||||
// use BOLD font on ERROR
|
||||
setFont(getFont().deriveFont(isError ? BOLD : PLAIN));
|
||||
|
||||
|
||||
if (pendingWorker) {
|
||||
setText("Pending...");
|
||||
} else if (value == null && !isSelected) {
|
||||
|
@ -58,16 +58,16 @@ public class ChecksumCellRenderer extends DefaultTableCellRenderer {
|
|||
} else if (value instanceof Throwable) {
|
||||
setText(ExceptionUtilities.getRootCauseMessage((Throwable) value));
|
||||
}
|
||||
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
|
||||
|
||||
private boolean isVerificationColumn(JTable table, int column) {
|
||||
ChecksumTableModel model = (ChecksumTableModel) table.getModel();
|
||||
int modelColumn = table.getColumnModel().getColumn(column).getModelIndex();
|
||||
|
||||
|
||||
return model.isVerificationColumn(modelColumn);
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
|
|
@ -53,7 +53,7 @@ class ChecksumComputationService {
|
|||
|
||||
/**
|
||||
* Get the number of active executors that are associated with this {@link ChecksumComputationService}.
|
||||
*
|
||||
*
|
||||
* @return number of active executors
|
||||
* @see {@link #newExecutor()}
|
||||
*/
|
||||
|
|
|
@ -21,17 +21,17 @@ import net.filebot.hash.HashType;
|
|||
|
||||
|
||||
class ChecksumRow {
|
||||
|
||||
|
||||
private String name;
|
||||
|
||||
|
||||
private Map<File, ChecksumCell> hashes = new HashMap<File, ChecksumCell>(4);
|
||||
private State state = State.UNKNOWN;
|
||||
|
||||
|
||||
/**
|
||||
* Checksum that is embedded in the file name (e.g. Test[49A93C5F].txt)
|
||||
*/
|
||||
private String embeddedChecksum;
|
||||
|
||||
|
||||
|
||||
public static enum State {
|
||||
UNKNOWN,
|
||||
|
@ -39,72 +39,72 @@ class ChecksumRow {
|
|||
WARNING,
|
||||
ERROR
|
||||
}
|
||||
|
||||
|
||||
|
||||
public ChecksumRow(String name) {
|
||||
this.name = name;
|
||||
this.embeddedChecksum = getEmbeddedChecksum(name);
|
||||
}
|
||||
|
||||
|
||||
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
|
||||
|
||||
public State getState() {
|
||||
return state;
|
||||
}
|
||||
|
||||
|
||||
|
||||
protected void setState(State newValue) {
|
||||
State oldValue = this.state;
|
||||
this.state = newValue;
|
||||
|
||||
|
||||
pcs.firePropertyChange("state", oldValue, newValue);
|
||||
}
|
||||
|
||||
|
||||
|
||||
public ChecksumCell getChecksum(File root) {
|
||||
return hashes.get(root);
|
||||
}
|
||||
|
||||
|
||||
|
||||
public Collection<ChecksumCell> values() {
|
||||
return Collections.unmodifiableCollection(hashes.values());
|
||||
}
|
||||
|
||||
|
||||
|
||||
public ChecksumCell put(ChecksumCell cell) {
|
||||
ChecksumCell old = hashes.put(cell.getRoot(), cell);
|
||||
|
||||
|
||||
// update state immediately, don't fire property change
|
||||
state = getState(hashes.values());
|
||||
|
||||
|
||||
// keep state up-to-date
|
||||
cell.addPropertyChangeListener(updateStateListener);
|
||||
|
||||
|
||||
return old;
|
||||
}
|
||||
|
||||
|
||||
|
||||
public void dispose() {
|
||||
// clear property change support
|
||||
for (PropertyChangeListener listener : pcs.getPropertyChangeListeners()) {
|
||||
pcs.removePropertyChangeListener(listener);
|
||||
}
|
||||
|
||||
|
||||
for (ChecksumCell cell : hashes.values()) {
|
||||
cell.dispose();
|
||||
}
|
||||
|
||||
|
||||
name = null;
|
||||
embeddedChecksum = null;
|
||||
hashes = null;
|
||||
state = null;
|
||||
pcs = null;
|
||||
}
|
||||
|
||||
|
||||
|
||||
protected State getState(Collection<ChecksumCell> cells) {
|
||||
// check states before we bother comparing the hash values
|
||||
|
@ -117,29 +117,29 @@ class ChecksumRow {
|
|||
return State.UNKNOWN;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// compare hash values
|
||||
Set<String> checksumSet = new HashSet<String>(2);
|
||||
Set<State> verdictSet = EnumSet.noneOf(State.class);
|
||||
|
||||
|
||||
for (HashType type : HashType.values()) {
|
||||
checksumSet.clear();
|
||||
|
||||
|
||||
for (ChecksumCell cell : cells) {
|
||||
String checksum = cell.getChecksum(type);
|
||||
|
||||
|
||||
if (checksum != null) {
|
||||
checksumSet.add(checksum.toLowerCase());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
verdictSet.add(getVerdict(checksumSet));
|
||||
}
|
||||
|
||||
// ERROR > WARNING > OK > UNKOWN
|
||||
|
||||
// ERROR > WARNING > OK > UNKOWN
|
||||
return Collections.max(verdictSet);
|
||||
}
|
||||
|
||||
|
||||
|
||||
protected State getVerdict(Set<String> checksumSet) {
|
||||
if (checksumSet.size() < 1) {
|
||||
|
@ -152,42 +152,43 @@ class ChecksumRow {
|
|||
// all hashes match
|
||||
if (embeddedChecksum != null) {
|
||||
String checksum = checksumSet.iterator().next();
|
||||
|
||||
|
||||
if (checksum.length() == embeddedChecksum.length() && !checksum.equalsIgnoreCase(embeddedChecksum)) {
|
||||
return State.WARNING;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
return State.OK;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return String.format("%s %s %s", state, name, hashes);
|
||||
}
|
||||
|
||||
|
||||
|
||||
private final PropertyChangeListener updateStateListener = new PropertyChangeListener() {
|
||||
|
||||
|
||||
@Override
|
||||
public void propertyChange(PropertyChangeEvent evt) {
|
||||
if ("state".equals(evt.getPropertyName())) {
|
||||
setState(getState(hashes.values()));
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
private SwingPropertyChangeSupport pcs = new SwingPropertyChangeSupport(this, true);
|
||||
|
||||
|
||||
|
||||
public void addPropertyChangeListener(PropertyChangeListener listener) {
|
||||
pcs.addPropertyChangeListener(listener);
|
||||
}
|
||||
|
||||
|
||||
|
||||
public void removePropertyChangeListener(PropertyChangeListener listener) {
|
||||
pcs.removePropertyChangeListener(listener);
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
|
|
@ -16,46 +16,46 @@ import net.filebot.util.ui.SwingUI.DragDropRowTableUI;
|
|||
|
||||
|
||||
class ChecksumTable extends JTable {
|
||||
|
||||
|
||||
public ChecksumTable() {
|
||||
setFillsViewportHeight(true);
|
||||
setAutoCreateRowSorter(true);
|
||||
setAutoCreateColumnsFromModel(true);
|
||||
setAutoResizeMode(AUTO_RESIZE_SUBSEQUENT_COLUMNS);
|
||||
|
||||
|
||||
setSelectionMode(ListSelectionModel.MULTIPLE_INTERVAL_SELECTION);
|
||||
|
||||
|
||||
setRowHeight(20);
|
||||
|
||||
|
||||
setDragEnabled(true);
|
||||
setUI(new DragDropRowTableUI());
|
||||
|
||||
|
||||
// force white background (e.g. gtk-laf default table background is gray)
|
||||
setBackground(Color.WHITE);
|
||||
|
||||
|
||||
// highlight CRC32 patterns in filenames in green and with smaller font-size
|
||||
setDefaultRenderer(String.class, new HighlightPatternCellRenderer(EMBEDDED_CHECKSUM));
|
||||
setDefaultRenderer(ChecksumRow.State.class, new StateIconCellRenderer());
|
||||
setDefaultRenderer(ChecksumCell.class, new ChecksumCellRenderer());
|
||||
}
|
||||
|
||||
|
||||
|
||||
@Override
|
||||
protected ChecksumTableModel createDefaultDataModel() {
|
||||
return new ChecksumTableModel();
|
||||
}
|
||||
|
||||
|
||||
|
||||
@Override
|
||||
protected JTableHeader createDefaultTableHeader() {
|
||||
return new JTableHeader(columnModel) {
|
||||
|
||||
|
||||
@Override
|
||||
public String getToolTipText(MouseEvent evt) {
|
||||
try {
|
||||
int columnIndex = columnModel.getColumnIndexAtX(evt.getX());
|
||||
int modelIndex = columnModel.getColumn(columnIndex).getModelIndex();
|
||||
|
||||
|
||||
// display column root of checksum column
|
||||
return getModel().getColumnRoot(modelIndex).getPath();
|
||||
} catch (Exception e) {
|
||||
|
@ -65,21 +65,21 @@ class ChecksumTable extends JTable {
|
|||
};
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
|
||||
@Override
|
||||
public ChecksumTableModel getModel() {
|
||||
return (ChecksumTableModel) super.getModel();
|
||||
}
|
||||
|
||||
|
||||
|
||||
@Override
|
||||
public void createDefaultColumnsFromModel() {
|
||||
super.createDefaultColumnsFromModel();
|
||||
|
||||
|
||||
for (int i = 0; i < getColumnCount(); i++) {
|
||||
TableColumn column = getColumnModel().getColumn(i);
|
||||
|
||||
|
||||
if (i == 0) {
|
||||
column.setPreferredWidth(45);
|
||||
} else if (i == 1) {
|
||||
|
@ -89,5 +89,5 @@ class ChecksumTable extends JTable {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
|
|
@ -13,32 +13,32 @@ import net.filebot.util.FileUtilities;
|
|||
|
||||
|
||||
class ChecksumTableExportHandler extends TextFileExportHandler {
|
||||
|
||||
|
||||
private final ChecksumTableModel model;
|
||||
|
||||
|
||||
|
||||
public ChecksumTableExportHandler(ChecksumTableModel model) {
|
||||
this.model = model;
|
||||
}
|
||||
|
||||
|
||||
|
||||
@Override
|
||||
public boolean canExport() {
|
||||
return model.getRowCount() > 0 && defaultColumn() != null;
|
||||
}
|
||||
|
||||
|
||||
|
||||
@Override
|
||||
public void export(PrintWriter out) {
|
||||
export(new VerificationFileWriter(out, model.getHashType().getFormat(), "UTF-8"), defaultColumn(), model.getHashType());
|
||||
}
|
||||
|
||||
|
||||
|
||||
@Override
|
||||
public String getDefaultFileName() {
|
||||
return getDefaultFileName(defaultColumn());
|
||||
}
|
||||
|
||||
|
||||
|
||||
protected File defaultColumn() {
|
||||
// select first column that is not a verification file column
|
||||
|
@ -46,46 +46,46 @@ class ChecksumTableExportHandler extends TextFileExportHandler {
|
|||
if (root.isDirectory())
|
||||
return root;
|
||||
}
|
||||
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
|
||||
public void export(File file, File column) throws IOException {
|
||||
VerificationFileWriter writer = new VerificationFileWriter(file, model.getHashType().getFormat(), "UTF-8");
|
||||
|
||||
|
||||
try {
|
||||
export(writer, column, model.getHashType());
|
||||
} finally {
|
||||
writer.close();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
public void export(VerificationFileWriter out, File column, HashType type) {
|
||||
for (ChecksumRow row : model.rows()) {
|
||||
ChecksumCell cell = row.getChecksum(column);
|
||||
|
||||
|
||||
if (cell != null) {
|
||||
String hash = cell.getChecksum(type);
|
||||
|
||||
|
||||
if (hash != null) {
|
||||
out.write(cell.getName(), hash);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
public String getDefaultFileName(File column) {
|
||||
StringBuilder sb = new StringBuilder();
|
||||
|
||||
|
||||
// append file name
|
||||
sb.append(column != null ? FileUtilities.getName(column) : "name");
|
||||
|
||||
|
||||
// append file extension
|
||||
sb.append('.').append(model.getHashType().name().toLowerCase());
|
||||
|
||||
|
||||
return sb.toString();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -242,6 +242,7 @@ class ChecksumTableModel extends AbstractTableModel {
|
|||
|
||||
private final PropertyChangeListener progressListener = new PropertyChangeListener() {
|
||||
|
||||
@Override
|
||||
public void propertyChange(PropertyChangeEvent evt) {
|
||||
ChecksumCell cell = (ChecksumCell) evt.getSource();
|
||||
|
||||
|
|
|
@ -17,63 +17,63 @@ import net.filebot.ui.sfv.ChecksumRow.State;
|
|||
* DefaultTableCellRenderer with highlighting of text patterns.
|
||||
*/
|
||||
class HighlightPatternCellRenderer extends DefaultTableCellRenderer {
|
||||
|
||||
|
||||
private final Pattern pattern;
|
||||
|
||||
|
||||
|
||||
|
||||
public HighlightPatternCellRenderer(Pattern pattern) {
|
||||
this.pattern = pattern;
|
||||
}
|
||||
|
||||
|
||||
|
||||
@Override
|
||||
public Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected, boolean hasFocus, int row, int column) {
|
||||
super.getTableCellRendererComponent(table, value, isSelected, false, row, column);
|
||||
|
||||
|
||||
// check for error or warning
|
||||
boolean isError = (EnumSet.of(State.ERROR, State.WARNING).contains(table.getValueAt(row, 0)));
|
||||
|
||||
|
||||
// highlight patterns by using a smaller font-size and changing the font-color to a dark green
|
||||
// do not change the font-color if cell is selected, because that would look ugly (imagine green text on blue background ...)
|
||||
Matcher matcher = pattern.matcher(value.toString());
|
||||
|
||||
|
||||
// use no-break, because we really don't want line-wrapping in our table cells
|
||||
StringBuffer htmlText = new StringBuffer("<html><nobr>");
|
||||
|
||||
|
||||
while (matcher.find()) {
|
||||
matcher.appendReplacement(htmlText, createReplacement(isSelected ? null : (isError ? "red" : "#009900"), "smaller", isError ? "bold" : null));
|
||||
}
|
||||
|
||||
|
||||
matcher.appendTail(htmlText);
|
||||
|
||||
|
||||
htmlText.append("</nobr></html>");
|
||||
|
||||
|
||||
setText(htmlText.toString());
|
||||
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
|
||||
|
||||
protected String createReplacement(String cssColor, String cssFontSize, String cssFontWeight) {
|
||||
// build replacement string like
|
||||
// e.g. <span style='font-size: smaller; color: #009900;'>$0</span>
|
||||
StringBuilder replacement = new StringBuilder(60);
|
||||
|
||||
|
||||
replacement.append("<span style='");
|
||||
|
||||
|
||||
if (cssColor != null) {
|
||||
replacement.append("color:").append(cssColor).append(';');
|
||||
}
|
||||
|
||||
|
||||
if (cssFontSize != null) {
|
||||
replacement.append("font-size:").append(cssFontSize).append(';');
|
||||
}
|
||||
|
||||
|
||||
if (cssFontWeight != null) {
|
||||
replacement.append("font-weight:").append(cssFontWeight).append(';');
|
||||
}
|
||||
|
||||
|
||||
return replacement.append("'>$0</span>").toString();
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
|
|
@ -10,22 +10,22 @@ import net.filebot.ui.PanelBuilder;
|
|||
|
||||
|
||||
public class SfvPanelBuilder implements PanelBuilder {
|
||||
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return "SFV";
|
||||
}
|
||||
|
||||
|
||||
|
||||
@Override
|
||||
public Icon getIcon() {
|
||||
return ResourceManager.getIcon("panel.sfv");
|
||||
}
|
||||
|
||||
|
||||
|
||||
@Override
|
||||
public JComponent create() {
|
||||
return new SfvPanel();
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
|
|
@ -16,28 +16,28 @@ import net.filebot.ui.sfv.ChecksumRow.State;
|
|||
|
||||
|
||||
class StateIconCellRenderer extends DefaultTableCellRenderer {
|
||||
|
||||
|
||||
private final Map<State, Icon> icons = new EnumMap<State, Icon>(State.class);
|
||||
|
||||
|
||||
|
||||
|
||||
public StateIconCellRenderer() {
|
||||
icons.put(State.UNKNOWN, ResourceManager.getIcon("status.unknown"));
|
||||
icons.put(State.OK, ResourceManager.getIcon("status.ok"));
|
||||
icons.put(State.WARNING, ResourceManager.getIcon("status.warning"));
|
||||
icons.put(State.ERROR, ResourceManager.getIcon("status.error"));
|
||||
|
||||
|
||||
setVerticalAlignment(SwingConstants.CENTER);
|
||||
setHorizontalAlignment(SwingConstants.CENTER);
|
||||
}
|
||||
|
||||
|
||||
|
||||
@Override
|
||||
public Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected, boolean hasFocus, int row, int column) {
|
||||
super.getTableCellRendererComponent(table, null, isSelected, false, row, column);
|
||||
|
||||
|
||||
setIcon(icons.get(value));
|
||||
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
|
|
@ -15,31 +15,32 @@ import javax.swing.table.TableCellRenderer;
|
|||
|
||||
|
||||
class SwingWorkerCellRenderer extends JPanel implements TableCellRenderer {
|
||||
|
||||
|
||||
private final JProgressBar progressBar = new JProgressBar(0, 100);
|
||||
|
||||
|
||||
|
||||
public SwingWorkerCellRenderer() {
|
||||
super(new BorderLayout());
|
||||
|
||||
// set margin for progress bar on parent component,
|
||||
|
||||
// set margin for progress bar on parent component,
|
||||
// because setting it on the progress bar itself does not work (border size is not respected in the paint method)
|
||||
setBorder(new EmptyBorder(2, 2, 2, 2));
|
||||
|
||||
|
||||
progressBar.setStringPainted(true);
|
||||
|
||||
|
||||
add(progressBar, BorderLayout.CENTER);
|
||||
}
|
||||
|
||||
|
||||
|
||||
@Override
|
||||
public Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected, boolean hasFocus, int row, int column) {
|
||||
setBackground(isSelected ? table.getSelectionBackground() : table.getBackground());
|
||||
|
||||
|
||||
progressBar.setValue(((SwingWorker<?, ?>) value).getProgress());
|
||||
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Overridden for performance reasons.
|
||||
|
@ -47,7 +48,7 @@ class SwingWorkerCellRenderer extends JPanel implements TableCellRenderer {
|
|||
@Override
|
||||
public void repaint(long tm, int x, int y, int width, int height) {
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Overridden for performance reasons.
|
||||
|
@ -55,7 +56,7 @@ class SwingWorkerCellRenderer extends JPanel implements TableCellRenderer {
|
|||
@Override
|
||||
public void repaint(Rectangle r) {
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Overridden for performance reasons.
|
||||
|
@ -63,7 +64,7 @@ class SwingWorkerCellRenderer extends JPanel implements TableCellRenderer {
|
|||
@Override
|
||||
public void repaint() {
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Overridden for performance reasons.
|
||||
|
@ -71,5 +72,5 @@ class SwingWorkerCellRenderer extends JPanel implements TableCellRenderer {
|
|||
@Override
|
||||
public void firePropertyChange(String propertyName, boolean oldValue, boolean newValue) {
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
|
|
@ -17,105 +17,107 @@ import net.miginfocom.swing.MigLayout;
|
|||
|
||||
|
||||
class TotalProgressPanel extends JComponent {
|
||||
|
||||
|
||||
private final JProgressBar progressBar = new JProgressBar(0, 0);
|
||||
|
||||
|
||||
private final int millisToSetVisible = 200;
|
||||
|
||||
|
||||
|
||||
|
||||
public TotalProgressPanel(ChecksumComputationService computationService) {
|
||||
setLayout(new MigLayout("insets 1px"));
|
||||
|
||||
|
||||
setBorder(new TitledBorder("Total Progress"));
|
||||
|
||||
|
||||
// invisible by default
|
||||
setVisible(false);
|
||||
|
||||
|
||||
progressBar.setStringPainted(true);
|
||||
|
||||
|
||||
add(progressBar, "growx");
|
||||
|
||||
|
||||
computationService.addPropertyChangeListener(progressListener);
|
||||
}
|
||||
|
||||
|
||||
private final PropertyChangeListener progressListener = new PropertyChangeListener() {
|
||||
|
||||
|
||||
private static final String SHOW = "show";
|
||||
private static final String HIDE = "hide";
|
||||
|
||||
|
||||
private final DelayedToggle delayed = new DelayedToggle();
|
||||
|
||||
|
||||
|
||||
|
||||
@Override
|
||||
public void propertyChange(PropertyChangeEvent evt) {
|
||||
final int completedTaskCount = getComputationService(evt).getCompletedTaskCount();
|
||||
final int totalTaskCount = getComputationService(evt).getTotalTaskCount();
|
||||
|
||||
|
||||
// invoke on EDT
|
||||
SwingUtilities.invokeLater(new Runnable() {
|
||||
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
|
||||
|
||||
if (completedTaskCount == totalTaskCount) {
|
||||
// delayed hide on reset, immediate hide on finish
|
||||
delayed.toggle(HIDE, totalTaskCount == 0 ? millisToSetVisible : 0, visibilityActionHandler);
|
||||
} else if (totalTaskCount != 0) {
|
||||
delayed.toggle(SHOW, millisToSetVisible, visibilityActionHandler);
|
||||
}
|
||||
|
||||
|
||||
if (totalTaskCount != 0) {
|
||||
progressBar.setValue(completedTaskCount);
|
||||
progressBar.setMaximum(totalTaskCount);
|
||||
|
||||
|
||||
progressBar.setString(String.format("%d / %d", completedTaskCount, totalTaskCount));
|
||||
}
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
|
||||
private ChecksumComputationService getComputationService(PropertyChangeEvent evt) {
|
||||
return ((ChecksumComputationService) evt.getSource());
|
||||
}
|
||||
|
||||
|
||||
private final ActionListener visibilityActionHandler = new ActionListener() {
|
||||
|
||||
|
||||
@Override
|
||||
public void actionPerformed(ActionEvent e) {
|
||||
setVisible(e.getActionCommand() == SHOW);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
};
|
||||
|
||||
|
||||
|
||||
|
||||
protected static class DelayedToggle {
|
||||
|
||||
|
||||
private Timer timer = null;
|
||||
|
||||
|
||||
|
||||
|
||||
public void toggle(String action, int delay, final ActionListener actionHandler) {
|
||||
if (timer != null) {
|
||||
if (action.equals(timer.getActionCommand())) {
|
||||
// action has not changed, don't stop existing timer
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
timer.stop();
|
||||
}
|
||||
|
||||
|
||||
timer = new Timer(delay, new ActionListener() {
|
||||
|
||||
|
||||
@Override
|
||||
public void actionPerformed(ActionEvent e) {
|
||||
actionHandler.actionPerformed(e);
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
timer.setActionCommand(action);
|
||||
timer.setRepeats(false);
|
||||
timer.start();
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
|
|
@ -21,29 +21,29 @@ import net.filebot.vfs.MemoryFile;
|
|||
|
||||
|
||||
class MemoryFileListExportHandler implements TransferableExportHandler, ClipboardHandler {
|
||||
|
||||
|
||||
public boolean canExport(JComponent component) {
|
||||
JList list = (JList) component;
|
||||
|
||||
|
||||
// can't export anything, if nothing is selected
|
||||
return !list.isSelectionEmpty();
|
||||
}
|
||||
|
||||
|
||||
|
||||
public List<MemoryFile> export(JComponent component) {
|
||||
JList list = (JList) component;
|
||||
|
||||
|
||||
// get selected values
|
||||
final Object[] selection = list.getSelectedValues();
|
||||
|
||||
|
||||
// as file list
|
||||
return new AbstractList<MemoryFile>() {
|
||||
|
||||
|
||||
@Override
|
||||
public MemoryFile get(int index) {
|
||||
return (MemoryFile) selection[index];
|
||||
}
|
||||
|
||||
|
||||
|
||||
@Override
|
||||
public int size() {
|
||||
|
@ -51,35 +51,35 @@ class MemoryFileListExportHandler implements TransferableExportHandler, Clipboar
|
|||
}
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
|
||||
@Override
|
||||
public int getSourceActions(JComponent component) {
|
||||
return canExport(component) ? TransferHandler.COPY_OR_MOVE : TransferHandler.NONE;
|
||||
}
|
||||
|
||||
|
||||
|
||||
@Override
|
||||
public Transferable createTransferable(JComponent component) {
|
||||
Map<String, ByteBuffer> vfs = new HashMap<String, ByteBuffer>();
|
||||
|
||||
|
||||
for (MemoryFile file : export(component)) {
|
||||
vfs.put(file.getName(), file.getData());
|
||||
}
|
||||
|
||||
|
||||
return new ByteBufferTransferable(vfs);
|
||||
}
|
||||
|
||||
|
||||
|
||||
@Override
|
||||
public void exportToClipboard(JComponent component, Clipboard clip, int action) {
|
||||
clip.setContents(createTransferable(component), null);
|
||||
}
|
||||
|
||||
|
||||
|
||||
@Override
|
||||
public void exportDone(JComponent source, Transferable data, int action) {
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue