Skip to content
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
36 changes: 30 additions & 6 deletions repair.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,13 +22,19 @@ def parseInterval(intervalStr):
totalSeconds += int(currentNumber) * timeDict[char]
currentNumber = ''
return totalSeconds

def is_broken_symlink(path):
"""Check if a symlink is broken by verifying if its target exists."""
return os.path.islink(path) and not os.path.exists(os.readlink(path))

# Parse arguments for dry run, no confirm options, and optional intervals
parser = argparse.ArgumentParser(description='Repair broken symlinks or missing files.')
parser.add_argument('--dry-run', action='store_true', help='Perform a dry run without making any changes.')
parser.add_argument('--no-confirm', action='store_true', help='Execute without confirmation prompts.')
parser.add_argument('--repair-interval', type=str, default=repair['repairInterval'], help='Optional interval in smart format (e.g. 1h2m3s) to wait between repairing each media file.')
parser.add_argument('--run-interval', type=str, default=repair['runInterval'], help='Optional interval in smart format (e.g. 1w2d3h4m5s) to run the repair process.')
parser.add_argument('--mode', type=str, choices=['symlink', 'file'], default='symlink', help='Choose repair mode: `symlink` or `file`. `symlink` to repair broken symlinks and `file` to repair missing files.')
parser.add_argument('--season-packs', action='store_true', help='Upgrade to season-packs when a non-season-pack is found. Only applicable in symlink mode.')
parser.add_argument('--include-unmonitored', action='store_true', help='Include unmonitored media in the repair process')
args = parser.parse_args()

Expand Down Expand Up @@ -86,8 +92,20 @@ def main():
fullPath = item.path
if os.path.islink(fullPath):
destinationPath = os.readlink(fullPath)
if ((realdebrid['enabled'] and destinationPath.startswith(realdebrid['mountTorrentsPath']) and not os.path.exists(destinationPath)) or
(torbox['enabled'] and destinationPath.startswith(torbox['mountTorrentsPath']) and not os.path.exists(os.path.realpath(fullPath)))):
is_broken = False

# Check if the symlink is broken (either doesn't exist or points to nothing)
if not os.path.exists(fullPath) or not os.path.exists(destinationPath):
is_broken = True

# Check debrid-specific paths if the link isn't already known to be broken
if not is_broken and (
(realdebrid['enabled'] and destinationPath.startswith(realdebrid['mountTorrentsPath']) and not os.path.exists(destinationPath)) or
(torbox['enabled'] and destinationPath.startswith(torbox['mountTorrentsPath']) and not os.path.exists(os.path.realpath(fullPath)))
):
is_broken = True

if is_broken:
brokenItems.append(os.path.realpath(fullPath))
else: # file mode
if item.reason == 'MissingFromDisk' and item.parentId not in media.fullyAvailableChildrenIds:
Expand Down Expand Up @@ -127,12 +145,19 @@ def main():
if childId in media.fullyAvailableChildrenIds and len(parentFolders) > 1:
print("Title:", media.title)
print("Movie ID/Season Number:", childId)
print("Inconsistent folders:")
print("Non-season-pack folders:")
[print(parentFolder) for parentFolder in parentFolders]
print()
if args.season_packs:
print("Searching for season-pack")
results = arr.automaticSearch(media, childId)
print(results)

if repairIntervalSeconds > 0:
time.sleep(repairIntervalSeconds)

except Exception:
e = traceback.format_exc()

print(f"An error occurred while processing {media.title}: {e}")
discordError(f"[{args.mode}] An error occurred while processing {media.title}", e)

Expand All @@ -151,9 +176,8 @@ def unsafe():
time.sleep(runIntervalSeconds)
except Exception:
e = traceback.format_exc()

print(f"An error occurred in the main loop: {e}")
discordError(f"[{args.mode}] An error occurred in the main loop", e)
time.sleep(runIntervalSeconds) # Still wait before retrying
else:
main()
main()