- Add more rclone supports

- Add rclone log viewer
- Add more stats to Stats page
- Fix some minor bugs
This commit is contained in:
Mukhtar Akere
2025-08-18 01:57:02 +01:00
parent 742d8fb088
commit 8696db42d2
40 changed files with 787 additions and 253 deletions
+21 -17
View File
@@ -70,7 +70,10 @@ func (m *Manager) performMount(provider, webdavURL string) error {
// Clean up any stale mount first
if exists && !existingMount.Mounted {
m.forceUnmountPath(mountPath)
err := m.forceUnmountPath(mountPath)
if err != nil {
return err
}
}
// Create rclone config for this provider
@@ -82,14 +85,13 @@ func (m *Manager) performMount(provider, webdavURL string) error {
mountArgs := map[string]interface{}{
"fs": fmt.Sprintf("%s:", provider),
"mountPoint": mountPath,
"mountType": "mount", // Use standard FUSE mount
"mountOpt": map[string]interface{}{
"AllowNonEmpty": true,
"AllowOther": true,
"DebugFUSE": false,
"DeviceName": fmt.Sprintf("decypharr-%s", provider),
"VolumeName": fmt.Sprintf("decypharr-%s", provider),
},
}
mountOpt := map[string]interface{}{
"AllowNonEmpty": true,
"AllowOther": true,
"DebugFUSE": false,
"DeviceName": fmt.Sprintf("decypharr-%s", provider),
"VolumeName": fmt.Sprintf("decypharr-%s", provider),
}
configOpts := make(map[string]interface{})
@@ -102,12 +104,13 @@ func (m *Manager) performMount(provider, webdavURL string) error {
// Only add _config if there are options to set
mountArgs["_config"] = configOpts
}
vfsOpt := map[string]interface{}{
"CacheMode": cfg.Rclone.VfsCacheMode,
}
vfsOpt["PollInterval"] = 0 // Poll interval not supported for webdav, set to 0
// Add VFS options if caching is enabled
if cfg.Rclone.VfsCacheMode != "off" {
vfsOpt := map[string]interface{}{
"CacheMode": cfg.Rclone.VfsCacheMode,
}
if cfg.Rclone.VfsCacheMaxAge != "" {
vfsOpt["CacheMaxAge"] = cfg.Rclone.VfsCacheMaxAge
@@ -130,22 +133,23 @@ func (m *Manager) performMount(provider, webdavURL string) error {
if cfg.Rclone.NoModTime {
vfsOpt["NoModTime"] = cfg.Rclone.NoModTime
}
mountArgs["vfsOpt"] = vfsOpt
}
// Add mount options based on configuration
if cfg.Rclone.UID != 0 {
mountArgs["mountOpt"].(map[string]interface{})["UID"] = cfg.Rclone.UID
mountOpt["UID"] = cfg.Rclone.UID
}
if cfg.Rclone.GID != 0 {
mountArgs["mountOpt"].(map[string]interface{})["GID"] = cfg.Rclone.GID
mountOpt["GID"] = cfg.Rclone.GID
}
if cfg.Rclone.AttrTimeout != "" {
if attrTimeout, err := time.ParseDuration(cfg.Rclone.AttrTimeout); err == nil {
mountArgs["mountOpt"].(map[string]interface{})["AttrTimeout"] = attrTimeout.String()
mountOpt["AttrTimeout"] = attrTimeout.String()
}
}
mountArgs["vfsOpt"] = vfsOpt
mountArgs["mountOpt"] = mountOpt
// Make the mount request
req := RCRequest{
Command: "mount/mount",
+1 -1
View File
@@ -71,7 +71,7 @@ func (m *Manager) RecoverMount(provider string) error {
}
// Wait a moment
time.Sleep(2 * time.Second)
time.Sleep(1 * time.Second)
// Try to remount
if err := m.Mount(provider, mountInfo.WebDAVURL); err != nil {
+18 -8
View File
@@ -24,7 +24,7 @@ type Manager struct {
rcPort string
rcUser string
rcPass string
configDir string
rcloneDir string
mounts map[string]*MountInfo
mountsMutex sync.RWMutex
logger zerolog.Logger
@@ -61,10 +61,10 @@ func NewManager() *Manager {
cfg := config.Get()
rcPort := "5572"
configDir := filepath.Join(cfg.Path, "rclone")
rcloneDir := filepath.Join(cfg.Path, "rclone")
// Ensure config directory exists
if err := os.MkdirAll(configDir, 0755); err != nil {
if err := os.MkdirAll(rcloneDir, 0755); err != nil {
_logger := logger.New("rclone")
_logger.Error().Err(err).Msg("Failed to create rclone config directory")
}
@@ -73,7 +73,7 @@ func NewManager() *Manager {
return &Manager{
rcPort: rcPort,
configDir: configDir,
rcloneDir: rcloneDir,
mounts: make(map[string]*MountInfo),
logger: logger.New("rclone"),
ctx: ctx,
@@ -98,12 +98,22 @@ func (m *Manager) Start(ctx context.Context) error {
return nil
}
logFile := filepath.Join(logger.GetLogPath(), "rclone.log")
// Delete old log file if it exists
if _, err := os.Stat(logFile); err == nil {
if err := os.Remove(logFile); err != nil {
return fmt.Errorf("failed to remove old rclone log file: %w", err)
}
}
args := []string{
"rcd",
"--rc-addr", ":" + m.rcPort,
"--rc-no-auth", // We'll handle auth at the application level
"--config", filepath.Join(m.configDir, "rclone.conf"),
"--log-level", "INFO",
"--config", filepath.Join(m.rcloneDir, "rclone.conf"),
"--log-level", cfg.Rclone.LogLevel,
"--log-file", logFile,
}
if cfg.Rclone.CacheDir != "" {
if err := os.MkdirAll(cfg.Rclone.CacheDir, 0755); err == nil {
@@ -111,7 +121,6 @@ func (m *Manager) Start(ctx context.Context) error {
}
}
m.cmd = exec.CommandContext(ctx, "rclone", args...)
m.cmd.Dir = m.configDir
// Capture output for debugging
var stdout, stderr bytes.Buffer
@@ -119,9 +128,10 @@ func (m *Manager) Start(ctx context.Context) error {
m.cmd.Stderr = &stderr
if err := m.cmd.Start(); err != nil {
m.logger.Error().Str("stderr", stderr.String()).Str("stdout", stdout.String()).
Err(err).Msg("Failed to start rclone RC server")
return fmt.Errorf("failed to start rclone RC server: %w", err)
}
m.serverStarted = true
// Wait for server to be ready in a goroutine
+12 -2
View File
@@ -7,6 +7,7 @@ import (
"github.com/sirrobot01/decypharr/internal/config"
"net/url"
"path/filepath"
"strings"
)
// Mount represents a mount using the rclone RC client
@@ -19,15 +20,24 @@ type Mount struct {
}
// NewMount creates a new RC-based mount
func NewMount(provider, webdavURL string, rcManager *Manager) *Mount {
func NewMount(provider, customRcloneMount, webdavURL string, rcManager *Manager) *Mount {
cfg := config.Get()
mountPath := filepath.Join(cfg.Rclone.MountPath, provider)
var mountPath string
if customRcloneMount != "" {
mountPath = customRcloneMount
} else {
mountPath = filepath.Join(cfg.Rclone.MountPath, provider)
}
_url, err := url.JoinPath(webdavURL, provider)
if err != nil {
_url = fmt.Sprintf("%s/%s", webdavURL, provider)
}
if !strings.HasSuffix(_url, "/") {
_url += "/"
}
return &Mount{
Provider: provider,
LocalPath: mountPath,
+1 -1
View File
@@ -76,7 +76,7 @@ func (m *Manager) GetStats() (*Stats, error) {
}
// Get bandwidth stats
bwStats, err := m.GetBandwidthStats()
if err == nil {
if err == nil && bwStats != nil {
stats.Bandwidth = *bwStats
} else {
fmt.Println("Failed to get rclone stats", err)