diff --git a/cmd/zurg/main.go b/cmd/zurg/main.go index d5b1e16..33f9e7d 100644 --- a/cmd/zurg/main.go +++ b/cmd/zurg/main.go @@ -14,6 +14,7 @@ import ( "github.com/debridmediamanager.com/zurg/internal/torrent" "github.com/debridmediamanager.com/zurg/pkg/logutil" "github.com/hashicorp/golang-lru/v2/expirable" + "github.com/nutsdb/nutsdb" ) func main() { @@ -25,9 +26,18 @@ func main() { log.Panicf("Config failed to load: %v", configErr) } + db, err := nutsdb.Open( + nutsdb.DefaultOptions, + nutsdb.WithDir("/tmp/nutsdb"), + ) + if err != nil { + log.Fatal(err) + } + defer db.Close() + cache := expirable.NewLRU[string, string](1e4, nil, time.Hour) - torrentMgr := torrent.NewTorrentManager(config, cache) + torrentMgr := torrent.NewTorrentManager(config, cache, db) mux := http.NewServeMux() net.Router(mux, config, torrentMgr, cache) diff --git a/go.mod b/go.mod index ad5f920..1b30310 100644 --- a/go.mod +++ b/go.mod @@ -9,7 +9,18 @@ require ( ) require ( + github.com/antlabs/stl v0.0.1 // indirect + github.com/antlabs/timer v0.0.11 // indirect + github.com/bwmarrin/snowflake v0.3.0 // indirect github.com/cenkalti/backoff v2.2.1+incompatible // indirect github.com/cenkalti/backoff/v4 v4.2.1 // indirect + github.com/emirpasic/gods v1.18.1 // indirect + github.com/gofrs/flock v0.8.1 // indirect + github.com/nutsdb/nutsdb v0.14.1 // indirect + github.com/pkg/errors v0.9.1 // indirect + github.com/tidwall/btree v1.6.0 // indirect + github.com/xujiajun/mmap-go v1.0.1 // indirect + github.com/xujiajun/utils v0.0.0-20220904132955-5f7c5b914235 // indirect go.uber.org/multierr v1.10.0 // indirect + golang.org/x/sys v0.10.0 // indirect ) diff --git a/go.sum b/go.sum index 5cfeeab..6061d0f 100644 --- a/go.sum +++ b/go.sum @@ -1,22 +1,49 @@ +github.com/antlabs/stl v0.0.1 h1:TRD3csCrjREeLhLoQ/supaoCvFhNLBTNIwuRGrDIs6Q= +github.com/antlabs/stl v0.0.1/go.mod h1:wvVwP1loadLG3cRjxUxK8RL4Co5xujGaZlhbztmUEqQ= +github.com/antlabs/timer v0.0.11 h1:z75oGFLeTqJHMOcWzUPBKsBbQAz4Ske3AfqJ7bsdcwU= +github.com/antlabs/timer v0.0.11/go.mod h1:JNV8J3yGvMKhCavGXgj9HXrVZkfdQyKCcqXBT8RdyuU= +github.com/bwmarrin/snowflake v0.3.0 h1:xm67bEhkKh6ij1790JB83OujPR5CzNe8QuQqAgISZN0= +github.com/bwmarrin/snowflake v0.3.0/go.mod h1:NdZxfVWX+oR6y2K0o6qAYv6gIOP9rjG0/E9WsDpxqwE= github.com/cenkalti/backoff v2.2.1+incompatible h1:tNowT99t7UNflLxfYYSlKYsBpXdEet03Pg2g16Swow4= github.com/cenkalti/backoff v2.2.1+incompatible/go.mod h1:90ReRw6GdpyfrHakVjL/QHaoyV4aDUVVkXQJJJ3NXXM= github.com/cenkalti/backoff/v4 v4.2.1 h1:y4OZtCnogmCPw98Zjyt5a6+QwPLGkiQsYW5oUqylYbM= github.com/cenkalti/backoff/v4 v4.2.1/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyYozVcomhLiZE= +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/emirpasic/gods v1.18.1 h1:FXtiHYKDGKCW2KzwZKx0iC0PQmdlorYgdFG9jPXJ1Bc= +github.com/emirpasic/gods v1.18.1/go.mod h1:8tpGGwCnJ5H4r6BWwaV6OrWmMoPhUl5jm/FMNAnJvWQ= +github.com/gofrs/flock v0.8.1 h1:+gYjHKf32LDeiEEFhQaotPbLuUXjY5ZqxKgXy7n59aw= +github.com/gofrs/flock v0.8.1/go.mod h1:F1TvTiK9OcQqauNUHlbJvyl9Qa1QvF/gOUDKA14jxHU= github.com/hashicorp/golang-lru/v2 v2.0.7 h1:a+bsQ5rvGLjzHuww6tVxozPZFVghXaHOwFs4luLUK2k= github.com/hashicorp/golang-lru/v2 v2.0.7/go.mod h1:QeFd9opnmA6QUJc5vARoKUSoFhyfM2/ZepoAG6RGpeM= +github.com/nutsdb/nutsdb v0.14.1 h1:z+Kth/kz2oYqKmOMBZho1YK2183xjrcl6KExRtCFl18= +github.com/nutsdb/nutsdb v0.14.1/go.mod h1:6inOji9rFBporXeHDjJny4g50RpQbkjSK5jI1hht0j8= +github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= +github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.8.1 h1:w7B6lhMri9wdJUVmEZPGGhZzrYTPvgJArz7wNPgYKsk= github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= +github.com/tidwall/btree v1.6.0 h1:LDZfKfQIBHGHWSwckhXI0RPSXzlo+KYdjK7FWSqOzzg= +github.com/tidwall/btree v1.6.0/go.mod h1:twD9XRA5jj9VUQGELzDO4HPQTNJsoWWfYEL+EUQ2cKY= +github.com/xujiajun/mmap-go v1.0.1 h1:7Se7ss1fLPPRW+ePgqGpCkfGIZzJV6JPq9Wq9iv/WHc= +github.com/xujiajun/mmap-go v1.0.1/go.mod h1:CNN6Sw4SL69Sui00p0zEzcZKbt+5HtEnYUsc6BKKRMg= +github.com/xujiajun/utils v0.0.0-20220904132955-5f7c5b914235 h1:w0si+uee0iAaCJO9q86T6yrhdadgcsoNuh47LrUykzg= +github.com/xujiajun/utils v0.0.0-20220904132955-5f7c5b914235/go.mod h1:MR4+0R6A9NS5IABnIM3384FfOq8QFVnm7WDrBOhIaMU= go.uber.org/goleak v1.2.0 h1:xqgm/S+aQvhWFTtR0XK3Jvg7z8kGV8P4X14IzwN3Eqk= go.uber.org/goleak v1.2.0/go.mod h1:XJYK+MuIchqpmGmUSAzotztawfKvYLUIgg7guXrwVUo= go.uber.org/multierr v1.10.0 h1:S0h4aNzvfcFsC3dRF1jLoaov7oRaKqRGC/pUEJ2yvPQ= go.uber.org/multierr v1.10.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y= go.uber.org/zap v1.26.0 h1:sI7k6L95XOKS281NhVKOFCUNIvv9e0w4BF8N3u+tCRo= go.uber.org/zap v1.26.0/go.mod h1:dtElttAiwGvoJ/vj4IwHBS/gXsEu/pZ50mUIRWuG0so= +golang.org/x/sys v0.0.0-20181221143128-b4a75ba826a6/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.10.0 h1:SqMFp9UcQJZa+pmYuAKjd9xq1f0j5rLcDIk0mj4qAsA= +golang.org/x/sys v0.10.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/internal/config/v1.go b/internal/config/v1.go index 85fbfb2..dd2bf9b 100644 --- a/internal/config/v1.go +++ b/internal/config/v1.go @@ -5,7 +5,6 @@ import ( "sort" "strings" - "github.com/debridmediamanager.com/zurg/pkg/logutil" "gopkg.in/yaml.v3" ) @@ -33,8 +32,6 @@ func (z *ZurgConfigV1) GetDirectories() []string { } func (z *ZurgConfigV1) GetGroupMap() map[string][]string { - log := logutil.NewLogger().Named("config") - var groupMap = make(map[string][]string) var groupOrderMap = make(map[string]int) // To store GroupOrder for each directory @@ -42,7 +39,6 @@ func (z *ZurgConfigV1) GetGroupMap() map[string][]string { for directory, val := range z.Directories { groupMap[val.Group] = append(groupMap[val.Group], directory) groupOrderMap[directory] = val.GroupOrder - log.Debugf("Added directory to group: %s, group: %s, order: %d", directory, val.Group, val.GroupOrder) } // Sort the slice based on GroupOrder and then directory name for deterministic order @@ -54,7 +50,6 @@ func (z *ZurgConfigV1) GetGroupMap() map[string][]string { return groupOrderMap[dirs[i]] < groupOrderMap[dirs[j]] }) groupMap[group] = dirs - log.Debugf("Sorted directories within a group: %s %v", group, dirs) } // Return a deep copy of the map diff --git a/internal/dav/listing.go b/internal/dav/listing.go index 805017f..0a9dae5 100644 --- a/internal/dav/listing.go +++ b/internal/dav/listing.go @@ -11,22 +11,14 @@ import ( "github.com/debridmediamanager.com/zurg/internal/torrent" "github.com/debridmediamanager.com/zurg/pkg/dav" "github.com/debridmediamanager.com/zurg/pkg/logutil" - "github.com/hashicorp/golang-lru/v2/expirable" ) -func HandlePropfindRequest(w http.ResponseWriter, r *http.Request, t *torrent.TorrentManager, c config.ConfigInterface, cache *expirable.LRU[string, string]) { +func HandlePropfindRequest(w http.ResponseWriter, r *http.Request, t *torrent.TorrentManager, c config.ConfigInterface) { log := logutil.NewLogger().Named("dav") requestPath := path.Clean(r.URL.Path) requestPath = strings.Trim(requestPath, "/") - if data, exists := cache.Get(requestPath); exists { - w.Header().Set("Content-Type", "text/xml; charset=\"utf-8\"") - w.WriteHeader(http.StatusMultiStatus) - fmt.Fprint(w, data) - return - } - var output []byte var err error @@ -53,8 +45,6 @@ func HandlePropfindRequest(w http.ResponseWriter, r *http.Request, t *torrent.To if output != nil { respBody := fmt.Sprintf("\n%s\n", output) - cache.Add(requestPath, respBody) - w.Header().Set("Content-Type", "text/xml; charset=\"utf-8\"") w.WriteHeader(http.StatusMultiStatus) fmt.Fprint(w, respBody) diff --git a/internal/dav/response.go b/internal/dav/response.go index c89d623..15d00b8 100644 --- a/internal/dav/response.go +++ b/internal/dav/response.go @@ -15,7 +15,7 @@ func createMultiTorrentResponse(basePath string, torrents []torrent.Torrent) (*d seen := make(map[string]bool) for _, item := range torrents { - if item.Progress != 100 { + if item.InProgress { continue } if _, exists := seen[item.AccessKey]; exists { @@ -64,7 +64,7 @@ func createSingleTorrentResponse(basePath string, torrents []torrent.Torrent) (* torrentResponses = append(torrentResponses, dav.File( filePath, file.Bytes, - convertRFC3339toRFC1123(torrent.Added), + convertRFC3339toRFC1123(torrent.LatestAdded), file.Link, )) } diff --git a/internal/http/listing.go b/internal/http/listing.go index 24746a3..6e39b9b 100644 --- a/internal/http/listing.go +++ b/internal/http/listing.go @@ -5,26 +5,19 @@ import ( "net/http" "net/url" "path" + "path/filepath" "strings" "github.com/debridmediamanager.com/zurg/internal/config" "github.com/debridmediamanager.com/zurg/internal/torrent" "github.com/debridmediamanager.com/zurg/pkg/logutil" - "github.com/hashicorp/golang-lru/v2/expirable" ) -func HandleDirectoryListing(w http.ResponseWriter, r *http.Request, t *torrent.TorrentManager, c config.ConfigInterface, cache *expirable.LRU[string, string]) { +func HandleDirectoryListing(w http.ResponseWriter, r *http.Request, t *torrent.TorrentManager, c config.ConfigInterface) { log := logutil.NewLogger().Named("http") requestPath := path.Clean(r.URL.Path) - if data, exists := cache.Get(requestPath); exists { - w.Header().Set("Content-Type", "text/html; charset=\"utf-8\"") - w.WriteHeader(http.StatusOK) - fmt.Fprint(w, data) - return - } - var output *string var err error @@ -49,8 +42,6 @@ func HandleDirectoryListing(w http.ResponseWriter, r *http.Request, t *torrent.T } if output != nil { - cache.Add(requestPath, *output) - w.Header().Set("Content-Type", "text/html; charset=\"utf-8\"") w.WriteHeader(http.StatusOK) fmt.Fprint(w, *output) @@ -73,12 +64,19 @@ func handleListOfTorrents(requestPath string, w http.ResponseWriter, r *http.Req for _, directory := range c.GetDirectories() { if basePath == directory { - torrents := t.GetByDirectory(basePath) - resp, err := createMultiTorrentResponse(requestPath, torrents) - if err != nil { - return nil, fmt.Errorf("cannot read directory (%s): %w", basePath, err) + htmlDoc := "
    " + for name, torrent := range t.TorrentMap { + if len(torrent.SelectedFiles) == 0 { + continue + } + for _, dir := range torrent.Directories { + if dir == basePath { + htmlDoc += fmt.Sprintf("
  1. %s
  2. ", filepath.Join(requestPath, url.PathEscape(name)), name) + break + } + } } - return &resp, nil + return &htmlDoc, nil } } @@ -86,18 +84,17 @@ func handleListOfTorrents(requestPath string, w http.ResponseWriter, r *http.Req } func handleSingleTorrent(requestPath string, w http.ResponseWriter, r *http.Request, t *torrent.TorrentManager) (*string, error) { - fullDir := path.Dir(requestPath) - directory := path.Base(fullDir) torrentName := path.Base(requestPath) - - sameNameTorrents := t.FindAllTorrentsWithName(directory, torrentName) - if len(sameNameTorrents) == 0 { - return nil, fmt.Errorf("cannot find directory when generating single torrent: %s", requestPath) + htmlDoc := "
      " + for _, file := range t.TorrentMap[torrentName].SelectedFiles { + if file.Link == "" { + // TODO: fix the file? + fmt.Printf("File %s has no link, skipping\n", file.Path) + continue + } + filename := filepath.Base(file.Path) + filePath := filepath.Join(requestPath, url.PathEscape(filename)) + htmlDoc += fmt.Sprintf("
    1. %s
    2. ", filePath, filename) } - - resp, err := createSingleTorrentResponse(requestPath, sameNameTorrents) - if err != nil { - return nil, fmt.Errorf("cannot read directory (%s): %w", requestPath, err) - } - return &resp, nil + return &htmlDoc, nil } diff --git a/internal/http/response.go b/internal/http/response.go deleted file mode 100644 index 13e0ec9..0000000 --- a/internal/http/response.go +++ /dev/null @@ -1,60 +0,0 @@ -package http - -import ( - "fmt" - "net/url" - "path/filepath" - - "github.com/debridmediamanager.com/zurg/internal/torrent" -) - -// createMultiTorrentResponse creates a WebDAV response for a list of torrents -func createMultiTorrentResponse(basePath string, torrents []torrent.Torrent) (string, error) { - htmlDoc := "