mirror of
https://github.com/mitb-archive/filebot
synced 2024-11-10 11:25:04 -05:00
Deploy and update script repository via signed jar bundles
This commit is contained in:
parent
d88fd57e9f
commit
6819fdc978
@ -204,7 +204,7 @@ public class CachedResource<K, R> implements Resource<R> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private static ByteBuffer fileNotFound(URL url, FileNotFoundException e) {
|
private static ByteBuffer fileNotFound(URL url, FileNotFoundException e) {
|
||||||
debug.warning(format("Resource not found: %s => %s", url, e.getMessage()));
|
debug.warning(format("Resource not found: %s", url));
|
||||||
return ByteBuffer.allocate(0);
|
return ByteBuffer.allocate(0);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -8,8 +8,8 @@ update.url: https://app.filebot.net/update.xml
|
|||||||
donate.url: https://app.filebot.net/donate.php
|
donate.url: https://app.filebot.net/donate.php
|
||||||
|
|
||||||
# base URL for resolving script resources
|
# base URL for resolving script resources
|
||||||
script.fn: https://raw.githubusercontent.com/filebot/scripts/m1/%s.groovy
|
github.stable: https://raw.githubusercontent.com/filebot/scripts/m1/
|
||||||
script.dev: https://raw.githubusercontent.com/filebot/scripts/devel/%s.groovy
|
github.master: https://raw.githubusercontent.com/filebot/scripts/devel/
|
||||||
|
|
||||||
# native links
|
# native links
|
||||||
link.mas: macappstore://itunes.apple.com/app/id905384638
|
link.mas: macappstore://itunes.apple.com/app/id905384638
|
||||||
|
@ -1,31 +1,21 @@
|
|||||||
package net.filebot.cli;
|
package net.filebot.cli;
|
||||||
|
|
||||||
import static net.filebot.Logging.*;
|
import static net.filebot.Logging.*;
|
||||||
import static net.filebot.Settings.*;
|
|
||||||
import static net.filebot.util.ExceptionUtilities.*;
|
import static net.filebot.util.ExceptionUtilities.*;
|
||||||
import static net.filebot.util.FileUtilities.*;
|
import static net.filebot.util.FileUtilities.*;
|
||||||
|
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
import java.io.FileNotFoundException;
|
|
||||||
import java.net.URI;
|
|
||||||
import java.net.URL;
|
|
||||||
import java.util.Collection;
|
import java.util.Collection;
|
||||||
import java.util.LinkedHashSet;
|
import java.util.LinkedHashSet;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.MissingResourceException;
|
|
||||||
import java.util.logging.Level;
|
import java.util.logging.Level;
|
||||||
import java.util.regex.Matcher;
|
|
||||||
import java.util.regex.Pattern;
|
|
||||||
|
|
||||||
import javax.script.Bindings;
|
import javax.script.Bindings;
|
||||||
import javax.script.SimpleBindings;
|
import javax.script.SimpleBindings;
|
||||||
|
|
||||||
import net.filebot.Cache;
|
|
||||||
import net.filebot.CacheType;
|
|
||||||
import net.filebot.MediaTypes;
|
import net.filebot.MediaTypes;
|
||||||
import net.filebot.StandardRenameAction;
|
import net.filebot.StandardRenameAction;
|
||||||
import net.filebot.WebServices;
|
import net.filebot.WebServices;
|
||||||
import net.filebot.cli.ScriptShell.ScriptProvider;
|
|
||||||
|
|
||||||
public class ArgumentProcessor {
|
public class ArgumentProcessor {
|
||||||
|
|
||||||
@ -119,109 +109,9 @@ public class ArgumentProcessor {
|
|||||||
bindings.put(ScriptShell.SHELL_ARGV_BINDING_NAME, args.getArray());
|
bindings.put(ScriptShell.SHELL_ARGV_BINDING_NAME, args.getArray());
|
||||||
bindings.put(ScriptShell.ARGV_BINDING_NAME, args.getFiles(false));
|
bindings.put(ScriptShell.ARGV_BINDING_NAME, args.getFiles(false));
|
||||||
|
|
||||||
DefaultScriptProvider scriptProvider = new DefaultScriptProvider();
|
ScriptSource source = ScriptSource.findScriptProvider(args.script);
|
||||||
URI script = scriptProvider.getScriptLocation(args.script);
|
ScriptShell shell = new ScriptShell(source.getScriptProvider(args.script), args.defines);
|
||||||
|
shell.runScript(source.accept(args.script), bindings);
|
||||||
if (!scriptProvider.isInline(script)) {
|
|
||||||
if (scriptProvider.resolveTemplate(script) != null) {
|
|
||||||
scriptProvider.setBaseScheme(new URI(script.getScheme(), "%s", null));
|
|
||||||
} else if (scriptProvider.isFile(script)) {
|
|
||||||
File parent = new File(script).getParentFile();
|
|
||||||
File template = new File(parent, "%s.groovy");
|
|
||||||
scriptProvider.setBaseScheme(template.toURI());
|
|
||||||
} else {
|
|
||||||
File parent = new File(script.getPath()).getParentFile();
|
|
||||||
String template = normalizePathSeparators(new File(parent, "%s.groovy").getPath());
|
|
||||||
scriptProvider.setBaseScheme(new URI(script.getScheme(), script.getHost(), template, script.getQuery(), script.getFragment()));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
ScriptShell shell = new ScriptShell(scriptProvider, args.defines);
|
|
||||||
shell.runScript(script, bindings);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static class DefaultScriptProvider implements ScriptProvider {
|
|
||||||
|
|
||||||
public static final String SCHEME_REMOTE_STABLE = "fn";
|
|
||||||
public static final String SCHEME_REMOTE_DEVEL = "dev";
|
|
||||||
public static final String SCHEME_INLINE_GROOVY = "g";
|
|
||||||
public static final String SCHEME_LOCAL_FILE = "file";
|
|
||||||
|
|
||||||
public static final Pattern TEMPLATE_SCHEME = Pattern.compile("([a-z]{1,5}):(.+)");
|
|
||||||
|
|
||||||
public static final String NAME = "script";
|
|
||||||
|
|
||||||
private URI baseScheme;
|
|
||||||
|
|
||||||
public void setBaseScheme(URI baseScheme) {
|
|
||||||
this.baseScheme = baseScheme;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String resolveTemplate(URI uri) {
|
|
||||||
try {
|
|
||||||
String template = getApplicationProperty(NAME + '.' + uri.getScheme());
|
|
||||||
return String.format(template, uri.getSchemeSpecificPart());
|
|
||||||
} catch (MissingResourceException e) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean isInline(URI r) {
|
|
||||||
return SCHEME_INLINE_GROOVY.equals(r.getScheme());
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean isFile(URI r) {
|
|
||||||
return SCHEME_LOCAL_FILE.equals(r.getScheme());
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public URI getScriptLocation(String input) throws Exception {
|
|
||||||
// e.g. dev:amc
|
|
||||||
Matcher template = TEMPLATE_SCHEME.matcher(input);
|
|
||||||
if (template.matches()) {
|
|
||||||
URI uri = new URI(template.group(1), template.group(2), null);
|
|
||||||
|
|
||||||
// 1. fn:amc
|
|
||||||
// 2. dev:sysinfo
|
|
||||||
// 3. g:println 'hello world'
|
|
||||||
switch (uri.getScheme()) {
|
|
||||||
case SCHEME_REMOTE_STABLE:
|
|
||||||
case SCHEME_REMOTE_DEVEL:
|
|
||||||
case SCHEME_INLINE_GROOVY:
|
|
||||||
return uri;
|
|
||||||
default:
|
|
||||||
return new URL(input).toURI();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
File file = new File(input);
|
|
||||||
if (baseScheme != null && !file.isAbsolute()) {
|
|
||||||
return new URI(baseScheme.getScheme(), String.format(baseScheme.getSchemeSpecificPart(), input), null);
|
|
||||||
}
|
|
||||||
|
|
||||||
// e.g. /path/to/script.groovy
|
|
||||||
if (!file.isFile()) {
|
|
||||||
throw new FileNotFoundException(file.getPath());
|
|
||||||
}
|
|
||||||
return file.getCanonicalFile().toURI();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public String fetchScript(URI uri) throws Exception {
|
|
||||||
if (isFile(uri)) {
|
|
||||||
return readTextFile(new File(uri));
|
|
||||||
}
|
|
||||||
|
|
||||||
if (isInline(uri)) {
|
|
||||||
return uri.getSchemeSpecificPart();
|
|
||||||
}
|
|
||||||
|
|
||||||
// check remote script for updates (weekly for stable and daily for devel branches)
|
|
||||||
Cache cache = Cache.getCache(NAME, CacheType.Persistent);
|
|
||||||
return cache.text(uri.toString(), s -> {
|
|
||||||
return new URL(resolveTemplate(uri));
|
|
||||||
}).expire(SCHEME_REMOTE_DEVEL.equals(uri.getScheme()) ? Cache.ONE_DAY : Cache.ONE_WEEK).get();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -14,7 +14,6 @@ import java.io.File;
|
|||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.io.PrintStream;
|
import java.io.PrintStream;
|
||||||
import java.io.UnsupportedEncodingException;
|
import java.io.UnsupportedEncodingException;
|
||||||
import java.net.URI;
|
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
|
|
||||||
import javax.script.Bindings;
|
import javax.script.Bindings;
|
||||||
@ -39,7 +38,6 @@ import org.fife.ui.rtextarea.RTextScrollPane;
|
|||||||
import net.filebot.ResourceManager;
|
import net.filebot.ResourceManager;
|
||||||
import net.filebot.Settings;
|
import net.filebot.Settings;
|
||||||
import net.filebot.Settings.ApplicationFolder;
|
import net.filebot.Settings.ApplicationFolder;
|
||||||
import net.filebot.cli.ArgumentProcessor.DefaultScriptProvider;
|
|
||||||
import net.filebot.util.TeePrintStream;
|
import net.filebot.util.TeePrintStream;
|
||||||
|
|
||||||
public class GroovyPad extends JFrame {
|
public class GroovyPad extends JFrame {
|
||||||
@ -155,10 +153,7 @@ public class GroovyPad extends JFrame {
|
|||||||
|
|
||||||
protected ScriptShell createScriptShell() {
|
protected ScriptShell createScriptShell() {
|
||||||
try {
|
try {
|
||||||
DefaultScriptProvider scriptProvider = new DefaultScriptProvider();
|
return new ScriptShell(ScriptSource.GITHUB_STABLE.getScriptProvider(null), new HashMap<String, Object>());
|
||||||
scriptProvider.setBaseScheme(new URI("fn", "%s", null));
|
|
||||||
|
|
||||||
return new ScriptShell(scriptProvider, new HashMap<String, Object>());
|
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
throw new RuntimeException(e);
|
throw new RuntimeException(e);
|
||||||
}
|
}
|
||||||
|
53
source/net/filebot/cli/ScriptBundle.java
Normal file
53
source/net/filebot/cli/ScriptBundle.java
Normal file
@ -0,0 +1,53 @@
|
|||||||
|
package net.filebot.cli;
|
||||||
|
|
||||||
|
import static java.nio.charset.StandardCharsets.*;
|
||||||
|
import static java.util.Arrays.*;
|
||||||
|
|
||||||
|
import java.io.ByteArrayInputStream;
|
||||||
|
import java.io.FileNotFoundException;
|
||||||
|
import java.io.InputStream;
|
||||||
|
import java.security.cert.Certificate;
|
||||||
|
import java.security.cert.CertificateException;
|
||||||
|
import java.security.cert.CertificateFactory;
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.jar.JarEntry;
|
||||||
|
import java.util.jar.JarInputStream;
|
||||||
|
|
||||||
|
import com.google.common.io.ByteStreams;
|
||||||
|
|
||||||
|
public class ScriptBundle implements ScriptProvider {
|
||||||
|
|
||||||
|
private byte[] bytes;
|
||||||
|
private Certificate certificate;
|
||||||
|
|
||||||
|
public ScriptBundle(byte[] bytes, InputStream certificate) throws CertificateException {
|
||||||
|
this.bytes = bytes;
|
||||||
|
this.certificate = CertificateFactory.getInstance("X.509").generateCertificate(certificate);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getScript(String name) throws Exception {
|
||||||
|
try (JarInputStream jar = new JarInputStream(new ByteArrayInputStream(bytes), true)) {
|
||||||
|
for (JarEntry f = jar.getNextJarEntry(); f != null; f = jar.getNextJarEntry()) {
|
||||||
|
if (f.isDirectory() || !f.getName().startsWith(name) || !f.getName().substring(name.length()).equals(".groovy"))
|
||||||
|
continue;
|
||||||
|
|
||||||
|
// completely read and verify current jar entry
|
||||||
|
byte[] bytes = ByteStreams.toByteArray(jar);
|
||||||
|
jar.closeEntry();
|
||||||
|
|
||||||
|
// file must be signed
|
||||||
|
Certificate[] certificates = f.getCertificates();
|
||||||
|
|
||||||
|
if (certificates == null || stream(f.getCertificates()).noneMatch(certificate::equals))
|
||||||
|
throw new SecurityException(String.format("BAD certificate: %s", Arrays.toString(certificates)));
|
||||||
|
|
||||||
|
return new String(bytes, UTF_8);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// script does not exist
|
||||||
|
throw new FileNotFoundException("Script not found: " + name);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
7
source/net/filebot/cli/ScriptProvider.java
Normal file
7
source/net/filebot/cli/ScriptProvider.java
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
package net.filebot.cli;
|
||||||
|
|
||||||
|
public interface ScriptProvider {
|
||||||
|
|
||||||
|
String getScript(String name) throws Exception;
|
||||||
|
|
||||||
|
}
|
@ -1,6 +1,5 @@
|
|||||||
package net.filebot.cli;
|
package net.filebot.cli;
|
||||||
|
|
||||||
import java.net.URI;
|
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.ResourceBundle;
|
import java.util.ResourceBundle;
|
||||||
|
|
||||||
@ -67,19 +66,8 @@ public class ScriptShell {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public static interface ScriptProvider {
|
public Object runScript(String name, Bindings bindings) throws Throwable {
|
||||||
|
return evaluate(scriptProvider.getScript(name), bindings);
|
||||||
public URI getScriptLocation(String input) throws Exception;
|
|
||||||
|
|
||||||
public String fetchScript(URI uri) throws Exception;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Object runScript(String input, Bindings bindings) throws Throwable {
|
|
||||||
return runScript(scriptProvider.getScriptLocation(input), bindings);
|
|
||||||
}
|
|
||||||
|
|
||||||
public Object runScript(URI resource, Bindings bindings) throws Throwable {
|
|
||||||
return evaluate(scriptProvider.fetchScript(resource), bindings);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
121
source/net/filebot/cli/ScriptSource.java
Normal file
121
source/net/filebot/cli/ScriptSource.java
Normal file
@ -0,0 +1,121 @@
|
|||||||
|
package net.filebot.cli;
|
||||||
|
|
||||||
|
import static java.util.Arrays.*;
|
||||||
|
import static net.filebot.Settings.*;
|
||||||
|
import static net.filebot.util.FileUtilities.*;
|
||||||
|
|
||||||
|
import java.io.File;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.net.URI;
|
||||||
|
import java.net.URL;
|
||||||
|
import java.nio.file.FileSystems;
|
||||||
|
import java.nio.file.Path;
|
||||||
|
import java.time.Duration;
|
||||||
|
|
||||||
|
import net.filebot.Cache;
|
||||||
|
import net.filebot.CacheType;
|
||||||
|
|
||||||
|
public enum ScriptSource {
|
||||||
|
|
||||||
|
GITHUB_STABLE {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String accept(String input) {
|
||||||
|
return input.startsWith("fn:") ? input.substring(3) : null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ScriptProvider getScriptProvider(String input) throws Exception {
|
||||||
|
return getScriptBundle(this, "github.stable", Cache.ONE_WEEK);
|
||||||
|
}
|
||||||
|
|
||||||
|
},
|
||||||
|
|
||||||
|
GITHUB_MASTER {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String accept(String input) {
|
||||||
|
return input.startsWith("dev:") ? input.substring(4) : null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ScriptProvider getScriptProvider(String input) throws Exception {
|
||||||
|
return getScriptBundle(this, "github.master", Cache.ONE_DAY);
|
||||||
|
}
|
||||||
|
|
||||||
|
},
|
||||||
|
|
||||||
|
INLINE_GROOVY {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String accept(String input) {
|
||||||
|
return input.startsWith("g:") ? input.substring(2) : null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ScriptProvider getScriptProvider(String input) throws Exception {
|
||||||
|
return g -> g;
|
||||||
|
}
|
||||||
|
|
||||||
|
},
|
||||||
|
|
||||||
|
REMOTE_URL {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String accept(String input) {
|
||||||
|
try {
|
||||||
|
URI uri = new URI(input);
|
||||||
|
if (uri.isAbsolute()) {
|
||||||
|
return getName(new File(uri.getPath()));
|
||||||
|
}
|
||||||
|
} catch (Exception e) {
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ScriptProvider getScriptProvider(String input) throws Exception {
|
||||||
|
Cache cache = Cache.getCache(name(), CacheType.Persistent);
|
||||||
|
URI parent = new URI(input).resolve(".");
|
||||||
|
|
||||||
|
return f -> cache.text(f, s -> parent.resolve(s + ".groovy").toURL()).expire(Duration.ZERO).get();
|
||||||
|
}
|
||||||
|
|
||||||
|
},
|
||||||
|
|
||||||
|
LOCAL_FILE {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String accept(String input) {
|
||||||
|
try {
|
||||||
|
return getName(new File(input).getCanonicalFile());
|
||||||
|
} catch (Exception e) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ScriptProvider getScriptProvider(String input) throws Exception {
|
||||||
|
File base = new File(input).getParentFile();
|
||||||
|
|
||||||
|
return f -> readTextFile(new File(base, f + ".groovy"));
|
||||||
|
}
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
public abstract String accept(String input);
|
||||||
|
|
||||||
|
public abstract ScriptProvider getScriptProvider(String input) throws Exception;
|
||||||
|
|
||||||
|
public static ScriptSource findScriptProvider(String input) throws Exception {
|
||||||
|
return stream(values()).filter(s -> s.accept(input) != null).findFirst().get();
|
||||||
|
}
|
||||||
|
|
||||||
|
private static ScriptProvider getScriptBundle(ScriptSource source, String branch, Duration expirationTime) throws Exception {
|
||||||
|
Cache cache = Cache.getCache(source.name(), CacheType.Persistent);
|
||||||
|
byte[] bytes = cache.bytes("bundle.jar", f -> new URL(getApplicationProperty(branch) + f)).expire(expirationTime).get();
|
||||||
|
|
||||||
|
return new ScriptBundle(bytes, source.getClass().getResourceAsStream("bundle.cer"));
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
BIN
source/net/filebot/cli/bundle.cer
Normal file
BIN
source/net/filebot/cli/bundle.cer
Normal file
Binary file not shown.
Loading…
Reference in New Issue
Block a user