2025.02.17 02:21:41 (desktop)

This commit is contained in:
Christian Moser 2025-02-17 02:21:41 +01:00
parent 35d8b71751
commit 0abb7db5c3
Failed to extract signature
9 changed files with 126 additions and 94 deletions

View File

@ -1,24 +0,0 @@
[build-system]
builbackend = 'setuptools.build_meta'
requires = ['setuptools >= 61.0']
[project]
dynamic = ["version"]
name = 'sgbackup'
version = '0.0.0'
requires_python = '>= 3.11'
description = 'Savegame Backup Tool'
readme = 'README.md'
license = {file = 'LICENSE'}
authors = [
{name = 'Christian Moser', email = 'christian@mydevel.at'},
]
dependencies = ['gi','yaml']
[project.scripts]
sgbackup = 'sgbackup:cli_main'
csgbackup = 'sgbackup:curses_main'
[project.gui-scripts]
gsgbackup = 'sgbackup:gui_main'

2
requirements.txt Normal file
View File

@ -0,0 +1,2 @@
rapidfuzz

38
setup.py Normal file
View File

@ -0,0 +1,38 @@
#!/usr/bin/env python
import os
import sys
from setuptools import setup
import subprocess
import bz2
PACKAGE_ROOT=os.path.dirname(__file__)
sys.path.insert(0,PACKAGE_ROOT)
import sgbackup
setup(
name='sgbackup',
version=sgbackup.__version__
description='A backup tool for savegames.',
author="Christian Moser",
author_email="christian@cmoser.eu",
packages=[
'sgbackup',
'sgbackup.archivers',
'sgbackup.commands',
'sgbackup.curses',
'sgbackup.help',
'sgbackup.gui',
],
package_data={
'sgbackup':[
'icons/sgbackup.ico',
'icons/hicolor/symbolic/*/*.svg'
],
'sgbackup.gui': [
'*.ui'
],
},
platforms=['win32','linux']
)

View File

@ -21,7 +21,7 @@ from . import _import_gtk
__version__ = "0.0.1" __version__ = "0.0.1"
from .settings import settings from .settings import settings
from . import _logging from . import _logging
from .main import cli_main,curses_main,gui_main from .main import cli_main,gui_main
from . import game from . import game
from .command import Command from .command import Command
from . import commands from . import commands
@ -31,7 +31,7 @@ __ALL__ = [
"settings" "settings"
"cli_main", "cli_main",
"gui_main", "gui_main",
"curses_main", #"curses_main",
'game', 'game',
"Command", "Command",
"commands", "commands",

View File

@ -164,9 +164,6 @@ class GameView(Gtk.Box):
pass pass
self.__liststore.append(g) self.__liststore.append(g)
self.__sort_model = Gtk.SortListModel.new(self._liststore,self.__name_sorter) self.__sort_model = Gtk.SortListModel.new(self._liststore,self.__name_sorter)
self.__sort_model
self.__action_dialog = None
self.__backup_dialog = None
factory_icon = Gtk.SignalListItemFactory.new() factory_icon = Gtk.SignalListItemFactory.new()
factory_icon.connect('setup',self._on_icon_column_setup) factory_icon.connect('setup',self._on_icon_column_setup)
@ -319,12 +316,14 @@ class GameView(Gtk.Box):
def _on_key_column_setup(self,factory,item): def _on_key_column_setup(self,factory,item):
label = Gtk.Label() label = Gtk.Label()
label.set_xalign(0.0) label.set_xalign(0.0)
label.set_use_markup(True)
item.set_child(label) item.set_child(label)
def _on_key_column_bind(self,factory,item): def _on_key_column_bind(self,factory,item):
label = item.get_child() label = item.get_child()
game = item.get_item() game = item.get_item()
game.bind_property('key',label,'label',BindingFlags.SYNC_CREATE) game.bind_property('key',label,'label',BindingFlags.SYNC_CREATE,
lambda _binding,s: '<span size="large">{}</span>'.format(GLib.markup_escape_text(s)))
def _on_name_column_setup(self,factory,item): def _on_name_column_setup(self,factory,item):
label = Gtk.Label() label = Gtk.Label()
@ -424,8 +423,13 @@ class GameView(Gtk.Box):
game = item.get_item() game = item.get_item()
archiver_manager = ArchiverManager.get_global() archiver_manager = ArchiverManager.get_global()
if not hasattr(child.backup_button,'_signal_clicked_connection'): # check if we are already connected.
# if we dont check we might have more than one dialog open or execute backups more than once
# due to Gtk4 reusing the widgets. When selecting a row in the columnview this method is called.
if hasattr(child.backup_button,'_signal_clicked_connection'):
child.backup_button.disconnect(child.backup_button._signal_clicked_connection)
child.backup_button._signal_clicked_connection = child.backup_button.connect('clicked',self._on_columnview_backup_button_clicked,item) child.backup_button._signal_clicked_connection = child.backup_button.connect('clicked',self._on_columnview_backup_button_clicked,item)
if not hasattr(child.backup_button,'_property_backup_in_progress_binding'): if not hasattr(child.backup_button,'_property_backup_in_progress_binding'):
child.backup_button._property_backup_in_progress_binding = archiver_manager.bind_property('backup-in-progress', child.backup_button._property_backup_in_progress_binding = archiver_manager.bind_property('backup-in-progress',
child.backup_button, child.backup_button,
@ -433,7 +437,8 @@ class GameView(Gtk.Box):
BindingFlags.SYNC_CREATE, BindingFlags.SYNC_CREATE,
lambda binding,x: False if x else True) lambda binding,x: False if x else True)
if not hasattr(child.edit_button,'_signal_clicked_connection'): if hasattr(child.edit_button,'_signal_clicked_connection'):
child.edit_button.disconnect(child.edit_button._signal_clicked_connection)
child.edit_button._signal_clicked_connection = child.edit_button.connect('clicked',self._on_columnview_edit_button_clicked,item) child.edit_button._signal_clicked_connection = child.edit_button.connect('clicked',self._on_columnview_edit_button_clicked,item)
if not hasattr(child.edit_button,'_property_backup_in_progress_binding'): if not hasattr(child.edit_button,'_property_backup_in_progress_binding'):
@ -443,8 +448,10 @@ class GameView(Gtk.Box):
BindingFlags.SYNC_CREATE, BindingFlags.SYNC_CREATE,
lambda binding,x: False if x else True) lambda binding,x: False if x else True)
if not hasattr(child.remove_button,'_signal_clicked_connection'): if hasattr(child.remove_button,'_signal_clicked_connection'):
child.remove_button.disconnect(child.remove_button._signal_clicked_connection)
child.remove_button._signal_clicked_connection = child.remove_button.connect('clicked',self._on_columnview_remove_button_clicked,item) child.remove_button._signal_clicked_connection = child.remove_button.connect('clicked',self._on_columnview_remove_button_clicked,item)
if not hasattr(child.remove_button,'_property_backup_in_progress_binding'): if not hasattr(child.remove_button,'_property_backup_in_progress_binding'):
child.remove_button._property_backup_in_progress_binding = archiver_manager.bind_property('backup-in-progress', child.remove_button._property_backup_in_progress_binding = archiver_manager.bind_property('backup-in-progress',
child.remove_button,'sensitive', child.remove_button,'sensitive',
@ -458,30 +465,22 @@ class GameView(Gtk.Box):
child.backup_button.set_sensitive(False) child.backup_button.set_sensitive(False)
def _on_columnview_backup_button_clicked(self,button,item): def _on_columnview_backup_button_clicked(self,button,item):
def on_dialog_response(dialog,response):
self.__backup_dialog = None
if self.__backup_dialog is None:
game = item.get_item() game = item.get_item()
self.__backup_dialog = BackupSingleDialog(self.get_root(),game) dialog = BackupSingleDialog(self.get_root(),game)
self.__backup_dialog.connect('response',on_dialog_response) dialog.connect('response',on_dialog_response)
self.__backup_dialog.run() dialog.run()
def _on_columnview_edit_button_clicked(self,button,item): def _on_columnview_edit_button_clicked(self,button,item):
def on_dialog_response(dialog,response): def on_dialog_response(dialog,response):
if response == Gtk.ResponseType.APPLY: if response == Gtk.ResponseType.APPLY:
self.refresh() self.refresh()
self.__action_dialog = None
if self.__action_dialog is None:
game = item.get_item() game = item.get_item()
self.__action_dialog = GameDialog(self.get_root(),game) dialog = GameDialog(self.get_root(),game)
self.__action_dialog.set_modal(False) dialog.set_modal(False)
self.__action_dialog.connect('response',on_dialog_response) dialog.connect('response',on_dialog_response)
self.__action_dialog.present() dialog.present()
else:
self.__action_dialog.present()
def _on_columnview_remove_button_clicked(self,button,item): def _on_columnview_remove_button_clicked(self,button,item):
@ -497,21 +496,17 @@ class GameView(Gtk.Box):
dialog.hide() dialog.hide()
dialog.destroy() dialog.destroy()
self.__action_dialog = None
game = item.get_item() game = item.get_item()
if self.__action_dialog is None: dialog = Gtk.MessageDialog(buttons=Gtk.ButtonsType.YES_NO,
self.__action_dialog = Gtk.MessageDialog(buttons=Gtk.ButtonsType.YES_NO,
text="Do you really want to remove the game <span weight='bold'>{game}</span>?".format( text="Do you really want to remove the game <span weight='bold'>{game}</span>?".format(
game=game.name), game=game.name),
use_markup=True, use_markup=True,
secondary_text="Removing games cannot be undone!!!") secondary_text="Removing games cannot be undone!!!")
self.__action_dialog.set_transient_for(self.get_root()) dialog.set_transient_for(self.get_root())
self.__action_dialog.set_modal(False) dialog.set_modal(False)
self.__action_dialog.connect('response',on_dialog_response,game) dialog.connect('response',on_dialog_response,game)
self.__action_dialog.present() dialog.present()
else:
self.__action_dialog.present()
# GameView class # GameView class

View File

@ -100,7 +100,10 @@ class RegistryKeyData(GObject):
""" """
GObject.__init__(self) GObject.__init__(self)
if not regkey: if not regkey:
self.__regkey = "" self.regkey = ""
else:
self.regkey = regkey
@Property(type=str) @Property(type=str)
def regkey(self)->str: def regkey(self)->str:
@ -972,12 +975,12 @@ class GameDialog(Gtk.Dialog):
irk = [] irk = []
for i in range(grk_model.get_n_items()): for i in range(grk_model.get_n_items()):
item = grk.model.get_item(i) item = grk_model.get_item(i)
if item.regkey: if item.regkey:
grk.append(item.regkey) grk.append(item.regkey)
for i in range(irk_model.get_n_items()): for i in range(irk_model.get_n_items()):
item = irk.model.get_item(i) item = irk_model.get_item(i)
if item.regkey: if item.regkey:
irk.append(item.regkey) irk.append(item.regkey)
@ -1255,8 +1258,8 @@ class GameDialog(Gtk.Dialog):
label = item.get_child() label = item.get_child()
data = item.get_item() data = item.get_item()
label.set_text(data.regkey) label.set_text(data.regkey)
label.bind_property('text',data,'regkey',GObject.BindingFlags.DEFAULT) label.bind_property('text',data,'regkey',BindingFlags.DEFAULT)
label.connect('changed',self._on_windows_regkey_label_changed,widget) label.connect('notify::editing',self._on_windows_regkey_label_notify_editing,widget)
if not label.get_text(): if not label.get_text():
label.start_editing() label.start_editing()
label.grab_focus() label.grab_focus()
@ -1316,16 +1319,15 @@ class GameDialog(Gtk.Dialog):
def _on_windows_regkey_add_button_clicked(self,button,widget): def _on_windows_regkey_add_button_clicked(self,button,widget):
widget.listview.get_model().get_model().append(RegistryKeyData()) widget.listview.get_model().get_model().append(RegistryKeyData())
def _on_windows_regkey_label_changed(self,label,widget): def _on_windows_regkey_label_notify_editing(self,label,state,widget):
if not label.get_editing():
if not label.get_text(): if not label.get_text():
model = widget.listview.get_model().get_model() model = widget.listview.get_model().get_model()
i = 0 i = 0
while i < model.get_n_items(): for i in reversed(range(model.get_n_items())):
item = model.get_item(i) item = model.get_item(i)
if not item.regkey: if not item.regkey:
model.remove(i) model.remove(i)
continue
i += 1
def do_response(self,response): def do_response(self,response):
if (response == Gtk.ResponseType.APPLY): if (response == Gtk.ResponseType.APPLY):

View File

@ -172,8 +172,17 @@ class SteamLibrariesDialog(Gtk.Dialog):
lib = item.get_item() lib = item.get_item()
child.label.set_text(lib.directory) child.label.set_text(lib.directory)
child.label.bind_property('text',lib,'directory',BindingFlags.DEFAULT) child.label.bind_property('text',lib,'directory',BindingFlags.DEFAULT)
child.chooser_button.connect('clicked',self._on_list_chooser_button_clicked,child.label) if hasattr(child.chooser_button,'_signal_clicked_connector'):
child.remove_button.connect('clicked',self._on_list_remove_button_clicked,child.label) child.chooser_button.disconnect(child.chooser_button._signal_clicked_connector)
child.chooser_button._signal_clicked_connector = child.chooser_button.connect('clicked',
self._on_list_chooser_button_clicked,
child.label)
if hasattr(child.remove_button,'_signal_clicked_connector'):
child.remove_button.disconnect(child.remove_button._signal_clicked_connector)
child.remove_button._signal_clicked_connector = child.remove_button.connect('clicked',
self._on_list_remove_button_clicked,
child.label)
def do_response(self,response): def do_response(self,response):
if response == Gtk.ResponseType.APPLY: if response == Gtk.ResponseType.APPLY:
@ -219,6 +228,7 @@ class NewSteamAppsDialog(Gtk.Dialog):
factory = Gtk.SignalListItemFactory() factory = Gtk.SignalListItemFactory()
factory.connect('setup',self._on_listitem_setup) factory.connect('setup',self._on_listitem_setup)
factory.connect('bind',self._on_listitem_bind) factory.connect('bind',self._on_listitem_bind)
factory.connect('unbind',self._on_listitem_unbind)
self.__listview = Gtk.ListView.new(selection,factory) self.__listview = Gtk.ListView.new(selection,factory)
self.__listview.set_vexpand(True) self.__listview.set_vexpand(True)
@ -287,23 +297,32 @@ class NewSteamAppsDialog(Gtk.Dialog):
child.name_label.set_markup("<span weight='bold' size='large'>{}</span>".format(GLib.markup_escape_text(data.name))) child.name_label.set_markup("<span weight='bold' size='large'>{}</span>".format(GLib.markup_escape_text(data.name)))
child.appid_label.set_text(str(data.appid)) child.appid_label.set_text(str(data.appid))
child.installdir_label.set_text(data.installdir) child.installdir_label.set_text(data.installdir)
if not hasattr(child.add_app_button,'_signal_clicked_connector'):
# Check if we are already connected.
# if we dont check we might have more than one dialog open
# due to Gtk4 reusing the widgets.
# When selecting a row in the columnview this method is called so we
# need to ensure that the last binding is used to work as expected.
if hasattr(child.add_app_button,'_signal_clicked_connector'):
child.add_app_button.disconnect(child.add_app_button._signal_clicked_connector)
child.add_app_button._signal_clicked_connector = child.add_app_button.connect('clicked',self._on_add_steamapp_button_clicked,data) child.add_app_button._signal_clicked_connector = child.add_app_button.connect('clicked',self._on_add_steamapp_button_clicked,data)
if not hasattr(child.ignore_app_button,'_signal_clicked_connector'):
if hasattr(child.ignore_app_button,'_signal_clicked_connector'):
child.ignore_app_button.disconnect(child.ignore_app_button._signal_clicked_connector)
child.ignore_app_button._signal_clicked_connector = child.ignore_app_button.connect('clicked',self._on_ignore_steamapp_button_clicked,data) child.ignore_app_button._signal_clicked_connector = child.ignore_app_button.connect('clicked',self._on_ignore_steamapp_button_clicked,data)
def _on_listitem_unbind(self,factory,item):
child = item.get_child()
data = item.get_item()
def _on_add_steamapp_button_clicked(self,button,data:SteamApp,*args): def _on_add_steamapp_button_clicked(self,button,data:SteamApp,*args):
def on_dialog_response(dialog,response): def on_dialog_response(dialog,response):
self.__gamedialog = None
if response == Gtk.ResponseType.APPLY: if response == Gtk.ResponseType.APPLY:
for i in range(self.__listmodel.get_n_items()): for i in range(self.__listmodel.get_n_items()):
if data.appid == self.__listmodel.get_item(i).appid: if data.appid == self.__listmodel.get_item(i).appid:
self.__listmodel.remove(i) self.__listmodel.remove(i)
break break
if self.__gamedialog is not None:
return
game = Game("Enter key",data.name,"") game = Game("Enter key",data.name,"")
if PLATFORM_WINDOWS: if PLATFORM_WINDOWS:
game.steam_windows = SteamWindowsGame(data.appid,"","",installdir=data.installdir) game.steam_windows = SteamWindowsGame(data.appid,"","",installdir=data.installdir)

View File

View File

@ -29,9 +29,9 @@ def cli_main():
logger.debug("Running cli_main()") logger.debug("Running cli_main()")
return 0 return 0
def curses_main(): #def curses_main():
logger.debug("Running curses_main()") # logger.debug("Running curses_main()")
return 0 # return 0
def gui_main(): def gui_main():
logger.debug("Running gui_main()") logger.debug("Running gui_main()")