001package net.filebot.postprocess; 002 003import static net.filebot.GroovyEngine.*; 004 005import java.io.File; 006import java.util.AbstractList; 007import java.util.List; 008import java.util.Map; 009import java.util.Map.Entry; 010 011import javax.script.CompiledScript; 012import javax.script.ScriptContext; 013import javax.script.ScriptException; 014import javax.script.SimpleScriptContext; 015 016import groovy.lang.Closure; 017 018import net.filebot.RenameAction; 019import net.filebot.format.AssociativeScriptObject; 020import net.filebot.format.MediaBindingBean; 021import net.filebot.similarity.Match; 022import net.filebot.util.FunctionList; 023 024public class Script implements Apply { 025 026 public String id; 027 public String name; 028 public String code; 029 030 // compile on demand 031 private transient CompiledScript script; 032 033 // built-in example script 034 public Script(String name, String code) { 035 this(name, name, code); 036 } 037 038 // user-defined custom script 039 public Script(String id, String name, String code) { 040 this.id = id; 041 this.name = name; 042 this.code = code; 043 } 044 045 public String getIdentifier() { 046 return id; 047 } 048 049 public String getName() { 050 return name; 051 } 052 053 public String getCode() { 054 return code; 055 } 056 057 public synchronized CompiledScript compile() throws ScriptException { 058 // compile on demand 059 if (script == null) { 060 script = ScriptEngine.getScriptEngine().compile(resolveScript(code)); 061 } 062 return script; 063 } 064 065 public ScriptContext createScriptContext(Map<File, Match<File, ?>> map, RenameAction action, Feedback log) { 066 Model model = new Model(map); 067 068 ScriptContext context = new SimpleScriptContext(); 069 context.setAttribute("args", model.getTarget(), ScriptContext.ENGINE_SCOPE); 070 context.setAttribute("model", model, ScriptContext.ENGINE_SCOPE); 071 context.setAttribute("action", action, ScriptContext.ENGINE_SCOPE); 072 context.setAttribute("log", log, ScriptContext.ENGINE_SCOPE); 073 074 context.setWriter(FeedbackWriter.newPrintWriter(line -> log.info(line, name))); 075 context.setErrorWriter(FeedbackWriter.newPrintWriter(line -> log.warning(line, name))); 076 077 return context; 078 } 079 080 @Override 081 public void apply(Map<File, Match<File, ?>> map, RenameAction action, Feedback log) { 082 log.info("Run Script", name); 083 084 try { 085 ScriptContext context = createScriptContext(map, action, log); 086 Object result = compile().eval(context); 087 088 // apply closure to each match 089 if (result instanceof Closure) { 090 ApplyClosure each = new ApplyClosure((Closure) result); 091 each.apply(map, action, log); 092 return; 093 } 094 095 log.trace(result, name); 096 } catch (ScriptException e) { 097 log.warning(sanitizeErrorMessage(e), name); 098 } 099 } 100 101 @Override 102 public String toString() { 103 return "SCRIPT"; 104 } 105 106 public static class Model extends AbstractList<AssociativeScriptObject> { 107 108 private final Entry<File, Match<File, ?>>[] map; 109 private final AssociativeScriptObject[] model; 110 111 public Model(Map<File, Match<File, ?>> map) { 112 this.map = map.entrySet().toArray(new Entry[0]); 113 this.model = new AssociativeScriptObject[this.map.length]; 114 } 115 116 public File getTarget(int i) { 117 return map[i].getKey(); 118 } 119 120 public File getSource(int i) { 121 return map[i].getValue().getValue(); 122 } 123 124 public Object getObject(int i) { 125 return map[i].getValue().getCandidate(); 126 } 127 128 @Override 129 public AssociativeScriptObject get(int i) { 130 if (model[i] == null) { 131 model[i] = new MediaBindingBean(getObject(i), getSource(i)).getSelf(); 132 } 133 return model[i]; 134 } 135 136 @Override 137 public int size() { 138 return model.length; 139 } 140 141 public List<File> getTarget() { 142 return new FunctionList<File>(this::getTarget, this::size); 143 } 144 145 public List<File> getSource() { 146 return new FunctionList<File>(this::getSource, this::size); 147 } 148 149 public List<Object> getObject() { 150 return new FunctionList<Object>(this::getObject, this::size); 151 } 152 153 } 154 155}