+ Script expressions in ExpressionFormat will now be evaluated in a secure sandbox
+ "preserve Extension" can be enabled/disabled in RenameModel * fixed rename list SelectionModel performance issue * create package for ui-independant Hash* stuff
This commit is contained in:
parent
9e60d2c5dd
commit
ca032f3b56
|
@ -106,7 +106,7 @@
|
|||
|
||||
<!--
|
||||
Mandatory Default Cache configuration. These settings will be applied to caches
|
||||
created programmtically using CacheManager.add(String cacheName).
|
||||
created pragmatically using CacheManager.add(String cacheName).
|
||||
-->
|
||||
<defaultCache
|
||||
maxElementsInMemory="100"
|
||||
|
|
|
@ -23,9 +23,6 @@ public class ArgumentBean {
|
|||
@Option(name = "-clear", usage = "Clear history and settings")
|
||||
private boolean clear = false;
|
||||
|
||||
@Option(name = "--analyze", usage = "Open file in 'Analyze' panel", metaVar = "<file>")
|
||||
private boolean analyze;
|
||||
|
||||
@Option(name = "--sfv", usage = "Open file in 'SFV' panel", metaVar = "<file>")
|
||||
private boolean sfv;
|
||||
|
||||
|
@ -48,11 +45,6 @@ public class ArgumentBean {
|
|||
}
|
||||
|
||||
|
||||
public boolean analyze() {
|
||||
return analyze;
|
||||
}
|
||||
|
||||
|
||||
public List<File> arguments() {
|
||||
return arguments;
|
||||
}
|
||||
|
|
|
@ -4,6 +4,12 @@ package net.sourceforge.filebot;
|
|||
|
||||
import static javax.swing.JFrame.EXIT_ON_CLOSE;
|
||||
|
||||
import java.security.CodeSource;
|
||||
import java.security.Permission;
|
||||
import java.security.PermissionCollection;
|
||||
import java.security.Permissions;
|
||||
import java.security.Policy;
|
||||
import java.security.ProtectionDomain;
|
||||
import java.util.logging.ConsoleHandler;
|
||||
import java.util.logging.Level;
|
||||
import java.util.logging.Logger;
|
||||
|
@ -12,10 +18,10 @@ import javax.swing.JFrame;
|
|||
import javax.swing.SwingUtilities;
|
||||
import javax.swing.UIManager;
|
||||
|
||||
import net.sourceforge.filebot.format.ExpressionFormat;
|
||||
import net.sourceforge.filebot.ui.MainFrame;
|
||||
import net.sourceforge.filebot.ui.NotificationLoggingHandler;
|
||||
import net.sourceforge.filebot.ui.SinglePanelFrame;
|
||||
import net.sourceforge.filebot.ui.panel.analyze.AnalyzePanelBuilder;
|
||||
import net.sourceforge.filebot.ui.panel.sfv.SfvPanelBuilder;
|
||||
|
||||
import org.kohsuke.args4j.CmdLineException;
|
||||
|
@ -45,6 +51,7 @@ public class Main {
|
|||
|
||||
initializeLogging();
|
||||
initializeSettings();
|
||||
initializeSecurityManager();
|
||||
|
||||
try {
|
||||
// use native laf an all platforms
|
||||
|
@ -59,19 +66,18 @@ public class Main {
|
|||
public void run() {
|
||||
JFrame frame;
|
||||
|
||||
if (argumentBean.analyze()) {
|
||||
frame = new SinglePanelFrame(new AnalyzePanelBuilder()).publish(argumentBean.transferable());
|
||||
} else if (argumentBean.sfv()) {
|
||||
if (argumentBean.sfv()) {
|
||||
// sfv frame
|
||||
frame = new SinglePanelFrame(new SfvPanelBuilder()).publish(argumentBean.transferable());
|
||||
} else {
|
||||
// default
|
||||
// default frame
|
||||
frame = new MainFrame();
|
||||
}
|
||||
|
||||
frame.setLocationByPlatform(true);
|
||||
frame.setDefaultCloseOperation(EXIT_ON_CLOSE);
|
||||
|
||||
// start
|
||||
// start application
|
||||
frame.setVisible(true);
|
||||
}
|
||||
});
|
||||
|
@ -95,11 +101,51 @@ public class Main {
|
|||
}
|
||||
|
||||
|
||||
/**
|
||||
* Preset the default thetvdb.apikey.
|
||||
*/
|
||||
private static void initializeSettings() {
|
||||
Settings.userRoot().putDefault("thetvdb.apikey", "58B4AA94C59AD656");
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Initialize default SecurityManager and grant all permissions via security policy.
|
||||
* Initialization is required in order to run {@link ExpressionFormat} in a secure sandbox.
|
||||
*/
|
||||
private static void initializeSecurityManager() {
|
||||
try {
|
||||
// initialize security policy used by the default security manager
|
||||
// because default the security policy is very restrictive (e.g. no FilePermission)
|
||||
Policy.setPolicy(new Policy() {
|
||||
|
||||
@Override
|
||||
public boolean implies(ProtectionDomain domain, Permission permission) {
|
||||
// all permissions
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public PermissionCollection getPermissions(CodeSource codesource) {
|
||||
// VisualVM can't connect if this method does return
|
||||
// a checked immutable PermissionCollection
|
||||
return new Permissions();
|
||||
}
|
||||
});
|
||||
|
||||
// set default security manager
|
||||
System.setSecurityManager(new SecurityManager());
|
||||
} catch (Exception e) {
|
||||
// security manager was probably set via system property
|
||||
Logger.getLogger(Main.class.getName()).log(Level.WARNING, e.toString(), e);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Parse command line arguments.
|
||||
*/
|
||||
private static ArgumentBean initializeArgumentBean(String... args) throws CmdLineException {
|
||||
ArgumentBean argumentBean = new ArgumentBean();
|
||||
|
||||
|
@ -109,6 +155,9 @@ public class Main {
|
|||
}
|
||||
|
||||
|
||||
/**
|
||||
* Print command line argument usage.
|
||||
*/
|
||||
private static void printUsage(ArgumentBean argumentBean) {
|
||||
System.out.println("Options:");
|
||||
|
||||
|
|
|
@ -2,6 +2,7 @@
|
|||
package net.sourceforge.filebot.format;
|
||||
|
||||
|
||||
import static net.sourceforge.filebot.FileBotUtilities.SFV_FILES;
|
||||
import static net.sourceforge.filebot.format.Define.undefined;
|
||||
|
||||
import java.io.File;
|
||||
|
@ -9,15 +10,21 @@ import java.io.FileInputStream;
|
|||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.util.Scanner;
|
||||
import java.util.Map.Entry;
|
||||
import java.util.logging.Level;
|
||||
import java.util.logging.Logger;
|
||||
import java.util.zip.CRC32;
|
||||
|
||||
import net.sf.ehcache.Cache;
|
||||
import net.sf.ehcache.CacheManager;
|
||||
import net.sf.ehcache.Element;
|
||||
import net.sourceforge.filebot.FileBotUtilities;
|
||||
import net.sourceforge.filebot.hash.IllegalSyntaxException;
|
||||
import net.sourceforge.filebot.hash.SfvFileScanner;
|
||||
import net.sourceforge.filebot.mediainfo.MediaInfo;
|
||||
import net.sourceforge.filebot.mediainfo.MediaInfo.StreamKind;
|
||||
import net.sourceforge.filebot.web.Episode;
|
||||
import net.sourceforge.tuned.FileUtilities;
|
||||
|
||||
|
||||
public class EpisodeFormatBindingBean {
|
||||
|
@ -78,6 +85,16 @@ public class EpisodeFormatBindingBean {
|
|||
}
|
||||
|
||||
|
||||
@Define("cf")
|
||||
public String getContainerFormat() {
|
||||
// container format extension
|
||||
String extensions = getMediaInfo(StreamKind.General, 0, "Codec/Extensions");
|
||||
|
||||
// get first token
|
||||
return new Scanner(extensions).next();
|
||||
}
|
||||
|
||||
|
||||
@Define("hi")
|
||||
public String getHeightAndInterlacement() {
|
||||
String height = getMediaInfo(StreamKind.Video, 0, "Height");
|
||||
|
@ -91,15 +108,6 @@ public class EpisodeFormatBindingBean {
|
|||
}
|
||||
|
||||
|
||||
@Define("ext")
|
||||
public String getContainerExtension() {
|
||||
String extensions = getMediaInfo(StreamKind.General, 0, "Codec/Extensions");
|
||||
|
||||
// get first token
|
||||
return new Scanner(extensions).next();
|
||||
}
|
||||
|
||||
|
||||
@Define("resolution")
|
||||
public String getVideoResolution() {
|
||||
String width = getMediaInfo(StreamKind.Video, 0, "Width");
|
||||
|
@ -117,11 +125,16 @@ public class EpisodeFormatBindingBean {
|
|||
public String getCRC32() throws IOException, InterruptedException {
|
||||
if (mediaFile != null) {
|
||||
// try to get checksum from file name
|
||||
String embeddedChecksum = FileBotUtilities.getEmbeddedChecksum(mediaFile.getName());
|
||||
String checksum = FileBotUtilities.getEmbeddedChecksum(mediaFile.getName());
|
||||
|
||||
if (embeddedChecksum != null) {
|
||||
return embeddedChecksum;
|
||||
}
|
||||
if (checksum != null)
|
||||
return checksum;
|
||||
|
||||
// try to get checksum from sfv file
|
||||
checksum = getChecksumFromSfvFile(mediaFile);
|
||||
|
||||
if (checksum != null)
|
||||
return checksum;
|
||||
|
||||
// calculate checksum from file
|
||||
return crc32(mediaFile);
|
||||
|
@ -131,6 +144,13 @@ public class EpisodeFormatBindingBean {
|
|||
}
|
||||
|
||||
|
||||
@Define("ext")
|
||||
public String getContainerExtension() {
|
||||
// file extension
|
||||
return FileUtilities.getExtension(mediaFile);
|
||||
}
|
||||
|
||||
|
||||
@Define("general")
|
||||
public Object getGeneralMediaInfo() {
|
||||
return new AssociativeScriptObject(getMediaInfo().snapshot(StreamKind.General, 0));
|
||||
|
@ -161,11 +181,13 @@ public class EpisodeFormatBindingBean {
|
|||
}
|
||||
|
||||
|
||||
@Define("episode")
|
||||
public Episode getEpisode() {
|
||||
return episode;
|
||||
}
|
||||
|
||||
|
||||
@Define("file")
|
||||
public File getMediaFile() {
|
||||
return mediaFile;
|
||||
}
|
||||
|
@ -201,6 +223,33 @@ public class EpisodeFormatBindingBean {
|
|||
}
|
||||
|
||||
|
||||
private String getChecksumFromSfvFile(File mediaFile) throws IOException {
|
||||
File folder = mediaFile.getParentFile();
|
||||
|
||||
for (File sfvFile : folder.listFiles(SFV_FILES)) {
|
||||
SfvFileScanner scanner = new SfvFileScanner(sfvFile);
|
||||
|
||||
try {
|
||||
while (scanner.hasNext()) {
|
||||
try {
|
||||
Entry<File, String> entry = scanner.next();
|
||||
|
||||
if (mediaFile.getName().equals(entry.getKey().getPath())) {
|
||||
return entry.getValue();
|
||||
}
|
||||
} catch (IllegalSyntaxException e) {
|
||||
Logger.getLogger("global").log(Level.WARNING, e.getMessage());
|
||||
}
|
||||
}
|
||||
} finally {
|
||||
scanner.close();
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
private String crc32(File file) throws IOException, InterruptedException {
|
||||
// try to get checksum from cache
|
||||
Cache cache = CacheManager.getInstance().getCache("checksum");
|
||||
|
|
|
@ -16,16 +16,16 @@ import net.sourceforge.tuned.ExceptionUtilities;
|
|||
|
||||
public class ExpressionBindings extends AbstractMap<String, Object> implements Bindings {
|
||||
|
||||
protected final Object bean;
|
||||
protected final Object bindingBean;
|
||||
|
||||
protected final Map<String, Method> bindings = new HashMap<String, Method>();
|
||||
|
||||
|
||||
public ExpressionBindings(Object bindingBean) {
|
||||
bean = bindingBean;
|
||||
this.bindingBean = bindingBean;
|
||||
|
||||
// get method bindings
|
||||
for (Method method : bean.getClass().getMethods()) {
|
||||
for (Method method : bindingBean.getClass().getMethods()) {
|
||||
Define define = method.getAnnotation(Define.class);
|
||||
|
||||
if (define != null) {
|
||||
|
@ -41,19 +41,19 @@ public class ExpressionBindings extends AbstractMap<String, Object> implements B
|
|||
|
||||
|
||||
public Object getBindingBean() {
|
||||
return bean;
|
||||
return bindingBean;
|
||||
}
|
||||
|
||||
|
||||
protected Object evaluate(Method method) throws Exception {
|
||||
Object value = method.invoke(getBindingBean());
|
||||
protected Object evaluate(final Method method) throws Exception {
|
||||
Object value = method.invoke(bindingBean);
|
||||
|
||||
if (value != null) {
|
||||
return value;
|
||||
}
|
||||
|
||||
// invoke fallback method
|
||||
return bindings.get(Define.undefined).invoke(getBindingBean());
|
||||
return bindings.get(Define.undefined).invoke(bindingBean);
|
||||
}
|
||||
|
||||
|
||||
|
|
|
@ -0,0 +1,31 @@
|
|||
|
||||
package net.sourceforge.filebot.format;
|
||||
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
}
|
|
@ -2,12 +2,26 @@
|
|||
package net.sourceforge.filebot.format;
|
||||
|
||||
|
||||
import java.io.FilePermission;
|
||||
import java.io.InputStreamReader;
|
||||
import java.lang.reflect.InvocationHandler;
|
||||
import java.lang.reflect.InvocationTargetException;
|
||||
import java.lang.reflect.Method;
|
||||
import java.lang.reflect.Proxy;
|
||||
import java.security.AccessControlContext;
|
||||
import java.security.AccessControlException;
|
||||
import java.security.AccessController;
|
||||
import java.security.PermissionCollection;
|
||||
import java.security.Permissions;
|
||||
import java.security.PrivilegedActionException;
|
||||
import java.security.PrivilegedExceptionAction;
|
||||
import java.security.ProtectionDomain;
|
||||
import java.text.FieldPosition;
|
||||
import java.text.Format;
|
||||
import java.text.ParsePosition;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.PropertyPermission;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
|
@ -19,6 +33,10 @@ import javax.script.ScriptEngine;
|
|||
import javax.script.ScriptException;
|
||||
import javax.script.SimpleScriptContext;
|
||||
|
||||
import net.sourceforge.tuned.ExceptionUtilities;
|
||||
|
||||
import org.mozilla.javascript.EcmaError;
|
||||
|
||||
import com.sun.phobos.script.javascript.RhinoScriptEngine;
|
||||
|
||||
|
||||
|
@ -33,7 +51,7 @@ public class ExpressionFormat extends Format {
|
|||
|
||||
public ExpressionFormat(String expression) throws ScriptException {
|
||||
this.expression = expression;
|
||||
this.compilation = compile(expression, (Compilable) initScriptEngine());
|
||||
this.compilation = secure(compile(expression, (Compilable) initScriptEngine()));
|
||||
}
|
||||
|
||||
|
||||
|
@ -97,7 +115,9 @@ public class ExpressionFormat extends Format {
|
|||
|
||||
public StringBuffer format(Bindings bindings, StringBuffer sb) {
|
||||
ScriptContext context = new SimpleScriptContext();
|
||||
context.setBindings(bindings, ScriptContext.GLOBAL_SCOPE);
|
||||
|
||||
// use privileged bindings so we are not restricted by the script sandbox
|
||||
context.setBindings(PrivilegedBindings.newProxy(bindings), ScriptContext.GLOBAL_SCOPE);
|
||||
|
||||
for (Object snipped : compilation) {
|
||||
if (snipped instanceof CompiledScript) {
|
||||
|
@ -108,9 +128,16 @@ public class ExpressionFormat extends Format {
|
|||
sb.append(value);
|
||||
}
|
||||
} catch (ScriptException e) {
|
||||
EcmaError ecmaError = ExceptionUtilities.findCause(e, EcmaError.class);
|
||||
|
||||
// try to unwrap EcmaError
|
||||
if (ecmaError != null) {
|
||||
lastException = new ExpressionException(String.format("%s: %s", ecmaError.getName(), ecmaError.getErrorMessage()), e);
|
||||
} else {
|
||||
lastException = e;
|
||||
} catch (Exception e) {
|
||||
lastException = new ScriptException(e);
|
||||
}
|
||||
} catch (RuntimeException e) {
|
||||
lastException = new ExpressionException(e);
|
||||
}
|
||||
} else {
|
||||
sb.append(snipped);
|
||||
|
@ -126,6 +153,123 @@ public class ExpressionFormat extends Format {
|
|||
}
|
||||
|
||||
|
||||
private Object[] secure(Object[] compilation) {
|
||||
// create sandbox AccessControlContext
|
||||
AccessControlContext sandbox = new AccessControlContext(new ProtectionDomain[] { new ProtectionDomain(null, getSandboxPermissions()) });
|
||||
|
||||
for (int i = 0; i < compilation.length; i++) {
|
||||
Object snipped = compilation[i];
|
||||
|
||||
if (snipped instanceof CompiledScript) {
|
||||
compilation[i] = new SecureCompiledScript(sandbox, (CompiledScript) snipped);
|
||||
}
|
||||
}
|
||||
|
||||
return compilation;
|
||||
}
|
||||
|
||||
|
||||
private PermissionCollection getSandboxPermissions() {
|
||||
Permissions permissions = new Permissions();
|
||||
|
||||
permissions.add(new RuntimePermission("createClassLoader"));
|
||||
permissions.add(new FilePermission("<<ALL FILES>>", "read"));
|
||||
permissions.add(new PropertyPermission("*", "read"));
|
||||
permissions.add(new RuntimePermission("getenv.*"));
|
||||
|
||||
return permissions;
|
||||
}
|
||||
|
||||
|
||||
private static class PrivilegedBindings implements InvocationHandler {
|
||||
|
||||
private final Bindings bindings;
|
||||
|
||||
|
||||
private PrivilegedBindings(Bindings bindings) {
|
||||
this.bindings = bindings;
|
||||
}
|
||||
|
||||
|
||||
@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(bindings, args);
|
||||
}
|
||||
});
|
||||
} 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 Bindings newProxy(Bindings bindings) {
|
||||
PrivilegedBindings invocationHandler = new PrivilegedBindings(bindings);
|
||||
|
||||
// create dynamic invocation proxy
|
||||
return (Bindings) Proxy.newProxyInstance(PrivilegedBindings.class.getClassLoader(), new Class[] { Bindings.class }, invocationHandler);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private static class SecureCompiledScript extends CompiledScript {
|
||||
|
||||
private final AccessControlContext sandbox;
|
||||
private final CompiledScript compiledScript;
|
||||
|
||||
|
||||
private SecureCompiledScript(AccessControlContext sandbox, CompiledScript compiledScript) {
|
||||
this.sandbox = sandbox;
|
||||
this.compiledScript = compiledScript;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public Object eval(final ScriptContext context) throws ScriptException {
|
||||
try {
|
||||
return AccessController.doPrivileged(new PrivilegedExceptionAction<Object>() {
|
||||
|
||||
@Override
|
||||
public Object run() throws ScriptException {
|
||||
return compiledScript.eval(context);
|
||||
}
|
||||
}, sandbox);
|
||||
} catch (PrivilegedActionException e) {
|
||||
AccessControlException accessException = ExceptionUtilities.findCause(e, AccessControlException.class);
|
||||
|
||||
// try to unwrap AccessControlException
|
||||
if (accessException != null)
|
||||
throw new ExpressionException(accessException);
|
||||
|
||||
// forward ScriptException
|
||||
// e.getException() should be an instance of ScriptException,
|
||||
// as only "checked" exceptions will be "wrapped" in a PrivilegedActionException
|
||||
throw (ScriptException) e.getException();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public ScriptEngine getEngine() {
|
||||
return compiledScript.getEngine();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public Object parseObject(String source, ParsePosition pos) {
|
||||
throw new UnsupportedOperationException();
|
||||
|
|
|
@ -1,11 +1,11 @@
|
|||
|
||||
package net.sourceforge.filebot.ui.panel.sfv;
|
||||
package net.sourceforge.filebot.hash;
|
||||
|
||||
|
||||
import java.util.zip.Checksum;
|
||||
|
||||
|
||||
class ChecksumHash implements Hash {
|
||||
public class ChecksumHash implements Hash {
|
||||
|
||||
private final Checksum checksum;
|
||||
|
|
@ -1,8 +1,8 @@
|
|||
|
||||
package net.sourceforge.filebot.ui.panel.sfv;
|
||||
package net.sourceforge.filebot.hash;
|
||||
|
||||
|
||||
interface Hash {
|
||||
public interface Hash {
|
||||
|
||||
public void update(byte[] bytes, int off, int len);
|
||||
|
|
@ -1,17 +1,13 @@
|
|||
|
||||
package net.sourceforge.filebot.ui.panel.sfv;
|
||||
package net.sourceforge.filebot.hash;
|
||||
|
||||
|
||||
import java.io.File;
|
||||
import java.util.Formatter;
|
||||
import java.util.Scanner;
|
||||
import java.util.Map.Entry;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
import java.util.zip.CRC32;
|
||||
|
||||
|
||||
enum HashType {
|
||||
public enum HashType {
|
||||
|
||||
SFV {
|
||||
|
||||
|
@ -23,44 +19,13 @@ enum HashType {
|
|||
|
||||
@Override
|
||||
public VerificationFileScanner newScanner(Scanner scanner) {
|
||||
// adapt default scanner to sfv line syntax
|
||||
return new VerificationFileScanner(scanner) {
|
||||
|
||||
/**
|
||||
* Pattern used to parse the lines of a sfv file.
|
||||
*
|
||||
* <pre>
|
||||
* Sample:
|
||||
* folder/file.txt 970E4EF1
|
||||
* | Group 1 | | Gr.2 |
|
||||
* </pre>
|
||||
*/
|
||||
private final Pattern pattern = Pattern.compile("(.+)\\s+(\\p{XDigit}{8})");
|
||||
|
||||
|
||||
@Override
|
||||
protected Entry<File, String> parseLine(String line) {
|
||||
Matcher matcher = pattern.matcher(line);
|
||||
|
||||
if (!matcher.matches())
|
||||
throw new IllegalSyntaxException(getLineNumber(), line);
|
||||
|
||||
return entry(new File(matcher.group(1)), matcher.group(2));
|
||||
}
|
||||
};
|
||||
return new SfvFileScanner(scanner);
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public VerificationFilePrinter newPrinter(Formatter out) {
|
||||
return new VerificationFilePrinter(out, "CRC32") {
|
||||
|
||||
@Override
|
||||
public void print(String path, String hash) {
|
||||
// e.g folder/file.txt 970E4EF1
|
||||
out.format(String.format("%s %s", path, hash));
|
||||
}
|
||||
};
|
||||
return new SfvFilePrinter(out);
|
||||
}
|
||||
|
||||
},
|
|
@ -0,0 +1,16 @@
|
|||
|
||||
package net.sourceforge.filebot.hash;
|
||||
|
||||
|
||||
public class IllegalSyntaxException extends RuntimeException {
|
||||
|
||||
public IllegalSyntaxException(int lineNumber, String line) {
|
||||
this(String.format("Illegal syntax in line %d: %s", lineNumber, line));
|
||||
}
|
||||
|
||||
|
||||
public IllegalSyntaxException(String message) {
|
||||
super(message);
|
||||
}
|
||||
|
||||
}
|
|
@ -1,5 +1,5 @@
|
|||
|
||||
package net.sourceforge.filebot.ui.panel.sfv;
|
||||
package net.sourceforge.filebot.hash;
|
||||
|
||||
|
||||
import java.math.BigInteger;
|
||||
|
@ -7,7 +7,7 @@ import java.security.MessageDigest;
|
|||
import java.security.NoSuchAlgorithmException;
|
||||
|
||||
|
||||
class MessageDigestHash implements Hash {
|
||||
public class MessageDigestHash implements Hash {
|
||||
|
||||
private final MessageDigest md;
|
||||
|
|
@ -0,0 +1,20 @@
|
|||
|
||||
package net.sourceforge.filebot.hash;
|
||||
|
||||
|
||||
import java.util.Formatter;
|
||||
|
||||
|
||||
public class SfvFilePrinter extends VerificationFilePrinter {
|
||||
|
||||
public SfvFilePrinter(Formatter out) {
|
||||
super(out, "CRC32");
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public void println(String path, String hash) {
|
||||
// e.g folder/file.txt 970E4EF1
|
||||
out.format(String.format("%s %s%n", path, hash));
|
||||
}
|
||||
}
|
|
@ -0,0 +1,46 @@
|
|||
|
||||
package net.sourceforge.filebot.hash;
|
||||
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileNotFoundException;
|
||||
import java.util.Scanner;
|
||||
import java.util.Map.Entry;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
|
||||
public class SfvFileScanner extends VerificationFileScanner {
|
||||
|
||||
public SfvFileScanner(File file) throws FileNotFoundException {
|
||||
super(file);
|
||||
}
|
||||
|
||||
|
||||
public SfvFileScanner(Scanner scanner) {
|
||||
super(scanner);
|
||||
}
|
||||
|
||||
/**
|
||||
* Pattern used to parse the lines of a sfv file.
|
||||
*
|
||||
* <pre>
|
||||
* Sample:
|
||||
* folder/file.txt 970E4EF1
|
||||
* | Group 1 | | Gr.2 |
|
||||
* </pre>
|
||||
*/
|
||||
private final Pattern pattern = Pattern.compile("(.+)\\s+(\\p{XDigit}{8})");
|
||||
|
||||
|
||||
@Override
|
||||
protected Entry<File, String> parseLine(String line) throws IllegalSyntaxException {
|
||||
Matcher matcher = pattern.matcher(line);
|
||||
|
||||
if (!matcher.matches())
|
||||
throw new IllegalSyntaxException(getLineNumber(), line);
|
||||
|
||||
return entry(new File(matcher.group(1)), matcher.group(2));
|
||||
}
|
||||
|
||||
}
|
|
@ -1,5 +1,5 @@
|
|||
|
||||
package net.sourceforge.filebot.ui.panel.sfv;
|
||||
package net.sourceforge.filebot.hash;
|
||||
|
||||
|
||||
import java.io.Closeable;
|
||||
|
@ -7,7 +7,7 @@ import java.io.IOException;
|
|||
import java.util.Formatter;
|
||||
|
||||
|
||||
class VerificationFilePrinter implements Closeable {
|
||||
public class VerificationFilePrinter implements Closeable {
|
||||
|
||||
protected final Formatter out;
|
||||
protected final String algorithm;
|
||||
|
@ -20,17 +20,8 @@ class VerificationFilePrinter implements Closeable {
|
|||
|
||||
|
||||
public void println(String path, String hash) {
|
||||
// print entry
|
||||
print(path, hash);
|
||||
|
||||
// print line separator
|
||||
out.format("%n");
|
||||
}
|
||||
|
||||
|
||||
protected void print(String path, String hash) {
|
||||
// e.g. 1a02a7c1e9ac91346d08829d5037b240f42ded07 ?SHA1*folder/file.txt
|
||||
out.format("%s %s*%s", hash, algorithm == null ? "" : '?' + algorithm.toUpperCase(), path);
|
||||
out.format("%s %s*%s%n", hash, algorithm == null ? "" : '?' + algorithm.toUpperCase(), path);
|
||||
}
|
||||
|
||||
|
|
@ -1,5 +1,5 @@
|
|||
|
||||
package net.sourceforge.filebot.ui.panel.sfv;
|
||||
package net.sourceforge.filebot.hash;
|
||||
|
||||
|
||||
import java.io.Closeable;
|
||||
|
@ -16,7 +16,7 @@ import java.util.regex.Matcher;
|
|||
import java.util.regex.Pattern;
|
||||
|
||||
|
||||
class VerificationFileScanner implements Iterator<Entry<File, String>>, Closeable {
|
||||
public class VerificationFileScanner implements Iterator<Entry<File, String>>, Closeable {
|
||||
|
||||
private final Scanner scanner;
|
||||
|
||||
|
@ -48,7 +48,7 @@ class VerificationFileScanner implements Iterator<Entry<File, String>>, Closeabl
|
|||
|
||||
|
||||
@Override
|
||||
public Entry<File, String> next() {
|
||||
public Entry<File, String> next() throws IllegalSyntaxException {
|
||||
// cache next line
|
||||
if (!hasNext()) {
|
||||
throw new NoSuchElementException();
|
||||
|
@ -88,10 +88,10 @@ class VerificationFileScanner implements Iterator<Entry<File, String>>, Closeabl
|
|||
* | Group 1 | | Group 2 |
|
||||
* </pre>
|
||||
*/
|
||||
private final Pattern pattern = Pattern.compile("(\\p{XDigit}{8,})\\s+(?:\\?\\w+)?\\*(.+)");
|
||||
private final Pattern pattern = Pattern.compile("(\\p{XDigit}+)\\s+(?:\\?\\w+)?\\*(.+)");
|
||||
|
||||
|
||||
protected Entry<File, String> parseLine(String line) {
|
||||
protected Entry<File, String> parseLine(String line) throws IllegalSyntaxException {
|
||||
Matcher matcher = pattern.matcher(line);
|
||||
|
||||
if (!matcher.matches())
|
||||
|
@ -127,18 +127,4 @@ class VerificationFileScanner implements Iterator<Entry<File, String>>, Closeabl
|
|||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
|
||||
public static class IllegalSyntaxException extends RuntimeException {
|
||||
|
||||
public IllegalSyntaxException(int lineNumber, String line) {
|
||||
this(String.format("Illegal syntax in line %d: %s", lineNumber, line));
|
||||
}
|
||||
|
||||
|
||||
public IllegalSyntaxException(String message) {
|
||||
super(message);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
|
@ -2,11 +2,11 @@
|
|||
package net.sourceforge.filebot.torrent;
|
||||
|
||||
|
||||
import java.io.BufferedInputStream;
|
||||
import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.IOException;
|
||||
import java.nio.channels.FileChannel;
|
||||
import java.nio.channels.FileChannel.MapMode;
|
||||
import java.io.InputStream;
|
||||
import java.nio.charset.Charset;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
|
@ -16,8 +16,6 @@ import java.util.Map;
|
|||
import java.util.logging.Level;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
import net.sourceforge.tuned.ByteBufferInputStream;
|
||||
|
||||
|
||||
public class Torrent {
|
||||
|
||||
|
@ -107,13 +105,12 @@ public class Torrent {
|
|||
|
||||
|
||||
private static Map<?, ?> decodeTorrent(File torrent) throws IOException {
|
||||
FileChannel fileChannel = new FileInputStream(torrent).getChannel();
|
||||
InputStream in = new BufferedInputStream(new FileInputStream(torrent));
|
||||
|
||||
try {
|
||||
// memory-map and decode torrent
|
||||
return BDecoder.decode(new ByteBufferInputStream(fileChannel.map(MapMode.READ_ONLY, 0, fileChannel.size())));
|
||||
return BDecoder.decode(in);
|
||||
} finally {
|
||||
fileChannel.close();
|
||||
in.close();
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -206,6 +203,12 @@ public class Torrent {
|
|||
public String getPath() {
|
||||
return path;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return getPath();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -17,7 +17,9 @@ import java.beans.PropertyChangeEvent;
|
|||
import java.beans.PropertyChangeListener;
|
||||
import java.io.File;
|
||||
import java.text.ParseException;
|
||||
import java.util.Arrays;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.ResourceBundle;
|
||||
import java.util.concurrent.ArrayBlockingQueue;
|
||||
import java.util.concurrent.ExecutorService;
|
||||
|
@ -53,6 +55,7 @@ import net.sourceforge.filebot.format.EpisodeFormatBindingBean;
|
|||
import net.sourceforge.filebot.format.ExpressionFormat;
|
||||
import net.sourceforge.filebot.web.Episode;
|
||||
import net.sourceforge.filebot.web.Episode.EpisodeFormat;
|
||||
import net.sourceforge.tuned.DefaultThreadFactory;
|
||||
import net.sourceforge.tuned.ExceptionUtilities;
|
||||
import net.sourceforge.tuned.ui.GradientStyle;
|
||||
import net.sourceforge.tuned.ui.LinkButton;
|
||||
|
@ -68,8 +71,7 @@ public class EpisodeFormatDialog extends JDialog {
|
|||
|
||||
private JLabel preview = new JLabel();
|
||||
|
||||
private JLabel warningMessage = new JLabel(ResourceManager.getIcon("status.warning"));
|
||||
private JLabel errorMessage = new JLabel(ResourceManager.getIcon("status.error"));
|
||||
private JLabel status = new JLabel();
|
||||
|
||||
private EpisodeFormatBindingBean previewSample = new EpisodeFormatBindingBean(getPreviewSampleEpisode(), getPreviewSampleMediaFile());
|
||||
|
||||
|
@ -107,15 +109,10 @@ public class EpisodeFormatDialog extends JDialog {
|
|||
header.setBackground(Color.white);
|
||||
header.setBorder(new SeparatorBorder(1, new Color(0xB4B4B4), new Color(0xACACAC), GradientStyle.LEFT_TO_RIGHT, Position.BOTTOM));
|
||||
|
||||
errorMessage.setVisible(false);
|
||||
warningMessage.setVisible(false);
|
||||
progressIndicator.setVisible(false);
|
||||
|
||||
header.add(progressIndicator, "pos 1al 0al, hidemode 3");
|
||||
header.add(title, "wrap unrel:push");
|
||||
header.add(preview, "gap indent, hidemode 3, wmax 90%");
|
||||
header.add(errorMessage, "gap indent, hidemode 3, wmax 90%, newline");
|
||||
header.add(warningMessage, "gap indent, hidemode 3, wmax 90%, newline");
|
||||
header.add(preview, "hmin 16px, gap indent, hidemode 3, wmax 90%");
|
||||
header.add(status, "hmin 16px, gap indent, hidemode 3, wmax 90%, newline");
|
||||
|
||||
JPanel content = new JPanel(new MigLayout("insets dialog, nogrid, fill"));
|
||||
|
||||
|
@ -125,7 +122,7 @@ public class EpisodeFormatDialog extends JDialog {
|
|||
content.add(createSyntaxPanel(), "gapx indent indent, wrap 8px");
|
||||
|
||||
content.add(new JLabel("Examples"), "gap indent+unrel, wrap 0");
|
||||
content.add(createExamplesPanel(), "gapx indent indent, wrap 25px:push");
|
||||
content.add(createExamplesPanel(), "hmin 50px, gapx indent indent, wrap 25px:push");
|
||||
|
||||
content.add(new JButton(useDefaultFormatAction), "tag left");
|
||||
content.add(new JButton(approveFormatAction), "tag apply");
|
||||
|
@ -137,12 +134,8 @@ public class EpisodeFormatDialog extends JDialog {
|
|||
pane.add(header, "h 60px, growx, dock north");
|
||||
pane.add(content, "grow");
|
||||
|
||||
setSize(485, 415);
|
||||
|
||||
header.setComponentPopupMenu(createPreviewSamplePopup());
|
||||
|
||||
setLocation(TunedUtilities.getPreferredLocation(this));
|
||||
|
||||
// update format on change
|
||||
editor.getDocument().addDocumentListener(new LazyDocumentAdapter() {
|
||||
|
||||
|
@ -171,6 +164,10 @@ public class EpisodeFormatDialog extends JDialog {
|
|||
|
||||
// update preview to current format
|
||||
firePreviewSampleChanged();
|
||||
|
||||
// initialize window properties
|
||||
setLocation(TunedUtilities.getPreferredLocation(this));
|
||||
pack();
|
||||
}
|
||||
|
||||
|
||||
|
@ -244,30 +241,58 @@ public class EpisodeFormatDialog extends JDialog {
|
|||
}
|
||||
|
||||
|
||||
private JPanel createExamplesPanel() {
|
||||
private JComponent createExamplesPanel() {
|
||||
JPanel panel = new JPanel(new MigLayout("fill, wrap 3"));
|
||||
|
||||
panel.setBorder(new LineBorder(new Color(0xACA899)));
|
||||
panel.setBackground(new Color(0xFFFFE1));
|
||||
panel.setOpaque(true);
|
||||
|
||||
ResourceBundle bundle = ResourceBundle.getBundle(getClass().getName());
|
||||
|
||||
// sort keys
|
||||
String[] keys = bundle.keySet().toArray(new String[0]);
|
||||
Arrays.sort(keys);
|
||||
// collect example keys
|
||||
List<String> examples = new ArrayList<String>();
|
||||
|
||||
for (String key : keys) {
|
||||
if (key.startsWith("example")) {
|
||||
String format = bundle.getString(key);
|
||||
for (String key : bundle.keySet()) {
|
||||
if (key.startsWith("example"))
|
||||
examples.add(key);
|
||||
}
|
||||
|
||||
// sort by example key
|
||||
Collections.sort(examples);
|
||||
|
||||
for (String key : examples) {
|
||||
final String format = bundle.getString(key);
|
||||
|
||||
LinkButton formatLink = new LinkButton(new AbstractAction(format) {
|
||||
|
||||
@Override
|
||||
public void actionPerformed(ActionEvent e) {
|
||||
editor.setText(format);
|
||||
}
|
||||
});
|
||||
|
||||
LinkButton formatLink = new LinkButton(new ExampleFormatAction(format));
|
||||
formatLink.setFont(new Font(MONOSPACED, PLAIN, 11));
|
||||
|
||||
final JLabel formatExample = new JLabel();
|
||||
|
||||
// bind text to preview
|
||||
addPropertyChangeListener("previewSample", new PropertyChangeListener() {
|
||||
|
||||
@Override
|
||||
public void propertyChange(PropertyChangeEvent evt) {
|
||||
try {
|
||||
formatExample.setText(new ExpressionFormat(format).format(previewSample));
|
||||
setForeground(defaultColor);
|
||||
} catch (Exception e) {
|
||||
formatExample.setText(ExceptionUtilities.getRootCauseMessage(e));
|
||||
setForeground(errorColor);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
panel.add(formatLink);
|
||||
panel.add(new JLabel("..."));
|
||||
panel.add(new ExampleFormatLabel(format));
|
||||
}
|
||||
panel.add(formatExample);
|
||||
}
|
||||
|
||||
return panel;
|
||||
|
@ -307,7 +332,31 @@ public class EpisodeFormatDialog extends JDialog {
|
|||
|
||||
|
||||
private ExecutorService createPreviewExecutor() {
|
||||
ThreadPoolExecutor executor = new ThreadPoolExecutor(1, 1, 0L, TimeUnit.SECONDS, new ArrayBlockingQueue<Runnable>(1));
|
||||
ThreadPoolExecutor executor = new ThreadPoolExecutor(1, 1, 0L, TimeUnit.SECONDS, new ArrayBlockingQueue<Runnable>(1), new DefaultThreadFactory("PreviewFormatter")) {
|
||||
|
||||
@SuppressWarnings("deprecation")
|
||||
@Override
|
||||
public List<Runnable> shutdownNow() {
|
||||
List<Runnable> remaining = super.shutdownNow();
|
||||
|
||||
try {
|
||||
if (!awaitTermination(3, TimeUnit.SECONDS)) {
|
||||
// if the thread has not terminated after 4 seconds, it is probably stuck
|
||||
ThreadGroup threadGroup = ((DefaultThreadFactory) getThreadFactory()).getThreadGroup();
|
||||
|
||||
// kill background thread by force
|
||||
threadGroup.stop();
|
||||
|
||||
// log access of potentially unsafe method
|
||||
Logger.getLogger("global").warning("Thread was forcibly terminated");
|
||||
}
|
||||
} catch (InterruptedException e) {
|
||||
Logger.getLogger("global").log(Level.WARNING, "Thread was not terminated", e);
|
||||
}
|
||||
|
||||
return remaining;
|
||||
}
|
||||
};
|
||||
|
||||
// only keep the latest task in the queue
|
||||
executor.setRejectedExecutionHandler(new DiscardOldestPolicy());
|
||||
|
@ -345,34 +394,33 @@ public class EpisodeFormatDialog extends JDialog {
|
|||
|
||||
// check internal script exception and empty output
|
||||
if (format.scriptException() != null) {
|
||||
warningMessage.setText(format.scriptException().getCause().getMessage());
|
||||
throw format.scriptException();
|
||||
} else if (get().trim().isEmpty()) {
|
||||
warningMessage.setText("Formatted value is empty");
|
||||
} else {
|
||||
warningMessage.setText(null);
|
||||
throw new RuntimeException("Formatted value is empty");
|
||||
}
|
||||
|
||||
// no warning or error
|
||||
status.setVisible(false);
|
||||
} catch (Exception e) {
|
||||
Logger.getLogger("global").log(Level.WARNING, e.getMessage(), e);
|
||||
}
|
||||
|
||||
status.setText(ExceptionUtilities.getMessage(e));
|
||||
status.setIcon(ResourceManager.getIcon("status.warning"));
|
||||
status.setVisible(true);
|
||||
} finally {
|
||||
preview.setVisible(true);
|
||||
warningMessage.setVisible(warningMessage.getText() != null);
|
||||
errorMessage.setVisible(false);
|
||||
|
||||
editor.setForeground(defaultColor);
|
||||
|
||||
progressIndicatorTimer.stop();
|
||||
progressIndicator.setVisible(false);
|
||||
}
|
||||
}
|
||||
});
|
||||
} catch (ScriptException e) {
|
||||
// incorrect syntax
|
||||
errorMessage.setText(ExceptionUtilities.getRootCauseMessage(e));
|
||||
errorMessage.setVisible(true);
|
||||
status.setText(ExceptionUtilities.getRootCauseMessage(e));
|
||||
status.setIcon(ResourceManager.getIcon("status.error"));
|
||||
status.setVisible(true);
|
||||
|
||||
preview.setVisible(false);
|
||||
warningMessage.setVisible(false);
|
||||
|
||||
editor.setForeground(errorColor);
|
||||
}
|
||||
}
|
||||
|
@ -418,6 +466,9 @@ public class EpisodeFormatDialog extends JDialog {
|
|||
@Override
|
||||
public void actionPerformed(ActionEvent evt) {
|
||||
try {
|
||||
if (progressIndicator.isVisible())
|
||||
throw new IllegalStateException("Format has not been verified yet.");
|
||||
|
||||
// check syntax
|
||||
new ExpressionFormat(editor.getText());
|
||||
|
||||
|
@ -425,8 +476,8 @@ public class EpisodeFormatDialog extends JDialog {
|
|||
Settings.userRoot().put("dialog.format", editor.getText());
|
||||
|
||||
finish(Option.APPROVE);
|
||||
} catch (ScriptException e) {
|
||||
Logger.getLogger("ui").log(Level.WARNING, ExceptionUtilities.getRootCauseMessage(e), e);
|
||||
} catch (Exception e) {
|
||||
Logger.getLogger("ui").log(Level.WARNING, ExceptionUtilities.getRootCauseMessage(e));
|
||||
}
|
||||
}
|
||||
};
|
||||
|
@ -437,42 +488,6 @@ public class EpisodeFormatDialog extends JDialog {
|
|||
}
|
||||
|
||||
|
||||
protected class ExampleFormatAction extends AbstractAction {
|
||||
|
||||
public ExampleFormatAction(String format) {
|
||||
super(format);
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public void actionPerformed(ActionEvent e) {
|
||||
editor.setText(getValue(Action.NAME).toString());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
protected class ExampleFormatLabel extends JLabel {
|
||||
|
||||
public ExampleFormatLabel(final String format) {
|
||||
// bind text to preview
|
||||
EpisodeFormatDialog.this.addPropertyChangeListener("previewSample", new PropertyChangeListener() {
|
||||
|
||||
@Override
|
||||
public void propertyChange(PropertyChangeEvent evt) {
|
||||
try {
|
||||
setText(new ExpressionFormat(format).format(previewSample));
|
||||
setForeground(defaultColor);
|
||||
} catch (Exception e) {
|
||||
setText(ExceptionUtilities.getRootCauseMessage(e));
|
||||
setForeground(errorColor);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
protected static abstract class LazyDocumentAdapter implements DocumentListener {
|
||||
|
||||
private final Timer timer = new Timer(200, new ActionListener() {
|
||||
|
|
|
@ -3,11 +3,11 @@ syntax: <html><b>{</b> <b>}</b> ... expression, <b>n</b> ... name, <b>s</b> ...
|
|||
# basic 1.01
|
||||
example[0]: {n} - {s}.{e} - {t}
|
||||
|
||||
# 1x01
|
||||
example[1]: {n} - {s+'x'}{e.pad(2)}
|
||||
|
||||
# S01E01
|
||||
example[2]: {n} - {'S'+s.pad(2)}E{e.pad(2)}
|
||||
example[1]: {n} - {'S'+s.pad(2)}E{e.pad(2)} - {t}
|
||||
|
||||
# 1x01
|
||||
example[2]: {n} - {s+'x'}{e.pad(2)}
|
||||
|
||||
# uglyfy name
|
||||
example[3]: {n.space('.').toLowerCase()}
|
||||
example[3]: {n.space('.').toLowerCase()}.{s}{e.pad(2)}
|
|
@ -51,7 +51,7 @@ class MatchAction extends AbstractAction {
|
|||
this.model = model;
|
||||
this.metrics = createMetrics();
|
||||
|
||||
putValue(SHORT_DESCRIPTION, "Match names to files");
|
||||
putValue(SHORT_DESCRIPTION, "Match files and names");
|
||||
}
|
||||
|
||||
|
||||
|
|
|
@ -15,7 +15,7 @@ import ca.odell.glazedlists.TransformedList;
|
|||
import ca.odell.glazedlists.event.ListEvent;
|
||||
|
||||
|
||||
class MatchModel<Value, Candidate> {
|
||||
public class MatchModel<Value, Candidate> {
|
||||
|
||||
private final EventList<Match<Value, Candidate>> source = new BasicEventList<Match<Value, Candidate>>();
|
||||
|
||||
|
|
|
@ -28,17 +28,15 @@ import net.sourceforge.filebot.ui.transfer.FileTransferablePolicy;
|
|||
import net.sourceforge.filebot.web.Episode;
|
||||
import net.sourceforge.tuned.FastFile;
|
||||
|
||||
import ca.odell.glazedlists.EventList;
|
||||
|
||||
|
||||
class NamesListTransferablePolicy extends FileTransferablePolicy {
|
||||
|
||||
private static final DataFlavor episodeArrayFlavor = ArrayTransferable.flavor(Episode.class);
|
||||
|
||||
private final EventList<Object> model;
|
||||
private final List<Object> model;
|
||||
|
||||
|
||||
public NamesListTransferablePolicy(EventList<Object> model) {
|
||||
public NamesListTransferablePolicy(List<Object> model) {
|
||||
this.model = model;
|
||||
}
|
||||
|
||||
|
|
|
@ -5,16 +5,15 @@ package net.sourceforge.filebot.ui.panel.rename;
|
|||
import java.awt.event.ActionEvent;
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayDeque;
|
||||
import java.util.Deque;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Map.Entry;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
import javax.swing.AbstractAction;
|
||||
|
||||
import net.sourceforge.filebot.ResourceManager;
|
||||
import net.sourceforge.filebot.similarity.Match;
|
||||
import net.sourceforge.tuned.ExceptionUtilities;
|
||||
import net.sourceforge.tuned.FileUtilities;
|
||||
|
||||
|
||||
class RenameAction extends AbstractAction {
|
||||
|
@ -32,52 +31,32 @@ class RenameAction extends AbstractAction {
|
|||
|
||||
|
||||
public void actionPerformed(ActionEvent evt) {
|
||||
Deque<Match<File, File>> todoQueue = new ArrayDeque<Match<File, File>>();
|
||||
Deque<Match<File, File>> doneQueue = new ArrayDeque<Match<File, File>>();
|
||||
|
||||
for (Match<String, File> match : model.getMatchesForRenaming()) {
|
||||
File source = match.getCandidate();
|
||||
String extension = FileUtilities.getExtension(source);
|
||||
|
||||
StringBuilder name = new StringBuilder(match.getValue());
|
||||
|
||||
if (extension != null) {
|
||||
name.append(".").append(extension);
|
||||
}
|
||||
|
||||
// same parent, different name
|
||||
File target = new File(source.getParentFile(), name.toString());
|
||||
|
||||
todoQueue.addLast(new Match<File, File>(source, target));
|
||||
}
|
||||
List<Entry<File, File>> renameLog = new ArrayList<Entry<File, File>>();
|
||||
|
||||
try {
|
||||
int renameCount = todoQueue.size();
|
||||
|
||||
for (Match<File, File> match : todoQueue) {
|
||||
for (Entry<File, File> mapping : model.getRenameMap().entrySet()) {
|
||||
// rename file
|
||||
if (!match.getValue().renameTo(match.getCandidate()))
|
||||
throw new IOException(String.format("Failed to rename file: %s.", match.getValue().getName()));
|
||||
if (!mapping.getKey().renameTo(mapping.getValue()))
|
||||
throw new IOException(String.format("Failed to rename file: \"%s\".", mapping.getKey().getName()));
|
||||
|
||||
// revert in reverse order if renaming of all matches fails
|
||||
doneQueue.addFirst(match);
|
||||
// remember successfully renamed matches for possible revert
|
||||
renameLog.add(mapping);
|
||||
}
|
||||
|
||||
// renamed all matches successfully
|
||||
Logger.getLogger("ui").info(String.format("%d files renamed.", renameCount));
|
||||
} catch (IOException e) {
|
||||
// rename failed
|
||||
Logger.getLogger("ui").warning(ExceptionUtilities.getRootCauseMessage(e));
|
||||
Logger.getLogger("ui").info(String.format("%d files renamed.", renameLog.size()));
|
||||
} catch (Exception e) {
|
||||
// could not rename one of the files, revert all changes
|
||||
Logger.getLogger("ui").warning(e.getMessage());
|
||||
|
||||
boolean revertSuccess = true;
|
||||
// revert in reverse order
|
||||
Collections.reverse(renameLog);
|
||||
|
||||
// revert rename operations
|
||||
for (Match<File, File> match : doneQueue) {
|
||||
revertSuccess &= match.getCandidate().renameTo(match.getValue());
|
||||
for (Entry<File, File> mapping : renameLog) {
|
||||
if (!mapping.getValue().renameTo(mapping.getKey())) {
|
||||
Logger.getLogger("ui").severe(String.format("Failed to revert file: \"%s\".", mapping.getValue().getName()));
|
||||
}
|
||||
|
||||
if (!revertSuccess) {
|
||||
Logger.getLogger("ui").severe("Failed to revert all rename operations.");
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -2,6 +2,8 @@
|
|||
package net.sourceforge.filebot.ui.panel.rename;
|
||||
|
||||
|
||||
import static java.util.Collections.swap;
|
||||
|
||||
import java.awt.BorderLayout;
|
||||
import java.awt.event.ActionEvent;
|
||||
import java.awt.event.MouseAdapter;
|
||||
|
@ -58,16 +60,6 @@ class RenameList<E> extends FileBotList<E> {
|
|||
loadAction.putValue(LoadAction.TRANSFERABLE_POLICY, transferablePolicy);
|
||||
}
|
||||
|
||||
|
||||
public void swap(int index1, int index2) {
|
||||
E e1 = model.get(index1);
|
||||
E e2 = model.get(index2);
|
||||
|
||||
// swap data
|
||||
model.set(index1, e2);
|
||||
model.set(index2, e1);
|
||||
}
|
||||
|
||||
private final LoadAction loadAction = new LoadAction(null);
|
||||
|
||||
private final AbstractAction upAction = new AbstractAction(null, ResourceManager.getIcon("action.up")) {
|
||||
|
@ -76,7 +68,7 @@ class RenameList<E> extends FileBotList<E> {
|
|||
int index = getListComponent().getSelectedIndex();
|
||||
|
||||
if (index > 0) {
|
||||
swap(index, index - 1);
|
||||
swap(model, index, index - 1);
|
||||
getListComponent().setSelectedIndex(index - 1);
|
||||
}
|
||||
}
|
||||
|
@ -88,7 +80,7 @@ class RenameList<E> extends FileBotList<E> {
|
|||
int index = getListComponent().getSelectedIndex();
|
||||
|
||||
if (index < model.size() - 1) {
|
||||
swap(index, index + 1);
|
||||
swap(model, index, index + 1);
|
||||
getListComponent().setSelectedIndex(index + 1);
|
||||
}
|
||||
}
|
||||
|
@ -109,8 +101,8 @@ class RenameList<E> extends FileBotList<E> {
|
|||
public void mouseDragged(MouseEvent m) {
|
||||
int currentIndex = getListComponent().getSelectedIndex();
|
||||
|
||||
if (currentIndex != lastIndex) {
|
||||
swap(lastIndex, currentIndex);
|
||||
if (currentIndex != lastIndex && lastIndex >= 0 && currentIndex >= 0) {
|
||||
swap(model, lastIndex, currentIndex);
|
||||
lastIndex = currentIndex;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -12,7 +12,7 @@ import java.awt.geom.Rectangle2D;
|
|||
import java.awt.geom.RoundRectangle2D;
|
||||
import java.io.File;
|
||||
|
||||
import javax.swing.JLabel;
|
||||
import javax.swing.DefaultListCellRenderer;
|
||||
import javax.swing.JList;
|
||||
import javax.swing.border.CompoundBorder;
|
||||
import javax.swing.border.EmptyBorder;
|
||||
|
@ -22,13 +22,14 @@ import net.sourceforge.filebot.ResourceManager;
|
|||
import net.sourceforge.filebot.ui.panel.rename.RenameModel.FormattedFuture;
|
||||
import net.sourceforge.tuned.FileUtilities;
|
||||
import net.sourceforge.tuned.ui.DefaultFancyListCellRenderer;
|
||||
import net.sourceforge.tuned.ui.GradientStyle;
|
||||
|
||||
|
||||
class RenameListCellRenderer extends DefaultFancyListCellRenderer {
|
||||
|
||||
private final RenameModel renameModel;
|
||||
|
||||
private final TypeLabel typeLabel = new TypeLabel();
|
||||
private final TypeRenderer typeRenderer = new TypeRenderer();
|
||||
|
||||
private final Color noMatchGradientBeginColor = new Color(0xB7B7B7);
|
||||
private final Color noMatchGradientEndColor = new Color(0x9A9A9A);
|
||||
|
@ -39,8 +40,8 @@ class RenameListCellRenderer extends DefaultFancyListCellRenderer {
|
|||
|
||||
setHighlightingEnabled(false);
|
||||
|
||||
setLayout(new MigLayout("fill, insets 0", "align left", "align center"));
|
||||
add(typeLabel, "gap rel:push");
|
||||
setLayout(new MigLayout("insets 0, fill", "align left", "align center"));
|
||||
add(typeRenderer, "gap rel:push, hidemode 3");
|
||||
}
|
||||
|
||||
|
||||
|
@ -48,19 +49,24 @@ class RenameListCellRenderer extends DefaultFancyListCellRenderer {
|
|||
public void configureListCellRendererComponent(JList list, Object value, int index, boolean isSelected, boolean cellHasFocus) {
|
||||
super.configureListCellRendererComponent(list, value, index, isSelected, cellHasFocus);
|
||||
|
||||
// reset
|
||||
// reset decoration
|
||||
setIcon(null);
|
||||
typeLabel.setText(null);
|
||||
typeLabel.setAlpha(1.0f);
|
||||
typeRenderer.setVisible(false);
|
||||
typeRenderer.setAlpha(1.0f);
|
||||
|
||||
if (value instanceof File) {
|
||||
// display file extension
|
||||
File file = (File) value;
|
||||
|
||||
if (renameModel.preserveExtension()) {
|
||||
setText(FileUtilities.getName(file));
|
||||
typeLabel.setText(getType(file));
|
||||
typeRenderer.setText(getType(file));
|
||||
typeRenderer.setVisible(true);
|
||||
} else {
|
||||
setText(file.getName());
|
||||
}
|
||||
} else if (value instanceof FormattedFuture) {
|
||||
// progress icon and value type
|
||||
// display progress icon
|
||||
FormattedFuture future = (FormattedFuture) value;
|
||||
|
||||
switch (future.getState()) {
|
||||
|
@ -78,7 +84,7 @@ class RenameListCellRenderer extends DefaultFancyListCellRenderer {
|
|||
setGradientColors(noMatchGradientBeginColor, noMatchGradientEndColor);
|
||||
} else {
|
||||
setForeground(noMatchGradientBeginColor);
|
||||
typeLabel.setAlpha(0.5f);
|
||||
typeRenderer.setAlpha(0.5f);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -98,7 +104,7 @@ class RenameListCellRenderer extends DefaultFancyListCellRenderer {
|
|||
}
|
||||
|
||||
|
||||
private class TypeLabel extends JLabel {
|
||||
private static class TypeRenderer extends DefaultListCellRenderer {
|
||||
|
||||
private final Insets margin = new Insets(0, 10, 0, 0);
|
||||
private final Insets padding = new Insets(0, 6, 0, 5);
|
||||
|
@ -110,7 +116,7 @@ class RenameListCellRenderer extends DefaultFancyListCellRenderer {
|
|||
private float alpha = 1.0f;
|
||||
|
||||
|
||||
public TypeLabel() {
|
||||
public TypeRenderer() {
|
||||
setOpaque(false);
|
||||
setForeground(new Color(0x141414));
|
||||
|
||||
|
@ -128,7 +134,7 @@ class RenameListCellRenderer extends DefaultFancyListCellRenderer {
|
|||
|
||||
g2d.setComposite(AlphaComposite.SrcOver.derive(alpha));
|
||||
|
||||
g2d.setPaint(getGradientStyle().getGradientPaint(shape, gradientBeginColor, gradientEndColor));
|
||||
g2d.setPaint(GradientStyle.TOP_TO_BOTTOM.getGradientPaint(shape, gradientBeginColor, gradientEndColor));
|
||||
g2d.fill(shape);
|
||||
|
||||
g2d.setFont(getFont());
|
||||
|
@ -139,15 +145,6 @@ class RenameListCellRenderer extends DefaultFancyListCellRenderer {
|
|||
}
|
||||
|
||||
|
||||
@Override
|
||||
public void setText(String text) {
|
||||
super.setText(text);
|
||||
|
||||
// auto-hide if text is null
|
||||
setVisible(text != null);
|
||||
}
|
||||
|
||||
|
||||
public void setAlpha(float alpha) {
|
||||
this.alpha = alpha;
|
||||
}
|
||||
|
|
|
@ -7,6 +7,7 @@ import java.beans.PropertyChangeListener;
|
|||
import java.io.File;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.Executor;
|
||||
|
@ -20,6 +21,7 @@ import javax.swing.SwingWorker;
|
|||
import javax.swing.SwingWorker.StateValue;
|
||||
|
||||
import net.sourceforge.filebot.similarity.Match;
|
||||
import net.sourceforge.tuned.FileUtilities;
|
||||
import net.sourceforge.tuned.ui.TunedUtilities;
|
||||
import ca.odell.glazedlists.EventList;
|
||||
import ca.odell.glazedlists.TransformedList;
|
||||
|
@ -52,17 +54,7 @@ public class RenameModel extends MatchModel<Object, File> {
|
|||
}
|
||||
};
|
||||
|
||||
|
||||
public void useFormatter(Class<?> type, MatchFormatter formatter) {
|
||||
if (formatter != null) {
|
||||
formatters.put(type, formatter);
|
||||
} else {
|
||||
formatters.remove(type);
|
||||
}
|
||||
|
||||
// reformat matches
|
||||
names.refresh();
|
||||
}
|
||||
private boolean preserveExtension = true;
|
||||
|
||||
|
||||
public EventList<FormattedFuture> names() {
|
||||
|
@ -75,16 +67,62 @@ public class RenameModel extends MatchModel<Object, File> {
|
|||
}
|
||||
|
||||
|
||||
public List<Match<String, File>> getMatchesForRenaming() {
|
||||
List<Match<String, File>> matches = new ArrayList<Match<String, File>>();
|
||||
public boolean preserveExtension() {
|
||||
return preserveExtension;
|
||||
}
|
||||
|
||||
for (int i = 0; i < size(); i++) {
|
||||
if (hasComplement(i) && names.get(i).isDone()) {
|
||||
matches.add(new Match<String, File>(names().get(i).toString(), files().get(i)));
|
||||
|
||||
public void setPreserveExtension(boolean preserveExtension) {
|
||||
this.preserveExtension = preserveExtension;
|
||||
}
|
||||
|
||||
|
||||
public Map<File, File> getRenameMap() {
|
||||
Map<File, File> map = new LinkedHashMap<File, File>();
|
||||
|
||||
for (int i = 0; i < names.size(); i++) {
|
||||
if (hasComplement(i)) {
|
||||
FormattedFuture future = names.get(i);
|
||||
|
||||
// check if background formatter is done
|
||||
if (!future.isDone()) {
|
||||
throw new IllegalStateException(String.format("\"%s\" has not been formatted yet.", future.toString()));
|
||||
}
|
||||
|
||||
File originalFile = files().get(i);
|
||||
StringBuilder newName = new StringBuilder(future.toString());
|
||||
|
||||
if (preserveExtension) {
|
||||
String extension = FileUtilities.getExtension(originalFile);
|
||||
|
||||
if (extension != null) {
|
||||
newName.append(".").append(extension);
|
||||
}
|
||||
}
|
||||
|
||||
return matches;
|
||||
// same parent, different name
|
||||
File newFile = new File(originalFile.getParentFile(), newName.toString());
|
||||
|
||||
// insert mapping
|
||||
if (map.put(originalFile, newFile) != null) {
|
||||
throw new IllegalStateException(String.format("Duplicate file entry: \"%s\"", originalFile.getName()));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return map;
|
||||
}
|
||||
|
||||
|
||||
public void useFormatter(Class<?> type, MatchFormatter formatter) {
|
||||
if (formatter != null) {
|
||||
formatters.put(type, formatter);
|
||||
} else {
|
||||
formatters.remove(type);
|
||||
}
|
||||
|
||||
// reformat matches
|
||||
names.refresh();
|
||||
}
|
||||
|
||||
|
||||
|
|
|
@ -23,11 +23,9 @@ import java.util.prefs.Preferences;
|
|||
import javax.script.ScriptException;
|
||||
import javax.swing.AbstractAction;
|
||||
import javax.swing.Action;
|
||||
import javax.swing.DefaultListSelectionModel;
|
||||
import javax.swing.JButton;
|
||||
import javax.swing.JComponent;
|
||||
import javax.swing.JLabel;
|
||||
import javax.swing.ListSelectionModel;
|
||||
import javax.swing.SwingConstants;
|
||||
import javax.swing.SwingUtilities;
|
||||
|
||||
|
@ -53,8 +51,8 @@ import net.sourceforge.tuned.PreferencesMap.AbstractAdapter;
|
|||
import net.sourceforge.tuned.PreferencesMap.PreferencesEntry;
|
||||
import net.sourceforge.tuned.ui.ActionPopup;
|
||||
import net.sourceforge.tuned.ui.LoadingOverlayPane;
|
||||
import ca.odell.glazedlists.event.ListEvent;
|
||||
import ca.odell.glazedlists.event.ListEventListener;
|
||||
import ca.odell.glazedlists.ListSelection;
|
||||
import ca.odell.glazedlists.swing.EventSelectionModel;
|
||||
|
||||
|
||||
public class RenamePanel extends JComponent {
|
||||
|
@ -88,8 +86,8 @@ public class RenamePanel extends JComponent {
|
|||
namesList.getListComponent().setCellRenderer(cellrenderer);
|
||||
filesList.getListComponent().setCellRenderer(cellrenderer);
|
||||
|
||||
ListSelectionModel selectionModel = new DefaultListSelectionModel();
|
||||
selectionModel.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
|
||||
EventSelectionModel<Match<Object, File>> selectionModel = new EventSelectionModel<Match<Object, File>>(renameModel.matches());
|
||||
selectionModel.setSelectionMode(ListSelection.MULTIPLE_INTERVAL_SELECTION_DEFENSIVE);
|
||||
|
||||
// use the same selection model for both lists to synchronize selection
|
||||
namesList.getListComponent().setSelectionModel(selectionModel);
|
||||
|
@ -127,10 +125,6 @@ public class RenamePanel extends JComponent {
|
|||
add(renameButton, "gapy 30px, sizegroupx button");
|
||||
|
||||
add(new LoadingOverlayPane(namesList, namesList, "28px", "30px"), "grow, sizegroupx list");
|
||||
|
||||
// repaint on change
|
||||
renameModel.names().addListEventListener(new RepaintHandler<Object>());
|
||||
renameModel.files().addListEventListener(new RepaintHandler<Object>());
|
||||
}
|
||||
|
||||
|
||||
|
@ -240,7 +234,7 @@ public class RenamePanel extends JComponent {
|
|||
// add remaining file entries
|
||||
renameModel.files().addAll(remainingFiles());
|
||||
} catch (Exception e) {
|
||||
Logger.getLogger("ui").log(Level.WARNING, ExceptionUtilities.getRootCauseMessage(e), e);
|
||||
Logger.getLogger("ui").warning(ExceptionUtilities.getRootCauseMessage(e));
|
||||
} finally {
|
||||
// auto-match finished
|
||||
namesList.firePropertyChange(LOADING_PROPERTY, true, false);
|
||||
|
@ -278,7 +272,7 @@ public class RenamePanel extends JComponent {
|
|||
// multiple results have been found, user must select one
|
||||
SelectDialog<SearchResult> selectDialog = new SelectDialog<SearchResult>(SwingUtilities.getWindowAncestor(RenamePanel.this), probableMatches.isEmpty() ? searchResults : probableMatches);
|
||||
|
||||
selectDialog.getHeaderLabel().setText(String.format("Shows matching '%s':", query));
|
||||
selectDialog.getHeaderLabel().setText(String.format("Shows matching \"%s\":", query));
|
||||
|
||||
selectDialog.setVisible(true);
|
||||
|
||||
|
@ -299,17 +293,6 @@ public class RenamePanel extends JComponent {
|
|||
}
|
||||
}
|
||||
|
||||
|
||||
protected class RepaintHandler<E> implements ListEventListener<E> {
|
||||
|
||||
@Override
|
||||
public void listChanged(ListEvent<E> listChanges) {
|
||||
namesList.repaint();
|
||||
filesList.repaint();
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
protected final PreferencesEntry<EpisodeExpressionFormatter> persistentFormatExpression = Settings.userRoot().entry("rename.format", new AbstractAdapter<EpisodeExpressionFormatter>() {
|
||||
|
||||
@Override
|
||||
|
|
|
@ -12,6 +12,7 @@ import java.util.concurrent.CancellationException;
|
|||
import javax.swing.SwingWorker.StateValue;
|
||||
import javax.swing.event.SwingPropertyChangeSupport;
|
||||
|
||||
import net.sourceforge.filebot.hash.HashType;
|
||||
import net.sourceforge.tuned.ExceptionUtilities;
|
||||
|
||||
|
||||
|
|
|
@ -12,6 +12,9 @@ import java.util.concurrent.CancellationException;
|
|||
|
||||
import javax.swing.SwingWorker;
|
||||
|
||||
import net.sourceforge.filebot.hash.Hash;
|
||||
import net.sourceforge.filebot.hash.HashType;
|
||||
|
||||
|
||||
class ChecksumComputationTask extends SwingWorker<Map<HashType, String>, Void> {
|
||||
|
||||
|
|
|
@ -16,6 +16,7 @@ import java.util.Set;
|
|||
import javax.swing.event.SwingPropertyChangeSupport;
|
||||
|
||||
import net.sourceforge.filebot.FileBotUtilities;
|
||||
import net.sourceforge.filebot.hash.HashType;
|
||||
|
||||
|
||||
class ChecksumRow {
|
||||
|
|
|
@ -9,6 +9,8 @@ import java.util.Date;
|
|||
import java.util.Formatter;
|
||||
|
||||
import net.sourceforge.filebot.Settings;
|
||||
import net.sourceforge.filebot.hash.HashType;
|
||||
import net.sourceforge.filebot.hash.VerificationFilePrinter;
|
||||
import net.sourceforge.filebot.ui.transfer.TextFileExportHandler;
|
||||
import net.sourceforge.tuned.FileUtilities;
|
||||
|
||||
|
|
|
@ -18,6 +18,7 @@ import java.util.Set;
|
|||
|
||||
import javax.swing.table.AbstractTableModel;
|
||||
|
||||
import net.sourceforge.filebot.hash.HashType;
|
||||
import net.sourceforge.tuned.FileUtilities;
|
||||
|
||||
|
||||
|
|
|
@ -15,7 +15,9 @@ import java.util.concurrent.ExecutorService;
|
|||
import java.util.logging.Level;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
import net.sourceforge.filebot.ui.panel.sfv.VerificationFileScanner.IllegalSyntaxException;
|
||||
import net.sourceforge.filebot.hash.HashType;
|
||||
import net.sourceforge.filebot.hash.IllegalSyntaxException;
|
||||
import net.sourceforge.filebot.hash.VerificationFileScanner;
|
||||
import net.sourceforge.filebot.ui.transfer.BackgroundFileTransferablePolicy;
|
||||
import net.sourceforge.tuned.ExceptionUtilities;
|
||||
import net.sourceforge.tuned.FileUtilities.ExtensionFileFilter;
|
||||
|
|
|
@ -30,6 +30,7 @@ import javax.swing.border.TitledBorder;
|
|||
|
||||
import net.miginfocom.swing.MigLayout;
|
||||
import net.sourceforge.filebot.ResourceManager;
|
||||
import net.sourceforge.filebot.hash.HashType;
|
||||
import net.sourceforge.filebot.ui.SelectDialog;
|
||||
import net.sourceforge.filebot.ui.transfer.DefaultTransferHandler;
|
||||
import net.sourceforge.filebot.ui.transfer.LoadAction;
|
||||
|
|
|
@ -26,7 +26,10 @@ public class DefaultThreadFactory implements ThreadFactory {
|
|||
|
||||
|
||||
public DefaultThreadFactory(String groupName, int priority, boolean daemon) {
|
||||
group = new ThreadGroup(groupName);
|
||||
SecurityManager sm = System.getSecurityManager();
|
||||
ThreadGroup parentGroup = (sm != null) ? sm.getThreadGroup() : Thread.currentThread().getThreadGroup();
|
||||
|
||||
this.group = new ThreadGroup(parentGroup, groupName);
|
||||
|
||||
this.daemon = daemon;
|
||||
this.priority = priority;
|
||||
|
@ -45,4 +48,9 @@ public class DefaultThreadFactory implements ThreadFactory {
|
|||
return thread;
|
||||
}
|
||||
|
||||
|
||||
public ThreadGroup getThreadGroup() {
|
||||
return group;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -13,6 +13,19 @@ public final class ExceptionUtilities {
|
|||
}
|
||||
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
public static <T extends Throwable> T findCause(Throwable t, Class<T> type) {
|
||||
while (t != null) {
|
||||
if (type.isInstance(t))
|
||||
return (T) t;
|
||||
|
||||
t = t.getCause();
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
public static String getRootCauseMessage(Throwable t) {
|
||||
return getMessage(getRootCause(t));
|
||||
}
|
||||
|
@ -22,7 +35,7 @@ public final class ExceptionUtilities {
|
|||
String message = t.getMessage();
|
||||
|
||||
if (message == null || message.isEmpty()) {
|
||||
message = t.toString().replaceAll(t.getClass().getName(), t.getClass().getSimpleName());
|
||||
message = t.toString();
|
||||
}
|
||||
|
||||
return message;
|
||||
|
|
|
@ -5,6 +5,7 @@ package net.sourceforge.tuned.ui;
|
|||
import java.awt.Color;
|
||||
import java.awt.Insets;
|
||||
|
||||
import javax.swing.DefaultListCellRenderer;
|
||||
import javax.swing.Icon;
|
||||
import javax.swing.JLabel;
|
||||
import javax.swing.JList;
|
||||
|
@ -12,7 +13,7 @@ import javax.swing.JList;
|
|||
|
||||
public class DefaultFancyListCellRenderer extends AbstractFancyListCellRenderer {
|
||||
|
||||
private final JLabel label = new JLabel();
|
||||
private final JLabel label = new DefaultListCellRenderer();
|
||||
|
||||
|
||||
public DefaultFancyListCellRenderer() {
|
||||
|
|
|
@ -3,9 +3,9 @@ package net.sourceforge.filebot;
|
|||
|
||||
|
||||
import net.sourceforge.filebot.format.ExpressionFormatTest;
|
||||
import net.sourceforge.filebot.hash.VerificationFileScannerTest;
|
||||
import net.sourceforge.filebot.similarity.SimilarityTestSuite;
|
||||
import net.sourceforge.filebot.ui.panel.rename.MatchModelTest;
|
||||
import net.sourceforge.filebot.ui.panel.sfv.VerificationFileScannerTest;
|
||||
import net.sourceforge.filebot.web.WebTestSuite;
|
||||
|
||||
import org.junit.runner.RunWith;
|
||||
|
|
|
@ -1,8 +1,9 @@
|
|||
|
||||
package net.sourceforge.filebot.ui.panel.sfv;
|
||||
package net.sourceforge.filebot.hash;
|
||||
|
||||
|
||||
import static org.junit.Assert.*;
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertFalse;
|
||||
|
||||
import java.io.File;
|
||||
import java.util.Scanner;
|
Loading…
Reference in New Issue