diff --git a/cmd/cache.go b/cmd/cache.go index 2abd50a..813b846 100644 --- a/cmd/cache.go +++ b/cmd/cache.go @@ -137,3 +137,20 @@ func serveCacheClear(args []string, cache *fileCache, formats *types.Types) http w.Write([]byte("Ok")) } } + +func registerCacheHandlers(mux *httprouter.Router, args []string, cache *fileCache, formats *types.Types) { + skipIndex := false + + if CacheFile != "" { + err := cache.Import(CacheFile) + if err == nil { + skipIndex = true + } + } + + if !skipIndex { + cache.generate(args, formats) + } + + mux.GET("/clear_cache", serveCacheClear(args, cache, formats)) +} diff --git a/cmd/info.go b/cmd/info.go index b77bf75..1182dd6 100644 --- a/cmd/info.go +++ b/cmd/info.go @@ -271,3 +271,22 @@ func serveEnabledMimeTypes(formats *types.Types) httprouter.Handle { } } } + +func registerInfoHandlers(mux *httprouter.Router, args []string, cache *fileCache, formats *types.Types) { + if Cache { + mux.GET("/html/", serveIndexHtml(args, cache, false)) + if PageLength != 0 { + mux.GET("/html/:page", serveIndexHtml(args, cache, true)) + } + + mux.GET("/json", serveIndexJson(args, cache)) + if PageLength != 0 { + mux.GET("/json/:page", serveIndexJson(args, cache)) + } + } + + mux.GET("/available_extensions", serveAvailableExtensions()) + mux.GET("/enabled_extensions", serveEnabledExtensions(formats)) + mux.GET("/available_mime_types", serveAvailableMimeTypes()) + mux.GET("/enabled_mime_types", serveEnabledMimeTypes(formats)) +} diff --git a/cmd/profile.go b/cmd/profile.go new file mode 100644 index 0000000..a1e9907 --- /dev/null +++ b/cmd/profile.go @@ -0,0 +1,19 @@ +/* +Copyright © 2023 Seednode +*/ + +package cmd + +import ( + "net/http/pprof" + + "github.com/julienschmidt/httprouter" +) + +func registerProfileHandlers(mux *httprouter.Router) { + mux.HandlerFunc("GET", "/debug/pprof/", pprof.Index) + mux.HandlerFunc("GET", "/debug/pprof/cmdline", pprof.Cmdline) + mux.HandlerFunc("GET", "/debug/pprof/profile", pprof.Profile) + mux.HandlerFunc("GET", "/debug/pprof/symbol", pprof.Symbol) + mux.HandlerFunc("GET", "/debug/pprof/trace", pprof.Trace) +} diff --git a/cmd/root.go b/cmd/root.go index 07ec4dc..4737cea 100644 --- a/cmd/root.go +++ b/cmd/root.go @@ -12,7 +12,7 @@ import ( ) const ( - ReleaseVersion string = "0.77.0" + ReleaseVersion string = "0.78.0" ) var ( diff --git a/cmd/stats.go b/cmd/stats.go index 1a28a5b..e1a32ea 100644 --- a/cmd/stats.go +++ b/cmd/stats.go @@ -10,17 +10,19 @@ import ( "fmt" "net/http" "os" + "os/signal" "sort" "strconv" "strings" "sync" + "syscall" "time" "github.com/julienschmidt/httprouter" "github.com/klauspost/compress/zstd" ) -type ServeStats struct { +type serveStats struct { mutex sync.RWMutex list []string count map[string]uint32 @@ -28,85 +30,90 @@ type ServeStats struct { times map[string][]string } -type exportedServeStats struct { +type publicServeStats struct { List []string Count map[string]uint32 Size map[string]string Times map[string][]string } -func (s *ServeStats) incrementCounter(file string, timestamp time.Time, filesize string) { - s.mutex.Lock() - - s.count[file]++ - - s.times[file] = append(s.times[file], timestamp.Format(logDate)) - - _, exists := s.size[file] - if !exists { - s.size[file] = filesize - } - - if !contains(s.list, file) { - s.list = append(s.list, file) - } - - s.mutex.Unlock() +type timesServed struct { + File string + Served uint32 + Size string + Times []string } -func (s *ServeStats) toExported() *exportedServeStats { - stats := &exportedServeStats{ - List: make([]string, len(s.list)), - Count: make(map[string]uint32), - Size: make(map[string]string), - Times: make(map[string][]string), +func (stats *serveStats) incrementCounter(file string, timestamp time.Time, filesize string) { + stats.mutex.Lock() + + stats.count[file]++ + + stats.times[file] = append(stats.times[file], timestamp.Format(logDate)) + + _, exists := stats.size[file] + if !exists { + stats.size[file] = filesize } - s.mutex.RLock() + if !contains(stats.list, file) { + stats.list = append(stats.list, file) + } - copy(stats.List, s.list) + stats.mutex.Unlock() +} - for k, v := range s.count { +func (stats *serveStats) Import(source *publicServeStats) { + stats.mutex.Lock() + + copy(stats.list, source.List) + + for k, v := range source.Count { + stats.count[k] = v + } + + for k, v := range source.Size { + stats.size[k] = v + } + + for k, v := range source.Times { + stats.times[k] = v + } + + stats.mutex.Unlock() +} + +func (source *serveStats) Export() *publicServeStats { + stats := &publicServeStats{ + List: make([]string, len(source.list)), + Count: make(map[string]uint32, len(source.count)), + Size: make(map[string]string, len(source.size)), + Times: make(map[string][]string, len(source.times)), + } + + source.mutex.RLock() + + copy(stats.List, source.list) + + for k, v := range source.count { stats.Count[k] = v } - for k, v := range s.size { + for k, v := range source.size { stats.Size[k] = v } - for k, v := range s.times { + for k, v := range source.times { stats.Times[k] = v } - s.mutex.RUnlock() + source.mutex.RUnlock() return stats } -func (s *ServeStats) toImported(stats *exportedServeStats) { - s.mutex.Lock() - - s.list = make([]string, len(stats.List)) - - copy(s.list, stats.List) - - for k, v := range stats.Count { - s.count[k] = v - } - - for k, v := range stats.Size { - s.size[k] = v - } - - for k, v := range stats.Times { - s.times[k] = v - } - - s.mutex.Unlock() -} - -func (s *ServeStats) ListFiles(page int) ([]byte, error) { - stats := s.toExported() +func (source *serveStats) listFiles(page int) ([]byte, error) { + stats := source.Export() sort.SliceStable(stats.List, func(p, q int) bool { return strings.ToLower(stats.List[p]) < strings.ToLower(stats.List[q]) @@ -116,7 +123,7 @@ func (s *ServeStats) ListFiles(page int) ([]byte, error) { if page == -1 { startIndex = 0 - stopIndex = len(stats.List) - 1 + stopIndex = len(stats.List) } else { startIndex = ((page - 1) * int(PageLength)) stopIndex = (startIndex + int(PageLength)) @@ -126,11 +133,11 @@ func (s *ServeStats) ListFiles(page int) ([]byte, error) { return []byte("{}"), nil } - if stopIndex > len(stats.List)-1 { - stopIndex = len(stats.List) - 1 + if stopIndex > len(stats.List) { + stopIndex = len(stats.List) } - a := make([]timesServed, stopIndex-startIndex) + a := make([]timesServed, (stopIndex - startIndex)) for k, v := range stats.List[startIndex:stopIndex] { a[k] = timesServed{v, stats.Count[v], stats.Size[v], stats.Times[v]} @@ -144,7 +151,7 @@ func (s *ServeStats) ListFiles(page int) ([]byte, error) { return r, nil } -func (s *ServeStats) Export(path string) error { +func (stats *serveStats) ExportFile(path string) error { file, err := os.OpenFile(path, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0600) if err != nil { return err @@ -159,9 +166,7 @@ func (s *ServeStats) Export(path string) error { enc := gob.NewEncoder(z) - stats := s.toExported() - - err = enc.Encode(&stats) + err = enc.Encode(stats.Export()) if err != nil { return err } @@ -169,7 +174,7 @@ func (s *ServeStats) Export(path string) error { return nil } -func (s *ServeStats) Import(path string) error { +func (stats *serveStats) ImportFile(path string) error { file, err := os.OpenFile(path, os.O_RDONLY, 0600) if err != nil { return err @@ -184,33 +189,25 @@ func (s *ServeStats) Import(path string) error { dec := gob.NewDecoder(z) - stats := &exportedServeStats{ + source := &publicServeStats{ List: []string{}, Count: make(map[string]uint32), Size: make(map[string]string), Times: make(map[string][]string), } - err = dec.Decode(stats) + err = dec.Decode(source) if err != nil { return err } - s.toImported(stats) + stats.Import(source) return nil } -type timesServed struct { - File string - Served uint32 - Size string - Times []string -} - -func serveStats(args []string, stats *ServeStats) httprouter.Handle { +func serveStatsPage(args []string, stats *serveStats) httprouter.Handle { return func(w http.ResponseWriter, r *http.Request, p httprouter.Params) { - startTime := time.Now() page, err := strconv.Atoi(p.ByName("page")) @@ -218,7 +215,7 @@ func serveStats(args []string, stats *ServeStats) httprouter.Handle { page = -1 } - response, err := stats.ListFiles(page) + response, err := stats.listFiles(page) if err != nil { fmt.Println(err) @@ -241,7 +238,29 @@ func serveStats(args []string, stats *ServeStats) httprouter.Handle { } if StatisticsFile != "" { - stats.Export(StatisticsFile) + stats.ExportFile(StatisticsFile) + } + } +} + +func registerStatsHandlers(mux *httprouter.Router, args []string, stats *serveStats) { + if StatisticsFile != "" { + stats.ImportFile(StatisticsFile) + + gracefulShutdown := make(chan os.Signal, 1) + signal.Notify(gracefulShutdown, syscall.SIGINT, syscall.SIGTERM) + + go func() { + <-gracefulShutdown + + stats.ExportFile(StatisticsFile) + + os.Exit(0) + }() + + mux.GET("/stats", serveStatsPage(args, stats)) + if PageLength != 0 { + mux.GET("/stats/:page", serveStatsPage(args, stats)) } } } diff --git a/cmd/web.go b/cmd/web.go index cbcd710..c9bc3b5 100644 --- a/cmd/web.go +++ b/cmd/web.go @@ -12,18 +12,14 @@ import ( "net" "net/http" "os" - "os/signal" "path/filepath" "regexp" "runtime" "strconv" "strings" "sync" - "syscall" "time" - "net/http/pprof" - "github.com/julienschmidt/httprouter" "github.com/yosssi/gohtml" "seedno.de/seednode/roulette/types" @@ -42,7 +38,7 @@ const ( timeout time.Duration = 10 * time.Second ) -func serveStaticFile(paths []string, stats *ServeStats, cache *fileCache) httprouter.Handle { +func serveStaticFile(paths []string, stats *serveStats, cache *fileCache) httprouter.Handle { return func(w http.ResponseWriter, r *http.Request, p httprouter.Params) { path := strings.TrimPrefix(r.URL.Path, sourcePrefix) @@ -314,8 +310,6 @@ func ServePage(args []string) error { return errors.New("invalid bind address provided") } - mux := httprouter.New() - formats := &types.Types{ Extensions: make(map[string]string), MimeTypes: make(map[string]types.Type), @@ -352,10 +346,6 @@ func ServePage(args []string) error { return ErrNoMediaFound } - if Russian { - fmt.Printf("WARNING! Files *will* be deleted after serving!\n\n") - } - cache := &fileCache{ mutex: sync.RWMutex{}, list: []string{}, @@ -366,6 +356,8 @@ func ServePage(args []string) error { alphanumeric: regexp.MustCompile(`^[A-z0-9]*$`), } + mux := httprouter.New() + srv := &http.Server{ Addr: net.JoinHostPort(Bind, strconv.Itoa(int(Port))), Handler: mux, @@ -374,7 +366,7 @@ func ServePage(args []string) error { WriteTimeout: 5 * time.Minute, } - stats := &ServeStats{ + stats := &serveStats{ mutex: sync.RWMutex{}, list: []string{}, count: make(map[string]uint32), @@ -397,69 +389,23 @@ func ServePage(args []string) error { mux.GET("/version", serveVersion()) if Cache { - skipIndex := false - - if CacheFile != "" { - err := cache.Import(CacheFile) - if err == nil { - skipIndex = true - } - } - - if !skipIndex { - cache.generate(args, formats) - } - - mux.GET("/clear_cache", serveCacheClear(args, cache, formats)) + registerCacheHandlers(mux, args, cache, formats) } if Info { - if Cache { - mux.GET("/html/", serveIndexHtml(args, cache, false)) - if PageLength != 0 { - mux.GET("/html/:page", serveIndexHtml(args, cache, true)) - } - - mux.GET("/json", serveIndexJson(args, cache)) - if PageLength != 0 { - mux.GET("/json/:page", serveIndexJson(args, cache)) - } - } - - mux.GET("/available_extensions", serveAvailableExtensions()) - mux.GET("/enabled_extensions", serveEnabledExtensions(formats)) - mux.GET("/available_mime_types", serveAvailableMimeTypes()) - mux.GET("/enabled_mime_types", serveEnabledMimeTypes(formats)) + registerInfoHandlers(mux, args, cache, formats) } if Profile { - mux.HandlerFunc("GET", "/debug/pprof/", pprof.Index) - mux.HandlerFunc("GET", "/debug/pprof/cmdline", pprof.Cmdline) - mux.HandlerFunc("GET", "/debug/pprof/profile", pprof.Profile) - mux.HandlerFunc("GET", "/debug/pprof/symbol", pprof.Symbol) - mux.HandlerFunc("GET", "/debug/pprof/trace", pprof.Trace) + registerProfileHandlers(mux) + } + + if Russian { + fmt.Printf("WARNING! Files *will* be deleted after serving!\n\n") } if Statistics { - if StatisticsFile != "" { - stats.Import(StatisticsFile) - - gracefulShutdown := make(chan os.Signal, 1) - signal.Notify(gracefulShutdown, syscall.SIGINT, syscall.SIGTERM) - - go func() { - <-gracefulShutdown - - stats.Export(StatisticsFile) - - os.Exit(0) - }() - } - - mux.GET("/stats", serveStats(args, stats)) - if PageLength != 0 { - mux.GET("/stats/:page", serveStats(args, stats)) - } + registerStatsHandlers(mux, args, stats) } err = srv.ListenAndServe()