// Project-wise File Management System with Local Storage // Comprehensive project organization and file handling interface ProjectFile { id: string; name: string; type: 'calculation' | 'report' | 'data' | 'image' | 'document'; content: any; size: number; createdAt: Date; modifiedAt: Date; tags: string[]; metadata: Record; } interface Project { id: string; name: string; description: string; createdAt: Date; modifiedAt: Date; files: ProjectFile[]; settings: ProjectSettings; status: 'active' | 'completed' | 'archived'; } interface ProjectSettings { autoSave: boolean; backupFrequency: number; // hours compressionEnabled: boolean; maxFileSize: number; // MB allowedFileTypes: string[]; } class ProjectManager { private readonly STORAGE_KEY = 'prithvi_projects'; private readonly MAX_STORAGE_SIZE = 50 * 1024 * 1024; // 50MB limit private projects: Map = new Map(); constructor() { this.loadProjects(); this.setupAutoSave(); } // Project Management async createProject(name: string, description: string = ''): Promise { try { if (!name || name.trim().length === 0) { throw new Error('Project name is required'); } if (name.length > 100) { throw new Error('Project name must be less than 100 characters'); } // Check for duplicate names const existingProject = Array.from(this.projects.values()).find(p => p.name === name); if (existingProject) { throw new Error('Project with this name already exists'); } const project: Project = { id: this.generateId(), name: name.trim(), description: description.trim(), createdAt: new Date(), modifiedAt: new Date(), files: [], settings: this.getDefaultSettings(), status: 'active' }; this.projects.set(project.id, project); await this.saveProjects(); return project; } catch (error) { throw new Error(`Failed to create project: ${error instanceof Error ? error.message : 'Unknown error'}`); } } async updateProject(projectId: string, updates: Partial): Promise { try { const project = this.projects.get(projectId); if (!project) { throw new Error('Project not found'); } // Validate updates if (updates.name !== undefined) { if (!updates.name || updates.name.trim().length === 0) { throw new Error('Project name cannot be empty'); } if (updates.name.length > 100) { throw new Error('Project name must be less than 100 characters'); } } const updatedProject = { ...project, ...updates, id: project.id, // Prevent ID changes modifiedAt: new Date() }; this.projects.set(projectId, updatedProject); await this.saveProjects(); return updatedProject; } catch (error) { throw new Error(`Failed to update project: ${error instanceof Error ? error.message : 'Unknown error'}`); } } async deleteProject(projectId: string): Promise { try { const project = this.projects.get(projectId); if (!project) { throw new Error('Project not found'); } this.projects.delete(projectId); await this.saveProjects(); return true; } catch (error) { throw new Error(`Failed to delete project: ${error instanceof Error ? error.message : 'Unknown error'}`); } } getProject(projectId: string): Project | undefined { return this.projects.get(projectId); } getAllProjects(): Project[] { return Array.from(this.projects.values()).sort((a, b) => b.modifiedAt.getTime() - a.modifiedAt.getTime() ); } getProjectsByStatus(status: Project['status']): Project[] { return this.getAllProjects().filter(p => p.status === status); } searchProjects(query: string): Project[] { if (!query || query.trim().length === 0) { return this.getAllProjects(); } const searchTerm = query.toLowerCase().trim(); return this.getAllProjects().filter(project => project.name.toLowerCase().includes(searchTerm) || project.description.toLowerCase().includes(searchTerm) || project.files.some(file => file.name.toLowerCase().includes(searchTerm) || file.tags.some(tag => tag.toLowerCase().includes(searchTerm)) ) ); } // File Management async addFile(projectId: string, file: Omit): Promise { try { const project = this.projects.get(projectId); if (!project) { throw new Error('Project not found'); } // Validate file this.validateFile(file, project.settings); // Check storage limits await this.checkStorageLimit(file.size); const newFile: ProjectFile = { ...file, id: this.generateId(), createdAt: new Date(), modifiedAt: new Date() }; project.files.push(newFile); project.modifiedAt = new Date(); this.projects.set(projectId, project); await this.saveProjects(); return newFile; } catch (error) { throw new Error(`Failed to add file: ${error instanceof Error ? error.message : 'Unknown error'}`); } } async updateFile(projectId: string, fileId: string, updates: Partial): Promise { try { const project = this.projects.get(projectId); if (!project) { throw new Error('Project not found'); } const fileIndex = project.files.findIndex(f => f.id === fileId); if (fileIndex === -1) { throw new Error('File not found'); } const updatedFile = { ...project.files[fileIndex], ...updates, id: fileId, // Prevent ID changes modifiedAt: new Date() }; // Validate updated file this.validateFile(updatedFile, project.settings); project.files[fileIndex] = updatedFile; project.modifiedAt = new Date(); this.projects.set(projectId, project); await this.saveProjects(); return updatedFile; } catch (error) { throw new Error(`Failed to update file: ${error instanceof Error ? error.message : 'Unknown error'}`); } } async deleteFile(projectId: string, fileId: string): Promise { try { const project = this.projects.get(projectId); if (!project) { throw new Error('Project not found'); } const fileIndex = project.files.findIndex(f => f.id === fileId); if (fileIndex === -1) { throw new Error('File not found'); } project.files.splice(fileIndex, 1); project.modifiedAt = new Date(); this.projects.set(projectId, project); await this.saveProjects(); return true; } catch (error) { throw new Error(`Failed to delete file: ${error instanceof Error ? error.message : 'Unknown error'}`); } } getFile(projectId: string, fileId: string): ProjectFile | undefined { const project = this.projects.get(projectId); return project?.files.find(f => f.id === fileId); } getFilesByType(projectId: string, type: ProjectFile['type']): ProjectFile[] { const project = this.projects.get(projectId); return project?.files.filter(f => f.type === type) || []; } searchFiles(projectId: string, query: string): ProjectFile[] { const project = this.projects.get(projectId); if (!project || !query.trim()) { return project?.files || []; } const searchTerm = query.toLowerCase().trim(); return project.files.filter(file => file.name.toLowerCase().includes(searchTerm) || file.tags.some(tag => tag.toLowerCase().includes(searchTerm)) || (typeof file.content === 'string' && file.content.toLowerCase().includes(searchTerm)) ); } // Data Export/Import async exportProject(projectId: string): Promise { try { const project = this.projects.get(projectId); if (!project) { throw new Error('Project not found'); } const exportData = { project, exportedAt: new Date().toISOString(), version: '1.0', metadata: { totalFiles: project.files.length, totalSize: this.calculateProjectSize(project) } }; return JSON.stringify(exportData, null, 2); } catch (error) { throw new Error(`Failed to export project: ${error instanceof Error ? error.message : 'Unknown error'}`); } } async importProject(importData: string): Promise { try { const data = JSON.parse(importData); if (!data.project) { throw new Error('Invalid import data: missing project information'); } const project = data.project; // Validate project structure if (!project.name || !project.id) { throw new Error('Invalid project data: missing required fields'); } // Generate new ID to avoid conflicts project.id = this.generateId(); project.createdAt = new Date(); project.modifiedAt = new Date(); // Validate and regenerate file IDs if (project.files) { project.files.forEach((file: ProjectFile) => { file.id = this.generateId(); file.createdAt = new Date(file.createdAt); file.modifiedAt = new Date(file.modifiedAt); }); } // Check storage limits const projectSize = this.calculateProjectSize(project); await this.checkStorageLimit(projectSize); this.projects.set(project.id, project); await this.saveProjects(); return project; } catch (error) { throw new Error(`Failed to import project: ${error instanceof Error ? error.message : 'Unknown error'}`); } } // Storage Management async getStorageInfo(): Promise<{ used: number; available: number; total: number; projects: number; files: number; }> { try { const projects = this.getAllProjects(); const totalFiles = projects.reduce((sum, p) => sum + p.files.length, 0); const usedSpace = this.calculateTotalSize(); return { used: usedSpace, available: this.MAX_STORAGE_SIZE - usedSpace, total: this.MAX_STORAGE_SIZE, projects: projects.length, files: totalFiles }; } catch (error) { throw new Error(`Failed to get storage info: ${error instanceof Error ? error.message : 'Unknown error'}`); } } async cleanupStorage(daysOld: number = 30): Promise<{ deletedProjects: number; freedSpace: number; }> { try { const cutoffDate = new Date(); cutoffDate.setDate(cutoffDate.getDate() - daysOld); let deletedProjects = 0; let freedSpace = 0; for (const [projectId, project] of this.projects) { if (project.status === 'archived' && project.modifiedAt < cutoffDate) { freedSpace += this.calculateProjectSize(project); this.projects.delete(projectId); deletedProjects++; } } if (deletedProjects > 0) { await this.saveProjects(); } return { deletedProjects, freedSpace }; } catch (error) { throw new Error(`Failed to cleanup storage: ${error instanceof Error ? error.message : 'Unknown error'}`); } } // Backup and Recovery async createBackup(): Promise { try { const backup = { projects: Array.from(this.projects.values()), backupDate: new Date().toISOString(), version: '1.0' }; return JSON.stringify(backup, null, 2); } catch (error) { throw new Error(`Failed to create backup: ${error instanceof Error ? error.message : 'Unknown error'}`); } } async restoreFromBackup(backupData: string): Promise { try { const backup = JSON.parse(backupData); if (!backup.projects || !Array.isArray(backup.projects)) { throw new Error('Invalid backup data'); } // Clear existing projects this.projects.clear(); // Restore projects backup.projects.forEach((project: Project) => { // Regenerate dates project.createdAt = new Date(project.createdAt); project.modifiedAt = new Date(project.modifiedAt); if (project.files) { project.files.forEach((file: ProjectFile) => { file.createdAt = new Date(file.createdAt); file.modifiedAt = new Date(file.modifiedAt); }); } this.projects.set(project.id, project); }); await this.saveProjects(); return true; } catch (error) { throw new Error(`Failed to restore from backup: ${error instanceof Error ? error.message : 'Unknown error'}`); } } // Private helper methods private generateId(): string { return `proj_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`; } private getDefaultSettings(): ProjectSettings { return { autoSave: true, backupFrequency: 24, compressionEnabled: true, maxFileSize: 10, allowedFileTypes: [ 'text/plain', 'application/json', 'text/csv', 'image/jpeg', 'image/png', 'image/gif', 'application/pdf', 'text/html' ] }; } private validateFile(file: Partial, settings: ProjectSettings): void { if (!file.name || file.name.trim().length === 0) { throw new Error('File name is required'); } if (file.name.length > 255) { throw new Error('File name must be less than 255 characters'); } if (file.size !== undefined && file.size > settings.maxFileSize * 1024 * 1024) { throw new Error(`File size exceeds limit of ${settings.maxFileSize}MB`); } if (file.tags && file.tags.some(tag => tag.length > 50)) { throw new Error('Tag length must be less than 50 characters'); } } private calculateProjectSize(project: Project): number { return project.files.reduce((total, file) => total + file.size, 0); } private calculateTotalSize(): number { let total = 0; for (const project of this.projects.values()) { total += this.calculateProjectSize(project); } return total; } private async checkStorageLimit(additionalSize: number): Promise { const currentSize = this.calculateTotalSize(); if (currentSize + additionalSize > this.MAX_STORAGE_SIZE) { throw new Error(`Storage limit exceeded. Available: ${((this.MAX_STORAGE_SIZE - currentSize) / 1024 / 1024).toFixed(2)}MB, Required: ${(additionalSize / 1024 / 1024).toFixed(2)}MB`); } } private loadProjects(): void { try { const data = localStorage.getItem(this.STORAGE_KEY); if (data) { const projects = JSON.parse(data); projects.forEach((project: Project) => { // Convert date strings back to Date objects project.createdAt = new Date(project.createdAt); project.modifiedAt = new Date(project.modifiedAt); if (project.files) { project.files.forEach((file: ProjectFile) => { file.createdAt = new Date(file.createdAt); file.modifiedAt = new Date(file.modifiedAt); }); } this.projects.set(project.id, project); }); } } catch (error) { console.error('Failed to load projects from storage:', error); } } private async saveProjects(): Promise { try { const projects = Array.from(this.projects.values()); localStorage.setItem(this.STORAGE_KEY, JSON.stringify(projects)); } catch (error) { throw new Error(`Failed to save projects: ${error instanceof Error ? error.message : 'Storage quota exceeded'}`); } } private setupAutoSave(): void { // Auto-save every 5 minutes setInterval(() => { this.saveProjects().catch(error => { console.error('Auto-save failed:', error); }); }, 5 * 60 * 1000); } } export const projectManager = new ProjectManager(); export { Project, ProjectFile, ProjectSettings };