fastled 1.1.7__py2.py3-none-any.whl → 1.1.16__py2.py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
fastled/compile_server.py CHANGED
@@ -1,251 +1,156 @@
1
- import socket
2
- import subprocess
3
- import threading
4
- import time
5
- from pathlib import Path
6
- from typing import Optional
7
-
8
- import httpx
9
-
10
- from fastled.docker_manager import DockerManager
11
- from fastled.sketch import looks_like_fastled_repo
12
-
13
- _DEFAULT_CONTAINER_NAME = "fastled-wasm-compiler"
14
-
15
- SERVER_PORT = 9021
16
-
17
-
18
- def find_available_port(start_port: int = SERVER_PORT) -> int:
19
- """Find an available port starting from the given port."""
20
- port = start_port
21
- end_port = start_port + 1000
22
- while True:
23
- with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
24
- if s.connect_ex(("localhost", port)) != 0:
25
- return port
26
- port += 1
27
- if port >= end_port:
28
- raise RuntimeError("No available ports found")
29
-
30
-
31
- class CompileServer:
32
- def __init__(
33
- self,
34
- container_name=_DEFAULT_CONTAINER_NAME,
35
- interactive: bool = False,
36
- ) -> None:
37
-
38
- cwd = Path(".").resolve()
39
- fastled_src_dir: Path | None = None
40
- if looks_like_fastled_repo(cwd):
41
- print(
42
- "Looks like a FastLED repo, using it as the source directory and mapping it into the server."
43
- )
44
- fastled_src_dir = cwd / "src"
45
-
46
- self.container_name = container_name
47
- self.docker = DockerManager(container_name=container_name)
48
- self.running = False
49
- self.thread: Optional[threading.Thread] = None
50
- self.running_process: subprocess.Popen | None = None
51
- self.fastled_src_dir: Path | None = fastled_src_dir
52
- self.interactive = interactive
53
- self._port = self._start()
54
-
55
- def port(self) -> int:
56
- return self._port
57
-
58
- def url(self) -> str:
59
- return f"http://localhost:{self._port}"
60
-
61
- def wait_for_startup(self, timeout: int = 100) -> bool:
62
- """Wait for the server to start up."""
63
- start_time = time.time()
64
- while time.time() - start_time < timeout:
65
- # ping the server to see if it's up
66
- if not self._port:
67
- return False
68
- # use httpx to ping the server
69
- # if successful, return True
70
- try:
71
- response = httpx.get(
72
- f"http://localhost:{self._port}", follow_redirects=True
73
- )
74
- if response.status_code < 400:
75
- return True
76
- except KeyboardInterrupt:
77
- raise
78
- except Exception:
79
- pass
80
- time.sleep(0.1)
81
- if not self.running:
82
- return False
83
- return False
84
-
85
- def _start(self) -> int:
86
- print("Compiling server starting")
87
- self.running = True
88
- # Ensure Docker is running
89
- with self.docker.get_lock():
90
- if not self.docker.is_running():
91
- if not self.docker.start():
92
- print("Docker could not be started. Exiting.")
93
- raise RuntimeError("Docker could not be started. Exiting.")
94
-
95
- # Clean up any existing container with the same name
96
-
97
- try:
98
- container_exists = (
99
- subprocess.run(
100
- ["docker", "inspect", self.container_name],
101
- capture_output=True,
102
- text=True,
103
- ).returncode
104
- == 0
105
- )
106
- if container_exists:
107
- print("Cleaning up existing container")
108
- subprocess.run(
109
- ["docker", "rm", "-f", self.container_name],
110
- check=False,
111
- )
112
- except KeyboardInterrupt:
113
- raise
114
- except Exception as e:
115
- print(f"Warning: Failed to remove existing container: {e}")
116
-
117
- print("Ensuring Docker image exists at latest version")
118
- if not self.docker.ensure_image_exists():
119
- print("Failed to ensure Docker image exists.")
120
- raise RuntimeError("Failed to ensure Docker image exists")
121
-
122
- print("Docker image now validated")
123
-
124
- # Remove the image to force a fresh download
125
- # subprocess.run(["docker", "rmi", "fastled-wasm"], capture_output=True)
126
- # print("All clean")
127
-
128
- port = find_available_port()
129
- print(f"Found an available port: {port}")
130
- # server_command = ["python", "/js/run.py", "server", "--allow-shutdown"]
131
- if self.interactive:
132
- server_command = ["/bin/bash"]
133
- else:
134
- server_command = ["python", "/js/run.py", "server"]
135
- print(f"Started Docker container with command: {server_command}")
136
- ports = {port: 80}
137
- volumes = None
138
- if self.fastled_src_dir:
139
- print(
140
- f"Mounting FastLED source directory {self.fastled_src_dir} into container /host/fastled/src"
141
- )
142
- volumes = {
143
- str(self.fastled_src_dir): {"bind": "/host/fastled/src", "mode": "ro"}
144
- }
145
- if not self.interactive:
146
- # no auto-update because the source directory is mapped in.
147
- # This should be automatic now.
148
- server_command.append("--no-auto-update") # stop git repo updates.
149
- self.running_process = self.docker.run_container(
150
- server_command, ports=ports, volumes=volumes
151
- )
152
- print("Compile server starting")
153
- time.sleep(3)
154
- if self.running_process.poll() is not None:
155
- print("Server failed to start")
156
- self.running = False
157
- raise RuntimeError("Server failed to start")
158
- self.thread = threading.Thread(target=self._server_loop, daemon=True)
159
- self.thread.start()
160
-
161
- return port
162
-
163
- def proceess_running(self) -> bool:
164
- if self.running_process is None:
165
- return False
166
- return self.running_process.poll() is None
167
-
168
- def stop(self) -> None:
169
- print(f"Stopping server on port {self._port}")
170
- # attempt to send a shutdown signal to the server
171
- try:
172
- httpx.get(f"http://localhost:{self._port}/shutdown", timeout=2)
173
- # except Exception:
174
- except Exception as e:
175
- print(f"Failed to send shutdown signal: {e}")
176
- pass
177
- try:
178
- # Stop the Docker container
179
- cp: subprocess.CompletedProcess
180
- cp = subprocess.run(
181
- ["docker", "stop", self.container_name],
182
- capture_output=True,
183
- text=True,
184
- check=True,
185
- )
186
- if cp.returncode != 0:
187
- print(f"Failed to stop Docker container: {cp.stderr}")
188
-
189
- cp = subprocess.run(
190
- ["docker", "rm", self.container_name],
191
- capture_output=True,
192
- text=True,
193
- check=True,
194
- )
195
- if cp.returncode != 0:
196
- print(f"Failed to remove Docker container: {cp.stderr}")
197
-
198
- # Close the stdout pipe
199
- if self.running_process and self.running_process.stdout:
200
- self.running_process.stdout.close()
201
-
202
- # Wait for the process to fully terminate with a timeout
203
- self.running_process.wait(timeout=10)
204
- if self.running_process.returncode is None:
205
- # kill
206
- self.running_process.kill()
207
- if self.running_process.returncode is not None:
208
- print(
209
- f"Server stopped with return code {self.running_process.returncode}"
210
- )
211
-
212
- except subprocess.TimeoutExpired:
213
- # Force kill if it doesn't stop gracefully
214
- if self.running_process:
215
- self.running_process.kill()
216
- self.running_process.wait()
217
- except KeyboardInterrupt:
218
- if self.running_process:
219
- self.running_process.kill()
220
- self.running_process.wait()
221
- except Exception as e:
222
- print(f"Error stopping Docker container: {e}")
223
- finally:
224
- self.running_process = None
225
- # Signal the server thread to stop
226
- self.running = False
227
- if self.thread:
228
- self.thread.join(timeout=10) # Wait up to 10 seconds for thread to finish
229
- if self.thread.is_alive():
230
- print("Warning: Server thread did not terminate properly")
231
-
232
- print("Compile server stopped")
233
-
234
- def _server_loop(self) -> None:
235
- try:
236
- while self.running:
237
- if self.running_process:
238
- # Read Docker container output
239
- # Check if Docker process is still running
240
- if self.running_process.poll() is not None:
241
- print("Docker server stopped unexpectedly")
242
- self.running = False
243
- break
244
-
245
- time.sleep(0.1) # Prevent busy waiting
246
- except KeyboardInterrupt:
247
- print("Server thread stopped by user.")
248
- except Exception as e:
249
- print(f"Error in server thread: {e}")
250
- finally:
251
- self.running = False
1
+ import subprocess
2
+ import time
3
+ from pathlib import Path
4
+
5
+ import httpx
6
+
7
+ from fastled.docker_manager import DISK_CACHE, DockerManager, RunningContainer
8
+ from fastled.sketch import looks_like_fastled_repo
9
+
10
+ _IMAGE_NAME = "niteris/fastled-wasm"
11
+ _DEFAULT_CONTAINER_NAME = "fastled-wasm-compiler"
12
+
13
+ SERVER_PORT = 9021
14
+
15
+ SERVER_OPTIONS = ["--allow-shutdown", "--no-auto-update"]
16
+
17
+
18
+ class CompileServer:
19
+ def __init__(
20
+ self,
21
+ container_name=_DEFAULT_CONTAINER_NAME,
22
+ interactive: bool = False,
23
+ ) -> None:
24
+
25
+ cwd = Path(".").resolve()
26
+ fastled_src_dir: Path | None = None
27
+ if looks_like_fastled_repo(cwd):
28
+ print(
29
+ "Looks like a FastLED repo, using it as the source directory and mapping it into the server."
30
+ )
31
+ fastled_src_dir = cwd / "src"
32
+
33
+ self.container_name = container_name
34
+ self.docker = DockerManager()
35
+ self.fastled_src_dir: Path | None = fastled_src_dir
36
+ self.interactive = interactive
37
+ self.running_container: RunningContainer | None = None
38
+ self._port = self._start()
39
+ # fancy print
40
+ if not interactive:
41
+ msg = f"# FastLED Compile Server started at {self.url()} #"
42
+ print("\n" + "#" * len(msg))
43
+ print(msg)
44
+ print("#" * len(msg) + "\n")
45
+
46
+ @property
47
+ def running(self) -> bool:
48
+ if not self._port:
49
+ return False
50
+ if not DockerManager.is_docker_installed():
51
+ return False
52
+ if not DockerManager.is_running():
53
+ return False
54
+ return self.docker.is_container_running(self.container_name)
55
+
56
+ def using_fastled_src_dir_volume(self) -> bool:
57
+ return self.fastled_src_dir is not None
58
+
59
+ def port(self) -> int:
60
+ return self._port
61
+
62
+ def url(self) -> str:
63
+ return f"http://localhost:{self._port}"
64
+
65
+ def wait_for_startup(self, timeout: int = 100) -> bool:
66
+ """Wait for the server to start up."""
67
+ start_time = time.time()
68
+ while time.time() - start_time < timeout:
69
+ # ping the server to see if it's up
70
+ if not self._port:
71
+ return False
72
+ # use httpx to ping the server
73
+ # if successful, return True
74
+ try:
75
+ response = httpx.get(
76
+ f"http://localhost:{self._port}", follow_redirects=True
77
+ )
78
+ if response.status_code < 400:
79
+ return True
80
+ except KeyboardInterrupt:
81
+ raise
82
+ except Exception:
83
+ pass
84
+ time.sleep(0.1)
85
+ if not self.docker.is_container_running(self.container_name):
86
+ return False
87
+ return False
88
+
89
+ def _start(self) -> int:
90
+ print("Compiling server starting")
91
+
92
+ # Ensure Docker is running
93
+ with self.docker.get_lock():
94
+ if not self.docker.is_running():
95
+ if not self.docker.start():
96
+ print("Docker could not be started. Exiting.")
97
+ raise RuntimeError("Docker could not be started. Exiting.")
98
+ from datetime import datetime, timezone
99
+
100
+ now = datetime.now(timezone.utc)
101
+ now_str = now.strftime("%Y-%m-%d %H %Z")
102
+ prev_date_str = DISK_CACHE.get("last-update")
103
+
104
+ upgrade = False
105
+ if prev_date_str != now_str:
106
+ print("New day, upgrading Docker image")
107
+ upgrade = True
108
+
109
+ self.docker.validate_or_download_image(
110
+ image_name=_IMAGE_NAME, tag="main", upgrade=upgrade
111
+ )
112
+ DISK_CACHE.put("last-update", now_str)
113
+
114
+ print("Docker image now validated")
115
+ port = SERVER_PORT
116
+ if self.interactive:
117
+ server_command = ["/bin/bash"]
118
+ else:
119
+ server_command = ["python", "/js/run.py", "server"] + SERVER_OPTIONS
120
+ server_cmd_str = subprocess.list2cmdline(server_command)
121
+ print(f"Started Docker container with command: {server_cmd_str}")
122
+ ports = {80: port}
123
+ volumes = None
124
+ if self.fastled_src_dir:
125
+ print(
126
+ f"Mounting FastLED source directory {self.fastled_src_dir} into container /host/fastled/src"
127
+ )
128
+ volumes = {
129
+ str(self.fastled_src_dir): {"bind": "/host/fastled/src", "mode": "ro"}
130
+ }
131
+
132
+ cmd_str = subprocess.list2cmdline(server_command)
133
+
134
+ self.docker.run_container(
135
+ image_name=_IMAGE_NAME,
136
+ tag="main",
137
+ container_name=self.container_name,
138
+ command=cmd_str,
139
+ ports=ports,
140
+ volumes=volumes,
141
+ )
142
+ self.running_container = self.docker.attach_and_run(self.container_name)
143
+ assert self.running_container is not None, "Container should be running"
144
+
145
+ print("Compile server starting")
146
+ return port
147
+
148
+ def proceess_running(self) -> bool:
149
+ return self.docker.is_container_running(self.container_name)
150
+
151
+ def stop(self) -> None:
152
+ # print(f"Stopping server on port {self._port}")
153
+ if self.running_container:
154
+ self.running_container.stop()
155
+ self.docker.suspend_container(self.container_name)
156
+ print("Compile server stopped")