UNPKG

hashfolder

Version:

Simple command line tool that can create/update an sqlite database that contains the sha256 hash of all files inside a specified root folder and then find duplicates.

7 lines (6 loc) 19.6 kB
"use strict";const _e=require("yargs"),we=require("better-sqlite3"),ue=require("fs"),B=require("fs/promises"),J=require("path"),Pe=require("stream/promises"),ce=require("crypto"),Oe=require("stream"),Ie=require("os");function Fe(t){return t&&t.__esModule&&Object.prototype.hasOwnProperty.call(t,"default")?t.default:t}var fe={exports:{}};(function(t){var e=Object.prototype.hasOwnProperty,s="~";function i(){}Object.create&&(i.prototype=Object.create(null),new i().__proto__||(s=!1));function n(a,o,h){this.fn=a,this.context=o,this.once=h||!1}function l(a,o,h,d,E){if(typeof h!="function")throw new TypeError("The listener must be a function");var m=new n(h,d||a,E),p=s?s+o:o;return a._events[p]?a._events[p].fn?a._events[p]=[a._events[p],m]:a._events[p].push(m):(a._events[p]=m,a._eventsCount++),a}function c(a,o){--a._eventsCount===0?a._events=new i:delete a._events[o]}function f(){this._events=new i,this._eventsCount=0}f.prototype.eventNames=function(){var o=[],h,d;if(this._eventsCount===0)return o;for(d in h=this._events)e.call(h,d)&&o.push(s?d.slice(1):d);return Object.getOwnPropertySymbols?o.concat(Object.getOwnPropertySymbols(h)):o},f.prototype.listeners=function(o){var h=s?s+o:o,d=this._events[h];if(!d)return[];if(d.fn)return[d.fn];for(var E=0,m=d.length,p=new Array(m);E<m;E++)p[E]=d[E].fn;return p},f.prototype.listenerCount=function(o){var h=s?s+o:o,d=this._events[h];return d?d.fn?1:d.length:0},f.prototype.emit=function(o,h,d,E,m,p){var b=s?s+o:o;if(!this._events[b])return!1;var u=this._events[b],w=arguments.length,O,y;if(u.fn){switch(u.once&&this.removeListener(o,u.fn,void 0,!0),w){case 1:return u.fn.call(u.context),!0;case 2:return u.fn.call(u.context,h),!0;case 3:return u.fn.call(u.context,h,d),!0;case 4:return u.fn.call(u.context,h,d,E),!0;case 5:return u.fn.call(u.context,h,d,E,m),!0;case 6:return u.fn.call(u.context,h,d,E,m,p),!0}for(y=1,O=new Array(w-1);y<w;y++)O[y-1]=arguments[y];u.fn.apply(u.context,O)}else{var Te=u.length,C;for(y=0;y<Te;y++)switch(u[y].once&&this.removeListener(o,u[y].fn,void 0,!0),w){case 1:u[y].fn.call(u[y].context);break;case 2:u[y].fn.call(u[y].context,h);break;case 3:u[y].fn.call(u[y].context,h,d);break;case 4:u[y].fn.call(u[y].context,h,d,E);break;default:if(!O)for(C=1,O=new Array(w-1);C<w;C++)O[C-1]=arguments[C];u[y].fn.apply(u[y].context,O)}}return!0},f.prototype.on=function(o,h,d){return l(this,o,h,d,!1)},f.prototype.once=function(o,h,d){return l(this,o,h,d,!0)},f.prototype.removeListener=function(o,h,d,E){var m=s?s+o:o;if(!this._events[m])return this;if(!h)return c(this,m),this;var p=this._events[m];if(p.fn)p.fn===h&&(!E||p.once)&&(!d||p.context===d)&&c(this,m);else{for(var b=0,u=[],w=p.length;b<w;b++)(p[b].fn!==h||E&&!p[b].once||d&&p[b].context!==d)&&u.push(p[b]);u.length?this._events[m]=u.length===1?u[0]:u:c(this,m)}return this},f.prototype.removeAllListeners=function(o){var h;return o?(h=s?s+o:o,this._events[h]&&c(this,h)):(this._events=new i,this._eventsCount=0),this},f.prototype.off=f.prototype.removeListener,f.prototype.addListener=f.prototype.on,f.prefixed=s,f.EventEmitter=f,t.exports=f})(fe);var Ne=fe.exports;const Le=Fe(Ne);class de extends Error{constructor(e){super(e),this.name="TimeoutError"}}let Ae=class extends Error{constructor(e){super(),this.name="AbortError",this.message=e}};const te=t=>globalThis.DOMException===void 0?new Ae(t):new DOMException(t),se=t=>{const e=t.reason===void 0?te("This operation was aborted."):t.reason;return e instanceof Error?e:te(e)};function Ce(t,e,s,i){let n;const l=new Promise((c,f)=>{if(typeof e!="number"||Math.sign(e)!==1)throw new TypeError(`Expected \`milliseconds\` to be a positive number, got \`${e}\``);if(e===Number.POSITIVE_INFINITY){c(t);return}if(i={customTimers:{setTimeout,clearTimeout},...i},i.signal){const{signal:a}=i;a.aborted&&f(se(a)),a.addEventListener("abort",()=>{f(se(a))})}n=i.customTimers.setTimeout.call(void 0,()=>{if(typeof s=="function"){try{c(s())}catch(h){f(h)}return}const a=typeof s=="string"?s:`Promise timed out after ${e} milliseconds`,o=s instanceof Error?s:new de(a);typeof t.cancel=="function"&&t.cancel(),f(o)},e),(async()=>{try{c(await t)}catch(a){f(a)}finally{i.customTimers.clearTimeout.call(void 0,n)}})()});return l.clear=()=>{clearTimeout(n),n=void 0},l}function Se(t,e,s){let i=0,n=t.length;for(;n>0;){const l=Math.trunc(n/2);let c=i+l;s(t[c],e)<=0?(i=++c,n-=l+1):n=l}return i}var A=globalThis&&globalThis.__classPrivateFieldGet||function(t,e,s,i){if(s==="a"&&!i)throw new TypeError("Private accessor was defined without a getter");if(typeof e=="function"?t!==e||!i:!e.has(t))throw new TypeError("Cannot read private member from an object whose class did not declare it");return s==="m"?i:s==="a"?i.call(t):i?i.value:e.get(t)},I;class He{constructor(){I.set(this,[])}enqueue(e,s){s={priority:0,...s};const i={priority:s.priority,run:e};if(this.size&&A(this,I,"f")[this.size-1].priority>=s.priority){A(this,I,"f").push(i);return}const n=Se(A(this,I,"f"),i,(l,c)=>c.priority-l.priority);A(this,I,"f").splice(n,0,i)}dequeue(){const e=A(this,I,"f").shift();return e?.run}filter(e){return A(this,I,"f").filter(s=>s.priority===e.priority).map(s=>s.run)}get size(){return A(this,I,"f").length}}I=new WeakMap;var g=globalThis&&globalThis.__classPrivateFieldSet||function(t,e,s,i,n){if(i==="m")throw new TypeError("Private method is not writable");if(i==="a"&&!n)throw new TypeError("Private accessor was defined without a setter");if(typeof e=="function"?t!==e||!n:!e.has(t))throw new TypeError("Cannot write private member to an object whose class did not declare it");return i==="a"?n.call(t,s):n?n.value=s:e.set(t,s),s},r=globalThis&&globalThis.__classPrivateFieldGet||function(t,e,s,i){if(s==="a"&&!i)throw new TypeError("Private accessor was defined without a getter");if(typeof e=="function"?t!==e||!i:!e.has(t))throw new TypeError("Cannot read private member from an object whose class did not declare it");return s==="m"?i:s==="a"?i.call(t):i?i.value:e.get(t)},v,H,R,N,j,M,x,P,S,T,D,_,Q,F,$,ie,re,me,ne,ae,q,G,V,Y,pe,U;class ge extends Error{}class Re extends Le{constructor(e){var s,i,n,l;if(super(),v.add(this),H.set(this,void 0),R.set(this,void 0),N.set(this,0),j.set(this,void 0),M.set(this,void 0),x.set(this,0),P.set(this,void 0),S.set(this,void 0),T.set(this,void 0),D.set(this,void 0),_.set(this,0),Q.set(this,void 0),F.set(this,void 0),$.set(this,void 0),Object.defineProperty(this,"timeout",{enumerable:!0,configurable:!0,writable:!0,value:void 0}),e={carryoverConcurrencyCount:!1,intervalCap:Number.POSITIVE_INFINITY,interval:0,concurrency:Number.POSITIVE_INFINITY,autoStart:!0,queueClass:He,...e},!(typeof e.intervalCap=="number"&&e.intervalCap>=1))throw new TypeError(`Expected \`intervalCap\` to be a number from 1 and up, got \`${(i=(s=e.intervalCap)===null||s===void 0?void 0:s.toString())!==null&&i!==void 0?i:""}\` (${typeof e.intervalCap})`);if(e.interval===void 0||!(Number.isFinite(e.interval)&&e.interval>=0))throw new TypeError(`Expected \`interval\` to be a finite number >= 0, got \`${(l=(n=e.interval)===null||n===void 0?void 0:n.toString())!==null&&l!==void 0?l:""}\` (${typeof e.interval})`);g(this,H,e.carryoverConcurrencyCount,"f"),g(this,R,e.intervalCap===Number.POSITIVE_INFINITY||e.interval===0,"f"),g(this,j,e.intervalCap,"f"),g(this,M,e.interval,"f"),g(this,T,new e.queueClass,"f"),g(this,D,e.queueClass,"f"),this.concurrency=e.concurrency,this.timeout=e.timeout,g(this,$,e.throwOnTimeout===!0,"f"),g(this,F,e.autoStart===!1,"f")}get concurrency(){return r(this,Q,"f")}set concurrency(e){if(!(typeof e=="number"&&e>=1))throw new TypeError(`Expected \`concurrency\` to be a number from 1 and up, got \`${e}\` (${typeof e})`);g(this,Q,e,"f"),r(this,v,"m",Y).call(this)}async add(e,s={}){return s={timeout:this.timeout,throwOnTimeout:r(this,$,"f"),...s},new Promise((i,n)=>{r(this,T,"f").enqueue(async()=>{var l,c,f;g(this,_,(c=r(this,_,"f"),c++,c),"f"),g(this,N,(f=r(this,N,"f"),f++,f),"f");try{if(!((l=s.signal)===null||l===void 0)&&l.aborted)throw new ge("The task was aborted.");let a=e({signal:s.signal});s.timeout&&(a=Ce(Promise.resolve(a),s.timeout)),s.signal&&(a=Promise.race([a,r(this,v,"m",pe).call(this,s.signal)]));const o=await a;i(o),this.emit("completed",o)}catch(a){if(a instanceof de&&!s.throwOnTimeout){i();return}n(a),this.emit("error",a)}finally{r(this,v,"m",me).call(this)}},s),this.emit("add"),r(this,v,"m",q).call(this)})}async addAll(e,s){return Promise.all(e.map(async i=>this.add(i,s)))}start(){return r(this,F,"f")?(g(this,F,!1,"f"),r(this,v,"m",Y).call(this),this):this}pause(){g(this,F,!0,"f")}clear(){g(this,T,new(r(this,D,"f")),"f")}async onEmpty(){r(this,T,"f").size!==0&&await r(this,v,"m",U).call(this,"empty")}async onSizeLessThan(e){r(this,T,"f").size<e||await r(this,v,"m",U).call(this,"next",()=>r(this,T,"f").size<e)}async onIdle(){r(this,_,"f")===0&&r(this,T,"f").size===0||await r(this,v,"m",U).call(this,"idle")}get size(){return r(this,T,"f").size}sizeBy(e){return r(this,T,"f").filter(e).length}get pending(){return r(this,_,"f")}get isPaused(){return r(this,F,"f")}}H=new WeakMap,R=new WeakMap,N=new WeakMap,j=new WeakMap,M=new WeakMap,x=new WeakMap,P=new WeakMap,S=new WeakMap,T=new WeakMap,D=new WeakMap,_=new WeakMap,Q=new WeakMap,F=new WeakMap,$=new WeakMap,v=new WeakSet,ie=function(){return r(this,R,"f")||r(this,N,"f")<r(this,j,"f")},re=function(){return r(this,_,"f")<r(this,Q,"f")},me=function(){var e;g(this,_,(e=r(this,_,"f"),e--,e),"f"),r(this,v,"m",q).call(this),this.emit("next")},ne=function(){r(this,v,"m",V).call(this),r(this,v,"m",G).call(this),g(this,S,void 0,"f")},ae=function(){const e=Date.now();if(r(this,P,"f")===void 0){const s=r(this,x,"f")-e;if(s<0)g(this,N,r(this,H,"f")?r(this,_,"f"):0,"f");else return r(this,S,"f")===void 0&&g(this,S,setTimeout(()=>{r(this,v,"m",ne).call(this)},s),"f"),!0}return!1},q=function(){if(r(this,T,"f").size===0)return r(this,P,"f")&&clearInterval(r(this,P,"f")),g(this,P,void 0,"f"),this.emit("empty"),r(this,_,"f")===0&&this.emit("idle"),!1;if(!r(this,F,"f")){const e=!r(this,v,"a",ae);if(r(this,v,"a",ie)&&r(this,v,"a",re)){const s=r(this,T,"f").dequeue();return s?(this.emit("active"),s(),e&&r(this,v,"m",G).call(this),!0):!1}}return!1},G=function(){r(this,R,"f")||r(this,P,"f")!==void 0||(g(this,P,setInterval(()=>{r(this,v,"m",V).call(this)},r(this,M,"f")),"f"),g(this,x,Date.now()+r(this,M,"f"),"f"))},V=function(){r(this,N,"f")===0&&r(this,_,"f")===0&&r(this,P,"f")&&(clearInterval(r(this,P,"f")),g(this,P,void 0,"f")),g(this,N,r(this,H,"f")?r(this,_,"f"):0,"f"),r(this,v,"m",Y).call(this)},Y=function(){for(;r(this,v,"m",q).call(this););},pe=async function(e){return new Promise((s,i)=>{e.addEventListener("abort",()=>{i(new ge("The task was aborted."))},{once:!0})})},U=async function(e,s){return new Promise(i=>{const n=()=>{s&&!s()||(this.off(e,n),i())};this.on(e,n)})};const Z="hashfolder-v0",Me=` CREATE TABLE files (path TEXT PRIMARY KEY NOT NULL, type TEXT NOT NULL, mode INT NOT NULL, size INT NOT NULL, cTime INT NOT NULL, mTime INT NOT NULL, lastCheckTime INT NOT NULL); CREATE TABLE fileHashes (path TEXT NOT NULL, algorithm TEXT NOT NULL, hash BLOB NOT NULL, PRIMARY KEY (path, algorithm), FOREIGN KEY (path) REFERENCES files (path) ON DELETE CASCADE); CREATE TABLE version (hashfolder_version TEXT PRIMARY KEY); INSERT INTO version (hashfolder_version) VALUES ('${Z}'); `;var L=(t=>(t.FILE="file",t.FOLDER="folder",t.LINK="link",t))(L||{});const oe=(t,e)=>{const s=t.prepare(e);return s.get.bind(s)},z=(t,e)=>{const s=t.prepare(e);return s.all.bind(s)},k=(t,e)=>{const s=t.prepare(e);return s.run.bind(s)},W=(t,{fileMustExist:e=!1,readonly:s=!1}={})=>{e||(e=s||ue.existsSync(t));const i=new we(t,{fileMustExist:e,readonly:s});if(i.pragma("journal_mode = WAL"),e){let l;try{l=i.prepare("SELECT hashfolder_version FROM version").get()}catch{throw new Error(`File '${t}' does not contain a hashfolder database.`)}if(l?.hashfolder_version!=Z)throw new Error(`File '${t}' contains an incompatible version of a hashfolder database, expected ${Z}, found ${l?.hashfolder_version}`)}else i.exec(Me);const n=z(i,"SELECT * FROM fileHashes WHERE path=?");return{close:()=>{i.close()},getAvailableHashAlgorithms:()=>n(".").map(l=>l.algorithm),getFile:oe(i,"SELECT * FROM files WHERE path=?"),getFileHash:oe(i,"SELECT * FROM fileHashes WHERE path=? AND algorithm=?"),getFileHashes:n,upsertFile:k(i,"INSERT INTO files (path,type,mode,size,cTime,mTime,lastCheckTime) VALUES (@path,@type,@mode,@size,@cTime,@mTime,@lastCheckTime) ON CONFLICT (path) DO UPDATE SET type=excluded.type,mode=excluded.mode,size=excluded.size,cTime=excluded.cTime,mTime=excluded.mTime,lastCheckTime=excluded.lastCheckTime"),upsertFileHash:k(i,"INSERT INTO fileHashes (path,algorithm,hash) VALUES (@path,@algorithm,@hash) ON CONFLICT (path,algorithm) DO UPDATE SET hash=excluded.hash"),deleteFileHashes:k(i,"DELETE FROM fileHashes WHERE path=?"),removeOldEntries:k(i,"DELETE FROM files WHERE lastCheckTime<>?"),listDuplicates:z(i,"SELECT type, hash, size, COUNT(*) as count FROM files INNER JOIN fileHashes ON files.path = fileHashes.path WHERE fileHashes.algorithm=? GROUP BY hash, size, type HAVING count > 1 ORDER BY type, count, size, hash"),listByChecksum:z(i,"SELECT * FROM files INNER JOIN fileHashes ON files.path = fileHashes.path WHERE fileHashes.hash=?"),listByChecksumOfAlgorithm:z(i,"SELECT * FROM files INNER JOIN fileHashes ON files.path = fileHashes.path WHERE fileHashes.hash=? AND fileHashes.algorithm=?")}},ee=t=>t.startsWith("GIT-"),le=ce.getHashes().map(t=>t.toUpperCase()).filter(t=>!t.startsWith("RSA-")&&!t.startsWith("ID-RSASSA-")&&!t.startsWith("SSL3-")&&!t.endsWith("WITHRSAENCRYPTION")&&!t.endsWith("WITHRSA")),K=[...le,...le.map(t=>`GIT-${t}`)],Qe=(t,e)=>{const s=ee(t),i=s?t.substring(4):t,n=ce.createHash(i);return s&&e(n,t),n},ye=(t,e)=>{const s=new Map;for(const i of t)s.set(i,Qe(i,e));return s},ve=t=>{const e={};for(const[s,i]of t.entries())e[s]=i.digest();return e};class ze extends Oe.Writable{#e=new Map;constructor(e,s){super({objectMode:!1,write:(i,n,l)=>{i instanceof Buffer||(i=Buffer.from(i,n)),this.update(i),l()}}),this.#e=ye(e,i=>{i.update(s)})}update(e){for(const[s,i]of this.#e.entries())i.update(e)}digest(){return ve(this.#e)}}const ke=(t,e)=>t<e?-1:t>e?1:0,he=t=>t.isDirectory()?`${t.name}/`:t.name,We=(t,e)=>ke(he(t),he(e)),xe=t=>{const e={};for(const{algorithm:s,hash:i}of t)e[s]=i;return e},De=(t,e)=>t.every(s=>Object.hasOwn(e,s)),$e=t=>t.isDirectory()?Ee:t.isSymbolicLink()?Ue:t.isFile()?Be:null,qe=t=>{switch(t.type){case L.FOLDER:return"40000";case L.LINK:return"120000";case L.FILE:return t.mode&64?"100755":"100644"}},Ee=async(t,e)=>{const s=J.join(t.rootPath,e),i=await B.stat(s);let n=0,l=i.ctimeMs,c=i.mtimeMs;const f=await t.pqueue.add(async()=>await B.readdir(s,{withFileTypes:!0}));f.sort(We);const a=new Map;t.hashes.filter(ee).forEach(m=>a.set(m,0));const o=await Promise.all(f.map(async m=>{const p=$e(m);if(!p)return null;const b=m.name,u=await p(t,J.posix.join(e,b));n+=u.file.size,l=Math.max(l,u.file.cTime),c=Math.max(l,u.file.mTime);const w=Buffer.from(`${qe(u.file)} ${b}\0`,"utf8");for(const[O,y]of a.entries())a.set(O,y+w.length+u.fileHashes[O].length);return{gitEntry:w,stdEntry:Buffer.from(`${u.file.type} ${b}\0`,"utf8"),hashes:u.fileHashes}})),h=ye(t.hashes,(m,p)=>{m.update(Buffer.from(`tree ${a.get(p)}\0`,"utf8"))});for(const m of o)if(m)for(const[p,b]of h.entries())b.update(ee(p)?m.gitEntry:m.stdEntry),b.update(m.hashes[p]);const d=ve(h),E={path:e,type:L.FOLDER,mode:i.mode,size:n,cTime:l,mTime:c,lastCheckTime:t.dbLastCheckTime};t.db.deleteFileHashes(e),t.db.upsertFile(E);for(const m of Object.keys(d))t.db.upsertFileHash({path:e,algorithm:m,hash:d[m]});return{file:E,fileHashes:d}},be=(t,e)=>async(s,i)=>{const n=s.db.getFile(i),l=J.join(s.rootPath,i),c=await B.lstat(l),f=c.size;let a=n?.type===t&&n.size===c.size&&n.mode===c.mode&&n.mTime===c.mtimeMs&&n.cTime===c.ctimeMs,o;a&&(o=xe(s.db.getFileHashes(i)),a=De(s.hashes,o)),a||(o=await s.pqueue.add(async()=>{const d=new ze(s.hashes,Buffer.from(`blob ${f}\0`,"utf8"));return await e(l,d),d.digest()}));const h={path:i,type:t,size:f,mode:c.mode,cTime:c.ctimeMs,mTime:c.mtimeMs,lastCheckTime:s.dbLastCheckTime};a||s.db.deleteFileHashes(i),s.db.upsertFile(h);for(const d of Object.keys(o))s.db.upsertFileHash({path:i,algorithm:d,hash:o[d]});return{file:h,fileHashes:o}},Ue=be(L.LINK,async(t,e)=>{const s=await B.readlink(t,{encoding:"buffer"});e.update(s)}),Be=be(L.FILE,async(t,e)=>{const s=ue.createReadStream(t);await Pe.pipeline(s,e)}),X="SHA256";_e.scriptName("hashfolder").usage("$0 <cmd> [args]").command({command:"show-hash dbFile [path]",describe:"show the hash of the given file in the database",builder:t=>t.positional("dbFile",{type:"string",describe:"sqlite hashfolder database file"}).positional("path",{type:"string",describe:"path of the file or folder",default:"."}),handler:async t=>{const{dbFile:e,path:s,hash:i}=t,n=W(e,{readonly:!0}),l=n.getFileHashes(s);n.close();for(const c of l)console.log(`${c.algorithm}: ${c.hash.toString("hex")}`)}}).command({command:"find-hash dbFile hash",describe:"list all files with the given hash",builder:t=>t.positional("dbFile",{type:"string",describe:"sqlite hashfolder database file"}).positional("hash",{type:"string",describe:"hash to look for in the database"}).option("algorithm",{type:"string",choices:K,describe:"hash algorithm"}),handler:async t=>{const{dbFile:e,hash:s,algorithm:i}=t,n=Buffer.from(s,"hex"),l=W(e,{readonly:!0}),c=i?l.listByChecksumOfAlgorithm(n,i):l.listByChecksum(n);if(c.length>0){console.log("Type Size Path");for(const f of c)console.log(`${f.type} ${f.size} ${f.path}`)}else console.log("The given hash was not found in the database.");l.close()}}).command({command:"find-duplicates dbFile",describe:"list duplicate files (having the same hash and length)",builder:t=>t.positional("dbFile",{type:"string",describe:"sqlite hashfolder database file"}).option("algorithm",{type:"string",choices:K,describe:"hash algorithm to use"}),handler:async t=>{const{dbFile:e,algorithm:s}=t,i=W(e,{readonly:!0}),n=i.getAvailableHashAlgorithms();if(n.length===0)throw new Error("The database contains no hash.");if(s&&!n.includes(s))throw new Error("The requested hash algorithm was not included in the database.");const l=s??(n.includes(X)?X:n[0]),c=i.listDuplicates(l);if(c.length>0){let f=0,a=0;console.log(`Type Copies Size ${l}`);for(let o of c){if(o.type===L.FILE){const h=o.count-1;a+=h,f+=h*o.size}console.log(`${o.type} ${o.count} ${o.size} ${o.hash.toString("hex")}`)}console.log(`${a} duplicate file(s), ${f} bytes`)}else console.log("There is no duplicate file.");i.close()}}).command({command:"update dbFile folder",describe:"update (or create) dbFile to contain the list of files from folder, and shows the hash of the full folder content",builder:t=>t.option("concurrency",{type:"number",describe:"maximum number of files/folders to open in parallel",default:Ie.cpus().length}).option("algorithm",{array:!0,type:"string",choices:K,describe:"hash algorithms to use"}).positional("dbFile",{type:"string",describe:"sqlite hashfolder database file to update or create"}).positional("folder",{type:"string",describe:"folder to index"}),handler:async t=>{const{dbFile:e,folder:s,algorithm:i,concurrency:n}=t,l=W(e);let c=i??l.getAvailableHashAlgorithms();c.length===0&&(c=[X]);const f=Date.now(),a=await Ee({hashes:c,rootPath:s,dbLastCheckTime:f,db:l,pqueue:new Re({concurrency:n})},".");l.removeOldEntries(f),l.close();for(const o of Object.keys(a.fileHashes))console.log(`${o}: ${a.fileHashes[o].toString("hex")}`)}}).completion().demandCommand().strict().help().argv;