Organize uploader storage and restore transfer readiness

This commit is contained in:
2026-10-14 19:15:46 +09:00
parent 69e7449ba7
commit c3573b605c
24 changed files with 966 additions and 449 deletions

View File

@@ -151,12 +151,12 @@ try {
`import {createHash} from 'node:crypto';const state=${JSON.stringify(fileState.pending)},base='http://127.0.0.1:${port}',path='/api/uploads/resumable/'+state.uploadId;const headers={Origin:'https://hop.test',Cookie:state.cookie,'X-CSRF-Token':state.csrf};const r=await fetch(base+path,{headers});const progress=await r.json();if(!r.ok||progress.offset!==8*1024*1024)throw Error('partial offset not preserved');const last=await fetch(base+path,{method:'PUT',headers:{...headers,'Content-Type':'application/octet-stream','Upload-Offset':String(progress.offset)},body:Buffer.alloc(7,120)});if(!last.ok)throw Error(await last.text());const complete=await fetch(base+path+'/complete',{method:'POST',headers:{...headers,'Content-Type':'application/json'},body:'{}'});if(!complete.ok)throw Error(await complete.text());const list=await fetch(base+'/api/device/items',{headers:{Cookie:${JSON.stringify(fileState.cookie)}}});const item=(await list.json()).items.find(i=>i.file?.name==='resume.bin');const downloaded=await fetch(base+'/api/access/'+item.accessId+'/download',{headers:{Cookie:${JSON.stringify(fileState.cookie)}}});const content=Buffer.from(await downloaded.arrayBuffer());if(!downloaded.ok||content.length!==8*1024*1024+7||!content.equals(Buffer.alloc(content.length,120)))throw Error('resumed content mismatch');console.log('resumed');`,
);
assert.equal(resumed, 'resumed');
// Completed physical paths and the original download URL survive another restart.
// Per-uploader physical paths and the original download URL survive another restart.
docker('restart', name);
wait();
assert.equal(
node(
`const r=await fetch('http://127.0.0.1:${port}/api/access/${fileState.accessId}/download',{headers:{Cookie:${JSON.stringify(fileState.cookie)}}});if(!r.ok)throw Error('completed download failed after restart');console.log(await r.text());`,
`const r=await fetch('http://127.0.0.1:${port}/api/access/${fileState.accessId}/download',{headers:{Cookie:${JSON.stringify(fileState.cookie)}}});if(!r.ok)throw Error('owner folder download failed after restart');console.log(await r.text());`,
),
'Docker file persistence',
);
@@ -172,14 +172,22 @@ try {
),
);
assert.deepEqual(mode, { uid: 12001, gid: 12001, mode: 0o700 });
const completed = resolve(folder, 'completed');
const stored = readdirSync(completed).find(
(key) => readFileSync(resolve(completed, key)).toString() === 'Docker file persistence',
);
assert(stored, 'downloaded anonymous file is visible in the host completed directory');
assert.match(stored, /^[a-f0-9-]{36}\.txt$/);
assert.equal(existsSync(resolve(folder, 'originals', stored)), false);
unlinkSync(resolve(completed, stored));
const originals = resolve(folder, 'originals');
const stored = readdirSync(originals)
.filter((key) => key.startsWith('guest-'))
.flatMap((owner) =>
readdirSync(resolve(originals, owner)).map((key) => ({
owner,
key,
path: resolve(originals, owner, key),
})),
)
.find(({ path }) => readFileSync(path).toString() === 'Docker file persistence');
assert(stored, 'uploaded file is visible under its guest owner in the host project');
assert.match(stored.owner, /^guest-[a-f0-9-]{36}$/);
assert.match(stored.key, /^[a-f0-9-]{36}\.txt$/);
assert.equal(existsSync(resolve(folder, 'completed')), false);
unlinkSync(stored.path);
const missing = JSON.parse(
node(
`const r=await fetch('http://127.0.0.1:${port}/api/access/${fileState.accessId}/download',{headers:{Cookie:${JSON.stringify(fileState.cookie)}}});console.log(JSON.stringify({status:r.status,body:await r.json()}));`,