mirror of
https://github.com/yusing/godoxy.git
synced 2026-03-22 17:19:06 +01:00
This is a large-scale refactoring across the codebase that replaces the custom `gperr.Error` type with Go's standard `error` interface. The changes include: - Replacing `gperr.Error` return types with `error` in function signatures - Using `errors.New()` and `fmt.Errorf()` instead of `gperr.New()` and `gperr.Errorf()` - Using `%w` format verb for error wrapping instead of `.With()` method - Replacing `gperr.Subject()` calls with `gperr.PrependSubject()` - Converting error logging from `gperr.Log*()` functions to zerolog's `.Err().Msg()` pattern - Update NewLogger to handle multiline error message - Updating `goutils` submodule to latest commit This refactoring aligns with Go idioms and removes the dependency on custom error handling abstractions in favor of standard library patterns.
99 lines
2.3 KiB
Go
99 lines
2.3 KiB
Go
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"
|
|
"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 <statusCode>.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 events.ActionFileWritten:
|
|
fileContentMap.Delete(filename)
|
|
loadContent()
|
|
case events.ActionFileDeleted:
|
|
fileContentMap.Delete(filename)
|
|
log.Warn().Msgf("error page resource %s deleted", filename)
|
|
case events.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")
|
|
}
|
|
}
|
|
}
|