diff --git a/src/main/java/net/geedge/api/controller/APIController.java b/src/main/java/net/geedge/api/controller/APIController.java index 0b7342d..5df1bad 100644 --- a/src/main/java/net/geedge/api/controller/APIController.java +++ b/src/main/java/net/geedge/api/controller/APIController.java @@ -12,10 +12,9 @@ import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.*; import org.springframework.web.multipart.MultipartFile; -import java.io.File; -import java.io.FileFilter; -import java.io.IOException; +import java.io.*; import java.util.Arrays; +import java.util.HashMap; import java.util.List; import java.util.Map; @@ -62,7 +61,7 @@ public class APIController { File tempFile = T.FileUtil.file(Constant.TEMP_PATH, fileName); try { - AdbUtil.CommandResult result = adbUtil.pull(filePath, tempFile.getAbsolutePath()); + AdbUtil.CommandResult result = adbUtil.pull(filePath, tempFile.getAbsolutePath(), false); if (0 != result.exitCode()) { throw new APIException(result.output()); } @@ -105,7 +104,7 @@ public class APIController { tempFile = T.FileUtil.file(Constant.TEMP_PATH, file.getOriginalFilename()); file.transferTo(tempFile); - AdbUtil.CommandResult result = adbUtil.install(tempFile.getAbsolutePath(), true, true); + AdbUtil.CommandResult result = adbUtil.install(tempFile.getAbsolutePath(), true, true, false); if (0 != result.exitCode()) { throw new APIException(result.output()); } @@ -116,7 +115,7 @@ public class APIController { } if (T.StrUtil.isNotEmpty(path)) { - AdbUtil.CommandResult result = adbUtil.install(path, true, true); + AdbUtil.CommandResult result = adbUtil.install(path, true, true, false); if (0 != result.exitCode()) { throw new APIException(result.output()); } @@ -141,7 +140,7 @@ public class APIController { @PostMapping("/pcap") public R startTcpdump(@RequestParam(required = false, defaultValue = "") String packageName) { - AdbUtil.CommandResult result = adbUtil.startTcpdump(packageName); + AdbUtil.CommandResult result = adbUtil.startTcpdump(packageName, false); if (0 != result.exitCode()) { throw new APIException("exec tcpdump error"); } @@ -150,9 +149,9 @@ public class APIController { @DeleteMapping("/pcap") public synchronized void stopTcpdump(@RequestParam String id, - @RequestParam(required = false, defaultValue = "false") Boolean returnFile, - HttpServletResponse response) throws IOException { - AdbUtil.CommandResult result = adbUtil.stopTcpdump(id); + @RequestParam(required = false, defaultValue = "false") Boolean returnFile, + HttpServletResponse response) throws IOException { + AdbUtil.CommandResult result = adbUtil.stopTcpdump(id, false); if (0 != result.exitCode()) { throw new APIException(result.output()); } @@ -166,7 +165,7 @@ public class APIController { if (T.StrUtil.isEmpty(filePath)) { throw new APIException(RCode.NOT_EXISTS); } - AdbUtil.CommandResult pulled = adbUtil.pull(filePath, tempFile.getAbsolutePath()); + AdbUtil.CommandResult pulled = adbUtil.pull(filePath, tempFile.getAbsolutePath(), false); if (0 != pulled.exitCode()) { throw new APIException(pulled.output()); } @@ -181,7 +180,7 @@ public class APIController { } finally { if (T.StrUtil.isNotEmpty(filePath)) { // remove pcap file - adbUtil.execShellCommand(String.format("shell rm -rf %s", filePath)); + adbUtil.execShellCommand(String.format("shell rm -rf %s", filePath), false); } } } @@ -254,53 +253,112 @@ public class APIController { } @PostMapping("/playbook") - public R execPlaybook(@RequestParam("files") MultipartFile[] files, @RequestParam("packageName") String packageName) throws IOException { - // save zip and apk - String tid = T.StrUtil.uuid(); - File appFile = null; + public R execPlaybook(@RequestParam("files") MultipartFile file, + @RequestParam("packageName") String packageName, + @RequestParam("id") String id) { + File apkFile = null; File playbookAirDir = null; - for (MultipartFile file : files) { - if (T.FileUtil.extName(file.getOriginalFilename()).equals("zip")) { - File playbookFile = T.FileUtil.file(Constant.TEMP_PATH, tid, file.getOriginalFilename()); - T.FileUtil.writeBytes(file.getInputStream().readAllBytes(), playbookFile); - File playbookDir = T.FileUtil.file(Constant.TEMP_PATH, tid); - T.ZipUtil.unzip(playbookFile, playbookDir); - playbookAirDir = Arrays.stream(playbookDir.listFiles(new FileFilter() { - @Override - public boolean accept(File pathname) { - return pathname.getName().endsWith(".air"); - } - })).findFirst().get(); - } else { - appFile = T.FileUtil.file(Constant.TEMP_PATH, tid, file.getOriginalFilename()); - T.FileUtil.writeBytes(file.getInputStream().readAllBytes(), appFile); - } + File destination = null; + try { + File playbookDir = T.FileUtil.file(Constant.TEMP_PATH, id); + destination = T.FileUtil.file(Constant.TEMP_PATH, id, file.getName()); + T.FileUtil.writeBytes(file.getInputStream().readAllBytes(), destination); + + // unzip file + T.ZipUtil.unzip(destination, playbookDir); + + // apk + apkFile = Arrays.stream(playbookDir.listFiles(new FilenameFilter() { + @Override + public boolean accept(File dir, String name) { + return name.endsWith(".apk"); + } + })).findFirst().get(); + + // playbook zip + File playbook = Arrays.stream(playbookDir.listFiles(new FilenameFilter() { + @Override + public boolean accept(File dir, String name) { + return name.endsWith(".zip") && !name.equals(file.getName()); + } + })).findFirst().get(); + + // unzip playbook zip + T.ZipUtil.unzip(playbook, playbookDir); + playbookAirDir = Arrays.stream(playbookDir.listFiles(new FileFilter() { + @Override + public boolean accept(File pathname) { + return pathname.getName().endsWith(".air"); + } + })).findFirst().get(); + + } catch (Exception e) { + log.error(e.getMessage()); + Map resultMap = T.MapUtil.builder() + .put("status", "error") + .build(); + Constant.PLAYBOOK_RUN_RESULT.put(id, resultMap); + return R.ok(); + } finally { + T.FileUtil.del(destination); } - PlaybookRunnable playbookRunnable = new PlaybookRunnable(adbUtil, appFile, playbookAirDir, tid, packageName); + PlaybookRunnable playbookRunnable = new PlaybookRunnable(adbUtil, apkFile, playbookAirDir, id, packageName); ThreadUtil.execAsync(playbookRunnable); - return R.ok().putData("tid", tid); + return R.ok(); } @GetMapping("/playbook/{id}") - public void getExecPlaybookResult(@PathVariable("id") String id, HttpServletResponse response) throws IOException { + public R checkJobResult(@PathVariable("id") String id){ if (T.StrUtil.isEmpty(id)) { throw new APIException(RCode.BAD_REQUEST); } Map result = Constant.PLAYBOOK_RUN_RESULT.get(id); - if (result != null) { - String status = T.MapUtil.getStr(result, "status"); - if (T.MapUtil.getStr(result, "status").equals("done")) { - String artifact = T.MapUtil.getStr(result, "artifact"); - File pcapFile = T.FileUtil.file(artifact); - Constant.PLAYBOOK_RUN_RESULT.remove(id); - T.ResponseUtil.downloadFile(response, pcapFile.getName(), T.FileUtil.readBytes(pcapFile)); - } else if (status.equals("error")) { - Constant.PLAYBOOK_RUN_RESULT.remove(id); - response.getWriter().write(T.JSONUtil.toJsonStr(R.ok().putData(result))); - } else { - response.getWriter().write(T.JSONUtil.toJsonStr(R.ok().putData(result))); - } + return R.ok().putData(result); + } + + + @GetMapping("/playbook/{id}/log") + public R getJobResultLog(@PathVariable("id") String id, + @RequestParam("offset") Integer offset){ + if (T.StrUtil.isEmpty(id)) { + throw new APIException(RCode.BAD_REQUEST); } + // log file + File logFile = T.FileUtil.file(Constant.TEMP_PATH, id, "result.log"); + HashMap result = T.MapUtil.newHashMap(false); + try (RandomAccessFile raf = new RandomAccessFile(logFile, "r")) { + raf.seek(offset); + byte[] bytes = new byte[(int)raf.length() - offset]; + raf.readFully(bytes); + String content = new String(bytes); + result.put("content", content); + result.put("length", bytes.length); + result.put("offset", offset + bytes.length); + } catch (IOException e) { + log.error("getJobResultLog error", e); + throw new APIException(RCode.ERROR); + } + return R.ok().putData(result); + } + + + @GetMapping("/playbook/{id}/artifact") + public void getJobResultArtifact(@PathVariable("id") String id, HttpServletResponse response) throws IOException { + if (T.StrUtil.isEmpty(id)) { + throw new APIException(RCode.BAD_REQUEST); + } + // job dir + File jobResult = T.FileUtil.file(Constant.TEMP_PATH, id); + File zipFile = T.FileUtil.file(Constant.TEMP_PATH, T.StrUtil.concat(true, id, ".zip")); + File[] files = jobResult.listFiles(new FilenameFilter() { + @Override + public boolean accept(File dir, String name) { + return name.endsWith(".log") || name.endsWith(".pcap"); + } + }); + T.ZipUtil.zip(zipFile, true, files); + T.ResponseUtil.downloadFile(response,zipFile.getName(), T.FileUtil.readBytes(zipFile)); + } public class PlaybookRunnable extends Thread { @@ -321,37 +379,47 @@ public class APIController { @Override public void run() { + File logFile = FileUtil.file(Constant.TEMP_PATH, tid, "result.log"); try { + + adbUtil.setLogFile(logFile); + Map resultMap = T.MapUtil.builder() .put("status", "running") .build(); Constant.PLAYBOOK_RUN_RESULT.put(tid, resultMap); + T.FileUtil.appendString(String.format("Running with %s Android Simulator \n", adbUtil.getSerial()), logFile, "UTF-8"); + // 1. install apk - AdbUtil.CommandResult install = adbUtil.install(apkFile.getAbsolutePath(), true, true); + AdbUtil.CommandResult install = adbUtil.install(apkFile.getAbsolutePath(), true, true, true); if (0 != install.exitCode()) { + T.FileUtil.appendString(String.format("ERROR: Install apk failed: exit code %s \n", install.exitCode()), logFile, "UTF-8"); throw new APIException(install.output()); } // 2. star tcpdump - AdbUtil.CommandResult startTcpdump = adbUtil.startTcpdump(packageName); + AdbUtil.CommandResult startTcpdump = adbUtil.startTcpdump(packageName, true); if (0 != startTcpdump.exitCode()) { + T.FileUtil.appendString(String.format("ERROR: Start tcpdump failed: exit code %s \n", startTcpdump.exitCode()), logFile, "UTF-8"); throw new APIException("exec tcpdump error"); } // 3. exec playbook - AdbUtil.CommandResult execResult = adbUtil.execPlaybook(playbookDir.getPath()); - T.FileUtil.writeString(execResult.output(), FileUtil.file(Constant.TEMP_PATH, tid, "log", "log.txt"), "UTF-8"); + AdbUtil.CommandResult execResult = adbUtil.execPlaybook(playbookDir.getPath(), true); if (0 != execResult.exitCode()) { // exec playbook error, stop tcpdump and delete pcap - AdbUtil.CommandResult stopTcpdump = adbUtil.stopTcpdump(startTcpdump.output()); - adbUtil.execShellCommand(String.format("shell rm -rf %s", stopTcpdump.output())); + T.FileUtil.appendString(String.format("ERROR: Exec playbook failed: exit code %s \n", execResult.exitCode()), logFile, "UTF-8"); + AdbUtil.CommandResult stopTcpdump = adbUtil.stopTcpdump(startTcpdump.output(), true); + adbUtil.execShellCommand(String.format("shell rm -rf %s", stopTcpdump.output()), true); throw new APIException("exec playbook error"); } // 4. stop tcpdump - AdbUtil.CommandResult stopTcpdump = adbUtil.stopTcpdump(startTcpdump.output()); + AdbUtil.CommandResult stopTcpdump = adbUtil.stopTcpdump(startTcpdump.output(), true); + T.FileUtil.appendString(T.StrUtil.concat(true, stopTcpdump.output(), "\n"), logFile, "UTF-8"); if (0 != stopTcpdump.exitCode()) { + T.FileUtil.appendString(String.format("ERROR: Stop tcpdump failed: exit code %s \n", stopTcpdump.exitCode()), logFile, "UTF-8"); throw new APIException(stopTcpdump.output()); } @@ -361,29 +429,30 @@ public class APIController { if (T.StrUtil.isEmpty(filePath)) { throw new APIException(RCode.NOT_EXISTS); } - AdbUtil.CommandResult pull = adbUtil.pull(filePath, localPcapFile.getAbsolutePath()); + + AdbUtil.CommandResult pull = adbUtil.pull(filePath, localPcapFile.getAbsolutePath(), true); + T.FileUtil.appendString(T.StrUtil.concat(true, pull.output(), "\n"), logFile, "UTF-8"); if (0 != pull.exitCode()) { + T.FileUtil.appendString(String.format("ERROR: Pull pcap file failed: exit code %s \n", pull.exitCode()), logFile, "UTF-8"); throw new APIException(pull.output()); } // 6. delete android pcap - adbUtil.execShellCommand(String.format("shell rm -rf %s", filePath)); + adbUtil.execShellCommand(String.format("shell rm -rf %s", filePath), true); resultMap = T.MapUtil.builder() .put("status", "done") - .put("artifact", localPcapFile.getAbsolutePath()) .build(); Constant.PLAYBOOK_RUN_RESULT.put(tid, resultMap); - }catch (Exception e) { + } catch (Exception e) { log.error(e); Map resultMap = T.MapUtil.builder() .put("status", "error") .build(); Constant.PLAYBOOK_RUN_RESULT.put(tid, resultMap); } finally { - adbUtil.stopApp(packageName); - //T.FileUtil.del(apkFile); - //T.FileUtil.clean(playbookDir); + adbUtil.stopApp(packageName, true); + T.FileUtil.appendString(String.format("Job succeeded"), logFile, "UTF-8"); } } } diff --git a/src/main/java/net/geedge/api/util/AdbUtil.java b/src/main/java/net/geedge/api/util/AdbUtil.java index b8aecce..7cb0d20 100644 --- a/src/main/java/net/geedge/api/util/AdbUtil.java +++ b/src/main/java/net/geedge/api/util/AdbUtil.java @@ -15,6 +15,7 @@ import java.util.*; import java.util.concurrent.*; import java.util.regex.Matcher; import java.util.regex.Pattern; +import java.util.stream.Collectors; public class AdbUtil { @@ -32,8 +33,14 @@ public class AdbUtil { private Integer vncPort; + private File logFile; + private ExecutorService threadPool; + public void setLogFile(File logFile) { + this.logFile = logFile; + } + public String getSerial() { return T.StrUtil.isNotEmpty(this.serial) ? serial : String.format("%s:%s", this.host, this.port); } @@ -86,7 +93,7 @@ public class AdbUtil { } } else { // remote - String result = CommandExec.exec(AdbCommandBuilder.builder() + String result = new CommandExec(logFile ,false).exec(AdbCommandBuilder.builder() .buildConnectCommand(this.host, this.port) .build()); log.info("[connect] [result: {}]", result); @@ -104,7 +111,7 @@ public class AdbUtil { */ public void init(boolean install) { // adb root - String result = CommandExec.exec(AdbCommandBuilder.builder() + String result = new CommandExec(logFile ,false).exec(AdbCommandBuilder.builder() .serial(this.getSerial()) .buildRootCommand() .build() @@ -113,26 +120,26 @@ public class AdbUtil { if (install) { // install droidVNC NG - CommandResult installed = this.install(DEFAULT_DROIDVNC_NG_APK_PATH, true, true); + CommandResult installed = this.install(DEFAULT_DROIDVNC_NG_APK_PATH, true, true,false); log.info("[init] [install droidVNC NG] [result: {}]", installed); // 上传默认配置 - this.execShellCommand("shell mkdir -p /storage/emulated/0/Android/data/net.christianbeier.droidvnc_ng/files"); + this.execShellCommand("shell mkdir -p /storage/emulated/0/Android/data/net.christianbeier.droidvnc_ng/files",false); this.push(DEFAULT_DROIDVNC_NG_DEFAULTS_JSON_PATH, "/storage/emulated/0/Android/data/net.christianbeier.droidvnc_ng/files/defaults.json"); // 无障碍权限 - this.execShellCommand("shell settings put secure enabled_accessibility_services net.christianbeier.droidvnc_ng/.InputService:$(settings get secure enabled_accessibility_services)"); + this.execShellCommand("shell settings put secure enabled_accessibility_services net.christianbeier.droidvnc_ng/.InputService:$(settings get secure enabled_accessibility_services)",false); // 存储空间权限 - this.execShellCommand("shell pm grant net.christianbeier.droidvnc_ng android.permission.WRITE_EXTERNAL_STORAGE"); + this.execShellCommand("shell pm grant net.christianbeier.droidvnc_ng android.permission.WRITE_EXTERNAL_STORAGE",false); // 屏幕录制权限 - this.execShellCommand("shell appops set net.christianbeier.droidvnc_ng PROJECT_MEDIA allow"); + this.execShellCommand("shell appops set net.christianbeier.droidvnc_ng PROJECT_MEDIA allow",false); // ACTION_STOP - this.execShellCommand("shell am start-foreground-service -n net.christianbeier.droidvnc_ng/.MainService -a net.christianbeier.droidvnc_ng.ACTION_STOP --es net.christianbeier.droidvnc_ng.EXTRA_ACCESS_KEY d042e2b5d5f348588a4e1a243eb7a9a0"); + this.execShellCommand("shell am start-foreground-service -n net.christianbeier.droidvnc_ng/.MainService -a net.christianbeier.droidvnc_ng.ACTION_STOP --es net.christianbeier.droidvnc_ng.EXTRA_ACCESS_KEY d042e2b5d5f348588a4e1a243eb7a9a0",false); } // ACTION_START - this.execShellCommand("shell am start-foreground-service -n net.christianbeier.droidvnc_ng/.MainService -a net.christianbeier.droidvnc_ng.ACTION_START --es net.christianbeier.droidvnc_ng.EXTRA_ACCESS_KEY d042e2b5d5f348588a4e1a243eb7a9a0"); + this.execShellCommand("shell am start-foreground-service -n net.christianbeier.droidvnc_ng/.MainService -a net.christianbeier.droidvnc_ng.ACTION_START --es net.christianbeier.droidvnc_ng.EXTRA_ACCESS_KEY d042e2b5d5f348588a4e1a243eb7a9a0",false); // 添加自定义链 this.addAswOutputChain(); @@ -172,7 +179,7 @@ public class AdbUtil { m.put("type", type); // check root - String checkRootResult = CommandExec.exec(AdbCommandBuilder.builder() + String checkRootResult = new CommandExec(logFile, false).exec(AdbCommandBuilder.builder() .serial(this.getSerial()) .buildCheckRootCommand() .build() @@ -188,7 +195,7 @@ public class AdbUtil { * @return */ private AdbDevice getAdbDevice() { - String result = CommandExec.exec(AdbCommandBuilder.builder() + String result = new CommandExec(logFile, false).exec(AdbCommandBuilder.builder() .buildDevicesCommand() .build() ); @@ -213,7 +220,7 @@ public class AdbUtil { * @return */ private Map getProp() { - String result = CommandExec.exec(AdbCommandBuilder.builder() + String result = new CommandExec(logFile, false).exec(AdbCommandBuilder.builder() .serial(this.getSerial()) .buildGetpropCommand() .build() @@ -230,7 +237,7 @@ public class AdbUtil { } // 分辨率 Physical size: 1440x3040 - String wmSize = CommandExec.exec(AdbCommandBuilder.builder() + String wmSize = new CommandExec(logFile, false).exec(AdbCommandBuilder.builder() .serial(this.getSerial()) .buildWmSizeCommand() .build() @@ -244,7 +251,7 @@ public class AdbUtil { * md5sum */ private CommandResult md5sum(String path) { - String result = CommandExec.exec(AdbCommandBuilder.builder() + String result = new CommandExec(logFile, false).exec(AdbCommandBuilder.builder() .serial(this.getSerial()) .buildMd5sumCommand(path) .build() @@ -262,7 +269,7 @@ public class AdbUtil { * 0 success; !0 failed */ public CommandResult push(String local, String remote) { - String result = CommandExec.exec(AdbCommandBuilder.builder() + String result = new CommandExec(logFile, false).exec(AdbCommandBuilder.builder() .serial(this.getSerial()) .buildPushCommand(local, remote) .build() @@ -275,8 +282,8 @@ public class AdbUtil { * pull * 0 success; !0 failed */ - public CommandResult pull(String remote, String local) { - String result = CommandExec.exec(AdbCommandBuilder.builder() + public CommandResult pull(String remote, String local, boolean isRecordLog) { + String result = new CommandExec(logFile, isRecordLog).exec(AdbCommandBuilder.builder() .serial(this.getSerial()) .buildPullCommand(remote, local) .build() @@ -291,7 +298,7 @@ public class AdbUtil { * stat filename */ public List listDir(String path) { - String result = CommandExec.exec(AdbCommandBuilder.builder() + String result = new CommandExec(logFile, false).exec(AdbCommandBuilder.builder() .serial(this.getSerial()) .buildLsDirCommand(path) .build() @@ -329,7 +336,7 @@ public class AdbUtil { String statCommand = "shell stat -c \"'%N %A %g %u %s %a %X %Y'\" " + statFilePath; futureList.add( CompletableFuture.supplyAsync(() -> { - String statResult = CommandExec.exec(AdbCommandBuilder.builder() + String statResult = new CommandExec(logFile, false).exec(AdbCommandBuilder.builder() .serial(this.getSerial()) .buildShellCommand(statCommand.replaceAll("\\\\", "/")) .build() @@ -390,7 +397,7 @@ public class AdbUtil { * @return */ public List listApp(String arg) { - String result = CommandExec.exec(AdbCommandBuilder.builder() + String result = new CommandExec(logFile, false).exec(AdbCommandBuilder.builder() .serial(this.getSerial()) .buildPmListPackagesCommand(arg) .build() @@ -406,7 +413,7 @@ public class AdbUtil { String packageName = T.StrUtil.trim(line.substring(prefix.length())); if (T.StrUtil.equals(DEFAULT_DROIDVNC_NG_PKG_NAME, packageName)) continue; - String dumpsysResult = CommandExec.exec(AdbCommandBuilder.builder() + String dumpsysResult = new CommandExec(logFile, false).exec(AdbCommandBuilder.builder() .serial(this.getSerial()) .buildShellCommand("shell dumpsys package " + packageName) .build() @@ -430,7 +437,7 @@ public class AdbUtil { String md5Value = md5sumRes.output(); File localApk = T.FileUtil.file(Constant.TEMP_PATH, md5Value + ".apk"); if (!T.FileUtil.exist(localApk)) { - CommandResult pulled = this.pull(finalApkPath, localApk.getAbsolutePath()); + CommandResult pulled = this.pull(finalApkPath, localApk.getAbsolutePath(), false); if (0 != pulled.exitCode()) { log.warn("[listApp] [pull apk error] [pkg: {}]", packageName); return null; @@ -487,12 +494,12 @@ public class AdbUtil { * install app * adb install apk */ - public CommandResult install(String localFilePath, boolean isDebugApk, boolean isReInstall) { - String result = CommandExec.exec(AdbCommandBuilder.builder() + public CommandResult install(String localFilePath, boolean isDebugApk, boolean isReInstall, boolean isRecordLog) { + String result = new CommandExec(logFile, isRecordLog).exec(AdbCommandBuilder.builder() .serial(this.getSerial()) .buildInstallCommand(localFilePath, isDebugApk, isReInstall) - .build() - ); + .build()); + log.info("[install] [localFilePath: {}] [isDebugApk: {}] [isReInstall: {}] [result: {}]", localFilePath, isDebugApk, isReInstall, result); return new CommandResult(T.StrUtil.containsAny(result, "Success") ? 0 : 1, result); } @@ -502,7 +509,7 @@ public class AdbUtil { * adb uninstall package_name */ public CommandResult uninstall(String packageName) { - String result = CommandExec.exec(AdbCommandBuilder.builder() + String result = new CommandExec(logFile, false).exec(AdbCommandBuilder.builder() .serial(this.getSerial()) .buildUnInstallCommand(packageName) .build() @@ -515,8 +522,8 @@ public class AdbUtil { * stop app * adb shell am force-stop package_name */ - public CommandResult stopApp(String packageName) { - String result = CommandExec.exec(AdbCommandBuilder.builder() + public CommandResult stopApp(String packageName, boolean isRecordLog) { + String result = new CommandExec(logFile, isRecordLog).exec(AdbCommandBuilder.builder() .serial(this.getSerial()) .buildStopAppCommand(packageName) .build() @@ -532,13 +539,13 @@ public class AdbUtil { @Deprecated private void cleanIptables() { // Delete all rules in chain or all chains - CommandExec.exec(AdbCommandBuilder.builder() + new CommandExec(logFile, false).exec(AdbCommandBuilder.builder() .serial(this.getSerial()) .buildShellCommand("shell iptables -F") .build() ); // Delete user-defined chain - CommandExec.exec(AdbCommandBuilder.builder() + new CommandExec(logFile, false).exec(AdbCommandBuilder.builder() .serial(this.getSerial()) .buildShellCommand("shell iptables -X") .build() @@ -549,7 +556,7 @@ public class AdbUtil { * list tcpdump */ public List listTcpdump() { - String result = CommandExec.exec(AdbCommandBuilder.builder() + String result = new CommandExec(logFile, false).exec(AdbCommandBuilder.builder() .serial(this.getSerial()) .buildShellCommand(String.format("shell \"ps -ef | grep tcpdump | grep -v grep | grep capture_ | awk '{print $NF}' \"")) .build()); @@ -584,11 +591,11 @@ public class AdbUtil { * start Tcpdump * tcpdump pcap */ - public CommandResult startTcpdump(String packageName) { + public CommandResult startTcpdump(String packageName, boolean isRecordLog) { String taskId = T.IdUtil.fastSimpleUUID(); if (T.StrUtil.isNotEmpty(packageName)) { log.info("[startTcpdump] [capture app package] [pkg: {}]", packageName); - String dumpsysResult = CommandExec.exec(AdbCommandBuilder.builder() + String dumpsysResult = new CommandExec(logFile, isRecordLog).exec(AdbCommandBuilder.builder() .serial(this.getSerial()) .buildShellCommand("shell dumpsys package " + packageName) .build() @@ -600,20 +607,20 @@ public class AdbUtil { .map(s -> T.StrUtil.trim(s).replaceAll("userId=", "")) .orElseThrow(() -> new APIException("Not found userId by package name. package name: " + packageName)); - CommandExec.exec(AdbCommandBuilder.builder() + new CommandExec(logFile, isRecordLog).exec(AdbCommandBuilder.builder() .serial(this.getSerial()) .buildShellCommand(String.format("shell iptables -A OUTPUT -m owner --uid-owner %s -j CONNMARK --set-mark %s", userId, userId)) .build()); - CommandExec.exec(AdbCommandBuilder.builder() + new CommandExec(logFile, isRecordLog).exec(AdbCommandBuilder.builder() .serial(this.getSerial()) .buildShellCommand(String.format("shell iptables -A INPUT -m connmark --mark %s -j NFLOG --nflog-group %s", userId, userId)) .build()); - CommandExec.exec(AdbCommandBuilder.builder() + new CommandExec(logFile, isRecordLog).exec(AdbCommandBuilder.builder() .serial(this.getSerial()) .buildShellCommand(String.format("shell iptables -A OUTPUT -m connmark --mark %s -j NFLOG --nflog-group %s", userId, userId)) .build()); - String ruleList = CommandExec.exec(AdbCommandBuilder.builder() + String ruleList = new CommandExec(logFile, isRecordLog).exec(AdbCommandBuilder.builder() .serial(this.getSerial()) .buildShellCommand("shell iptables -L") .build()); @@ -621,7 +628,7 @@ public class AdbUtil { // pcap 格式:capture_{userId}_{pcakageName}_{taskId}.pcap String pcapFilePath = "/data/local/tmp/capture_" + userId + "_" + packageName + "_" + taskId + ".pcap"; - CommandExec.execForProcess(AdbCommandBuilder.builder() + new CommandExec(logFile, isRecordLog).execForProcess(AdbCommandBuilder.builder() .serial(this.getSerial()) .buildShellCommand(String.format("shell tcpdump -i nflog:%s -w %s &", userId, pcapFilePath)) .build()); @@ -629,13 +636,13 @@ public class AdbUtil { log.info("[startTcpdump] [capture all package]"); // pcap 格式:capture_all_{taskId}.pcap String pcapFilePath = "/data/local/tmp/capture_all_" + taskId + ".pcap"; - CommandExec.execForProcess(AdbCommandBuilder.builder() + new CommandExec(logFile, isRecordLog).execForProcess(AdbCommandBuilder.builder() .serial(this.getSerial()) .buildShellCommand(String.format("shell tcpdump not port %s -w %s &", this.vncPort, pcapFilePath)) .build()); } - String result = CommandExec.exec(AdbCommandBuilder.builder() + String result = new CommandExec(logFile, isRecordLog).exec(AdbCommandBuilder.builder() .serial(this.getSerial()) .buildShellCommand(String.format("shell \"ps -ef | grep tcpdump | grep -v grep | grep %s | awk '{print $2}' \"", taskId)) .build()); @@ -647,8 +654,8 @@ public class AdbUtil { * stop tcpdump * kill -INT {pid} */ - public CommandResult stopTcpdump(String id) { - String pcapFilePath = CommandExec.exec(AdbCommandBuilder.builder() + public CommandResult stopTcpdump(String id, boolean isRecordLog) { + String pcapFilePath = new CommandExec(logFile, isRecordLog).exec(AdbCommandBuilder.builder() .serial(this.getSerial()) .buildShellCommand(String.format("shell \"ps -ef | grep tcpdump | grep -v grep | grep %s | awk '{print $NF}' \"", id)) .build()); @@ -658,21 +665,21 @@ public class AdbUtil { String[] split = T.FileUtil.mainName(pcapFilePath).split("_"); String userId = split[1]; log.info("[stopTcpdump] [remove iptables rule] [userId: {}]", userId); - CommandExec.exec(AdbCommandBuilder.builder() + new CommandExec(logFile, isRecordLog).exec(AdbCommandBuilder.builder() .serial(this.getSerial()) .buildShellCommand(String.format("shell iptables -D OUTPUT -m owner --uid-owner %s -j CONNMARK --set-mark %s", userId, userId)) .build()); - CommandExec.exec(AdbCommandBuilder.builder() + new CommandExec(logFile, isRecordLog).exec(AdbCommandBuilder.builder() .serial(this.getSerial()) .buildShellCommand(String.format("shell iptables -D INPUT -m connmark --mark %s -j NFLOG --nflog-group %s", userId, userId)) .build()); - CommandExec.exec(AdbCommandBuilder.builder() + new CommandExec(logFile, isRecordLog).exec(AdbCommandBuilder.builder() .serial(this.getSerial()) .buildShellCommand(String.format("shell iptables -D OUTPUT -m connmark --mark %s -j NFLOG --nflog-group %s", userId, userId)) .build()); } } - String result = CommandExec.exec(AdbCommandBuilder.builder() + String result = new CommandExec(logFile, isRecordLog).exec(AdbCommandBuilder.builder() .serial(this.getSerial()) .buildShellCommand(String.format("shell \"ps -ef | grep tcpdump | grep -v grep | grep %s | awk '{print $2}' | xargs kill -INT \"", id)) .build()); @@ -681,7 +688,7 @@ public class AdbUtil { for (int i = 0; i < 10; i++) { T.ThreadUtil.sleep(500); - String str = CommandExec.exec(AdbCommandBuilder.builder() + String str = new CommandExec(logFile, isRecordLog).exec(AdbCommandBuilder.builder() .serial(this.getSerial()) .buildShellCommand(String.format("shell \"ps -ef | grep tcpdump | grep -v grep | grep %s \"", id)) .build()); @@ -701,8 +708,8 @@ public class AdbUtil { /** * exec shell command */ - public void execShellCommand(String shellCmd) { - String result = CommandExec.exec(AdbCommandBuilder.builder() + public void execShellCommand(String shellCmd, boolean isRecordLog) { + String result = new CommandExec(logFile, isRecordLog).exec(AdbCommandBuilder.builder() .serial(this.getSerial()) .buildShellCommand(shellCmd) .build()); @@ -713,7 +720,7 @@ public class AdbUtil { * exec shell command */ public String execShellCommand(String cmd, Integer timeout){ - Process process = CommandExec.execForProcess(AdbCommandBuilder.builder() + Process process = new CommandExec(logFile, false).execForProcess(AdbCommandBuilder.builder() .serial(this.getSerial()) .buildShellCommand("shell " + cmd) .build()); @@ -739,16 +746,16 @@ public class AdbUtil { */ private void addAswOutputChain() { // name=ASW_OUTPUT - this.execShellCommand("shell iptables -N ASW_OUTPUT"); + this.execShellCommand("shell iptables -N ASW_OUTPUT", false); - String outputChainResult = CommandExec.exec(AdbCommandBuilder.builder() + String outputChainResult = new CommandExec(logFile, false).exec(AdbCommandBuilder.builder() .serial(this.getSerial()) .buildShellCommand(String.format("shell \"iptables -L OUTPUT --line-numbers | grep ASW_OUTPUT\"")) .build()); log.info("[addAswOutputChain] [ASW_OUTPUT in OUTPUT Chain] [result: {}]", outputChainResult); if (T.StrUtil.isEmpty(outputChainResult)) { // ASW_OUTPUT 添加到 OUTPUT 链中 - this.execShellCommand("shell iptables -A OUTPUT -j ASW_OUTPUT"); + this.execShellCommand("shell iptables -A OUTPUT -j ASW_OUTPUT", false); } } @@ -757,7 +764,7 @@ public class AdbUtil { * iptables -nL ASW_OUTPUT --line-numbers */ public List listAcl() { - String result = CommandExec.exec(AdbCommandBuilder.builder() + String result = new CommandExec(logFile, false).exec(AdbCommandBuilder.builder() .serial(this.getSerial()) .buildIptablesLnRulesCommand("ASW_OUTPUT") .build()); @@ -804,7 +811,7 @@ public class AdbUtil { this.addAswOutputChain(); // add chain ruls - String result = CommandExec.exec(AdbCommandBuilder.builder() + String result = new CommandExec(logFile, false).exec(AdbCommandBuilder.builder() .serial(this.getSerial()) .buildIptablesAddRuleCommand("ASW_OUTPUT", protocol, ip, port) .build()); @@ -818,7 +825,7 @@ public class AdbUtil { */ public void deleteAcl(String protocol, String ip, String port) { // add chain ruls - String result = CommandExec.exec(AdbCommandBuilder.builder() + String result = new CommandExec(logFile, false).exec(AdbCommandBuilder.builder() .serial(this.getSerial()) .buildIptablesDelRuleCommand("ASW_OUTPUT", protocol, ip, port) .build()); @@ -830,7 +837,7 @@ public class AdbUtil { * iptables -F ASW_OUTPUT */ public CommandResult flushAcl() { - String result = CommandExec.exec(AdbCommandBuilder.builder() + String result = new CommandExec(logFile, false).exec(AdbCommandBuilder.builder() .serial(this.getSerial()) .buildIptablesFlushRuleCommand("ASW_OUTPUT") .build()); @@ -851,30 +858,48 @@ public class AdbUtil { return threadPool; } + class CommandExec { - public static String exec(List command) { + + private File logFile; + private boolean isRecordLog; + + public String exec(List command) { String str = T.RuntimeUtil.execForStr(T.CharsetUtil.CHARSET_UTF_8, command.stream().toArray(String[]::new)); + if (isRecordLog) { + T.FileUtil.appendString(T.StrUtil.concat(true, "$ ", command.stream().collect(Collectors.joining(" ")), "\n"), this.logFile, "UTF-8"); + T.FileUtil.appendString(T.StrUtil.concat(true, str.stripTrailing(), "\n"), this.logFile, "UTF-8"); + } return str.stripTrailing(); } - public static Process execForProcess(List command) { + public Process execForProcess(List command) { Process process = T.RuntimeUtil.exec(command.stream().toArray(String[]::new)); return process; } + + public CommandExec(File logFile, boolean isRecordLog) { + this.logFile = logFile; + this.isRecordLog = isRecordLog; + } } - public CommandResult execPlaybook(String playbookPath) { + public CommandResult execPlaybook(String playbookPath, boolean isRecordLog) { log.info("[execPlaybook] [begin!] [serial:{}]", this.getSerial()); - Process process = CommandExec.execForProcess(new AdbCommandBuilder("airtest") + List command = new AdbCommandBuilder("airtest") .buildRunPlaybook(playbookPath, this.getSerial()) - .build()); + .build(); + Process process = new CommandExec(logFile, isRecordLog).execForProcess(command); ExecutorService executor = Executors.newSingleThreadExecutor(); Future future = executor.submit(() -> T.IoUtil.read(process.getInputStream(), T.CharsetUtil.CHARSET_UTF_8)); try { int exitCode = process.waitFor(); String result = future.get(10, TimeUnit.SECONDS); - log.info("[execPlaybook] [result: {}]", result); + if (isRecordLog){ + T.FileUtil.appendString(T.StrUtil.concat(true, "$ ", command.stream().collect(Collectors.joining(" ")), "\n"), this.logFile, "UTF-8"); + T.FileUtil.appendString(T.StrUtil.concat(true, result.stripTrailing(), "\n"), this.logFile, "UTF-8"); + } return new CommandResult(exitCode, result); } catch (Exception e) { process.destroyForcibly();