Add stage-3 content triage: sort unknown files into binary/ and text/
Unknown files are no longer terminal. Stage 1 now routes :unknown onto a dedicated queue (with the same blocking backpressure as the known queue), and a third worker pool sorts each file into data/binary/ or data/text/ using a NUL-byte sniff of the first 8000 bytes. - content.jl: is_binary content sniff (stage 3) - worker.jl: handle_unknown_job; stage-1 routes unknown with backpressure; KNOWN_ENQUEUE_RETRY_SECONDS -> ROUTE_ENQUEUE_RETRY_SECONDS (serves both) - config.jl: unknown_worker_count/queue_capacity, binary_dir, text_dir + env - FileServer.jl: unknown queue, pool, stage-aware recovery, drain ordering - tests for is_binary and handle_unknown_job; tmp_config isolates new dirs - README: three-stage pipeline
This commit is contained in:
@@ -7,7 +7,8 @@ using JSON3
|
||||
# directly rather than only through the HTTP surface.
|
||||
using FileServer: Job, Config, ChannelQueue, enqueue!, dequeue!, length,
|
||||
sanitize_filename, recover_dir!, normalize_metadata,
|
||||
build_metadata, finalize_known!, run_exiftool
|
||||
build_metadata, finalize_known!, run_exiftool,
|
||||
is_binary, handle_unknown_job
|
||||
|
||||
# A minimal, valid 1×1 PNG. Lets the real-exiftool tests assert stable facts
|
||||
# (FileType == "PNG", 1×1 dimensions) that don't drift across exiftool versions.
|
||||
@@ -21,6 +22,8 @@ function tmp_config(root; kwargs...)
|
||||
spool_dir = joinpath(root, "spool"),
|
||||
known_dir = joinpath(root, "known"),
|
||||
unknown_dir = joinpath(root, "unknown"),
|
||||
binary_dir = joinpath(root, "binary"),
|
||||
text_dir = joinpath(root, "text"),
|
||||
done_dir = joinpath(root, "done"),
|
||||
failed_dir = joinpath(root, "failed"),
|
||||
kwargs...,
|
||||
@@ -138,6 +141,52 @@ end
|
||||
end
|
||||
end
|
||||
|
||||
@testset "is_binary: NUL-byte sniff" begin
|
||||
mktempdir() do root
|
||||
# Plain text → text.
|
||||
txt = joinpath(root, "notes.txt")
|
||||
write(txt, "hello, world\nsecond line\n")
|
||||
@test is_binary(txt) == false
|
||||
|
||||
# A NUL byte anywhere in the sniff window → binary.
|
||||
bin = joinpath(root, "blob.dat")
|
||||
write(bin, UInt8[0x01, 0x02, 0x00, 0x03])
|
||||
@test is_binary(bin) == true
|
||||
|
||||
# Empty file has no NUL → treated as text.
|
||||
empty = joinpath(root, "empty")
|
||||
write(empty, UInt8[])
|
||||
@test is_binary(empty) == false
|
||||
|
||||
# A NUL past the sniff window is not seen → still text.
|
||||
far = joinpath(root, "far.txt")
|
||||
write(far, vcat(fill(UInt8('a'), FileServer.CONTENT_SNIFF_BYTES), UInt8[0x00]))
|
||||
@test is_binary(far) == false
|
||||
end
|
||||
end
|
||||
|
||||
@testset "handle_unknown_job: routes to binary/ and text/" begin
|
||||
mktempdir() do root
|
||||
cfg = tmp_config(root)
|
||||
|
||||
# A binary file (embedded NUL) lands in binary/.
|
||||
bpath = joinpath(cfg.unknown_dir, "id-b-blob.dat")
|
||||
write(bpath, UInt8[0x00, 0xFF, 0x10])
|
||||
bjob = Job("id-b", "blob.dat", bpath, filesize(bpath), 0.0)
|
||||
handle_unknown_job(bjob, cfg, 1)
|
||||
@test isfile(joinpath(cfg.binary_dir, "id-b-blob.dat"))
|
||||
@test !isfile(bpath)
|
||||
|
||||
# A text file lands in text/.
|
||||
tpath = joinpath(cfg.unknown_dir, "id-t-notes.log")
|
||||
write(tpath, "just some log text\n")
|
||||
tjob = Job("id-t", "notes.log", tpath, filesize(tpath), 0.0)
|
||||
handle_unknown_job(tjob, cfg, 1)
|
||||
@test isfile(joinpath(cfg.text_dir, "id-t-notes.log"))
|
||||
@test !isfile(tpath)
|
||||
end
|
||||
end
|
||||
|
||||
@testset "recover_dir!: re-enqueues work, skips sidecars" begin
|
||||
mktempdir() do root
|
||||
dir = joinpath(root, "known"); mkpath(dir)
|
||||
|
||||
Reference in New Issue
Block a user