aboutsummaryrefslogtreecommitdiffstats
path: root/packages/meshbay-node/src/meshbay_node/transport/webrtc/disk.py
diff options
context:
space:
mode:
authorChristophe Besson <cbesson@gmail.com>2026-10-14 11:17:38 +0200
committerChristophe Besson <cbesson@gmail.com>2026-10-14 11:17:38 +0200
commitdaec4bc6a2a9f9e038e5269586b06018ff327a27 (patch)
treec34c58aad624d594dbb1aa8a47f71ab38a5e3081 /packages/meshbay-node/src/meshbay_node/transport/webrtc/disk.py
parent80ac882346b474bb7ea4e8c8afd4694bd13dd9dd (diff)
downloadmeshbay-daec4bc6a2a9f9e038e5269586b06018ff327a27.tar.gz
fix(node): index, serve and write files past Windows' MAX_PATHHEADmain
With the system's default setting (LongPathsEnabled off) every call given a plain path of 260 characters or more refuses it, so a deep library was partly invisible: the walk could not list such a directory and its files never reached the members. The node now hands each call that touches a group's disk the extended form (paths.long_path), and keeps the plain path in the index, the hash cache and the log. A directory is listed under its long form and its entries named from the plain one (indexer.walk_files, wire._walk_dirs). Covered: the walk, stats and hashing, reconcile and the watcher, the folder list members see, serving over WebRTC and QUIC, deletion, uploads and their recovery, folder creation and removal, audio tags and the cover beside a track, photos, and a show's episode numbering. ffmpeg extends a long path itself and keeps the plain one. long_path now normalises before prefixing (the extended form skips the system's normalisation) and starts at 248 characters, where creating a directory is already refused. test_long_paths.py runs with the setting off: 15 of its 17 tests fail without the change. Checked by hand in the installed app with files at 312 to 380 characters. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Diffstat (limited to 'packages/meshbay-node/src/meshbay_node/transport/webrtc/disk.py')
-rw-r--r--packages/meshbay-node/src/meshbay_node/transport/webrtc/disk.py45
1 files changed, 34 insertions, 11 deletions
diff --git a/packages/meshbay-node/src/meshbay_node/transport/webrtc/disk.py b/packages/meshbay-node/src/meshbay_node/transport/webrtc/disk.py
index e298942..386b81f 100644
--- a/packages/meshbay-node/src/meshbay_node/transport/webrtc/disk.py
+++ b/packages/meshbay-node/src/meshbay_node/transport/webrtc/disk.py
@@ -1,8 +1,15 @@
-"""Blocking disk work the session hands to `off_disk`."""
+"""
+Blocking disk work the session hands to `off_disk`.
+Every call takes the path's long form (`long_path`): a file past Windows'
+MAX_PATH is indexed, so it is served, deleted and written beside like any other.
+"""
+
+import os
import shutil
from pathlib import Path
+from meshbay_common.paths import long_path
from meshbay_common.protocol import file_chunk_wire
from meshbay_node.roots import ROOT_NOT_SERVED, RootSet, entry_abs_path
@@ -22,7 +29,7 @@ def _locate(roots: RootSet, entry) -> tuple[Path | None, str | None]:
path = entry_abs_path(roots, entry)
if path is None:
return None, ROOT_NOT_SERVED
- if not path.exists():
+ if not os.path.exists(long_path(path)):
return None, "File not on disk"
return path, None
@@ -37,15 +44,16 @@ def _mkdir_if_absent(target: Path) -> str | None:
`mkdir` would raise where a refusal was meant. Blocking; called through
`off_disk`.
"""
- if target.exists():
+ if os.path.exists(long_path(target)):
return "Already exists"
- target.mkdir(parents=False)
+ os.mkdir(long_path(target))
return None
def _is_empty_dir(target: Path) -> bool:
"""Blocking; called through `off_disk`."""
- return not any(target.iterdir())
+ with os.scandir(long_path(target)) as listing:
+ return not any(listing)
def _rmdir_if_empty(target: Path) -> bool:
@@ -57,9 +65,10 @@ def _rmdir_if_empty(target: Path) -> bool:
operator's browser, and a file can land in between. Two awaits here would
reopen the same window one size smaller. Blocking; called through `off_disk`.
"""
- if any(target.iterdir()):
- return False
- target.rmdir()
+ with os.scandir(long_path(target)) as listing:
+ if any(listing):
+ return False
+ os.rmdir(long_path(target))
return True
@@ -71,7 +80,7 @@ def _read_and_encrypt(
file_id: str = "",
) -> dict:
"""Read one chunk off disk and encrypt it. Blocking; called through `off_disk`."""
- with open(file_path, "rb") as f:
+ with open(long_path(file_path), "rb") as f:
f.seek(chunk_index * CHUNK_SIZE)
plaintext = f.read(CHUNK_SIZE)
return file_chunk_wire(gek, plaintext, chunk_index, file_hash, file_id)
@@ -80,10 +89,24 @@ def _read_and_encrypt(
def _free_bytes(directory: Path) -> int:
"""Space left on the filesystem holding `directory`. Blocking; called
through `off_disk`, since a `statvfs` wakes a sleeping disk like any stat."""
- return shutil.disk_usage(directory).free
+ return shutil.disk_usage(long_path(directory)).free
def _append_chunk(tmp_path: Path, chunk_bytes: bytes, first: bool) -> None:
"""Add one chunk to a partial upload. Blocking; called through `off_disk`."""
- with open(tmp_path, "wb" if first else "ab") as f:
+ with open(long_path(tmp_path), "wb" if first else "ab") as f:
f.write(chunk_bytes)
+
+
+def _unlink(path: Path, missing_ok: bool = False) -> None:
+ """Remove one file. Blocking; called through `off_disk`."""
+ try:
+ os.unlink(long_path(path))
+ except FileNotFoundError:
+ if not missing_ok:
+ raise
+
+
+def _exists(path: Path) -> bool:
+ """Blocking; called through `off_disk`."""
+ return os.path.exists(long_path(path))