mirror of
https://github.com/catlog22/Claude-Code-Workflow.git
synced 2026-02-10 02:24:35 +08:00
feat(storage): implement storage manager for centralized management and cleanup
- Added a new Storage Manager component to handle storage statistics, project cleanup, and configuration for CCW centralized storage. - Introduced functions to calculate directory sizes, get project storage stats, and clean specific or all storage. - Enhanced SQLiteStore with a public API for executing queries securely. - Updated tests to utilize the new execute_query method and validate storage management functionalities. - Improved performance by implementing connection pooling with idle timeout management in SQLiteStore. - Added new fields (token_count, symbol_type) to the symbols table and adjusted related insertions. - Enhanced error handling and logging for storage operations.
This commit is contained in:
@@ -29,8 +29,11 @@ export class CacheManager<T> {
|
||||
* @param options - Cache configuration options
|
||||
*/
|
||||
constructor(cacheKey: string, options: CacheOptions = {}) {
|
||||
if (!options.cacheDir) {
|
||||
throw new Error('CacheManager requires cacheDir option. Use StoragePaths.project(path).cache');
|
||||
}
|
||||
this.ttl = options.ttl || 5 * 60 * 1000; // Default: 5 minutes
|
||||
this.cacheDir = options.cacheDir || '.ccw-cache';
|
||||
this.cacheDir = options.cacheDir;
|
||||
this.cacheFile = join(this.cacheDir, `${cacheKey}.json`);
|
||||
}
|
||||
|
||||
|
||||
@@ -10,6 +10,15 @@ import { join } from 'path';
|
||||
import { resolvePath, getRecentPaths, trackRecentPath, removeRecentPath, normalizePathForDisplay } from '../../utils/path-resolver.js';
|
||||
import { scanSessions } from '../session-scanner.js';
|
||||
import { aggregateData } from '../data-aggregator.js';
|
||||
import {
|
||||
getStorageStats,
|
||||
getStorageConfig,
|
||||
cleanProjectStorage,
|
||||
cleanAllStorage,
|
||||
resolveProjectId,
|
||||
projectExists,
|
||||
formatBytes
|
||||
} from '../../tools/storage-manager.js';
|
||||
|
||||
export interface RouteContext {
|
||||
pathname: string;
|
||||
@@ -325,5 +334,94 @@ export async function handleSystemRoutes(ctx: RouteContext): Promise<boolean> {
|
||||
return true;
|
||||
}
|
||||
|
||||
// API: Get storage statistics
|
||||
if (pathname === '/api/storage/stats') {
|
||||
try {
|
||||
const stats = getStorageStats();
|
||||
const config = getStorageConfig();
|
||||
|
||||
// Format for dashboard display
|
||||
const response = {
|
||||
location: stats.rootPath,
|
||||
isCustomLocation: config.isCustom,
|
||||
totalSize: stats.totalSize,
|
||||
totalSizeFormatted: formatBytes(stats.totalSize),
|
||||
projectCount: stats.projectCount,
|
||||
globalDb: stats.globalDb,
|
||||
projects: stats.projects.map(p => ({
|
||||
id: p.projectId,
|
||||
totalSize: p.totalSize,
|
||||
totalSizeFormatted: formatBytes(p.totalSize),
|
||||
historyRecords: p.cliHistory.recordCount ?? 0,
|
||||
hasCliHistory: p.cliHistory.exists,
|
||||
hasMemory: p.memory.exists,
|
||||
hasCache: p.cache.exists,
|
||||
lastModified: p.lastModified?.toISOString() || null
|
||||
}))
|
||||
};
|
||||
|
||||
res.writeHead(200, { 'Content-Type': 'application/json' });
|
||||
res.end(JSON.stringify(response));
|
||||
} catch (err) {
|
||||
res.writeHead(500, { 'Content-Type': 'application/json' });
|
||||
res.end(JSON.stringify({ error: 'Failed to get storage stats', details: String(err) }));
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
// API: Clean storage
|
||||
if (pathname === '/api/storage/clean' && req.method === 'POST') {
|
||||
handlePostRequest(req, res, async (body) => {
|
||||
const { projectId, projectPath, all, types } = body as {
|
||||
projectId?: string;
|
||||
projectPath?: string;
|
||||
all?: boolean;
|
||||
types?: { cliHistory?: boolean; memory?: boolean; cache?: boolean; config?: boolean };
|
||||
};
|
||||
|
||||
const cleanOptions = types || { all: true };
|
||||
|
||||
if (projectId) {
|
||||
// Clean specific project by ID
|
||||
if (!projectExists(projectId)) {
|
||||
return { error: 'Project not found', status: 404 };
|
||||
}
|
||||
const result = cleanProjectStorage(projectId, cleanOptions);
|
||||
return {
|
||||
success: result.success,
|
||||
freedBytes: result.freedBytes,
|
||||
freedFormatted: formatBytes(result.freedBytes),
|
||||
errors: result.errors
|
||||
};
|
||||
} else if (projectPath) {
|
||||
// Clean specific project by path
|
||||
const id = resolveProjectId(projectPath);
|
||||
if (!projectExists(id)) {
|
||||
return { error: 'No storage found for project', status: 404 };
|
||||
}
|
||||
const result = cleanProjectStorage(id, cleanOptions);
|
||||
return {
|
||||
success: result.success,
|
||||
freedBytes: result.freedBytes,
|
||||
freedFormatted: formatBytes(result.freedBytes),
|
||||
errors: result.errors
|
||||
};
|
||||
} else if (all) {
|
||||
// Clean all storage
|
||||
const result = cleanAllStorage(cleanOptions);
|
||||
return {
|
||||
success: result.success,
|
||||
projectsCleaned: result.projectsCleaned,
|
||||
freedBytes: result.freedBytes,
|
||||
freedFormatted: formatBytes(result.freedBytes),
|
||||
errors: result.errors
|
||||
};
|
||||
} else {
|
||||
return { error: 'Specify projectId, projectPath, or all=true', status: 400 };
|
||||
}
|
||||
});
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -82,6 +82,7 @@ const MODULE_FILES = [
|
||||
'components/hook-manager.js',
|
||||
'components/cli-status.js',
|
||||
'components/cli-history.js',
|
||||
'components/storage-manager.js',
|
||||
'components/_exp_helpers.js',
|
||||
'components/tabs-other.js',
|
||||
'components/tabs-context.js',
|
||||
@@ -295,11 +296,12 @@ export async function startServer(options: ServerOptions = {}): Promise<http.Ser
|
||||
if (await handleFilesRoutes(routeContext)) return;
|
||||
}
|
||||
|
||||
// System routes (data, health, version, paths, shutdown, notify)
|
||||
// System routes (data, health, version, paths, shutdown, notify, storage)
|
||||
if (pathname === '/api/data' || pathname === '/api/health' ||
|
||||
pathname === '/api/version-check' || pathname === '/api/shutdown' ||
|
||||
pathname === '/api/recent-paths' || pathname === '/api/switch-path' ||
|
||||
pathname === '/api/remove-recent-path' || pathname === '/api/system/notify') {
|
||||
pathname === '/api/remove-recent-path' || pathname === '/api/system/notify' ||
|
||||
pathname.startsWith('/api/storage/')) {
|
||||
if (await handleSystemRoutes(routeContext)) return;
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user