Big refactor
This commit is contained in:
@@ -1,8 +1,10 @@
|
||||
package torrent
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"encoding/gob"
|
||||
"fmt"
|
||||
"hash/fnv"
|
||||
"math"
|
||||
"os"
|
||||
"path/filepath"
|
||||
@@ -19,6 +21,7 @@ import (
|
||||
|
||||
type TorrentManager struct {
|
||||
config config.ConfigInterface
|
||||
DirectoryMap *orderedmap.OrderedMap[string, *orderedmap.OrderedMap[string, *Torrent]]
|
||||
TorrentMap *orderedmap.OrderedMap[string, *Torrent] // accessKey -> Torrent
|
||||
repairMap *orderedmap.OrderedMap[string, time.Time] // accessKey -> time last repaired
|
||||
requiredVersion string
|
||||
@@ -35,6 +38,7 @@ type TorrentManager struct {
|
||||
func NewTorrentManager(config config.ConfigInterface, api *realdebrid.RealDebrid) *TorrentManager {
|
||||
t := &TorrentManager{
|
||||
config: config,
|
||||
DirectoryMap: orderedmap.NewOrderedMap[string, *orderedmap.OrderedMap[string, *Torrent]](),
|
||||
TorrentMap: orderedmap.NewOrderedMap[string, *Torrent](),
|
||||
repairMap: orderedmap.NewOrderedMap[string, time.Time](),
|
||||
requiredVersion: "10.11.2023",
|
||||
@@ -44,8 +48,6 @@ func NewTorrentManager(config config.ConfigInterface, api *realdebrid.RealDebrid
|
||||
log: logutil.NewLogger().Named("manager"),
|
||||
}
|
||||
|
||||
t.mu.Lock()
|
||||
|
||||
newTorrents, _, err := t.api.GetTorrents(0)
|
||||
if err != nil {
|
||||
t.log.Fatalf("Cannot get torrents: %v\n", err)
|
||||
@@ -62,28 +64,34 @@ func NewTorrentManager(config config.ConfigInterface, api *realdebrid.RealDebrid
|
||||
<-t.workerPool
|
||||
}(i)
|
||||
}
|
||||
t.log.Infof("Got %d torrents", len(newTorrents))
|
||||
t.log.Infof("Received %d torrents", len(newTorrents))
|
||||
wg.Wait()
|
||||
t.log.Infof("Fetched info for %d torrents", len(newTorrents))
|
||||
close(torrentsChan)
|
||||
count := 0
|
||||
for newTorrent := range torrentsChan {
|
||||
if newTorrent == nil {
|
||||
count++
|
||||
continue
|
||||
}
|
||||
torrent, _ := t.TorrentMap.Get(newTorrent.AccessKey)
|
||||
if torrent != nil {
|
||||
t.mu.Lock()
|
||||
t.TorrentMap.Set(newTorrent.AccessKey, t.mergeToMain(torrent, newTorrent))
|
||||
t.mu.Unlock()
|
||||
} else {
|
||||
t.mu.Lock()
|
||||
t.TorrentMap.Set(newTorrent.AccessKey, newTorrent)
|
||||
t.mu.Unlock()
|
||||
}
|
||||
}
|
||||
t.log.Infof("Compiled to %d unique movies and shows", t.TorrentMap.Len())
|
||||
t.log.Infof("Compiled all torrents to %d unique movies and shows, %d were missing info", t.TorrentMap.Len(), count)
|
||||
t.checksum = t.getChecksum()
|
||||
t.mu.Unlock()
|
||||
|
||||
if t.config.EnableRepair() {
|
||||
go t.repairAll()
|
||||
}
|
||||
go t.startRefreshJob()
|
||||
// go t.startRefreshJob()
|
||||
|
||||
return t
|
||||
}
|
||||
@@ -194,8 +202,6 @@ func (t *TorrentManager) startRefreshJob() {
|
||||
continue
|
||||
}
|
||||
|
||||
t.mu.Lock()
|
||||
|
||||
newTorrents, _, err := t.api.GetTorrents(0)
|
||||
if err != nil {
|
||||
t.log.Warnf("Cannot get torrents: %v\n", err)
|
||||
@@ -231,6 +237,15 @@ func (t *TorrentManager) startRefreshJob() {
|
||||
}
|
||||
for _, accessKey := range toDelete {
|
||||
t.TorrentMap.Delete(accessKey)
|
||||
for el := t.DirectoryMap.Front(); el != nil; el = el.Next() {
|
||||
torrents := el.Value
|
||||
for el2 := torrents.Front(); el2 != nil; el2 = el2.Next() {
|
||||
if el2.Key == accessKey {
|
||||
torrents.Delete(accessKey)
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
wg.Wait()
|
||||
@@ -241,13 +256,16 @@ func (t *TorrentManager) startRefreshJob() {
|
||||
}
|
||||
torrent, _ := t.TorrentMap.Get(newTorrent.AccessKey)
|
||||
if torrent != nil {
|
||||
t.mu.Lock()
|
||||
t.TorrentMap.Set(newTorrent.AccessKey, t.mergeToMain(torrent, newTorrent))
|
||||
t.mu.Unlock()
|
||||
} else {
|
||||
t.mu.Lock()
|
||||
t.TorrentMap.Set(newTorrent.AccessKey, newTorrent)
|
||||
t.mu.Unlock()
|
||||
}
|
||||
}
|
||||
t.checksum = t.getChecksum()
|
||||
t.mu.Unlock()
|
||||
|
||||
if t.config.EnableRepair() {
|
||||
go t.repairAll()
|
||||
@@ -293,9 +311,10 @@ func (t *TorrentManager) getMoreInfo(rdTorrent realdebrid.Torrent) *Torrent {
|
||||
continue
|
||||
}
|
||||
selectedFiles.Set(filepath.Base(file.Path), &File{
|
||||
File: file,
|
||||
Added: info.Added,
|
||||
Link: "", // no link yet
|
||||
File: file,
|
||||
Added: info.Added,
|
||||
Link: "", // no link yet
|
||||
ZurgFS: hashStringToFh(file.Path + info.Hash),
|
||||
})
|
||||
}
|
||||
if selectedFiles.Len() > len(info.Links) && info.Progress == 100 {
|
||||
@@ -304,7 +323,7 @@ func (t *TorrentManager) getMoreInfo(rdTorrent realdebrid.Torrent) *Torrent {
|
||||
var isChaotic bool
|
||||
selectedFiles, isChaotic = t.organizeChaos(info.Links, selectedFiles)
|
||||
if isChaotic {
|
||||
// t.log.Warnf("Torrent id=%s %s is unfixable, it is always returning an unstreamable link (it is no longer shown in your directories)", info.ID, info.Name)
|
||||
t.log.Warnf("Torrent id=%s %s is unrepairable, it is always returning a rar file (it will no longer show up in your directories)", info.ID, info.Name)
|
||||
// t.log.Debugf("You can try fixing it yourself magnet:?xt=urn:btih:%s", info.Hash)
|
||||
return nil
|
||||
} else {
|
||||
@@ -316,7 +335,7 @@ func (t *TorrentManager) getMoreInfo(rdTorrent realdebrid.Torrent) *Torrent {
|
||||
t.log.Infof("Torrent id=%s %s marked for repair", info.ID, info.Name)
|
||||
forRepair = true
|
||||
} else {
|
||||
// t.log.Warnf("Torrent id=%s %s is unfixable, the lone streamable link has expired (it is no longer shown in your directories)", info.ID, info.Name)
|
||||
t.log.Warnf("Torrent id=%s %s is unrepairable, the lone streamable link has expired (it will no longer show up in your directories)", info.ID, info.Name)
|
||||
// t.log.Debugf("You can try fixing it yourself magnet:?xt=urn:btih:%s", info.Hash)
|
||||
return nil
|
||||
}
|
||||
@@ -344,12 +363,31 @@ func (t *TorrentManager) getMoreInfo(rdTorrent realdebrid.Torrent) *Torrent {
|
||||
InProgress: info.Progress != 100,
|
||||
Instances: []realdebrid.TorrentInfo{*info},
|
||||
}
|
||||
for _, directory := range torrent.Directories {
|
||||
if _, ok := t.DirectoryMap.Get(directory); !ok {
|
||||
newMap := orderedmap.NewOrderedMap[string, *Torrent]()
|
||||
t.mu.Lock()
|
||||
t.DirectoryMap.Set(directory, newMap)
|
||||
t.mu.Unlock()
|
||||
} else {
|
||||
torrents, _ := t.DirectoryMap.Get(directory)
|
||||
t.mu.Lock()
|
||||
torrents.Set(torrent.AccessKey, &torrent)
|
||||
t.mu.Unlock()
|
||||
}
|
||||
}
|
||||
if selectedFiles.Len() > 0 && torrentFromFile == nil {
|
||||
t.writeToFile(info) // only when there are selected files, else it's useless
|
||||
}
|
||||
return &torrent
|
||||
}
|
||||
|
||||
func hashStringToFh(s string) (fh uint64) {
|
||||
hasher := fnv.New64a()
|
||||
_, _ = hasher.Write([]byte(s)) // Write the string to the hasher; ignoring error as it never returns an error
|
||||
return hasher.Sum64() // Returns a 64-bit hash value
|
||||
}
|
||||
|
||||
func (t *TorrentManager) getName(name, originalName string) string {
|
||||
// drop the extension from the name
|
||||
if t.config.EnableRetainFolderNameExtension() && strings.Contains(name, originalName) {
|
||||
@@ -393,41 +431,40 @@ func (t *TorrentManager) getDirectories(torrent *realdebrid.TorrentInfo) []strin
|
||||
return ret
|
||||
}
|
||||
|
||||
func (t *TorrentManager) writeToFile(torrent *realdebrid.TorrentInfo) {
|
||||
filePath := fmt.Sprintf("data/%s.bin", torrent.ID)
|
||||
func (t *TorrentManager) writeToFile(torrent *realdebrid.TorrentInfo) error {
|
||||
filePath := "data/" + torrent.ID + ".bin"
|
||||
file, err := os.Create(filePath)
|
||||
if err != nil {
|
||||
t.log.Fatalf("Failed creating file: %s", err)
|
||||
return
|
||||
return fmt.Errorf("failed creating file: %w", err)
|
||||
}
|
||||
defer file.Close()
|
||||
|
||||
w := bufio.NewWriter(file)
|
||||
defer w.Flush()
|
||||
|
||||
torrent.Version = t.requiredVersion
|
||||
dataEncoder := gob.NewEncoder(file)
|
||||
dataEncoder.Encode(torrent)
|
||||
dataEncoder := gob.NewEncoder(w)
|
||||
if err := dataEncoder.Encode(torrent); err != nil {
|
||||
return fmt.Errorf("failed encoding torrent: %w", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (t *TorrentManager) readFromFile(torrentID string) *realdebrid.TorrentInfo {
|
||||
filePath := fmt.Sprintf("data/%s.bin", torrentID)
|
||||
fileInfo, err := os.Stat(filePath)
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
if time.Since(fileInfo.ModTime()) > time.Duration(t.config.GetCacheTimeHours())*time.Hour {
|
||||
return nil
|
||||
}
|
||||
|
||||
filePath := "data/" + torrentID + ".bin"
|
||||
file, err := os.Open(filePath)
|
||||
if err != nil {
|
||||
if os.IsNotExist(err) {
|
||||
return nil
|
||||
}
|
||||
return nil
|
||||
}
|
||||
defer file.Close()
|
||||
|
||||
r := bufio.NewReader(file)
|
||||
var torrent realdebrid.TorrentInfo
|
||||
dataDecoder := gob.NewDecoder(file)
|
||||
err = dataDecoder.Decode(&torrent)
|
||||
if err != nil {
|
||||
dataDecoder := gob.NewDecoder(r)
|
||||
if err := dataDecoder.Decode(&torrent); err != nil {
|
||||
return nil
|
||||
}
|
||||
if torrent.Version != t.requiredVersion {
|
||||
@@ -482,8 +519,9 @@ func (t *TorrentManager) organizeChaos(links []string, selectedFiles *orderedmap
|
||||
Bytes: result.Response.Filesize,
|
||||
Selected: 1,
|
||||
},
|
||||
Added: time.Now().Format(time.RFC3339),
|
||||
Link: result.Response.Link,
|
||||
Added: time.Now().Format(time.RFC3339),
|
||||
Link: result.Response.Link,
|
||||
ZurgFS: hashStringToFh(result.Response.Filename),
|
||||
})
|
||||
} else {
|
||||
isChaotic = true
|
||||
@@ -495,6 +533,7 @@ func (t *TorrentManager) organizeChaos(links []string, selectedFiles *orderedmap
|
||||
}
|
||||
|
||||
func (t *TorrentManager) repairAll() {
|
||||
t.log.Info("Checking for torrents to repair")
|
||||
// side note: iteration works!
|
||||
for el := t.TorrentMap.Front(); el != nil; el = el.Next() {
|
||||
torrent := el.Value
|
||||
@@ -515,13 +554,15 @@ func (t *TorrentManager) repairAll() {
|
||||
if !forRepair {
|
||||
// if it was marked for repair, unmark it
|
||||
torrent.ForRepair = false
|
||||
t.mu.Lock()
|
||||
t.TorrentMap.Set(torrent.AccessKey, torrent)
|
||||
t.mu.Unlock()
|
||||
continue
|
||||
}
|
||||
|
||||
// when getting info, we mark it for repair if it's missing some links
|
||||
if torrent.ForRepair {
|
||||
t.log.Infof("Found torrent for repair %s", torrent.AccessKey)
|
||||
t.log.Infof("Found torrent for repair: %s", torrent.AccessKey)
|
||||
t.Repair(torrent.AccessKey)
|
||||
break // only repair the first one for repair and then move on
|
||||
}
|
||||
@@ -534,7 +575,9 @@ func (t *TorrentManager) Repair(accessKey string) {
|
||||
return
|
||||
}
|
||||
}
|
||||
t.mu.Lock()
|
||||
t.repairMap.Set(accessKey, time.Now())
|
||||
t.mu.Unlock()
|
||||
|
||||
if !t.config.EnableRepair() {
|
||||
t.log.Warn("Repair is disabled; if you do not have other zurg instances running, you should enable repair")
|
||||
@@ -569,7 +612,9 @@ func (t *TorrentManager) Repair(accessKey string) {
|
||||
}
|
||||
selectedFiles, _ := t.organizeChaos(links, torrent.SelectedFiles)
|
||||
torrent.SelectedFiles = selectedFiles
|
||||
t.mu.Lock()
|
||||
t.TorrentMap.Set(torrent.AccessKey, torrent)
|
||||
t.mu.Unlock()
|
||||
|
||||
// first solution: add the same selection, maybe it can be fixed by reinsertion?
|
||||
if t.reinsertTorrent(torrent, "") {
|
||||
|
||||
@@ -18,6 +18,7 @@ type Torrent struct {
|
||||
|
||||
type File struct {
|
||||
realdebrid.File
|
||||
Added string
|
||||
Link string
|
||||
Added string
|
||||
Link string
|
||||
ZurgFS uint64
|
||||
}
|
||||
|
||||
@@ -160,8 +160,8 @@ func createErrorFile(path, link string) *intTor.File {
|
||||
return &ret
|
||||
}
|
||||
|
||||
func GetFileReader(torrent *intTor.Torrent, file *intTor.File, offset int64, size int, t *intTor.TorrentManager, c config.ConfigInterface, log *zap.SugaredLogger) io.ReadCloser {
|
||||
unres := t.UnrestrictUntilOk(file.Link)
|
||||
func GetFileReader(torrent *intTor.Torrent, file *intTor.File, offset int64, size int, torMgr *intTor.TorrentManager, cfg config.ConfigInterface, log *zap.SugaredLogger) []byte {
|
||||
unres := torMgr.UnrestrictUntilOk(file.Link)
|
||||
if unres == nil {
|
||||
if strings.Contains(file.Link, "www.youtube.com") {
|
||||
log.Errorf("Even the error page is broken! Sorry!")
|
||||
@@ -169,10 +169,10 @@ func GetFileReader(torrent *intTor.Torrent, file *intTor.File, offset int64, siz
|
||||
}
|
||||
log.Warnf("File %s is no longer available, torrent is marked for repair", file.Path)
|
||||
if torrent != nil {
|
||||
go t.Repair(torrent.AccessKey)
|
||||
go torMgr.Repair(torrent.AccessKey)
|
||||
}
|
||||
errFile := createErrorFile("unavailable.mp4", "https://www.youtube.com/watch?v=gea_FJrtFVA")
|
||||
return GetFileReader(nil, errFile, 0, 0, t, c, log)
|
||||
return GetFileReader(nil, errFile, 0, 0, torMgr, cfg, log)
|
||||
}
|
||||
|
||||
req, err := http.NewRequest(http.MethodGet, unres.Download, nil)
|
||||
@@ -183,15 +183,15 @@ func GetFileReader(torrent *intTor.Torrent, file *intTor.File, offset int64, siz
|
||||
}
|
||||
log.Errorf("Error creating new request: %v", err)
|
||||
errFile := createErrorFile("new_request.mp4", "https://www.youtube.com/watch?v=H3NSrObyAxM")
|
||||
return GetFileReader(nil, errFile, 0, 0, t, c, log)
|
||||
return GetFileReader(nil, errFile, 0, 0, torMgr, cfg, log)
|
||||
}
|
||||
|
||||
if size == 0 {
|
||||
size = int(file.Bytes)
|
||||
}
|
||||
req.Header.Add("Range", fmt.Sprintf("bytes=%v-%v", offset, size-1))
|
||||
req.Header.Add("Range", fmt.Sprintf("bytes=%v-%v", offset, offset+int64(size)-1))
|
||||
|
||||
client := zurghttp.NewHTTPClient(c.GetToken(), 10, c)
|
||||
client := zurghttp.NewHTTPClient(cfg.GetToken(), 10, cfg)
|
||||
resp, err := client.Do(req)
|
||||
if err != nil {
|
||||
if strings.Contains(file.Link, "www.youtube.com") {
|
||||
@@ -200,10 +200,10 @@ func GetFileReader(torrent *intTor.Torrent, file *intTor.File, offset int64, siz
|
||||
}
|
||||
log.Warnf("Cannot download file %v ; torrent is marked for repair", err)
|
||||
if torrent != nil {
|
||||
go t.Repair(torrent.AccessKey)
|
||||
go torMgr.Repair(torrent.AccessKey)
|
||||
}
|
||||
errFile := createErrorFile("cannot_download.mp4", "https://www.youtube.com/watch?v=FSSd8cponAA")
|
||||
return GetFileReader(nil, errFile, 0, 0, t, c, log)
|
||||
return GetFileReader(nil, errFile, 0, 0, torMgr, cfg, log)
|
||||
}
|
||||
|
||||
if resp.StatusCode != http.StatusOK && resp.StatusCode != http.StatusPartialContent {
|
||||
@@ -213,11 +213,19 @@ func GetFileReader(torrent *intTor.Torrent, file *intTor.File, offset int64, siz
|
||||
}
|
||||
log.Warnf("Received a %s status code ; torrent is marked for repair", resp.Status)
|
||||
if torrent != nil {
|
||||
go t.Repair(torrent.AccessKey)
|
||||
go torMgr.Repair(torrent.AccessKey)
|
||||
}
|
||||
errFile := createErrorFile("not_ok_status.mp4", "https://www.youtube.com/watch?v=BcseUxviVqE")
|
||||
return GetFileReader(nil, errFile, 0, 0, t, c, log)
|
||||
return GetFileReader(nil, errFile, 0, 0, torMgr, cfg, log)
|
||||
}
|
||||
|
||||
return resp.Body
|
||||
defer resp.Body.Close()
|
||||
requestedBytes, err := io.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
if err != io.EOF {
|
||||
log.Errorf("Error reading bytes: %v", err)
|
||||
errFile := createErrorFile("read_error.mp4", "https://www.youtube.com/watch?v=t9VgOriBHwE")
|
||||
return GetFileReader(nil, errFile, 0, 0, torMgr, cfg, log)
|
||||
}
|
||||
}
|
||||
return requestedBytes
|
||||
}
|
||||
|
||||
17
internal/version/version.go
Normal file
17
internal/version/version.go
Normal file
@@ -0,0 +1,17 @@
|
||||
package version
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
)
|
||||
|
||||
var (
|
||||
BuiltAt string
|
||||
GoVersion string
|
||||
GitCommit string
|
||||
Version string = "dev"
|
||||
)
|
||||
|
||||
func Show() {
|
||||
fmt.Printf("zurg\nBuilt At: %s\nGo Version: %s\nCommit: %s\nVersion: %s\n",
|
||||
BuiltAt, GoVersion, GitCommit, Version)
|
||||
}
|
||||
@@ -1,33 +0,0 @@
|
||||
package zfs
|
||||
|
||||
import (
|
||||
"os"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"bazil.org/fuse/fs"
|
||||
"github.com/debridmediamanager.com/zurg/internal/config"
|
||||
"github.com/debridmediamanager.com/zurg/internal/torrent"
|
||||
"go.uber.org/zap"
|
||||
)
|
||||
|
||||
type FS struct {
|
||||
uid uint32
|
||||
gid uint32
|
||||
umask os.FileMode
|
||||
directIO bool
|
||||
lock sync.RWMutex
|
||||
config config.ConfigInterface
|
||||
tMgr *torrent.TorrentManager
|
||||
log *zap.SugaredLogger
|
||||
initTime time.Time
|
||||
}
|
||||
|
||||
// Root returns the root path
|
||||
func (f *FS) Root() (fs.Node, error) {
|
||||
return Object{
|
||||
fs: f,
|
||||
objType: ROOT,
|
||||
mtime: f.initTime,
|
||||
}, nil
|
||||
}
|
||||
@@ -1,54 +1,18 @@
|
||||
package zfs
|
||||
|
||||
import (
|
||||
"os"
|
||||
"time"
|
||||
|
||||
"bazil.org/fuse"
|
||||
"bazil.org/fuse/fs"
|
||||
"github.com/debridmediamanager.com/zurg/internal/config"
|
||||
"github.com/debridmediamanager.com/zurg/internal/torrent"
|
||||
"github.com/debridmediamanager.com/zurg/pkg/logutil"
|
||||
|
||||
"golang.org/x/sys/unix"
|
||||
"github.com/winfsp/cgofuse/fuse"
|
||||
)
|
||||
|
||||
func Mount(mountpoint string, cfg config.ConfigInterface, tMgr *torrent.TorrentManager) error {
|
||||
log := logutil.NewLogger().Named("zfs")
|
||||
|
||||
options := []fuse.MountOption{
|
||||
fuse.AllowOther(),
|
||||
fuse.AllowNonEmptyMount(),
|
||||
fuse.MaxReadahead(uint32(128 << 10)),
|
||||
fuse.DefaultPermissions(),
|
||||
fuse.FSName("zurgfs"),
|
||||
}
|
||||
conn, err := fuse.Mount(mountpoint, options...)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer conn.Close()
|
||||
|
||||
srv := fs.New(conn, nil)
|
||||
|
||||
filesys := &FS{
|
||||
uid: uint32(unix.Geteuid()),
|
||||
gid: uint32(unix.Getegid()),
|
||||
umask: os.FileMode(0),
|
||||
config: cfg,
|
||||
tMgr: tMgr,
|
||||
log: log,
|
||||
initTime: time.Now(),
|
||||
}
|
||||
|
||||
if err := srv.Serve(filesys); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
func Mount(host *fuse.FileSystemHost, cfg config.ConfigInterface) error {
|
||||
host.SetCapCaseInsensitive(false)
|
||||
host.SetCapReaddirPlus(false)
|
||||
host.Mount(cfg.GetMountPoint(), []string{})
|
||||
return nil
|
||||
}
|
||||
|
||||
func Unmount(mountpoint string) error {
|
||||
fuse.Unmount(mountpoint)
|
||||
func Unmount(host *fuse.FileSystemHost) error {
|
||||
_ = host.Unmount()
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -1,188 +0,0 @@
|
||||
package zfs
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"syscall"
|
||||
"time"
|
||||
|
||||
"bazil.org/fuse"
|
||||
"bazil.org/fuse/fs"
|
||||
"github.com/debridmediamanager.com/zurg/internal/torrent"
|
||||
"github.com/debridmediamanager.com/zurg/internal/universal"
|
||||
)
|
||||
|
||||
// define variable as rootObject id
|
||||
const (
|
||||
ROOT = 0
|
||||
DIRECTORY = 1
|
||||
TORRENT = 2
|
||||
FILE = 3
|
||||
)
|
||||
|
||||
type Object struct {
|
||||
fs *FS
|
||||
objType int
|
||||
parentName string
|
||||
name string
|
||||
torrent *torrent.Torrent
|
||||
file *torrent.File
|
||||
size uint64
|
||||
mtime time.Time
|
||||
}
|
||||
|
||||
// Attr returns the attributes for a directory
|
||||
func (o Object) Attr(ctx context.Context, attr *fuse.Attr) error {
|
||||
if o.objType == FILE {
|
||||
attr.Mode = 0644
|
||||
} else {
|
||||
attr.Mode = os.ModeDir | 0755
|
||||
}
|
||||
attr.Size = o.size
|
||||
|
||||
attr.Uid = o.fs.uid
|
||||
attr.Gid = o.fs.gid
|
||||
|
||||
attr.Ctime = o.mtime
|
||||
attr.Mtime = o.mtime
|
||||
|
||||
attr.Blocks = (attr.Size + 511) >> 9
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// ReadDirAll shows all files in the current directory
|
||||
func (o Object) ReadDirAll(ctx context.Context) ([]fuse.Dirent, error) {
|
||||
dirs := []fuse.Dirent{}
|
||||
switch o.objType {
|
||||
case ROOT:
|
||||
for _, directory := range o.fs.config.GetDirectories() {
|
||||
dirs = append(dirs, fuse.Dirent{
|
||||
Name: directory,
|
||||
Type: fuse.DT_Dir,
|
||||
})
|
||||
}
|
||||
case DIRECTORY:
|
||||
for el := o.fs.tMgr.TorrentMap.Front(); el != nil; el = el.Next() {
|
||||
torrent := el.Value
|
||||
if torrent.InProgress {
|
||||
continue
|
||||
}
|
||||
dirs = append(dirs, fuse.Dirent{
|
||||
Name: torrent.AccessKey,
|
||||
Type: fuse.DT_Dir,
|
||||
})
|
||||
}
|
||||
case TORRENT:
|
||||
torrent, _ := o.fs.tMgr.TorrentMap.Get(o.name)
|
||||
if torrent == nil || torrent.InProgress {
|
||||
return nil, syscall.ENOENT
|
||||
}
|
||||
for el := torrent.SelectedFiles.Front(); el != nil; el = el.Next() {
|
||||
file := el.Value
|
||||
if file.Link == "" {
|
||||
// log.Println("File has no link, skipping", file.Path)
|
||||
continue
|
||||
}
|
||||
filename := filepath.Base(file.Path)
|
||||
dirs = append(dirs, fuse.Dirent{
|
||||
Name: filename,
|
||||
Type: fuse.DT_File,
|
||||
})
|
||||
}
|
||||
}
|
||||
return dirs, nil
|
||||
}
|
||||
|
||||
// Lookup tests if a file is existent in the current directory
|
||||
func (o Object) Lookup(ctx context.Context, name string) (fs.Node, error) {
|
||||
switch o.objType {
|
||||
case ROOT:
|
||||
for _, directory := range o.fs.config.GetDirectories() {
|
||||
if directory == name {
|
||||
return Object{
|
||||
fs: o.fs,
|
||||
objType: DIRECTORY,
|
||||
parentName: o.name,
|
||||
name: name,
|
||||
mtime: o.fs.initTime,
|
||||
}, nil
|
||||
}
|
||||
}
|
||||
case DIRECTORY:
|
||||
torrent, _ := o.fs.tMgr.TorrentMap.Get(name)
|
||||
if torrent == nil {
|
||||
return nil, syscall.ENOENT
|
||||
}
|
||||
return Object{
|
||||
fs: o.fs,
|
||||
objType: TORRENT,
|
||||
parentName: o.name,
|
||||
name: name,
|
||||
mtime: convertRFC3339toTime(torrent.LatestAdded),
|
||||
}, nil
|
||||
|
||||
case TORRENT:
|
||||
torrent, _ := o.fs.tMgr.TorrentMap.Get(o.name)
|
||||
if torrent == nil {
|
||||
return nil, syscall.ENOENT
|
||||
}
|
||||
file, _ := torrent.SelectedFiles.Get(name)
|
||||
if file == nil {
|
||||
return nil, syscall.ENOENT
|
||||
}
|
||||
return Object{
|
||||
fs: o.fs,
|
||||
objType: FILE,
|
||||
parentName: o.name,
|
||||
name: name,
|
||||
torrent: torrent,
|
||||
file: file,
|
||||
size: uint64(file.Bytes),
|
||||
mtime: convertRFC3339toTime(file.Added),
|
||||
}, nil
|
||||
}
|
||||
return nil, syscall.ENOENT
|
||||
}
|
||||
|
||||
// Open a file
|
||||
func (o Object) Open(ctx context.Context, req *fuse.OpenRequest, resp *fuse.OpenResponse) (fs.Handle, error) {
|
||||
resp.Flags |= fuse.OpenDirectIO
|
||||
return o, nil
|
||||
}
|
||||
|
||||
// Read reads some bytes or the whole file
|
||||
func (o Object) Read(ctx context.Context, req *fuse.ReadRequest, resp *fuse.ReadResponse) error {
|
||||
reader := universal.GetFileReader(o.torrent, o.file, req.Offset, int(req.Size), o.fs.tMgr, o.fs.config, o.fs.log)
|
||||
if reader == nil {
|
||||
return syscall.EIO
|
||||
}
|
||||
defer reader.Close()
|
||||
resp.Data = make([]byte, req.Size)
|
||||
_, err := reader.Read(resp.Data)
|
||||
if err != nil && err.Error() != "EOF" {
|
||||
o.fs.log.Errorf("Error reading bytes from Real-Debrid: %v", err)
|
||||
return syscall.EIO
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Remove deletes an element
|
||||
func (o Object) Remove(ctx context.Context, req *fuse.RemoveRequest) error {
|
||||
return fmt.Errorf("Remove not yet implemented")
|
||||
}
|
||||
|
||||
// Rename renames an element
|
||||
func (o Object) Rename(ctx context.Context, req *fuse.RenameRequest, newDir fs.Node) error {
|
||||
return fmt.Errorf("Rename not yet implemented")
|
||||
}
|
||||
|
||||
func convertRFC3339toTime(input string) time.Time {
|
||||
t, err := time.Parse(time.RFC3339, input)
|
||||
if err != nil {
|
||||
return time.Now()
|
||||
}
|
||||
return t
|
||||
}
|
||||
185
internal/zfs/zfs.go
Normal file
185
internal/zfs/zfs.go
Normal file
@@ -0,0 +1,185 @@
|
||||
package zfs
|
||||
|
||||
import (
|
||||
"strings"
|
||||
|
||||
"github.com/debridmediamanager.com/zurg/internal/config"
|
||||
"github.com/debridmediamanager.com/zurg/internal/torrent"
|
||||
"github.com/debridmediamanager.com/zurg/pkg/chunk"
|
||||
"github.com/winfsp/cgofuse/fuse"
|
||||
"go.uber.org/zap"
|
||||
)
|
||||
|
||||
type ZurgFS struct {
|
||||
fuse.FileSystemBase
|
||||
TorrentManager *torrent.TorrentManager
|
||||
Config config.ConfigInterface
|
||||
Chunk *chunk.Manager
|
||||
Log *zap.SugaredLogger
|
||||
}
|
||||
|
||||
func NewZurgFS(tm *torrent.TorrentManager, cfg config.ConfigInterface, chunk *chunk.Manager, log *zap.SugaredLogger) *ZurgFS {
|
||||
return &ZurgFS{
|
||||
TorrentManager: tm,
|
||||
Config: cfg,
|
||||
Chunk: chunk,
|
||||
Log: log,
|
||||
}
|
||||
}
|
||||
|
||||
func (fs *ZurgFS) Open(path string, flags int) (errc int, fh uint64) {
|
||||
segments := splitIntoSegments(path)
|
||||
switch len(segments) {
|
||||
case 0:
|
||||
return 0, 0
|
||||
case 1:
|
||||
if _, dirFound := fs.TorrentManager.DirectoryMap.Get(segments[0]); !dirFound {
|
||||
return -fuse.ENOENT, ^uint64(0)
|
||||
}
|
||||
return 0, 0
|
||||
case 2:
|
||||
if directory, dirFound := fs.TorrentManager.DirectoryMap.Get(segments[0]); !dirFound {
|
||||
return -fuse.ENOENT, ^uint64(0)
|
||||
} else if _, torFound := directory.Get(segments[1]); !torFound {
|
||||
return -fuse.ENOENT, ^uint64(0)
|
||||
}
|
||||
return 0, 0
|
||||
case 3:
|
||||
if directory, dirFound := fs.TorrentManager.DirectoryMap.Get(segments[0]); !dirFound {
|
||||
return -fuse.ENOENT, ^uint64(0)
|
||||
} else if torrent, torFound := directory.Get(segments[1]); !torFound {
|
||||
return -fuse.ENOENT, ^uint64(0)
|
||||
} else if file, fileFound := torrent.SelectedFiles.Get(segments[2]); !fileFound {
|
||||
return -fuse.ENOENT, ^uint64(0)
|
||||
} else {
|
||||
return 0, file.ZurgFS
|
||||
}
|
||||
}
|
||||
|
||||
return -fuse.ENOENT, ^uint64(0)
|
||||
}
|
||||
|
||||
func (fs *ZurgFS) Getattr(path string, stat *fuse.Stat_t, fh uint64) (errc int) {
|
||||
segments := splitIntoSegments(path)
|
||||
switch len(segments) {
|
||||
case 0:
|
||||
stat.Mode = fuse.S_IFDIR | 0555
|
||||
return 0
|
||||
case 1:
|
||||
if _, dirFound := fs.TorrentManager.DirectoryMap.Get(segments[0]); !dirFound {
|
||||
return -fuse.ENOENT
|
||||
}
|
||||
stat.Mode = fuse.S_IFDIR | 0555
|
||||
return 0
|
||||
case 2:
|
||||
if directory, dirFound := fs.TorrentManager.DirectoryMap.Get(segments[0]); !dirFound {
|
||||
return -fuse.ENOENT
|
||||
} else if _, torFound := directory.Get(segments[1]); !torFound {
|
||||
return -fuse.ENOENT
|
||||
}
|
||||
stat.Mode = fuse.S_IFDIR | 0555
|
||||
return 0
|
||||
case 3:
|
||||
if directory, dirFound := fs.TorrentManager.DirectoryMap.Get(segments[0]); !dirFound {
|
||||
return -fuse.ENOENT
|
||||
} else if torrent, torFound := directory.Get(segments[1]); !torFound {
|
||||
return -fuse.ENOENT
|
||||
} else if file, fileFound := torrent.SelectedFiles.Get(segments[2]); !fileFound {
|
||||
return -fuse.ENOENT
|
||||
} else {
|
||||
stat.Mode = fuse.S_IFREG | 0444
|
||||
stat.Size = file.Bytes
|
||||
return 0
|
||||
}
|
||||
}
|
||||
|
||||
return -fuse.ENOENT
|
||||
}
|
||||
|
||||
func (fs *ZurgFS) Read(path string, buff []byte, ofst int64, fh uint64) (n int) {
|
||||
segments := splitIntoSegments(path)
|
||||
if len(segments) != 3 {
|
||||
return -fuse.ENOENT
|
||||
} else if directory, dirFound := fs.TorrentManager.DirectoryMap.Get(segments[0]); !dirFound {
|
||||
return -fuse.ENOENT
|
||||
} else if torrent, torFound := directory.Get(segments[1]); !torFound {
|
||||
return -fuse.ENOENT
|
||||
} else if file, fileFound := torrent.SelectedFiles.Get(segments[2]); !fileFound {
|
||||
return -fuse.ENOENT
|
||||
} else {
|
||||
size := int64(len(buff))
|
||||
if size < int64(fs.Config.GetNetworkBufferSize()) {
|
||||
size = int64(fs.Config.GetNetworkBufferSize())
|
||||
}
|
||||
endofst := ofst + size
|
||||
if endofst > file.Bytes {
|
||||
endofst = file.Bytes
|
||||
}
|
||||
if endofst < ofst {
|
||||
return 0
|
||||
}
|
||||
response, err := fs.Chunk.GetChunk(file, ofst, size)
|
||||
if err != nil {
|
||||
return -fuse.ENOENT
|
||||
}
|
||||
// response := universal.GetFileReader(torrent, file, ofst, int(size), fs.TorrentManager, fs.Config, fs.Log)
|
||||
// if response == nil {
|
||||
// return -fuse.ENOENT
|
||||
// }
|
||||
n = copy(buff, response[:endofst-ofst])
|
||||
return n
|
||||
}
|
||||
}
|
||||
|
||||
func (fs *ZurgFS) Readdir(path string,
|
||||
fill func(name string, stat *fuse.Stat_t, ofst int64) bool,
|
||||
ofst int64,
|
||||
fh uint64) (errc int) {
|
||||
|
||||
segments := splitIntoSegments(path)
|
||||
switch len(segments) {
|
||||
case 0:
|
||||
fill(".", nil, 0)
|
||||
fill("..", nil, 0)
|
||||
for el := fs.TorrentManager.DirectoryMap.Front(); el != nil; el = el.Next() {
|
||||
fill(el.Key, nil, 0)
|
||||
}
|
||||
case 1:
|
||||
fill(".", nil, 0)
|
||||
fill("..", nil, 0)
|
||||
if directory, dirFound := fs.TorrentManager.DirectoryMap.Get(segments[0]); !dirFound {
|
||||
return -fuse.ENOENT
|
||||
} else {
|
||||
for el := directory.Front(); el != nil; el = el.Next() {
|
||||
fill(el.Key, nil, 0)
|
||||
}
|
||||
}
|
||||
case 2:
|
||||
fill(".", nil, 0)
|
||||
fill("..", nil, 0)
|
||||
if directory, dirFound := fs.TorrentManager.DirectoryMap.Get(segments[0]); !dirFound {
|
||||
return -fuse.ENOENT
|
||||
} else if torrent, torFound := directory.Get(segments[1]); !torFound {
|
||||
return -fuse.ENOENT
|
||||
} else {
|
||||
for el := torrent.SelectedFiles.Front(); el != nil; el = el.Next() {
|
||||
fill(el.Key, nil, 0)
|
||||
}
|
||||
}
|
||||
default:
|
||||
return -fuse.ENOENT
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
func splitIntoSegments(path string) []string {
|
||||
segments := strings.Split(path, "/")
|
||||
// remove empty segments
|
||||
for i := 0; i < len(segments); i++ {
|
||||
if segments[i] == "" {
|
||||
segments = append(segments[:i], segments[i+1:]...)
|
||||
i--
|
||||
}
|
||||
}
|
||||
return segments
|
||||
}
|
||||
Reference in New Issue
Block a user