From 741fea494abc5b6d15ddb8be00e56adbe117668e Mon Sep 17 00:00:00 2001 From: 7eventy7 Date: Tue, 12 Nov 2024 13:12:57 -0700 Subject: [PATCH] Added persistent appdata path --- Dockerfile | 5 ++- docker-compose.yml | 5 ++- requirements.txt | 3 +- src/main.py | 108 +++++++++++++++++++++++++++++++++++++++------ 4 files changed, 101 insertions(+), 20 deletions(-) diff --git a/Dockerfile b/Dockerfile index d21b246..fe91aee 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,3 +1,4 @@ +# Dockerfile # Use Python 3.12 slim image FROM python:3.12-slim @@ -13,8 +14,8 @@ RUN apt-get update && apt-get install -y --no-install-recommends \ COPY requirements.txt . RUN pip install --no-cache-dir -r requirements.txt -# Copy source code -COPY src/ . +# Copy application +COPY main.py . # Run the script CMD ["python", "main.py"] \ No newline at end of file diff --git a/docker-compose.yml b/docker-compose.yml index 4d26af8..615c2a6 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -1,3 +1,4 @@ +# docker-compose.yml version: '3.8' services: @@ -5,9 +6,9 @@ services: build: . volumes: - ${MUSIC_PATH:-./music}:/music:ro # Read-only mount of music directory + - ./config:/config # Persistent storage for config environment: - - MUSIC_PATH=/music - - UPDATE_INTERVAL=0 * * * * # Default to every hour + - UPDATE_INTERVAL=00:00 # Default to midnight - DISCORD_WEBHOOK= # Set this to your Discord webhook URL - DISCOGS_TOKEN= # Set this to your Discogs API token restart: unless-stopped diff --git a/requirements.txt b/requirements.txt index 2fd083d..8755624 100644 --- a/requirements.txt +++ b/requirements.txt @@ -2,5 +2,4 @@ python-dotenv==1.0.0 python3-discogs-client>=2.0.0 requests==2.31.0 schedule==1.2.1 -watchdog==3.0.0 -python-crontab==3.0.0 \ No newline at end of file +watchdog==3.0.0 \ No newline at end of file diff --git a/src/main.py b/src/main.py index 5be3d7e..08864f6 100644 --- a/src/main.py +++ b/src/main.py @@ -8,10 +8,21 @@ from watchdog.observers import Observer from watchdog.events import FileSystemEventHandler from dotenv import load_dotenv +import logging + +# Set up logging +logging.basicConfig( + level=logging.INFO, + format='%(asctime)s - %(levelname)s - %(message)s', + datefmt='%Y-%m-%d %H:%M:%S' +) +logger = logging.getLogger(__name__) + +CONFIG_PATH = "/config/artists.json" class MusicFolderHandler(FileSystemEventHandler): - def __init__(self, artists_file): - self.artists_file = artists_file + def __init__(self): + self.artists_file = CONFIG_PATH self.last_update = time.time() # Prevent multiple updates within 5 seconds self.update_cooldown = 5 @@ -22,16 +33,26 @@ def on_any_event(self, event): if event.is_directory: self.last_update = time.time() + logger.info(f"Directory change detected: {event.src_path}") update_artist_list() def load_config(): """Load environment variables""" update_interval = os.getenv('UPDATE_INTERVAL') discord_webhook = os.getenv('DISCORD_WEBHOOK') + discogs_token = os.getenv('DISCOGS_TOKEN') - if not all([update_interval, discord_webhook]): + logger.info("Loading configuration...") + if not all([update_interval, discord_webhook, discogs_token]): + missing_vars = [var for var, val in { + 'UPDATE_INTERVAL': update_interval, + 'DISCORD_WEBHOOK': discord_webhook, + 'DISCOGS_TOKEN': discogs_token + }.items() if not val] + logger.error(f"Missing required environment variables: {', '.join(missing_vars)}") raise ValueError("Missing required environment variables") + logger.info(f"Configuration loaded successfully. Update interval set to: {update_interval}") # Use the mounted path directly music_path = "/music" @@ -39,17 +60,29 @@ def load_config(): def update_artist_list(): """Update the JSON list of artists from the music directory""" + logger.info("Updating artist list...") music_path = "/music" - artists = [d for d in os.listdir(music_path) - if os.path.isdir(os.path.join(music_path, d))] - - with open('artists.json', 'w') as f: - json.dump({'artists': artists, 'last_updated': datetime.now().isoformat()}, f, indent=2) + try: + artists = [d for d in os.listdir(music_path) + if os.path.isdir(os.path.join(music_path, d))] + + logger.info(f"Found {len(artists)} artists in music directory") + + with open(CONFIG_PATH, 'w') as f: + json.dump({'artists': artists, 'last_updated': datetime.now().isoformat()}, f, indent=2) + + logger.info("Artist list updated successfully") + logger.debug(f"Artists found: {', '.join(artists)}") + except Exception as e: + logger.error(f"Error updating artist list: {str(e)}") + raise def send_discord_notification(release_info): """Send a Discord webhook notification about new releases""" webhook_url = os.getenv('DISCORD_WEBHOOK') + logger.info(f"Sending Discord notification for {release_info['artist']} - {release_info['title']}") + embed = { "title": f"New {release_info['type']} Release!", "description": f"Artist: {release_info['artist']}\n" @@ -60,38 +93,59 @@ def send_discord_notification(release_info): } payload = {"embeds": [embed]} - requests.post(webhook_url, json=payload) + try: + response = requests.post(webhook_url, json=payload) + response.raise_for_status() + logger.info("Discord notification sent successfully") + except requests.exceptions.RequestException as e: + logger.error(f"Failed to send Discord notification: {str(e)}") def check_new_releases(): """Check for new releases from tracked artists""" + logger.info("Starting new release check...") try: - with open('artists.json', 'r') as f: + with open(CONFIG_PATH, 'r') as f: data = json.load(f) artists = data['artists'] last_check = datetime.fromisoformat(data.get('last_updated', '2024-01-01T00:00:00')) + logger.info(f"Loaded {len(artists)} artists from config. Last check: {last_check}") except FileNotFoundError: - print("No artists.json found. Running initial scan...") + logger.warning("No artists.json found. Running initial scan...") update_artist_list() return + except json.JSONDecodeError: + logger.error("Error reading artists.json - file may be corrupted") + return + except Exception as e: + logger.error(f"Unexpected error reading config: {str(e)}") + return # Initialize Discogs client + logger.info("Initializing Discogs client...") discogs = discogs_client.Client('TracklyBot/1.0', user_token=os.getenv('DISCOGS_TOKEN')) for artist in artists: + logger.info(f"Checking releases for artist: {artist}") try: # Search for the artist on Discogs results = discogs.search(artist, type='artist') if not results: + logger.warning(f"No Discogs results found for artist: {artist}") continue artist_id = results[0].id + logger.info(f"Found Discogs ID for {artist}: {artist_id}") artist_releases = discogs.artist(artist_id).releases # Check recent releases + releases_checked = 0 for release in artist_releases: try: release_date = datetime.strptime(release.release_date, '%Y-%m-%d') + releases_checked += 1 + if release_date > last_check: + logger.info(f"Found new release for {artist}: {release.title}") # Determine release type release_type = 'album' if release.formats[0].get('quantity', '1') == '1': @@ -110,37 +164,63 @@ def check_new_releases(): send_discord_notification(release_info) except (AttributeError, ValueError) as e: - print(f"Error processing release for {artist}: {e}") + logger.error(f"Error processing release for {artist}: {e}") continue + + logger.info(f"Checked {releases_checked} releases for {artist}") except Exception as e: - print(f"Error checking releases for {artist}: {e}") + logger.error(f"Error checking releases for {artist}: {e}") continue + + logger.info("Completed release check for all artists") def main(): """Main function to run the artist tracker""" + logger.info("Starting Trackly...") + # Load environment variables music_path, update_interval, _ = load_config() + # Make sure config directory exists + try: + os.makedirs(os.path.dirname(CONFIG_PATH), exist_ok=True) + logger.info(f"Config directory ensured at {os.path.dirname(CONFIG_PATH)}") + except Exception as e: + logger.error(f"Failed to create config directory: {str(e)}") + raise + # Initial scan of music directory + logger.info("Performing initial music directory scan...") update_artist_list() # Set up file system monitoring - event_handler = MusicFolderHandler('artists.json') + logger.info("Setting up file system monitoring...") + event_handler = MusicFolderHandler() observer = Observer() observer.schedule(event_handler, music_path, recursive=False) observer.start() + logger.info(f"File system monitoring active for: {music_path}") # Schedule regular checks based on update interval + logger.info(f"Scheduling daily checks at {update_interval}") schedule.every().day.at(update_interval).do(check_new_releases) + # Run initial check + logger.info("Running initial release check...") + check_new_releases() + + logger.info("Trackly startup complete") + try: while True: schedule.run_pending() time.sleep(60) # Check every minute for scheduled tasks except KeyboardInterrupt: + logger.info("Shutting down Trackly...") observer.stop() observer.join() + logger.info("Shutdown complete") if __name__ == "__main__": main() \ No newline at end of file