update
This commit is contained in:
parent
5591f145a9
commit
d8ee8a3a24
166 changed files with 15431 additions and 889 deletions
File diff suppressed because it is too large
Load diff
47
src/delivery/graphql/model/entry.go
Normal file
47
src/delivery/graphql/model/entry.go
Normal file
|
@ -0,0 +1,47 @@
|
|||
package model
|
||||
|
||||
import (
|
||||
"git.kmsign.ru/royalcat/tstor/src/host/vfs"
|
||||
)
|
||||
|
||||
type FsElem interface {
|
||||
Name() string
|
||||
IsDir() bool
|
||||
}
|
||||
|
||||
func FillFsEntry(e FsElem, fs vfs.Filesystem, path string) FsEntry {
|
||||
switch e.(type) {
|
||||
case *vfs.ArchiveFS:
|
||||
e := e.(*vfs.ArchiveFS)
|
||||
return ArchiveFs{
|
||||
Name: e.Name(),
|
||||
Size: e.Size(),
|
||||
FS: e,
|
||||
}
|
||||
case *vfs.ResolverFS:
|
||||
e := e.(*vfs.ResolverFS)
|
||||
return ResolverFs{
|
||||
Name: e.Name(),
|
||||
FS: e,
|
||||
}
|
||||
case *vfs.TorrentFS:
|
||||
e := e.(*vfs.TorrentFS)
|
||||
return TorrentFs{
|
||||
Name: e.Name(),
|
||||
Torrent: MapTorrent(e.Torrent),
|
||||
FS: e,
|
||||
}
|
||||
default:
|
||||
if e.IsDir() {
|
||||
return SimpleDir{
|
||||
Name: e.Name(),
|
||||
FS: fs,
|
||||
Path: path,
|
||||
}
|
||||
} else {
|
||||
return SimpleFile{
|
||||
Name: e.Name(),
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,9 +1,18 @@
|
|||
package model
|
||||
|
||||
import "slices"
|
||||
import (
|
||||
"slices"
|
||||
"strings"
|
||||
)
|
||||
|
||||
type Filter[T any] interface {
|
||||
Include(v T) bool
|
||||
}
|
||||
|
||||
func (f *IntFilter) Include(v int64) bool {
|
||||
if f.Eq != nil {
|
||||
if f == nil {
|
||||
return true
|
||||
} else if f.Eq != nil {
|
||||
return v == *f.Eq
|
||||
} else if f.Gt != nil {
|
||||
return v > *f.Gt
|
||||
|
@ -19,3 +28,17 @@ func (f *IntFilter) Include(v int64) bool {
|
|||
|
||||
return true
|
||||
}
|
||||
|
||||
func (f *StringFilter) Include(v string) bool {
|
||||
if f == nil {
|
||||
return true
|
||||
} else if f.Eq != nil {
|
||||
return v == *f.Eq
|
||||
} else if f.Substr != nil {
|
||||
return strings.Contains(v, *f.Substr)
|
||||
} else if f.In != nil {
|
||||
return slices.Contains(f.In, v)
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
|
|
@ -6,11 +6,26 @@ import (
|
|||
"time"
|
||||
|
||||
"git.kmsign.ru/royalcat/tstor/src/host/controller"
|
||||
"git.kmsign.ru/royalcat/tstor/src/host/vfs"
|
||||
"github.com/anacrolix/torrent"
|
||||
)
|
||||
|
||||
type DirEntry interface {
|
||||
IsDirEntry()
|
||||
type Dir interface {
|
||||
IsFsEntry()
|
||||
IsDir()
|
||||
GetName() string
|
||||
GetEntries() []FsEntry
|
||||
}
|
||||
|
||||
type File interface {
|
||||
IsFsEntry()
|
||||
IsFile()
|
||||
GetName() string
|
||||
GetSize() int64
|
||||
}
|
||||
|
||||
type FsEntry interface {
|
||||
IsFsEntry()
|
||||
GetName() string
|
||||
}
|
||||
|
||||
|
@ -21,12 +36,26 @@ type Progress interface {
|
|||
}
|
||||
|
||||
type ArchiveFs struct {
|
||||
Name string `json:"name"`
|
||||
Size int64 `json:"size"`
|
||||
Name string `json:"name"`
|
||||
Entries []FsEntry `json:"entries"`
|
||||
Size int64 `json:"size"`
|
||||
FS *vfs.ArchiveFS `json:"-"`
|
||||
}
|
||||
|
||||
func (ArchiveFs) IsDirEntry() {}
|
||||
func (ArchiveFs) IsDir() {}
|
||||
func (this ArchiveFs) GetName() string { return this.Name }
|
||||
func (this ArchiveFs) GetEntries() []FsEntry {
|
||||
if this.Entries == nil {
|
||||
return nil
|
||||
}
|
||||
interfaceSlice := make([]FsEntry, 0, len(this.Entries))
|
||||
for _, concrete := range this.Entries {
|
||||
interfaceSlice = append(interfaceSlice, concrete)
|
||||
}
|
||||
return interfaceSlice
|
||||
}
|
||||
|
||||
func (ArchiveFs) IsFsEntry() {}
|
||||
|
||||
type BooleanFilter struct {
|
||||
Eq *bool `json:"eq,omitempty"`
|
||||
|
@ -45,25 +74,10 @@ type DateTimeFilter struct {
|
|||
Lte *time.Time `json:"lte,omitempty"`
|
||||
}
|
||||
|
||||
type Dir struct {
|
||||
Name string `json:"name"`
|
||||
}
|
||||
|
||||
func (Dir) IsDirEntry() {}
|
||||
func (this Dir) GetName() string { return this.Name }
|
||||
|
||||
type DownloadTorrentResponse struct {
|
||||
Task *Task `json:"task,omitempty"`
|
||||
}
|
||||
|
||||
type File struct {
|
||||
Name string `json:"name"`
|
||||
Size int64 `json:"size"`
|
||||
}
|
||||
|
||||
func (File) IsDirEntry() {}
|
||||
func (this File) GetName() string { return this.Name }
|
||||
|
||||
type IntFilter struct {
|
||||
Eq *int64 `json:"eq,omitempty"`
|
||||
Gt *int64 `json:"gt,omitempty"`
|
||||
|
@ -73,11 +87,6 @@ type IntFilter struct {
|
|||
In []int64 `json:"in,omitempty"`
|
||||
}
|
||||
|
||||
type ListDirResponse struct {
|
||||
Root DirEntry `json:"root"`
|
||||
Entries []DirEntry `json:"entries"`
|
||||
}
|
||||
|
||||
type Mutation struct {
|
||||
}
|
||||
|
||||
|
@ -90,17 +99,64 @@ type Query struct {
|
|||
}
|
||||
|
||||
type ResolverFs struct {
|
||||
Name string `json:"name"`
|
||||
Name string `json:"name"`
|
||||
Entries []FsEntry `json:"entries"`
|
||||
FS *vfs.ResolverFS `json:"-"`
|
||||
}
|
||||
|
||||
func (ResolverFs) IsDirEntry() {}
|
||||
func (ResolverFs) IsDir() {}
|
||||
func (this ResolverFs) GetName() string { return this.Name }
|
||||
func (this ResolverFs) GetEntries() []FsEntry {
|
||||
if this.Entries == nil {
|
||||
return nil
|
||||
}
|
||||
interfaceSlice := make([]FsEntry, 0, len(this.Entries))
|
||||
for _, concrete := range this.Entries {
|
||||
interfaceSlice = append(interfaceSlice, concrete)
|
||||
}
|
||||
return interfaceSlice
|
||||
}
|
||||
|
||||
func (ResolverFs) IsFsEntry() {}
|
||||
|
||||
type Schema struct {
|
||||
Query *Query `json:"query,omitempty"`
|
||||
Mutation *Mutation `json:"mutation,omitempty"`
|
||||
}
|
||||
|
||||
type SimpleDir struct {
|
||||
Name string `json:"name"`
|
||||
Entries []FsEntry `json:"entries"`
|
||||
FS vfs.Filesystem `json:"-"`
|
||||
Path string `json:"-"`
|
||||
}
|
||||
|
||||
func (SimpleDir) IsDir() {}
|
||||
func (this SimpleDir) GetName() string { return this.Name }
|
||||
func (this SimpleDir) GetEntries() []FsEntry {
|
||||
if this.Entries == nil {
|
||||
return nil
|
||||
}
|
||||
interfaceSlice := make([]FsEntry, 0, len(this.Entries))
|
||||
for _, concrete := range this.Entries {
|
||||
interfaceSlice = append(interfaceSlice, concrete)
|
||||
}
|
||||
return interfaceSlice
|
||||
}
|
||||
|
||||
func (SimpleDir) IsFsEntry() {}
|
||||
|
||||
type SimpleFile struct {
|
||||
Name string `json:"name"`
|
||||
Size int64 `json:"size"`
|
||||
}
|
||||
|
||||
func (SimpleFile) IsFile() {}
|
||||
func (this SimpleFile) GetName() string { return this.Name }
|
||||
func (this SimpleFile) GetSize() int64 { return this.Size }
|
||||
|
||||
func (SimpleFile) IsFsEntry() {}
|
||||
|
||||
type StringFilter struct {
|
||||
Eq *string `json:"eq,omitempty"`
|
||||
Substr *string `json:"substr,omitempty"`
|
||||
|
@ -127,12 +183,26 @@ type Torrent struct {
|
|||
}
|
||||
|
||||
type TorrentFs struct {
|
||||
Name string `json:"name"`
|
||||
Torrent *Torrent `json:"torrent"`
|
||||
Name string `json:"name"`
|
||||
Torrent *Torrent `json:"torrent"`
|
||||
Entries []FsEntry `json:"entries"`
|
||||
FS *vfs.TorrentFS `json:"-"`
|
||||
}
|
||||
|
||||
func (TorrentFs) IsDirEntry() {}
|
||||
func (TorrentFs) IsDir() {}
|
||||
func (this TorrentFs) GetName() string { return this.Name }
|
||||
func (this TorrentFs) GetEntries() []FsEntry {
|
||||
if this.Entries == nil {
|
||||
return nil
|
||||
}
|
||||
interfaceSlice := make([]FsEntry, 0, len(this.Entries))
|
||||
for _, concrete := range this.Entries {
|
||||
interfaceSlice = append(interfaceSlice, concrete)
|
||||
}
|
||||
return interfaceSlice
|
||||
}
|
||||
|
||||
func (TorrentFs) IsFsEntry() {}
|
||||
|
||||
type TorrentFile struct {
|
||||
Filename string `json:"filename"`
|
||||
|
@ -141,6 +211,18 @@ type TorrentFile struct {
|
|||
F *torrent.File `json:"-"`
|
||||
}
|
||||
|
||||
type TorrentFileEntry struct {
|
||||
Name string `json:"name"`
|
||||
Torrent *Torrent `json:"torrent"`
|
||||
Size int64 `json:"size"`
|
||||
}
|
||||
|
||||
func (TorrentFileEntry) IsFile() {}
|
||||
func (this TorrentFileEntry) GetName() string { return this.Name }
|
||||
func (this TorrentFileEntry) GetSize() int64 { return this.Size }
|
||||
|
||||
func (TorrentFileEntry) IsFsEntry() {}
|
||||
|
||||
type TorrentFilter struct {
|
||||
Everything *bool `json:"everything,omitempty"`
|
||||
Infohash *string `json:"infohash,omitempty"`
|
||||
|
@ -166,6 +248,7 @@ func (this TorrentProgress) GetCurrent() int64 { return this.Current }
|
|||
func (this TorrentProgress) GetTotal() int64 { return this.Total }
|
||||
|
||||
type TorrentsFilter struct {
|
||||
Infohash *StringFilter `json:"infohash,omitempty"`
|
||||
Name *StringFilter `json:"name,omitempty"`
|
||||
BytesCompleted *IntFilter `json:"bytesCompleted,omitempty"`
|
||||
BytesMissing *IntFilter `json:"bytesMissing,omitempty"`
|
||||
|
|
81
src/delivery/graphql/resolver/fs.resolvers.go
Normal file
81
src/delivery/graphql/resolver/fs.resolvers.go
Normal file
|
@ -0,0 +1,81 @@
|
|||
package resolver
|
||||
|
||||
// This file will be automatically regenerated based on the schema, any resolver implementations
|
||||
// will be copied through when generating and any unknown code will be moved to the end.
|
||||
// Code generated by github.com/99designs/gqlgen version v0.17.45
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
graph "git.kmsign.ru/royalcat/tstor/src/delivery/graphql"
|
||||
"git.kmsign.ru/royalcat/tstor/src/delivery/graphql/model"
|
||||
)
|
||||
|
||||
// Entries is the resolver for the entries field.
|
||||
func (r *archiveFSResolver) Entries(ctx context.Context, obj *model.ArchiveFs) ([]model.FsEntry, error) {
|
||||
entries, err := obj.FS.ReadDir(ctx, ".")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
out := []model.FsEntry{}
|
||||
for _, e := range entries {
|
||||
out = append(out, model.FillFsEntry(e, obj.FS, "."))
|
||||
}
|
||||
return out, nil
|
||||
}
|
||||
|
||||
// Entries is the resolver for the entries field.
|
||||
func (r *resolverFSResolver) Entries(ctx context.Context, obj *model.ResolverFs) ([]model.FsEntry, error) {
|
||||
entries, err := obj.FS.ReadDir(ctx, ".")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
out := []model.FsEntry{}
|
||||
for _, e := range entries {
|
||||
out = append(out, model.FillFsEntry(e, obj.FS, "."))
|
||||
}
|
||||
return out, nil
|
||||
}
|
||||
|
||||
// Entries is the resolver for the entries field.
|
||||
func (r *simpleDirResolver) Entries(ctx context.Context, obj *model.SimpleDir) ([]model.FsEntry, error) {
|
||||
entries, err := obj.FS.ReadDir(ctx, obj.Path)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
out := []model.FsEntry{}
|
||||
for _, e := range entries {
|
||||
out = append(out, model.FillFsEntry(e, obj.FS, obj.Path))
|
||||
}
|
||||
return out, nil
|
||||
}
|
||||
|
||||
// Entries is the resolver for the entries field.
|
||||
func (r *torrentFSResolver) Entries(ctx context.Context, obj *model.TorrentFs) ([]model.FsEntry, error) {
|
||||
entries, err := obj.FS.ReadDir(ctx, ".")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
out := []model.FsEntry{}
|
||||
for _, e := range entries {
|
||||
out = append(out, model.FillFsEntry(e, obj.FS, "."))
|
||||
}
|
||||
return out, nil
|
||||
}
|
||||
|
||||
// ArchiveFS returns graph.ArchiveFSResolver implementation.
|
||||
func (r *Resolver) ArchiveFS() graph.ArchiveFSResolver { return &archiveFSResolver{r} }
|
||||
|
||||
// ResolverFS returns graph.ResolverFSResolver implementation.
|
||||
func (r *Resolver) ResolverFS() graph.ResolverFSResolver { return &resolverFSResolver{r} }
|
||||
|
||||
// SimpleDir returns graph.SimpleDirResolver implementation.
|
||||
func (r *Resolver) SimpleDir() graph.SimpleDirResolver { return &simpleDirResolver{r} }
|
||||
|
||||
// TorrentFS returns graph.TorrentFSResolver implementation.
|
||||
func (r *Resolver) TorrentFS() graph.TorrentFSResolver { return &torrentFSResolver{r} }
|
||||
|
||||
type archiveFSResolver struct{ *Resolver }
|
||||
type resolverFSResolver struct{ *Resolver }
|
||||
type simpleDirResolver struct{ *Resolver }
|
||||
type torrentFSResolver struct{ *Resolver }
|
|
@ -2,7 +2,7 @@ package resolver
|
|||
|
||||
// This file will be automatically regenerated based on the schema, any resolver implementations
|
||||
// will be copied through when generating and any unknown code will be moved to the end.
|
||||
// Code generated by github.com/99designs/gqlgen version v0.17.43
|
||||
// Code generated by github.com/99designs/gqlgen version v0.17.45
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
|
|
@ -2,15 +2,16 @@ package resolver
|
|||
|
||||
// This file will be automatically regenerated based on the schema, any resolver implementations
|
||||
// will be copied through when generating and any unknown code will be moved to the end.
|
||||
// Code generated by github.com/99designs/gqlgen version v0.17.43
|
||||
// Code generated by github.com/99designs/gqlgen version v0.17.45
|
||||
|
||||
import (
|
||||
"context"
|
||||
"io/fs"
|
||||
"slices"
|
||||
"strings"
|
||||
|
||||
graph "git.kmsign.ru/royalcat/tstor/src/delivery/graphql"
|
||||
"git.kmsign.ru/royalcat/tstor/src/delivery/graphql/model"
|
||||
"git.kmsign.ru/royalcat/tstor/src/host/vfs"
|
||||
"git.kmsign.ru/royalcat/tstor/src/host/controller"
|
||||
)
|
||||
|
||||
// Torrents is the resolver for the torrents field.
|
||||
|
@ -40,6 +41,13 @@ func (r *queryResolver) Torrents(ctx context.Context, filter *model.TorrentsFilt
|
|||
)
|
||||
})
|
||||
}
|
||||
if filter.Infohash != nil {
|
||||
filterFuncs = append(filterFuncs, func(torrent *model.Torrent) bool {
|
||||
return filter.Infohash.Include(
|
||||
torrent.Infohash,
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
@ -62,78 +70,21 @@ func (r *queryResolver) Torrents(ctx context.Context, filter *model.TorrentsFilt
|
|||
tr = append(tr, d)
|
||||
}
|
||||
|
||||
slices.SortStableFunc(torrents, func(t1, t2 *controller.Torrent) int {
|
||||
return strings.Compare(t1.InfoHash(), t2.InfoHash())
|
||||
})
|
||||
|
||||
return tr, nil
|
||||
}
|
||||
|
||||
type dirEntry interface {
|
||||
Name() string
|
||||
IsDir() bool
|
||||
}
|
||||
|
||||
func fillDirEntry(e dirEntry) model.DirEntry {
|
||||
switch e.(type) {
|
||||
case *vfs.ArchiveFS:
|
||||
e := e.(*vfs.ArchiveFS)
|
||||
return model.ArchiveFs{
|
||||
Name: e.Name(),
|
||||
Size: e.Size(),
|
||||
}
|
||||
case *vfs.ResolverFS:
|
||||
e := e.(*vfs.ResolverFS)
|
||||
return model.ResolverFs{
|
||||
Name: e.Name(),
|
||||
}
|
||||
case *vfs.TorrentFs:
|
||||
e := e.(*vfs.TorrentFs)
|
||||
return model.TorrentFs{
|
||||
Name: e.Name(),
|
||||
Torrent: model.MapTorrent(e.Torrent),
|
||||
}
|
||||
default:
|
||||
if e.IsDir() {
|
||||
return model.Dir{
|
||||
Name: e.Name(),
|
||||
}
|
||||
}
|
||||
if de, ok := e.(fs.DirEntry); ok {
|
||||
info, _ := de.Info()
|
||||
return model.File{
|
||||
Name: e.Name(),
|
||||
Size: info.Size(),
|
||||
}
|
||||
}
|
||||
|
||||
if fe, ok := e.(fs.FileInfo); ok {
|
||||
return model.File{
|
||||
Name: fe.Name(),
|
||||
Size: fe.Size(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
panic("this dir entry is strange af")
|
||||
}
|
||||
|
||||
// FsListDir is the resolver for the fsListDir field.
|
||||
func (r *queryResolver) FsListDir(ctx context.Context, path string) (*model.ListDirResponse, error) {
|
||||
root, err := r.VFS.Stat(ctx, path)
|
||||
// FsEntry is the resolver for the fsEntry field.
|
||||
func (r *queryResolver) FsEntry(ctx context.Context, path string) (model.FsEntry, error) {
|
||||
entry, err := r.VFS.Stat(ctx, path)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
entries, err := r.VFS.ReadDir(ctx, path)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
out := []model.DirEntry{}
|
||||
for _, e := range entries {
|
||||
out = append(out, fillDirEntry(e))
|
||||
}
|
||||
|
||||
return &model.ListDirResponse{
|
||||
Root: fillDirEntry(root),
|
||||
Entries: out,
|
||||
}, nil
|
||||
return model.FillFsEntry(entry, r.VFS, path), nil
|
||||
}
|
||||
|
||||
// Query returns graph.QueryResolver implementation.
|
||||
|
|
|
@ -2,7 +2,7 @@ package resolver
|
|||
|
||||
// This file will be automatically regenerated based on the schema, any resolver implementations
|
||||
// will be copied through when generating and any unknown code will be moved to the end.
|
||||
// Code generated by github.com/99designs/gqlgen version v0.17.43
|
||||
// Code generated by github.com/99designs/gqlgen version v0.17.45
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
|
|
@ -2,7 +2,7 @@ package resolver
|
|||
|
||||
// This file will be automatically regenerated based on the schema, any resolver implementations
|
||||
// will be copied through when generating and any unknown code will be moved to the end.
|
||||
// Code generated by github.com/99designs/gqlgen version v0.17.43
|
||||
// Code generated by github.com/99designs/gqlgen version v0.17.45
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
|
|
@ -5,84 +5,84 @@ import (
|
|||
"log/slog"
|
||||
"net/http"
|
||||
|
||||
"git.kmsign.ru/royalcat/tstor"
|
||||
"git.kmsign.ru/royalcat/tstor/pkg/rlog"
|
||||
"git.kmsign.ru/royalcat/tstor/src/config"
|
||||
"git.kmsign.ru/royalcat/tstor/src/host/service"
|
||||
"git.kmsign.ru/royalcat/tstor/src/host/vfs"
|
||||
"github.com/anacrolix/missinggo/v2/filecache"
|
||||
"github.com/gin-contrib/pprof"
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/shurcooL/httpfs/html/vfstemplate"
|
||||
echopprof "github.com/labstack/echo-contrib/pprof"
|
||||
"github.com/labstack/echo/v4"
|
||||
"github.com/labstack/echo/v4/middleware"
|
||||
)
|
||||
|
||||
func New(fc *filecache.Cache, ss *service.Stats, s *service.Service, vfs vfs.Filesystem, logPath string, cfg *config.Settings) error {
|
||||
log := slog.With()
|
||||
|
||||
gin.SetMode(gin.ReleaseMode)
|
||||
r := gin.New()
|
||||
r.Use(gin.Recovery())
|
||||
r.Use(gin.ErrorLogger())
|
||||
r.Use(Logger())
|
||||
pprof.Register(r)
|
||||
r := echo.New()
|
||||
r.Use(
|
||||
middleware.Recover(),
|
||||
middleware.Gzip(),
|
||||
middleware.Decompress(),
|
||||
Logger(),
|
||||
)
|
||||
|
||||
r.GET("/assets/*filepath", func(c *gin.Context) {
|
||||
c.FileFromFS(c.Request.URL.Path, http.FS(tstor.Assets))
|
||||
})
|
||||
echopprof.Register(r)
|
||||
|
||||
t, err := vfstemplate.ParseGlob(http.FS(tstor.Templates), nil, "/templates/*")
|
||||
if err != nil {
|
||||
return fmt.Errorf("error parsing html: %w", err)
|
||||
}
|
||||
// r.GET("/assets/*filepath", func(c *echo.Context) {
|
||||
// c.FileFromFS(c.Request.URL.Path, http.FS(tstor.Assets))
|
||||
// })
|
||||
|
||||
r.SetHTMLTemplate(t)
|
||||
// t, err := vfstemplate.ParseGlob(http.FS(tstor.Templates), nil, "/templates/*")
|
||||
// if err != nil {
|
||||
// return fmt.Errorf("error parsing html: %w", err)
|
||||
// }
|
||||
|
||||
r.GET("/", indexHandler)
|
||||
// r.GET("/routes", routesHandler(ss))
|
||||
r.GET("/logs", logsHandler)
|
||||
r.GET("/servers", serversFoldersHandler())
|
||||
r.Any("/graphql", gin.WrapH(GraphQLHandler(s, vfs)))
|
||||
// r.SetHTMLTemplate(t)
|
||||
|
||||
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))
|
||||
}
|
||||
// r.GET("/", indexHandler)
|
||||
// // r.GET("/routes", routesHandler(ss))
|
||||
// r.GET("/logs", logsHandler)
|
||||
// r.GET("/servers", serversFoldersHandler())
|
||||
r.Any("/graphql", echo.WrapHandler((GraphQLHandler(s, vfs))))
|
||||
|
||||
// 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("starting webserver", "host", fmt.Sprintf("%s:%d", cfg.WebUi.IP, cfg.WebUi.Port))
|
||||
|
||||
if err := r.Run(fmt.Sprintf("%s:%d", cfg.WebUi.IP, cfg.WebUi.Port)); err != nil {
|
||||
return fmt.Errorf("error initializing server: %w", err)
|
||||
}
|
||||
// if err := r.Run(fmt.Sprintf("%s:%d", cfg.WebUi.IP, cfg.WebUi.Port)); err != nil {
|
||||
// return fmt.Errorf("error initializing server: %w", err)
|
||||
// }
|
||||
|
||||
go r.Start((fmt.Sprintf("%s:%d", cfg.WebUi.IP, cfg.WebUi.Port)))
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func Logger() gin.HandlerFunc {
|
||||
l := slog.With("component", "http")
|
||||
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(msg, "path", path, "status", s)
|
||||
case s >= 500:
|
||||
l.Error(msg, "path", path, "status", s)
|
||||
default:
|
||||
l.Debug(msg, "path", path, "status", s)
|
||||
}
|
||||
}
|
||||
func Logger() echo.MiddlewareFunc {
|
||||
l := rlog.Component("http")
|
||||
return middleware.BodyDumpWithConfig(middleware.BodyDumpConfig{
|
||||
Skipper: func(c echo.Context) bool {
|
||||
return c.Request().Method == http.MethodGet
|
||||
},
|
||||
Handler: func(c echo.Context, reqBody, resBody []byte) {
|
||||
log := l.With(
|
||||
slog.String("method", c.Request().Method),
|
||||
slog.String("uri", c.Request().RequestURI),
|
||||
)
|
||||
if c.Request().Header.Get("Content-Type") == "application/json" {
|
||||
log.Info(c.Request().Context(), "Request body", slog.String("body", string(reqBody)))
|
||||
}
|
||||
if c.Response().Header().Get("Content-Type") == "application/json" {
|
||||
log.Info(c.Request().Context(), "Response body", slog.String("body", string(resBody)))
|
||||
}
|
||||
},
|
||||
})
|
||||
}
|
||||
|
|
|
@ -1,8 +1,11 @@
|
|||
package delivery
|
||||
|
||||
import (
|
||||
"context"
|
||||
"log/slog"
|
||||
"net/http"
|
||||
|
||||
"git.kmsign.ru/royalcat/tstor/pkg/rlog"
|
||||
graph "git.kmsign.ru/royalcat/tstor/src/delivery/graphql"
|
||||
"git.kmsign.ru/royalcat/tstor/src/delivery/graphql/resolver"
|
||||
"git.kmsign.ru/royalcat/tstor/src/host/service"
|
||||
|
@ -27,6 +30,15 @@ func GraphQLHandler(service *service.Service, vfs vfs.Filesystem) http.Handler {
|
|||
),
|
||||
)
|
||||
|
||||
log := rlog.Component("graphql")
|
||||
|
||||
graphqlHandler.AroundResponses(func(ctx context.Context, next graphql.ResponseHandler) *graphql.Response {
|
||||
resp := next(ctx)
|
||||
responseJson, _ := resp.Data.MarshalJSON()
|
||||
log.Info(ctx, "response", slog.String("body", string(responseJson)))
|
||||
return resp
|
||||
})
|
||||
|
||||
graphqlHandler.AddTransport(&transport.POST{})
|
||||
graphqlHandler.AddTransport(&transport.Websocket{})
|
||||
graphqlHandler.AddTransport(&transport.SSE{})
|
||||
|
@ -39,6 +51,5 @@ func GraphQLHandler(service *service.Service, vfs vfs.Filesystem) http.Handler {
|
|||
return ctx.Field.Directives.ForName("link") != nil
|
||||
}),
|
||||
))
|
||||
|
||||
return graphqlHandler
|
||||
}
|
||||
|
|
|
@ -36,10 +36,12 @@ func (s *Service) Download(ctx context.Context, task *TorrentDownloadTask) error
|
|||
}
|
||||
|
||||
file.Download()
|
||||
return nil
|
||||
} else {
|
||||
for _, file := range t.Files() {
|
||||
file.Download()
|
||||
}
|
||||
}
|
||||
|
||||
t.DownloadAll()
|
||||
return nil
|
||||
}
|
||||
|
||||
|
@ -111,15 +113,19 @@ func (s *Service) DownloadProgress(ctx context.Context) (<-chan TorrentProgress,
|
|||
defer close(out)
|
||||
for _, t := range torrents {
|
||||
sub := t.Torrent().SubscribePieceStateChanges()
|
||||
go func() {
|
||||
for range sub.Values {
|
||||
go func(t *controller.Torrent) {
|
||||
for stateChange := range sub.Values {
|
||||
if !stateChange.Complete && !stateChange.Partial {
|
||||
continue
|
||||
}
|
||||
|
||||
out <- TorrentProgress{
|
||||
Torrent: t,
|
||||
Current: t.BytesCompleted(),
|
||||
Total: t.Length(),
|
||||
}
|
||||
}
|
||||
}()
|
||||
}(t)
|
||||
defer sub.Close()
|
||||
}
|
||||
|
||||
|
|
|
@ -342,7 +342,7 @@ func getFile[F File](m map[string]F, name string) (File, error) {
|
|||
|
||||
func listDirFromFiles[F File](m map[string]F, name string) ([]fs.DirEntry, error) {
|
||||
out := make([]fs.DirEntry, 0, len(m))
|
||||
name = AddTrailSlash(name)
|
||||
name = AddTrailSlash(path.Clean(name))
|
||||
for p, f := range m {
|
||||
if strings.HasPrefix(p, name) {
|
||||
parts := strings.Split(trimRelPath(p, name), Separator)
|
||||
|
|
|
@ -19,7 +19,7 @@ import (
|
|||
"golang.org/x/exp/maps"
|
||||
)
|
||||
|
||||
type TorrentFs struct {
|
||||
type TorrentFS struct {
|
||||
name string
|
||||
|
||||
mu sync.Mutex
|
||||
|
@ -32,64 +32,64 @@ type TorrentFs struct {
|
|||
resolver *resolver
|
||||
}
|
||||
|
||||
var _ Filesystem = (*TorrentFs)(nil)
|
||||
var _ Filesystem = (*TorrentFS)(nil)
|
||||
|
||||
func NewTorrentFs(name string, c *controller.Torrent) *TorrentFs {
|
||||
return &TorrentFs{
|
||||
func NewTorrentFs(name string, c *controller.Torrent) *TorrentFS {
|
||||
return &TorrentFS{
|
||||
name: name,
|
||||
Torrent: c,
|
||||
resolver: newResolver(ArchiveFactories),
|
||||
}
|
||||
}
|
||||
|
||||
var _ fs.DirEntry = (*TorrentFs)(nil)
|
||||
var _ fs.DirEntry = (*TorrentFS)(nil)
|
||||
|
||||
// Name implements fs.DirEntry.
|
||||
func (tfs *TorrentFs) Name() string {
|
||||
func (tfs *TorrentFS) Name() string {
|
||||
return tfs.name
|
||||
}
|
||||
|
||||
// Info implements fs.DirEntry.
|
||||
func (tfs *TorrentFs) Info() (fs.FileInfo, error) {
|
||||
func (tfs *TorrentFS) Info() (fs.FileInfo, error) {
|
||||
return tfs, nil
|
||||
}
|
||||
|
||||
// IsDir implements fs.DirEntry.
|
||||
func (tfs *TorrentFs) IsDir() bool {
|
||||
func (tfs *TorrentFS) IsDir() bool {
|
||||
return true
|
||||
}
|
||||
|
||||
// Type implements fs.DirEntry.
|
||||
func (tfs *TorrentFs) Type() fs.FileMode {
|
||||
func (tfs *TorrentFS) Type() fs.FileMode {
|
||||
return fs.ModeDir
|
||||
}
|
||||
|
||||
// ModTime implements fs.FileInfo.
|
||||
func (tfs *TorrentFs) ModTime() time.Time {
|
||||
func (tfs *TorrentFS) ModTime() time.Time {
|
||||
return time.Time{}
|
||||
}
|
||||
|
||||
// Mode implements fs.FileInfo.
|
||||
func (tfs *TorrentFs) Mode() fs.FileMode {
|
||||
func (tfs *TorrentFS) Mode() fs.FileMode {
|
||||
return fs.ModeDir
|
||||
}
|
||||
|
||||
// Size implements fs.FileInfo.
|
||||
func (tfs *TorrentFs) Size() int64 {
|
||||
func (tfs *TorrentFS) Size() int64 {
|
||||
return 0
|
||||
}
|
||||
|
||||
// Sys implements fs.FileInfo.
|
||||
func (tfs *TorrentFs) Sys() any {
|
||||
func (tfs *TorrentFS) Sys() any {
|
||||
return nil
|
||||
}
|
||||
|
||||
// FsName implements Filesystem.
|
||||
func (tfs *TorrentFs) FsName() string {
|
||||
func (tfs *TorrentFS) FsName() string {
|
||||
return "torrentfs"
|
||||
}
|
||||
|
||||
func (fs *TorrentFs) files(ctx context.Context) (map[string]File, error) {
|
||||
func (fs *TorrentFS) files(ctx context.Context) (map[string]File, error) {
|
||||
fs.mu.Lock()
|
||||
defer fs.mu.Unlock()
|
||||
|
||||
|
@ -175,7 +175,7 @@ DEFAULT_DIR:
|
|||
// return true
|
||||
// }
|
||||
|
||||
func (fs *TorrentFs) listFilesRecursive(ctx context.Context, vfs Filesystem, start string) (map[string]File, error) {
|
||||
func (fs *TorrentFS) listFilesRecursive(ctx context.Context, vfs Filesystem, start string) (map[string]File, error) {
|
||||
ctx, span := tracer.Start(ctx, "listFilesRecursive",
|
||||
fs.traceAttrs(attribute.String("start", start)),
|
||||
)
|
||||
|
@ -206,7 +206,7 @@ func (fs *TorrentFs) listFilesRecursive(ctx context.Context, vfs Filesystem, sta
|
|||
return out, nil
|
||||
}
|
||||
|
||||
func (fs *TorrentFs) rawOpen(ctx context.Context, filename string) (file File, err error) {
|
||||
func (fs *TorrentFS) rawOpen(ctx context.Context, filename string) (file File, err error) {
|
||||
ctx, span := tracer.Start(ctx, "rawOpen",
|
||||
fs.traceAttrs(attribute.String("filename", filename)),
|
||||
)
|
||||
|
@ -225,7 +225,7 @@ func (fs *TorrentFs) rawOpen(ctx context.Context, filename string) (file File, e
|
|||
return file, err
|
||||
}
|
||||
|
||||
func (fs *TorrentFs) rawStat(ctx context.Context, filename string) (fs.FileInfo, error) {
|
||||
func (fs *TorrentFS) rawStat(ctx context.Context, filename string) (fs.FileInfo, error) {
|
||||
ctx, span := tracer.Start(ctx, "rawStat",
|
||||
fs.traceAttrs(attribute.String("filename", filename)),
|
||||
)
|
||||
|
@ -243,7 +243,7 @@ func (fs *TorrentFs) rawStat(ctx context.Context, filename string) (fs.FileInfo,
|
|||
return file.Info()
|
||||
}
|
||||
|
||||
func (fs *TorrentFs) traceAttrs(add ...attribute.KeyValue) trace.SpanStartOption {
|
||||
func (fs *TorrentFS) traceAttrs(add ...attribute.KeyValue) trace.SpanStartOption {
|
||||
return trace.WithAttributes(append([]attribute.KeyValue{
|
||||
attribute.String("fs", fs.FsName()),
|
||||
attribute.String("torrent", fs.Torrent.Name()),
|
||||
|
@ -252,7 +252,7 @@ func (fs *TorrentFs) traceAttrs(add ...attribute.KeyValue) trace.SpanStartOption
|
|||
}
|
||||
|
||||
// Stat implements Filesystem.
|
||||
func (tfs *TorrentFs) Stat(ctx context.Context, filename string) (fs.FileInfo, error) {
|
||||
func (tfs *TorrentFS) Stat(ctx context.Context, filename string) (fs.FileInfo, error) {
|
||||
ctx, span := tracer.Start(ctx, "Stat",
|
||||
tfs.traceAttrs(attribute.String("filename", filename)),
|
||||
)
|
||||
|
@ -287,7 +287,7 @@ func (tfs *TorrentFs) Stat(ctx context.Context, filename string) (fs.FileInfo, e
|
|||
return tfs.rawStat(ctx, fsPath)
|
||||
}
|
||||
|
||||
func (tfs *TorrentFs) Open(ctx context.Context, filename string) (file File, err error) {
|
||||
func (tfs *TorrentFS) Open(ctx context.Context, filename string) (file File, err error) {
|
||||
ctx, span := tracer.Start(ctx, "Open",
|
||||
tfs.traceAttrs(attribute.String("filename", filename)),
|
||||
)
|
||||
|
@ -322,7 +322,7 @@ func (tfs *TorrentFs) Open(ctx context.Context, filename string) (file File, err
|
|||
return tfs.rawOpen(ctx, fsPath)
|
||||
}
|
||||
|
||||
func (tfs *TorrentFs) ReadDir(ctx context.Context, name string) ([]fs.DirEntry, error) {
|
||||
func (tfs *TorrentFS) ReadDir(ctx context.Context, name string) ([]fs.DirEntry, error) {
|
||||
ctx, span := tracer.Start(ctx, "ReadDir",
|
||||
tfs.traceAttrs(attribute.String("name", name)),
|
||||
)
|
||||
|
@ -357,7 +357,7 @@ func (tfs *TorrentFs) ReadDir(ctx context.Context, name string) ([]fs.DirEntry,
|
|||
return listDirFromFiles(files, fsPath)
|
||||
}
|
||||
|
||||
func (fs *TorrentFs) Unlink(ctx context.Context, name string) error {
|
||||
func (fs *TorrentFS) Unlink(ctx context.Context, name string) error {
|
||||
ctx, span := tracer.Start(ctx, "Unlink",
|
||||
fs.traceAttrs(attribute.String("name", name)),
|
||||
)
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue