summaryrefslogtreecommitdiff
path: root/internal/thumbnail/cache.go
blob: 2a1af402acde677ca6ef0308d9049ae6f4e9720c (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
package thumbnail

import (
	"encoding/json"
	"errors"
	"fmt"
	"os"
	"path/filepath"
	"sync"
	"time"
)

type entry struct {
	VideoPath string    `json:"video_path"`
	Thumbnail string    `json:"thumbnail"`
	ModTime   time.Time `json:"mod_time"`
	Timestamp time.Time `json:"timestamp"`
}

type Cache struct {
	entries map[string]entry
	mu      sync.RWMutex
	path    string
}

func NewCache(root string) *Cache {
	return newCache(root)
}

func newCache(root string) *Cache {
	path := filepath.Join(root, cacheFilename)
	return &Cache{
		entries: make(map[string]entry),
		path:    path,
	}
}

func (c *Cache) Load() error {
	c.mu.Lock()
	defer c.mu.Unlock()

	data, err := os.ReadFile(c.path)
	if err != nil {
		if errors.Is(err, os.ErrNotExist) {
			return nil
		}
		return fmt.Errorf("read cache file: %w", err)
	}

	var loaded []entry
	if err := json.Unmarshal(data, &loaded); err != nil {
		return fmt.Errorf("unmarshal cache: %w", err)
	}

	c.entries = make(map[string]entry, len(loaded))
	for _, e := range loaded {
		c.entries[e.VideoPath] = e
	}

	return nil
}

func (c *Cache) Lookup(videoPath string, modTime time.Time) (string, bool) {
	c.mu.RLock()
	defer c.mu.RUnlock()

	e, ok := c.entries[videoPath]
	if !ok {
		return "", false
	}

	if !e.ModTime.Equal(modTime) {
		return "", false
	}

	if _, err := os.Stat(e.Thumbnail); err != nil {
		return "", false
	}

	return e.Thumbnail, true
}

func (c *Cache) Store(videoPath string, modTime time.Time, thumbnailPath string) error {
	c.mu.Lock()
	defer c.mu.Unlock()

	c.entries[videoPath] = entry{
		VideoPath: videoPath,
		Thumbnail: thumbnailPath,
		ModTime:   modTime,
		Timestamp: time.Now(),
	}

	return c.flush()
}

func (c *Cache) Remove(videoPath string) error {
	c.mu.Lock()
	defer c.mu.Unlock()

	delete(c.entries, videoPath)
	return c.flush()
}

func (c *Cache) flush() error {
	loaded := make([]entry, 0, len(c.entries))
	for _, e := range c.entries {
		loaded = append(loaded, e)
	}

	data, err := json.MarshalIndent(loaded, "", "  ")
	if err != nil {
		return fmt.Errorf("marshal cache: %w", err)
	}

	if err := os.WriteFile(c.path, data, 0o644); err != nil {
		return fmt.Errorf("write cache file: %w", err)
	}

	return nil
}