chore: Refactor code to use SourceUpdater struct for managing sources

This commit is contained in:
royalcat 2024-05-18 10:24:14 +03:00
parent fa1fdcfc63
commit 99cdd5471e
12 changed files with 328 additions and 263 deletions

View file

@ -93,9 +93,7 @@ func (r *mutationResolver) DownloadTorrent(ctx context.Context, infohash string,
// UploadFile is the resolver for the uploadFile field.
func (r *mutationResolver) UploadFile(ctx context.Context, dir string, file graphql.Upload) (bool, error) {
dir = pathlib.Join(r.Service.SourceDir, dir)
dirInfo, err := os.Stat(dir)
dirInfo, err := r.SourceFS.Stat(dir)
if err != nil {
return false, err
}
@ -105,7 +103,7 @@ func (r *mutationResolver) UploadFile(ctx context.Context, dir string, file grap
}
filename := pathlib.Join(dir, file.Filename)
target, err := os.OpenFile(filename, os.O_CREATE|os.O_WRONLY, os.ModePerm)
target, err := r.SourceFS.OpenFile(filename, os.O_CREATE|os.O_WRONLY, os.ModePerm)
defer target.Close()
if err != nil {
return false, err

View file

@ -3,6 +3,7 @@ package resolver
import (
"git.kmsign.ru/royalcat/tstor/src/host/service"
"git.kmsign.ru/royalcat/tstor/src/host/vfs"
"github.com/go-git/go-billy/v5"
)
// This file will not be regenerated automatically.
@ -10,6 +11,7 @@ import (
// It serves as dependency injection for your app, add any dependencies you require here.
type Resolver struct {
Service *service.Service
VFS vfs.Filesystem
Service *service.Service
VFS vfs.Filesystem
SourceFS billy.Filesystem
}

View file

@ -6,6 +6,10 @@ import (
"github.com/lrstanley/go-ytdlp"
)
type SourceUpdater struct {
sources []VirtDirSource
}
type SourcedDirSource string
const (

View file

@ -17,7 +17,7 @@ type TorrentDownloadTask struct {
}
func (s *Service) Download(ctx context.Context, task *TorrentDownloadTask) error {
t, ok := s.c.Torrent(task.InfoHash)
t, ok := s.client.Torrent(task.InfoHash)
if !ok {
return fmt.Errorf("torrent with IH %s not found", task.InfoHash.HexString())
}

View file

@ -11,6 +11,7 @@ import (
"slices"
"strings"
"sync"
"time"
"git.kmsign.ru/royalcat/tstor/pkg/ctxio"
"git.kmsign.ru/royalcat/tstor/pkg/rlog"
@ -28,8 +29,9 @@ import (
"github.com/anacrolix/torrent"
"github.com/anacrolix/torrent/bencode"
"github.com/anacrolix/torrent/metainfo"
"github.com/anacrolix/torrent/types"
"github.com/anacrolix/torrent/types/infohash"
"github.com/go-git/go-billy/v5"
"github.com/go-git/go-billy/v5/util"
"github.com/royalcat/kv"
)
@ -41,45 +43,68 @@ type DirAquire struct {
}
type Service struct {
c *torrent.Client
client *torrent.Client
excludedFiles *store.FilesMappings
infoBytes *store.InfoBytes
Storage *datastorage.DataStorage
fis *store.FileItemStore
dirsAquire kv.Store[string, DirAquire]
loadMutex sync.Mutex
torrentLoaded chan struct{}
loadMutex sync.Mutex
// stats *Stats
DefaultPriority types.PiecePriority
Storage *datastorage.DataStorage
SourceDir string
dirsAquire kv.Store[string, DirAquire]
sourceFs billy.Filesystem
log *rlog.Logger
}
func NewService(sourceDir string, cfg config.TorrentClient, c *torrent.Client,
storage *datastorage.DataStorage, excludedFiles *store.FilesMappings, infoBytes *store.InfoBytes,
) (*Service, error) {
dirsAcquire, err := tkv.New[string, DirAquire](cfg.MetadataFolder, "dir-acquire")
func New(sourceFs billy.Filesystem, conf config.TorrentClient) (*Service, error) {
s := &Service{
log: rlog.Component("torrent-service"),
sourceFs: sourceFs,
torrentLoaded: make(chan struct{}),
loadMutex: sync.Mutex{},
}
err := os.MkdirAll(conf.MetadataFolder, 0744)
if err != nil {
return nil, fmt.Errorf("error creating metadata folder: %w", err)
}
s.fis, err = store.NewFileItemStore(filepath.Join(conf.MetadataFolder, "items"), 2*time.Hour)
if err != nil {
return nil, fmt.Errorf("error starting item store: %w", err)
}
s.Storage, _, err = datastorage.Setup(conf)
if err != nil {
return nil, err
}
s := &Service{
log: rlog.Component("torrent-service"),
c: c,
DefaultPriority: types.PiecePriorityNone,
excludedFiles: excludedFiles,
infoBytes: infoBytes,
Storage: storage,
SourceDir: sourceDir,
torrentLoaded: make(chan struct{}),
loadMutex: sync.Mutex{},
dirsAquire: dirsAcquire,
s.excludedFiles, err = store.NewFileMappings(conf.MetadataFolder, s.Storage)
if err != nil {
return nil, err
}
// stats: newStats(), // TODO persistent
s.infoBytes, err = store.NewInfoBytes(conf.MetadataFolder)
if err != nil {
return nil, err
}
id, err := store.GetOrCreatePeerID(filepath.Join(conf.MetadataFolder, "ID"))
if err != nil {
return nil, fmt.Errorf("error creating node ID: %w", err)
}
client, err := store.NewClient(s.Storage, s.fis, &conf, id)
if err != nil {
return nil, fmt.Errorf("error starting torrent client: %w", err)
}
client.AddDhtNodes(conf.DHTNodes)
s.dirsAquire, err = tkv.New[string, DirAquire](conf.MetadataFolder, "dir-acquire")
if err != nil {
return nil, err
}
go func() {
@ -96,11 +121,14 @@ func NewService(sourceDir string, cfg config.TorrentClient, c *torrent.Client,
var _ vfs.FsFactory = (*Service)(nil).NewTorrentFs
func (s *Service) Close() error {
func (s *Service) Close(ctx context.Context) error {
return errors.Join(append(
s.c.Close(),
s.client.Close(),
s.Storage.Close(),
s.dirsAquire.Close(ctx),
s.excludedFiles.Close(ctx),
s.infoBytes.Close(),
s.fis.Close(),
)...)
}
@ -123,7 +151,7 @@ func (s *Service) LoadTorrent(ctx context.Context, f vfs.File) (*torrent.Torrent
return nil, fmt.Errorf("loading torrent metadata from file %s, error: %w", stat.Name(), err)
}
t, ok := s.c.Torrent(mi.HashInfoBytes())
t, ok := s.client.Torrent(mi.HashInfoBytes())
if !ok {
span.AddEvent("torrent not found, loading from file")
@ -148,7 +176,7 @@ func (s *Service) LoadTorrent(ctx context.Context, f vfs.File) (*torrent.Torrent
}
}
t, _ = s.c.AddTorrentOpt(torrent.AddTorrentOpts{
t, _ = s.client.AddTorrentOpt(torrent.AddTorrentOpts{
InfoHash: spec.InfoHash,
Storage: s.Storage,
InfoBytes: infoBytes,
@ -223,7 +251,7 @@ func (s *Service) checkTorrentCompatable(ctx context.Context, ih infohash.T, inf
return true, false, nil
}
for _, existingTorrent := range s.c.Torrents() {
for _, existingTorrent := range s.client.Torrents() {
if existingTorrent.Name() != name || existingTorrent.InfoHash() == ih {
continue
}
@ -344,7 +372,7 @@ func (s *Service) Stats() (*Stats, error) {
}
func (s *Service) GetStats() torrent.ConnStats {
return s.c.ConnStats()
return s.client.ConnStats()
}
const loadWorkers = 5
@ -386,7 +414,7 @@ func (s *Service) loadTorrentFiles(ctx context.Context) error {
go loaderWorker()
}
return filepath.Walk(s.SourceDir, func(path string, info os.FileInfo, err error) error {
return util.Walk(s.sourceFs, "/", func(path string, info os.FileInfo, err error) error {
if err != nil {
return fmt.Errorf("fs walk error: %w", err)
}
@ -411,7 +439,7 @@ func (s *Service) ListTorrents(ctx context.Context) ([]*controller.Torrent, erro
<-s.torrentLoaded
out := []*controller.Torrent{}
for _, v := range s.c.Torrents() {
for _, v := range s.client.Torrents() {
out = append(out, controller.NewTorrent(v, s.excludedFiles))
}
return out, nil
@ -420,7 +448,7 @@ func (s *Service) ListTorrents(ctx context.Context) ([]*controller.Torrent, erro
func (s *Service) GetTorrent(infohashHex string) (*controller.Torrent, error) {
<-s.torrentLoaded
t, ok := s.c.Torrent(infohash.FromHexString(infohashHex))
t, ok := s.client.Torrent(infohash.FromHexString(infohashHex))
if !ok {
return nil, nil
}

View file

@ -5,7 +5,7 @@ import (
"git.kmsign.ru/royalcat/tstor/src/host/vfs"
)
func NewTorrentStorage(dataPath string, tsrv *service.Service) vfs.Filesystem {
func NewTorrentStorage(sourceFS vfs.Filesystem, tsrv *service.Service) vfs.Filesystem {
factories := map[string]vfs.FsFactory{
".torrent": tsrv.NewTorrentFs,
}
@ -15,5 +15,5 @@ func NewTorrentStorage(dataPath string, tsrv *service.Service) vfs.Filesystem {
factories[k] = v
}
return vfs.NewResolveFS(vfs.NewOsFs(dataPath), factories)
return vfs.NewResolveFS(sourceFS, factories)
}

View file

@ -55,3 +55,7 @@ func (r *FilesMappings) FileMappings(ctx context.Context, ih infohash.T) (map[st
})
return out, err
}
func (r *FilesMappings) Close(ctx context.Context) error {
return r.mappings.Close(ctx)
}

146
src/host/vfs/ctxbillyfs.go Normal file
View file

@ -0,0 +1,146 @@
package vfs
import (
"context"
"io/fs"
"git.kmsign.ru/royalcat/tstor/pkg/ctxbilly"
)
func NewCtxBillyFs(name string, fs ctxbilly.Filesystem) *CtxBillyFs {
return &CtxBillyFs{
name: name,
fs: fs,
}
}
type CtxBillyFs struct {
name string
fs ctxbilly.Filesystem
}
var _ Filesystem = (*CtxBillyFs)(nil)
// Info implements Filesystem.
func (c *CtxBillyFs) Info() (fs.FileInfo, error) {
return c.fs.Stat(context.Background(), "")
}
// IsDir implements Filesystem.
func (c *CtxBillyFs) IsDir() bool {
return false
}
// Name implements Filesystem.
func (c *CtxBillyFs) Name() string {
return c.name
}
// Open implements Filesystem.
func (c *CtxBillyFs) Open(ctx context.Context, filename string) (File, error) {
info, err := c.fs.Stat(ctx, filename)
if err != nil {
return nil, err
}
bf, err := c.fs.Open(ctx, filename)
if err != nil {
return nil, err
}
return &CtxBillyFile{
info: info,
file: bf,
}, nil
}
// ReadDir implements Filesystem.
func (c *CtxBillyFs) ReadDir(ctx context.Context, path string) ([]fs.DirEntry, error) {
infos, err := c.fs.ReadDir(ctx, path)
if err != nil {
return nil, err
}
entries := make([]fs.DirEntry, 0, len(infos))
for _, i := range infos {
entries = append(entries, &infoEntry{i})
}
return entries, nil
}
type infoEntry struct {
fs.FileInfo
}
var _ fs.DirEntry = (*infoEntry)(nil)
// Info implements fs.DirEntry.
func (i *infoEntry) Info() (fs.FileInfo, error) {
return i.FileInfo, nil
}
// Type implements fs.DirEntry.
func (i *infoEntry) Type() fs.FileMode {
return i.FileInfo.Mode()
}
// Stat implements Filesystem.
func (c *CtxBillyFs) Stat(ctx context.Context, filename string) (fs.FileInfo, error) {
return c.fs.Stat(ctx, filename)
}
// Type implements Filesystem.
func (c *CtxBillyFs) Type() fs.FileMode {
return fs.ModeDir
}
// Unlink implements Filesystem.
func (c *CtxBillyFs) Unlink(ctx context.Context, filename string) error {
return fs.ErrInvalid
}
var _ File = (*CtxBillyFile)(nil)
type CtxBillyFile struct {
info fs.FileInfo
file ctxbilly.File
}
// Close implements File.
func (c *CtxBillyFile) Close(ctx context.Context) error {
return c.file.Close(ctx)
}
// Info implements File.
func (c *CtxBillyFile) Info() (fs.FileInfo, error) {
return c.info, nil
}
// IsDir implements File.
func (c *CtxBillyFile) IsDir() bool {
return c.info.IsDir()
}
// Name implements File.
func (c *CtxBillyFile) Name() string {
return c.file.Name()
}
// Read implements File.
func (c *CtxBillyFile) Read(ctx context.Context, p []byte) (n int, err error) {
return c.file.Read(ctx, p)
}
// ReadAt implements File.
func (c *CtxBillyFile) ReadAt(ctx context.Context, p []byte, off int64) (n int, err error) {
return c.file.ReadAt(ctx, p, off)
}
// Size implements File.
func (c *CtxBillyFile) Size() int64 {
return c.info.Size()
}
// Type implements File.
func (c *CtxBillyFile) Type() fs.FileMode {
return c.info.Mode()
}