package errorpage import ( "fmt" "os" "path" "sync" "github.com/puzpuzpuz/xsync/v4" "github.com/rs/zerolog/log" "github.com/yusing/godoxy/internal/common" "github.com/yusing/godoxy/internal/watcher" watcherEvents "github.com/yusing/godoxy/internal/watcher/events" "github.com/yusing/goutils/fs" "github.com/yusing/goutils/task" ) const errPagesBasePath = common.ErrorPagesBasePath var ( setupOnce sync.Once dirWatcher watcher.Watcher fileContentMap = xsync.NewMap[string, []byte](xsync.WithGrowOnly()) ) func setup() { t := task.RootTask("error_page", false) dirWatcher = watcher.NewDirectoryWatcher(t, errPagesBasePath) loadContent() go watchDir() } func GetStaticFile(filename string) ([]byte, bool) { if common.IsTest { return nil, false } setupOnce.Do(setup) return fileContentMap.Load(filename) } // try .html -> 404.html -> not ok. func GetErrorPageByStatus(statusCode int) (content []byte, ok bool) { content, ok = GetStaticFile(fmt.Sprintf("%d.html", statusCode)) if !ok && statusCode != 404 { return fileContentMap.Load("404.html") } return content, ok } func loadContent() { files, err := fs.ListFiles(errPagesBasePath, 0) if err != nil { log.Err(err).Msg("failed to list error page resources") return } for _, file := range files { if _, ok := fileContentMap.Load(file); ok { continue } content, err := os.ReadFile(file) if err != nil { log.Warn().Err(err).Msgf("failed to read error page resource %s", file) continue } file = path.Base(file) log.Info().Msgf("error page resource %s loaded", file) fileContentMap.Store(file, content) } } func watchDir() { eventCh, errCh := dirWatcher.Events(task.RootContext()) for { select { case <-task.RootContextCanceled(): return case event, ok := <-eventCh: if !ok { return } filename := event.ActorName switch event.Action { case watcherEvents.ActionFileWritten: fileContentMap.Delete(filename) loadContent() case watcherEvents.ActionFileDeleted: fileContentMap.Delete(filename) log.Warn().Msgf("error page resource %s deleted", filename) case watcherEvents.ActionFileRenamed: log.Warn().Msgf("error page resource %s deleted", filename) fileContentMap.Delete(filename) loadContent() } case err := <-errCh: log.Err(err).Msg("error watching error page directory") } } }