fix: pre-validate all cache() inputs before any upload so a bad late input orphans nothing
cache() built discord.File objects per batch inside the send loop, so an unsupported type or a missing file path in the 11th+ item raised only after earlier batches were already uploaded - orphaning those stored messages with no returned ref. A new _prevalidate pass checks every source up front (supported type; a path source exists and is a file) WITHOUT opening any File or reading bytes, so a locally-detectable bad input fails loud before the first send. The File is still built per batch in the loop (fresh-File-per-send unchanged), so no extra open FDs are held. Neg control: old code uploaded batch 1 then raised on the bad path (sends=1, orphaned); new raises with sends=0. Signed-off-by: disqualifier <dev@disqualifier.me>
This commit is contained in:
@@ -122,10 +122,13 @@ class DPYCache:
|
|||||||
) -> CacheResult:
|
) -> CacheResult:
|
||||||
"""upload files to the storage channel (batching over 10/message) and return refs
|
"""upload files to the storage channel (batching over 10/message) and return refs
|
||||||
keyed by original filename; raises ValueError on unnamed bytes in a list input,
|
keyed by original filename; raises ValueError on unnamed bytes in a list input,
|
||||||
DPYCacheError on a duplicate filename in a list input (checked before any upload, so
|
DPYCacheError on a duplicate filename in a list input, an unsupported content type,
|
||||||
a collision costs zero requests) or an attachment-count mismatch after a send, and
|
or a missing file path (all checked before any upload, so a locally-detectable bad
|
||||||
propagates discord's own Forbidden/HTTPException unwrapped on a send failure"""
|
input in a >10-file batch costs zero requests and orphans nothing), or an
|
||||||
|
attachment-count mismatch after a send, and propagates discord's own
|
||||||
|
Forbidden/HTTPException unwrapped on a send failure"""
|
||||||
items = self._normalize(files)
|
items = self._normalize(files)
|
||||||
|
self._prevalidate(items)
|
||||||
merged: dict[str, FileRef] = {}
|
merged: dict[str, FileRef] = {}
|
||||||
jump_urls: list[str] = []
|
jump_urls: list[str] = []
|
||||||
|
|
||||||
@@ -248,6 +251,27 @@ class DPYCache:
|
|||||||
items.append((name, source))
|
items.append((name, source))
|
||||||
return items
|
return items
|
||||||
|
|
||||||
|
def _prevalidate(self, items: "list[tuple[str, FileContent]]") -> None:
|
||||||
|
"""check every source is a supported, locally-resolvable input BEFORE any upload
|
||||||
|
|
||||||
|
catches an unsupported content type or a missing/directory file path up front so a
|
||||||
|
bad input in a >10-file batch fails loud before the first send rather than after
|
||||||
|
earlier batches already landed (which would orphan those stored messages). does NOT
|
||||||
|
open Files or read bytes - only a cheap type check and, for a path source, an
|
||||||
|
existence/is-file probe; the actual File is still built per batch in the send loop.
|
||||||
|
"""
|
||||||
|
for name, source in items:
|
||||||
|
if isinstance(source, (discord.File, bytes, bytearray)):
|
||||||
|
continue
|
||||||
|
if isinstance(source, (str, os.PathLike)):
|
||||||
|
path = os.fspath(source)
|
||||||
|
if not os.path.isfile(path):
|
||||||
|
raise DPYCacheError(
|
||||||
|
f"file source for {name!r} is not an existing file: {path!r}"
|
||||||
|
)
|
||||||
|
continue
|
||||||
|
raise ValueError(f"unsupported file content type for {name!r}: {type(source).__name__}")
|
||||||
|
|
||||||
def _to_file(self, name: str, source: FileContent) -> discord.File:
|
def _to_file(self, name: str, source: FileContent) -> discord.File:
|
||||||
"""build a genuinely fresh discord.File for ``source`` named ``name``, distinct from
|
"""build a genuinely fresh discord.File for ``source`` named ``name``, distinct from
|
||||||
any File previously handed to a send; a fresh File is built per send so the source
|
any File previously handed to a send; a fresh File is built per send so the source
|
||||||
|
|||||||
Reference in New Issue
Block a user