mirror of
https://github.com/Omni-guides/Jackify.git
synced 2026-06-08 01:47:45 +02:00
219 lines
10 KiB
Python
219 lines
10 KiB
Python
#!/usr/bin/env python3
|
|
# -*- coding: utf-8 -*-
|
|
"""
|
|
Steam path and library mixin for PathHandler.
|
|
Extracted from path_handler for file-size and domain separation.
|
|
"""
|
|
|
|
import os
|
|
import re
|
|
import logging
|
|
from pathlib import Path
|
|
from typing import Optional, List
|
|
from datetime import datetime
|
|
import vdf
|
|
from jackify.shared.steam_utils import (
|
|
get_ordered_steam_roots,
|
|
STEAM_PREFERENCE_AUTO,
|
|
)
|
|
|
|
logger = logging.getLogger(__name__)
|
|
|
|
|
|
class PathHandlerSteamMixin:
|
|
"""Mixin providing Steam config, library, and shortcuts path discovery."""
|
|
|
|
@staticmethod
|
|
def find_steam_config_vdf() -> Optional[Path]:
|
|
"""Finds the active Steam config.vdf file."""
|
|
logger.debug("Searching for Steam config.vdf...")
|
|
possible_steam_paths = get_ordered_steam_roots(STEAM_PREFERENCE_AUTO)
|
|
for steam_path in possible_steam_paths:
|
|
potential_path = steam_path / "config/config.vdf"
|
|
if potential_path.is_file():
|
|
logger.info(f"Found config.vdf at: {potential_path}")
|
|
return potential_path
|
|
logger.warning("Could not locate Steam's config.vdf file in standard locations.")
|
|
return None
|
|
|
|
@staticmethod
|
|
def find_steam_library() -> Optional[Path]:
|
|
"""Find the primary Steam library common directory containing games."""
|
|
logger.debug("Attempting to find Steam library...")
|
|
ordered_roots = get_ordered_steam_roots(STEAM_PREFERENCE_AUTO)
|
|
libraryfolders_vdf_paths = [
|
|
str(root / "config" / "libraryfolders.vdf") for root in ordered_roots
|
|
]
|
|
for path in libraryfolders_vdf_paths:
|
|
if os.path.exists(path):
|
|
backup_dir = os.path.join(os.path.dirname(path), "backups")
|
|
if not os.path.exists(backup_dir):
|
|
try:
|
|
os.makedirs(backup_dir)
|
|
except OSError as e:
|
|
logger.warning(f"Could not create backup directory {backup_dir}: {e}")
|
|
timestamp = datetime.now().strftime("%Y%m%d")
|
|
backup_filename = f"libraryfolders_{timestamp}.vdf.bak"
|
|
backup_path = os.path.join(backup_dir, backup_filename)
|
|
if not os.path.exists(backup_path):
|
|
try:
|
|
import shutil
|
|
shutil.copy2(path, backup_path)
|
|
logger.debug(f"Created backup of libraryfolders.vdf at {backup_path}")
|
|
except Exception as e:
|
|
logger.error(f"Failed to create backup of libraryfolders.vdf: {e}")
|
|
libraryfolders_vdf_path_obj = None
|
|
found_path_str = None
|
|
for path_str in libraryfolders_vdf_paths:
|
|
if os.path.exists(path_str):
|
|
found_path_str = path_str
|
|
libraryfolders_vdf_path_obj = Path(path_str)
|
|
logger.debug(f"Found libraryfolders.vdf at: {path_str}")
|
|
break
|
|
if not libraryfolders_vdf_path_obj or not libraryfolders_vdf_path_obj.is_file():
|
|
logger.warning("libraryfolders.vdf not found or is not a file. Cannot automatically detect Steam Library.")
|
|
return None
|
|
library_paths = []
|
|
try:
|
|
with open(found_path_str, 'r') as f:
|
|
content = f.read()
|
|
path_matches = re.finditer(r'"path"\s*"([^"]+)"', content)
|
|
for match in path_matches:
|
|
library_path_str = match.group(1).replace('\\\\', '\\')
|
|
common_path = os.path.join(library_path_str, "steamapps", "common")
|
|
if os.path.isdir(common_path):
|
|
library_paths.append(Path(common_path))
|
|
logger.debug(f"Found potential common path: {common_path}")
|
|
else:
|
|
logger.debug(f"Skipping non-existent common path derived from VDF: {common_path}")
|
|
logger.debug(f"Found {len(library_paths)} valid library common paths from VDF.")
|
|
if library_paths:
|
|
logger.info(f"Using Steam library common path: {library_paths[0]}")
|
|
return library_paths[0]
|
|
logger.debug("No valid common paths found in VDF, checking default location...")
|
|
for root in ordered_roots:
|
|
default_common_path = root / "steamapps" / "common"
|
|
if default_common_path.is_dir():
|
|
logger.info(f"Using default Steam library common path: {default_common_path}")
|
|
return default_common_path
|
|
logger.error("No valid Steam library common path found in VDF or default locations.")
|
|
return None
|
|
except Exception as e:
|
|
logger.error(f"Error parsing libraryfolders.vdf or finding Steam library: {e}", exc_info=True)
|
|
return None
|
|
|
|
@staticmethod
|
|
def get_steam_library_path(steam_path: str) -> Optional[str]:
|
|
"""Get the Steam library path from libraryfolders.vdf."""
|
|
try:
|
|
libraryfolders_path = os.path.join(steam_path, 'steamapps', 'libraryfolders.vdf')
|
|
if not os.path.exists(libraryfolders_path):
|
|
return None
|
|
with open(libraryfolders_path, 'r', encoding='utf-8') as f:
|
|
content = f.read()
|
|
libraries = {}
|
|
current_library = None
|
|
for line in content.split('\n'):
|
|
line = line.strip()
|
|
if line.startswith('"path"'):
|
|
current_library = line.split('"')[3].replace('\\\\', '\\')
|
|
elif line.startswith('"apps"') and current_library:
|
|
libraries[current_library] = True
|
|
for library_path in libraries:
|
|
if os.path.exists(library_path):
|
|
return library_path
|
|
return None
|
|
except Exception as e:
|
|
logger.error(f"Error getting Steam library path: {str(e)}")
|
|
return None
|
|
|
|
@staticmethod
|
|
def get_mountpoint(path) -> Optional[str]:
|
|
"""Return the mount point for the given path (Linux). Used for STEAM_COMPAT_MOUNTS."""
|
|
if not path:
|
|
return None
|
|
try:
|
|
p = Path(path).resolve()
|
|
if not p.exists():
|
|
p = p.parent
|
|
while p != p.parent:
|
|
if os.path.ismount(p):
|
|
return str(p)
|
|
p = p.parent
|
|
return str(p)
|
|
except (OSError, RuntimeError) as e:
|
|
logger.debug(f"Could not get mountpoint for {path}: {e}")
|
|
return None
|
|
|
|
def get_steam_compat_mount_paths(self, install_dir=None, download_dir=None) -> List[str]:
|
|
"""
|
|
Build list of mount paths for STEAM_COMPAT_MOUNTS: other Steam library roots plus
|
|
mountpoints of install_dir and download_dir so MO2 can access game and downloads.
|
|
"""
|
|
seen = set()
|
|
result = []
|
|
main_steam_lib_path_obj = self.find_steam_library()
|
|
if main_steam_lib_path_obj and main_steam_lib_path_obj.name == "common":
|
|
main_steam_lib_path = main_steam_lib_path_obj.parent.parent
|
|
else:
|
|
main_steam_lib_path = main_steam_lib_path_obj
|
|
main_resolved = str(main_steam_lib_path.resolve()) if main_steam_lib_path else None
|
|
for lib_path in self.get_all_steam_library_paths():
|
|
try:
|
|
r = str(lib_path.resolve())
|
|
except (OSError, RuntimeError):
|
|
r = str(lib_path)
|
|
if r not in seen and r != main_resolved:
|
|
seen.add(r)
|
|
result.append(r)
|
|
for extra in (install_dir, download_dir):
|
|
mp = self.get_mountpoint(extra) if extra else None
|
|
if mp and mp not in seen:
|
|
seen.add(mp)
|
|
result.append(mp)
|
|
return result
|
|
|
|
@staticmethod
|
|
def get_all_steam_library_paths() -> List[Path]:
|
|
"""Finds all Steam library paths listed in all known libraryfolders.vdf files (including Flatpak)."""
|
|
logger.info("[DEBUG] Searching for all Steam libraryfolders.vdf files...")
|
|
ordered_roots = get_ordered_steam_roots(STEAM_PREFERENCE_AUTO)
|
|
vdf_paths = [root / "config" / "libraryfolders.vdf" for root in ordered_roots]
|
|
library_paths = set()
|
|
for vdf_path in vdf_paths:
|
|
if vdf_path.is_file():
|
|
logger.info(f"[DEBUG] Parsing libraryfolders.vdf: {vdf_path}")
|
|
try:
|
|
with open(vdf_path, 'r', encoding='utf-8') as f:
|
|
data = vdf.load(f)
|
|
libraryfolders = data.get('libraryfolders', {})
|
|
for key, lib_data in libraryfolders.items():
|
|
if isinstance(lib_data, dict) and 'path' in lib_data:
|
|
lib_path = Path(lib_data['path'])
|
|
try:
|
|
resolved_path = lib_path.resolve()
|
|
library_paths.add(resolved_path)
|
|
logger.debug(f"[DEBUG] Found library path: {resolved_path}")
|
|
except (OSError, RuntimeError) as resolve_err:
|
|
logger.warning(f"[DEBUG] Could not resolve {lib_path}, using as-is: {resolve_err}")
|
|
library_paths.add(lib_path)
|
|
except Exception as e:
|
|
logger.error(f"[DEBUG] Failed to parse {vdf_path}: {e}")
|
|
logger.info(f"[DEBUG] All detected Steam libraries: {library_paths}")
|
|
return list(library_paths)
|
|
|
|
def _find_shortcuts_vdf(self) -> Optional[str]:
|
|
"""Helper to find the active shortcuts.vdf file for the current Steam user."""
|
|
try:
|
|
from jackify.backend.services.native_steam_service import NativeSteamService
|
|
steam_service = NativeSteamService()
|
|
shortcuts_path = steam_service.get_shortcuts_vdf_path()
|
|
if shortcuts_path:
|
|
logger.info(f"Found shortcuts.vdf using multi-user detection: {shortcuts_path}")
|
|
return str(shortcuts_path)
|
|
logger.error("Could not determine shortcuts.vdf path using multi-user detection")
|
|
return None
|
|
except Exception as e:
|
|
logger.error(f"Error using multi-user detection for shortcuts.vdf: {e}")
|
|
return None
|