/*
* Copyright (c) 2010-2016, Sikuli.org, sikulix.com
* Released under the MIT License.
*
*/
package org.sikuli.scriptrunner;
import java.awt.GraphicsDevice;
import java.awt.GraphicsEnvironment;
import java.io.File;
import java.io.IOException;
import java.io.ObjectOutputStream;
import java.net.InetAddress;
import java.net.ServerSocket;
import java.net.Socket;
import java.net.URL;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.Scanner;
import java.util.ServiceConfigurationError;
import java.util.ServiceLoader;
import javax.swing.ImageIcon;
import org.sikuli.basics.Debug;
import org.sikuli.basics.FileManager;
import org.sikuli.basics.Settings;
import org.sikuli.script.ImagePath;
import org.sikuli.script.RunTime;
import org.sikuli.script.Runner;
import org.sikuli.script.Sikulix;
public class ScriptingSupport {
public static RunTime runTime = RunTime.get();
private static final String me = "ScriptingSupport: ";
private static final int lvl = 3;
private static void log(int level, String message, Object... args) {
Debug.logx(level, me + message, args);
}
private static Boolean runAsTest;
public static Map<String, IScriptRunner> scriptRunner = new HashMap<String, IScriptRunner>();
private static Map<String, IScriptRunner> supportedRunner = new HashMap<String, IScriptRunner>();
public static boolean systemRedirected = false;
public static String TypeCommentToken = "---SikuliX---";
public static String TypeCommentDefault = "# This script uses %s " + TypeCommentToken + "\n";
private static boolean isRunningInteractive = false;
private static String[] runScripts = null;
private static String[] testScripts = null;
private static int lastReturnCode = 0;
private static boolean isReady = false;
private static ServerSocket server = null;
private static boolean isHandling = false;
private static boolean shouldStop = false;
private static ObjectOutputStream out = null;
private static Scanner in = null;
private static int runnerID = -1;
private static Map<String, RemoteRunner> remoteRunners = new HashMap<String, RemoteRunner>();
//<editor-fold defaultstate="collapsed" desc="remote runner support">
public static void startRemoteRunner(String[] args) {
int port = getPort(args.length > 0 ? args[0] : null);
try {
try {
if (port > 0) {
server = new ServerSocket(port);
}
} catch (Exception ex) {
log(-1, "Remote: at start: " + ex.getMessage());
}
if (server == null) {
log(-1, "Remote: could not be started on port: " + (args.length > 0 ? args[0] : null));
System.exit(1);
}
while (true) {
log(lvl, "Remote: now waiting on port: %d at %s", port, InetAddress.getLocalHost().getHostAddress());
Socket socket = server.accept();
out = new ObjectOutputStream(socket.getOutputStream());
in = new Scanner(socket.getInputStream());
HandleClient client = new HandleClient(socket);
isHandling = true;
while (true) {
if (socket.isClosed()) {
shouldStop = client.getShouldStop();
break;
}
try {
Thread.sleep(1000);
} catch (InterruptedException ex) {
}
}
if (shouldStop) {
break;
}
}
} catch (Exception e) {
}
if (!isHandling) {
log(-1, "Remote: start handling not possible: " + port);
}
log(lvl, "Remote: now stopped on port: " + port);
}
private static int getPort(String p) {
int port;
int pDefault = 50000;
if (p != null) {
try {
port = Integer.parseInt(p);
} catch (NumberFormatException ex) {
return -1;
}
} else {
return pDefault;
}
if (port < 1024) {
port += pDefault;
}
return port;
}
private static class HandleClient implements Runnable {
private volatile boolean keepRunning;
Thread thread;
Socket socket;
Boolean shouldStop = false;
public HandleClient(Socket sock) {
init(sock);
}
private void init(Socket sock) {
socket = sock;
if (in == null || out == null) {
ScriptingSupport.log(-1, "communication not established");
System.exit(1);
}
thread = new Thread(this, "HandleClient");
keepRunning = true;
thread.start();
}
public boolean getShouldStop() {
return shouldStop;
}
@Override
public void run() {
String e;
ScriptingSupport.log(lvl,"now handling client: " + socket);
while (keepRunning) {
try {
e = in.nextLine();
if (e != null) {
ScriptingSupport.log(lvl,"processing: " + e);
if (e.contains("EXIT")) {
stopRunning();
in.close();
out.close();
if (e.contains("STOP")) {
ScriptingSupport.log(lvl,"stop server requested");
shouldStop = true;
}
return;
}
if (e.toLowerCase().startsWith("run")) {
int retVal = runScript(e);
send((new Integer(retVal)));
} else if (e.toLowerCase().startsWith("system")) {
getSystem();
}
}
} catch (Exception ex) {
ScriptingSupport.log(-1, "Exception while processing\n" + ex.getMessage());
stopRunning();
}
}
try {
Thread.sleep(100);
} catch (InterruptedException ex) {
stopRunning();
}
}
private void getSystem() {
String os = System.getProperty("os.name").toLowerCase();
if (os.startsWith("mac")) {
os = "MAC";
} else if (os.startsWith("windows")) {
os = "WINDOWS";
} else if (os.startsWith("linux")) {
os = "LINUX";
} else {
os = "NOTSUPPORTED";
}
GraphicsEnvironment genv = GraphicsEnvironment.getLocalGraphicsEnvironment();
GraphicsDevice[] gdevs = genv.getScreenDevices();
send(os + " " + gdevs.length);
}
private int runScript(String command) {
return 0;
}
private void send(Object o) {
try {
out.writeObject(o);
out.flush();
if (o instanceof ImageIcon) {
ScriptingSupport.log(lvl,"returned: Image(%dx%d)",
((ImageIcon) o).getIconWidth(), ((ImageIcon) o).getIconHeight());
} else {
ScriptingSupport.log(lvl,"returned: " + o);
}
} catch (IOException ex) {
ScriptingSupport.log(-1, "send: writeObject: Exception: " + ex.getMessage());
}
}
public void stopRunning() {
ScriptingSupport.log(lvl,"stop client handling requested");
try {
socket.close();
} catch (IOException ex) {
ScriptingSupport.log(-1, "fatal: socket not closeable");
System.exit(1);
}
keepRunning = false;
}
}
public static String getRemoteRunner(String adr, String p) {
RemoteRunner rr = new RemoteRunner(adr, p);
String rname = null;
if (rr.isValid()) {
rname = getNextRunnerName();
remoteRunners.put(rname, rr);
} else {
log(-1, "getRemoteRunner: adr(%s) port(%s) not available");
}
return rname;
}
static synchronized String getNextRunnerName() {
return String.format("remoterunner%d", runnerID++);
}
public int runRemote(String rname, String script, String[] args) {
RemoteRunner rr = remoteRunners.get(rname);
if (rr == null) {
log(-1, "runRemote: RemortRunner(%s) not available");
return rr.runRemote(args);
}
return 0;
}
//</editor-fold>
public static void init() {
if (isReady) {
return;
}
log(lvl, "initScriptingSupport: enter");
if (scriptRunner.isEmpty()) {
ServiceLoader<IScriptRunner> rloader = ServiceLoader.load(IScriptRunner.class);
Iterator<IScriptRunner> rIterator = rloader.iterator();
while (rIterator.hasNext()) {
IScriptRunner current = null;
try {
current = rIterator.next();
} catch (ServiceConfigurationError e) {
log(lvl, "initScriptingSupport: warning: %s", e.getMessage());
continue;
}
String name = current.getName();
if (name != null && !name.startsWith("Not")) {
scriptRunner.put(name, current);
current.init(null);
log(lvl, "initScriptingSupport: added: %s", name);
}
}
}
if (scriptRunner.isEmpty()) {
Debug.error("Settings: No scripting support available. Rerun Setup!");
String em = "Terminating: No scripting support available. Rerun Setup!";
log(-1, em);
if (Settings.isRunningIDE) {
Sikulix.popError(em, "IDE has problems ...");
}
System.exit(1);
} else {
Runner.RDEFAULT = (String) scriptRunner.keySet().toArray()[0];
Runner.EDEFAULT = scriptRunner.get(Runner.RDEFAULT).getFileEndings()[0];
for (IScriptRunner r : scriptRunner.values()) {
for (String e : r.getFileEndings()) {
if (!supportedRunner.containsKey(Runner.endingTypes.get(e))) {
supportedRunner.put(Runner.endingTypes.get(e), r);
}
}
}
}
log(lvl, "initScriptingSupport: exit with defaultrunner: %s (%s)", Runner.RDEFAULT, Runner.EDEFAULT);
isReady = true;
}
public static IScriptRunner getRunner(String script, String type) {
init();
IScriptRunner currentRunner = null;
String ending = null;
if (script != null) {
for (String suffix : Runner.endingTypes.keySet()) {
if (script.endsWith(suffix)) {
ending = suffix;
break;
}
}
} else if (type != null) {
currentRunner = scriptRunner.get(type);
if (currentRunner != null) {
return currentRunner;
}
ending = Runner.typeEndings.get(type);
if (ending == null) {
if (Runner.endingTypes.containsKey(type)) {
ending = type;
}
}
}
if (ending != null) {
for (IScriptRunner r : scriptRunner.values()) {
if (r.hasFileEnding(ending) != null) {
currentRunner = r;
break;
}
}
}
if (currentRunner == null) {
log(-1, "getRunner: no runner found for:\n%s", (script == null ? type : script));
}
return currentRunner;
}
public static boolean hasTypeRunner(String type) {
return supportedRunner.containsKey(type);
}
public static void runningInteractive() {
isRunningInteractive = true;
}
public static boolean getRunningInteractive() {
return isRunningInteractive;
}
//<editor-fold defaultstate="collapsed" desc="run scripts">
private static boolean isRunningScript = false;
/**
* INTERNAL USE: run scripts when sikulix.jar is used on commandline with args -r, -t or -i<br>
* If you want to use it the args content must be according to the Sikulix command line parameter rules<br>
* use run(script, args) to run one script from a script or Java program
* @param args parameters given on commandline
*/
public static void runscript(String[] args) {
if (isRunningScript) {
log(-1, "can run only one script at a time!");
return;
}
IScriptRunner currentRunner = null;
if (args != null && args.length > 1 && args[0].startsWith("-testSetup")) {
currentRunner = getRunner(null, args[1]);
if (currentRunner == null) {
args[0] = null;
} else {
String[] stmts = new String[0];
if (args.length > 2) {
stmts = new String[args.length - 2];
for (int i = 0; i < stmts.length; i++) {
stmts[i] = args[i+2];
}
}
if (0 != currentRunner.runScript(null, null, stmts, null)) {
args[0] = null;
}
}
isRunningScript = false;
return;
}
runScripts = Runner.evalArgs(args);
isRunningScript = true;
if (runTime.runningInteractive) {
int exitCode = 0;
if (currentRunner == null) {
String givenRunnerName = runTime.interactiveRunner;
if (givenRunnerName == null) {
currentRunner = getRunner(null, Runner.RDEFAULT);
} else {
currentRunner = getRunner(null, givenRunnerName);
}
}
if (currentRunner == null) {
System.exit(1);
}
exitCode = currentRunner.runInteractive(runTime.getSikuliArgs());
currentRunner.close();
Sikulix.endNormal(exitCode);
}
if (runScripts == null) {
runTime.terminate(1, "option -r without any script");
}
if (runScripts.length > 0) {
String scriptName = runScripts[0];
if (scriptName != null && !scriptName.isEmpty() && scriptName.startsWith("git*")) {
run(scriptName, runTime.getSikuliArgs());
return;
}
}
if (runScripts != null && runScripts.length > 0) {
int exitCode = 0;
runAsTest = runTime.runningTests;
for (String givenScriptName : runScripts) {
if (lastReturnCode == -1) {
log(lvl, "Exit code -1: Terminating multi-script-run");
break;
}
exitCode = new RunBox(givenScriptName, runTime.getArgs(), runAsTest).run();
lastReturnCode = exitCode;
}
System.exit(exitCode);
}
}
/**
* run a script at scriptPath (.sikuli or .skl)
* @param scriptPath absolute or relative to working folder
* @param args parameter given to the script
* @return exit code
*/
public static int run(String scriptPath, String[] args) {
runAsTest = false;
init();
String savePath = ImagePath.getBundlePath();
int retVal = new RunBox(scriptPath, args, runAsTest).run();
ImagePath.setBundlePath(savePath);
return retVal;
}
/**
* run a script at scriptPath (.sikuli or .skl)
* @param scriptPath absolute or relative to working folder
* @return exit code
*/
public static int run(String scriptPath) {
return run(scriptPath, new String[0]);
}
public static boolean transferScript(String src, String dest) {
log(lvl, "transferScript: %s\nto: %s", src, dest);
FileManager.FileFilter filter = new FileManager.FileFilter() {
@Override
public boolean accept(File entry) {
if (entry.getName().endsWith(".html")) {
return false;
} else if (entry.getName().endsWith(".$py.class")) {
return false;
} else {
for (String ending : Runner.endingTypes.keySet()) {
if (entry.getName().endsWith("." + ending)) {
return false;
}
}
}
return true;
}
};
try {
FileManager.xcopy(src, dest, filter);
} catch (IOException ex) {
log(-1, "transferScript: IOError: %s", ex.getMessage(), src, dest);
return false;
}
log(lvl, "transferScript: completed");
return true;
}
//</editor-fold>
//<editor-fold defaultstate="collapsed" desc="helpers">
public static void setProject() {
RunBox.setProject();
}
public static int getLastReturnCode() {
return lastReturnCode;
}
private static class RunBox {
boolean asTest = false;
String[] args = new String[0];
String givenScriptHost = "";
String givenScriptFolder = "";
String givenScriptName = "";
String givenScriptScript = "";
String givenScriptType = "sikuli";
String givenScriptScriptType = Runner.RDEFAULT;
URL uGivenScript = null;
URL uGivenScriptFile = null;
boolean givenScriptExists = true;
private RunBox(String givenName, String[] givenArgs, boolean isTest) {
Object[] vars = Runner.runBoxInit(givenName, RunTime.scriptProject, RunTime.uScriptProject);
givenScriptHost = (String) vars[0];
givenScriptFolder = (String) vars[1];
givenScriptName = (String) vars[2];
givenScriptScript = (String) vars[3];
givenScriptType = (String) vars[4];
givenScriptScriptType = (String) vars[5];
uGivenScript = (URL) vars[6];
uGivenScriptFile = (URL) vars[7];
givenScriptExists = (Boolean) vars[8];
RunTime.scriptProject = (File) vars[9];
RunTime.uScriptProject = (URL) vars[10];
args = givenArgs;
asTest = isTest;
}
private static void setProject() {
if (RunTime.scriptProject == null) {
RunTime.scriptProject = new File(FileManager.normalizeAbsolute(ImagePath.getBundlePath(), false)).getParentFile();
}
}
private int run() {
if (Runner.RASCRIPT.equals(givenScriptScriptType)) {
return Runner.runas(givenScriptScript);
} else if (Runner.RSSCRIPT.equals(givenScriptScriptType)) {
return Runner.runps(givenScriptScript);
} else if (Runner.RRSCRIPT.equals(givenScriptScriptType)) {
return Runner.runrobot(givenScriptScript);
}
int exitCode = -1;
IScriptRunner currentRunner = null;
if (givenScriptType == "NET" && givenScriptExists) {
log(lvl, "running script from net:\n%s", uGivenScript);
if (Runner.RJSCRIPT.equals(givenScriptScriptType)) {
exitCode = Runner.runjs(null, uGivenScript, givenScriptScript, args);
} else {
ScriptingSupport.init();
currentRunner = scriptRunner.get(givenScriptScriptType);
if (null == currentRunner) {
log(-1, "running from net not supported for %s\n%s", givenScriptScriptType, uGivenScript);
} else {
ImagePath.addHTTP(uGivenScript.toExternalForm());
exitCode = currentRunner.runScript(null, null, new String[] {givenScriptScript}, null);
}
}
} else {
log(lvl, "givenScriptName:\n%s", givenScriptName);
if (givenScriptName.endsWith(".skl")) {
givenScriptName = FileManager.unzipSKL(givenScriptName);
if (givenScriptName == null) {
log(-1, "not possible to make .skl runnable");
return -9999;
}
}
File fScript = Runner.getScriptFile(new File(givenScriptName));
if (fScript == null) {
return -9999;
}
fScript = new File(FileManager.normalizeAbsolute(fScript.getPath(), true));
log(lvl, "Trying to run script:\n%s", fScript);
if (fScript.getName().endsWith(".js")) {
return Runner.runjs(fScript, null, givenScriptScript, args);
}
currentRunner = getRunner(fScript.getName(), null);
if (currentRunner != null) {
ImagePath.setBundlePath(fScript.getParent());
if (asTest) {
exitCode = currentRunner.runTest(fScript, null, args, null);
} else {
exitCode = currentRunner.runScript(fScript, null, args, null);
}
}
}
if (currentRunner != null) {
currentRunner.close();
}
return exitCode;
}
}
}