fastled 1.2.82__tar.gz → 1.2.84__tar.gz
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-1.2.82 → fastled-1.2.84}/.gitignore +1 -0
- {fastled-1.2.82 → fastled-1.2.84}/PKG-INFO +1 -1
- fastled-1.2.84/build_local_docker.py +4 -0
- {fastled-1.2.82 → fastled-1.2.84}/compiler/server.py +50 -43
- {fastled-1.2.82 → fastled-1.2.84}/src/fastled/__init__.py +1 -1
- {fastled-1.2.82 → fastled-1.2.84}/src/fastled/client_server.py +8 -2
- fastled-1.2.84/src/fastled/server_flask.py +462 -0
- {fastled-1.2.82 → fastled-1.2.84}/src/fastled.egg-info/PKG-INFO +1 -1
- {fastled-1.2.82 → fastled-1.2.84}/src/fastled.egg-info/SOURCES.txt +2 -1
- {fastled-1.2.82 → fastled-1.2.84}/test +4 -0
- fastled-1.2.82/tests/unit/test_fastled_source_get.py → fastled-1.2.84/tests/unit/test_fetch_source_files.py +13 -1
- fastled-1.2.82/src/fastled/server_flask.py +0 -212
- {fastled-1.2.82 → fastled-1.2.84}/.aiderignore +0 -0
- {fastled-1.2.82 → fastled-1.2.84}/.dockerignore +0 -0
- {fastled-1.2.82 → fastled-1.2.84}/.github/workflows/build_multi_docker_image.yml +0 -0
- {fastled-1.2.82 → fastled-1.2.84}/.github/workflows/build_webpage.yml +0 -0
- {fastled-1.2.82 → fastled-1.2.84}/.github/workflows/lint.yml +0 -0
- {fastled-1.2.82 → fastled-1.2.84}/.github/workflows/publish_release.yml +0 -0
- {fastled-1.2.82 → fastled-1.2.84}/.github/workflows/template_build_docker_image.yml +0 -0
- {fastled-1.2.82 → fastled-1.2.84}/.github/workflows/test_build_exe.yml +0 -0
- {fastled-1.2.82 → fastled-1.2.84}/.github/workflows/test_macos.yml +0 -0
- {fastled-1.2.82 → fastled-1.2.84}/.github/workflows/test_ubuntu.yml +0 -0
- {fastled-1.2.82 → fastled-1.2.84}/.github/workflows/test_win.yml +0 -0
- {fastled-1.2.82 → fastled-1.2.84}/.pylintrc +0 -0
- {fastled-1.2.82 → fastled-1.2.84}/.vscode/launch.json +0 -0
- {fastled-1.2.82 → fastled-1.2.84}/.vscode/settings.json +0 -0
- {fastled-1.2.82 → fastled-1.2.84}/.vscode/tasks.json +0 -0
- {fastled-1.2.82 → fastled-1.2.84}/Dockerfile +0 -0
- {fastled-1.2.82 → fastled-1.2.84}/LICENSE +0 -0
- {fastled-1.2.82 → fastled-1.2.84}/MANIFEST.in +0 -0
- {fastled-1.2.82 → fastled-1.2.84}/README.md +0 -0
- {fastled-1.2.82 → fastled-1.2.84}/RELEASE.md +0 -0
- {fastled-1.2.82 → fastled-1.2.84}/TODO.md +0 -0
- {fastled-1.2.82 → fastled-1.2.84}/build_exe.py +0 -0
- {fastled-1.2.82 → fastled-1.2.84}/build_site.py +0 -0
- {fastled-1.2.82 → fastled-1.2.84}/clean +0 -0
- {fastled-1.2.82 → fastled-1.2.84}/compiler/CMakeLists.txt +0 -0
- {fastled-1.2.82 → fastled-1.2.84}/compiler/__init__.py +0 -0
- {fastled-1.2.82 → fastled-1.2.84}/compiler/arduino-pre-process.sh +0 -0
- {fastled-1.2.82 → fastled-1.2.84}/compiler/build.sh +0 -0
- {fastled-1.2.82 → fastled-1.2.84}/compiler/build_archive.sh +0 -0
- {fastled-1.2.82 → fastled-1.2.84}/compiler/build_fast.sh +0 -0
- {fastled-1.2.82 → fastled-1.2.84}/compiler/code_sync.py +0 -0
- {fastled-1.2.82 → fastled-1.2.84}/compiler/compile.py +0 -0
- {fastled-1.2.82 → fastled-1.2.84}/compiler/compile_lock.py +0 -0
- {fastled-1.2.82 → fastled-1.2.84}/compiler/debug.sh +0 -0
- {fastled-1.2.82 → fastled-1.2.84}/compiler/entrypoint.sh +0 -0
- {fastled-1.2.82 → fastled-1.2.84}/compiler/extra/100dots.html +0 -0
- {fastled-1.2.82 → fastled-1.2.84}/compiler/extra/demo_threejs.html +0 -0
- {fastled-1.2.82 → fastled-1.2.84}/compiler/extra/micdemo.html +0 -0
- {fastled-1.2.82 → fastled-1.2.84}/compiler/extra/mp3upload.html +0 -0
- {fastled-1.2.82 → fastled-1.2.84}/compiler/extra/webgl_postprocessing_unreal_bloom.html +0 -0
- {fastled-1.2.82 → fastled-1.2.84}/compiler/final_prewarm.sh +0 -0
- {fastled-1.2.82 → fastled-1.2.84}/compiler/init_runtime.py +0 -0
- {fastled-1.2.82 → fastled-1.2.84}/compiler/install-arduino-cli.sh +0 -0
- {fastled-1.2.82 → fastled-1.2.84}/compiler/libcompile/CMakeLists.txt +0 -0
- {fastled-1.2.82 → fastled-1.2.84}/compiler/paths.py +0 -0
- {fastled-1.2.82 → fastled-1.2.84}/compiler/pre-process.sh +0 -0
- {fastled-1.2.82 → fastled-1.2.84}/compiler/prewarm.sh +0 -0
- {fastled-1.2.82 → fastled-1.2.84}/compiler/process-ino.py +0 -0
- {fastled-1.2.82 → fastled-1.2.84}/compiler/process_extended.py +0 -0
- {fastled-1.2.82 → fastled-1.2.84}/compiler/pyproject.toml +0 -0
- {fastled-1.2.82 → fastled-1.2.84}/compiler/run.py +0 -0
- {fastled-1.2.82 → fastled-1.2.84}/compiler/sketch_hasher.py +0 -0
- {fastled-1.2.82 → fastled-1.2.84}/compiler/wasm_compiler_flags.py +0 -0
- {fastled-1.2.82 → fastled-1.2.84}/docker-compose.yml +0 -0
- {fastled-1.2.82 → fastled-1.2.84}/install +0 -0
- {fastled-1.2.82 → fastled-1.2.84}/install_linux.sh +0 -0
- {fastled-1.2.82 → fastled-1.2.84}/lint +0 -0
- {fastled-1.2.82 → fastled-1.2.84}/pyproject.toml +0 -0
- {fastled-1.2.82 → fastled-1.2.84}/requirements.testing.txt +0 -0
- {fastled-1.2.82 → fastled-1.2.84}/setup.cfg +0 -0
- {fastled-1.2.82 → fastled-1.2.84}/setup.py +0 -0
- {fastled-1.2.82 → fastled-1.2.84}/src/fastled/app.py +0 -0
- {fastled-1.2.82 → fastled-1.2.84}/src/fastled/assets/example.txt +0 -0
- {fastled-1.2.82 → fastled-1.2.84}/src/fastled/assets/localhost-key.pem +0 -0
- {fastled-1.2.82 → fastled-1.2.84}/src/fastled/assets/localhost.pem +0 -0
- {fastled-1.2.82 → fastled-1.2.84}/src/fastled/cli.py +0 -0
- {fastled-1.2.82 → fastled-1.2.84}/src/fastled/cli_test.py +0 -0
- {fastled-1.2.82 → fastled-1.2.84}/src/fastled/cli_test_interactive.py +0 -0
- {fastled-1.2.82 → fastled-1.2.84}/src/fastled/compile_server.py +0 -0
- {fastled-1.2.82 → fastled-1.2.84}/src/fastled/compile_server_impl.py +0 -0
- {fastled-1.2.82 → fastled-1.2.84}/src/fastled/docker_manager.py +0 -0
- {fastled-1.2.82 → fastled-1.2.84}/src/fastled/filewatcher.py +0 -0
- {fastled-1.2.82 → fastled-1.2.84}/src/fastled/keyboard.py +0 -0
- {fastled-1.2.82 → fastled-1.2.84}/src/fastled/keyz.py +0 -0
- {fastled-1.2.82 → fastled-1.2.84}/src/fastled/live_client.py +0 -0
- {fastled-1.2.82 → fastled-1.2.84}/src/fastled/open_browser.py +0 -0
- {fastled-1.2.82 → fastled-1.2.84}/src/fastled/parse_args.py +0 -0
- {fastled-1.2.82 → fastled-1.2.84}/src/fastled/paths.py +0 -0
- {fastled-1.2.82 → fastled-1.2.84}/src/fastled/print_filter.py +0 -0
- {fastled-1.2.82 → fastled-1.2.84}/src/fastled/project_init.py +0 -0
- {fastled-1.2.82 → fastled-1.2.84}/src/fastled/select_sketch_directory.py +0 -0
- {fastled-1.2.82 → fastled-1.2.84}/src/fastled/server_start.py +0 -0
- {fastled-1.2.82 → fastled-1.2.84}/src/fastled/settings.py +0 -0
- {fastled-1.2.82 → fastled-1.2.84}/src/fastled/site/build.py +0 -0
- {fastled-1.2.82 → fastled-1.2.84}/src/fastled/site/examples.py +0 -0
- {fastled-1.2.82 → fastled-1.2.84}/src/fastled/sketch.py +0 -0
- {fastled-1.2.82 → fastled-1.2.84}/src/fastled/spinner.py +0 -0
- {fastled-1.2.82 → fastled-1.2.84}/src/fastled/string_diff.py +0 -0
- {fastled-1.2.82 → fastled-1.2.84}/src/fastled/test/can_run_local_docker_tests.py +0 -0
- {fastled-1.2.82 → fastled-1.2.84}/src/fastled/test/examples.py +0 -0
- {fastled-1.2.82 → fastled-1.2.84}/src/fastled/types.py +0 -0
- {fastled-1.2.82 → fastled-1.2.84}/src/fastled/util.py +0 -0
- {fastled-1.2.82 → fastled-1.2.84}/src/fastled/web_compile.py +0 -0
- {fastled-1.2.82 → fastled-1.2.84}/src/fastled.egg-info/dependency_links.txt +0 -0
- {fastled-1.2.82 → fastled-1.2.84}/src/fastled.egg-info/entry_points.txt +0 -0
- {fastled-1.2.82 → fastled-1.2.84}/src/fastled.egg-info/requires.txt +0 -0
- {fastled-1.2.82 → fastled-1.2.84}/src/fastled.egg-info/top_level.txt +0 -0
- {fastled-1.2.82 → fastled-1.2.84}/tests/integration/test_build_examples.py +0 -0
- {fastled-1.2.82 → fastled-1.2.84}/tests/integration/test_examples.py +0 -0
- {fastled-1.2.82 → fastled-1.2.84}/tests/unit/html/index.html +0 -0
- {fastled-1.2.82 → fastled-1.2.84}/tests/unit/test_api.py +0 -0
- {fastled-1.2.82 → fastled-1.2.84}/tests/unit/test_bad_ino.py +0 -0
- {fastled-1.2.82 → fastled-1.2.84}/tests/unit/test_cli.py +0 -0
- {fastled-1.2.82 → fastled-1.2.84}/tests/unit/test_compile_server.py +0 -0
- {fastled-1.2.82 → fastled-1.2.84}/tests/unit/test_docker_linux_on_windows.py +0 -0
- {fastled-1.2.82 → fastled-1.2.84}/tests/unit/test_embedded_data.py +0 -0
- {fastled-1.2.82 → fastled-1.2.84}/tests/unit/test_filechanger.py +0 -0
- {fastled-1.2.82 → fastled-1.2.84}/tests/unit/test_http_server.py +0 -0
- {fastled-1.2.82 → fastled-1.2.84}/tests/unit/test_ino/bad/bad.ino +0 -0
- {fastled-1.2.82 → fastled-1.2.84}/tests/unit/test_ino/bad_platformio/bad_platformio.ino +0 -0
- {fastled-1.2.82 → fastled-1.2.84}/tests/unit/test_ino/bad_platformio/platformio.ini +0 -0
- {fastled-1.2.82 → fastled-1.2.84}/tests/unit/test_ino/embedded/data/bigdata.dat +0 -0
- {fastled-1.2.82 → fastled-1.2.84}/tests/unit/test_ino/embedded/wasm.ino +0 -0
- {fastled-1.2.82 → fastled-1.2.84}/tests/unit/test_ino/wasm/wasm.ino +0 -0
- {fastled-1.2.82 → fastled-1.2.84}/tests/unit/test_print_filter.py +0 -0
- {fastled-1.2.82 → fastled-1.2.84}/tests/unit/test_project_init.py +0 -0
- {fastled-1.2.82 → fastled-1.2.84}/tests/unit/test_server_and_client_seperatly.py +0 -0
- {fastled-1.2.82 → fastled-1.2.84}/tests/unit/test_string_diff.py +0 -0
- {fastled-1.2.82 → fastled-1.2.84}/tests/unit/test_version.py +0 -0
- {fastled-1.2.82 → fastled-1.2.84}/tests/unit/test_webcompile.py +0 -0
- {fastled-1.2.82 → fastled-1.2.84}/upload_package.sh +0 -0
@@ -431,6 +431,32 @@ def get_settings() -> dict:
|
|
431
431
|
return settings
|
432
432
|
|
433
433
|
|
434
|
+
# Return content and media type
|
435
|
+
def fetch_file(full_path: Path) -> tuple[bytes, str] | HTTPException:
|
436
|
+
"""Fetch the file from the server."""
|
437
|
+
print(f"Fetching file: {full_path}")
|
438
|
+
if not full_path.exists():
|
439
|
+
raise HTTPException(status_code=404, detail="File not found.")
|
440
|
+
if not full_path.is_file():
|
441
|
+
raise HTTPException(status_code=400, detail="Not a file.")
|
442
|
+
if not full_path.is_relative_to(FASTLED_SRC):
|
443
|
+
raise HTTPException(status_code=400, detail="Invalid file path.")
|
444
|
+
|
445
|
+
content = full_path.read_bytes()
|
446
|
+
# Determine media type based on file extension
|
447
|
+
media_type = "text/plain"
|
448
|
+
if full_path.suffix in [".h", ".cpp"]:
|
449
|
+
media_type = "text/plain"
|
450
|
+
elif full_path.suffix == ".html":
|
451
|
+
media_type = "text/html"
|
452
|
+
elif full_path.suffix == ".js":
|
453
|
+
media_type = "application/javascript"
|
454
|
+
elif full_path.suffix == ".css":
|
455
|
+
media_type = "text/css"
|
456
|
+
|
457
|
+
return content, media_type
|
458
|
+
|
459
|
+
|
434
460
|
def startup() -> None:
|
435
461
|
print("Starting FastLED wasm compiler server...")
|
436
462
|
try:
|
@@ -599,64 +625,45 @@ def project_init_example(
|
|
599
625
|
|
600
626
|
|
601
627
|
@app.get("/sourcefiles/{filepath:path}")
|
602
|
-
def source_file(filepath: str) ->
|
628
|
+
def source_file(filepath: str) -> Response:
|
603
629
|
"""Get the source file from the server."""
|
604
630
|
print(f"Endpoint accessed: /sourcefiles/{filepath}")
|
605
631
|
if ".." in filepath:
|
606
632
|
raise HTTPException(status_code=400, detail="Invalid file path.")
|
607
633
|
full_path = Path(FASTLED_SRC / filepath)
|
608
|
-
|
609
|
-
|
610
|
-
|
611
|
-
|
612
|
-
|
613
|
-
|
614
|
-
|
615
|
-
|
616
|
-
|
617
|
-
|
618
|
-
elif suffix == ".html":
|
619
|
-
media_type = "text/html"
|
620
|
-
elif suffix == ".js":
|
621
|
-
media_type = "application/javascript"
|
622
|
-
elif suffix == ".css":
|
623
|
-
media_type = "text/css"
|
624
|
-
else:
|
625
|
-
media_type = "application/octet-stream"
|
626
|
-
|
627
|
-
fr = FileResponse(
|
628
|
-
path=full_path,
|
629
|
-
media_type=media_type,
|
630
|
-
filename=filepath,
|
631
|
-
)
|
632
|
-
return fr
|
634
|
+
result: tuple[bytes, str] | HTTPException = fetch_file(full_path=full_path)
|
635
|
+
if isinstance(result, HTTPException):
|
636
|
+
assert isinstance(result, HTTPException)
|
637
|
+
return Response(
|
638
|
+
content=result.detail, # type: ignore
|
639
|
+
media_type="text/plain",
|
640
|
+
status_code=result.status_code, # type: ignore
|
641
|
+
)
|
642
|
+
content, media_type = result
|
643
|
+
return Response(content=content, media_type=media_type)
|
633
644
|
|
634
645
|
|
635
|
-
@app.get("/
|
646
|
+
@app.get("/drawfsource/{file_path:path}")
|
636
647
|
async def static_files(file_path: str) -> Response:
|
637
648
|
"""Serve static files."""
|
638
|
-
print(f"Endpoint accessed: /
|
649
|
+
print(f"Endpoint accessed: /drawfsource/{file_path}")
|
639
650
|
|
640
651
|
# Check if path matches the pattern js/fastled/src/...
|
641
652
|
if file_path.startswith("js/fastled/src/"):
|
642
653
|
# Extract the path after "js/fastled/src/"
|
643
654
|
relative_path = file_path[len("js/fastled/src/") :]
|
644
655
|
full_path = FASTLED_SRC / relative_path
|
645
|
-
|
646
|
-
|
647
|
-
|
648
|
-
|
649
|
-
|
650
|
-
|
651
|
-
media_type
|
652
|
-
|
653
|
-
|
654
|
-
|
655
|
-
|
656
|
-
elif full_path.suffix == ".css":
|
657
|
-
media_type = "text/css"
|
658
|
-
|
659
|
-
return Response(content=content, media_type=media_type)
|
656
|
+
result: tuple[bytes, str] | HTTPException = fetch_file(full_path=full_path)
|
657
|
+
|
658
|
+
# return Response(content=content, media_type=media_type)
|
659
|
+
if isinstance(result, HTTPException):
|
660
|
+
return Response(
|
661
|
+
content=result.detail, # type: ignore
|
662
|
+
media_type="text/plain",
|
663
|
+
status_code=result.status_code, # type: ignore
|
664
|
+
)
|
665
|
+
content, media_type = result
|
666
|
+
return Response(content=content, media_type=media_type)
|
660
667
|
|
661
668
|
# If file not found or path doesn't match expected format
|
662
669
|
return Response(
|
@@ -14,7 +14,7 @@ from .types import BuildMode, CompileResult, CompileServerError, FileResponse
|
|
14
14
|
# IMPORTANT! There's a bug in github which will REJECT any version update
|
15
15
|
# that has any other change in the repo. Please bump the version as the
|
16
16
|
# ONLY change in a commit, or else the pypi update and the release will fail.
|
17
|
-
__version__ = "1.2.
|
17
|
+
__version__ = "1.2.84"
|
18
18
|
|
19
19
|
|
20
20
|
class Api:
|
@@ -63,6 +63,12 @@ def TEST_BEFORE_COMPILE(url) -> None:
|
|
63
63
|
pass
|
64
64
|
|
65
65
|
|
66
|
+
def _chunked_print(stdout: str) -> None:
|
67
|
+
lines = stdout.splitlines()
|
68
|
+
for line in lines:
|
69
|
+
print(line)
|
70
|
+
|
71
|
+
|
66
72
|
def _run_web_compiler(
|
67
73
|
directory: Path,
|
68
74
|
host: str,
|
@@ -80,7 +86,7 @@ def _run_web_compiler(
|
|
80
86
|
if not web_result.success:
|
81
87
|
print("\nWeb compilation failed:")
|
82
88
|
print(f"Time taken: {diff:.2f} seconds")
|
83
|
-
|
89
|
+
_chunked_print(web_result.stdout)
|
84
90
|
# Create error page
|
85
91
|
output_dir.mkdir(exist_ok=True)
|
86
92
|
error_html = _create_error_html(web_result.stdout)
|
@@ -117,7 +123,7 @@ def _run_web_compiler(
|
|
117
123
|
# Extract zip contents
|
118
124
|
shutil.unpack_archive(temp_zip, output_dir, "zip")
|
119
125
|
|
120
|
-
|
126
|
+
_chunked_print(web_result.stdout)
|
121
127
|
print_results()
|
122
128
|
return web_result
|
123
129
|
|
@@ -0,0 +1,462 @@
|
|
1
|
+
import argparse
|
2
|
+
import logging
|
3
|
+
import time
|
4
|
+
from multiprocessing import Process
|
5
|
+
from pathlib import Path
|
6
|
+
|
7
|
+
import httpx
|
8
|
+
from livereload import Server
|
9
|
+
|
10
|
+
# Logging configuration
|
11
|
+
_ENABLE_LOGGING = False # Set to False to disable logging
|
12
|
+
|
13
|
+
if _ENABLE_LOGGING:
|
14
|
+
logging.basicConfig(
|
15
|
+
level=logging.INFO,
|
16
|
+
format="%(asctime)s - %(name)s - %(levelname)s - %(message)s",
|
17
|
+
)
|
18
|
+
logger = logging.getLogger("flask_server")
|
19
|
+
else:
|
20
|
+
# Disable all logging
|
21
|
+
logging.getLogger("flask_server").addHandler(logging.NullHandler())
|
22
|
+
logging.getLogger("flask_server").propagate = False
|
23
|
+
logger = logging.getLogger("flask_server")
|
24
|
+
logger.disabled = True
|
25
|
+
|
26
|
+
_DRAWF_SOURCE_PREFIX = "drawfsource/js/fastled/src/"
|
27
|
+
|
28
|
+
|
29
|
+
def _run_flask_server(
|
30
|
+
fastled_js: Path,
|
31
|
+
port: int,
|
32
|
+
compile_server_port: int,
|
33
|
+
certfile: Path | None = None,
|
34
|
+
keyfile: Path | None = None,
|
35
|
+
) -> None:
|
36
|
+
"""Run Flask server with live reload in a subprocess
|
37
|
+
|
38
|
+
Args:
|
39
|
+
fastled_js: Path to the fastled_js directory
|
40
|
+
port: Port to run the server on
|
41
|
+
certfile: Path to the SSL certificate file
|
42
|
+
keyfile: Path to the SSL key file
|
43
|
+
"""
|
44
|
+
try:
|
45
|
+
from flask import Flask, Response, request, send_from_directory
|
46
|
+
|
47
|
+
app = Flask(__name__)
|
48
|
+
|
49
|
+
# Must be a full path or flask will fail to find the file.
|
50
|
+
fastled_js = fastled_js.resolve()
|
51
|
+
|
52
|
+
# logger.error(f"Server error: {e}")
|
53
|
+
|
54
|
+
@app.before_request
|
55
|
+
def log_request_info():
|
56
|
+
"""Log details of each request before processing"""
|
57
|
+
if _ENABLE_LOGGING:
|
58
|
+
logger.info("=" * 80)
|
59
|
+
logger.info(f"Request: {request.method} {request.url}")
|
60
|
+
logger.info(f"Headers: {dict(request.headers)}")
|
61
|
+
logger.info(f"Args: {dict(request.args)}")
|
62
|
+
if request.is_json:
|
63
|
+
logger.info(f"JSON: {request.json}")
|
64
|
+
if request.form:
|
65
|
+
logger.info(f"Form: {dict(request.form)}")
|
66
|
+
logger.info(f"Remote addr: {request.remote_addr}")
|
67
|
+
logger.info(f"User agent: {request.user_agent}")
|
68
|
+
|
69
|
+
@app.route("/")
|
70
|
+
def serve_index():
|
71
|
+
if _ENABLE_LOGGING:
|
72
|
+
logger.info("Serving index.html")
|
73
|
+
response = send_from_directory(fastled_js, "index.html")
|
74
|
+
if _ENABLE_LOGGING:
|
75
|
+
logger.info(f"Index response status: {response.status_code}")
|
76
|
+
return response
|
77
|
+
|
78
|
+
@app.route("/sourcefiles/<path:path>")
|
79
|
+
def serve_source_files(path):
|
80
|
+
"""Proxy requests to /sourcefiles/* to the compile server"""
|
81
|
+
from flask import request
|
82
|
+
|
83
|
+
start_time = time.time()
|
84
|
+
logger.info(f"Serving source file: {path}")
|
85
|
+
|
86
|
+
# Forward the request to the compile server
|
87
|
+
target_url = f"http://localhost:{compile_server_port}/sourcefiles/{path}"
|
88
|
+
logger.info(f"Forwarding to: {target_url}")
|
89
|
+
|
90
|
+
# Log request headers
|
91
|
+
request_headers = {
|
92
|
+
key: value for key, value in request.headers if key != "Host"
|
93
|
+
}
|
94
|
+
logger.debug(f"Request headers: {request_headers}")
|
95
|
+
|
96
|
+
# Forward the request with the same method, headers, and body
|
97
|
+
try:
|
98
|
+
with httpx.Client() as client:
|
99
|
+
resp = client.request(
|
100
|
+
method=request.method,
|
101
|
+
url=target_url,
|
102
|
+
headers=request_headers,
|
103
|
+
content=request.get_data(),
|
104
|
+
cookies=request.cookies,
|
105
|
+
follow_redirects=True,
|
106
|
+
)
|
107
|
+
|
108
|
+
logger.info(f"Response status: {resp.status_code}")
|
109
|
+
logger.debug(f"Response headers: {dict(resp.headers)}")
|
110
|
+
|
111
|
+
# Create a Flask Response object from the httpx response
|
112
|
+
raw_data = resp.content
|
113
|
+
logger.debug(f"Response size: {len(raw_data)} bytes")
|
114
|
+
|
115
|
+
response = Response(
|
116
|
+
raw_data, status=resp.status_code, headers=dict(resp.headers)
|
117
|
+
)
|
118
|
+
|
119
|
+
elapsed_time = time.time() - start_time
|
120
|
+
logger.info(f"Request completed in {elapsed_time:.3f} seconds")
|
121
|
+
|
122
|
+
return response
|
123
|
+
|
124
|
+
except Exception as e:
|
125
|
+
logger.error(f"Error forwarding request: {e}", exc_info=True)
|
126
|
+
return Response(f"Error: {str(e)}", status=500)
|
127
|
+
|
128
|
+
def handle_dwarfsource(path: str) -> Response:
|
129
|
+
"""Handle requests to /drawfsource/js/fastled/src/"""
|
130
|
+
from flask import request
|
131
|
+
|
132
|
+
start_time = time.time()
|
133
|
+
logger.info(f"Processing request: {request.method} {request.url}")
|
134
|
+
|
135
|
+
if not path.startswith(_DRAWF_SOURCE_PREFIX):
|
136
|
+
# unexpected
|
137
|
+
error_msg = f"Unexpected path: {path}"
|
138
|
+
logger.error(error_msg)
|
139
|
+
# Logging disabled
|
140
|
+
return Response("Malformed path", status=400)
|
141
|
+
|
142
|
+
path = path.replace("drawfsource/js/fastled/src/", "")
|
143
|
+
logger.info(f"Transformed path: {path}")
|
144
|
+
|
145
|
+
# Forward the request to the compile server
|
146
|
+
target_url = f"http://localhost:{compile_server_port}/sourcefiles/{path}"
|
147
|
+
logger.info(f"Requesting: {target_url}")
|
148
|
+
logger.info(f"Processing dwarfsource request for {path}")
|
149
|
+
|
150
|
+
# Log request headers
|
151
|
+
request_headers = {
|
152
|
+
key: value for key, value in request.headers if key != "Host"
|
153
|
+
}
|
154
|
+
logger.debug(f"Request headers: {request_headers}")
|
155
|
+
|
156
|
+
try:
|
157
|
+
# Forward the request with the same method, headers, and body
|
158
|
+
with httpx.Client() as client:
|
159
|
+
resp = client.request(
|
160
|
+
method=request.method,
|
161
|
+
url=target_url,
|
162
|
+
headers=request_headers,
|
163
|
+
content=request.get_data(),
|
164
|
+
cookies=request.cookies,
|
165
|
+
follow_redirects=True,
|
166
|
+
)
|
167
|
+
|
168
|
+
logger.info(f"Response status: {resp.status_code}")
|
169
|
+
logger.debug(f"Response headers: {dict(resp.headers)}")
|
170
|
+
|
171
|
+
# Create a Flask Response object from the httpx response
|
172
|
+
payload = resp.content
|
173
|
+
assert isinstance(payload, bytes)
|
174
|
+
|
175
|
+
# Check if the payload is empty
|
176
|
+
if len(payload) == 0:
|
177
|
+
logger.error("Empty payload received from compile server")
|
178
|
+
return Response("Empty payload", status=400)
|
179
|
+
|
180
|
+
response = Response(
|
181
|
+
payload, status=resp.status_code, headers=dict(resp.headers)
|
182
|
+
)
|
183
|
+
|
184
|
+
elapsed_time = time.time() - start_time
|
185
|
+
logger.info(f"Request completed in {elapsed_time:.3f} seconds")
|
186
|
+
|
187
|
+
return response
|
188
|
+
|
189
|
+
except Exception as e:
|
190
|
+
logger.error(f"Error handling dwarfsource request: {e}", exc_info=True)
|
191
|
+
return Response(f"Error: {str(e)}", status=500)
|
192
|
+
|
193
|
+
def handle_sourcefile(path: str) -> Response:
|
194
|
+
"""Handle requests to /sourcefiles/*"""
|
195
|
+
from flask import Response, request
|
196
|
+
|
197
|
+
start_time = time.time()
|
198
|
+
logger.info("\n##################################")
|
199
|
+
logger.info(f"# Serving source file /sourcefiles/ {path}")
|
200
|
+
logger.info("##################################\n")
|
201
|
+
|
202
|
+
logger.info(f"Processing sourcefile request for {path}")
|
203
|
+
|
204
|
+
# Forward the request to the compile server
|
205
|
+
target_url = f"http://localhost:{compile_server_port}/sourcefiles/{path}"
|
206
|
+
logger.info(f"Forwarding to: {target_url}")
|
207
|
+
|
208
|
+
# Log request headers
|
209
|
+
request_headers = {
|
210
|
+
key: value for key, value in request.headers if key != "Host"
|
211
|
+
}
|
212
|
+
logger.debug(f"Request headers: {request_headers}")
|
213
|
+
|
214
|
+
try:
|
215
|
+
# Forward the request with the same method, headers, and body
|
216
|
+
with httpx.Client() as client:
|
217
|
+
resp = client.request(
|
218
|
+
method=request.method,
|
219
|
+
url=target_url,
|
220
|
+
headers=request_headers,
|
221
|
+
content=request.get_data(),
|
222
|
+
cookies=request.cookies,
|
223
|
+
follow_redirects=True,
|
224
|
+
)
|
225
|
+
|
226
|
+
logger.info(f"Response status: {resp.status_code}")
|
227
|
+
logger.debug(f"Response headers: {dict(resp.headers)}")
|
228
|
+
|
229
|
+
# Create a Flask Response object from the httpx response
|
230
|
+
raw_data = resp.content
|
231
|
+
logger.debug(f"Response size: {len(raw_data)} bytes")
|
232
|
+
|
233
|
+
response = Response(
|
234
|
+
raw_data, status=resp.status_code, headers=dict(resp.headers)
|
235
|
+
)
|
236
|
+
|
237
|
+
elapsed_time = time.time() - start_time
|
238
|
+
logger.info(f"Request completed in {elapsed_time:.3f} seconds")
|
239
|
+
|
240
|
+
return response
|
241
|
+
|
242
|
+
except Exception as e:
|
243
|
+
logger.error(f"Error handling sourcefile request: {e}", exc_info=True)
|
244
|
+
return Response(f"Error: {str(e)}", status=500)
|
245
|
+
|
246
|
+
def handle_local_file_fetch(path: str) -> Response:
|
247
|
+
start_time = time.time()
|
248
|
+
logger.info("\n##################################")
|
249
|
+
logger.info(f"# Serving generic file {path}")
|
250
|
+
logger.info("##################################\n")
|
251
|
+
|
252
|
+
logger.info(f"Processing local file request for {path}")
|
253
|
+
|
254
|
+
try:
|
255
|
+
file_path = fastled_js / path
|
256
|
+
logger.info(f"Full file path: {file_path}")
|
257
|
+
logger.info(f"File exists: {file_path.exists()}")
|
258
|
+
|
259
|
+
# Check if file exists before trying to serve it
|
260
|
+
if not file_path.exists():
|
261
|
+
logger.warning(f"File not found: {file_path}")
|
262
|
+
return Response(f"File not found: {path}", status=404)
|
263
|
+
|
264
|
+
response = send_from_directory(fastled_js, path)
|
265
|
+
|
266
|
+
# Some servers don't set the Content-Type header for a bunch of files.
|
267
|
+
content_type = None
|
268
|
+
if path.endswith(".js"):
|
269
|
+
content_type = "application/javascript"
|
270
|
+
elif path.endswith(".css"):
|
271
|
+
content_type = "text/css"
|
272
|
+
elif path.endswith(".wasm"):
|
273
|
+
content_type = "application/wasm"
|
274
|
+
elif path.endswith(".json"):
|
275
|
+
content_type = "application/json"
|
276
|
+
elif path.endswith(".png"):
|
277
|
+
content_type = "image/png"
|
278
|
+
elif path.endswith(".jpg") or path.endswith(".jpeg"):
|
279
|
+
content_type = "image/jpeg"
|
280
|
+
elif path.endswith(".gif"):
|
281
|
+
content_type = "image/gif"
|
282
|
+
elif path.endswith(".svg"):
|
283
|
+
content_type = "image/svg+xml"
|
284
|
+
elif path.endswith(".ico"):
|
285
|
+
content_type = "image/x-icon"
|
286
|
+
elif path.endswith(".html"):
|
287
|
+
content_type = "text/html"
|
288
|
+
|
289
|
+
if content_type:
|
290
|
+
logger.info(f"Setting Content-Type to {content_type}")
|
291
|
+
response.headers["Content-Type"] = content_type
|
292
|
+
|
293
|
+
# now also add headers to force no caching
|
294
|
+
response.headers["Cache-Control"] = (
|
295
|
+
"no-cache, no-store, must-revalidate"
|
296
|
+
)
|
297
|
+
response.headers["Pragma"] = "no-cache"
|
298
|
+
response.headers["Expires"] = "0"
|
299
|
+
|
300
|
+
logger.info(f"Response status: {response.status_code}")
|
301
|
+
logger.debug(f"Response headers: {dict(response.headers)}")
|
302
|
+
|
303
|
+
elapsed_time = time.time() - start_time
|
304
|
+
logger.info(f"Request completed in {elapsed_time:.3f} seconds")
|
305
|
+
|
306
|
+
return response
|
307
|
+
|
308
|
+
except Exception as e:
|
309
|
+
logger.error(f"Error serving local file {path}: {e}", exc_info=True)
|
310
|
+
# Check if this is a FileNotFoundError (which can happen with send_from_directory)
|
311
|
+
if isinstance(e, FileNotFoundError):
|
312
|
+
return Response(f"File not found: {path}", status=404)
|
313
|
+
return Response(f"Error serving file: {str(e)}", status=500)
|
314
|
+
|
315
|
+
@app.route("/<path:path>")
|
316
|
+
def serve_files(path: str):
|
317
|
+
logger.info(f"Received request for path: {path}")
|
318
|
+
|
319
|
+
try:
|
320
|
+
if path.startswith("drawfsource/"):
|
321
|
+
logger.info(f"Handling as drawfsource: {path}")
|
322
|
+
return handle_dwarfsource(path)
|
323
|
+
elif path.startswith("sourcefiles/"):
|
324
|
+
logger.info(f"Handling as sourcefiles: {path}")
|
325
|
+
return handle_sourcefile(path)
|
326
|
+
else:
|
327
|
+
logger.info(f"Handling as local file: {path}")
|
328
|
+
return handle_local_file_fetch(path)
|
329
|
+
except Exception as e:
|
330
|
+
logger.error(f"Error in serve_files for {path}: {e}", exc_info=True)
|
331
|
+
return Response(f"Server error: {str(e)}", status=500)
|
332
|
+
|
333
|
+
@app.errorhandler(Exception)
|
334
|
+
def handle_exception(e):
|
335
|
+
"""Log any uncaught exceptions"""
|
336
|
+
logger.error(f"Unhandled exception: {e}", exc_info=True)
|
337
|
+
return Response(f"Server error: {str(e)}", status=500)
|
338
|
+
|
339
|
+
logger.info("Setting up livereload server")
|
340
|
+
server = Server(app.wsgi_app)
|
341
|
+
# Watch index.html for changes
|
342
|
+
server.watch(str(fastled_js / "index.html"))
|
343
|
+
# server.watch(str(fastled_js / "index.js"))
|
344
|
+
# server.watch(str(fastled_js / "index.css"))
|
345
|
+
# Start the server
|
346
|
+
logger.info(f"Starting server on port {port}")
|
347
|
+
server.serve(port=port, debug=True)
|
348
|
+
except KeyboardInterrupt:
|
349
|
+
logger.info("Server stopped by keyboard interrupt")
|
350
|
+
import _thread
|
351
|
+
|
352
|
+
_thread.interrupt_main()
|
353
|
+
except Exception as e:
|
354
|
+
logger.error(f"Failed to run Flask server: {e}", exc_info=True)
|
355
|
+
logger.info("Flask server thread running")
|
356
|
+
import _thread
|
357
|
+
|
358
|
+
_thread.interrupt_main()
|
359
|
+
|
360
|
+
|
361
|
+
def run_flask_in_thread(
|
362
|
+
port: int,
|
363
|
+
cwd: Path,
|
364
|
+
compile_server_port: int,
|
365
|
+
certfile: Path | None = None,
|
366
|
+
keyfile: Path | None = None,
|
367
|
+
) -> None:
|
368
|
+
"""Run the Flask server."""
|
369
|
+
try:
|
370
|
+
if _ENABLE_LOGGING:
|
371
|
+
logger.info(f"Starting Flask server thread on port {port}")
|
372
|
+
logger.info(f"Serving files from {cwd}")
|
373
|
+
logger.info(f"Compile server port: {compile_server_port}")
|
374
|
+
if certfile:
|
375
|
+
logger.info(f"Using SSL certificate: {certfile}")
|
376
|
+
if keyfile:
|
377
|
+
logger.info(f"Using SSL key: {keyfile}")
|
378
|
+
|
379
|
+
_run_flask_server(cwd, port, compile_server_port, certfile, keyfile)
|
380
|
+
except KeyboardInterrupt:
|
381
|
+
logger.info("Flask server thread stopped by keyboard interrupt")
|
382
|
+
import _thread
|
383
|
+
|
384
|
+
_thread.interrupt_main()
|
385
|
+
pass
|
386
|
+
except Exception as e:
|
387
|
+
logger.error(f"Error in Flask server thread: {e}", exc_info=True)
|
388
|
+
|
389
|
+
|
390
|
+
def parse_args() -> argparse.Namespace:
|
391
|
+
"""Parse the command line arguments."""
|
392
|
+
parser = argparse.ArgumentParser(
|
393
|
+
description="Open a browser to the fastled_js directory"
|
394
|
+
)
|
395
|
+
parser.add_argument(
|
396
|
+
"fastled_js", type=Path, help="Path to the fastled_js directory"
|
397
|
+
)
|
398
|
+
parser.add_argument(
|
399
|
+
"--port",
|
400
|
+
"-p",
|
401
|
+
type=int,
|
402
|
+
required=True,
|
403
|
+
help="Port to run the server on (default: %(default)s)",
|
404
|
+
)
|
405
|
+
parser.add_argument(
|
406
|
+
"--certfile",
|
407
|
+
type=Path,
|
408
|
+
help="Path to the SSL certificate file for HTTPS",
|
409
|
+
)
|
410
|
+
parser.add_argument(
|
411
|
+
"--keyfile",
|
412
|
+
type=Path,
|
413
|
+
help="Path to the SSL key file for HTTPS",
|
414
|
+
)
|
415
|
+
return parser.parse_args()
|
416
|
+
|
417
|
+
|
418
|
+
def run_flask_server_process(
|
419
|
+
port: int,
|
420
|
+
cwd: Path,
|
421
|
+
compile_server_port: int,
|
422
|
+
certfile: Path | None = None,
|
423
|
+
keyfile: Path | None = None,
|
424
|
+
) -> Process:
|
425
|
+
"""Run the Flask server in a separate process."""
|
426
|
+
if _ENABLE_LOGGING:
|
427
|
+
logger.info(f"Starting Flask server process on port {port}")
|
428
|
+
logger.info(f"Serving files from {cwd}")
|
429
|
+
logger.info(f"Compile server port: {compile_server_port}")
|
430
|
+
|
431
|
+
process = Process(
|
432
|
+
target=run_flask_in_thread,
|
433
|
+
args=(port, cwd, compile_server_port, certfile, keyfile),
|
434
|
+
)
|
435
|
+
process.start()
|
436
|
+
if _ENABLE_LOGGING:
|
437
|
+
logger.info(f"Flask server process started with PID {process.pid}")
|
438
|
+
return process
|
439
|
+
|
440
|
+
|
441
|
+
def main() -> None:
|
442
|
+
"""Main function."""
|
443
|
+
if _ENABLE_LOGGING:
|
444
|
+
logger.info("Starting main function")
|
445
|
+
args = parse_args()
|
446
|
+
if _ENABLE_LOGGING:
|
447
|
+
logger.info(f"Arguments: port={args.port}, fastled_js={args.fastled_js}")
|
448
|
+
if args.certfile:
|
449
|
+
logger.info(f"Using SSL certificate: {args.certfile}")
|
450
|
+
if args.keyfile:
|
451
|
+
logger.info(f"Using SSL key: {args.keyfile}")
|
452
|
+
logger.warning("Note: main() is missing compile_server_port parameter")
|
453
|
+
|
454
|
+
# Note: This call is missing the compile_server_port parameter
|
455
|
+
# This is a bug in the original code
|
456
|
+
run_flask_in_thread(args.port, args.fastled_js, 0, args.certfile, args.keyfile)
|
457
|
+
if _ENABLE_LOGGING:
|
458
|
+
logger.info("Main function completed")
|
459
|
+
|
460
|
+
|
461
|
+
if __name__ == "__main__":
|
462
|
+
main()
|
@@ -9,6 +9,7 @@ README.md
|
|
9
9
|
RELEASE.md
|
10
10
|
TODO.md
|
11
11
|
build_exe.py
|
12
|
+
build_local_docker.py
|
12
13
|
build_site.py
|
13
14
|
clean
|
14
15
|
docker-compose.yml
|
@@ -111,7 +112,7 @@ tests/unit/test_cli.py
|
|
111
112
|
tests/unit/test_compile_server.py
|
112
113
|
tests/unit/test_docker_linux_on_windows.py
|
113
114
|
tests/unit/test_embedded_data.py
|
114
|
-
tests/unit/
|
115
|
+
tests/unit/test_fetch_source_files.py
|
115
116
|
tests/unit/test_filechanger.py
|
116
117
|
tests/unit/test_http_server.py
|
117
118
|
tests/unit/test_print_filter.py
|