Phase 3: Add history data structures and persistence
Added conversion history tracking with persistence to disk. Jobs are automatically added to history when they complete, fail, or are cancelled. Changes: - Added HistoryEntry struct to represent completed jobs - Added historyConfig for JSON persistence - Added historyConfigPath(), loadHistoryConfig(), saveHistoryConfig() functions - Added historyEntries and sidebarVisible fields to appState - Added addToHistory() method to save completed jobs - Initialize history loading on app startup - Hook into queue change callback to automatically save finished jobs - Store FFmpeg command in history for each job - Limit history to 20 most recent entries History is saved to ~/.config/VideoTools/history.json and includes job details, timestamps, error messages, and the FFmpeg command for manual reproduction.
This commit is contained in:
parent
65a4883b6c
commit
da1778ab44
140
main.go
140
main.go
|
|
@ -593,6 +593,74 @@ func saveBenchmarkConfig(cfg benchmarkConfig) error {
|
||||||
return os.WriteFile(path, data, 0o644)
|
return os.WriteFile(path, data, 0o644)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// HistoryEntry represents a completed job in the history
|
||||||
|
type HistoryEntry struct {
|
||||||
|
ID string `json:"id"`
|
||||||
|
Type queue.JobType `json:"type"`
|
||||||
|
Status queue.JobStatus `json:"status"`
|
||||||
|
Title string `json:"title"`
|
||||||
|
InputFile string `json:"input_file"`
|
||||||
|
OutputFile string `json:"output_file"`
|
||||||
|
LogPath string `json:"log_path,omitempty"`
|
||||||
|
Config map[string]interface{} `json:"config"`
|
||||||
|
CreatedAt time.Time `json:"created_at"`
|
||||||
|
StartedAt *time.Time `json:"started_at,omitempty"`
|
||||||
|
CompletedAt *time.Time `json:"completed_at,omitempty"`
|
||||||
|
Error string `json:"error,omitempty"`
|
||||||
|
FFmpegCmd string `json:"ffmpeg_cmd,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// historyConfig holds conversion history
|
||||||
|
type historyConfig struct {
|
||||||
|
Entries []HistoryEntry `json:"entries"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func historyConfigPath() string {
|
||||||
|
configDir, err := os.UserConfigDir()
|
||||||
|
if err != nil || configDir == "" {
|
||||||
|
home := os.Getenv("HOME")
|
||||||
|
if home != "" {
|
||||||
|
configDir = filepath.Join(home, ".config")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if configDir == "" {
|
||||||
|
return "history.json"
|
||||||
|
}
|
||||||
|
return filepath.Join(configDir, "VideoTools", "history.json")
|
||||||
|
}
|
||||||
|
|
||||||
|
func loadHistoryConfig() (historyConfig, error) {
|
||||||
|
path := historyConfigPath()
|
||||||
|
data, err := os.ReadFile(path)
|
||||||
|
if err != nil {
|
||||||
|
if os.IsNotExist(err) {
|
||||||
|
return historyConfig{Entries: []HistoryEntry{}}, nil
|
||||||
|
}
|
||||||
|
return historyConfig{}, err
|
||||||
|
}
|
||||||
|
var cfg historyConfig
|
||||||
|
if err := json.Unmarshal(data, &cfg); err != nil {
|
||||||
|
return historyConfig{}, err
|
||||||
|
}
|
||||||
|
return cfg, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func saveHistoryConfig(cfg historyConfig) error {
|
||||||
|
// Limit to 20 most recent entries
|
||||||
|
if len(cfg.Entries) > 20 {
|
||||||
|
cfg.Entries = cfg.Entries[:20]
|
||||||
|
}
|
||||||
|
path := historyConfigPath()
|
||||||
|
if err := os.MkdirAll(filepath.Dir(path), 0o755); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
data, err := json.MarshalIndent(cfg, "", " ")
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return os.WriteFile(path, data, 0o644)
|
||||||
|
}
|
||||||
|
|
||||||
type appState struct {
|
type appState struct {
|
||||||
window fyne.Window
|
window fyne.Window
|
||||||
active string
|
active string
|
||||||
|
|
@ -697,6 +765,10 @@ type appState struct {
|
||||||
// Interlacing detection state
|
// Interlacing detection state
|
||||||
interlaceResult *interlace.DetectionResult
|
interlaceResult *interlace.DetectionResult
|
||||||
interlaceAnalyzing bool
|
interlaceAnalyzing bool
|
||||||
|
|
||||||
|
// History sidebar state
|
||||||
|
historyEntries []HistoryEntry
|
||||||
|
sidebarVisible bool
|
||||||
}
|
}
|
||||||
|
|
||||||
type mergeClip struct {
|
type mergeClip struct {
|
||||||
|
|
@ -711,6 +783,55 @@ func (s *appState) persistConvertConfig() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// addToHistory adds a completed job to the history
|
||||||
|
func (s *appState) addToHistory(job *queue.Job) {
|
||||||
|
if job == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Only add completed, failed, or cancelled jobs
|
||||||
|
if job.Status != queue.JobStatusCompleted &&
|
||||||
|
job.Status != queue.JobStatusFailed &&
|
||||||
|
job.Status != queue.JobStatusCancelled {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Build FFmpeg command from job config
|
||||||
|
cmdStr := buildFFmpegCommandFromJob(job)
|
||||||
|
|
||||||
|
entry := HistoryEntry{
|
||||||
|
ID: job.ID,
|
||||||
|
Type: job.Type,
|
||||||
|
Status: job.Status,
|
||||||
|
Title: job.Title,
|
||||||
|
InputFile: job.InputFile,
|
||||||
|
OutputFile: job.OutputFile,
|
||||||
|
LogPath: job.LogPath,
|
||||||
|
Config: job.Config,
|
||||||
|
CreatedAt: job.CreatedAt,
|
||||||
|
StartedAt: job.StartedAt,
|
||||||
|
CompletedAt: job.CompletedAt,
|
||||||
|
Error: job.Error,
|
||||||
|
FFmpegCmd: cmdStr,
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check for duplicates
|
||||||
|
for _, existing := range s.historyEntries {
|
||||||
|
if existing.ID == entry.ID {
|
||||||
|
return // Already in history
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Prepend to history (newest first)
|
||||||
|
s.historyEntries = append([]HistoryEntry{entry}, s.historyEntries...)
|
||||||
|
|
||||||
|
// Save to disk
|
||||||
|
cfg := historyConfig{Entries: s.historyEntries}
|
||||||
|
if err := saveHistoryConfig(cfg); err != nil {
|
||||||
|
logging.Debug(logging.CatSystem, "failed to save history: %v", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func (s *appState) stopPreview() {
|
func (s *appState) stopPreview() {
|
||||||
if s.anim != nil {
|
if s.anim != nil {
|
||||||
s.anim.Stop()
|
s.anim.Stop()
|
||||||
|
|
@ -4591,6 +4712,15 @@ func runGUI() {
|
||||||
logging.Debug(logging.CatSystem, "failed to load persisted convert config: %v", err)
|
logging.Debug(logging.CatSystem, "failed to load persisted convert config: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Initialize conversion history
|
||||||
|
if historyCfg, err := loadHistoryConfig(); err == nil {
|
||||||
|
state.historyEntries = historyCfg.Entries
|
||||||
|
} else {
|
||||||
|
state.historyEntries = []HistoryEntry{}
|
||||||
|
logging.Debug(logging.CatSystem, "failed to load history config: %v", err)
|
||||||
|
}
|
||||||
|
state.sidebarVisible = false
|
||||||
|
|
||||||
// Initialize conversion stats bar
|
// Initialize conversion stats bar
|
||||||
state.statsBar = ui.NewConversionStatsBar(func() {
|
state.statsBar = ui.NewConversionStatsBar(func() {
|
||||||
// Clicking the stats bar opens the queue view
|
// Clicking the stats bar opens the queue view
|
||||||
|
|
@ -4605,6 +4735,16 @@ func runGUI() {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
app.Driver().DoFromGoroutine(func() {
|
app.Driver().DoFromGoroutine(func() {
|
||||||
|
// Add completed jobs to history
|
||||||
|
jobs := state.jobQueue.List()
|
||||||
|
for _, job := range jobs {
|
||||||
|
if job.Status == queue.JobStatusCompleted ||
|
||||||
|
job.Status == queue.JobStatusFailed ||
|
||||||
|
job.Status == queue.JobStatusCancelled {
|
||||||
|
state.addToHistory(job)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
state.updateStatsBar()
|
state.updateStatsBar()
|
||||||
state.updateQueueButtonLabel()
|
state.updateQueueButtonLabel()
|
||||||
if state.active == "queue" {
|
if state.active == "queue" {
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue
Block a user