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}