1
0
Fork 0
mirror of synced 2024-05-19 12:02:54 +12:00
Rare/rare/utils/paths.py
loathingKernel 582b83c12b WIP
2024-02-12 21:52:08 +02:00

274 lines
8.5 KiB
Python

import os
import platform
import shlex
import shutil
import sys
from logging import getLogger
from pathlib import Path
from typing import List
from PyQt5.QtCore import QStandardPaths
if platform.system() == "Windows":
# noinspection PyUnresolvedReferences
from win32com.client import Dispatch # pylint: disable=E0401
logger = getLogger("Paths")
# This depends on the location of this file (obviously)
resources_path = Path(__file__).absolute().parent.parent.joinpath("resources")
# lk: delete old Rare directories
for old_dir in [
Path(QStandardPaths.writableLocation(QStandardPaths.CacheLocation), "rare").joinpath("tmp"),
Path(QStandardPaths.writableLocation(QStandardPaths.AppDataLocation), "rare").joinpath("images"),
Path(QStandardPaths.writableLocation(QStandardPaths.CacheLocation), "rare"),
Path(QStandardPaths.writableLocation(QStandardPaths.AppDataLocation), "rare"),
]:
if old_dir.exists():
# lk: case-sensitive matching on Winblows
if old_dir.stem in old_dir.parent.iterdir():
shutil.rmtree(old_dir, ignore_errors=True)
# lk: TempLocation doesn't depend on OrganizationName or ApplicationName
# lk: so it is fine to use it before initializing the QApplication
def lock_file() -> Path:
return Path(QStandardPaths.writableLocation(QStandardPaths.TempLocation), "Rare.lock")
def config_dir() -> Path:
# FIXME: This returns ~/.config/Rare/Rare/ for some reason while the settings are in ~/.config/Rare/Rare.conf
# Take the parent for now, but this should be investigated
return Path(QStandardPaths.writableLocation(QStandardPaths.AppConfigLocation)).parent
def data_dir() -> Path:
return Path(QStandardPaths.writableLocation(QStandardPaths.AppDataLocation))
def cache_dir() -> Path:
return Path(QStandardPaths.writableLocation(QStandardPaths.CacheLocation))
def image_dir() -> Path:
return data_dir().joinpath("images")
def log_dir() -> Path:
return cache_dir().joinpath("logs")
def tmp_dir() -> Path:
return cache_dir().joinpath("tmp")
def create_dirs() -> None:
for path in (data_dir(), cache_dir(), image_dir(), log_dir(), tmp_dir()):
if not path.exists():
path.mkdir(parents=True)
logger.info(f"Created directory at {path}")
def home_dir() -> Path:
return Path(QStandardPaths.writableLocation(QStandardPaths.HomeLocation))
def desktop_dir() -> Path:
return Path(QStandardPaths.writableLocation(QStandardPaths.DesktopLocation))
def applications_dir() -> Path:
return Path(QStandardPaths.writableLocation(QStandardPaths.ApplicationsLocation))
def proton_compat_dir(app_name: str) -> Path:
return data_dir().joinpath(f"compatdata/{app_name}")
def wine_compat_dir(app_name: str) -> Path:
return proton_compat_dir(app_name).joinpath("pfx")
# fmt: off
__link_suffix = {
"Windows": {
"link": "lnk",
"icon": "ico",
},
"FreeBSD": {
"link": "desktop",
"icon": "png",
},
"Linux": {
"link": "desktop",
"icon": "png",
},
"Darwin": {
"link": "",
"icon": "icns",
},
}
def desktop_links_supported() -> bool:
supported_systems = [k for (k, v) in __link_suffix.items() if v["link"]]
return platform.system() in supported_systems
def desktop_icon_suffix() -> str:
return __link_suffix[platform.system()]["icon"]
__link_type = {
"desktop": desktop_dir(),
# lk: for some undocumented reason, on Windows we used the parent directory
# lk: for start menu items. Mirror it here for backwards compatibility
"start_menu": applications_dir().parent if platform.system() == "Windows" else applications_dir(),
}
def desktop_link_types() -> List:
return list(__link_type.keys())
# fmt: on
def desktop_link_path(link_name: str, link_type: str) -> Path:
"""
Creates the path of a shortcut link
:param link_name:
Name of the shortcut file
:param link_type:
"desktop" or "start_menu"
:return Path:
shortcut path
"""
return __link_type[link_type].joinpath(f"{link_name}.{__link_suffix[platform.system()]['link']}")
def get_rare_executable() -> List[str]:
logger.debug(f"Trying to find executable: {sys.executable}, {sys.argv}")
# lk: detect if nuitka
if "__compiled__" in globals():
executable = [sys.executable]
elif sys.argv[0].endswith("__main__.py"):
executable = [sys.executable, "-m", "rare"]
elif platform.system() in {"Linux", "FreeBSD", "Darwin"}:
if p := os.environ.get("APPIMAGE"):
executable = [p]
else:
if sys.executable == os.path.abspath(sys.argv[0]):
executable = [sys.executable]
else:
executable = [os.path.abspath(sys.argv[0])]
elif platform.system() == "Windows":
executable = [sys.executable]
if sys.executable != os.path.abspath(sys.argv[0]):
executable.append(os.path.abspath(sys.argv[0]))
if executable[0].endswith("python.exe"):
# be sure to start console-less then
executable[0] = executable[0].replace("python.exe", "pythonw.exe")
if executable[0].endswith("pythonw.exe"):
if executable[1].endswith("rare"):
executable[1] = executable[1] + ".exe"
else:
executable = [sys.executable]
executable[0] = os.path.abspath(executable[0])
return executable
def create_desktop_link(app_name: str, app_title: str = "", link_name: str = "", link_type="desktop") -> bool:
"""
Creates a desktop or start menu shortcut link
:param app_name:
app_name or "rare_shortcut" for Rare itself
:param app_title:
the title shown in the shortcut
(overrides to "Rare" for "rare_shortcut")
:param link_name:
the sanitized filename of the shortcut (use the folder_name attribute of RareGame)
(overrides to "Rare" for "rare_shortcut")
:param link_type:
"desktop" or "start_menu"
:return bool:
True if successful else False
"""
# macOS is based on Darwin
if not desktop_links_supported():
logger.error(f"Shortcut creation is not available on {platform.system()}")
return False
if link_type not in ["desktop", "start_menu"]:
logger.error(f"Invalid link type {link_type}")
return False
for_rare = app_name == "rare_shortcut"
if for_rare:
icon_path = resources_path.joinpath("images", f"Rare.{desktop_icon_suffix()}")
app_title = "Rare"
link_name = "Rare"
else:
icon_path = image_dir().joinpath(app_name, f"icon.{desktop_icon_suffix()}")
if not app_title or not link_name:
logger.error("Missing app_title or link_name")
return False
shortcut_path = desktop_link_path(link_name, link_type)
if not shortcut_path.parent.exists():
logger.error(f"Parent directory {shortcut_path.parent} does not exist")
return False
else:
logger.info(f"Creating shortcut for {app_title} at {shortcut_path}")
if platform.system() in {"Linux", "FreeBSD"}:
executable = get_rare_executable()
executable = shlex.join(executable)
if not for_rare:
executable = f"{executable} launch {app_name}"
with shortcut_path.open(mode="w") as desktop_file:
desktop_file.write(
"[Desktop Entry]\n"
f"Name={app_title}\n"
"Type=Application\n"
"Categories=Game;\n"
f"Icon={icon_path}\n"
f"Exec={executable}\n"
"Terminal=false\n"
"StartupWMClass=Rare\n"
)
# shortcut_path.chmod(0o755)
return True
elif platform.system() == "Windows":
# Add shortcut
shell = Dispatch("WScript.Shell")
shortcut = shell.CreateShortCut(str(shortcut_path))
executable = get_rare_executable()
arguments = []
if len(executable) > 1:
arguments.extend(executable[1:])
executable = executable[0]
if not for_rare:
arguments.extend(["launch", app_name])
shortcut.Targetpath = executable
# Maybe there is a better solution, but windows does not accept single quotes (Windows is weird)
shortcut.Arguments = shlex.join(arguments).replace("'", '"')
shortcut.Description = app_title
if for_rare:
shortcut.WorkingDirectory = str(home_dir())
# Icon
shortcut.IconLocation = str(icon_path.absolute())
shortcut.save()
return True