diff --git a/sgbackup/epic.py b/sgbackup/epic.py
index 49d419e..0d24ea3 100644
--- a/sgbackup/epic.py
+++ b/sgbackup/epic.py
@@ -24,24 +24,24 @@ from .settings import settings
import json
import logging
-from i18n import gettext as _
+from .i18n import gettext as _
+from .game import GameManager
logger = logging.getLogger(__name__)
-PLATFORM_WINDOWS = sys.platform.lower().startswith('win')
class EpicGameInfo(GObject):
def __init__(self,
name:str,
installdir:str,
- appname:str,
- main_appname:str):
+ catalog_item_id:str,
+ main_catalog_item_id:str):
GObject.__init__(self)
self.__name = name
self.__installdir = installdir
- self.__appname = appname
- self.__main_appname = main_appname
+ self.__catalog_item_id = catalog_item_id
+ self.__main_catalog_item_id = main_catalog_item_id
@Property(type=str)
@@ -53,27 +53,27 @@ class EpicGameInfo(GObject):
return self.__installdir
@Property(type=str)
- def appname(self)->str:
- return self.__appname
+ def catalog_item_id(self)->str:
+ return self.__catalog_item_id
@Property(type=str)
- def main_appname(self)->str:
- return self.__main_appname
+ def main_catalog_item_id(self)->str:
+ return self.__main_catalog_item_id
@Property(type=bool,default=False)
def is_main(self)->bool:
- return (self.appname == self.main_appname)
+ return (self.catalog_item_id == self.main_catalog_item_id)
class EpicIgnoredApp(GObject):
- def __init__(self,appname:str,name:str,reason:str):
+ def __init__(self,catalog_item_id:str,name:str,reason:str):
GObject.__init__(self)
- self.__appname = appname
+ self.__catalog_item_id = catalog_item_id
self.__name = name
self.__reason = reason
@Property(type=str)
- def appname(self)->str:
- return self.__appname
+ def catalog_item_id(self)->str:
+ return self.__catalog_item_id
@Property(type=str)
def name(self)->str:
@@ -85,7 +85,7 @@ class EpicIgnoredApp(GObject):
def serialize(self):
return {
- 'appname':self.appname,
+ 'catalog_item_id':self.catalog_item_id,
'name':self.name,
'reason':self.reason,
}
@@ -107,11 +107,11 @@ class Epic(GObject):
def __parse_ignore_file(self)->dict[str:EpicIgnoredApp]:
ret = {}
if os.path.isfile(self.ignore_file):
- with open(self.ignore_file,'r',encoding="urf-8") as ifile:
+ with open(self.ignore_file,'r',encoding="utf-8") as ifile:
data = json.loads(ifile.read())
for i in data:
- ret[i['appname']] = EpicIgnoredApp(i['appname'],i['name'],i['reason'])
+ ret[i['catalog_item_id']] = EpicIgnoredApp(i['catalog_item_id'],i['name'],i['reason'])
return ret
@@ -126,24 +126,24 @@ class Epic(GObject):
if not isinstance(app,EpicIgnoredApp):
raise TypeError('app is not an EpicIgnoredApp instance!')
- self.__ignored_apps[app.appname] = app
+ self.__ignored_apps[app.catalog_item_id] = app
self.__write_ignore_file()
def remove_ignored_apps(self,app:str|EpicIgnoredApp):
if isinstance(app,str):
- appname = app
+ item_id = app
elif isinstance(app,EpicIgnoredApp):
- appname = app.appname
+ item_id = app.catalog_item_id
else:
raise TypeError("app is not a string and not an EpicIgnoredApp instance!")
- if appname in self.__ignored_apps:
- del self.__ignored_apps[appname]
+ if item_id in self.__ignored_apps:
+ del self.__ignored_apps[item_id]
self.__write_ignore_file()
@Property
def ignored_apps(self)->dict[str:EpicIgnoredApp]:
- return self.__ignored_apps
+ return dict(self.__ignored_apps)
@Property(type=str)
def datadir(self):
@@ -169,8 +169,8 @@ class Epic(GObject):
return EpicGameInfo(
name=data['DisplayName'],
installdir=data['InstallLocation'],
- appname=data['AppName'],
- main_appname=data['MainGameAppName']
+ catalog_item_id=data['CatalogItemId'],
+ main_catalog_item_id=data['MainGameCatalogItemId']
)
return None
@@ -181,13 +181,15 @@ class Epic(GObject):
manifest_file = os.path.join(manifest_dir,item)
info = self.parse_manifest(manifest_file)
if info is not None:
- ret += info
+ ret.append(info)
return ret
- def get_apps(self)->list[EpicGameInfo]:
- return [i for i in self.parse_all_manifests() if i.appname == i.main_appname]
+ def find_apps(self)->list[EpicGameInfo]:
+ return [i for i in self.parse_all_manifests() if i.is_main]
- def get_new_apps(self)->list[EpicGameInfo]:
- return []
+ def find_new_apps(self)->list[EpicGameInfo]:
+ gm = GameManager.get_global()
+ return [i for i in self.find_apps()
+ if not gm.has_epic_game(i.catalog_item_id) and not i.catalog_item_id in self.__ignored_apps]
\ No newline at end of file
diff --git a/sgbackup/game.py b/sgbackup/game.py
index 87018d4..1e4bfb1 100644
--- a/sgbackup/game.py
+++ b/sgbackup/game.py
@@ -1207,19 +1207,21 @@ class EpicWindowsData(EpicPlatformData):
class EpicGameData(GObject):
- def __init__(self,appname:str,windows:EpicWindowsData|None):
+ def __init__(self,
+ catalog_item_id:str|None=None,
+ windows:EpicWindowsData|None=None):
GObject.__init__(self)
- self.__appname = appname
+ self.__catalog_item_id = catalog_item_id
self.windows = windows
@Property(type=str)
- def appname(self)->str:
- return self.__appname
+ def catalog_item_id(self)->str:
+ return self.__catalog_item_id if self.__catalog_item_id else ""
- @appname.setter
- def appname(self,appname:str):
- self.__appname = appname
+ @catalog_item_id.setter
+ def catalog_item_id(self,catalog_item_id:str):
+ self.__catalog_item_id = catalog_item_id
@Property
def windows(self)->EpicWindowsData|None:
@@ -1236,7 +1238,7 @@ class EpicGameData(GObject):
def serialize(self):
ret = {
- "appname": self.appname
+ "catalog_item_id": self.catalog_item_id
}
if self.windows and self.windows.is_valid:
ret["windows"] = self.windows.serialize()
@@ -1299,7 +1301,7 @@ class Game(GObject):
librarydir=data['librarydir'] if ('librarydir' in data and data['librarydir']) else None
)
- if ('steam' not in conf or not 'appid' in conf['steam']):
+ if ('steam' not in conf):
return None
steam=conf['steam']
@@ -1322,7 +1324,7 @@ class Game(GObject):
if windows is None and linux is None and macos is None:
return None
- return SteamGameData(steam['appid'],
+ return SteamGameData(steam['appid'] if 'appid' in steam else -1,
windows=windows,
linux=linux,
macos=macos)
@@ -1344,7 +1346,7 @@ class Game(GObject):
librarydir=data['librarydir'] if ('librarydir' in data and data['librarydir']) else None
)
- if not "epic" in conf or not "appname" in conf["epic"]:
+ if not "epic" in conf:
return None
if ("windows" in conf['epic']):
@@ -1352,7 +1354,7 @@ class Game(GObject):
else:
windows = None
- return EpicGameData(conf['epic']['appname'],
+ return EpicGameData(conf['epic']['catalog_item_id'] if 'catalog_item_id' in conf['epic'] else "",
windows=windows)
# new_epic_data()
@@ -1811,8 +1813,17 @@ class GameManager(GObject):
@Property
def steam_games(self)->dict[int:Game]:
- return self.__steam_games
+ return dict(self.__steam_games)
+ def has_steam_game(self,appid:int)->bool:
+ return (appid in self.__steam_games)
+ @Property
+ def epic_games(self)->dict[str:Game]:
+ return dict(self.__epic_games)
+
+ def has_epic_game(self,catalog_item_id:str)->bool:
+ return (catalog_item_id in self.__epic_games)
+
def load(self):
if self.__games:
self.__games = {}
@@ -1844,7 +1855,7 @@ class GameManager(GObject):
if game.steam:
self.__steam_games[game.steam.appid] = game
if game.epic:
- self.__epic_games[game.epic.appname] = game
+ self.__epic_games[game.epic.catalog_item_id] = game
def remove_game(self,game:Game|str):
if isinstance(game,str):
diff --git a/sgbackup/gui/_app.py b/sgbackup/gui/_app.py
index 72850ed..2636cac 100644
--- a/sgbackup/gui/_app.py
+++ b/sgbackup/gui/_app.py
@@ -46,8 +46,9 @@ from ._steam import (
SteamNoIgnoredAppsDialog,
SteamIgnoreAppsDialog,
)
-
from ..steam import Steam
+from ..epic import Epic
+from ._epic import EpicNewAppsDialog
from ._backupdialog import BackupSingleDialog,BackupManyDialog
from ..archiver import ArchiverManager
from ._dialogs import (
@@ -392,7 +393,7 @@ class GameView(Gtk.Box):
def do_game_live_changed(self,game:Game):
pass
- def _on_new_steamapps_dialog_response(self,dialog,response):
+ def _on_new_apps_dialog_response(self,dialog,response):
self.refresh()
def _on_add_game_button_clicked(self,button):
@@ -404,15 +405,28 @@ class GameView(Gtk.Box):
steam = Steam()
if steam.find_new_steamapps():
dialog = NewSteamAppsDialog(parent=self.get_root())
- dialog.connect_after('response',self._on_new_steamapps_dialog_response)
+ dialog.connect_after('response',self._on_new_apps_dialog_response)
dialog.present()
else:
dialog = SteamNoNewAppsDialog(parent=self.get_root())
dialog.present()
def _on_new_epic_games_button_clicked(self,button):
- # TODO
- pass
+ epic = Epic()
+ if not epic.find_new_apps():
+ dialog = Gtk.MessageDialog(
+ transient_for=self.get_root(),
+ message="No new Epic-Games applications found!",
+ buttons=Gtk.ButtonsType.OK,
+ modal=False
+ )
+ dialog.connect('response',lambda d,r: d.destroy())
+ dialog.present()
+ return
+
+ dialog = EpicNewAppsDialog(self.get_root())
+ dialog.connect_after('response',self._on_new_apps_dialog_response)
+ dialog.present()
def _on_backup_active_live_button_clicked(self,button):
backup_games = []
diff --git a/sgbackup/gui/_epic.py b/sgbackup/gui/_epic.py
new file mode 100644
index 0000000..f40fb89
--- /dev/null
+++ b/sgbackup/gui/_epic.py
@@ -0,0 +1,229 @@
+###############################################################################
+# sgbackup - The SaveGame Backup tool #
+# Copyright (C) 2024,2025 Christian Moser #
+# #
+# This program is free software: you can redistribute it and/or modify #
+# it under the terms of the GNU General Public License as published by #
+# the Free Software Foundation, either version 3 of the License, or #
+# (at your option) any later version. #
+# #
+# This program is distributed in the hope that it will be useful, #
+# but WITHOUT ANY WARRANTY; without even the implied warranty of #
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the #
+# GNU General Public License for more details. #
+# #
+# You should have received a copy of the GNU General Public License #
+# along with this program. If not, see . #
+###############################################################################
+
+from gi.repository import Gtk,GLib,Gio
+from gi.repository.GObject import GObject,Property,Signal,SignalFlags
+
+from ..i18n import gettext as _,gettext_noop as N_
+from ..game import GameManager,Game,EpicGameData,EpicWindowsData
+from ..epic import Epic,EpicGameInfo,EpicIgnoredApp
+from ..utility import PLATFORM_WINDOWS
+
+from ._gamedialog import GameDialog
+
+class EpicNewAppsDialogSorter(Gtk.Sorter):
+ def do_compare(self,item1:EpicGameInfo,item2:EpicGameInfo):
+ name1 = item1.name.lower()
+ name2 = item2.name.lower()
+
+ if name1 > name2:
+ return Gtk.Ordering.LARGER
+ elif name1 < name2:
+ return Gtk.Ordering.SMALLER
+ return Gtk.Ordering.EQUAL
+
+class EpicNewAppsDialog(Gtk.Dialog):
+ def __init__(self,parent:Gtk.Window|None=None):
+ Gtk.Dialog.__init__(self,
+ transient_for=parent,
+ title=_("SGBackup: Manage new Epic-Games apps"),
+ modal=False)
+
+ self.set_default_size(800,600)
+ scrolled = Gtk.ScrolledWindow()
+
+ epic = Epic()
+ self.__liststore = Gio.ListStore.new(EpicGameInfo)
+ for info in epic.find_new_apps():
+ self.__liststore.append(info)
+
+ sort_model = Gtk.SortListModel(model=self.__liststore,
+ sorter=EpicNewAppsDialogSorter())
+ selection = Gtk.SingleSelection(model=sort_model,
+ autoselect=False,
+ can_unselect=True)
+
+ factory = Gtk.SignalListItemFactory()
+ factory.connect('setup',self._on_listview_setup)
+ factory.connect('bind',self._on_listview_bind)
+
+ self.__listview = Gtk.ListView(model=selection,factory=factory,hexpand=True,vexpand=True)
+ scrolled.set_child(self.__listview)
+ self.get_content_area().append(scrolled)
+ self.__close_button = self.add_button(_("Close"),Gtk.ResponseType.OK)
+
+
+ def _on_listview_setup(self,factory,item):
+ child = Gtk.Grid(column_spacing=4,row_spacing=2)
+
+ child.name_label = Gtk.Label(xalign=0.0,hexpand=True)
+ child.attach(child.name_label,1,0,1,1)
+
+ label = Gtk.Label(label=_("CatalogItemId:"),
+ use_markup=False,
+ xalign=0.0)
+ child.catalogitemid_label = Gtk.Label(xalign=0.0,
+ use_markup=False,
+ hexpand=True)
+ child.attach(label,0,1,1,1)
+ child.attach(child.catalogitemid_label,1,1,1,1)
+
+ label = Gtk.Label(label=_("Installation Directory:"),
+ use_markup=False,
+ xalign=0.0)
+ child.installdir_label = Gtk.Label(xalign=0.0,
+ use_markup=False,
+ hexpand=True)
+ child.attach(label,0,2,1,1)
+ child.attach(child.installdir_label,1,2,1,1)
+
+ actiongrid = Gtk.Grid(row_spacing=2,column_spacing=2)
+
+ icon = Gtk.Image.new_from_icon_name('list-add-symbolic')
+ icon.set_pixel_size(16)
+ child.new_button = Gtk.Button()
+ child.new_button.set_child(icon)
+ child.new_button.set_tooltip_text(_("Add Epic-Games-app as a new game."))
+ actiongrid.attach(child.new_button,0,0,1,1)
+
+ icon = Gtk.Image.new_from_icon_name('edit-delete-symbolic')
+ icon.set_pixel_size(16)
+ child.ignore_button = Gtk.Button()
+ child.ignore_button.set_child(icon)
+ child.ignore_button.set_tooltip_text(_("Add Epic-Games-app to the ignore list."))
+ actiongrid.attach(child.ignore_button,1,0,1,1)
+
+ icon = Gtk.Image.new_from_icon_name('edit-find-symbolic')
+ icon.set_pixel_size(16)
+ child.lookup_button = Gtk.Button()
+ child.lookup_button.set_child(icon)
+ child.lookup_button.set_tooltip_text(_("Lookup Epic-Games-app for already registered game."))
+ actiongrid.attach(child.lookup_button,0,1,1,1)
+
+ icon = Gtk.Image.new_from_icon_name('folder-download-symbolic')
+ icon.set_pixel_size(16)
+ child.online_button = Gtk.Button()
+ child.online_button.set_child(icon)
+ child.online_button.set_tooltip_text(_("Lookup Epic-Games-app online."))
+ actiongrid.attach(child.online_button,1,1,1,1)
+
+ child.attach(actiongrid,2,0,1,3)
+ item.set_child(child)
+
+
+ def _on_listview_bind(self,factory,item):
+ child = item.get_child()
+ data = item.get_item()
+
+ child.name_label.set_markup("{}".format(
+ GLib.markup_escape_text(data.name)))
+ child.catalogitemid_label.set_text(data.catalog_item_id)
+ child.installdir_label.set_text(data.installdir)
+
+ if hasattr(child.new_button,'_signal_clicked_connector'):
+ child.new_button.disconnect(child.new_button._signal_clicked_connector)
+ child.new_button._signal_clicked_connector = child.new_button.connect('clicked',
+ self._on_listview_new_button_clicked,
+ data)
+
+ if hasattr(child.ignore_button,'_signal_clicked_connector'):
+ child.ignore_button.disconnect(child.ignore_button._signal_clicked_connector)
+ child.ignore_button._signal_clicked_connector = child.ignore_button.connect('clicked',
+ self._on_listview_ignore_button_clicked,
+ data)
+
+ if hasattr(child.lookup_button,'_signal_clicked_connector'):
+ child.lookup_button.disconnect(child.lookup_button._signal_clicked_connector)
+ child.lookup_button._signal_clicked_connector = child.lookup_button.connect('clicked',
+ self._on_listview_lookup_button_clicked,
+ data)
+ child.lookup_button.set_sensitive(False)
+
+ if hasattr(child.online_button,'_signal_clicked_connector'):
+ child.online_button.disconnect(child.online_button._signal_clicked_connector)
+ child.online_button._signal_clicked_connector = child.online_button.connect('clicked',
+ self._on_listview_online_button_clicked,
+ data)
+ child.online_button.set_sensitive(False)
+
+ def _on_game_dialog_response(self,dialog,response,info:EpicGameInfo):
+ if response == Gtk.ResponseType.APPLY:
+ for i in range(self.__liststore.get_n_items()):
+ item = self.__liststore.get_item(i)
+ if item.catalog_item_id == info.catalog_item_id:
+ self.__liststore.remove(i)
+ break
+
+
+ def _on_listview_new_button_clicked(self,button:Gtk.Button,info:EpicGameInfo):
+ game = Game("",info.name,"")
+ if PLATFORM_WINDOWS:
+ windows = EpicWindowsData("","",installdir=info.installdir)
+ else:
+ windows = None
+
+ game.epic = EpicGameData(catalog_item_id=info.catalog_item_id,
+ windows=windows)
+
+ dialog = GameDialog(parent=self,game=game)
+ dialog.connect_after('response',self._on_dialog_reponse,info)
+ dialog.present()
+
+
+ def _on_ignore_dialog_response(self,dialog,response,info:EpicGameInfo):
+ if response == Gtk.ResponseType.YES:
+ epic = Epic()
+ epic.add_ignored_app(EpicIgnoredApp(info.catalog_item_id,
+ info.name,
+ dialog.reason_entry.get_text()))
+ for i in range(self.__liststore.get_n_items()):
+ item = self.__liststore.get_item()
+ if item.catalog_item_id == info.catalog_item_id:
+ self.__liststore.remove(i)
+ break
+
+ dialog.hide()
+ dialog.destroy()
+
+ def _on_listview_ignore_button_clicked(self,button:Gtk.Button,info:EpicGameInfo):
+ dialog = Gtk.MessageDialog(transient_for=self,
+ text=_("Do you really won to add the game {game} to the ignore list?").format(
+ game=GLib.markup_escape_text(info.name)),
+ use_markup=True,
+ secondary_text=_("Please enter a reason below."),
+ secondary_use_markup=True,
+ buttons=Gtk.ButtonsType.YES_NO,
+ modal=False)
+ dialog.reason_entry = Gtk.Entry()
+ dialog.reason_entry.set_hexpand(True)
+ dialog.get_content_area().append(dialog.reason_entry)
+ dialog.connect('response',self._on_ignore_dialog_response,info)
+ dialog.present()
+
+
+
+ def _on_listview_lookup_button_clicked(self,button:Gtk.Button,info:EpicGameInfo):
+ pass
+
+ def _on_listview_online_button_clicked(self,button:Gtk.Button,info:EpicGameInfo):
+ pass
+
+ def do_response(self,response):
+ self.hide()
+ self.destroy()
+
\ No newline at end of file
diff --git a/sgbackup/gui/_gamedialog.py b/sgbackup/gui/_gamedialog.py
index 96e3d91..c145bdf 100644
--- a/sgbackup/gui/_gamedialog.py
+++ b/sgbackup/gui/_gamedialog.py
@@ -755,9 +755,9 @@ class GameDialog(Gtk.Dialog):
page = Gtk.Box.new(Gtk.Orientation.VERTICAL,2)
grid = Gtk.Grid()
- label = self.__create_label(_("AppName:"))
- page.appname_entry = Gtk.Entry()
- page.appname_entry.set_hexpand(True)
+ label = self.__create_label(_("CatalogIemID:"))
+ page.catalogitemid_entry = Gtk.Entry()
+ page.catalogitemid_entry.set_hexpand(True)
grid.attach(label,0,0,1,1)
grid.attach(page.appname_entry,1,0,1,1)
page.append(grid)
@@ -1015,9 +1015,11 @@ class GameDialog(Gtk.Dialog):
else "")
# Epic Games
- set_game_widget_data(self.__epic.windows,self.__game.epic.windows if self.has_game and self.__game.epic else None)
+ set_game_widget_data(self.__epic.windows,self.__game.epic.windows
+ if self.has_game and self.__game.epic else None)
- self.__epic.appname_entry.set_text(self.__game.epic.appname if self.has_game and self.__game.epic else "")
+ self.__epic.catalogitemid_entry.set_text(self.__game.epic.catalog_item_id
+ if self.has_game and self.__game.epic else "")
self.__epic.windows.installdir_entry.set_text(self.__game.epic.windows.installdir
if self.has_game
and self.__game.epic
@@ -1254,9 +1256,9 @@ class GameDialog(Gtk.Dialog):
data = get_epic_data(self.__epic.windows)
if self.__game.epic:
- self.__game.epic.appname = self.__epic.appname_entry.get_text()
+ self.__game.epic.catalog_item_id = self.__epic.catalogitemid_entry.get_text()
else:
- self.__game.epic = EpicGameData(appname=self.__epic.appname_entry.get_text())
+ self.__game.epic = EpicGameData(appname=self.__epic.catalogitemid_entry.get_text())
if self.__game.epic.windows:
self.__game.epic.windows.savegame_root = data['sgroot']
diff --git a/sgbackup/gui/_steam.py b/sgbackup/gui/_steam.py
index 03ec92c..e9c7d5f 100644
--- a/sgbackup/gui/_steam.py
+++ b/sgbackup/gui/_steam.py
@@ -238,7 +238,7 @@ class SteamGameLookupDialog(GameSearchDialog):
def do_prepare_game(self, game):
game = super().do_prepare_game(game)
if game.steam:
- if not game.steam.appid != self.steam_app.appid:
+ if not game.steam.appid != self.steam_app.appid and game.steam_appid >= 0:
raise ValueError("Steam appid error")
if PLATFORM_WINDOWS:
@@ -494,6 +494,9 @@ class SteamNoNewAppsDialog(Gtk.MessageDialog):
self.hide()
self.destroy()
+
+### SteamIgnoreApps ###########################################################
+
class SteamNoIgnoredAppsDialog(Gtk.MessageDialog):
def __init__(self,parent:Gtk.Window|None=None):
Gtk.MessageDialog.__init__(self,buttons=Gtk.ButtonsType.OK)
@@ -506,7 +509,8 @@ class SteamNoIgnoredAppsDialog(Gtk.MessageDialog):
def do_response(self,response):
self.hide()
self.destroy()
-
+
+
class SteamIgnoreAppsSorter(Gtk.Sorter):
def do_compare(self,item1:IgnoreSteamApp,item2:IgnoreSteamApp):
s1 = item1.name.lower()
diff --git a/sgbackup/steam.py b/sgbackup/steam.py
index eb01279..9ed32e1 100644
--- a/sgbackup/steam.py
+++ b/sgbackup/steam.py
@@ -350,7 +350,7 @@ class Steam(GObject):
new_apps = []
for lib in self.libraries:
for app in lib.steam_apps:
- if not app.appid in GameManager.get_global().steam_games and not app.appid in self.ignore_apps:
+ if not GameManager.get_global().has_steam_game(app.appid) and not app.appid in self.ignore_apps:
new_apps.append(app)
return sorted(new_apps)
diff --git a/sgbackup/utility.py b/sgbackup/utility.py
index d044ae1..3ba735d 100644
--- a/sgbackup/utility.py
+++ b/sgbackup/utility.py
@@ -17,15 +17,29 @@
###############################################################################
import sys
-if sys.platform.lower() == 'win32':
- PLATFORM_WINDOWS=True
-else:
- PLATFORM_WINDOWS=False
-
-if sys.platform.lower() in ['linux','freebsd','netbsd','openbsd','dragonfly','macos','cygwin']:
- PLATFORM_UNIX = True
-else:
- PLATFORM_UNIX = False
+def _platform_is_linux():
+ if sys.platform == 'linux':
+ return True
+ for i in ('freebsd','netbsd','openbsd','dragonfly'):
+ if sys.platform.startswith(i):
+ return True
+ return False
+
+def _platform_is_unix():
+ if sys.platform in ('linux','darwin','aix'):
+ return True
+ for i in ('freebsd','netbsd','openbsd','dragonfly'):
+ if sys.platform.startswith(i):
+ return True
+ return False
+
+PLATFORM_WINDOWS = (sys.platform == 'win32')
+PLATFORM_LINUX = _platform_is_linux()
+PLATFORM_MACOS = (sys.platform == 'darwin')
+PLATFORM_UNIX = _platform_is_unix()
+
+del _platform_is_unix
+del _platform_is_linux
def sanitize_windows_path(path:str)->str:
return path.replace('/','\\')