package repair import ( "fmt" "github.com/sirrobot01/decypharr/pkg/arr" "os" "path/filepath" "strconv" "strings" "time" ) func parseSchedule(schedule string) (time.Duration, error) { if schedule == "" { return time.Hour, nil // default 60m } // Check if it's a time-of-day format (HH:MM) if strings.Contains(schedule, ":") { return parseTimeOfDay(schedule) } // Otherwise treat as duration interval return parseDurationInterval(schedule) } func parseTimeOfDay(schedule string) (time.Duration, error) { now := time.Now() scheduledTime, err := time.Parse("15:04", schedule) if err != nil { return 0, fmt.Errorf("invalid time format: %s. Use HH:MM in 24-hour format", schedule) } // Convert scheduled time to today scheduleToday := time.Date( now.Year(), now.Month(), now.Day(), scheduledTime.Hour(), scheduledTime.Minute(), 0, 0, now.Location(), ) if scheduleToday.Before(now) { scheduleToday = scheduleToday.Add(24 * time.Hour) } return scheduleToday.Sub(now), nil } func parseDurationInterval(interval string) (time.Duration, error) { if len(interval) < 2 { return 0, fmt.Errorf("invalid interval format: %s", interval) } numStr := interval[:len(interval)-1] unit := interval[len(interval)-1] num, err := strconv.Atoi(numStr) if err != nil { return 0, fmt.Errorf("invalid number in interval: %s", numStr) } switch unit { case 'm': return time.Duration(num) * time.Minute, nil case 'h': return time.Duration(num) * time.Hour, nil case 'd': return time.Duration(num) * 24 * time.Hour, nil case 's': return time.Duration(num) * time.Second, nil default: return 0, fmt.Errorf("invalid unit in interval: %c", unit) } } func fileIsSymlinked(file string) bool { info, err := os.Lstat(file) if err != nil { return false } return info.Mode()&os.ModeSymlink != 0 } func getSymlinkTarget(file string) string { if fileIsSymlinked(file) { target, err := os.Readlink(file) if err != nil { return "" } if !filepath.IsAbs(target) { dir := filepath.Dir(file) target = filepath.Join(dir, target) } return target } return "" } func fileIsReadable(filePath string) error { // First check if file exists and is accessible info, err := os.Stat(filePath) if err != nil { return err } // Check if it's a regular file if !info.Mode().IsRegular() { return fmt.Errorf("not a regular file") } // Try to read the first 1024 bytes err = checkFileStart(filePath) if err != nil { return err } return nil } func checkFileStart(filePath string) error { f, err := os.Open(filePath) if err != nil { return err } defer f.Close() // Read first 1kb buffer := make([]byte, 1024) _, err = f.Read(buffer) if err != nil { return err } return nil } func collectFiles(media arr.Content) map[string][]arr.ContentFile { uniqueParents := make(map[string][]arr.ContentFile) files := media.Files for _, file := range files { target := getSymlinkTarget(file.Path) if target != "" { file.IsSymlink = true dir, f := filepath.Split(target) torrentNamePath := filepath.Clean(dir) // Set target path folder/file.mkv file.TargetPath = f uniqueParents[torrentNamePath] = append(uniqueParents[torrentNamePath], file) } } return uniqueParents }