diff --git a/internal/dav/delete.go b/internal/dav/delete.go new file mode 100644 index 0000000..c3efa6e --- /dev/null +++ b/internal/dav/delete.go @@ -0,0 +1,88 @@ +package dav + +import ( + "fmt" + "net/http" + "path" + "strings" + + "github.com/debridmediamanager.com/zurg/internal/torrent" + "go.uber.org/zap" +) + +func HandleDeleteRequest(w http.ResponseWriter, r *http.Request, t *torrent.TorrentManager, log *zap.SugaredLogger) { + requestPath := path.Clean(r.URL.Path) + filteredSegments := splitIntoSegments(requestPath) + + var err error + switch { + case len(filteredSegments) == 0: + err = fmt.Errorf("cannot delete root") + case len(filteredSegments) == 1: + err = fmt.Errorf("cannot delete configured directory") + case len(filteredSegments) == 2: + err = handleDeleteTorrent(w, filteredSegments, t) + case len(filteredSegments) >= 3: + err = handleDeleteFile(w, filteredSegments, t) + default: + log.Warnf("Request %s %s not found", r.Method, requestPath) + http.Error(w, "Not Found", http.StatusNotFound) + return + } + + if err != nil { + if strings.Contains(err.Error(), "cannot find") { + http.Error(w, "Not Found", http.StatusNotFound) + return + } + log.Errorf("Error processing request: %v", err) + http.Error(w, "Server error", http.StatusInternalServerError) + return + } + + w.Header().Set("Content-Type", "text/xml; charset=\"utf-8\"") +} + +func handleDeleteTorrent(w http.ResponseWriter, segments []string, t *torrent.TorrentManager) error { + directory := segments[0] + torrents, ok := t.DirectoryMap.Get(directory) + if !ok { + return fmt.Errorf("cannot find directory %s", directory) + } + + accessKey := segments[1] + tor, ok := torrents.Get(accessKey) + if !ok { + return fmt.Errorf("cannot find torrent %s", accessKey) + } + + t.Delete(tor.AccessKey) + + w.WriteHeader(http.StatusNoContent) + return nil +} + +func handleDeleteFile(w http.ResponseWriter, segments []string, t *torrent.TorrentManager) error { + directory := segments[0] + torrents, ok := t.DirectoryMap.Get(directory) + if !ok { + return fmt.Errorf("cannot find directory %s", directory) + } + + accessKey := segments[1] + tor, ok := torrents.Get(accessKey) + if !ok { + return fmt.Errorf("cannot find torrent %s", accessKey) + } + + // set filepath to last segment + filepath := segments[len(segments)-1] + file, ok := tor.SelectedFiles.Get(filepath) + if !ok { + return fmt.Errorf("cannot find file %s", filepath) + } + + file.Link = "-" + w.WriteHeader(http.StatusNoContent) + return nil +} diff --git a/internal/dav/listing.go b/internal/dav/listing.go index efd97e9..c9ba56b 100644 --- a/internal/dav/listing.go +++ b/internal/dav/listing.go @@ -10,26 +10,21 @@ import ( "github.com/debridmediamanager.com/zurg/internal/torrent" "github.com/debridmediamanager.com/zurg/pkg/dav" - "github.com/debridmediamanager.com/zurg/pkg/logutil" + "go.uber.org/zap" ) -func HandlePropfindRequest(w http.ResponseWriter, r *http.Request, t *torrent.TorrentManager) { - log := logutil.NewLogger().Named("dav") - +func HandlePropfindRequest(w http.ResponseWriter, r *http.Request, t *torrent.TorrentManager, log *zap.SugaredLogger) { requestPath := path.Clean(r.URL.Path) - requestPath = strings.Trim(requestPath, "/") + filteredSegments := splitIntoSegments(requestPath) var err error - - filteredSegments := strings.Split(requestPath, "/") - switch { - case len(filteredSegments) == 1 && filteredSegments[0] == "": - err = handleRoot(w, t) + case len(filteredSegments) == 0: + err = handleListDirectories(w, t) case len(filteredSegments) == 1: - err = handleListOfTorrents(w, requestPath, t) + err = handleListTorrents(w, requestPath, t) case len(filteredSegments) == 2: - err = handleSingleTorrent(w, requestPath, t) + err = handleListFiles(w, requestPath, t) default: log.Warnf("Request %s %s not found", r.Method, requestPath) http.Error(w, "Not Found", http.StatusNotFound) @@ -49,7 +44,7 @@ func HandlePropfindRequest(w http.ResponseWriter, r *http.Request, t *torrent.To w.Header().Set("Content-Type", "text/xml; charset=\"utf-8\"") } -func handleRoot(w http.ResponseWriter, t *torrent.TorrentManager) error { +func handleListDirectories(w http.ResponseWriter, t *torrent.TorrentManager) error { fmt.Fprint(w, "") // initial response is the directory itself fmt.Fprint(w, dav.Directory("", "")) @@ -64,7 +59,7 @@ func handleRoot(w http.ResponseWriter, t *torrent.TorrentManager) error { return nil } -func handleListOfTorrents(w http.ResponseWriter, requestPath string, t *torrent.TorrentManager) error { +func handleListTorrents(w http.ResponseWriter, requestPath string, t *torrent.TorrentManager) error { basePath := path.Base(requestPath) torrents, ok := t.DirectoryMap.Get(basePath) if !ok { @@ -73,7 +68,7 @@ func handleListOfTorrents(w http.ResponseWriter, requestPath string, t *torrent. fmt.Fprint(w, "") // initial response is the directory itself - fmt.Fprint(w, dav.Directory(basePath+"/", "")) + fmt.Fprint(w, dav.Directory(basePath, "")) var allTorrents []*torrent.Torrent torrents.IterCb(func(_ string, tor *torrent.Torrent) { @@ -91,7 +86,8 @@ func handleListOfTorrents(w http.ResponseWriter, requestPath string, t *torrent. return nil } -func handleSingleTorrent(w http.ResponseWriter, requestPath string, t *torrent.TorrentManager) error { +func handleListFiles(w http.ResponseWriter, requestPath string, t *torrent.TorrentManager) error { + requestPath = strings.TrimPrefix(requestPath, "/") basePath := path.Base(path.Dir(requestPath)) torrents, ok := t.DirectoryMap.Get(basePath) if !ok { @@ -105,7 +101,7 @@ func handleSingleTorrent(w http.ResponseWriter, requestPath string, t *torrent.T fmt.Fprint(w, "") // initial response is the directory itself - fmt.Fprint(w, dav.Directory(requestPath+"/", tor.LatestAdded)) + fmt.Fprint(w, dav.Directory(requestPath, tor.LatestAdded)) filenames := tor.SelectedFiles.Keys() sort.Strings(filenames) diff --git a/internal/dav/util.go b/internal/dav/util.go index b57303e..dffe175 100644 --- a/internal/dav/util.go +++ b/internal/dav/util.go @@ -1,14 +1,17 @@ package dav import ( - "time" + "strings" ) -// convertRFC3339toRFC1123 converts a date from RFC3339 to RFC1123 -func convertRFC3339toRFC1123(input string) string { - t, err := time.Parse(time.RFC3339, input) - if err != nil { - return "" +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 t.Format("Mon, 02 Jan 2006 15:04:05 GMT") + return segments } diff --git a/internal/net/router.go b/internal/net/router.go index 9f758a5..1868800 100644 --- a/internal/net/router.go +++ b/internal/net/router.go @@ -38,9 +38,13 @@ func Router(mux *http.ServeMux, c config.ConfigInterface, t *torrent.TorrentMana }) mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { + davlog := logutil.NewLogger().Named("dav") switch r.Method { case "PROPFIND": - dav.HandlePropfindRequest(w, r, t) + dav.HandlePropfindRequest(w, r, t, davlog) + + case "DELETE": + dav.HandleDeleteRequest(w, r, t, davlog) case http.MethodGet: universal.HandleGetRequest(w, r, t, c, cache) diff --git a/internal/torrent/manager.go b/internal/torrent/manager.go index ce5817a..773bbff 100644 --- a/internal/torrent/manager.go +++ b/internal/torrent/manager.go @@ -548,6 +548,17 @@ func (t *TorrentManager) repairAll() { }) } +func (t *TorrentManager) Delete(accessKey string) { + t.log.Infof("Deleting torrent %s", accessKey) + allTorrents, _ := t.DirectoryMap.Get(ALL_TORRENTS) + if torrent, ok := allTorrents.Get(accessKey); ok { + for _, instance := range torrent.Instances { + t.api.DeleteTorrent(instance.ID) + } + allTorrents.Remove(accessKey) + } +} + func (t *TorrentManager) Repair(accessKey string) { if !t.cfg.EnableRepair() { t.log.Warn("Repair is disabled; if you do not have other zurg instances running, you should enable repair") diff --git a/internal/universal/get.go b/internal/universal/get.go index 210f402..7d8a3ad 100644 --- a/internal/universal/get.go +++ b/internal/universal/get.go @@ -34,7 +34,7 @@ func HandleGetRequest(w http.ResponseWriter, r *http.Request, t *intTor.TorrentM // If there are less than 3 segments, return an error or adjust as needed if len(segments) <= 3 { if isDav { - dav.HandlePropfindRequest(w, r, t) + dav.HandlePropfindRequest(w, r, t, log) } else { intHttp.HandleDirectoryListing(w, r, t) } diff --git a/pkg/dav/response.go b/pkg/dav/response.go index 33079db..7d47de2 100644 --- a/pkg/dav/response.go +++ b/pkg/dav/response.go @@ -31,7 +31,7 @@ func DavFile(path string, fileSize int64, added string, link string) Response { // optimized versions, no more marshalling func Directory(path, added string) string { - return fmt.Sprintf("/%s%sHTTP/1.1 200 OK", customPathEscape(path), added) + return fmt.Sprintf("/%s%sHTTP/1.1 200 OK", customPathEscape(path), added) } func File(path string, fileSize int64, added string) string { diff --git a/rclone.conf b/rclone.conf index 4d90499..d3af21d 100644 --- a/rclone.conf +++ b/rclone.conf @@ -1,10 +1,11 @@ [zurg] +type = webdav +url = http://zurg:9999 +vendor = other + +[zurghttp] +# it's readonly but it's crazy fast type = http url = http://zurg:9999/http no_head = false no_slash = false - -[zurgwd] -type = webdav -url = http://zurg:9999 -vendor = other