package services import ( "fmt" "os" "os/exec" "path/filepath" "runtime" "time" "voicesnap/internal/history" ) // HistoryService provides recognition history to the frontend. type HistoryService struct { store *history.Store } // HistoryStatus summarizes full-history state for paginated views. type HistoryStatus struct { RetentionDays int `json:"retentionDays"` StoragePath string `json:"storagePath"` StorageSizeBytes int64 `json:"storageSizeBytes"` StorageExists bool `json:"storageExists"` StorageSizeAvailable bool `json:"storageSizeAvailable"` TotalCount int `json:"totalCount"` ExportedCount int `json:"exportedCount"` UnexportedTodayCount int `json:"unexportedTodayCount"` UnexportedLast7DaysCount int `json:"unexportedLast7DaysCount"` UnexportedAllCount int `json:"unexportedAllCount"` } func NewHistoryService(store *history.Store) *HistoryService { return &HistoryService{store: store} } // GetAll returns all history entries (newest first). func (s *HistoryService) GetAll() []history.Entry { return s.store.GetAll() } // GetPage returns one page of history entries (newest first). func (s *HistoryService) GetPage(page int, pageSize int) history.PageResult { return s.store.GetPage(page, pageSize) } // GetStatus returns full-history metadata used by the paginated UI. func (s *HistoryService) GetStatus() HistoryStatus { path := s.store.Path() counts := s.store.Counts(time.Now(), time.Local) status := HistoryStatus{ RetentionDays: s.store.GetRetentionDays(), StoragePath: path, TotalCount: counts.TotalCount, ExportedCount: counts.ExportedCount, UnexportedTodayCount: counts.UnexportedTodayCount, UnexportedLast7DaysCount: counts.UnexportedLast7DaysCount, UnexportedAllCount: counts.UnexportedAllCount, } if info, err := os.Stat(path); err == nil { status.StorageExists = true status.StorageSizeAvailable = true status.StorageSizeBytes = info.Size() } else if os.IsPermission(err) { status.StorageExists = true } return status } // Add adds a new recognition result to history. func (s *HistoryService) Add(text string) { s.store.Add(text) } // Delete removes a single entry by timestamp. func (s *HistoryService) Delete(timestamp int64) { s.store.Delete(timestamp) } // ClearAll removes all history entries. func (s *HistoryService) ClearAll() { s.store.ClearAll() } // ClearExported removes all history entries that have already been exported. func (s *HistoryService) ClearExported() (history.ClearExportedResult, error) { return s.store.ClearExported() } // GetRetentionDays returns the current retention period in days. func (s *HistoryService) GetRetentionDays() int { return s.store.GetRetentionDays() } // SetRetentionDays sets how long to keep history (0 = forever). func (s *HistoryService) SetRetentionDays(days int) error { return s.store.SetRetentionDays(days) } // GetStoragePath returns the full history.json path. func (s *HistoryService) GetStoragePath() string { return s.store.Path() } // OpenStorageFolder opens the folder containing history.json. func (s *HistoryService) OpenStorageFolder() error { dir := filepath.Dir(s.store.Path()) info, err := os.Stat(dir) if err != nil { return fmt.Errorf("history storage folder unavailable: %w", err) } if !info.IsDir() { return fmt.Errorf("history storage path is not a folder: %s", dir) } switch runtime.GOOS { case "darwin": return exec.Command("open", dir).Start() case "windows": return exec.Command("rundll32", "url.dll,FileProtocolHandler", dir).Start() default: return exec.Command("xdg-open", dir).Start() } }