This commit is contained in:
royalcat 2024-01-28 23:22:49 +03:00
parent 2cefb9db98
commit b97dcc8d8f
52 changed files with 7570 additions and 555 deletions

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,21 @@
package model
import "slices"
func (f *IntFilter) IsValid(v int64) bool {
if f.Eq != nil {
return v == *f.Eq
} else if f.Gt != nil {
return v > *f.Gt
} else if f.Gte != nil {
return v >= *f.Gte
} else if f.Lt != nil {
return v < *f.Lt
} else if f.Lte != nil {
return v <= *f.Lte
} else if f.In != nil {
return slices.Contains(f.In, v)
}
return true
}

View file

@ -0,0 +1,24 @@
package model
import "github.com/anacrolix/torrent"
func MapPeerSource(source torrent.PeerSource) string {
switch source {
case torrent.PeerSourceDirect:
return "Direct"
case torrent.PeerSourceUtHolepunch:
return "Ut Holepunch"
case torrent.PeerSourceDhtAnnouncePeer:
return "DHT Announce"
case torrent.PeerSourceDhtGetPeers:
return "DHT"
case torrent.PeerSourceIncoming:
return "Incoming"
case torrent.PeerSourceTracker:
return "Tracker"
case torrent.PeerSourcePex:
return "PEX"
default:
return "Unknown"
}
}

View file

@ -0,0 +1,93 @@
// Code generated by github.com/99designs/gqlgen, DO NOT EDIT.
package model
import (
"time"
"git.kmsign.ru/royalcat/tstor/src/host/controller"
"github.com/anacrolix/torrent"
)
type BooleanFilter struct {
Eq *bool `json:"eq,omitempty"`
}
type DateTimeFilter struct {
Eq *time.Time `json:"eq,omitempty"`
Gt *time.Time `json:"gt,omitempty"`
Lt *time.Time `json:"lt,omitempty"`
Gte *time.Time `json:"gte,omitempty"`
Lte *time.Time `json:"lte,omitempty"`
}
type IntFilter struct {
Eq *int64 `json:"eq,omitempty"`
Gt *int64 `json:"gt,omitempty"`
Lt *int64 `json:"lt,omitempty"`
Gte *int64 `json:"gte,omitempty"`
Lte *int64 `json:"lte,omitempty"`
In []int64 `json:"in,omitempty"`
}
type Mutation struct {
}
type Pagination struct {
Offset int64 `json:"offset"`
Limit int64 `json:"limit"`
}
type Query struct {
}
type Schema struct {
Query *Query `json:"query,omitempty"`
Mutation *Mutation `json:"mutation,omitempty"`
}
type StringFilter struct {
Eq *string `json:"eq,omitempty"`
Substr *string `json:"substr,omitempty"`
In []string `json:"in,omitempty"`
}
type Torrent struct {
Name string `json:"name"`
Infohash string `json:"infohash"`
BytesCompleted int64 `json:"bytesCompleted"`
TorrentFilePath string `json:"torrentFilePath"`
BytesMissing int64 `json:"bytesMissing"`
Files []*TorrentFile `json:"files"`
ExcludedFiles []*TorrentFile `json:"excludedFiles"`
Peers []*TorrentPeer `json:"peers"`
T *controller.Torrent `json:"-"`
}
type TorrentFile struct {
Filename string `json:"filename"`
Size int64 `json:"size"`
BytesCompleted int64 `json:"bytesCompleted"`
F *torrent.File `json:"-"`
}
type TorrentFilter struct {
Everything *bool `json:"everything,omitempty"`
Infohash *string `json:"infohash,omitempty"`
}
type TorrentPeer struct {
IP string `json:"ip"`
DownloadRate float64 `json:"downloadRate"`
Discovery string `json:"discovery"`
Port int64 `json:"port"`
ClientName string `json:"clientName"`
F *torrent.PeerConn `json:"-"`
}
type TorrentsFilter struct {
Name *StringFilter `json:"name,omitempty"`
BytesCompleted *IntFilter `json:"bytesCompleted,omitempty"`
BytesMissing *IntFilter `json:"bytesMissing,omitempty"`
PeersCount *IntFilter `json:"peersCount,omitempty"`
}

View file

@ -0,0 +1,28 @@
package graph
import (
"context"
"fmt"
"github.com/99designs/gqlgen/graphql"
)
func OneOf(ctx context.Context, obj interface{}, next graphql.Resolver) (res interface{}, err error) {
wasValue := false
m, ok := obj.(map[string]any)
if !ok {
return nil, fmt.Errorf("OneOf error, unknow object type: %T", obj)
}
for k, v := range m {
if v != nil {
if !wasValue {
wasValue = true
} else {
return nil, fmt.Errorf("OneOf with multiple fields: %s", k)
}
}
}
return next(ctx)
}

View file

@ -0,0 +1,64 @@
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
import (
"context"
graph "git.kmsign.ru/royalcat/tstor/src/delivery/graphql"
"git.kmsign.ru/royalcat/tstor/src/delivery/graphql/model"
)
// ValidateTorrents is the resolver for the validateTorrents field.
func (r *mutationResolver) ValidateTorrents(ctx context.Context, filter model.TorrentFilter) (bool, error) {
if filter.Infohash != nil {
t, err := r.Resolver.Service.GetTorrent(*filter.Infohash)
if err != nil {
return false, err
}
if t == nil {
return false, nil
}
t.ValidateTorrent()
return true, nil
}
if filter.Everything != nil && *filter.Everything {
torrents, err := r.Resolver.Service.ListTorrents(ctx)
if err != nil {
return false, err
}
for _, v := range torrents {
if err := v.ValidateTorrent(); err != nil {
return false, err
}
}
return true, nil
}
return false, nil
}
// CleanupTorrents is the resolver for the cleanupTorrents field.
func (r *mutationResolver) CleanupTorrents(ctx context.Context, files *bool, dryRun bool) (int64, error) {
torrents, err := r.Service.ListTorrents(ctx)
if err != nil {
return 0, err
}
if files != nil && *files {
r, err := r.Service.Storage.CleanupFiles(ctx, torrents, dryRun)
return int64(r), err
} else {
r, err := r.Service.Storage.CleanupDirs(ctx, torrents, dryRun)
return int64(r), err
}
}
// Mutation returns graph.MutationResolver implementation.
func (r *Resolver) Mutation() graph.MutationResolver { return &mutationResolver{r} }
type mutationResolver struct{ *Resolver }

View file

@ -0,0 +1,75 @@
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
import (
"context"
graph "git.kmsign.ru/royalcat/tstor/src/delivery/graphql"
"git.kmsign.ru/royalcat/tstor/src/delivery/graphql/model"
)
// Torrents is the resolver for the torrents field.
func (r *queryResolver) Torrents(ctx context.Context, filter *model.TorrentsFilter, pagination *model.Pagination) ([]*model.Torrent, error) {
torrents, err := r.Service.ListTorrents(ctx)
if err != nil {
return nil, err
}
filterFuncs := []func(torrent *model.Torrent) bool{}
if filter != nil {
if filter.BytesCompleted != nil {
filterFuncs = append(filterFuncs, func(torrent *model.Torrent) bool {
return filter.BytesCompleted.IsValid(torrent.BytesCompleted)
})
}
if filter.BytesMissing != nil {
filterFuncs = append(filterFuncs, func(torrent *model.Torrent) bool {
return filter.BytesMissing.IsValid(torrent.BytesMissing)
})
}
if filter.PeersCount != nil {
filterFuncs = append(filterFuncs, func(torrent *model.Torrent) bool {
return filter.PeersCount.IsValid(
int64(len(torrent.T.Torrent().PeerConns())),
)
})
}
}
filterFunc := func(torrent *model.Torrent) bool {
for _, f := range filterFuncs {
if !f(torrent) {
return false
}
}
return true
}
tr := []*model.Torrent{}
for _, t := range torrents {
d := &model.Torrent{
Infohash: t.InfoHash(),
Name: t.Name(),
BytesCompleted: t.BytesCompleted(),
BytesMissing: t.BytesMissing(),
T: t,
}
if !filterFunc(d) {
continue
}
tr = append(tr, d)
}
return tr, nil
}
// Query returns graph.QueryResolver implementation.
func (r *Resolver) Query() graph.QueryResolver { return &queryResolver{r} }
type queryResolver struct{ *Resolver }

View file

@ -0,0 +1,11 @@
package resolver
import "git.kmsign.ru/royalcat/tstor/src/host/service"
// This file will not be regenerated automatically.
//
// It serves as dependency injection for your app, add any dependencies you require here.
type Resolver struct {
Service *service.Service
}

View file

@ -0,0 +1,73 @@
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
import (
"context"
graph "git.kmsign.ru/royalcat/tstor/src/delivery/graphql"
"git.kmsign.ru/royalcat/tstor/src/delivery/graphql/model"
)
// Name is the resolver for the name field.
func (r *torrentResolver) Name(ctx context.Context, obj *model.Torrent) (string, error) {
return obj.T.Name(), nil
}
// Files is the resolver for the files field.
func (r *torrentResolver) Files(ctx context.Context, obj *model.Torrent) ([]*model.TorrentFile, error) {
out := []*model.TorrentFile{}
files, err := obj.T.Files()
if err != nil {
return nil, err
}
for _, f := range files {
out = append(out, &model.TorrentFile{
Filename: f.DisplayPath(),
Size: f.Length(),
BytesCompleted: f.BytesCompleted(),
F: f,
})
}
return out, nil
}
// ExcludedFiles is the resolver for the excludedFiles field.
func (r *torrentResolver) ExcludedFiles(ctx context.Context, obj *model.Torrent) ([]*model.TorrentFile, error) {
out := []*model.TorrentFile{}
files, err := obj.T.ExcludedFiles()
if err != nil {
return nil, err
}
for _, f := range files {
out = append(out, &model.TorrentFile{
Filename: f.DisplayPath(),
Size: f.Length(),
F: f,
})
}
return out, nil
}
// Peers is the resolver for the peers field.
func (r *torrentResolver) Peers(ctx context.Context, obj *model.Torrent) ([]*model.TorrentPeer, error) {
peers := []*model.TorrentPeer{}
for _, peer := range obj.T.Torrent().PeerConns() {
peers = append(peers, &model.TorrentPeer{
IP: peer.RemoteAddr.String(),
DownloadRate: peer.DownloadRate(),
Discovery: model.MapPeerSource(peer.Discovery),
Port: int64(peer.PeerListenPort),
ClientName: peer.PeerClientName.Load().(string),
F: peer,
})
}
return peers, nil
}
// Torrent returns graph.TorrentResolver implementation.
func (r *Resolver) Torrent() graph.TorrentResolver { return &torrentResolver{r} }
type torrentResolver struct{ *Resolver }

35
src/delivery/router.go Normal file
View file

@ -0,0 +1,35 @@
package delivery
import (
"net/http"
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"
"github.com/99designs/gqlgen/graphql/handler"
"github.com/99designs/gqlgen/graphql/handler/extension"
"github.com/99designs/gqlgen/graphql/handler/lru"
"github.com/99designs/gqlgen/graphql/handler/transport"
)
func GraphQLHandler(service *service.Service) http.Handler {
graphqlHandler := handler.NewDefaultServer(
graph.NewExecutableSchema(
graph.Config{
Resolvers: &resolver.Resolver{Service: service},
Directives: graph.DirectiveRoot{
OneOf: graph.OneOf,
},
},
),
)
graphqlHandler.AddTransport(&transport.POST{})
graphqlHandler.AddTransport(&transport.Websocket{})
graphqlHandler.AddTransport(&transport.SSE{})
graphqlHandler.AddTransport(&transport.UrlEncodedForm{})
graphqlHandler.SetQueryCache(lru.New(1000))
graphqlHandler.Use(extension.Introspection{})
graphqlHandler.Use(extension.AutomaticPersistedQuery{Cache: lru.New(100)})
return graphqlHandler
}