Skip to content

Commit 7167849

Browse files
committed
feat: Make python daemon run foreveeerr
1 parent 25ebd5b commit 7167849

File tree

3 files changed

+281
-91
lines changed

3 files changed

+281
-91
lines changed
Lines changed: 128 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,128 @@
1+
package io.github.berstanio.pymobiledevice3.daemon;
2+
3+
import io.github.berstanio.pymobiledevice3.venv.PyInstallation;
4+
5+
import java.io.BufferedReader;
6+
import java.io.File;
7+
import java.io.IOException;
8+
import java.io.InputStreamReader;
9+
import java.net.InetSocketAddress;
10+
import java.net.Socket;
11+
import java.nio.charset.StandardCharsets;
12+
import java.nio.file.FileSystems;
13+
import java.nio.file.Files;
14+
import java.nio.file.Path;
15+
import java.nio.file.Paths;
16+
import java.nio.file.StandardWatchEventKinds;
17+
import java.nio.file.WatchEvent;
18+
import java.nio.file.WatchKey;
19+
import java.nio.file.WatchService;
20+
21+
public class DaemonHandler {
22+
public static final String BASE_PORT_NAME = "javapymobiledevice3";
23+
public static final File BASE_TEMP_DIR_WIN = Paths.get(System.getProperty("user.home"), "AppData", "Local", "Temp").toFile();
24+
public static final File BASE_TEMP_DIR_UNIX = new File("/tmp/" + BASE_PORT_NAME + "/");
25+
public static final File UNIX_PORT_PATH = new File(BASE_TEMP_DIR_UNIX, BASE_PORT_NAME + "--" + getUnixUserId() + ".port");
26+
public static final File WINDOWS_PORT_PATH = new File(BASE_TEMP_DIR_WIN, BASE_PORT_NAME + ".port");
27+
28+
29+
public static File getTempDir() {
30+
if (System.getProperty("os.name").toLowerCase().contains("windows"))
31+
return BASE_TEMP_DIR_WIN;
32+
else
33+
return BASE_TEMP_DIR_UNIX;
34+
}
35+
36+
public static File getPortFile() {
37+
if (System.getProperty("os.name").toLowerCase().contains("windows"))
38+
return WINDOWS_PORT_PATH;
39+
else
40+
return UNIX_PORT_PATH;
41+
}
42+
43+
public static File getLogFile() {
44+
File tempDir = getTempDir();
45+
tempDir.mkdirs();
46+
return new File(tempDir, BASE_PORT_NAME + ".log");
47+
}
48+
49+
public static int getDaemonPort() {
50+
if (!getPortFile().exists())
51+
return -1;
52+
try {
53+
return Integer.parseInt(Files.readString(getPortFile().toPath(), StandardCharsets.UTF_8));
54+
} catch (IOException | NumberFormatException e) {
55+
return -1;
56+
}
57+
}
58+
59+
public static boolean isDaemonRunning() {
60+
Socket socket = getDaemonSocket();
61+
if (socket == null)
62+
return false;
63+
try {
64+
socket.close();
65+
} catch (IOException e) {
66+
return false;
67+
}
68+
69+
return true;
70+
}
71+
72+
public static Socket getDaemonSocket() {
73+
int port = getDaemonPort();
74+
if (port == -1)
75+
return null;
76+
77+
try {
78+
Socket socket = new Socket();
79+
socket.connect(new InetSocketAddress("127.0.0.1", port), 5000);
80+
return socket;
81+
} catch (IOException e) {
82+
return null;
83+
}
84+
}
85+
86+
public static void startDaemon(PyInstallation installation) throws IOException {
87+
File portFile = getPortFile();
88+
Files.deleteIfExists(portFile.toPath());
89+
ProcessBuilder pb = new ProcessBuilder()
90+
.command(installation.getPythonExecutable().getAbsolutePath() , "-u", installation.getHandler().getAbsolutePath(), portFile.getAbsolutePath());
91+
92+
pb.redirectErrorStream(true);
93+
pb.redirectOutput(getLogFile());
94+
pb.start();
95+
96+
try (WatchService watchService = FileSystems.getDefault().newWatchService()){
97+
portFile.toPath().getParent().register(watchService, StandardWatchEventKinds.ENTRY_MODIFY);
98+
while (true) {
99+
WatchKey key = watchService.take();
100+
for (WatchEvent<?> event : key.pollEvents()) {
101+
WatchEvent.Kind<?> kind = event.kind();
102+
if (kind != StandardWatchEventKinds.ENTRY_MODIFY)
103+
continue;
104+
Path modifiedFile = (Path) event.context();
105+
if (!modifiedFile.toString().equals(portFile.getName()))
106+
continue;
107+
if (getDaemonPort() != -1)
108+
return;
109+
}
110+
}
111+
} catch (InterruptedException e) {
112+
throw new RuntimeException(e);
113+
}
114+
}
115+
116+
private static String getUnixUserId() {
117+
try {
118+
Process process = new ProcessBuilder("id", "-u").start();
119+
int res = process.waitFor();
120+
if (res != 0)
121+
throw new IllegalStateException("Failed to retrive uid");
122+
BufferedReader reader = new BufferedReader(new InputStreamReader(process.getInputStream()));
123+
return reader.readLine().trim();
124+
} catch (Exception e) {
125+
throw new IllegalStateException("Failed to retrive uid");
126+
}
127+
}
128+
}

src/main/java/io/github/berstanio/pymobiledevice3/ipc/PyMobileDevice3IPC.java

Lines changed: 22 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,10 @@
11
package io.github.berstanio.pymobiledevice3.ipc;
22

3+
import io.github.berstanio.pymobiledevice3.daemon.DaemonHandler;
34
import io.github.berstanio.pymobiledevice3.data.DebugServerConnection;
4-
import io.github.berstanio.pymobiledevice3.data.PyMobileDevice3Error;
55
import io.github.berstanio.pymobiledevice3.data.DeviceInfo;
66
import io.github.berstanio.pymobiledevice3.data.InstallMode;
7+
import io.github.berstanio.pymobiledevice3.data.PyMobileDevice3Error;
78
import io.github.berstanio.pymobiledevice3.data.USBMuxForwarder;
89
import io.github.berstanio.pymobiledevice3.venv.PyInstallation;
910
import io.github.berstanio.pymobiledevice3.venv.PyInstallationHandler;
@@ -16,10 +17,7 @@
1617
import java.io.IOException;
1718
import java.io.InputStreamReader;
1819
import java.io.PrintWriter;
19-
import java.net.InetAddress;
20-
import java.net.ServerSocket;
2120
import java.net.Socket;
22-
import java.nio.charset.StandardCharsets;
2321
import java.util.concurrent.ArrayBlockingQueue;
2422
import java.util.concurrent.CompletableFuture;
2523
import java.util.concurrent.ConcurrentHashMap;
@@ -32,9 +30,6 @@ public class PyMobileDevice3IPC implements Closeable {
3230

3331
private static final boolean DEBUG = System.getProperty("java.pymobiledevice3.debug") != null;
3432

35-
private final PyInstallation installation;
36-
private final Process process;
37-
private final ServerSocket serverSocket;
3833
private final Socket socket;
3934
private final BufferedReader reader;
4035
private final PrintWriter writer;
@@ -48,21 +43,12 @@ public class PyMobileDevice3IPC implements Closeable {
4843

4944
private boolean destroyed = false;
5045

51-
public PyMobileDevice3IPC(PyInstallation installation) throws IOException {
52-
this.installation = installation;
53-
serverSocket = new ServerSocket(0, 50, InetAddress.getByName("localhost"));
54-
int port = serverSocket.getLocalPort();
55-
ProcessBuilder pb = new ProcessBuilder()
56-
.command(installation.getPythonExecutable().getAbsolutePath() , "-u", installation.getHandler().getAbsolutePath(), String.valueOf(port));
57-
58-
if (DEBUG)
59-
pb.inheritIO();
60-
else
61-
pb.redirectErrorStream(true);
62-
63-
process = pb.start();
46+
public PyMobileDevice3IPC() throws IOException {
47+
Socket daemonSocket = DaemonHandler.getDaemonSocket();
48+
if (daemonSocket == null)
49+
throw new IllegalStateException("Daemon is not running");
6450

65-
socket = serverSocket.accept();
51+
socket = daemonSocket;
6652
reader = new BufferedReader(new InputStreamReader(socket.getInputStream()));
6753
writer = new PrintWriter(socket.getOutputStream(), true);
6854

@@ -84,9 +70,8 @@ public PyMobileDevice3IPC(PyInstallation installation) throws IOException {
8470
try {
8571
String line = reader.readLine();
8672
if (line == null) {
87-
String log = new String(process.getInputStream().readAllBytes(), StandardCharsets.UTF_8);
8873
for (CompletableFuture<?> future : futures.values()) {
89-
future.completeExceptionally(new PyMobileDevice3Error("Python process died: " + log));
74+
future.completeExceptionally(new PyMobileDevice3Error("Python process died"));
9075
}
9176

9277
close();
@@ -257,7 +242,7 @@ public CompletableFuture<DebugServerConnection> debugServerConnect(DeviceInfo in
257242
object.put("port", port);
258243
return createRequest(object, (future, jsonObject) -> {
259244
if (jsonObject.getString("state").equals("failed_tunneld")) {
260-
future.completeExceptionally(new PyMobileDevice3Error("No tunneld instance for device " + info.getUniqueDeviceId() + " found. Have you started the service with `sudo " + installation.getPythonExecutable().getAbsolutePath() + " -m pymobiledevice3 remote tunneld`?"));
245+
future.completeExceptionally(new PyMobileDevice3Error("No tunneld instance for device " + info.getUniqueDeviceId() + " found."));
261246
return;
262247
}
263248
JSONObject result = jsonObject.getJSONObject("result");
@@ -296,9 +281,19 @@ public CompletableFuture<Void> usbMuxForwarderClose(USBMuxForwarder connection)
296281
});
297282
}
298283

284+
public CompletableFuture<Void> forceKillDaemon() {
285+
JSONObject object = new JSONObject();
286+
object.put("id", commandId.getAndIncrement());
287+
object.put("command", "exit");
288+
if (!writeQueue.offer(object.toString()))
289+
return CompletableFuture.failedFuture(new PyMobileDevice3Error("Write queue overflow"));
290+
return CompletableFuture.completedFuture(null);
291+
}
292+
299293
public static void main(String[] args) throws IOException {
300294
PyInstallation installation = PyInstallationHandler.install(new File("build/pyenv/"));
301-
try (PyMobileDevice3IPC ipc = new PyMobileDevice3IPC(installation)) {
295+
DaemonHandler.startDaemon(installation);
296+
try (PyMobileDevice3IPC ipc = new PyMobileDevice3IPC()) {
302297
//JSONObject object = ipc.decodePList(new File("/Volumes/ExternalSSD/IdeaProjects/MOE-Upstream/moe/samples-java/Calculator/ios/build/moe/xcodebuild/Release-iphoneos/ios.app/Info.plist")).join();
303298
//System.out.println(object.getString("CFBundleExecutable"));
304299
//for (DeviceInfo info : ipc.listDevices().join())
@@ -308,6 +303,8 @@ public static void main(String[] args) throws IOException {
308303
DebugServerConnection connection = ipc.debugServerConnect(info, 0).join();
309304

310305
ipc.debugServerClose(connection).join();
306+
307+
ipc.forceKillDaemon().join();
311308
}
312309
}
313310

@@ -322,9 +319,6 @@ public void close() {
322319
writeThread.join();
323320

324321
socket.close();
325-
serverSocket.close();
326-
process.destroyForcibly();
327-
process.onExit().join();
328322
commandResults.clear();
329323
writeQueue.clear();
330324

0 commit comments

Comments
 (0)