updated filepaths for multiplatform support (#56)
- Migrate from Go's path to path/filepath to support multi-OS support
This commit is contained in:
@@ -5,6 +5,14 @@ import (
|
|||||||
"context"
|
"context"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
"sync"
|
||||||
|
"sync/atomic"
|
||||||
|
"time"
|
||||||
|
|
||||||
"github.com/go-co-op/gocron/v2"
|
"github.com/go-co-op/gocron/v2"
|
||||||
"github.com/goccy/go-json"
|
"github.com/goccy/go-json"
|
||||||
"github.com/puzpuzpuz/xsync/v3"
|
"github.com/puzpuzpuz/xsync/v3"
|
||||||
@@ -13,13 +21,6 @@ import (
|
|||||||
"github.com/sirrobot01/decypharr/internal/logger"
|
"github.com/sirrobot01/decypharr/internal/logger"
|
||||||
"github.com/sirrobot01/decypharr/internal/utils"
|
"github.com/sirrobot01/decypharr/internal/utils"
|
||||||
"github.com/sirrobot01/decypharr/pkg/debrid/types"
|
"github.com/sirrobot01/decypharr/pkg/debrid/types"
|
||||||
"os"
|
|
||||||
path "path/filepath"
|
|
||||||
"strconv"
|
|
||||||
"strings"
|
|
||||||
"sync"
|
|
||||||
"sync/atomic"
|
|
||||||
"time"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
type WebDavFolderNaming string
|
type WebDavFolderNaming string
|
||||||
@@ -115,8 +116,8 @@ func New(dc config.Debrid, client types.Client) *Cache {
|
|||||||
autoExpiresLinksAfter = 48 * time.Hour
|
autoExpiresLinksAfter = 48 * time.Hour
|
||||||
}
|
}
|
||||||
return &Cache{
|
return &Cache{
|
||||||
dir: path.Join(cfg.Path, "cache", dc.Name), // path to save cache files
|
dir: filepath.Join(cfg.Path, "cache", dc.Name), // path to save cache files
|
||||||
torrents: xsync.NewMapOf[string, string](),
|
torrents: xsync.NewMapOf[string, *CachedTorrent](),
|
||||||
torrentsNames: xsync.NewMapOf[string, *CachedTorrent](),
|
torrentsNames: xsync.NewMapOf[string, *CachedTorrent](),
|
||||||
invalidDownloadLinks: xsync.NewMapOf[string, string](),
|
invalidDownloadLinks: xsync.NewMapOf[string, string](),
|
||||||
client: client,
|
client: client,
|
||||||
@@ -178,7 +179,7 @@ func (c *Cache) load() (map[string]*CachedTorrent, error) {
|
|||||||
// Get only json files
|
// Get only json files
|
||||||
var jsonFiles []os.DirEntry
|
var jsonFiles []os.DirEntry
|
||||||
for _, file := range files {
|
for _, file := range files {
|
||||||
if !file.IsDir() && path.Ext(file.Name()) == ".json" {
|
if !file.IsDir() && filepath.Ext(file.Name()) == ".json" {
|
||||||
jsonFiles = append(jsonFiles, file)
|
jsonFiles = append(jsonFiles, file)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -206,7 +207,7 @@ func (c *Cache) load() (map[string]*CachedTorrent, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fileName := file.Name()
|
fileName := file.Name()
|
||||||
filePath := path.Join(c.dir, fileName)
|
filePath := filepath.Join(c.dir, fileName)
|
||||||
data, err := os.ReadFile(filePath)
|
data, err := os.ReadFile(filePath)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
c.logger.Error().Err(err).Msgf("Failed to read file: %s", filePath)
|
c.logger.Error().Err(err).Msgf("Failed to read file: %s", filePath)
|
||||||
@@ -238,8 +239,8 @@ func (c *Cache) load() (map[string]*CachedTorrent, error) {
|
|||||||
ct.AddedOn = addedOn
|
ct.AddedOn = addedOn
|
||||||
}
|
}
|
||||||
ct.IsComplete = true
|
ct.IsComplete = true
|
||||||
ct.Name = path.Clean(ct.Name)
|
|
||||||
ct.Files = fs
|
ct.Files = fs
|
||||||
|
ct.Name = filepath.Clean(ct.Name)
|
||||||
results.Store(ct.Id, &ct)
|
results.Store(ct.Id, &ct)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -393,19 +394,19 @@ func (c *Cache) sync(torrents []*types.Torrent) error {
|
|||||||
func (c *Cache) GetTorrentFolder(torrent *types.Torrent) string {
|
func (c *Cache) GetTorrentFolder(torrent *types.Torrent) string {
|
||||||
switch c.folderNaming {
|
switch c.folderNaming {
|
||||||
case WebDavUseFileName:
|
case WebDavUseFileName:
|
||||||
return path.Clean(torrent.Filename)
|
return filepath.Clean(torrent.Filename)
|
||||||
case WebDavUseOriginalName:
|
case WebDavUseOriginalName:
|
||||||
return path.Clean(torrent.OriginalFilename)
|
return filepath.Clean(torrent.OriginalFilename)
|
||||||
case WebDavUseFileNameNoExt:
|
case WebDavUseFileNameNoExt:
|
||||||
return path.Clean(utils.RemoveExtension(torrent.Filename))
|
return filepath.Clean(utils.RemoveExtension(torrent.Filename))
|
||||||
case WebDavUseOriginalNameNoExt:
|
case WebDavUseOriginalNameNoExt:
|
||||||
return path.Clean(utils.RemoveExtension(torrent.OriginalFilename))
|
return filepath.Clean(utils.RemoveExtension(torrent.OriginalFilename))
|
||||||
case WebDavUseID:
|
case WebDavUseID:
|
||||||
return torrent.Id
|
return torrent.Id
|
||||||
case WebdavUseHash:
|
case WebdavUseHash:
|
||||||
return strings.ToLower(torrent.InfoHash)
|
return strings.ToLower(torrent.InfoHash)
|
||||||
default:
|
default:
|
||||||
return path.Clean(torrent.Filename)
|
return filepath.Clean(torrent.Filename)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -514,7 +515,7 @@ func (c *Cache) SaveTorrent(ct *CachedTorrent) {
|
|||||||
func (c *Cache) saveTorrent(id string, data []byte) {
|
func (c *Cache) saveTorrent(id string, data []byte) {
|
||||||
|
|
||||||
fileName := id + ".json"
|
fileName := id + ".json"
|
||||||
filePath := path.Join(c.dir, fileName)
|
filePath := filepath.Join(c.dir, fileName)
|
||||||
|
|
||||||
// Use a unique temporary filename for concurrent safety
|
// Use a unique temporary filename for concurrent safety
|
||||||
tmpFile := filePath + ".tmp." + strconv.FormatInt(time.Now().UnixNano(), 10)
|
tmpFile := filePath + ".tmp." + strconv.FormatInt(time.Now().UnixNano(), 10)
|
||||||
@@ -675,7 +676,7 @@ func (c *Cache) DeleteTorrents(ids []string) {
|
|||||||
|
|
||||||
func (c *Cache) removeFromDB(torrentId string) {
|
func (c *Cache) removeFromDB(torrentId string) {
|
||||||
// Moves the torrent file to the trash
|
// Moves the torrent file to the trash
|
||||||
filePath := path.Join(c.dir, torrentId+".json")
|
filePath := filepath.Join(c.dir, torrentId+".json")
|
||||||
|
|
||||||
// Check if the file exists
|
// Check if the file exists
|
||||||
if _, err := os.Stat(filePath); errors.Is(err, os.ErrNotExist) {
|
if _, err := os.Stat(filePath); errors.Is(err, os.ErrNotExist) {
|
||||||
@@ -683,8 +684,8 @@ func (c *Cache) removeFromDB(torrentId string) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Move the file to the trash
|
// Move the file to the trash
|
||||||
trashPath := path.Join(c.dir, "trash", torrentId+".json")
|
trashPath := filepath.Join(c.dir, "trash", torrentId+".json")
|
||||||
if err := os.MkdirAll(path.Dir(trashPath), 0755); err != nil {
|
if err := os.MkdirAll(filepath.Dir(trashPath), 0755); err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if err := os.Rename(filePath, trashPath); err != nil {
|
if err := os.Rename(filePath, trashPath); err != nil {
|
||||||
|
|||||||
@@ -2,12 +2,14 @@ package debrid
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"github.com/beevik/etree"
|
|
||||||
"github.com/sirrobot01/decypharr/internal/request"
|
|
||||||
"net/http"
|
"net/http"
|
||||||
"os"
|
"os"
|
||||||
path "path/filepath"
|
"path"
|
||||||
|
"path/filepath"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/beevik/etree"
|
||||||
|
"github.com/sirrobot01/decypharr/internal/request"
|
||||||
)
|
)
|
||||||
|
|
||||||
// resetPropfindResponse resets the propfind response cache for the specified parent directories.
|
// resetPropfindResponse resets the propfind response cache for the specified parent directories.
|
||||||
@@ -64,15 +66,15 @@ func (c *Cache) refreshParentXml(torrents []os.FileInfo, clientName, parent stri
|
|||||||
currentTime := time.Now().UTC().Format(http.TimeFormat)
|
currentTime := time.Now().UTC().Format(http.TimeFormat)
|
||||||
|
|
||||||
// Add the parent directory
|
// Add the parent directory
|
||||||
baseUrl := path.Clean(fmt.Sprintf("/webdav/%s/%s", clientName, parent))
|
baseUrl := path.Clean(path.Join("webdav", clientName, parent))
|
||||||
parentPath := fmt.Sprintf("%s/", baseUrl)
|
parentPath := path.Join(baseUrl)
|
||||||
addDirectoryResponse(multistatus, parentPath, parent, currentTime)
|
addDirectoryResponse(multistatus, parentPath, parent, currentTime)
|
||||||
|
|
||||||
// Add torrents to the XML
|
// Add torrents to the XML
|
||||||
for _, torrent := range torrents {
|
for _, torrent := range torrents {
|
||||||
name := torrent.Name()
|
name := torrent.Name()
|
||||||
// Note the path structure change - parent first, then torrent name
|
// Note the path structure change - parent first, then torrent name
|
||||||
torrentPath := fmt.Sprintf("/webdav/%s/%s/%s/",
|
torrentPath := filepath.Join("webdav",
|
||||||
clientName,
|
clientName,
|
||||||
parent,
|
parent,
|
||||||
name,
|
name,
|
||||||
|
|||||||
@@ -4,19 +4,20 @@ import (
|
|||||||
"context"
|
"context"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"net/http"
|
||||||
|
"net/url"
|
||||||
|
"os"
|
||||||
|
"os/signal"
|
||||||
|
"runtime"
|
||||||
|
"syscall"
|
||||||
|
|
||||||
"github.com/go-chi/chi/v5"
|
"github.com/go-chi/chi/v5"
|
||||||
"github.com/go-chi/chi/v5/middleware"
|
"github.com/go-chi/chi/v5/middleware"
|
||||||
"github.com/rs/zerolog"
|
"github.com/rs/zerolog"
|
||||||
"github.com/sirrobot01/decypharr/internal/config"
|
"github.com/sirrobot01/decypharr/internal/config"
|
||||||
"github.com/sirrobot01/decypharr/internal/logger"
|
"github.com/sirrobot01/decypharr/internal/logger"
|
||||||
"github.com/sirrobot01/decypharr/internal/request"
|
"github.com/sirrobot01/decypharr/internal/request"
|
||||||
"io"
|
|
||||||
"net/http"
|
|
||||||
"os"
|
|
||||||
"os/signal"
|
|
||||||
path "path/filepath"
|
|
||||||
"runtime"
|
|
||||||
"syscall"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
type Server struct {
|
type Server struct {
|
||||||
@@ -34,9 +35,9 @@ func New(handlers map[string]http.Handler) *Server {
|
|||||||
s := &Server{
|
s := &Server{
|
||||||
logger: l,
|
logger: l,
|
||||||
}
|
}
|
||||||
|
staticPath, _ := url.JoinPath(cfg.URLBase, "static")
|
||||||
r.Handle(path.Join(cfg.URLBase, "static")+"/*",
|
r.Handle(staticPath+"/*",
|
||||||
http.StripPrefix(path.Join(cfg.URLBase, "static"), http.FileServer(http.Dir("static"))),
|
http.StripPrefix(staticPath, http.FileServer(http.Dir("static"))),
|
||||||
)
|
)
|
||||||
|
|
||||||
r.Route(cfg.URLBase, func(r chi.Router) {
|
r.Route(cfg.URLBase, func(r chi.Router) {
|
||||||
|
|||||||
@@ -4,6 +4,18 @@ import (
|
|||||||
"bytes"
|
"bytes"
|
||||||
"context"
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"html/template"
|
||||||
|
"io"
|
||||||
|
"net/http"
|
||||||
|
"net/http/httptest"
|
||||||
|
"net/url"
|
||||||
|
"os"
|
||||||
|
"path"
|
||||||
|
"path/filepath"
|
||||||
|
"slices"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
"github.com/rs/zerolog"
|
"github.com/rs/zerolog"
|
||||||
"github.com/sirrobot01/decypharr/internal/request"
|
"github.com/sirrobot01/decypharr/internal/request"
|
||||||
"github.com/sirrobot01/decypharr/internal/utils"
|
"github.com/sirrobot01/decypharr/internal/utils"
|
||||||
@@ -11,16 +23,6 @@ import (
|
|||||||
"github.com/sirrobot01/decypharr/pkg/debrid/types"
|
"github.com/sirrobot01/decypharr/pkg/debrid/types"
|
||||||
"github.com/sirrobot01/decypharr/pkg/version"
|
"github.com/sirrobot01/decypharr/pkg/version"
|
||||||
"golang.org/x/net/webdav"
|
"golang.org/x/net/webdav"
|
||||||
"html/template"
|
|
||||||
"io"
|
|
||||||
"net/http"
|
|
||||||
"net/http/httptest"
|
|
||||||
"net/url"
|
|
||||||
"os"
|
|
||||||
path "path/filepath"
|
|
||||||
"slices"
|
|
||||||
"strings"
|
|
||||||
"time"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
type Handler struct {
|
type Handler struct {
|
||||||
@@ -47,9 +49,12 @@ func (h *Handler) Mkdir(ctx context.Context, name string, perm os.FileMode) erro
|
|||||||
|
|
||||||
// RemoveAll implements webdav.FileSystem
|
// RemoveAll implements webdav.FileSystem
|
||||||
func (h *Handler) RemoveAll(ctx context.Context, name string) error {
|
func (h *Handler) RemoveAll(ctx context.Context, name string) error {
|
||||||
name = path.Clean("/" + name)
|
if name[0] != '/' {
|
||||||
|
name = "/" + name
|
||||||
|
}
|
||||||
|
name = filepath.Clean(name)
|
||||||
|
|
||||||
rootDir := h.getRootPath()
|
rootDir := filepath.Clean(h.getRootPath())
|
||||||
|
|
||||||
if name == rootDir {
|
if name == rootDir {
|
||||||
return os.ErrPermission
|
return os.ErrPermission
|
||||||
@@ -72,7 +77,7 @@ func (h *Handler) Rename(ctx context.Context, oldName, newName string) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (h *Handler) getRootPath() string {
|
func (h *Handler) getRootPath() string {
|
||||||
return fmt.Sprintf("/webdav/%s", h.Name)
|
return fmt.Sprintf(filepath.Join(string(os.PathSeparator), "webdav", "%s"), h.Name)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (h *Handler) getTorrentsFolders() []os.FileInfo {
|
func (h *Handler) getTorrentsFolders() []os.FileInfo {
|
||||||
@@ -104,8 +109,11 @@ func (h *Handler) getParentFiles() []os.FileInfo {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (h *Handler) OpenFile(ctx context.Context, name string, flag int, perm os.FileMode) (webdav.File, error) {
|
func (h *Handler) OpenFile(ctx context.Context, name string, flag int, perm os.FileMode) (webdav.File, error) {
|
||||||
name = utils.UnescapePath(path.Clean("/" + name))
|
if name[0] != '/' {
|
||||||
rootDir := h.getRootPath()
|
name = "/" + name
|
||||||
|
}
|
||||||
|
name = utils.UnescapePath(filepath.Clean(name))
|
||||||
|
rootDir := filepath.Clean(h.getRootPath())
|
||||||
|
|
||||||
metadataOnly := ctx.Value("metadataOnly") != nil
|
metadataOnly := ctx.Value("metadataOnly") != nil
|
||||||
|
|
||||||
@@ -118,11 +126,11 @@ func (h *Handler) OpenFile(ctx context.Context, name string, flag int, perm os.F
|
|||||||
cache: h.cache,
|
cache: h.cache,
|
||||||
isDir: true,
|
isDir: true,
|
||||||
children: h.getParentFiles(),
|
children: h.getParentFiles(),
|
||||||
name: "/",
|
name: string(os.PathSeparator),
|
||||||
metadataOnly: true,
|
metadataOnly: true,
|
||||||
modTime: now,
|
modTime: now,
|
||||||
}, nil
|
}, nil
|
||||||
case path.Join(rootDir, "version.txt"):
|
case filepath.Join(rootDir, "version.txt"):
|
||||||
versionInfo := version.GetInfo().String()
|
versionInfo := version.GetInfo().String()
|
||||||
return &File{
|
return &File{
|
||||||
cache: h.cache,
|
cache: h.cache,
|
||||||
@@ -138,7 +146,7 @@ func (h *Handler) OpenFile(ctx context.Context, name string, flag int, perm os.F
|
|||||||
// Single check for top-level folders
|
// Single check for top-level folders
|
||||||
if h.isParentPath(name) {
|
if h.isParentPath(name) {
|
||||||
folderName := strings.TrimPrefix(name, rootDir)
|
folderName := strings.TrimPrefix(name, rootDir)
|
||||||
folderName = strings.TrimPrefix(folderName, "/")
|
folderName = strings.TrimPrefix(folderName, string(os.PathSeparator))
|
||||||
|
|
||||||
// Only fetcher the torrent folders once
|
// Only fetcher the torrent folders once
|
||||||
children := h.getTorrentsFolders()
|
children := h.getTorrentsFolders()
|
||||||
@@ -155,7 +163,7 @@ func (h *Handler) OpenFile(ctx context.Context, name string, flag int, perm os.F
|
|||||||
}
|
}
|
||||||
|
|
||||||
_path := strings.TrimPrefix(name, rootDir)
|
_path := strings.TrimPrefix(name, rootDir)
|
||||||
parts := strings.Split(strings.TrimPrefix(_path, "/"), "/")
|
parts := strings.Split(strings.TrimPrefix(_path, string(os.PathSeparator)), string(os.PathSeparator))
|
||||||
|
|
||||||
if len(parts) >= 2 && (slices.Contains(h.getParentItems(), parts[0])) {
|
if len(parts) >= 2 && (slices.Contains(h.getParentItems(), parts[0])) {
|
||||||
|
|
||||||
@@ -181,7 +189,7 @@ func (h *Handler) OpenFile(ctx context.Context, name string, flag int, perm os.F
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Torrent file level
|
// Torrent file level
|
||||||
filename := strings.Join(parts[2:], "/")
|
filename := filepath.Join(parts[2:]...)
|
||||||
if file, ok := cachedTorrent.Files[filename]; ok {
|
if file, ok := cachedTorrent.Files[filename]; ok {
|
||||||
return &File{
|
return &File{
|
||||||
cache: h.cache,
|
cache: h.cache,
|
||||||
@@ -239,7 +247,7 @@ func (h *Handler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
|||||||
// Set metadata only
|
// Set metadata only
|
||||||
ctx := context.WithValue(r.Context(), "metadataOnly", true)
|
ctx := context.WithValue(r.Context(), "metadataOnly", true)
|
||||||
r = r.WithContext(ctx)
|
r = r.WithContext(ctx)
|
||||||
cleanPath := path.Clean(r.URL.Path)
|
cleanPath := filepath.Clean(r.URL.Path)
|
||||||
if r.Header.Get("Depth") == "" {
|
if r.Header.Get("Depth") == "" {
|
||||||
r.Header.Set("Depth", "1")
|
r.Header.Set("Depth", "1")
|
||||||
}
|
}
|
||||||
@@ -399,7 +407,7 @@ func (h *Handler) isParentPath(_path string) bool {
|
|||||||
rootPath := h.getRootPath()
|
rootPath := h.getRootPath()
|
||||||
parents := h.getParentItems()
|
parents := h.getParentItems()
|
||||||
for _, p := range parents {
|
for _, p := range parents {
|
||||||
if path.Clean(_path) == path.Clean(path.Join(rootPath, p)) {
|
if filepath.Clean(_path) == filepath.Clean(filepath.Join(rootPath, p)) {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ package webdav
|
|||||||
import (
|
import (
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/url"
|
"net/url"
|
||||||
|
"os"
|
||||||
"strings"
|
"strings"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -10,11 +11,11 @@ import (
|
|||||||
// /webdav/alldebrid/__all__/TorrentName
|
// /webdav/alldebrid/__all__/TorrentName
|
||||||
func getName(rootDir, path string) (string, string) {
|
func getName(rootDir, path string) (string, string) {
|
||||||
path = strings.TrimPrefix(path, rootDir)
|
path = strings.TrimPrefix(path, rootDir)
|
||||||
parts := strings.Split(strings.TrimPrefix(path, "/"), "/")
|
parts := strings.Split(strings.TrimPrefix(path, string(os.PathSeparator)), string(os.PathSeparator))
|
||||||
if len(parts) < 2 {
|
if len(parts) < 2 {
|
||||||
return "", ""
|
return "", ""
|
||||||
}
|
}
|
||||||
return parts[1], strings.Join(parts[2:], "/") // Note the change from [0] to [1]
|
return parts[1], strings.Join(parts[2:], string(os.PathSeparator)) // Note the change from [0] to [1]
|
||||||
}
|
}
|
||||||
|
|
||||||
func acceptsGzip(r *http.Request) bool {
|
func acceptsGzip(r *http.Request) bool {
|
||||||
|
|||||||
Reference in New Issue
Block a user