189 lines
3.5 KiB
Go
189 lines
3.5 KiB
Go
package main
|
|
|
|
import (
|
|
"encoding/json"
|
|
"fmt"
|
|
"log"
|
|
"os"
|
|
"path/filepath"
|
|
"sort"
|
|
"strings"
|
|
"sync"
|
|
"time"
|
|
)
|
|
|
|
var (
|
|
photoMu sync.Mutex
|
|
photoFiles []string
|
|
photoIdx int
|
|
photoDir string
|
|
photoStop chan struct{}
|
|
photoDone chan struct{}
|
|
photoCacheMu sync.Mutex
|
|
photoCacheMap map[string]string
|
|
photoCacheDir string
|
|
)
|
|
|
|
func scanPhotoDir(dir string) []string {
|
|
entries, err := os.ReadDir(dir)
|
|
if err != nil {
|
|
return nil
|
|
}
|
|
var files []string
|
|
for _, e := range entries {
|
|
if e.IsDir() {
|
|
continue
|
|
}
|
|
ext := strings.ToLower(filepath.Ext(e.Name()))
|
|
switch ext {
|
|
case ".jpg", ".jpeg", ".png", ".bmp", ".webp", ".gif":
|
|
files = append(files, e.Name())
|
|
}
|
|
}
|
|
sort.Strings(files)
|
|
return files
|
|
}
|
|
|
|
func preCachePhotos(dir string, files []string) {
|
|
cache := make(map[string]string, len(files))
|
|
for _, name := range files {
|
|
uri := imageToDataURI(filepath.Join(dir, name))
|
|
if uri != "" {
|
|
cache[name] = uri
|
|
}
|
|
}
|
|
photoCacheMu.Lock()
|
|
photoCacheMap = cache
|
|
photoCacheDir = dir
|
|
photoCacheMu.Unlock()
|
|
log.Printf("相册: 预缓存 %d/%d 张", len(cache), len(files))
|
|
}
|
|
|
|
func getCachedPhotoURI(dir, name string) string {
|
|
photoCacheMu.Lock()
|
|
if photoCacheMap != nil && photoCacheDir == dir {
|
|
if uri, ok := photoCacheMap[name]; ok {
|
|
photoCacheMu.Unlock()
|
|
return uri
|
|
}
|
|
}
|
|
photoCacheMu.Unlock()
|
|
return imageToDataURI(filepath.Join(dir, name))
|
|
}
|
|
|
|
func pushCurrentPhoto(interval int) {
|
|
photoMu.Lock()
|
|
files := photoFiles
|
|
idx := photoIdx
|
|
dir := photoDir
|
|
photoMu.Unlock()
|
|
|
|
if len(files) == 0 || dir == "" {
|
|
evalJS(`if(window.updatePhotoFromGo) updatePhotoFromGo(null)`)
|
|
return
|
|
}
|
|
if idx >= len(files) {
|
|
idx = 0
|
|
}
|
|
|
|
src := getCachedPhotoURI(dir, files[idx])
|
|
if src == "" {
|
|
return
|
|
}
|
|
|
|
data, _ := json.Marshal(map[string]interface{}{
|
|
"src": src,
|
|
"counter": fmt.Sprintf("%d / %d", idx+1, len(files)),
|
|
"interval": interval,
|
|
})
|
|
evalJS(fmt.Sprintf(`if(window.updatePhotoFromGo) updatePhotoFromGo(%s)`, string(data)))
|
|
}
|
|
|
|
func startPhotoLoop() {
|
|
cfg := loadConfig()
|
|
if cfg.PhotoDir == "" || cfg.HidePhoto {
|
|
return
|
|
}
|
|
|
|
interval := cfg.PhotoInterval
|
|
if interval <= 0 {
|
|
interval = 15
|
|
}
|
|
|
|
files := scanPhotoDir(cfg.PhotoDir)
|
|
if len(files) == 0 {
|
|
log.Println("相册: 目录为空或无图片")
|
|
return
|
|
}
|
|
|
|
photoMu.Lock()
|
|
photoDir = cfg.PhotoDir
|
|
photoFiles = files
|
|
photoIdx = 0
|
|
stop := make(chan struct{})
|
|
done := make(chan struct{})
|
|
photoStop = stop
|
|
photoDone = done
|
|
photoMu.Unlock()
|
|
|
|
photoCacheMu.Lock()
|
|
photoCacheMap = nil
|
|
photoCacheDir = cfg.PhotoDir
|
|
photoCacheMu.Unlock()
|
|
|
|
log.Printf("相册: 共 %d 张, 间隔 %ds", len(files), interval)
|
|
go preCachePhotos(cfg.PhotoDir, files)
|
|
pushCurrentPhoto(interval)
|
|
|
|
go func() {
|
|
ticker := time.NewTicker(time.Duration(interval) * time.Second)
|
|
defer func() {
|
|
ticker.Stop()
|
|
close(done)
|
|
}()
|
|
for {
|
|
select {
|
|
case <-ticker.C:
|
|
photoMu.Lock()
|
|
if len(photoFiles) > 0 {
|
|
photoIdx = (photoIdx + 1) % len(photoFiles)
|
|
}
|
|
photoMu.Unlock()
|
|
pushCurrentPhoto(interval)
|
|
case <-stop:
|
|
return
|
|
}
|
|
}
|
|
}()
|
|
}
|
|
|
|
func stopPhotoLoop() {
|
|
photoMu.Lock()
|
|
stop := photoStop
|
|
done := photoDone
|
|
photoStop = nil
|
|
photoDone = nil
|
|
photoFiles = nil
|
|
photoIdx = 0
|
|
photoDir = ""
|
|
photoMu.Unlock()
|
|
|
|
photoCacheMu.Lock()
|
|
photoCacheMap = nil
|
|
photoCacheDir = ""
|
|
photoCacheMu.Unlock()
|
|
|
|
if stop != nil {
|
|
close(stop)
|
|
}
|
|
if done != nil {
|
|
<-done
|
|
}
|
|
}
|
|
|
|
func restartPhotoLoop() {
|
|
stopPhotoLoop()
|
|
evalJS(`if(window.updatePhotoFromGo) updatePhotoFromGo(null)`)
|
|
startPhotoLoop()
|
|
}
|