royalcat refactoring

This commit is contained in:
royalcat 2023-10-08 19:46:03 +03:00
parent 1da835cea6
commit b245c9f451
81 changed files with 1476 additions and 1580 deletions

127
src/http/api.go Normal file
View file

@ -0,0 +1,127 @@
package http
import (
"bytes"
"io"
"math"
"net/http"
"os"
"sort"
"git.kmsign.ru/royalcat/tstor/src/torrent"
"github.com/anacrolix/missinggo/v2/filecache"
"github.com/gin-gonic/gin"
)
var apiStatusHandler = func(fc *filecache.Cache, ss *torrent.Stats) gin.HandlerFunc {
return func(ctx *gin.Context) {
stat := gin.H{
"torrentStats": ss.GlobalStats(),
}
if fc != nil {
stat["cacheItems"] = fc.Info().NumItems
stat["cacheFilled"] = fc.Info().Filled / 1024 / 1024
stat["cacheCapacity"] = fc.Info().Capacity / 1024 / 1024
}
// TODO move to a struct
ctx.JSON(http.StatusOK, stat)
}
}
var apiServersHandler = func(ss []*torrent.Server) gin.HandlerFunc {
return func(ctx *gin.Context) {
var infos []*torrent.ServerInfo
for _, s := range ss {
infos = append(infos, s.Info())
}
ctx.JSON(http.StatusOK, infos)
}
}
var apiRoutesHandler = func(ss *torrent.Stats) gin.HandlerFunc {
return func(ctx *gin.Context) {
s := ss.RoutesStats()
sort.Sort(torrent.ByName(s))
ctx.JSON(http.StatusOK, s)
}
}
var apiAddTorrentHandler = func(s *torrent.Service) gin.HandlerFunc {
return func(ctx *gin.Context) {
route := ctx.Param("route")
var json RouteAdd
if err := ctx.ShouldBindJSON(&json); err != nil {
ctx.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
return
}
if err := s.AddMagnet(route, json.Magnet); err != nil {
ctx.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
return
}
ctx.JSON(http.StatusOK, nil)
}
}
var apiDelTorrentHandler = func(s *torrent.Service) gin.HandlerFunc {
return func(ctx *gin.Context) {
route := ctx.Param("route")
hash := ctx.Param("torrent_hash")
if err := s.RemoveFromHash(route, hash); err != nil {
ctx.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
return
}
ctx.JSON(http.StatusOK, nil)
}
}
var apiLogHandler = func(path string) gin.HandlerFunc {
return func(ctx *gin.Context) {
f, err := os.Open(path)
if err != nil {
ctx.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
return
}
fi, err := f.Stat()
if err != nil {
ctx.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
return
}
max := math.Max(float64(-fi.Size()), -1024*8*8)
_, err = f.Seek(int64(max), io.SeekEnd)
if err != nil {
ctx.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
return
}
var b bytes.Buffer
ctx.Stream(func(w io.Writer) bool {
_, err := b.ReadFrom(f)
if err != nil {
ctx.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
return false
}
_, err = b.WriteTo(w)
if err != nil {
ctx.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
return false
}
return true
})
if err := f.Close(); err != nil {
ctx.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
return
}
}
}

92
src/http/http.go Normal file
View file

@ -0,0 +1,92 @@
package http
import (
"fmt"
"net/http"
"git.kmsign.ru/royalcat/tstor"
"git.kmsign.ru/royalcat/tstor/src/config"
"git.kmsign.ru/royalcat/tstor/src/torrent"
"github.com/anacrolix/missinggo/v2/filecache"
"github.com/gin-gonic/gin"
"github.com/rs/zerolog/log"
"github.com/shurcooL/httpfs/html/vfstemplate"
)
func New(fc *filecache.Cache, ss *torrent.Stats, s *torrent.Service, ch *config.Config, tss []*torrent.Server, fs http.FileSystem, logPath string, cfg *config.Config) error {
gin.SetMode(gin.ReleaseMode)
r := gin.New()
r.Use(gin.Recovery())
r.Use(gin.ErrorLogger())
r.Use(Logger())
r.GET("/assets/*filepath", func(c *gin.Context) {
c.FileFromFS(c.Request.URL.Path, http.FS(tstor.Assets))
})
if cfg.Mounts.HttpFs.Enabled {
log.Info().Str("host", fmt.Sprintf("0.0.0.0:%d/fs", cfg.Mounts.HttpFs.Port)).Msg("starting HTTPFS")
r.GET("/fs/*filepath", func(c *gin.Context) {
path := c.Param("filepath")
c.FileFromFS(path, fs)
})
}
t, err := vfstemplate.ParseGlob(http.FS(tstor.Templates), nil, "/templates/*")
if err != nil {
return fmt.Errorf("error parsing html: %w", err)
}
r.SetHTMLTemplate(t)
r.GET("/", indexHandler)
r.GET("/routes", routesHandler(ss))
r.GET("/logs", logsHandler)
r.GET("/servers", serversFoldersHandler())
api := r.Group("/api")
{
api.GET("/log", apiLogHandler(logPath))
api.GET("/status", apiStatusHandler(fc, ss))
api.GET("/servers", apiServersHandler(tss))
api.GET("/routes", apiRoutesHandler(ss))
api.POST("/routes/:route/torrent", apiAddTorrentHandler(s))
api.DELETE("/routes/:route/torrent/:torrent_hash", apiDelTorrentHandler(s))
}
log.Info().Str("host", fmt.Sprintf("%s:%d", cfg.WebUi.IP, cfg.WebUi.Port)).Msg("starting webserver")
if err := r.Run(fmt.Sprintf("%s:%d", cfg.WebUi.IP, cfg.WebUi.Port)); err != nil {
return fmt.Errorf("error initializing server: %w", err)
}
return nil
}
func Logger() gin.HandlerFunc {
l := log.Logger.With().Str("component", "http").Logger()
return func(c *gin.Context) {
path := c.Request.URL.Path
raw := c.Request.URL.RawQuery
c.Next()
if raw != "" {
path = path + "?" + raw
}
msg := c.Errors.String()
if msg == "" {
msg = "Request"
}
s := c.Writer.Status()
switch {
case s >= 400 && s < 500:
l.Warn().Str("path", path).Int("status", s).Msg(msg)
case s >= 500:
l.Error().Str("path", path).Int("status", s).Msg(msg)
default:
l.Debug().Str("path", path).Int("status", s).Msg(msg)
}
}
}

9
src/http/model.go Normal file
View file

@ -0,0 +1,9 @@
package http
type RouteAdd struct {
Magnet string `json:"magnet" binding:"required"`
}
type Error struct {
Error string `json:"error"`
}

28
src/http/web.go Normal file
View file

@ -0,0 +1,28 @@
package http
import (
"net/http"
"git.kmsign.ru/royalcat/tstor/src/torrent"
"github.com/gin-gonic/gin"
)
var indexHandler = func(c *gin.Context) {
c.HTML(http.StatusOK, "index.html", nil)
}
var routesHandler = func(ss *torrent.Stats) gin.HandlerFunc {
return func(c *gin.Context) {
c.HTML(http.StatusOK, "routes.html", ss.RoutesStats())
}
}
var logsHandler = func(c *gin.Context) {
c.HTML(http.StatusOK, "logs.html", nil)
}
var serversFoldersHandler = func() gin.HandlerFunc {
return func(c *gin.Context) {
c.HTML(http.StatusOK, "servers.html", nil)
}
}