mirror of
https://github.com/mitb-archive/filebot
synced 2024-11-16 06:15:02 -05:00
+ 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
|
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
|
<defaultCache
|
||||||
maxElementsInMemory="100"
|
maxElementsInMemory="100"
|
||||||
|
@ -23,9 +23,6 @@ public class ArgumentBean {
|
|||||||
@Option(name = "-clear", usage = "Clear history and settings")
|
@Option(name = "-clear", usage = "Clear history and settings")
|
||||||
private boolean clear = false;
|
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>")
|
@Option(name = "--sfv", usage = "Open file in 'SFV' panel", metaVar = "<file>")
|
||||||
private boolean sfv;
|
private boolean sfv;
|
||||||
|
|
||||||
@ -48,11 +45,6 @@ public class ArgumentBean {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
public boolean analyze() {
|
|
||||||
return analyze;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
public List<File> arguments() {
|
public List<File> arguments() {
|
||||||
return arguments;
|
return arguments;
|
||||||
}
|
}
|
||||||
|
@ -4,6 +4,12 @@ package net.sourceforge.filebot;
|
|||||||
|
|
||||||
import static javax.swing.JFrame.EXIT_ON_CLOSE;
|
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.ConsoleHandler;
|
||||||
import java.util.logging.Level;
|
import java.util.logging.Level;
|
||||||
import java.util.logging.Logger;
|
import java.util.logging.Logger;
|
||||||
@ -12,10 +18,10 @@ import javax.swing.JFrame;
|
|||||||
import javax.swing.SwingUtilities;
|
import javax.swing.SwingUtilities;
|
||||||
import javax.swing.UIManager;
|
import javax.swing.UIManager;
|
||||||
|
|
||||||
|
import net.sourceforge.filebot.format.ExpressionFormat;
|
||||||
import net.sourceforge.filebot.ui.MainFrame;
|
import net.sourceforge.filebot.ui.MainFrame;
|
||||||
import net.sourceforge.filebot.ui.NotificationLoggingHandler;
|
import net.sourceforge.filebot.ui.NotificationLoggingHandler;
|
||||||
import net.sourceforge.filebot.ui.SinglePanelFrame;
|
import net.sourceforge.filebot.ui.SinglePanelFrame;
|
||||||
import net.sourceforge.filebot.ui.panel.analyze.AnalyzePanelBuilder;
|
|
||||||
import net.sourceforge.filebot.ui.panel.sfv.SfvPanelBuilder;
|
import net.sourceforge.filebot.ui.panel.sfv.SfvPanelBuilder;
|
||||||
|
|
||||||
import org.kohsuke.args4j.CmdLineException;
|
import org.kohsuke.args4j.CmdLineException;
|
||||||
@ -45,6 +51,7 @@ public class Main {
|
|||||||
|
|
||||||
initializeLogging();
|
initializeLogging();
|
||||||
initializeSettings();
|
initializeSettings();
|
||||||
|
initializeSecurityManager();
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// use native laf an all platforms
|
// use native laf an all platforms
|
||||||
@ -59,19 +66,18 @@ public class Main {
|
|||||||
public void run() {
|
public void run() {
|
||||||
JFrame frame;
|
JFrame frame;
|
||||||
|
|
||||||
if (argumentBean.analyze()) {
|
if (argumentBean.sfv()) {
|
||||||
frame = new SinglePanelFrame(new AnalyzePanelBuilder()).publish(argumentBean.transferable());
|
// sfv frame
|
||||||
} else if (argumentBean.sfv()) {
|
|
||||||
frame = new SinglePanelFrame(new SfvPanelBuilder()).publish(argumentBean.transferable());
|
frame = new SinglePanelFrame(new SfvPanelBuilder()).publish(argumentBean.transferable());
|
||||||
} else {
|
} else {
|
||||||
// default
|
// default frame
|
||||||
frame = new MainFrame();
|
frame = new MainFrame();
|
||||||
}
|
}
|
||||||
|
|
||||||
frame.setLocationByPlatform(true);
|
frame.setLocationByPlatform(true);
|
||||||
frame.setDefaultCloseOperation(EXIT_ON_CLOSE);
|
frame.setDefaultCloseOperation(EXIT_ON_CLOSE);
|
||||||
|
|
||||||
// start
|
// start application
|
||||||
frame.setVisible(true);
|
frame.setVisible(true);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@ -95,11 +101,51 @@ public class Main {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Preset the default thetvdb.apikey.
|
||||||
|
*/
|
||||||
private static void initializeSettings() {
|
private static void initializeSettings() {
|
||||||
Settings.userRoot().putDefault("thetvdb.apikey", "58B4AA94C59AD656");
|
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 {
|
private static ArgumentBean initializeArgumentBean(String... args) throws CmdLineException {
|
||||||
ArgumentBean argumentBean = new ArgumentBean();
|
ArgumentBean argumentBean = new ArgumentBean();
|
||||||
|
|
||||||
@ -109,6 +155,9 @@ public class Main {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Print command line argument usage.
|
||||||
|
*/
|
||||||
private static void printUsage(ArgumentBean argumentBean) {
|
private static void printUsage(ArgumentBean argumentBean) {
|
||||||
System.out.println("Options:");
|
System.out.println("Options:");
|
||||||
|
|
||||||
|
@ -2,6 +2,7 @@
|
|||||||
package net.sourceforge.filebot.format;
|
package net.sourceforge.filebot.format;
|
||||||
|
|
||||||
|
|
||||||
|
import static net.sourceforge.filebot.FileBotUtilities.SFV_FILES;
|
||||||
import static net.sourceforge.filebot.format.Define.undefined;
|
import static net.sourceforge.filebot.format.Define.undefined;
|
||||||
|
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
@ -9,15 +10,21 @@ import java.io.FileInputStream;
|
|||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.io.InputStream;
|
import java.io.InputStream;
|
||||||
import java.util.Scanner;
|
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 java.util.zip.CRC32;
|
||||||
|
|
||||||
import net.sf.ehcache.Cache;
|
import net.sf.ehcache.Cache;
|
||||||
import net.sf.ehcache.CacheManager;
|
import net.sf.ehcache.CacheManager;
|
||||||
import net.sf.ehcache.Element;
|
import net.sf.ehcache.Element;
|
||||||
import net.sourceforge.filebot.FileBotUtilities;
|
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;
|
||||||
import net.sourceforge.filebot.mediainfo.MediaInfo.StreamKind;
|
import net.sourceforge.filebot.mediainfo.MediaInfo.StreamKind;
|
||||||
import net.sourceforge.filebot.web.Episode;
|
import net.sourceforge.filebot.web.Episode;
|
||||||
|
import net.sourceforge.tuned.FileUtilities;
|
||||||
|
|
||||||
|
|
||||||
public class EpisodeFormatBindingBean {
|
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")
|
@Define("hi")
|
||||||
public String getHeightAndInterlacement() {
|
public String getHeightAndInterlacement() {
|
||||||
String height = getMediaInfo(StreamKind.Video, 0, "Height");
|
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")
|
@Define("resolution")
|
||||||
public String getVideoResolution() {
|
public String getVideoResolution() {
|
||||||
String width = getMediaInfo(StreamKind.Video, 0, "Width");
|
String width = getMediaInfo(StreamKind.Video, 0, "Width");
|
||||||
@ -117,11 +125,16 @@ public class EpisodeFormatBindingBean {
|
|||||||
public String getCRC32() throws IOException, InterruptedException {
|
public String getCRC32() throws IOException, InterruptedException {
|
||||||
if (mediaFile != null) {
|
if (mediaFile != null) {
|
||||||
// try to get checksum from file name
|
// try to get checksum from file name
|
||||||
String embeddedChecksum = FileBotUtilities.getEmbeddedChecksum(mediaFile.getName());
|
String checksum = FileBotUtilities.getEmbeddedChecksum(mediaFile.getName());
|
||||||
|
|
||||||
if (embeddedChecksum != null) {
|
if (checksum != null)
|
||||||
return embeddedChecksum;
|
return checksum;
|
||||||
}
|
|
||||||
|
// try to get checksum from sfv file
|
||||||
|
checksum = getChecksumFromSfvFile(mediaFile);
|
||||||
|
|
||||||
|
if (checksum != null)
|
||||||
|
return checksum;
|
||||||
|
|
||||||
// calculate checksum from file
|
// calculate checksum from file
|
||||||
return crc32(mediaFile);
|
return crc32(mediaFile);
|
||||||
@ -131,6 +144,13 @@ public class EpisodeFormatBindingBean {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Define("ext")
|
||||||
|
public String getContainerExtension() {
|
||||||
|
// file extension
|
||||||
|
return FileUtilities.getExtension(mediaFile);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
@Define("general")
|
@Define("general")
|
||||||
public Object getGeneralMediaInfo() {
|
public Object getGeneralMediaInfo() {
|
||||||
return new AssociativeScriptObject(getMediaInfo().snapshot(StreamKind.General, 0));
|
return new AssociativeScriptObject(getMediaInfo().snapshot(StreamKind.General, 0));
|
||||||
@ -161,11 +181,13 @@ public class EpisodeFormatBindingBean {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Define("episode")
|
||||||
public Episode getEpisode() {
|
public Episode getEpisode() {
|
||||||
return episode;
|
return episode;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Define("file")
|
||||||
public File getMediaFile() {
|
public File getMediaFile() {
|
||||||
return mediaFile;
|
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 {
|
private String crc32(File file) throws IOException, InterruptedException {
|
||||||
// try to get checksum from cache
|
// try to get checksum from cache
|
||||||
Cache cache = CacheManager.getInstance().getCache("checksum");
|
Cache cache = CacheManager.getInstance().getCache("checksum");
|
||||||
|
@ -16,16 +16,16 @@ import net.sourceforge.tuned.ExceptionUtilities;
|
|||||||
|
|
||||||
public class ExpressionBindings extends AbstractMap<String, Object> implements Bindings {
|
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>();
|
protected final Map<String, Method> bindings = new HashMap<String, Method>();
|
||||||
|
|
||||||
|
|
||||||
public ExpressionBindings(Object bindingBean) {
|
public ExpressionBindings(Object bindingBean) {
|
||||||
bean = bindingBean;
|
this.bindingBean = bindingBean;
|
||||||
|
|
||||||
// get method bindings
|
// get method bindings
|
||||||
for (Method method : bean.getClass().getMethods()) {
|
for (Method method : bindingBean.getClass().getMethods()) {
|
||||||
Define define = method.getAnnotation(Define.class);
|
Define define = method.getAnnotation(Define.class);
|
||||||
|
|
||||||
if (define != null) {
|
if (define != null) {
|
||||||
@ -41,19 +41,19 @@ public class ExpressionBindings extends AbstractMap<String, Object> implements B
|
|||||||
|
|
||||||
|
|
||||||
public Object getBindingBean() {
|
public Object getBindingBean() {
|
||||||
return bean;
|
return bindingBean;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
protected Object evaluate(Method method) throws Exception {
|
protected Object evaluate(final Method method) throws Exception {
|
||||||
Object value = method.invoke(getBindingBean());
|
Object value = method.invoke(bindingBean);
|
||||||
|
|
||||||
if (value != null) {
|
if (value != null) {
|
||||||
return value;
|
return value;
|
||||||
}
|
}
|
||||||
|
|
||||||
// invoke fallback method
|
// 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;
|
package net.sourceforge.filebot.format;
|
||||||
|
|
||||||
|
|
||||||
|
import java.io.FilePermission;
|
||||||
import java.io.InputStreamReader;
|
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.FieldPosition;
|
||||||
import java.text.Format;
|
import java.text.Format;
|
||||||
import java.text.ParsePosition;
|
import java.text.ParsePosition;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.PropertyPermission;
|
||||||
import java.util.regex.Matcher;
|
import java.util.regex.Matcher;
|
||||||
import java.util.regex.Pattern;
|
import java.util.regex.Pattern;
|
||||||
|
|
||||||
@ -19,6 +33,10 @@ import javax.script.ScriptEngine;
|
|||||||
import javax.script.ScriptException;
|
import javax.script.ScriptException;
|
||||||
import javax.script.SimpleScriptContext;
|
import javax.script.SimpleScriptContext;
|
||||||
|
|
||||||
|
import net.sourceforge.tuned.ExceptionUtilities;
|
||||||
|
|
||||||
|
import org.mozilla.javascript.EcmaError;
|
||||||
|
|
||||||
import com.sun.phobos.script.javascript.RhinoScriptEngine;
|
import com.sun.phobos.script.javascript.RhinoScriptEngine;
|
||||||
|
|
||||||
|
|
||||||
@ -33,7 +51,7 @@ public class ExpressionFormat extends Format {
|
|||||||
|
|
||||||
public ExpressionFormat(String expression) throws ScriptException {
|
public ExpressionFormat(String expression) throws ScriptException {
|
||||||
this.expression = expression;
|
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) {
|
public StringBuffer format(Bindings bindings, StringBuffer sb) {
|
||||||
ScriptContext context = new SimpleScriptContext();
|
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) {
|
for (Object snipped : compilation) {
|
||||||
if (snipped instanceof CompiledScript) {
|
if (snipped instanceof CompiledScript) {
|
||||||
@ -108,9 +128,16 @@ public class ExpressionFormat extends Format {
|
|||||||
sb.append(value);
|
sb.append(value);
|
||||||
}
|
}
|
||||||
} catch (ScriptException e) {
|
} catch (ScriptException e) {
|
||||||
lastException = e;
|
EcmaError ecmaError = ExceptionUtilities.findCause(e, EcmaError.class);
|
||||||
} catch (Exception e) {
|
|
||||||
lastException = new ScriptException(e);
|
// try to unwrap EcmaError
|
||||||
|
if (ecmaError != null) {
|
||||||
|
lastException = new ExpressionException(String.format("%s: %s", ecmaError.getName(), ecmaError.getErrorMessage()), e);
|
||||||
|
} else {
|
||||||
|
lastException = e;
|
||||||
|
}
|
||||||
|
} catch (RuntimeException e) {
|
||||||
|
lastException = new ExpressionException(e);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
sb.append(snipped);
|
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
|
@Override
|
||||||
public Object parseObject(String source, ParsePosition pos) {
|
public Object parseObject(String source, ParsePosition pos) {
|
||||||
throw new UnsupportedOperationException();
|
throw new UnsupportedOperationException();
|
||||||
|
@ -1,11 +1,11 @@
|
|||||||
|
|
||||||
package net.sourceforge.filebot.ui.panel.sfv;
|
package net.sourceforge.filebot.hash;
|
||||||
|
|
||||||
|
|
||||||
import java.util.zip.Checksum;
|
import java.util.zip.Checksum;
|
||||||
|
|
||||||
|
|
||||||
class ChecksumHash implements Hash {
|
public class ChecksumHash implements Hash {
|
||||||
|
|
||||||
private final Checksum checksum;
|
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);
|
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.Formatter;
|
||||||
import java.util.Scanner;
|
import java.util.Scanner;
|
||||||
import java.util.Map.Entry;
|
|
||||||
import java.util.regex.Matcher;
|
|
||||||
import java.util.regex.Pattern;
|
|
||||||
import java.util.zip.CRC32;
|
import java.util.zip.CRC32;
|
||||||
|
|
||||||
|
|
||||||
enum HashType {
|
public enum HashType {
|
||||||
|
|
||||||
SFV {
|
SFV {
|
||||||
|
|
||||||
@ -23,44 +19,13 @@ enum HashType {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public VerificationFileScanner newScanner(Scanner scanner) {
|
public VerificationFileScanner newScanner(Scanner scanner) {
|
||||||
// adapt default scanner to sfv line syntax
|
return new SfvFileScanner(scanner);
|
||||||
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));
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public VerificationFilePrinter newPrinter(Formatter out) {
|
public VerificationFilePrinter newPrinter(Formatter out) {
|
||||||
return new VerificationFilePrinter(out, "CRC32") {
|
return new SfvFilePrinter(out);
|
||||||
|
|
||||||
@Override
|
|
||||||
public void print(String path, String hash) {
|
|
||||||
// e.g folder/file.txt 970E4EF1
|
|
||||||
out.format(String.format("%s %s", path, hash));
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
},
|
},
|
@ -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;
|
import java.math.BigInteger;
|
||||||
@ -7,7 +7,7 @@ import java.security.MessageDigest;
|
|||||||
import java.security.NoSuchAlgorithmException;
|
import java.security.NoSuchAlgorithmException;
|
||||||
|
|
||||||
|
|
||||||
class MessageDigestHash implements Hash {
|
public class MessageDigestHash implements Hash {
|
||||||
|
|
||||||
private final MessageDigest md;
|
private final MessageDigest md;
|
||||||
|
|
20
source/net/sourceforge/filebot/hash/SfvFilePrinter.java
Normal file
20
source/net/sourceforge/filebot/hash/SfvFilePrinter.java
Normal file
@ -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));
|
||||||
|
}
|
||||||
|
}
|
46
source/net/sourceforge/filebot/hash/SfvFileScanner.java
Normal file
46
source/net/sourceforge/filebot/hash/SfvFileScanner.java
Normal file
@ -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;
|
import java.io.Closeable;
|
||||||
@ -7,7 +7,7 @@ import java.io.IOException;
|
|||||||
import java.util.Formatter;
|
import java.util.Formatter;
|
||||||
|
|
||||||
|
|
||||||
class VerificationFilePrinter implements Closeable {
|
public class VerificationFilePrinter implements Closeable {
|
||||||
|
|
||||||
protected final Formatter out;
|
protected final Formatter out;
|
||||||
protected final String algorithm;
|
protected final String algorithm;
|
||||||
@ -20,17 +20,8 @@ class VerificationFilePrinter implements Closeable {
|
|||||||
|
|
||||||
|
|
||||||
public void println(String path, String hash) {
|
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
|
// 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;
|
import java.io.Closeable;
|
||||||
@ -16,7 +16,7 @@ import java.util.regex.Matcher;
|
|||||||
import java.util.regex.Pattern;
|
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;
|
private final Scanner scanner;
|
||||||
|
|
||||||
@ -48,7 +48,7 @@ class VerificationFileScanner implements Iterator<Entry<File, String>>, Closeabl
|
|||||||
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Entry<File, String> next() {
|
public Entry<File, String> next() throws IllegalSyntaxException {
|
||||||
// cache next line
|
// cache next line
|
||||||
if (!hasNext()) {
|
if (!hasNext()) {
|
||||||
throw new NoSuchElementException();
|
throw new NoSuchElementException();
|
||||||
@ -88,10 +88,10 @@ class VerificationFileScanner implements Iterator<Entry<File, String>>, Closeabl
|
|||||||
* | Group 1 | | Group 2 |
|
* | Group 1 | | Group 2 |
|
||||||
* </pre>
|
* </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);
|
Matcher matcher = pattern.matcher(line);
|
||||||
|
|
||||||
if (!matcher.matches())
|
if (!matcher.matches())
|
||||||
@ -127,18 +127,4 @@ class VerificationFileScanner implements Iterator<Entry<File, String>>, Closeabl
|
|||||||
throw new UnsupportedOperationException();
|
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;
|
package net.sourceforge.filebot.torrent;
|
||||||
|
|
||||||
|
|
||||||
|
import java.io.BufferedInputStream;
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
import java.io.FileInputStream;
|
import java.io.FileInputStream;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.nio.channels.FileChannel;
|
import java.io.InputStream;
|
||||||
import java.nio.channels.FileChannel.MapMode;
|
|
||||||
import java.nio.charset.Charset;
|
import java.nio.charset.Charset;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
@ -16,8 +16,6 @@ import java.util.Map;
|
|||||||
import java.util.logging.Level;
|
import java.util.logging.Level;
|
||||||
import java.util.logging.Logger;
|
import java.util.logging.Logger;
|
||||||
|
|
||||||
import net.sourceforge.tuned.ByteBufferInputStream;
|
|
||||||
|
|
||||||
|
|
||||||
public class Torrent {
|
public class Torrent {
|
||||||
|
|
||||||
@ -107,13 +105,12 @@ public class Torrent {
|
|||||||
|
|
||||||
|
|
||||||
private static Map<?, ?> decodeTorrent(File torrent) throws IOException {
|
private static Map<?, ?> decodeTorrent(File torrent) throws IOException {
|
||||||
FileChannel fileChannel = new FileInputStream(torrent).getChannel();
|
InputStream in = new BufferedInputStream(new FileInputStream(torrent));
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// memory-map and decode torrent
|
return BDecoder.decode(in);
|
||||||
return BDecoder.decode(new ByteBufferInputStream(fileChannel.map(MapMode.READ_ONLY, 0, fileChannel.size())));
|
|
||||||
} finally {
|
} finally {
|
||||||
fileChannel.close();
|
in.close();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -206,6 +203,12 @@ public class Torrent {
|
|||||||
public String getPath() {
|
public String getPath() {
|
||||||
return path;
|
return path;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return getPath();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -17,7 +17,9 @@ import java.beans.PropertyChangeEvent;
|
|||||||
import java.beans.PropertyChangeListener;
|
import java.beans.PropertyChangeListener;
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
import java.text.ParseException;
|
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.ResourceBundle;
|
||||||
import java.util.concurrent.ArrayBlockingQueue;
|
import java.util.concurrent.ArrayBlockingQueue;
|
||||||
import java.util.concurrent.ExecutorService;
|
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.format.ExpressionFormat;
|
||||||
import net.sourceforge.filebot.web.Episode;
|
import net.sourceforge.filebot.web.Episode;
|
||||||
import net.sourceforge.filebot.web.Episode.EpisodeFormat;
|
import net.sourceforge.filebot.web.Episode.EpisodeFormat;
|
||||||
|
import net.sourceforge.tuned.DefaultThreadFactory;
|
||||||
import net.sourceforge.tuned.ExceptionUtilities;
|
import net.sourceforge.tuned.ExceptionUtilities;
|
||||||
import net.sourceforge.tuned.ui.GradientStyle;
|
import net.sourceforge.tuned.ui.GradientStyle;
|
||||||
import net.sourceforge.tuned.ui.LinkButton;
|
import net.sourceforge.tuned.ui.LinkButton;
|
||||||
@ -68,8 +71,7 @@ public class EpisodeFormatDialog extends JDialog {
|
|||||||
|
|
||||||
private JLabel preview = new JLabel();
|
private JLabel preview = new JLabel();
|
||||||
|
|
||||||
private JLabel warningMessage = new JLabel(ResourceManager.getIcon("status.warning"));
|
private JLabel status = new JLabel();
|
||||||
private JLabel errorMessage = new JLabel(ResourceManager.getIcon("status.error"));
|
|
||||||
|
|
||||||
private EpisodeFormatBindingBean previewSample = new EpisodeFormatBindingBean(getPreviewSampleEpisode(), getPreviewSampleMediaFile());
|
private EpisodeFormatBindingBean previewSample = new EpisodeFormatBindingBean(getPreviewSampleEpisode(), getPreviewSampleMediaFile());
|
||||||
|
|
||||||
@ -107,15 +109,10 @@ public class EpisodeFormatDialog extends JDialog {
|
|||||||
header.setBackground(Color.white);
|
header.setBackground(Color.white);
|
||||||
header.setBorder(new SeparatorBorder(1, new Color(0xB4B4B4), new Color(0xACACAC), GradientStyle.LEFT_TO_RIGHT, Position.BOTTOM));
|
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(progressIndicator, "pos 1al 0al, hidemode 3");
|
||||||
header.add(title, "wrap unrel:push");
|
header.add(title, "wrap unrel:push");
|
||||||
header.add(preview, "gap indent, hidemode 3, wmax 90%");
|
header.add(preview, "hmin 16px, gap indent, hidemode 3, wmax 90%");
|
||||||
header.add(errorMessage, "gap indent, hidemode 3, wmax 90%, newline");
|
header.add(status, "hmin 16px, gap indent, hidemode 3, wmax 90%, newline");
|
||||||
header.add(warningMessage, "gap indent, hidemode 3, wmax 90%, newline");
|
|
||||||
|
|
||||||
JPanel content = new JPanel(new MigLayout("insets dialog, nogrid, fill"));
|
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(createSyntaxPanel(), "gapx indent indent, wrap 8px");
|
||||||
|
|
||||||
content.add(new JLabel("Examples"), "gap indent+unrel, wrap 0");
|
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(useDefaultFormatAction), "tag left");
|
||||||
content.add(new JButton(approveFormatAction), "tag apply");
|
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(header, "h 60px, growx, dock north");
|
||||||
pane.add(content, "grow");
|
pane.add(content, "grow");
|
||||||
|
|
||||||
setSize(485, 415);
|
|
||||||
|
|
||||||
header.setComponentPopupMenu(createPreviewSamplePopup());
|
header.setComponentPopupMenu(createPreviewSamplePopup());
|
||||||
|
|
||||||
setLocation(TunedUtilities.getPreferredLocation(this));
|
|
||||||
|
|
||||||
// update format on change
|
// update format on change
|
||||||
editor.getDocument().addDocumentListener(new LazyDocumentAdapter() {
|
editor.getDocument().addDocumentListener(new LazyDocumentAdapter() {
|
||||||
|
|
||||||
@ -171,6 +164,10 @@ public class EpisodeFormatDialog extends JDialog {
|
|||||||
|
|
||||||
// update preview to current format
|
// update preview to current format
|
||||||
firePreviewSampleChanged();
|
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"));
|
JPanel panel = new JPanel(new MigLayout("fill, wrap 3"));
|
||||||
|
|
||||||
panel.setBorder(new LineBorder(new Color(0xACA899)));
|
panel.setBorder(new LineBorder(new Color(0xACA899)));
|
||||||
panel.setBackground(new Color(0xFFFFE1));
|
panel.setBackground(new Color(0xFFFFE1));
|
||||||
panel.setOpaque(true);
|
|
||||||
|
|
||||||
ResourceBundle bundle = ResourceBundle.getBundle(getClass().getName());
|
ResourceBundle bundle = ResourceBundle.getBundle(getClass().getName());
|
||||||
|
|
||||||
// sort keys
|
// collect example keys
|
||||||
String[] keys = bundle.keySet().toArray(new String[0]);
|
List<String> examples = new ArrayList<String>();
|
||||||
Arrays.sort(keys);
|
|
||||||
|
|
||||||
for (String key : keys) {
|
for (String key : bundle.keySet()) {
|
||||||
if (key.startsWith("example")) {
|
if (key.startsWith("example"))
|
||||||
String format = bundle.getString(key);
|
examples.add(key);
|
||||||
|
}
|
||||||
|
|
||||||
LinkButton formatLink = new LinkButton(new ExampleFormatAction(format));
|
// sort by example key
|
||||||
formatLink.setFont(new Font(MONOSPACED, PLAIN, 11));
|
Collections.sort(examples);
|
||||||
|
|
||||||
panel.add(formatLink);
|
for (String key : examples) {
|
||||||
panel.add(new JLabel("..."));
|
final String format = bundle.getString(key);
|
||||||
panel.add(new ExampleFormatLabel(format));
|
|
||||||
}
|
LinkButton formatLink = new LinkButton(new AbstractAction(format) {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void actionPerformed(ActionEvent e) {
|
||||||
|
editor.setText(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(formatExample);
|
||||||
}
|
}
|
||||||
|
|
||||||
return panel;
|
return panel;
|
||||||
@ -307,7 +332,31 @@ public class EpisodeFormatDialog extends JDialog {
|
|||||||
|
|
||||||
|
|
||||||
private ExecutorService createPreviewExecutor() {
|
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
|
// only keep the latest task in the queue
|
||||||
executor.setRejectedExecutionHandler(new DiscardOldestPolicy());
|
executor.setRejectedExecutionHandler(new DiscardOldestPolicy());
|
||||||
@ -345,34 +394,33 @@ public class EpisodeFormatDialog extends JDialog {
|
|||||||
|
|
||||||
// check internal script exception and empty output
|
// check internal script exception and empty output
|
||||||
if (format.scriptException() != null) {
|
if (format.scriptException() != null) {
|
||||||
warningMessage.setText(format.scriptException().getCause().getMessage());
|
throw format.scriptException();
|
||||||
} else if (get().trim().isEmpty()) {
|
} else if (get().trim().isEmpty()) {
|
||||||
warningMessage.setText("Formatted value is empty");
|
throw new RuntimeException("Formatted value is empty");
|
||||||
} else {
|
|
||||||
warningMessage.setText(null);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// no warning or error
|
||||||
|
status.setVisible(false);
|
||||||
} catch (Exception e) {
|
} 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);
|
||||||
|
editor.setForeground(defaultColor);
|
||||||
|
|
||||||
|
progressIndicatorTimer.stop();
|
||||||
|
progressIndicator.setVisible(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
preview.setVisible(true);
|
|
||||||
warningMessage.setVisible(warningMessage.getText() != null);
|
|
||||||
errorMessage.setVisible(false);
|
|
||||||
|
|
||||||
editor.setForeground(defaultColor);
|
|
||||||
|
|
||||||
progressIndicatorTimer.stop();
|
|
||||||
progressIndicator.setVisible(false);
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
} catch (ScriptException e) {
|
} catch (ScriptException e) {
|
||||||
// incorrect syntax
|
// incorrect syntax
|
||||||
errorMessage.setText(ExceptionUtilities.getRootCauseMessage(e));
|
status.setText(ExceptionUtilities.getRootCauseMessage(e));
|
||||||
errorMessage.setVisible(true);
|
status.setIcon(ResourceManager.getIcon("status.error"));
|
||||||
|
status.setVisible(true);
|
||||||
|
|
||||||
preview.setVisible(false);
|
preview.setVisible(false);
|
||||||
warningMessage.setVisible(false);
|
|
||||||
|
|
||||||
editor.setForeground(errorColor);
|
editor.setForeground(errorColor);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -418,6 +466,9 @@ public class EpisodeFormatDialog extends JDialog {
|
|||||||
@Override
|
@Override
|
||||||
public void actionPerformed(ActionEvent evt) {
|
public void actionPerformed(ActionEvent evt) {
|
||||||
try {
|
try {
|
||||||
|
if (progressIndicator.isVisible())
|
||||||
|
throw new IllegalStateException("Format has not been verified yet.");
|
||||||
|
|
||||||
// check syntax
|
// check syntax
|
||||||
new ExpressionFormat(editor.getText());
|
new ExpressionFormat(editor.getText());
|
||||||
|
|
||||||
@ -425,8 +476,8 @@ public class EpisodeFormatDialog extends JDialog {
|
|||||||
Settings.userRoot().put("dialog.format", editor.getText());
|
Settings.userRoot().put("dialog.format", editor.getText());
|
||||||
|
|
||||||
finish(Option.APPROVE);
|
finish(Option.APPROVE);
|
||||||
} catch (ScriptException e) {
|
} catch (Exception e) {
|
||||||
Logger.getLogger("ui").log(Level.WARNING, ExceptionUtilities.getRootCauseMessage(e), 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 {
|
protected static abstract class LazyDocumentAdapter implements DocumentListener {
|
||||||
|
|
||||||
private final Timer timer = new Timer(200, new ActionListener() {
|
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
|
# basic 1.01
|
||||||
example[0]: {n} - {s}.{e} - {t}
|
example[0]: {n} - {s}.{e} - {t}
|
||||||
|
|
||||||
# 1x01
|
|
||||||
example[1]: {n} - {s+'x'}{e.pad(2)}
|
|
||||||
|
|
||||||
# S01E01
|
# 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
|
# 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.model = model;
|
||||||
this.metrics = createMetrics();
|
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;
|
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>>();
|
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.filebot.web.Episode;
|
||||||
import net.sourceforge.tuned.FastFile;
|
import net.sourceforge.tuned.FastFile;
|
||||||
|
|
||||||
import ca.odell.glazedlists.EventList;
|
|
||||||
|
|
||||||
|
|
||||||
class NamesListTransferablePolicy extends FileTransferablePolicy {
|
class NamesListTransferablePolicy extends FileTransferablePolicy {
|
||||||
|
|
||||||
private static final DataFlavor episodeArrayFlavor = ArrayTransferable.flavor(Episode.class);
|
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;
|
this.model = model;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -5,16 +5,15 @@ package net.sourceforge.filebot.ui.panel.rename;
|
|||||||
import java.awt.event.ActionEvent;
|
import java.awt.event.ActionEvent;
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.util.ArrayDeque;
|
import java.util.ArrayList;
|
||||||
import java.util.Deque;
|
import java.util.Collections;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map.Entry;
|
||||||
import java.util.logging.Logger;
|
import java.util.logging.Logger;
|
||||||
|
|
||||||
import javax.swing.AbstractAction;
|
import javax.swing.AbstractAction;
|
||||||
|
|
||||||
import net.sourceforge.filebot.ResourceManager;
|
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 {
|
class RenameAction extends AbstractAction {
|
||||||
@ -32,52 +31,32 @@ class RenameAction extends AbstractAction {
|
|||||||
|
|
||||||
|
|
||||||
public void actionPerformed(ActionEvent evt) {
|
public void actionPerformed(ActionEvent evt) {
|
||||||
Deque<Match<File, File>> todoQueue = new ArrayDeque<Match<File, File>>();
|
List<Entry<File, File>> renameLog = new ArrayList<Entry<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));
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
int renameCount = todoQueue.size();
|
for (Entry<File, File> mapping : model.getRenameMap().entrySet()) {
|
||||||
|
|
||||||
for (Match<File, File> match : todoQueue) {
|
|
||||||
// rename file
|
// rename file
|
||||||
if (!match.getValue().renameTo(match.getCandidate()))
|
if (!mapping.getKey().renameTo(mapping.getValue()))
|
||||||
throw new IOException(String.format("Failed to rename file: %s.", match.getValue().getName()));
|
throw new IOException(String.format("Failed to rename file: \"%s\".", mapping.getKey().getName()));
|
||||||
|
|
||||||
// revert in reverse order if renaming of all matches fails
|
// remember successfully renamed matches for possible revert
|
||||||
doneQueue.addFirst(match);
|
renameLog.add(mapping);
|
||||||
}
|
}
|
||||||
|
|
||||||
// renamed all matches successfully
|
// renamed all matches successfully
|
||||||
Logger.getLogger("ui").info(String.format("%d files renamed.", renameCount));
|
Logger.getLogger("ui").info(String.format("%d files renamed.", renameLog.size()));
|
||||||
} catch (IOException e) {
|
} catch (Exception e) {
|
||||||
// rename failed
|
// could not rename one of the files, revert all changes
|
||||||
Logger.getLogger("ui").warning(ExceptionUtilities.getRootCauseMessage(e));
|
Logger.getLogger("ui").warning(e.getMessage());
|
||||||
|
|
||||||
boolean revertSuccess = true;
|
// revert in reverse order
|
||||||
|
Collections.reverse(renameLog);
|
||||||
|
|
||||||
// revert rename operations
|
// revert rename operations
|
||||||
for (Match<File, File> match : doneQueue) {
|
for (Entry<File, File> mapping : renameLog) {
|
||||||
revertSuccess &= match.getCandidate().renameTo(match.getValue());
|
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;
|
package net.sourceforge.filebot.ui.panel.rename;
|
||||||
|
|
||||||
|
|
||||||
|
import static java.util.Collections.swap;
|
||||||
|
|
||||||
import java.awt.BorderLayout;
|
import java.awt.BorderLayout;
|
||||||
import java.awt.event.ActionEvent;
|
import java.awt.event.ActionEvent;
|
||||||
import java.awt.event.MouseAdapter;
|
import java.awt.event.MouseAdapter;
|
||||||
@ -58,16 +60,6 @@ class RenameList<E> extends FileBotList<E> {
|
|||||||
loadAction.putValue(LoadAction.TRANSFERABLE_POLICY, transferablePolicy);
|
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 LoadAction loadAction = new LoadAction(null);
|
||||||
|
|
||||||
private final AbstractAction upAction = new AbstractAction(null, ResourceManager.getIcon("action.up")) {
|
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();
|
int index = getListComponent().getSelectedIndex();
|
||||||
|
|
||||||
if (index > 0) {
|
if (index > 0) {
|
||||||
swap(index, index - 1);
|
swap(model, index, index - 1);
|
||||||
getListComponent().setSelectedIndex(index - 1);
|
getListComponent().setSelectedIndex(index - 1);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -88,7 +80,7 @@ class RenameList<E> extends FileBotList<E> {
|
|||||||
int index = getListComponent().getSelectedIndex();
|
int index = getListComponent().getSelectedIndex();
|
||||||
|
|
||||||
if (index < model.size() - 1) {
|
if (index < model.size() - 1) {
|
||||||
swap(index, index + 1);
|
swap(model, index, index + 1);
|
||||||
getListComponent().setSelectedIndex(index + 1);
|
getListComponent().setSelectedIndex(index + 1);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -109,8 +101,8 @@ class RenameList<E> extends FileBotList<E> {
|
|||||||
public void mouseDragged(MouseEvent m) {
|
public void mouseDragged(MouseEvent m) {
|
||||||
int currentIndex = getListComponent().getSelectedIndex();
|
int currentIndex = getListComponent().getSelectedIndex();
|
||||||
|
|
||||||
if (currentIndex != lastIndex) {
|
if (currentIndex != lastIndex && lastIndex >= 0 && currentIndex >= 0) {
|
||||||
swap(lastIndex, currentIndex);
|
swap(model, lastIndex, currentIndex);
|
||||||
lastIndex = currentIndex;
|
lastIndex = currentIndex;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -12,7 +12,7 @@ import java.awt.geom.Rectangle2D;
|
|||||||
import java.awt.geom.RoundRectangle2D;
|
import java.awt.geom.RoundRectangle2D;
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
|
|
||||||
import javax.swing.JLabel;
|
import javax.swing.DefaultListCellRenderer;
|
||||||
import javax.swing.JList;
|
import javax.swing.JList;
|
||||||
import javax.swing.border.CompoundBorder;
|
import javax.swing.border.CompoundBorder;
|
||||||
import javax.swing.border.EmptyBorder;
|
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.filebot.ui.panel.rename.RenameModel.FormattedFuture;
|
||||||
import net.sourceforge.tuned.FileUtilities;
|
import net.sourceforge.tuned.FileUtilities;
|
||||||
import net.sourceforge.tuned.ui.DefaultFancyListCellRenderer;
|
import net.sourceforge.tuned.ui.DefaultFancyListCellRenderer;
|
||||||
|
import net.sourceforge.tuned.ui.GradientStyle;
|
||||||
|
|
||||||
|
|
||||||
class RenameListCellRenderer extends DefaultFancyListCellRenderer {
|
class RenameListCellRenderer extends DefaultFancyListCellRenderer {
|
||||||
|
|
||||||
private final RenameModel renameModel;
|
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 noMatchGradientBeginColor = new Color(0xB7B7B7);
|
||||||
private final Color noMatchGradientEndColor = new Color(0x9A9A9A);
|
private final Color noMatchGradientEndColor = new Color(0x9A9A9A);
|
||||||
@ -39,8 +40,8 @@ class RenameListCellRenderer extends DefaultFancyListCellRenderer {
|
|||||||
|
|
||||||
setHighlightingEnabled(false);
|
setHighlightingEnabled(false);
|
||||||
|
|
||||||
setLayout(new MigLayout("fill, insets 0", "align left", "align center"));
|
setLayout(new MigLayout("insets 0, fill", "align left", "align center"));
|
||||||
add(typeLabel, "gap rel:push");
|
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) {
|
public void configureListCellRendererComponent(JList list, Object value, int index, boolean isSelected, boolean cellHasFocus) {
|
||||||
super.configureListCellRendererComponent(list, value, index, isSelected, cellHasFocus);
|
super.configureListCellRendererComponent(list, value, index, isSelected, cellHasFocus);
|
||||||
|
|
||||||
// reset
|
// reset decoration
|
||||||
setIcon(null);
|
setIcon(null);
|
||||||
typeLabel.setText(null);
|
typeRenderer.setVisible(false);
|
||||||
typeLabel.setAlpha(1.0f);
|
typeRenderer.setAlpha(1.0f);
|
||||||
|
|
||||||
if (value instanceof File) {
|
if (value instanceof File) {
|
||||||
// display file extension
|
// display file extension
|
||||||
File file = (File) value;
|
File file = (File) value;
|
||||||
|
|
||||||
setText(FileUtilities.getName(file));
|
if (renameModel.preserveExtension()) {
|
||||||
typeLabel.setText(getType(file));
|
setText(FileUtilities.getName(file));
|
||||||
|
typeRenderer.setText(getType(file));
|
||||||
|
typeRenderer.setVisible(true);
|
||||||
|
} else {
|
||||||
|
setText(file.getName());
|
||||||
|
}
|
||||||
} else if (value instanceof FormattedFuture) {
|
} else if (value instanceof FormattedFuture) {
|
||||||
// progress icon and value type
|
// display progress icon
|
||||||
FormattedFuture future = (FormattedFuture) value;
|
FormattedFuture future = (FormattedFuture) value;
|
||||||
|
|
||||||
switch (future.getState()) {
|
switch (future.getState()) {
|
||||||
@ -78,7 +84,7 @@ class RenameListCellRenderer extends DefaultFancyListCellRenderer {
|
|||||||
setGradientColors(noMatchGradientBeginColor, noMatchGradientEndColor);
|
setGradientColors(noMatchGradientBeginColor, noMatchGradientEndColor);
|
||||||
} else {
|
} else {
|
||||||
setForeground(noMatchGradientBeginColor);
|
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 margin = new Insets(0, 10, 0, 0);
|
||||||
private final Insets padding = new Insets(0, 6, 0, 5);
|
private final Insets padding = new Insets(0, 6, 0, 5);
|
||||||
@ -110,7 +116,7 @@ class RenameListCellRenderer extends DefaultFancyListCellRenderer {
|
|||||||
private float alpha = 1.0f;
|
private float alpha = 1.0f;
|
||||||
|
|
||||||
|
|
||||||
public TypeLabel() {
|
public TypeRenderer() {
|
||||||
setOpaque(false);
|
setOpaque(false);
|
||||||
setForeground(new Color(0x141414));
|
setForeground(new Color(0x141414));
|
||||||
|
|
||||||
@ -128,7 +134,7 @@ class RenameListCellRenderer extends DefaultFancyListCellRenderer {
|
|||||||
|
|
||||||
g2d.setComposite(AlphaComposite.SrcOver.derive(alpha));
|
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.fill(shape);
|
||||||
|
|
||||||
g2d.setFont(getFont());
|
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) {
|
public void setAlpha(float alpha) {
|
||||||
this.alpha = alpha;
|
this.alpha = alpha;
|
||||||
}
|
}
|
||||||
|
@ -7,6 +7,7 @@ import java.beans.PropertyChangeListener;
|
|||||||
import java.io.File;
|
import java.io.File;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
|
import java.util.LinkedHashMap;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.concurrent.Executor;
|
import java.util.concurrent.Executor;
|
||||||
@ -20,6 +21,7 @@ import javax.swing.SwingWorker;
|
|||||||
import javax.swing.SwingWorker.StateValue;
|
import javax.swing.SwingWorker.StateValue;
|
||||||
|
|
||||||
import net.sourceforge.filebot.similarity.Match;
|
import net.sourceforge.filebot.similarity.Match;
|
||||||
|
import net.sourceforge.tuned.FileUtilities;
|
||||||
import net.sourceforge.tuned.ui.TunedUtilities;
|
import net.sourceforge.tuned.ui.TunedUtilities;
|
||||||
import ca.odell.glazedlists.EventList;
|
import ca.odell.glazedlists.EventList;
|
||||||
import ca.odell.glazedlists.TransformedList;
|
import ca.odell.glazedlists.TransformedList;
|
||||||
@ -52,17 +54,7 @@ public class RenameModel extends MatchModel<Object, File> {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
private boolean preserveExtension = true;
|
||||||
public void useFormatter(Class<?> type, MatchFormatter formatter) {
|
|
||||||
if (formatter != null) {
|
|
||||||
formatters.put(type, formatter);
|
|
||||||
} else {
|
|
||||||
formatters.remove(type);
|
|
||||||
}
|
|
||||||
|
|
||||||
// reformat matches
|
|
||||||
names.refresh();
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
public EventList<FormattedFuture> names() {
|
public EventList<FormattedFuture> names() {
|
||||||
@ -75,16 +67,62 @@ public class RenameModel extends MatchModel<Object, File> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
public List<Match<String, File>> getMatchesForRenaming() {
|
public boolean preserveExtension() {
|
||||||
List<Match<String, File>> matches = new ArrayList<Match<String, File>>();
|
return preserveExtension;
|
||||||
|
}
|
||||||
|
|
||||||
for (int i = 0; i < size(); i++) {
|
|
||||||
if (hasComplement(i) && names.get(i).isDone()) {
|
public void setPreserveExtension(boolean preserveExtension) {
|
||||||
matches.add(new Match<String, File>(names().get(i).toString(), files().get(i)));
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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 matches;
|
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.script.ScriptException;
|
||||||
import javax.swing.AbstractAction;
|
import javax.swing.AbstractAction;
|
||||||
import javax.swing.Action;
|
import javax.swing.Action;
|
||||||
import javax.swing.DefaultListSelectionModel;
|
|
||||||
import javax.swing.JButton;
|
import javax.swing.JButton;
|
||||||
import javax.swing.JComponent;
|
import javax.swing.JComponent;
|
||||||
import javax.swing.JLabel;
|
import javax.swing.JLabel;
|
||||||
import javax.swing.ListSelectionModel;
|
|
||||||
import javax.swing.SwingConstants;
|
import javax.swing.SwingConstants;
|
||||||
import javax.swing.SwingUtilities;
|
import javax.swing.SwingUtilities;
|
||||||
|
|
||||||
@ -53,8 +51,8 @@ import net.sourceforge.tuned.PreferencesMap.AbstractAdapter;
|
|||||||
import net.sourceforge.tuned.PreferencesMap.PreferencesEntry;
|
import net.sourceforge.tuned.PreferencesMap.PreferencesEntry;
|
||||||
import net.sourceforge.tuned.ui.ActionPopup;
|
import net.sourceforge.tuned.ui.ActionPopup;
|
||||||
import net.sourceforge.tuned.ui.LoadingOverlayPane;
|
import net.sourceforge.tuned.ui.LoadingOverlayPane;
|
||||||
import ca.odell.glazedlists.event.ListEvent;
|
import ca.odell.glazedlists.ListSelection;
|
||||||
import ca.odell.glazedlists.event.ListEventListener;
|
import ca.odell.glazedlists.swing.EventSelectionModel;
|
||||||
|
|
||||||
|
|
||||||
public class RenamePanel extends JComponent {
|
public class RenamePanel extends JComponent {
|
||||||
@ -88,8 +86,8 @@ public class RenamePanel extends JComponent {
|
|||||||
namesList.getListComponent().setCellRenderer(cellrenderer);
|
namesList.getListComponent().setCellRenderer(cellrenderer);
|
||||||
filesList.getListComponent().setCellRenderer(cellrenderer);
|
filesList.getListComponent().setCellRenderer(cellrenderer);
|
||||||
|
|
||||||
ListSelectionModel selectionModel = new DefaultListSelectionModel();
|
EventSelectionModel<Match<Object, File>> selectionModel = new EventSelectionModel<Match<Object, File>>(renameModel.matches());
|
||||||
selectionModel.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
|
selectionModel.setSelectionMode(ListSelection.MULTIPLE_INTERVAL_SELECTION_DEFENSIVE);
|
||||||
|
|
||||||
// use the same selection model for both lists to synchronize selection
|
// use the same selection model for both lists to synchronize selection
|
||||||
namesList.getListComponent().setSelectionModel(selectionModel);
|
namesList.getListComponent().setSelectionModel(selectionModel);
|
||||||
@ -127,10 +125,6 @@ public class RenamePanel extends JComponent {
|
|||||||
add(renameButton, "gapy 30px, sizegroupx button");
|
add(renameButton, "gapy 30px, sizegroupx button");
|
||||||
|
|
||||||
add(new LoadingOverlayPane(namesList, namesList, "28px", "30px"), "grow, sizegroupx list");
|
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
|
// add remaining file entries
|
||||||
renameModel.files().addAll(remainingFiles());
|
renameModel.files().addAll(remainingFiles());
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
Logger.getLogger("ui").log(Level.WARNING, ExceptionUtilities.getRootCauseMessage(e), e);
|
Logger.getLogger("ui").warning(ExceptionUtilities.getRootCauseMessage(e));
|
||||||
} finally {
|
} finally {
|
||||||
// auto-match finished
|
// auto-match finished
|
||||||
namesList.firePropertyChange(LOADING_PROPERTY, true, false);
|
namesList.firePropertyChange(LOADING_PROPERTY, true, false);
|
||||||
@ -278,7 +272,7 @@ public class RenamePanel extends JComponent {
|
|||||||
// multiple results have been found, user must select one
|
// multiple results have been found, user must select one
|
||||||
SelectDialog<SearchResult> selectDialog = new SelectDialog<SearchResult>(SwingUtilities.getWindowAncestor(RenamePanel.this), probableMatches.isEmpty() ? searchResults : probableMatches);
|
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);
|
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>() {
|
protected final PreferencesEntry<EpisodeExpressionFormatter> persistentFormatExpression = Settings.userRoot().entry("rename.format", new AbstractAdapter<EpisodeExpressionFormatter>() {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -12,6 +12,7 @@ import java.util.concurrent.CancellationException;
|
|||||||
import javax.swing.SwingWorker.StateValue;
|
import javax.swing.SwingWorker.StateValue;
|
||||||
import javax.swing.event.SwingPropertyChangeSupport;
|
import javax.swing.event.SwingPropertyChangeSupport;
|
||||||
|
|
||||||
|
import net.sourceforge.filebot.hash.HashType;
|
||||||
import net.sourceforge.tuned.ExceptionUtilities;
|
import net.sourceforge.tuned.ExceptionUtilities;
|
||||||
|
|
||||||
|
|
||||||
|
@ -12,6 +12,9 @@ import java.util.concurrent.CancellationException;
|
|||||||
|
|
||||||
import javax.swing.SwingWorker;
|
import javax.swing.SwingWorker;
|
||||||
|
|
||||||
|
import net.sourceforge.filebot.hash.Hash;
|
||||||
|
import net.sourceforge.filebot.hash.HashType;
|
||||||
|
|
||||||
|
|
||||||
class ChecksumComputationTask extends SwingWorker<Map<HashType, String>, Void> {
|
class ChecksumComputationTask extends SwingWorker<Map<HashType, String>, Void> {
|
||||||
|
|
||||||
|
@ -16,6 +16,7 @@ import java.util.Set;
|
|||||||
import javax.swing.event.SwingPropertyChangeSupport;
|
import javax.swing.event.SwingPropertyChangeSupport;
|
||||||
|
|
||||||
import net.sourceforge.filebot.FileBotUtilities;
|
import net.sourceforge.filebot.FileBotUtilities;
|
||||||
|
import net.sourceforge.filebot.hash.HashType;
|
||||||
|
|
||||||
|
|
||||||
class ChecksumRow {
|
class ChecksumRow {
|
||||||
|
@ -9,6 +9,8 @@ import java.util.Date;
|
|||||||
import java.util.Formatter;
|
import java.util.Formatter;
|
||||||
|
|
||||||
import net.sourceforge.filebot.Settings;
|
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.filebot.ui.transfer.TextFileExportHandler;
|
||||||
import net.sourceforge.tuned.FileUtilities;
|
import net.sourceforge.tuned.FileUtilities;
|
||||||
|
|
||||||
|
@ -18,6 +18,7 @@ import java.util.Set;
|
|||||||
|
|
||||||
import javax.swing.table.AbstractTableModel;
|
import javax.swing.table.AbstractTableModel;
|
||||||
|
|
||||||
|
import net.sourceforge.filebot.hash.HashType;
|
||||||
import net.sourceforge.tuned.FileUtilities;
|
import net.sourceforge.tuned.FileUtilities;
|
||||||
|
|
||||||
|
|
||||||
|
@ -15,7 +15,9 @@ import java.util.concurrent.ExecutorService;
|
|||||||
import java.util.logging.Level;
|
import java.util.logging.Level;
|
||||||
import java.util.logging.Logger;
|
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.filebot.ui.transfer.BackgroundFileTransferablePolicy;
|
||||||
import net.sourceforge.tuned.ExceptionUtilities;
|
import net.sourceforge.tuned.ExceptionUtilities;
|
||||||
import net.sourceforge.tuned.FileUtilities.ExtensionFileFilter;
|
import net.sourceforge.tuned.FileUtilities.ExtensionFileFilter;
|
||||||
|
@ -30,6 +30,7 @@ import javax.swing.border.TitledBorder;
|
|||||||
|
|
||||||
import net.miginfocom.swing.MigLayout;
|
import net.miginfocom.swing.MigLayout;
|
||||||
import net.sourceforge.filebot.ResourceManager;
|
import net.sourceforge.filebot.ResourceManager;
|
||||||
|
import net.sourceforge.filebot.hash.HashType;
|
||||||
import net.sourceforge.filebot.ui.SelectDialog;
|
import net.sourceforge.filebot.ui.SelectDialog;
|
||||||
import net.sourceforge.filebot.ui.transfer.DefaultTransferHandler;
|
import net.sourceforge.filebot.ui.transfer.DefaultTransferHandler;
|
||||||
import net.sourceforge.filebot.ui.transfer.LoadAction;
|
import net.sourceforge.filebot.ui.transfer.LoadAction;
|
||||||
|
@ -26,7 +26,10 @@ public class DefaultThreadFactory implements ThreadFactory {
|
|||||||
|
|
||||||
|
|
||||||
public DefaultThreadFactory(String groupName, int priority, boolean daemon) {
|
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.daemon = daemon;
|
||||||
this.priority = priority;
|
this.priority = priority;
|
||||||
@ -45,4 +48,9 @@ public class DefaultThreadFactory implements ThreadFactory {
|
|||||||
return thread;
|
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) {
|
public static String getRootCauseMessage(Throwable t) {
|
||||||
return getMessage(getRootCause(t));
|
return getMessage(getRootCause(t));
|
||||||
}
|
}
|
||||||
@ -22,7 +35,7 @@ public final class ExceptionUtilities {
|
|||||||
String message = t.getMessage();
|
String message = t.getMessage();
|
||||||
|
|
||||||
if (message == null || message.isEmpty()) {
|
if (message == null || message.isEmpty()) {
|
||||||
message = t.toString().replaceAll(t.getClass().getName(), t.getClass().getSimpleName());
|
message = t.toString();
|
||||||
}
|
}
|
||||||
|
|
||||||
return message;
|
return message;
|
||||||
|
@ -5,6 +5,7 @@ package net.sourceforge.tuned.ui;
|
|||||||
import java.awt.Color;
|
import java.awt.Color;
|
||||||
import java.awt.Insets;
|
import java.awt.Insets;
|
||||||
|
|
||||||
|
import javax.swing.DefaultListCellRenderer;
|
||||||
import javax.swing.Icon;
|
import javax.swing.Icon;
|
||||||
import javax.swing.JLabel;
|
import javax.swing.JLabel;
|
||||||
import javax.swing.JList;
|
import javax.swing.JList;
|
||||||
@ -12,7 +13,7 @@ import javax.swing.JList;
|
|||||||
|
|
||||||
public class DefaultFancyListCellRenderer extends AbstractFancyListCellRenderer {
|
public class DefaultFancyListCellRenderer extends AbstractFancyListCellRenderer {
|
||||||
|
|
||||||
private final JLabel label = new JLabel();
|
private final JLabel label = new DefaultListCellRenderer();
|
||||||
|
|
||||||
|
|
||||||
public DefaultFancyListCellRenderer() {
|
public DefaultFancyListCellRenderer() {
|
||||||
|
@ -3,9 +3,9 @@ package net.sourceforge.filebot;
|
|||||||
|
|
||||||
|
|
||||||
import net.sourceforge.filebot.format.ExpressionFormatTest;
|
import net.sourceforge.filebot.format.ExpressionFormatTest;
|
||||||
|
import net.sourceforge.filebot.hash.VerificationFileScannerTest;
|
||||||
import net.sourceforge.filebot.similarity.SimilarityTestSuite;
|
import net.sourceforge.filebot.similarity.SimilarityTestSuite;
|
||||||
import net.sourceforge.filebot.ui.panel.rename.MatchModelTest;
|
import net.sourceforge.filebot.ui.panel.rename.MatchModelTest;
|
||||||
import net.sourceforge.filebot.ui.panel.sfv.VerificationFileScannerTest;
|
|
||||||
import net.sourceforge.filebot.web.WebTestSuite;
|
import net.sourceforge.filebot.web.WebTestSuite;
|
||||||
|
|
||||||
import org.junit.runner.RunWith;
|
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.io.File;
|
||||||
import java.util.Scanner;
|
import java.util.Scanner;
|
Loading…
Reference in New Issue
Block a user