- Add PROPFIND for root path

- Reduce signifcantly memoery footprint
- Fix minor bugs
This commit is contained in:
Mukhtar Akere
2025-05-20 12:57:27 +01:00
parent 53748ea297
commit 5aa1c67544
36 changed files with 632 additions and 335 deletions

View File

@@ -2,9 +2,9 @@ package config
import (
"cmp"
"encoding/json"
"errors"
"fmt"
"github.com/goccy/go-json"
"os"
"path/filepath"
"runtime"

View File

@@ -2,8 +2,8 @@ package request
import (
"bytes"
"encoding/json"
"fmt"
"github.com/goccy/go-json"
"github.com/sirrobot01/decypharr/internal/config"
"io"
"net/http"

View File

@@ -5,8 +5,9 @@ import (
"compress/gzip"
"context"
"crypto/tls"
"encoding/json"
"errors"
"fmt"
"github.com/goccy/go-json"
"github.com/rs/zerolog"
"github.com/sirrobot01/decypharr/internal/logger"
"golang.org/x/net/proxy"
@@ -180,7 +181,7 @@ func (c *Client) Do(req *http.Request) (*http.Response, error) {
resp, err = c.doRequest(req)
if err != nil {
// Check if this is a network error that might be worth retrying
if attempt < c.maxRetries {
if isRetryableError(err) && attempt < c.maxRetries {
// Apply backoff with jitter
jitter := time.Duration(rand.Int63n(int64(backoff / 4)))
sleepTime := backoff + jitter
@@ -413,3 +414,30 @@ func Default() *Client {
})
return instance
}
func isRetryableError(err error) bool {
errString := err.Error()
// Connection reset and other network errors
if strings.Contains(errString, "connection reset by peer") ||
strings.Contains(errString, "read: connection reset") ||
strings.Contains(errString, "connection refused") ||
strings.Contains(errString, "network is unreachable") ||
strings.Contains(errString, "connection timed out") ||
strings.Contains(errString, "no such host") ||
strings.Contains(errString, "i/o timeout") ||
strings.Contains(errString, "unexpected EOF") ||
strings.Contains(errString, "TLS handshake timeout") {
return true
}
// Check for net.Error type which can provide more information
var netErr net.Error
if errors.As(err, &netErr) {
// Retry on timeout errors and temporary errors
return netErr.Timeout() || netErr.Temporary()
}
// Not a retryable error
return false
}

View File

@@ -31,3 +31,13 @@ func (d *Debouncer[T]) Call(arg T) {
d.caller(arg)
})
}
func (d *Debouncer[T]) Stop() {
d.mu.Lock()
defer d.mu.Unlock()
if d.timer != nil {
d.timer.Stop()
d.timer = nil
}
}

View File

@@ -10,29 +10,27 @@ import (
"time"
)
func ScheduleJob(ctx context.Context, interval string, loc *time.Location, jobFunc func()) (gocron.Job, error) {
func ScheduleJob(ctx context.Context, interval string, loc *time.Location, jobFunc func()) (gocron.Scheduler, error) {
if loc == nil {
loc = time.Local
}
var job gocron.Job
s, err := gocron.NewScheduler(gocron.WithLocation(loc))
if err != nil {
return job, fmt.Errorf("failed to create scheduler: %w", err)
return s, fmt.Errorf("failed to create scheduler: %w", err)
}
jd, err := convertToJD(interval)
jd, err := ConvertToJobDef(interval)
if err != nil {
return job, fmt.Errorf("failed to convert interval to job definition: %w", err)
return s, fmt.Errorf("failed to convert interval to job definition: %w", err)
}
// Schedule the job
if job, err = s.NewJob(jd, gocron.NewTask(jobFunc), gocron.WithContext(ctx)); err != nil {
return job, fmt.Errorf("failed to create job: %w", err)
if _, err = s.NewJob(jd, gocron.NewTask(jobFunc), gocron.WithContext(ctx)); err != nil {
return s, fmt.Errorf("failed to create job: %w", err)
}
s.Start()
return job, nil
return s, nil
}
// ConvertToJobDef converts a string interval to a gocron.JobDefinition.
func convertToJD(interval string) (gocron.JobDefinition, error) {
func ConvertToJobDef(interval string) (gocron.JobDefinition, error) {
// Parse the interval string
// Interval could be in the format "1h", "30m", "15s" or "1h30m" or "04:05"
var jd gocron.JobDefinition