From bb9442a07cebe70c3492cca31f31a8270bb83134 Mon Sep 17 00:00:00 2001 From: "Danilo M." Date: Fri, 16 Oct 2026 16:52:45 +0200 Subject: fix(assistant): review of the Do row and fileops The canvas: a To .md ended any other way than its own Stop (the footer Stop, a question, a drop, a daemon restart, an older daemon refusing the command) left the card at Working forever. The footer Stop goes through stopOp(), asking and dropping end the operation first, a disconnect ends it, and an error while nothing else runs is taken as its outcome. Each fileops run gets its own Process, so a stopped run's late answer is never read as the next one's. To .md takes one PDF, as one file is written. Compress... no longer flips back when file(1) refines a kind, long errors are elided, and a desktop with no documents folder gets Documents/Assistant rather than ~/Assistant. fileops: an animated GIF to PNG or JPEG left one file per frame under names the rename never found; the first frame is taken. JPEG flattens transparency onto white. Zipping a folder holding the output folder read its own growing archive without end; only regular files outside it are added, which also skips FIFOs and dangling links. qpdf warnings no longer fail a merge. A folder's free name keeps its dots, a lone .gz or .xz is decompressed rather than refused, and a dotfile's archive is not hidden. Tools die with fileops even when it is killed outright, a killed run's leftovers are swept on the next run, a stop between fork and bookkeeping cannot miss the child, and a late stop cannot add a second answer. Error lines lose bsdtar's doubled prefix and its delayed-exit noise. Co-Authored-By: Claude Opus 5.5 --- desktop/assistant/test_fileops.py | 41 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 41 insertions(+) (limited to 'desktop/assistant/test_fileops.py') diff --git a/desktop/assistant/test_fileops.py b/desktop/assistant/test_fileops.py index 850d1d7..8fd8449 100755 --- a/desktop/assistant/test_fileops.py +++ b/desktop/assistant/test_fileops.py @@ -122,6 +122,47 @@ with tempfile.TemporaryDirectory() as src, tempfile.TemporaryDirectory() as out: r, _ = fileops("audio", out, g) assert not r["ok"] and "audio" in r["error"], r # a GIF has no audio track + # From review: each of these failed or misbehaved once. + anim = os.path.join(src, "anim.gif") + sh("magick", "-size", "8x8", "xc:red", "xc:blue", "-loop", "0", anim) + [ap] = one("png", anim) # first frame, no stray frame files + assert mime(ap) == "image/png" and not [n for n in os.listdir(out) if n.startswith(".")] + clear = os.path.join(src, "clear.png") + sh("magick", "-size", "8x8", "xc:none", clear) + [cj] = one("jpeg", clear) # transparency flattened onto white + assert subprocess.run(["magick", cj, "-format", "%[fx:int(255*p{0,0}.r)]", "info:"], + capture_output=True, text=True).stdout == "255" + dotted = os.path.join(src, "v1.2 notes.pdf") + sh("magick", "-size", "50x50", "xc:white", dotted) + names = [os.path.basename(one("pdf-images", dotted)[0]) for _ in range(2)] + assert names == ["v1.2 notes (pages)", "v1.2 notes (pages) (1)"], names + gz = os.path.join(src, "note.txt.gz") + sh("sh", "-c", f"gzip -c {os.path.join(src, 'note.txt')} > '{gz}'") + [gx] = one("extract", gz) # a single compressed file + assert os.path.basename(gx) == "note.txt" and open(gx).read() == "hello\n" + pgz = os.path.join(src, "pic.png.gz") + sh("sh", "-c", f"gzip -c '{png}' > '{pgz}'") + [px] = one("extract", pgz) # binary content survives + assert open(px, "rb").read() == open(png, "rb").read() + with open(os.path.join(src, ".bashrc"), "w") as fh: + fh.write("x\n") + [dz] = one("zip", os.path.join(src, ".bashrc")) + assert os.path.basename(dz) == "bashrc.zip" + with tempfile.TemporaryDirectory() as tree: # the output folder inside the input + inner = os.path.join(tree, "Assistant") + with open(os.path.join(tree, "a.txt"), "w") as fh: + fh.write("a\n") + os.mkfifo(os.path.join(tree, "pipe")) + os.symlink("/nonexistent", os.path.join(tree, "dangling")) + r, rc = fileops("zip", inner, tree) + assert r["ok"], r + import zipfile + assert sorted(zipfile.ZipFile(r["out"][0]).namelist()) == [os.path.basename(tree) + "/a.txt"] + stale = os.path.join(out, ".fileops-999999-0.png") + open(stale, "w").close() + one("png", png) # a dead run's leftovers are swept + assert not os.path.exists(stale) + # SIGTERM mid-encode (the canvas's Stop): no ffmpeg left, no partial file. long_clip = os.path.join(src, "long.mp4") sh("ffmpeg", "-v", "error", "-f", "lavfi", "-i", "testsrc=duration=20:size=1280x720:rate=30", -- cgit v1.2.3