11#!/usr/bin/env python3
22"""
3- Huntarr [Lidarr Edition] - Python Version
4- Main entry point for the application
3+ Huntarr [Lidarr Edition]
4+
5+ Main entry point, orchestrating:
6+ - Missing logic (artist/album) from missing.py
7+ - Upgrade logic (track-based) from upgrade.py
8+
9+ Ensures both run in a single cycle if desired, then sleeps once at the end.
10+ Handles state reset time comparison with datetime objects (avoids TypeError).
511"""
612
13+ import os
14+ import json
715import time
8- import sys
9- from utils .logger import logger
10- from config import HUNT_MISSING_MODE , log_configuration
11- from missing .artist import process_artists_missing
12- from missing .album import process_albums_missing
13- from upgrade .album import process_album_upgrades
14-
15- def main_loop () -> None :
16- """Main processing loop for Huntarr-Lidarr"""
16+ import logging
17+ import pathlib
18+ from datetime import datetime , timedelta
19+ from typing import Dict , Any
20+
21+ from missing import process_albums_missing , process_artists_missing
22+ from upgrade import process_cutoff_upgrades
23+
24+ # ---------------------------
25+ # Environment / Config
26+ # ---------------------------
27+ API_KEY = os .environ .get ("API_KEY" , "your-api-key" )
28+ API_URL = os .environ .get ("API_URL" , "http://10.0.0.10:8686" )
29+
30+ HUNT_MISSING_MODE = os .environ .get ("HUNT_MISSING_MODE" , "album" ) # "artist" or "album"
31+ HUNT_MISSING_ITEMS = int (os .environ .get ("HUNT_MISSING_ITEMS" , "0" )) # how many to process
32+ HUNT_UPGRADE_ALBUMS = int (os .environ .get ("HUNT_UPGRADE_ALBUMS" , "0" )) # how many upgrades
33+
34+ MONITORED_ONLY = os .environ .get ("MONITORED_ONLY" , "true" ).lower () == "true"
35+ RANDOM_SELECTION = os .environ .get ("RANDOM_SELECTION" , "true" ).lower () == "true"
36+
37+ try :
38+ STATE_RESET_INTERVAL_HOURS = int (os .environ .get ("STATE_RESET_INTERVAL_HOURS" , "168" ))
39+ except ValueError :
40+ STATE_RESET_INTERVAL_HOURS = 168
41+
42+ try :
43+ SLEEP_DURATION = int (os .environ .get ("SLEEP_DURATION" , "900" ))
44+ except ValueError :
45+ SLEEP_DURATION = 900
46+
47+ logging .basicConfig (
48+ level = logging .INFO ,
49+ format = "%(asctime)s - %(name)s - %(levelname)s - %(message)s" ,
50+ datefmt = "%Y-%m-%d %H:%M:%S" ,
51+ )
52+ logger = logging .getLogger ("huntarr-lidarr" )
53+
54+ STATE_FILE = pathlib .Path ("/config/state.json" )
55+
56+ # ---------------------------
57+ # Load/Save State
58+ # ---------------------------
59+ def load_state () -> Dict [str , Any ]:
60+ if not STATE_FILE .exists ():
61+ return {"last_reset" : None }
62+ try :
63+ with STATE_FILE .open ("r" ) as f :
64+ return json .load (f )
65+ except Exception as e :
66+ logger .warning (f"Could not load state from { STATE_FILE } : { e } " )
67+ return {"last_reset" : None }
68+
69+ def save_state (state : Dict [str , Any ]) -> None :
70+ try :
71+ STATE_FILE .parent .mkdir (parents = True , exist_ok = True )
72+ with STATE_FILE .open ("w" ) as f :
73+ json .dump (state , f )
74+ except Exception as e :
75+ logger .error (f"Could not save state to { STATE_FILE } : { e } " )
76+
77+ # ---------------------------
78+ # Reset Logic
79+ # ---------------------------
80+ def reset_state_files () -> None :
81+ logger .info ("Resetting additional processed state if needed..." )
82+ # e.g. if you have /config/missing_ids.txt or /config/upgrades.json, clear them here
83+ # For now, we just log.
84+
85+ def check_reset_state (state : Dict [str , Any ]) -> Dict [str , Any ]:
86+ """Compare now - last_reset to STATE_RESET_INTERVAL_HOURS. If exceeded, reset."""
87+ if STATE_RESET_INTERVAL_HOURS <= 0 :
88+ logger .info ("State reset disabled; skipping." )
89+ return state
90+
91+ now = datetime .utcnow ()
92+ reset_interval = timedelta (hours = STATE_RESET_INTERVAL_HOURS )
93+ last_reset_str = state .get ("last_reset" )
94+
95+ if last_reset_str :
96+ try :
97+ last_reset_dt = datetime .fromisoformat (last_reset_str )
98+ except ValueError :
99+ logger .warning (f"Failed to parse last_reset: { last_reset_str } . Resetting now." )
100+ last_reset_dt = now
101+ else :
102+ last_reset_dt = now
103+
104+ if now - last_reset_dt > reset_interval :
105+ logger .info (f"State older than { STATE_RESET_INTERVAL_HOURS } hours. Resetting now." )
106+ reset_state_files ()
107+ state ["last_reset" ] = now .isoformat ()
108+ else :
109+ logger .debug ("Not time to reset yet." )
110+
111+ return state
112+
113+ # ---------------------------
114+ # Main Loop
115+ # ---------------------------
116+ def main_loop ():
117+ state = load_state ()
118+
17119 while True :
18- logger .info (f"=== Starting Huntarr-Lidarr cycle ===" )
120+ # 1) Possibly reset if old
121+ state = check_reset_state (state )
122+
123+ logger .info ("=== Starting Huntarr-Lidarr cycle ===" )
124+ logger .info (f"Missing Content Configuration: HUNT_MISSING_MODE={ HUNT_MISSING_MODE } , HUNT_MISSING_ITEMS={ HUNT_MISSING_ITEMS } " )
125+ logger .info (f"Upgrade Configuration: HUNT_UPGRADE_ALBUMS={ HUNT_UPGRADE_ALBUMS } " )
126+ logger .info (f"MONITORED_ONLY={ MONITORED_ONLY } , RANDOM_SELECTION={ RANDOM_SELECTION } " )
127+
128+ # 2) Missing step
129+ if HUNT_MISSING_ITEMS > 0 :
130+ if HUNT_MISSING_MODE == "album" :
131+ process_albums_missing (
132+ max_items = HUNT_MISSING_ITEMS ,
133+ monitored_only = MONITORED_ONLY ,
134+ random_selection = RANDOM_SELECTION
135+ )
136+ elif HUNT_MISSING_MODE == "artist" :
137+ process_artists_missing (
138+ max_items = HUNT_MISSING_ITEMS ,
139+ monitored_only = MONITORED_ONLY ,
140+ random_selection = RANDOM_SELECTION
141+ )
142+ else :
143+ logger .warning (f"Unknown HUNT_MISSING_MODE={ HUNT_MISSING_MODE } , skipping missing step." )
19144
20- # 1) Handle missing content based on HUNT_MISSING_MODE
21- if HUNT_MISSING_MODE == "artist" or HUNT_MISSING_MODE == "both" :
22- process_artists_missing ()
23-
24- if HUNT_MISSING_MODE == "album" or HUNT_MISSING_MODE == "both" :
25- process_albums_missing ()
26-
27- # 2) Handle album upgrade processing
28- process_album_upgrades ( )
145+ # 3) Upgrade step
146+ if HUNT_UPGRADE_ALBUMS > 0 :
147+ # This function scans the entire library for tracks below cutoff
148+ # and will process up to HUNT_UPGRADE_ALBUMS items
149+ process_cutoff_upgrades (
150+ max_items = HUNT_UPGRADE_ALBUMS ,
151+ monitored_only = MONITORED_ONLY ,
152+ random_selection = RANDOM_SELECTION
153+ )
29154
30- logger .info ("Cycle complete. Waiting 60s before next cycle..." )
31- time .sleep (60 )
155+ # 4) Save updated state & final cycle sleep
156+ save_state (state )
157+ logger .info (f"Cycle complete. Waiting { SLEEP_DURATION } s..." )
158+ time .sleep (SLEEP_DURATION )
32159
33160if __name__ == "__main__" :
34- # Log configuration settings
35- log_configuration ( logger )
161+ logger . info ( "=== Huntarr [Lidarr Edition] Starting ===" )
162+ logger . info ( f"API URL: { API_URL } " )
36163
37164 try :
38165 main_loop ()
39166 except KeyboardInterrupt :
40167 logger .info ("Huntarr-Lidarr stopped by user." )
41- sys .exit (0 )
42168 except Exception as e :
43- logger .exception (f"Unexpected error: { e } " )
44- sys . exit ( 1 )
169+ logger .error (f"Unexpected error: { e } " , exc_info = True )
170+ raise
0 commit comments