Compare commits
2 commits
ee2fc5ab9d
...
0ae11aa283
Author | SHA1 | Date | |
---|---|---|---|
0ae11aa283 | |||
92bb67959b |
21 changed files with 246 additions and 94 deletions
daemons
graphql/sources
src
delivery/graphql
export/webdav
vfs
ui/lib/api
|
@ -160,6 +160,11 @@ func (fs *Daemon) GetTorrentFS(ctx context.Context, sourcePath string, file vfs.
|
||||||
ctx, span := trace.Start(ctx, "GetTorrentFS")
|
ctx, span := trace.Start(ctx, "GetTorrentFS")
|
||||||
defer span.End()
|
defer span.End()
|
||||||
|
|
||||||
|
stat, err := file.Info()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
log := fs.log.With(slog.String("file", file.Name()))
|
log := fs.log.With(slog.String("file", file.Name()))
|
||||||
|
|
||||||
ih, err := readInfoHash(ctx, file)
|
ih, err := readInfoHash(ctx, file)
|
||||||
|
@ -185,7 +190,7 @@ func (fs *Daemon) GetTorrentFS(ctx context.Context, sourcePath string, file vfs.
|
||||||
fs.sourceFiles[sourcePath] = ih.HexString()
|
fs.sourceFiles[sourcePath] = ih.HexString()
|
||||||
fs.sourceFilesMu.Unlock()
|
fs.sourceFilesMu.Unlock()
|
||||||
|
|
||||||
return newTorrentFS(ctx, fs.ur, fs.client, file.Name(), ih.HexString(), torrentPath)
|
return newTorrentFS(ctx, fs.client, file.Name(), ih.HexString(), stat.ModTime(), torrentPath)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (d *Daemon) syncTorrentState(ctx context.Context, file vfs.File, ih metainfo.Hash, torrentPath string) error {
|
func (d *Daemon) syncTorrentState(ctx context.Context, file vfs.File, ih metainfo.Hash, torrentPath string) error {
|
||||||
|
|
|
@ -15,7 +15,6 @@ import (
|
||||||
|
|
||||||
"git.kmsign.ru/royalcat/tstor/pkg/qbittorrent"
|
"git.kmsign.ru/royalcat/tstor/pkg/qbittorrent"
|
||||||
"git.kmsign.ru/royalcat/tstor/pkg/rlog"
|
"git.kmsign.ru/royalcat/tstor/pkg/rlog"
|
||||||
"git.kmsign.ru/royalcat/tstor/pkg/uring"
|
|
||||||
"git.kmsign.ru/royalcat/tstor/src/vfs"
|
"git.kmsign.ru/royalcat/tstor/src/vfs"
|
||||||
"github.com/iceber/iouring-go"
|
"github.com/iceber/iouring-go"
|
||||||
)
|
)
|
||||||
|
@ -26,6 +25,7 @@ type FS struct {
|
||||||
name string
|
name string
|
||||||
hash string
|
hash string
|
||||||
dataDir string // directory where torrent files are stored
|
dataDir string // directory where torrent files are stored
|
||||||
|
modTime time.Time
|
||||||
|
|
||||||
ur *iouring.IOURing
|
ur *iouring.IOURing
|
||||||
|
|
||||||
|
@ -43,7 +43,7 @@ type fileEntry struct {
|
||||||
|
|
||||||
var _ vfs.Filesystem = (*FS)(nil)
|
var _ vfs.Filesystem = (*FS)(nil)
|
||||||
|
|
||||||
func newTorrentFS(ctx context.Context, ur *iouring.IOURing, client *cacheClient, name string, hash string, dataDir string) (*FS, error) {
|
func newTorrentFS(ctx context.Context, client *cacheClient, name string, hash string, modTime time.Time, dataDir string) (*FS, error) {
|
||||||
ctx, span := trace.Start(ctx, "newTorrentFS")
|
ctx, span := trace.Start(ctx, "newTorrentFS")
|
||||||
defer span.End()
|
defer span.End()
|
||||||
|
|
||||||
|
@ -60,21 +60,20 @@ func newTorrentFS(ctx context.Context, ur *iouring.IOURing, client *cacheClient,
|
||||||
|
|
||||||
entries[vfs.AbsPath(cnt.Name)] = fileEntry{
|
entries[vfs.AbsPath(cnt.Name)] = fileEntry{
|
||||||
Content: cnt,
|
Content: cnt,
|
||||||
FileInfo: vfs.NewFileInfo(cnt.Name, cnt.Size),
|
FileInfo: vfs.NewFileInfo(cnt.Name, cnt.Size, modTime),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return &FS{
|
return &FS{
|
||||||
client: client,
|
client: client,
|
||||||
name: name,
|
name: name,
|
||||||
hash: hash,
|
hash: hash,
|
||||||
|
modTime: modTime,
|
||||||
|
|
||||||
dataDir: dataDir,
|
dataDir: dataDir,
|
||||||
|
|
||||||
entries: entries,
|
entries: entries,
|
||||||
|
|
||||||
ur: ur,
|
|
||||||
|
|
||||||
log: rlog.Component("qbittorrent", "fs"),
|
log: rlog.Component("qbittorrent", "fs"),
|
||||||
|
|
||||||
FilesystemPrototype: vfs.FilesystemPrototype(name),
|
FilesystemPrototype: vfs.FilesystemPrototype(name),
|
||||||
|
@ -115,7 +114,7 @@ func (f *FS) Stat(ctx context.Context, name string) (fs.FileInfo, error) {
|
||||||
name = vfs.AbsPath(path.Clean(name))
|
name = vfs.AbsPath(path.Clean(name))
|
||||||
|
|
||||||
if vfs.IsRoot(name) {
|
if vfs.IsRoot(name) {
|
||||||
return vfs.NewDirInfo(f.name), nil
|
return vfs.NewDirInfo(f.name, f.modTime), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
if entry, ok := f.entries[name]; ok {
|
if entry, ok := f.entries[name]; ok {
|
||||||
|
@ -124,7 +123,7 @@ func (f *FS) Stat(ctx context.Context, name string) (fs.FileInfo, error) {
|
||||||
|
|
||||||
for p := range f.entries {
|
for p := range f.entries {
|
||||||
if strings.HasPrefix(p, name) {
|
if strings.HasPrefix(p, name) {
|
||||||
return vfs.NewDirInfo(name), nil
|
return vfs.NewDirInfo(name, f.modTime), nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -219,23 +218,24 @@ func openFile(ctx context.Context, ur *iouring.IOURing, client *cacheClient, tor
|
||||||
pieceSize: props.PieceSize,
|
pieceSize: props.PieceSize,
|
||||||
fileSize: content.Size,
|
fileSize: content.Size,
|
||||||
|
|
||||||
file: uring.NewFile(ur, file),
|
file: file,
|
||||||
|
|
||||||
offset: 0,
|
offset: 0,
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
type File struct {
|
type File struct {
|
||||||
client *cacheClient
|
client *cacheClient
|
||||||
hash string
|
hash string
|
||||||
torrentDir string
|
torrentModTime time.Time
|
||||||
filePath string // path inside a torrent directory
|
torrentDir string
|
||||||
contentIndex int
|
filePath string // path inside a torrent directory
|
||||||
pieceSize int
|
contentIndex int
|
||||||
fileSize int64
|
pieceSize int
|
||||||
|
fileSize int64
|
||||||
|
|
||||||
mu sync.Mutex
|
mu sync.Mutex
|
||||||
file *uring.File
|
file *os.File
|
||||||
offset int64
|
offset int64
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -243,7 +243,7 @@ var _ vfs.File = (*File)(nil)
|
||||||
|
|
||||||
// Info implements vfs.File.
|
// Info implements vfs.File.
|
||||||
func (f *File) Info() (fs.FileInfo, error) {
|
func (f *File) Info() (fs.FileInfo, error) {
|
||||||
return &fileInfo{name: path.Base(f.filePath), size: f.fileSize}, nil
|
return vfs.NewFileInfo(path.Base(f.filePath), f.fileSize, f.torrentModTime), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// IsDir implements vfs.File.
|
// IsDir implements vfs.File.
|
||||||
|
@ -345,7 +345,7 @@ func (f *File) Read(ctx context.Context, p []byte) (int, error) {
|
||||||
return 0, err
|
return 0, err
|
||||||
}
|
}
|
||||||
|
|
||||||
n, err := f.file.ReadAt(ctx, p, f.offset)
|
n, err := f.file.ReadAt(p, f.offset)
|
||||||
f.offset += int64(n)
|
f.offset += int64(n)
|
||||||
return n, err
|
return n, err
|
||||||
}
|
}
|
||||||
|
@ -356,7 +356,7 @@ func (f *File) ReadAt(ctx context.Context, p []byte, off int64) (int, error) {
|
||||||
return 0, err
|
return 0, err
|
||||||
}
|
}
|
||||||
|
|
||||||
return f.file.ReadAt(ctx, p, off)
|
return f.file.ReadAt(p, off)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Size implements vfs.File.
|
// Size implements vfs.File.
|
||||||
|
@ -371,42 +371,43 @@ func (f *File) Type() fs.FileMode {
|
||||||
|
|
||||||
// Close implements vfs.File.
|
// Close implements vfs.File.
|
||||||
func (f *File) Close(ctx context.Context) error {
|
func (f *File) Close(ctx context.Context) error {
|
||||||
return f.file.Close(ctx)
|
return f.file.Close()
|
||||||
}
|
}
|
||||||
|
|
||||||
type fileInfo struct {
|
// type fileInfo struct {
|
||||||
name string
|
// name string
|
||||||
size int64
|
// size int64
|
||||||
}
|
// modTime time.Time
|
||||||
|
// }
|
||||||
|
|
||||||
var _ fs.FileInfo = (*fileInfo)(nil)
|
// var _ fs.FileInfo = (*fileInfo)(nil)
|
||||||
|
|
||||||
// IsDir implements fs.FileInfo.
|
// // IsDir implements fs.FileInfo.
|
||||||
func (f *fileInfo) IsDir() bool {
|
// func (f *fileInfo) IsDir() bool {
|
||||||
return false
|
// return false
|
||||||
}
|
// }
|
||||||
|
|
||||||
// ModTime implements fs.FileInfo.
|
// // ModTime implements fs.FileInfo.
|
||||||
func (f *fileInfo) ModTime() time.Time {
|
// func (f *fileInfo) ModTime() time.Time {
|
||||||
return time.Time{}
|
// return f.modTime
|
||||||
}
|
// }
|
||||||
|
|
||||||
// Mode implements fs.FileInfo.
|
// // Mode implements fs.FileInfo.
|
||||||
func (f *fileInfo) Mode() fs.FileMode {
|
// func (f *fileInfo) Mode() fs.FileMode {
|
||||||
return vfs.ModeFileRO
|
// return vfs.ModeFileRO
|
||||||
}
|
// }
|
||||||
|
|
||||||
// Name implements fs.FileInfo.
|
// // Name implements fs.FileInfo.
|
||||||
func (f *fileInfo) Name() string {
|
// func (f *fileInfo) Name() string {
|
||||||
return f.name
|
// return f.name
|
||||||
}
|
// }
|
||||||
|
|
||||||
// Size implements fs.FileInfo.
|
// // Size implements fs.FileInfo.
|
||||||
func (f *fileInfo) Size() int64 {
|
// func (f *fileInfo) Size() int64 {
|
||||||
return f.size
|
// return f.size
|
||||||
}
|
// }
|
||||||
|
|
||||||
// Sys implements fs.FileInfo.
|
// // Sys implements fs.FileInfo.
|
||||||
func (f *fileInfo) Sys() any {
|
// func (f *fileInfo) Sys() any {
|
||||||
return nil
|
// return nil
|
||||||
}
|
// }
|
||||||
|
|
|
@ -3,6 +3,7 @@ package rclone
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"io/fs"
|
"io/fs"
|
||||||
|
"time"
|
||||||
|
|
||||||
"git.kmsign.ru/royalcat/tstor/src/vfs"
|
"git.kmsign.ru/royalcat/tstor/src/vfs"
|
||||||
rclonefs "github.com/rclone/rclone/fs"
|
rclonefs "github.com/rclone/rclone/fs"
|
||||||
|
@ -78,7 +79,7 @@ func (f *fileWrapper) Close(ctx context.Context) error {
|
||||||
|
|
||||||
// Info implements vfs.File.
|
// Info implements vfs.File.
|
||||||
func (f *fileWrapper) Info() (fs.FileInfo, error) {
|
func (f *fileWrapper) Info() (fs.FileInfo, error) {
|
||||||
return vfs.NewFileInfo(f.name, f.Size()), nil
|
return vfs.NewFileInfo(f.name, f.Size(), time.Time{}), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// IsDir implements vfs.File.
|
// IsDir implements vfs.File.
|
||||||
|
|
|
@ -450,7 +450,7 @@ func (tf *torrentFile) Type() fs.FileMode {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (tf *torrentFile) Info() (fs.FileInfo, error) {
|
func (tf *torrentFile) Info() (fs.FileInfo, error) {
|
||||||
return vfs.NewFileInfo(tf.name, tf.file.Length()), nil
|
return vfs.NewFileInfo(tf.name, tf.file.Length(), time.Time{}), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (tf *torrentFile) Size() int64 {
|
func (tf *torrentFile) Size() int64 {
|
||||||
|
|
|
@ -4,6 +4,7 @@ import (
|
||||||
"context"
|
"context"
|
||||||
"io/fs"
|
"io/fs"
|
||||||
"os"
|
"os"
|
||||||
|
"time"
|
||||||
|
|
||||||
"git.kmsign.ru/royalcat/tstor/pkg/ctxbilly"
|
"git.kmsign.ru/royalcat/tstor/pkg/ctxbilly"
|
||||||
"git.kmsign.ru/royalcat/tstor/src/vfs"
|
"git.kmsign.ru/royalcat/tstor/src/vfs"
|
||||||
|
@ -53,7 +54,7 @@ func (s *SourceFS) ReadDir(ctx context.Context, path string) ([]fs.DirEntry, err
|
||||||
|
|
||||||
entries := make([]fs.DirEntry, 0, len(infos))
|
entries := make([]fs.DirEntry, 0, len(infos))
|
||||||
for _, info := range infos {
|
for _, info := range infos {
|
||||||
entries = append(entries, vfs.NewFileInfo(info.Name(), info.Size()))
|
entries = append(entries, vfs.NewFileInfo(info.Name(), info.Size(), time.Time{}))
|
||||||
}
|
}
|
||||||
|
|
||||||
return entries, nil
|
return entries, nil
|
||||||
|
|
|
@ -1,3 +1,7 @@
|
||||||
type QBitTorrentDaemonQuery {
|
type QBitTorrentDaemonQuery {
|
||||||
torrents: [QTorrent!]! @resolver
|
torrents(filter: QBitTorrentDaemonFilter): [QTorrent!]! @resolver
|
||||||
|
}
|
||||||
|
|
||||||
|
input QBitTorrentDaemonFilter {
|
||||||
|
sourcesCount: IntFilter
|
||||||
}
|
}
|
||||||
|
|
|
@ -102,7 +102,7 @@ type ComplexityRoot struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
QBitTorrentDaemonQuery struct {
|
QBitTorrentDaemonQuery struct {
|
||||||
Torrents func(childComplexity int) int
|
Torrents func(childComplexity int, filter *model.QBitTorrentDaemonFilter) int
|
||||||
}
|
}
|
||||||
|
|
||||||
QTorrent struct {
|
QTorrent struct {
|
||||||
|
@ -243,7 +243,7 @@ type QBitTorrentDaemonMutationResolver interface {
|
||||||
CleanupUnregistred(ctx context.Context, obj *model.QBitTorrentDaemonMutation, run bool) (*model.QBitCleanupUnregistredResponse, error)
|
CleanupUnregistred(ctx context.Context, obj *model.QBitTorrentDaemonMutation, run bool) (*model.QBitCleanupUnregistredResponse, error)
|
||||||
}
|
}
|
||||||
type QBitTorrentDaemonQueryResolver interface {
|
type QBitTorrentDaemonQueryResolver interface {
|
||||||
Torrents(ctx context.Context, obj *model.QBitTorrentDaemonQuery) ([]*model.QTorrent, error)
|
Torrents(ctx context.Context, obj *model.QBitTorrentDaemonQuery, filter *model.QBitTorrentDaemonFilter) ([]*model.QTorrent, error)
|
||||||
}
|
}
|
||||||
type QTorrentResolver interface {
|
type QTorrentResolver interface {
|
||||||
SourceFiles(ctx context.Context, obj *model.QTorrent) ([]string, error)
|
SourceFiles(ctx context.Context, obj *model.QTorrent) ([]string, error)
|
||||||
|
@ -438,7 +438,12 @@ func (e *executableSchema) Complexity(typeName, field string, childComplexity in
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
|
|
||||||
return e.complexity.QBitTorrentDaemonQuery.Torrents(childComplexity), true
|
args, err := ec.field_QBitTorrentDaemonQuery_torrents_args(context.TODO(), rawArgs)
|
||||||
|
if err != nil {
|
||||||
|
return 0, false
|
||||||
|
}
|
||||||
|
|
||||||
|
return e.complexity.QBitTorrentDaemonQuery.Torrents(childComplexity, args["filter"].(*model.QBitTorrentDaemonFilter)), true
|
||||||
|
|
||||||
case "QTorrent.hash":
|
case "QTorrent.hash":
|
||||||
if e.complexity.QTorrent.Hash == nil {
|
if e.complexity.QTorrent.Hash == nil {
|
||||||
|
@ -970,6 +975,7 @@ func (e *executableSchema) Exec(ctx context.Context) graphql.ResponseHandler {
|
||||||
ec.unmarshalInputDateTimeFilter,
|
ec.unmarshalInputDateTimeFilter,
|
||||||
ec.unmarshalInputIntFilter,
|
ec.unmarshalInputIntFilter,
|
||||||
ec.unmarshalInputPagination,
|
ec.unmarshalInputPagination,
|
||||||
|
ec.unmarshalInputQBitTorrentDaemonFilter,
|
||||||
ec.unmarshalInputStringFilter,
|
ec.unmarshalInputStringFilter,
|
||||||
ec.unmarshalInputTorrentFilter,
|
ec.unmarshalInputTorrentFilter,
|
||||||
ec.unmarshalInputTorrentPriorityFilter,
|
ec.unmarshalInputTorrentPriorityFilter,
|
||||||
|
@ -1153,7 +1159,11 @@ type QBitCleanupUnregistredResponse {
|
||||||
}
|
}
|
||||||
`, BuiltIn: false},
|
`, BuiltIn: false},
|
||||||
{Name: "../../../graphql/sources/qbittorrent_query.graphql", Input: `type QBitTorrentDaemonQuery {
|
{Name: "../../../graphql/sources/qbittorrent_query.graphql", Input: `type QBitTorrentDaemonQuery {
|
||||||
torrents: [QTorrent!]! @resolver
|
torrents(filter: QBitTorrentDaemonFilter): [QTorrent!]! @resolver
|
||||||
|
}
|
||||||
|
|
||||||
|
input QBitTorrentDaemonFilter {
|
||||||
|
sourcesCount: IntFilter
|
||||||
}
|
}
|
||||||
`, BuiltIn: false},
|
`, BuiltIn: false},
|
||||||
{Name: "../../../graphql/sources/qbittorrent_types.graphql", Input: `type QTorrent {
|
{Name: "../../../graphql/sources/qbittorrent_types.graphql", Input: `type QTorrent {
|
||||||
|
@ -1484,6 +1494,38 @@ func (ec *executionContext) field_QBitTorrentDaemonMutation_cleanup_argsRun(
|
||||||
return zeroVal, nil
|
return zeroVal, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (ec *executionContext) field_QBitTorrentDaemonQuery_torrents_args(ctx context.Context, rawArgs map[string]interface{}) (map[string]interface{}, error) {
|
||||||
|
var err error
|
||||||
|
args := map[string]interface{}{}
|
||||||
|
arg0, err := ec.field_QBitTorrentDaemonQuery_torrents_argsFilter(ctx, rawArgs)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
args["filter"] = arg0
|
||||||
|
return args, nil
|
||||||
|
}
|
||||||
|
func (ec *executionContext) field_QBitTorrentDaemonQuery_torrents_argsFilter(
|
||||||
|
ctx context.Context,
|
||||||
|
rawArgs map[string]interface{},
|
||||||
|
) (*model.QBitTorrentDaemonFilter, error) {
|
||||||
|
// We won't call the directive if the argument is null.
|
||||||
|
// Set call_argument_directives_with_null to true to call directives
|
||||||
|
// even if the argument is null.
|
||||||
|
_, ok := rawArgs["filter"]
|
||||||
|
if !ok {
|
||||||
|
var zeroVal *model.QBitTorrentDaemonFilter
|
||||||
|
return zeroVal, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx = graphql.WithPathContext(ctx, graphql.NewPathWithField("filter"))
|
||||||
|
if tmp, ok := rawArgs["filter"]; ok {
|
||||||
|
return ec.unmarshalOQBitTorrentDaemonFilter2ᚖgitᚗkmsignᚗruᚋroyalcatᚋtstorᚋsrcᚋdeliveryᚋgraphqlᚋmodelᚐQBitTorrentDaemonFilter(ctx, tmp)
|
||||||
|
}
|
||||||
|
|
||||||
|
var zeroVal *model.QBitTorrentDaemonFilter
|
||||||
|
return zeroVal, nil
|
||||||
|
}
|
||||||
|
|
||||||
func (ec *executionContext) field_Query___type_args(ctx context.Context, rawArgs map[string]interface{}) (map[string]interface{}, error) {
|
func (ec *executionContext) field_Query___type_args(ctx context.Context, rawArgs map[string]interface{}) (map[string]interface{}, error) {
|
||||||
var err error
|
var err error
|
||||||
args := map[string]interface{}{}
|
args := map[string]interface{}{}
|
||||||
|
@ -2803,7 +2845,7 @@ func (ec *executionContext) _QBitTorrentDaemonQuery_torrents(ctx context.Context
|
||||||
resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) {
|
resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) {
|
||||||
directive0 := func(rctx context.Context) (interface{}, error) {
|
directive0 := func(rctx context.Context) (interface{}, error) {
|
||||||
ctx = rctx // use context from middleware stack in children
|
ctx = rctx // use context from middleware stack in children
|
||||||
return ec.resolvers.QBitTorrentDaemonQuery().Torrents(rctx, obj)
|
return ec.resolvers.QBitTorrentDaemonQuery().Torrents(rctx, obj, fc.Args["filter"].(*model.QBitTorrentDaemonFilter))
|
||||||
}
|
}
|
||||||
|
|
||||||
directive1 := func(ctx context.Context) (interface{}, error) {
|
directive1 := func(ctx context.Context) (interface{}, error) {
|
||||||
|
@ -2841,7 +2883,7 @@ func (ec *executionContext) _QBitTorrentDaemonQuery_torrents(ctx context.Context
|
||||||
return ec.marshalNQTorrent2ᚕᚖgitᚗkmsignᚗruᚋroyalcatᚋtstorᚋsrcᚋdeliveryᚋgraphqlᚋmodelᚐQTorrentᚄ(ctx, field.Selections, res)
|
return ec.marshalNQTorrent2ᚕᚖgitᚗkmsignᚗruᚋroyalcatᚋtstorᚋsrcᚋdeliveryᚋgraphqlᚋmodelᚐQTorrentᚄ(ctx, field.Selections, res)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (ec *executionContext) fieldContext_QBitTorrentDaemonQuery_torrents(_ context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) {
|
func (ec *executionContext) fieldContext_QBitTorrentDaemonQuery_torrents(ctx context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) {
|
||||||
fc = &graphql.FieldContext{
|
fc = &graphql.FieldContext{
|
||||||
Object: "QBitTorrentDaemonQuery",
|
Object: "QBitTorrentDaemonQuery",
|
||||||
Field: field,
|
Field: field,
|
||||||
|
@ -2859,6 +2901,17 @@ func (ec *executionContext) fieldContext_QBitTorrentDaemonQuery_torrents(_ conte
|
||||||
return nil, fmt.Errorf("no field named %q was found under type QTorrent", field.Name)
|
return nil, fmt.Errorf("no field named %q was found under type QTorrent", field.Name)
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
defer func() {
|
||||||
|
if r := recover(); r != nil {
|
||||||
|
err = ec.Recover(ctx, r)
|
||||||
|
ec.Error(ctx, err)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
ctx = graphql.WithFieldContext(ctx, fc)
|
||||||
|
if fc.Args, err = ec.field_QBitTorrentDaemonQuery_torrents_args(ctx, field.ArgumentMap(ec.Variables)); err != nil {
|
||||||
|
ec.Error(ctx, err)
|
||||||
|
return fc, err
|
||||||
|
}
|
||||||
return fc, nil
|
return fc, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -8820,6 +8873,33 @@ func (ec *executionContext) unmarshalInputPagination(ctx context.Context, obj in
|
||||||
return it, nil
|
return it, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (ec *executionContext) unmarshalInputQBitTorrentDaemonFilter(ctx context.Context, obj interface{}) (model.QBitTorrentDaemonFilter, error) {
|
||||||
|
var it model.QBitTorrentDaemonFilter
|
||||||
|
asMap := map[string]interface{}{}
|
||||||
|
for k, v := range obj.(map[string]interface{}) {
|
||||||
|
asMap[k] = v
|
||||||
|
}
|
||||||
|
|
||||||
|
fieldsInOrder := [...]string{"sourcesCount"}
|
||||||
|
for _, k := range fieldsInOrder {
|
||||||
|
v, ok := asMap[k]
|
||||||
|
if !ok {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
switch k {
|
||||||
|
case "sourcesCount":
|
||||||
|
ctx := graphql.WithPathContext(ctx, graphql.NewPathWithField("sourcesCount"))
|
||||||
|
data, err := ec.unmarshalOIntFilter2ᚖgitᚗkmsignᚗruᚋroyalcatᚋtstorᚋsrcᚋdeliveryᚋgraphqlᚋmodelᚐIntFilter(ctx, v)
|
||||||
|
if err != nil {
|
||||||
|
return it, err
|
||||||
|
}
|
||||||
|
it.SourcesCount = data
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return it, nil
|
||||||
|
}
|
||||||
|
|
||||||
func (ec *executionContext) unmarshalInputStringFilter(ctx context.Context, obj interface{}) (model.StringFilter, error) {
|
func (ec *executionContext) unmarshalInputStringFilter(ctx context.Context, obj interface{}) (model.StringFilter, error) {
|
||||||
var it model.StringFilter
|
var it model.StringFilter
|
||||||
asMap := map[string]interface{}{}
|
asMap := map[string]interface{}{}
|
||||||
|
@ -12561,6 +12641,14 @@ func (ec *executionContext) marshalOProgress2gitᚗkmsignᚗruᚋroyalcatᚋtsto
|
||||||
return ec._Progress(ctx, sel, v)
|
return ec._Progress(ctx, sel, v)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (ec *executionContext) unmarshalOQBitTorrentDaemonFilter2ᚖgitᚗkmsignᚗruᚋroyalcatᚋtstorᚋsrcᚋdeliveryᚋgraphqlᚋmodelᚐQBitTorrentDaemonFilter(ctx context.Context, v interface{}) (*model.QBitTorrentDaemonFilter, error) {
|
||||||
|
if v == nil {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
res, err := ec.unmarshalInputQBitTorrentDaemonFilter(ctx, v)
|
||||||
|
return &res, graphql.ErrorOnPath(ctx, err)
|
||||||
|
}
|
||||||
|
|
||||||
func (ec *executionContext) marshalOQBitTorrentDaemonMutation2ᚖgitᚗkmsignᚗruᚋroyalcatᚋtstorᚋsrcᚋdeliveryᚋgraphqlᚋmodelᚐQBitTorrentDaemonMutation(ctx context.Context, sel ast.SelectionSet, v *model.QBitTorrentDaemonMutation) graphql.Marshaler {
|
func (ec *executionContext) marshalOQBitTorrentDaemonMutation2ᚖgitᚗkmsignᚗruᚋroyalcatᚋtstorᚋsrcᚋdeliveryᚋgraphqlᚋmodelᚐQBitTorrentDaemonMutation(ctx context.Context, sel ast.SelectionSet, v *model.QBitTorrentDaemonMutation) graphql.Marshaler {
|
||||||
if v == nil {
|
if v == nil {
|
||||||
return graphql.Null
|
return graphql.Null
|
||||||
|
|
|
@ -106,6 +106,10 @@ type QBitCleanupUnregistredResponse struct {
|
||||||
Hashes []string `json:"hashes"`
|
Hashes []string `json:"hashes"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type QBitTorrentDaemonFilter struct {
|
||||||
|
SourcesCount *IntFilter `json:"sourcesCount,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
type QBitTorrentDaemonMutation struct {
|
type QBitTorrentDaemonMutation struct {
|
||||||
Cleanup *QBitCleanupResponse `json:"cleanup"`
|
Cleanup *QBitCleanupResponse `json:"cleanup"`
|
||||||
CleanupUnregistred *QBitCleanupUnregistredResponse `json:"cleanupUnregistred"`
|
CleanupUnregistred *QBitCleanupUnregistredResponse `json:"cleanupUnregistred"`
|
||||||
|
|
|
@ -7,13 +7,14 @@ package resolver
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"slices"
|
||||||
|
|
||||||
graph "git.kmsign.ru/royalcat/tstor/src/delivery/graphql"
|
graph "git.kmsign.ru/royalcat/tstor/src/delivery/graphql"
|
||||||
"git.kmsign.ru/royalcat/tstor/src/delivery/graphql/model"
|
"git.kmsign.ru/royalcat/tstor/src/delivery/graphql/model"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Torrents is the resolver for the torrents field.
|
// Torrents is the resolver for the torrents field.
|
||||||
func (r *qBitTorrentDaemonQueryResolver) Torrents(ctx context.Context, obj *model.QBitTorrentDaemonQuery) ([]*model.QTorrent, error) {
|
func (r *qBitTorrentDaemonQueryResolver) Torrents(ctx context.Context, obj *model.QBitTorrentDaemonQuery, filter *model.QBitTorrentDaemonFilter) ([]*model.QTorrent, error) {
|
||||||
info, err := r.QBitTorrentDaemon.ListTorrents(ctx)
|
info, err := r.QBitTorrentDaemon.ListTorrents(ctx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("error listing torrents: %w", err)
|
return nil, fmt.Errorf("error listing torrents: %w", err)
|
||||||
|
@ -27,6 +28,21 @@ func (r *qBitTorrentDaemonQueryResolver) Torrents(ctx context.Context, obj *mode
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if filter != nil {
|
||||||
|
if filter.SourcesCount != nil {
|
||||||
|
for _, t := range out {
|
||||||
|
srcs, err := r.QBitTorrentDaemon.SourceFiles(ctx, t.Hash)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("hash %s failed listing source files: %w", t.Hash, err)
|
||||||
|
}
|
||||||
|
t.SourceFiles = srcs
|
||||||
|
}
|
||||||
|
|
||||||
|
out = slices.DeleteFunc(out, func(t *model.QTorrent) bool {
|
||||||
|
return !filter.SourcesCount.Include(int64(len(t.SourceFiles)))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
return out, nil
|
return out, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
15
src/delivery/graphql/resolver/utils.go
Normal file
15
src/delivery/graphql/resolver/utils.go
Normal file
|
@ -0,0 +1,15 @@
|
||||||
|
package resolver
|
||||||
|
|
||||||
|
import "iter"
|
||||||
|
|
||||||
|
func Filter1Func[K, V any](S iter.Seq2[K, V], match func(V) bool) iter.Seq2[K, V] {
|
||||||
|
return func(yield func(K, V) bool) {
|
||||||
|
for k, v := range S {
|
||||||
|
if match(v) {
|
||||||
|
if !yield(k, v) {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -94,10 +94,15 @@ type webDAVFile struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
func newFile(ctx context.Context, name string, f vfs.File, df func() ([]os.FileInfo, error)) *webDAVFile {
|
func newFile(ctx context.Context, name string, f vfs.File, df func() ([]os.FileInfo, error)) *webDAVFile {
|
||||||
|
info, err := f.Info()
|
||||||
|
if err != nil {
|
||||||
|
info = newFileInfo(name, f.Size(), f.IsDir())
|
||||||
|
}
|
||||||
|
|
||||||
return &webDAVFile{
|
return &webDAVFile{
|
||||||
ctx: ctx,
|
ctx: ctx,
|
||||||
f: f,
|
f: f,
|
||||||
fi: NewFileInfo(name, f.Size(), f.IsDir()),
|
fi: info,
|
||||||
dirFunc: df,
|
dirFunc: df,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -185,7 +190,7 @@ type webDAVFileInfo struct {
|
||||||
isDir bool
|
isDir bool
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewFileInfo(name string, size int64, isDir bool) *webDAVFileInfo {
|
func newFileInfo(name string, size int64, isDir bool) *webDAVFileInfo {
|
||||||
return &webDAVFileInfo{
|
return &webDAVFileInfo{
|
||||||
name: name,
|
name: name,
|
||||||
size: size,
|
size: size,
|
||||||
|
@ -210,8 +215,7 @@ func (wdfi *webDAVFileInfo) Mode() os.FileMode {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (wdfi *webDAVFileInfo) ModTime() time.Time {
|
func (wdfi *webDAVFileInfo) ModTime() time.Time {
|
||||||
// TODO fix it
|
return time.Time{}
|
||||||
return time.Now()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (wdfi *webDAVFileInfo) IsDir() bool {
|
func (wdfi *webDAVFileInfo) IsDir() bool {
|
||||||
|
|
|
@ -119,7 +119,7 @@ func NewArchive(ctx context.Context, archivePath, name string, f File, size int6
|
||||||
|
|
||||||
// FIXME configurable
|
// FIXME configurable
|
||||||
files["/.forcegallery"] = fileEntry{
|
files["/.forcegallery"] = fileEntry{
|
||||||
FileInfo: NewFileInfo("/.forcegallery", 0),
|
FileInfo: NewFileInfo("/.forcegallery", 0, time.Time{}),
|
||||||
open: func(ctx context.Context) (File, error) {
|
open: func(ctx context.Context) (File, error) {
|
||||||
return NewMemoryFile(".forcegallery", []byte{}), nil
|
return NewMemoryFile(".forcegallery", []byte{}), nil
|
||||||
},
|
},
|
||||||
|
@ -173,7 +173,7 @@ func (afs *ArchiveFS) Stat(ctx context.Context, filename string) (fs.FileInfo, e
|
||||||
|
|
||||||
for p, _ := range afs.files {
|
for p, _ := range afs.files {
|
||||||
if strings.HasPrefix(p, filename) {
|
if strings.HasPrefix(p, filename) {
|
||||||
return NewDirInfo(path.Base(filename)), nil
|
return NewDirInfo(path.Base(filename), time.Time{}), nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -245,7 +245,7 @@ func (d *archiveFile) Type() fs.FileMode {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (d *archiveFile) Info() (fs.FileInfo, error) {
|
func (d *archiveFile) Info() (fs.FileInfo, error) {
|
||||||
return NewFileInfo(d.name, d.size), nil
|
return NewFileInfo(d.name, d.size, time.Time{}), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (d *archiveFile) Size() int64 {
|
func (d *archiveFile) Size() int64 {
|
||||||
|
@ -431,7 +431,7 @@ func RarLoader(ctx context.Context, archivePath string, f File, size int64) (map
|
||||||
rr := newRandomReaderFromLinear(archiveFileIndex{archiveHash: hash, filename: header.Name}, header.UnPackedSize, af)
|
rr := newRandomReaderFromLinear(archiveFileIndex{archiveHash: hash, filename: header.Name}, header.UnPackedSize, af)
|
||||||
|
|
||||||
out[AbsPath(header.Name)] = fileEntry{
|
out[AbsPath(header.Name)] = fileEntry{
|
||||||
FileInfo: NewFileInfo(header.Name, header.UnPackedSize),
|
FileInfo: NewFileInfo(header.Name, header.UnPackedSize, header.ModificationTime),
|
||||||
open: func(ctx context.Context) (File, error) {
|
open: func(ctx context.Context) (File, error) {
|
||||||
return newArchiveFile(header.Name, header.UnPackedSize, rr), nil
|
return newArchiveFile(header.Name, header.UnPackedSize, rr), nil
|
||||||
},
|
},
|
||||||
|
|
|
@ -2,13 +2,14 @@ package vfs
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"io/fs"
|
"io/fs"
|
||||||
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
type DefaultFS string
|
type DefaultFS string
|
||||||
|
|
||||||
// Info implements Filesystem.
|
// Info implements Filesystem.
|
||||||
func (d DefaultFS) Info() (fs.FileInfo, error) {
|
func (d DefaultFS) Info() (fs.FileInfo, error) {
|
||||||
return NewDirInfo(string(d)), nil
|
return NewDirInfo(string(d), time.Time{}), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// IsDir implements Filesystem.
|
// IsDir implements Filesystem.
|
||||||
|
|
|
@ -4,6 +4,7 @@ import (
|
||||||
"context"
|
"context"
|
||||||
"io/fs"
|
"io/fs"
|
||||||
"path"
|
"path"
|
||||||
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
var _ File = &dirFile{}
|
var _ File = &dirFile{}
|
||||||
|
@ -25,7 +26,7 @@ func (d *dirFile) Close(ctx context.Context) error {
|
||||||
|
|
||||||
// Info implements File.
|
// Info implements File.
|
||||||
func (d *dirFile) Info() (fs.FileInfo, error) {
|
func (d *dirFile) Info() (fs.FileInfo, error) {
|
||||||
return NewDirInfo(d.name), nil
|
return NewDirInfo(d.name, time.Time{}), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// IsDir implements File.
|
// IsDir implements File.
|
||||||
|
|
|
@ -36,7 +36,7 @@ func (d *DummyFs) FsName() string {
|
||||||
|
|
||||||
// Stat implements Filesystem.
|
// Stat implements Filesystem.
|
||||||
func (*DummyFs) Stat(ctx context.Context, filename string) (fs.FileInfo, error) {
|
func (*DummyFs) Stat(ctx context.Context, filename string) (fs.FileInfo, error) {
|
||||||
return NewFileInfo(path.Base(filename), 0), nil // TODO
|
return NewFileInfo(path.Base(filename), 0, time.Time{}), nil // TODO
|
||||||
}
|
}
|
||||||
|
|
||||||
func (d *DummyFs) Open(ctx context.Context, filename string) (File, error) {
|
func (d *DummyFs) Open(ctx context.Context, filename string) (File, error) {
|
||||||
|
@ -50,8 +50,8 @@ func (d *DummyFs) Unlink(ctx context.Context, filename string) error {
|
||||||
func (d *DummyFs) ReadDir(ctx context.Context, path string) ([]fs.DirEntry, error) {
|
func (d *DummyFs) ReadDir(ctx context.Context, path string) ([]fs.DirEntry, error) {
|
||||||
if path == "/dir/here" {
|
if path == "/dir/here" {
|
||||||
return []fs.DirEntry{
|
return []fs.DirEntry{
|
||||||
NewFileInfo("file1.txt", 0),
|
NewFileInfo("file1.txt", 0, time.Time{}),
|
||||||
NewFileInfo("file2.txt", 0),
|
NewFileInfo("file2.txt", 0, time.Time{}),
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -60,7 +60,7 @@ func (d *DummyFs) ReadDir(ctx context.Context, path string) ([]fs.DirEntry, erro
|
||||||
|
|
||||||
// Info implements Filesystem.
|
// Info implements Filesystem.
|
||||||
func (d *DummyFs) Info() (fs.FileInfo, error) {
|
func (d *DummyFs) Info() (fs.FileInfo, error) {
|
||||||
return NewDirInfo(d.name), nil
|
return NewDirInfo(d.name, time.Time{}), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// IsDir implements Filesystem.
|
// IsDir implements Filesystem.
|
||||||
|
@ -101,7 +101,7 @@ func (d *DummyFile) Type() fs.FileMode {
|
||||||
|
|
||||||
// Stat implements File.
|
// Stat implements File.
|
||||||
func (d *DummyFile) Info() (fs.FileInfo, error) {
|
func (d *DummyFile) Info() (fs.FileInfo, error) {
|
||||||
return NewFileInfo(d.name, 0), nil
|
return NewFileInfo(d.name, 0, time.Time{}), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (d *DummyFile) Size() int64 {
|
func (d *DummyFile) Size() int64 {
|
||||||
|
|
|
@ -55,7 +55,7 @@ type fileInfo struct {
|
||||||
var _ fs.FileInfo = &fileInfo{}
|
var _ fs.FileInfo = &fileInfo{}
|
||||||
var _ fs.DirEntry = &fileInfo{}
|
var _ fs.DirEntry = &fileInfo{}
|
||||||
|
|
||||||
func NewDirInfo(name string) *fileInfo {
|
func NewDirInfo(name string, modTime time.Time) *fileInfo {
|
||||||
return &fileInfo{
|
return &fileInfo{
|
||||||
name: path.Base(name),
|
name: path.Base(name),
|
||||||
size: 0,
|
size: 0,
|
||||||
|
@ -63,7 +63,7 @@ func NewDirInfo(name string) *fileInfo {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewFileInfo(name string, size int64) *fileInfo {
|
func NewFileInfo(name string, size int64, modTime time.Time) *fileInfo {
|
||||||
return &fileInfo{
|
return &fileInfo{
|
||||||
name: path.Base(name),
|
name: path.Base(name),
|
||||||
size: size,
|
size: size,
|
||||||
|
@ -116,7 +116,7 @@ type FilesystemPrototype string
|
||||||
|
|
||||||
// Info implements Filesystem.
|
// Info implements Filesystem.
|
||||||
func (p FilesystemPrototype) Info() (fs.FileInfo, error) {
|
func (p FilesystemPrototype) Info() (fs.FileInfo, error) {
|
||||||
return NewDirInfo(string(p)), nil
|
return NewDirInfo(string(p), time.Time{}), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// IsDir implements Filesystem.
|
// IsDir implements Filesystem.
|
||||||
|
|
|
@ -3,6 +3,7 @@ package vfs
|
||||||
import (
|
import (
|
||||||
"io/fs"
|
"io/fs"
|
||||||
"testing"
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
)
|
)
|
||||||
|
@ -12,7 +13,7 @@ func TestFileinfo(t *testing.T) {
|
||||||
|
|
||||||
require := require.New(t)
|
require := require.New(t)
|
||||||
|
|
||||||
fi := NewFileInfo("abc/name", 42)
|
fi := NewFileInfo("abc/name", 42, time.Time{})
|
||||||
|
|
||||||
require.Equal("name", fi.Name())
|
require.Equal("name", fi.Name())
|
||||||
require.False(fi.IsDir())
|
require.False(fi.IsDir())
|
||||||
|
@ -29,7 +30,7 @@ func TestDirInfo(t *testing.T) {
|
||||||
|
|
||||||
require := require.New(t)
|
require := require.New(t)
|
||||||
|
|
||||||
fi := NewDirInfo("abc/name")
|
fi := NewDirInfo("abc/name", time.Time{})
|
||||||
|
|
||||||
require.True(fi.IsDir())
|
require.True(fi.IsDir())
|
||||||
require.Equal("name", fi.Name())
|
require.Equal("name", fi.Name())
|
||||||
|
|
|
@ -42,7 +42,7 @@ func (fs *MemoryFs) FsName() string {
|
||||||
|
|
||||||
// Info implements Filesystem.
|
// Info implements Filesystem.
|
||||||
func (fs *MemoryFs) Info() (fs.FileInfo, error) {
|
func (fs *MemoryFs) Info() (fs.FileInfo, error) {
|
||||||
return NewDirInfo(fs.name), nil
|
return NewDirInfo(fs.name, time.Time{}), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// IsDir implements Filesystem.
|
// IsDir implements Filesystem.
|
||||||
|
@ -86,7 +86,7 @@ func (mfs *MemoryFs) Stat(ctx context.Context, filename string) (fs.FileInfo, er
|
||||||
if !ok {
|
if !ok {
|
||||||
return nil, ErrNotExist
|
return nil, ErrNotExist
|
||||||
}
|
}
|
||||||
return NewFileInfo(path.Base(filename), file.Size()), nil
|
return NewFileInfo(path.Base(filename), file.Size(), time.Time{}), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Unlink implements Filesystem.
|
// Unlink implements Filesystem.
|
||||||
|
@ -124,7 +124,7 @@ func (d *MemoryFile) Type() fs.FileMode {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (d *MemoryFile) Info() (fs.FileInfo, error) {
|
func (d *MemoryFile) Info() (fs.FileInfo, error) {
|
||||||
return NewFileInfo(d.name, int64(d.data.Len())), nil
|
return NewFileInfo(d.name, int64(d.data.Len()), time.Time{}), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (d *MemoryFile) Size() int64 {
|
func (d *MemoryFile) Size() int64 {
|
||||||
|
|
|
@ -7,6 +7,7 @@ import (
|
||||||
"os"
|
"os"
|
||||||
"path"
|
"path"
|
||||||
"sync"
|
"sync"
|
||||||
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
type OsFS struct {
|
type OsFS struct {
|
||||||
|
@ -18,7 +19,7 @@ var _ Filesystem = (*OsFS)(nil)
|
||||||
// Stat implements Filesystem.
|
// Stat implements Filesystem.
|
||||||
func (fs *OsFS) Stat(ctx context.Context, filename string) (fs.FileInfo, error) {
|
func (fs *OsFS) Stat(ctx context.Context, filename string) (fs.FileInfo, error) {
|
||||||
if path.Clean(filename) == Separator {
|
if path.Clean(filename) == Separator {
|
||||||
return NewDirInfo(Separator), nil
|
return NewDirInfo(Separator, time.Time{}), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
info, err := os.Stat(path.Join(fs.hostDir, filename))
|
info, err := os.Stat(path.Join(fs.hostDir, filename))
|
||||||
|
@ -58,7 +59,7 @@ func (o *OsFS) ReadDir(ctx context.Context, dir string) ([]fs.DirEntry, error) {
|
||||||
|
|
||||||
// Info implements Filesystem.
|
// Info implements Filesystem.
|
||||||
func (fs *OsFS) Info() (fs.FileInfo, error) {
|
func (fs *OsFS) Info() (fs.FileInfo, error) {
|
||||||
return NewDirInfo(fs.Name()), nil
|
return NewDirInfo(fs.Name(), time.Time{}), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// IsDir implements Filesystem.
|
// IsDir implements Filesystem.
|
||||||
|
|
|
@ -389,11 +389,16 @@ func ListDirFromFiles[F File](m map[string]F, name string) ([]fs.DirEntry, error
|
||||||
name = AddTrailSlash(path.Clean(name))
|
name = AddTrailSlash(path.Clean(name))
|
||||||
for p, f := range m {
|
for p, f := range m {
|
||||||
if strings.HasPrefix(p, name) {
|
if strings.HasPrefix(p, name) {
|
||||||
|
stat, err := f.Info()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
parts := strings.Split(trimRelPath(p, name), Separator)
|
parts := strings.Split(trimRelPath(p, name), Separator)
|
||||||
if len(parts) == 1 {
|
if len(parts) == 1 {
|
||||||
out = append(out, NewFileInfo(parts[0], f.Size()))
|
out = append(out, NewFileInfo(parts[0], f.Size(), stat.ModTime()))
|
||||||
} else {
|
} else {
|
||||||
out = append(out, NewDirInfo(parts[0]))
|
out = append(out, NewDirInfo(parts[0], stat.ModTime()))
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -413,11 +418,12 @@ func ListDirFromInfo(m map[string]fs.FileInfo, name string) ([]fs.DirEntry, erro
|
||||||
name = AddTrailSlash(path.Clean(name))
|
name = AddTrailSlash(path.Clean(name))
|
||||||
for p, f := range m {
|
for p, f := range m {
|
||||||
if strings.HasPrefix(p, name) {
|
if strings.HasPrefix(p, name) {
|
||||||
|
|
||||||
parts := strings.Split(trimRelPath(p, name), Separator)
|
parts := strings.Split(trimRelPath(p, name), Separator)
|
||||||
if len(parts) == 1 {
|
if len(parts) == 1 {
|
||||||
out = append(out, NewFileInfo(parts[0], f.Size()))
|
out = append(out, NewFileInfo(parts[0], f.Size(), f.ModTime()))
|
||||||
} else {
|
} else {
|
||||||
out = append(out, NewDirInfo(parts[0]))
|
out = append(out, NewDirInfo(parts[0], f.ModTime()))
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -65,12 +65,15 @@ type QBitCleanupUnregistredResponse {
|
||||||
count: Int!
|
count: Int!
|
||||||
hashes: [String!]!
|
hashes: [String!]!
|
||||||
}
|
}
|
||||||
|
input QBitTorrentDaemonFilter {
|
||||||
|
multipleSources: Boolean
|
||||||
|
}
|
||||||
type QBitTorrentDaemonMutation {
|
type QBitTorrentDaemonMutation {
|
||||||
cleanup(run: Boolean!): QBitCleanupResponse! @resolver
|
cleanup(run: Boolean!): QBitCleanupResponse! @resolver
|
||||||
cleanupUnregistred(run: Boolean!): QBitCleanupUnregistredResponse! @resolver
|
cleanupUnregistred(run: Boolean!): QBitCleanupUnregistredResponse! @resolver
|
||||||
}
|
}
|
||||||
type QBitTorrentDaemonQuery {
|
type QBitTorrentDaemonQuery {
|
||||||
torrents: [QTorrent!]! @resolver
|
torrents(filter: QBitTorrentDaemonFilter): [QTorrent!]! @resolver
|
||||||
}
|
}
|
||||||
type QTorrent {
|
type QTorrent {
|
||||||
name: String!
|
name: String!
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue