mirror of
https://github.com/binaricat/Netcatty.git
synced 2026-10-29 18:11:40 +00:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
8af458c8e0 |
No files matched your search
@@ -64,6 +64,7 @@ import {
|
||||
mergeConnectionLogsFromStorage,
|
||||
mergeTerminalDataIntoLogs,
|
||||
mergeTerminalDataMapsForStorage,
|
||||
pruneInMemoryUnsavedTerminalData,
|
||||
type ConnectionLogTerminalDataMap,
|
||||
} from "../../domain/connectionLogTerminalData";
|
||||
import { getNextVaultOrder, normalizeVaultOrder } from "../../domain/vaultOrder";
|
||||
@@ -450,14 +451,10 @@ export const useVaultState = () => {
|
||||
buildPersistedLogIds(logs, usePersistedLogIdsFromDisk),
|
||||
);
|
||||
const persisted = writeConnectionLogTerminalDataMap(existing, merged);
|
||||
if (persisted) {
|
||||
connectionLogTerminalDataRef.current = merged;
|
||||
} else {
|
||||
connectionLogTerminalDataRef.current = {
|
||||
...connectionLogTerminalDataRef.current,
|
||||
...buildTerminalDataMapFromLogs(logs),
|
||||
};
|
||||
}
|
||||
// On a failed write (e.g. QuotaExceeded once the side store nears ~50 MB)
|
||||
// keep the same capped map instead of accumulating every log's replay
|
||||
// buffer in memory; anything outside it would be pruned on the next write.
|
||||
connectionLogTerminalDataRef.current = merged;
|
||||
return { map: connectionLogTerminalDataRef.current, persisted };
|
||||
}, []);
|
||||
|
||||
@@ -1305,8 +1302,10 @@ export const useVaultState = () => {
|
||||
const savedLogs = prev.filter((l) => l.saved);
|
||||
const unsavedLogs = prev.filter((l) => !l.saved);
|
||||
const updated = [newLog, ...unsavedLogs].slice(0, 500);
|
||||
const final = [...updated, ...savedLogs].sort(
|
||||
(a, b) => b.startTime - a.startTime
|
||||
const final = pruneInMemoryUnsavedTerminalData(
|
||||
[...updated, ...savedLogs].sort(
|
||||
(a, b) => b.startTime - a.startTime
|
||||
),
|
||||
);
|
||||
persistConnectionLogState(final, { pruneMainBlob: true });
|
||||
return final;
|
||||
@@ -1319,8 +1318,8 @@ export const useVaultState = () => {
|
||||
const updateConnectionLog = useCallback(
|
||||
(id: string, updates: Partial<ConnectionLog>) => {
|
||||
setConnectionLogs((prev) => {
|
||||
const updated = prev.map((log) =>
|
||||
log.id === id ? { ...log, ...updates } : log
|
||||
const updated = pruneInMemoryUnsavedTerminalData(
|
||||
prev.map((log) => (log.id === id ? { ...log, ...updates } : log)),
|
||||
);
|
||||
persistConnectionLogState(updated, { pruneMainBlob: true });
|
||||
return updated;
|
||||
@@ -1331,8 +1330,10 @@ export const useVaultState = () => {
|
||||
|
||||
const toggleConnectionLogSaved = useCallback((id: string) => {
|
||||
setConnectionLogs((prev) => {
|
||||
const updated = prev.map((log) =>
|
||||
log.id === id ? { ...log, saved: !log.saved } : log
|
||||
const updated = pruneInMemoryUnsavedTerminalData(
|
||||
prev.map((log) =>
|
||||
log.id === id ? { ...log, saved: !log.saved } : log
|
||||
),
|
||||
);
|
||||
persistConnectionLogState(updated, { pruneMainBlob: false });
|
||||
return updated;
|
||||
|
||||
@@ -8,6 +8,7 @@ import {
|
||||
mergeConnectionLogsFromStorage,
|
||||
mergeTerminalDataIntoLogs,
|
||||
mergeTerminalDataMapsForStorage,
|
||||
pruneInMemoryUnsavedTerminalData,
|
||||
pruneTerminalDataMapForStorage,
|
||||
} from "./connectionLogTerminalData.ts";
|
||||
|
||||
@@ -149,6 +150,44 @@ test("pruneTerminalDataMapForStorage caps unsaved replay buffers", () => {
|
||||
assert.equal(pruned["log-0"], undefined);
|
||||
});
|
||||
|
||||
test("pruneInMemoryUnsavedTerminalData drops oldest unsaved replay buffers beyond the persist cap", () => {
|
||||
const logs: ConnectionLog[] = Array.from({ length: 60 }, (_, index) => ({
|
||||
...baseLog,
|
||||
id: `log-${index}`,
|
||||
startTime: index,
|
||||
saved: false,
|
||||
terminalData: `data-${index}`,
|
||||
}));
|
||||
// One bookmarked entry must keep its replay buffer even when unsaved count is high.
|
||||
logs.push({
|
||||
...baseLog,
|
||||
id: "saved-keep",
|
||||
startTime: 0,
|
||||
saved: true,
|
||||
terminalData: "saved-data",
|
||||
});
|
||||
|
||||
const pruned = pruneInMemoryUnsavedTerminalData(logs);
|
||||
const unsavedWithData = pruned.filter((log) => !log.saved && log.terminalData !== undefined);
|
||||
assert.equal(unsavedWithData.length, MAX_PERSISTED_UNSAVED_TERMINAL_DATA_ENTRIES);
|
||||
assert.equal(pruned.find((log) => log.id === "log-59")?.terminalData, "data-59");
|
||||
assert.equal(pruned.find((log) => log.id === "log-0")?.terminalData, undefined);
|
||||
assert.equal(pruned.find((log) => log.id === "log-0")?.id, "log-0"); // metadata kept
|
||||
assert.equal(pruned.find((log) => log.id === "saved-keep")?.terminalData, "saved-data");
|
||||
assert.equal(pruned.length, logs.length);
|
||||
});
|
||||
|
||||
test("pruneInMemoryUnsavedTerminalData is a no-op at or under the cap", () => {
|
||||
const logs: ConnectionLog[] = Array.from({ length: 10 }, (_, index) => ({
|
||||
...baseLog,
|
||||
id: `log-${index}`,
|
||||
startTime: index,
|
||||
terminalData: `data-${index}`,
|
||||
}));
|
||||
const pruned = pruneInMemoryUnsavedTerminalData(logs);
|
||||
assert.equal(pruned, logs);
|
||||
});
|
||||
|
||||
test("mergeTerminalDataMapsForStorage keeps replay data from other windows", () => {
|
||||
const logs: ConnectionLog[] = [
|
||||
{ ...baseLog, id: "local", startTime: 2000 },
|
||||
|
||||
@@ -88,6 +88,32 @@ export const pruneTerminalDataMapForStorage = (
|
||||
return next;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Drop in-memory `terminalData` from the oldest unsaved logs beyond the
|
||||
* persisted-entry cap. List metadata stays; only replay buffers are removed.
|
||||
* Matches what survives a restart (`pruneTerminalDataMapForStorage`).
|
||||
*/
|
||||
export const pruneInMemoryUnsavedTerminalData = (
|
||||
logs: ConnectionLog[],
|
||||
maxEntries: number = MAX_PERSISTED_UNSAVED_TERMINAL_DATA_ENTRIES,
|
||||
): ConnectionLog[] => {
|
||||
const unsavedWithData = logs
|
||||
.filter((log) => !log.saved && log.terminalData !== undefined)
|
||||
.sort((a, b) => b.startTime - a.startTime);
|
||||
if (unsavedWithData.length <= maxEntries) return logs;
|
||||
|
||||
const keep = new Set(unsavedWithData.slice(0, maxEntries).map((log) => log.id));
|
||||
let changed = false;
|
||||
const next = logs.map((log) => {
|
||||
if (log.saved || log.terminalData === undefined || keep.has(log.id)) return log;
|
||||
changed = true;
|
||||
const { terminalData: _omitted, ...rest } = log;
|
||||
return rest;
|
||||
});
|
||||
return changed ? next : logs;
|
||||
};
|
||||
|
||||
/** Fold side-store maps together, then cap to the allowed unsaved/saved set. */
|
||||
export const mergeTerminalDataMapsForStorage = (
|
||||
logs: ConnectionLog[],
|
||||
|
||||
Reference in new issue
Block a user