// Package pool provides the IO worker pool for the Pad editor. // It follows the architecture's single-owner pattern: the logic goroutine // owns all state, workers perform IO and post results, never touching state. package pool import ( "context" "fmt" "path/filepath" "sync/atomic" "time" "pad/internal/io/pool/mock" ) // taskCounter generates unique task IDs. var taskCounter atomic.Int64 // Priority determines which channel a task is dispatched to. type Priority int const ( HighPriority Priority = iota // UI-critical, blocks user progress LowPriority // Background work, can be delayed ) func (p Priority) String() string { switch p { case HighPriority: return "high" case LowPriority: return "low" default: return fmt.Sprintf("unknown(%d)", p) } } // TaskType identifies the kind of work for result routing. type TaskType string const ( // Browser tasks TypeReadDir TaskType = "read_dir" TypeBuildIndex TaskType = "build_index" TypeLoadIndex TaskType = "load_index" TypeLoadPages TaskType = "load_pages" TypeStatDir TaskType = "stat_dir" // File tasks TypeReadFile TaskType = "read_file" TypeWriteFile TaskType = "write_file" TypeStatFile TaskType = "stat_file" // Cache tasks TypeWriteCache TaskType = "write_cache" TypeReadCache TaskType = "read_cache" TypeInvalidate TaskType = "invalidate_cache" // State persistence TypeSaveState TaskType = "save_state" TypeSaveUndo TaskType = "save_undo" ) // Task represents a unit of work to be executed by a worker. // Tasks are immutable after creation. type Task interface { // Execute performs the task and returns a result. Execute() Result // Priority returns the task priority level. Priority() Priority // TaskID returns a unique identifier for this task. TaskID() string // TaskType returns the type of task (for result routing). TaskType() TaskType // DirPath returns the directory this task operates on, if any. DirPath() string // Context returns the context for this task, used for cancellation. Context() context.Context // Cancel cancels this task if it's still running. Cancel() // Timeout returns the timeout for this task. Zero means no timeout. Timeout() time.Duration } // --- Browser Tasks --- // ReadDirTask reads directory entries from the filesystem. type ReadDirTask struct { taskID string Dir string FS *mock.FileSystem } func NewReadDirTask(dir string, fs *mock.FileSystem) *ReadDirTask { return &ReadDirTask{ taskID: fmt.Sprintf("read_dir_%s_%d", filepath.Base(dir), taskCounter.Add(1)), Dir: dir, FS: fs, } } func (t *ReadDirTask) Execute() Result { entries, err := t.FS.ReadDir(t.Dir) if err != nil { return Result{ TaskID: t.taskID, TaskType: TypeReadDir, Success: false, Error: err, } } return Result{ TaskID: t.taskID, TaskType: TypeReadDir, Success: true, Data: entries, } } func (t *ReadDirTask) Priority() Priority { return HighPriority } func (t *ReadDirTask) TaskID() string { return t.taskID } func (t *ReadDirTask) TaskType() TaskType { return TypeReadDir } func (t *ReadDirTask) DirPath() string { return t.Dir } func (t *ReadDirTask) Context() context.Context { return context.Background() } func (t *ReadDirTask) Cancel() {} func (t *ReadDirTask) Timeout() time.Duration { return 5 * time.Second } // BuildIndexTask builds a directory index and writes it to cache. type BuildIndexTask struct { taskID string Dir string FS *mock.FileSystem } func NewBuildIndexTask(dir string, fs *mock.FileSystem) *BuildIndexTask { return &BuildIndexTask{ taskID: fmt.Sprintf("build_index_%s_%d", filepath.Base(dir), taskCounter.Add(1)), Dir: dir, FS: fs, } } func (t *BuildIndexTask) Execute() Result { // In production, this would read the directory, sort entries, // compute letter offsets, and write to cache. // For now, we just signal success with the directory info. entries, err := t.FS.ReadDir(t.Dir) if err != nil { return Result{ TaskID: t.taskID, TaskType: TypeBuildIndex, Success: false, Error: err, } } return Result{ TaskID: t.taskID, TaskType: TypeBuildIndex, Success: true, Data: entries, } } func (t *BuildIndexTask) Priority() Priority { return HighPriority } func (t *BuildIndexTask) TaskID() string { return t.taskID } func (t *BuildIndexTask) TaskType() TaskType { return TypeBuildIndex } func (t *BuildIndexTask) DirPath() string { return t.Dir } func (t *BuildIndexTask) Context() context.Context { return context.Background() } func (t *BuildIndexTask) Cancel() {} func (t *BuildIndexTask) Timeout() time.Duration { return 5 * time.Second } // LoadIndexTask loads a cached directory index. type LoadIndexTask struct { taskID string Dir string FS *mock.FileSystem } func NewLoadIndexTask(dir string, fs *mock.FileSystem) *LoadIndexTask { return &LoadIndexTask{ taskID: fmt.Sprintf("load_index_%s_%d", filepath.Base(dir), taskCounter.Add(1)), Dir: dir, FS: fs, } } func (t *LoadIndexTask) Execute() Result { // In production, this would read the index from cache. // For now, we just signal success. return Result{ TaskID: t.taskID, TaskType: TypeLoadIndex, Success: true, Data: nil, } } func (t *LoadIndexTask) Priority() Priority { return HighPriority } func (t *LoadIndexTask) TaskID() string { return t.taskID } func (t *LoadIndexTask) TaskType() TaskType { return TypeLoadIndex } func (t *LoadIndexTask) DirPath() string { return t.Dir } func (t *LoadIndexTask) Context() context.Context { return context.Background() } func (t *LoadIndexTask) Cancel() {} func (t *LoadIndexTask) Timeout() time.Duration { return 5 * time.Second } // LoadPagesTask loads specific pages of directory entries from cache. type LoadPagesTask struct { taskID string Dir string PageIndices []int FS *mock.FileSystem } func NewLoadPagesTask(dir string, pageIndices []int, fs *mock.FileSystem) *LoadPagesTask { return &LoadPagesTask{ taskID: fmt.Sprintf("load_pages_%s_%d", filepath.Base(dir), taskCounter.Add(1)), Dir: dir, PageIndices: pageIndices, FS: fs, } } func (t *LoadPagesTask) Execute() Result { // In production, this would read pages from cache. return Result{ TaskID: t.taskID, TaskType: TypeLoadPages, Success: true, Data: t.PageIndices, } } func (t *LoadPagesTask) Priority() Priority { return HighPriority } func (t *LoadPagesTask) TaskID() string { return t.taskID } func (t *LoadPagesTask) TaskType() TaskType { return TypeLoadPages } func (t *LoadPagesTask) DirPath() string { return t.Dir } func (t *LoadPagesTask) Context() context.Context { return context.Background() } func (t *LoadPagesTask) Cancel() {} func (t *LoadPagesTask) Timeout() time.Duration { return 5 * time.Second } // StatDirTask gets directory metadata. type StatDirTask struct { taskID string Dir string FS *mock.FileSystem } func NewStatDirTask(dir string, fs *mock.FileSystem) *StatDirTask { return &StatDirTask{ taskID: fmt.Sprintf("stat_dir_%s_%d", filepath.Base(dir), taskCounter.Add(1)), Dir: dir, FS: fs, } } func (t *StatDirTask) Execute() Result { exists := t.FS.DirExists(t.Dir) return Result{ TaskID: t.taskID, TaskType: TypeStatDir, Success: true, Data: map[string]bool{"exists": exists}, } } func (t *StatDirTask) Priority() Priority { return LowPriority } func (t *StatDirTask) TaskID() string { return t.taskID } func (t *StatDirTask) TaskType() TaskType { return TypeStatDir } func (t *StatDirTask) DirPath() string { return t.Dir } func (t *StatDirTask) Context() context.Context { return context.Background() } func (t *StatDirTask) Cancel() {} func (t *StatDirTask) Timeout() time.Duration { return 30 * time.Second } // --- File Tasks --- // ReadFileTask reads file content. type ReadFileTask struct { taskID string Path string FS *mock.FileSystem } func NewReadFileTask(path string, fs *mock.FileSystem) *ReadFileTask { return &ReadFileTask{ taskID: fmt.Sprintf("read_file_%s_%d", filepath.Base(path), taskCounter.Add(1)), Path: path, FS: fs, } } func (t *ReadFileTask) Execute() Result { content, err := t.FS.ReadFile(t.Path) if err != nil { return Result{ TaskID: t.taskID, TaskType: TypeReadFile, Success: false, Error: err, } } return Result{ TaskID: t.taskID, TaskType: TypeReadFile, Success: true, Data: content, } } func (t *ReadFileTask) Priority() Priority { return HighPriority } func (t *ReadFileTask) TaskID() string { return t.taskID } func (t *ReadFileTask) TaskType() TaskType { return TypeReadFile } func (t *ReadFileTask) DirPath() string { return filepath.Dir(t.Path) } func (t *ReadFileTask) Context() context.Context { return context.Background() } func (t *ReadFileTask) Cancel() {} func (t *ReadFileTask) Timeout() time.Duration { return 5 * time.Second } // WriteFileTask writes file content (auto-save). type WriteFileTask struct { taskID string Path string FS *mock.FileSystem Data []byte } func NewWriteFileTask(path string, data []byte, fs *mock.FileSystem) *WriteFileTask { return &WriteFileTask{ taskID: fmt.Sprintf("write_file_%s_%d", filepath.Base(path), taskCounter.Add(1)), Path: path, FS: fs, Data: data, } } func (t *WriteFileTask) Execute() Result { err := t.FS.WriteFile(t.Path, t.Data) if err != nil { return Result{ TaskID: t.taskID, TaskType: TypeWriteFile, Success: false, Error: err, } } return Result{ TaskID: t.taskID, TaskType: TypeWriteFile, Success: true, } } func (t *WriteFileTask) Priority() Priority { return LowPriority } func (t *WriteFileTask) TaskID() string { return t.taskID } func (t *WriteFileTask) TaskType() TaskType { return TypeWriteFile } func (t *WriteFileTask) DirPath() string { return filepath.Dir(t.Path) } func (t *WriteFileTask) Context() context.Context { return context.Background() } func (t *WriteFileTask) Cancel() {} func (t *WriteFileTask) Timeout() time.Duration { return 30 * time.Second } // --- Cache Tasks --- // WriteCacheTask writes cache data. type WriteCacheTask struct { taskID string Path string FS *mock.FileSystem Data []byte } func NewWriteCacheTask(path string, data []byte, fs *mock.FileSystem) *WriteCacheTask { return &WriteCacheTask{ taskID: fmt.Sprintf("write_cache_%s_%d", filepath.Base(path), taskCounter.Add(1)), Path: path, FS: fs, Data: data, } } func (t *WriteCacheTask) Execute() Result { err := t.FS.WriteFile(t.Path, t.Data) if err != nil { return Result{ TaskID: t.taskID, TaskType: TypeWriteCache, Success: false, Error: err, } } return Result{ TaskID: t.taskID, TaskType: TypeWriteCache, Success: true, } } func (t *WriteCacheTask) Priority() Priority { return LowPriority } func (t *WriteCacheTask) TaskID() string { return t.taskID } func (t *WriteCacheTask) TaskType() TaskType { return TypeWriteCache } func (t *WriteCacheTask) DirPath() string { return filepath.Dir(t.Path) } func (t *WriteCacheTask) Context() context.Context { return context.Background() } func (t *WriteCacheTask) Cancel() {} func (t *WriteCacheTask) Timeout() time.Duration { return 30 * time.Second } // --- State Persistence Tasks --- // SaveStateTask persists application state. type SaveStateTask struct { taskID string Path string FS *mock.FileSystem Data []byte } func NewSaveStateTask(path string, data []byte, fs *mock.FileSystem) *SaveStateTask { return &SaveStateTask{ taskID: fmt.Sprintf("save_state_%s_%d", filepath.Base(path), taskCounter.Add(1)), Path: path, FS: fs, Data: data, } } func (t *SaveStateTask) Execute() Result { err := t.FS.WriteFile(t.Path, t.Data) if err != nil { return Result{ TaskID: t.taskID, TaskType: TypeSaveState, Success: false, Error: err, } } return Result{ TaskID: t.taskID, TaskType: TypeSaveState, Success: true, } } func (t *SaveStateTask) Priority() Priority { return LowPriority } func (t *SaveStateTask) TaskID() string { return t.taskID } func (t *SaveStateTask) TaskType() TaskType { return TypeSaveState } func (t *SaveStateTask) DirPath() string { return filepath.Dir(t.Path) } func (t *SaveStateTask) Context() context.Context { return context.Background() } func (t *SaveStateTask) Cancel() {} func (t *SaveStateTask) Timeout() time.Duration { return 30 * time.Second } // SaveUndoTask persists undo stack. type SaveUndoTask struct { taskID string Path string FS *mock.FileSystem Data []byte } func NewSaveUndoTask(path string, data []byte, fs *mock.FileSystem) *SaveUndoTask { return &SaveUndoTask{ taskID: fmt.Sprintf("save_undo_%s_%d", filepath.Base(path), taskCounter.Add(1)), Path: path, FS: fs, Data: data, } } func (t *SaveUndoTask) Execute() Result { err := t.FS.WriteFile(t.Path, t.Data) if err != nil { return Result{ TaskID: t.taskID, TaskType: TypeSaveUndo, Success: false, Error: err, } } return Result{ TaskID: t.taskID, TaskType: TypeSaveUndo, Success: true, } } func (t *SaveUndoTask) Priority() Priority { return LowPriority } func (t *SaveUndoTask) TaskID() string { return t.taskID } func (t *SaveUndoTask) TaskType() TaskType { return TypeSaveUndo } func (t *SaveUndoTask) DirPath() string { return filepath.Dir(t.Path) } func (t *SaveUndoTask) Context() context.Context { return context.Background() } func (t *SaveUndoTask) Cancel() {} func (t *SaveUndoTask) Timeout() time.Duration { return 30 * time.Second }