先核來源與已訂規則
本例用 ASCII 路徑名稱,並明訂目標是否分大小寫。不分時 A.txt、a.txt 是同一目標鍵;兩原項及位元組仍須分得開。撞鍵便停整包,不選最後項頂上。
| 紀錄 | 輸入 JSON |
|---|---|
| 原例輸入 | {"caseSensitive":false,"entries":[{"name":"A.txt","bytes":[65]},{"name":"a.txt","bytes":[66]}]} |
比較兩條實際路徑
錯目標集合只剩 a.txt 的 66;更正保 A.txt 的 65、a.txt 的 66,列碰撞,沒有預定寫入項。
| 路徑 | 輸出 JSON |
|---|---|
| 錯誤路徑 | {"accepted":true,"files":[{"destination":"/import/a.txt","source":"a.txt","bytes":[66]}]} |
| 更正路徑 | {"accepted":false,"sources":[{"name":"A.txt","bytes":[65]},{"name":"a.txt","bytes":[66]}],"collisions":[{"destination":"/import/a.txt","sources":["A.txt","a.txt"]}],"files":[]} |
import {DatabaseSync} from 'node:sqlite';
import {TextDecoder} from 'node:util';
import {inflateSync} from 'node:zlib';
import {posix} from 'node:path';
import {createHash} from 'node:crypto';
const rows=(db,sql)=>db.prepare(sql).all().map(r=>({...r}));
const failed=(action)=>{try{action();return null;}catch(e){return {code:e.errcode??e.code??e.name};}};
import assert from 'node:assert/strict';
const run=function run(i,corrected=true){
const groups=new Map();const sourceEntries=structuredClone(i.entries);
for(const e of i.entries){const path=posix.resolve('/import',e.name);const destination=i.caseSensitive?path:path.toLowerCase();
if(!groups.has(destination))groups.set(destination,[]);groups.get(destination).push(e);}
if(!corrected)return {accepted:true,files:[...groups].map(([destination,entries])=>{const e=entries.at(-1);return {destination,source:e.name,bytes:e.bytes};})};
const collisions=[...groups].filter(([,es])=>es.length>1).map(([destination,es])=>({destination,sources:es.map(e=>e.name)}));
return {accepted:collisions.length===0,sources:sourceEntries,collisions,files:collisions.length?[]:[...groups].map(([destination,es])=>({destination,source:es[0].name,bytes:es[0].bytes}))};
};
const cases=[{"label":"misleading base","input":{"caseSensitive":false,"entries":[{"name":"A.txt","bytes":[65]},{"name":"a.txt","bytes":[66]}]},"expected":{"accepted":true,"files":[{"destination":"/import/a.txt","source":"a.txt","bytes":[66]}]},"corrected":false},{"label":"corrected base","input":{"caseSensitive":false,"entries":[{"name":"A.txt","bytes":[65]},{"name":"a.txt","bytes":[66]}]},"expected":{"accepted":false,"sources":[{"name":"A.txt","bytes":[65]},{"name":"a.txt","bytes":[66]}],"collisions":[{"destination":"/import/a.txt","sources":["A.txt","a.txt"]}],"files":[]},"corrected":true},{"label":"declared case-sensitive destination","input":{"caseSensitive":true,"entries":[{"name":"A.txt","bytes":[65]},{"name":"a.txt","bytes":[66]}]},"expected":{"accepted":true,"sources":[{"name":"A.txt","bytes":[65]},{"name":"a.txt","bytes":[66]}],"collisions":[],"files":[{"destination":"/import/A.txt","source":"A.txt","bytes":[65]},{"destination":"/import/a.txt","source":"a.txt","bytes":[66]}]},"corrected":true},{"label":"single destination entry","input":{"caseSensitive":false,"entries":[{"name":"A.txt","bytes":[65]}]},"expected":{"accepted":true,"sources":[{"name":"A.txt","bytes":[65]}],"collisions":[],"files":[{"destination":"/import/a.txt","source":"A.txt","bytes":[65]}]},"corrected":true}];
for(const test of cases){
const actual=run(test.input,test.corrected);
assert.deepEqual(actual,test.expected,test.label);
console.log(test.label,actual);
}
下次載入前核邊界
明訂分大小寫便有兩個目標;不分模式只得一項時也沒有碰撞。
| 情況 | 輸入 JSON | 預期輸出 JSON |
|---|---|---|
| 明訂分大小寫目標 | {"caseSensitive":true,"entries":[{"name":"A.txt","bytes":[65]},{"name":"a.txt","bytes":[66]}]} | {"accepted":true,"sources":[{"name":"A.txt","bytes":[65]},{"name":"a.txt","bytes":[66]}],"collisions":[],"files":[{"destination":"/import/A.txt","source":"A.txt","bytes":[65]},{"destination":"/import/a.txt","source":"a.txt","bytes":[66]}]} |
| 只有一個來源項目 | {"caseSensitive":false,"entries":[{"name":"A.txt","bytes":[65]}]} | {"accepted":true,"sources":[{"name":"A.txt","bytes":[65]}],"collisions":[],"files":[{"destination":"/import/a.txt","source":"A.txt","bytes":[65]}]} |
此約定可否靜靜以最後項勝出?
查看參考答案
不可,兩來源都留,共同目標暫停,另作明確處理。