| 
					
				 | 
			
			
				@@ -0,0 +1,121 @@ 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+import json 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+import logging 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+import os 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+from datetime import datetime, timedelta 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+from typing import List 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+ 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+import pytz 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+from dateutil.parser import ParserError, parse 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+ 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+from vrobbler.apps.scrobbles.utils import convert_to_seconds 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+from vrobbler.apps.videogames.utils import get_or_create_videogame 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+ 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+logger = logging.getLogger(__name__) 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+ 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+from videogames.models import VideoGame 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+ 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+ 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+def load_game_data(directory_path: str, user_tz=None) -> dict: 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+    """Given a path to a directory, cycle through each found lrtl file and 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+    generate game data. 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+ 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+    Example json file as follows: 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+ 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+      Name: "Sonic The Hedgehog 2 (World).lrtl" 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+ 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+      Contents: 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+      { 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+        "version": "1.0", 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+        "runtime": "0:20:19", 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+        "last_played": "2023-05-23 15:30:15" 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+      } 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+ 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+    """ 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+    directory = os.fsencode(directory_path) 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+    games = {} 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+    if not user_tz: 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+        user_tz = pytz.utc 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+ 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+    for file in os.listdir(directory): 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+        filename = os.fsdecode(file) 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+        if not filename.endswith("lrtl"): 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+            logger.info( 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                f"Found non-gamelog file extension, skipping {filename}" 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+            ) 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+            continue 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+ 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+        game_name = filename.split(" (")[0] 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+        with open("".join([directory_path, filename])) as f: 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+            games[game_name] = json.load(f) 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+            # Convert runtime to seconds 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+            games[game_name]["runtime"] = convert_to_seconds( 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                games[game_name]["runtime"] 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+            ) 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+            # Convert last_played to datetime in UTC 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+            games[game_name]["last_played"] = ( 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                parse(games[game_name]["last_played"]) 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                .replace(tzinfo=user_tz) 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                .astimezone(pytz.utc) 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+            ) 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+ 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+    return games 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+ 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+ 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+def import_retroarch_lrtl_files(playlog_path: str, user_id: int) -> List[dict]: 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+    """Given a path to Retroarch lrtl game log file data, 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+    gather 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+ 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+    For each found log file, we'll do: 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+        1. Look up game, create if it doesn't exist 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+        2. Check for existing scrobbles 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+        3. Create new scrobble if last_played != last_scrobble.timestamp 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+        4. Calculate scrobble time from runtime - last_scrobble.long_play_time 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+    """ 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+ 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+    game_logs = load_game_data(playlog_path) 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+    found_game = None 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+    new_scrobbles = [] 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+ 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+    for game_name, game_data in game_logs.items(): 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+        # Use the retroarch name, because we can't change those but may want to 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+        # tweak the found game 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+        found_game = VideoGame.objects.filter(retroarch_name=game_name).first() 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+ 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+        if not found_game: 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+            found_game = get_or_create_videogame(game_name) 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+            if found_game: 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                found_game.retroarch_name = game_name 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                found_game.save(update_fields=["retroarch_name"]) 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+ 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+        if found_game: 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+            found_scrobble = found_game.scrobble_set.filter( 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                timestamp=game_data["last_played"] 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+            ) 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+            if found_scrobble: 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                logger.info( 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                    f"Found scrobble for {game_name} with timestamp {game_data['last_played']}, not scrobbling" 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                ) 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                continue 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+            last_scrobble = found_game.scrobble_set.last() 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+            delta_runtime = 0 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+            if last_scrobble: 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                delta_runtime = last_scrobble.long_play_seconds 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+            playback_position_seconds = game_data["runtime"] - delta_runtime 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+            stop_timestamp = game_data["last_played"] + timedelta( 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                seconds=playback_position_seconds 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+            ) 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+            new_scrobbles.append( 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                { 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                    "video_game_id": found_game.id, 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                    "timestamp": game_data["last_played"], 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                    "stop_timestamp": stop_timestamp, 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                    "playback_position_seconds": playback_position_seconds, 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                    "played_to_completion": True, 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                    "in_progress": False, 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                    "long_play_seconds": game_data["runtime"], 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                    "user_id": user_id, 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                    "source_id": "Retroarch", 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                    "source": "Imported from Retroarch play log file", 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                } 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+            ) 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+    return new_scrobbles 
			 |