Add more rclone flags, fix minor issues

This commit is contained in:
Mukhtar Akere
2025-08-23 06:00:07 +01:00
parent b0a698f15e
commit f8667938b6
27 changed files with 256 additions and 123 deletions
+30 -7
View File
@@ -4,19 +4,18 @@ import (
"fmt"
"os"
"os/exec"
"path/filepath"
"time"
"github.com/sirrobot01/decypharr/internal/config"
)
// Mount creates a mount using the rclone RC API with retry logic
func (m *Manager) Mount(provider, webdavURL string) error {
return m.mountWithRetry(provider, webdavURL, 3)
func (m *Manager) Mount(mountPath, provider, webdavURL string) error {
return m.mountWithRetry(mountPath, provider, webdavURL, 3)
}
// mountWithRetry attempts to mount with retry logic
func (m *Manager) mountWithRetry(provider, webdavURL string, maxRetries int) error {
func (m *Manager) mountWithRetry(mountPath, provider, webdavURL string, maxRetries int) error {
if !m.IsReady() {
if err := m.WaitForReady(30 * time.Second); err != nil {
return fmt.Errorf("rclone RC server not ready: %w", err)
@@ -34,7 +33,7 @@ func (m *Manager) mountWithRetry(provider, webdavURL string, maxRetries int) err
time.Sleep(wait)
}
if err := m.performMount(provider, webdavURL); err != nil {
if err := m.performMount(mountPath, provider, webdavURL); err != nil {
m.logger.Error().
Err(err).
Str("provider", provider).
@@ -49,9 +48,8 @@ func (m *Manager) mountWithRetry(provider, webdavURL string, maxRetries int) err
}
// performMount performs a single mount attempt
func (m *Manager) performMount(provider, webdavURL string) error {
func (m *Manager) performMount(mountPath, provider, webdavURL string) error {
cfg := config.Get()
mountPath := filepath.Join(cfg.Rclone.MountPath, provider)
// Create mount directory
if err := os.MkdirAll(mountPath, 0755); err != nil {
@@ -94,6 +92,18 @@ func (m *Manager) performMount(provider, webdavURL string) error {
"VolumeName": fmt.Sprintf("decypharr-%s", provider),
}
if cfg.Rclone.AsyncRead != nil {
mountOpt["AsyncRead"] = *cfg.Rclone.AsyncRead
}
if cfg.Rclone.UseMmap {
mountOpt["UseMmap"] = cfg.Rclone.UseMmap
}
if cfg.Rclone.Transfers != 0 {
mountOpt["Transfers"] = cfg.Rclone.Transfers
}
configOpts := make(map[string]interface{})
if cfg.Rclone.BufferSize != "" {
@@ -127,6 +137,19 @@ func (m *Manager) performMount(provider, webdavURL string) error {
if cfg.Rclone.VfsReadAhead != "" {
vfsOpt["ReadAhead"] = cfg.Rclone.VfsReadAhead
}
if cfg.Rclone.VfsCacheMinFreeSpace != "" {
vfsOpt["CacheMinFreeSpace"] = cfg.Rclone.VfsCacheMinFreeSpace
}
if cfg.Rclone.VfsFastFingerprint {
vfsOpt["FastFingerprint"] = cfg.Rclone.VfsFastFingerprint
}
if cfg.Rclone.VfsReadChunkStreams != 0 {
vfsOpt["ReadChunkStreams"] = cfg.Rclone.VfsReadChunkStreams
}
if cfg.Rclone.NoChecksum {
vfsOpt["NoChecksum"] = cfg.Rclone.NoChecksum
}
+1 -1
View File
@@ -74,7 +74,7 @@ func (m *Manager) RecoverMount(provider string) error {
time.Sleep(1 * time.Second)
// Try to remount
if err := m.Mount(provider, mountInfo.WebDAVURL); err != nil {
if err := m.Mount(mountInfo.LocalPath, provider, mountInfo.WebDAVURL); err != nil {
return fmt.Errorf("failed to recover mount for %s: %w", provider, err)
}
+21 -29
View File
@@ -6,11 +6,13 @@ import (
"encoding/json"
"errors"
"fmt"
"io"
"net/http"
"os"
"os/exec"
"path/filepath"
"slices"
"strings"
"sync"
"time"
@@ -261,50 +263,35 @@ func (m *Manager) Stop() error {
case err := <-done:
if err != nil && !errors.Is(err, context.Canceled) && !WasHardTerminated(err) {
m.logger.Warn().Err(err).Msg("Rclone process exited with error")
} else {
m.logger.Info().Msg("Rclone process exited gracefully")
}
case <-time.After(10 * time.Second):
case <-time.After(2 * time.Second):
m.logger.Warn().Msg("Timeout waiting for rclone to exit, force killing")
if err := m.cmd.Process.Kill(); err != nil {
m.logger.Error().Err(err).Msg("Failed to force kill rclone process")
return err
// Check if the process already finished
if !strings.Contains(err.Error(), "process already finished") {
m.logger.Error().Err(err).Msg("Failed to force kill rclone process")
return err
}
m.logger.Info().Msg("Process already finished during kill attempt")
}
// Wait a bit more for the kill to take effect
// Still wait for the Wait() to complete to clean up the process
select {
case <-done:
m.logger.Info().Msg("Rclone process killed successfully")
m.logger.Info().Msg("Rclone process cleanup completed")
case <-time.After(5 * time.Second):
m.logger.Error().Msg("Process may still be running after kill")
m.logger.Error().Msg("Process cleanup timeout")
}
}
}
// Clean up any remaining mount directories
cfg := config.Get()
if cfg.Rclone.MountPath != "" {
m.cleanupMountDirectories(cfg.Rclone.MountPath)
}
m.serverStarted = false
m.logger.Info().Msg("Rclone RC server stopped")
return nil
}
// cleanupMountDirectories removes empty mount directories
func (m *Manager) cleanupMountDirectories(_ string) {
m.mountsMutex.RLock()
defer m.mountsMutex.RUnlock()
for _, mount := range m.mounts {
if mount.LocalPath != "" {
// Try to remove the directory if it's empty
if err := os.Remove(mount.LocalPath); err == nil {
m.logger.Debug().Str("path", mount.LocalPath).Msg("Removed empty mount directory")
}
// Don't log errors here as the directory might not be empty, which is fine
}
}
}
// waitForServer waits for the RC server to become available
func (m *Manager) waitForServer() {
maxAttempts := 30
@@ -352,7 +339,12 @@ func (m *Manager) makeRequest(req RCRequest, close bool) (*http.Response, error)
if resp.StatusCode != http.StatusOK {
// Read the response body to get more details
defer resp.Body.Close()
defer func(Body io.ReadCloser) {
err := Body.Close()
if err != nil {
m.logger.Debug().Err(err).Msg("Failed to close response body")
}
}(resp.Body)
var errorResp RCResponse
if err := json.NewDecoder(resp.Body).Decode(&errorResp); err != nil {
return nil, fmt.Errorf("request failed with status %s, but could not decode error response: %w", resp.Status, err)
+1 -1
View File
@@ -65,7 +65,7 @@ func (m *Mount) Mount(ctx context.Context) error {
Str("mount_path", m.LocalPath).
Msg("Creating mount via RC")
if err := m.rcManager.Mount(m.Provider, m.WebDAVURL); err != nil {
if err := m.rcManager.Mount(m.LocalPath, m.Provider, m.WebDAVURL); err != nil {
m.logger.Error().Str("provider", m.Provider).Msg("Mount operation failed")
return fmt.Errorf("mount failed for %s", m.Provider)
}