Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
49 changes: 31 additions & 18 deletions src/hooks/file-explorer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ export const useFileExplorer = () => {
const { mode, setLoading } = useSearch();
const [results, setResults] = useState<SearchResult[]>([]);
const [error, setError] = useState<Error | null>(null);

const useRegex = true;
// Refs to store version and DB instance across renders
const versionKeyRef = useRef<string | null>(null);
const dbInstanceRef = useRef<FilelistDB | null>(null);
Expand Down Expand Up @@ -45,7 +45,7 @@ export const useFileExplorer = () => {
const metadata = await response.json();
return metadata.version;
}
} catch {}
} catch { }
return patch;
},
[],
Expand Down Expand Up @@ -106,39 +106,52 @@ export const useFileExplorer = () => {
const db = dbInstanceRef.current;

// Get file list from cache or network
let files = await db.getFileList();
if (!files) {
let file = await db.getFileList_NoCache();
if (!file) {
const filelistUrl = `${RAW_HOST}/${patch}/cdragon/files.exported.txt`;
const response = await fetch(filelistUrl, {
signal: abortController.signal,
});
if (!response.ok) throw new Error("Failed to fetch file list");
const text = await response.text();
files = text.split("\n").filter((line) => line.trim() !== "");
await db.saveFileList(files);
//console.log("test_117"+text.split("\n"));
//const text=text2
//file=await text.split("\n").filter((line) => line.trim() !== "");
//console.log("raw filelist"+text);
db.saveFileList(text);
}

// Build regex with local prefix if in local mode

const prefix = localPrefix();
const escapedQuery = escapeRegex(trimmedQuery);

const content = useRegex ? trimmedQuery : escapeRegex(trimmedQuery);
let regex: RegExp;
try {
regex = new RegExp(`^${prefix}.*(?:${escapedQuery}).*$`, "mi");
} catch (e) {
regex = new RegExp(`^${prefix}.*?(?:${content}).*$`, "gim");
//const fast_regexp =new RegExp(`$\\n${prefix}[^\\n]*?${content}[^\\n]*\\n`,"gim")
//regex=fast_regexp;
}
catch (e) {
console.error("Invalid regex", e);
setResults([]);
return;
}

// Perform search (worker‑based)
console.log(regex);
const matches = await db.searchFileList(regex);
const currentPatch = getPatch();
setResults(
matches.map((filename) => ({
filename,
href: `/${currentPatch}/${filename}`,
})),
);
if (matches) {
setResults(
matches.map((filename) => ({
filename,
href: `/${currentPatch}/${filename}`,
})),
);
}
else {
setResults(
[]
);
}
} catch (err) {
if (err instanceof Error && err.name === "AbortError") return;
setError(err instanceof Error ? err : new Error(String(err)));
Expand Down
98 changes: 76 additions & 22 deletions src/lib/db.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,15 @@
// lib/filelist-db.ts

const DB_NAME = "filelists";
const STORE_NAME = "filelists";
const DB_NAME = "file";
const STORE_NAME = "file";
const DB_VERSION = 1;

export class FilelistDB {
private static dbConnection: Promise<IDBDatabase> | null = null;

//private static filelist=null;
private static file;
private version: string;

private worker: Worker | null = null;
private workerPromiseMap = new Map<
string,
Expand All @@ -18,28 +20,53 @@ export class FilelistDB {
>();

constructor(version: string) {

this.version = version;
//console.log(version);
//FilelistDB.filelist=null;
//this.getFileList();
}

private static async getConnection(): Promise<IDBDatabase> {
if (FilelistDB.dbConnection) return FilelistDB.dbConnection;

FilelistDB.dbConnection = new Promise((resolve, reject) => {
FilelistDB.dbConnection = new Promise<IDBDatabase>((resolve, reject) => {
const request = indexedDB.open(DB_NAME, DB_VERSION);
request.onerror = () => reject(request.error);
request.onsuccess = () => resolve(request.result);
request.onupgradeneeded = (event) => {

const db = (event.target as IDBOpenDBRequest).result;
if (!db.objectStoreNames.contains(STORE_NAME)) {
db.createObjectStore(STORE_NAME, { keyPath: "version" });
}

};
});

return FilelistDB.dbConnection;
}

async getFileList(): Promise<string[] | undefined> {


async getFileList(): Promise<string | undefined> {
if (FilelistDB.file) return FilelistDB.file;
const db = await FilelistDB.getConnection();
FilelistDB.file = new Promise((resolve, reject) => {
const tx = db.transaction(STORE_NAME, "readonly");
const store = tx.objectStore(STORE_NAME);
const request = store.get(this.version);
request.onerror = () => reject(request.error);
request.onsuccess = () => {
const result = request.result;
//console.log(result);
resolve(result?.file);
};
});
return FilelistDB.file
}
async getFileList_NoCache(): Promise<string | undefined> {

const db = await FilelistDB.getConnection();
return new Promise((resolve, reject) => {
const tx = db.transaction(STORE_NAME, "readonly");
Expand All @@ -48,20 +75,25 @@ export class FilelistDB {
request.onerror = () => reject(request.error);
request.onsuccess = () => {
const result = request.result;
resolve(result?.files);
//console.log(result);
resolve(result?.file);
};
});

}

async saveFileList(files: string[]): Promise<void> {
async saveFileList(file: string): Promise<void> {

FilelistDB.file = null;
const db = await FilelistDB.getConnection();
return new Promise((resolve, reject) => {
const tx = db.transaction(STORE_NAME, "readwrite");
const store = tx.objectStore(STORE_NAME);
const request = store.put({ version: this.version, files });
const request = store.put({ version: this.version, file });
request.onerror = () => reject(request.error);
request.onsuccess = () => resolve();
});

}

async deleteFileList(): Promise<void> {
Expand All @@ -75,49 +107,71 @@ export class FilelistDB {
});
}






/**
* Search the file list of this version using a regular expression.
* The search runs in a Web Worker if supported.
* @param regex The RegExp to test against each filename
* @param query The RegExp to test against each filename
* @returns Array of matching filenames
*/
async searchFileList(regex: RegExp): Promise<string[]> {
const files = await this.getFileList();
if (!files) return [];

async searchFileList(query): Promise<string[]> {
const start = Date.now();
//const start3 = Date.now();
const file = await this.getFileList();
console.log("files-fetch: " + (Date.now() - start));
if (!file) return [];
console.log("File loaded in "+ (Date.now()-start) +"ms");
// Fallback to main thread if workers are not supported
if (typeof Worker === "undefined") {
return files.filter((file) => regex.test(file));
//console.log("regex: "+regex);
//const start = Date.now();
const start2 = Date.now();
let res = file.match(query);
console.log("query: " + (Date.now() - start2));
//console.log(res);
//console.log(res);//files.filter((file) => regex.test(file));
//console.log("main-thered-search: "+(Date.now()-start));
//console.log("total-search: "+(Date.now()-start2));

return res;
}


this.initWorker();

const requestId = Math.random().toString(36).substring(2) + Date.now();

return new Promise((resolve, reject) => {
return new Promise<string[]>((resolve, reject) => {
this.workerPromiseMap.set(requestId, { resolve, reject });

// biome-ignore lint/style/noNonNullAssertion: debug
this.worker!.postMessage({
type: "search",
files,
pattern: regex.source,
flags: regex.flags,
file,
query,
requestId,
});
});
}).then((data) =>{console.log("total-query-time "+(Date.now()-start));return data;});


}

private initWorker() {
if (this.worker) return;

const workerCode = `
self.onmessage = (e) => {
const { type, files, pattern, flags, requestId } = e.data;
const { type, file, query, requestId } = e.data;
if (type === 'search') {
try {
const regex = new RegExp(pattern, flags);
const matches = files.filter(file => regex.test(file));

const start = Date.now();
const matches =file.match(query);
console.log("worker-query: "+(Date.now()-start));
self.postMessage({ type: 'result', matches, requestId });
} catch (err) {
self.postMessage({ type: 'error', error: err.message, requestId });
Expand Down