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() }