mirror of
https://github.com/c9moser/sgbackup.git
synced 2026-01-19 19:40:13 +00:00
2025.02.23 13:23:07 (desktop)
This commit is contained in:
parent
2fa39385c0
commit
6aac8282ef
@ -24,6 +24,7 @@ if [ ! -d "$bindir" ]; then
|
||||
fi
|
||||
|
||||
pythonpath="$( python -c 'import sys; print(sys.executable)' )"
|
||||
pythonwpath="$( pythonw -c 'import sys; print(sys.executable)' )"
|
||||
cat > "${bindir}/sgbackup" << EOF
|
||||
#!/bin/bash
|
||||
|
||||
@ -52,7 +53,7 @@ install_ps1="${PRJECT_DIR}/install.ps1"
|
||||
wproject_dir="$( cygpath -w "${PROJECT_DIR}" )"
|
||||
|
||||
cat > "$install_ps1" << EOF
|
||||
[Environment]::SetEnvironemtnVariable("Path","\$env:PATH;$wbindir","User")
|
||||
[Environment]::SetEnvironmentVariable("Path","\$env:PATH;$wbindir","User")
|
||||
|
||||
\$desktop_dir=[Environment]::getFolderPath("Desktop")
|
||||
\$startmenu_dir=[Environment]::getFolderPath("StartMenu")
|
||||
@ -62,8 +63,9 @@ Copy-Item -Path "$wproject_dir\\sgbackup\\icons\\sgbackup.ico" -Destination "\$p
|
||||
|
||||
foreach (\$dir in \$desktop_dir,\$startmenu_dir) {
|
||||
\$shell=New-Object -ComObject WScript.Shell
|
||||
\$shortcut=\$shell.CreateShortcut('\$dir\\sgbackup.lnk')
|
||||
\$shortcut.TargetPath='$wbindir\\gsgbackup.cmd'
|
||||
\$shortcut=\$shell.CreateShortcut("\$dir\\sgbackup.lnk")
|
||||
\$shortcut.TargetPath='$pythonwpath'
|
||||
\$shortcut.Arguments='-m sgbackup.gui'
|
||||
\$shortcut.IconLocation="\$picture_dir\\sgbackup.ico"
|
||||
\$shortcut.Save()
|
||||
}
|
||||
|
||||
1
setup.py
1
setup.py
@ -31,6 +31,7 @@ setup(
|
||||
],
|
||||
package_data={
|
||||
'sgbackup':[
|
||||
'logger.conf',
|
||||
'icons/sgbackup.ico',
|
||||
'icons/hicolor/symbolic/*/*.svg'
|
||||
],
|
||||
|
||||
@ -29,7 +29,7 @@ import os
|
||||
import threading
|
||||
import time
|
||||
|
||||
from ..game import Game
|
||||
from ..game import Game,SavegameType,VALID_SAVEGAME_TYPES,SAVEGAME_TYPE_ICONS
|
||||
from ..settings import settings
|
||||
from ..utility import sanitize_path,sanitize_windows_path
|
||||
|
||||
@ -72,9 +72,8 @@ class Archiver(GObject):
|
||||
return False
|
||||
|
||||
def backup(self,game:Game)->bool:
|
||||
self._logger.info("Backing up {game}".format(game=game.key))
|
||||
if not game.get_backup_files():
|
||||
self._logger.warning("No files SaveGame files for game {game}!".format(game=game.key))
|
||||
self._logger.warning("[backup] No files SaveGame files for game {game}!".format(game=game.key))
|
||||
return False
|
||||
|
||||
filename = self.generate_new_backup_filename(game)
|
||||
@ -82,18 +81,24 @@ class Archiver(GObject):
|
||||
if not os.path.isdir(dirname):
|
||||
os.makedirs(dirname)
|
||||
|
||||
self._logger.info("Backing up {game} -> {filename}".format(
|
||||
self._logger.info("[backup] {game} -> {filename}".format(
|
||||
game=game.key,filename=filename))
|
||||
return self.emit('backup',game,filename)
|
||||
|
||||
def generate_new_backup_filename(self,game:Game)->str:
|
||||
dt = datetime.datetime.now()
|
||||
|
||||
basename = '.'.join((game.savegame_name,
|
||||
game.savegame_subdir,
|
||||
dt.strftime("%Y%m%d-%H%M%S"),
|
||||
game.savegame_type.value,
|
||||
game.savegame_subdir,
|
||||
"sgbackup",
|
||||
self.extensions[0][1:] if self.extensions[0].startswith('.') else self.extensions[0]))
|
||||
return sanitize_path(os.path.join(settings.backup_dir,game.savegame_name,game.subdir,basename))
|
||||
return sanitize_path(os.path.join(settings.backup_dir,
|
||||
game.savegame_name,
|
||||
game.savegame_type.value,
|
||||
game.subdir,
|
||||
basename))
|
||||
|
||||
def _backup_progress(self,game:Game,fraction:float,message:str|None):
|
||||
if fraction > 1.0:
|
||||
@ -201,12 +206,13 @@ class ArchiverManager(GObject):
|
||||
archiver.backup(game)
|
||||
archiver.disconnect(backup_sc)
|
||||
if game.is_live and settings.backup_versions > 0:
|
||||
backups = sorted(self.get_live_backups(game))
|
||||
backups = sorted(self.get_live_backups_for_type(game,game.savegame_type),reverse=True)
|
||||
if backups and len(backups) > settings.backup_versions:
|
||||
for filename in backups[settings.backup_versions:]:
|
||||
self.remove_backup(game,filename)
|
||||
|
||||
|
||||
|
||||
self.emit("backup-game-finished",game)
|
||||
if not multi_backups:
|
||||
self.emit("backup-finished")
|
||||
@ -253,8 +259,6 @@ class ArchiverManager(GObject):
|
||||
else:
|
||||
n = len(games)
|
||||
|
||||
print("Starting backup with {n} threads".format(n=n))
|
||||
|
||||
for i in range(n):
|
||||
game=game_list[0]
|
||||
del game_list[0]
|
||||
@ -307,29 +311,9 @@ class ArchiverManager(GObject):
|
||||
|
||||
def get_live_backups(self,game:Game):
|
||||
ret = []
|
||||
backupdir = os.path.join(settings.backup_dir,game.savegame_name,'live')
|
||||
for sgtype in VALID_SAVEGAME_TYPES:
|
||||
backupdir = os.path.join(settings.backup_dir,game.savegame_name,sgtype.value,'live')
|
||||
|
||||
if os.path.isdir(backupdir):
|
||||
for basename in os.listdir(backupdir):
|
||||
filename = os.path.join(backupdir,basename)
|
||||
if (self.is_archive(filename)):
|
||||
ret.append(filename)
|
||||
return ret
|
||||
|
||||
def get_finished_backups(self,game:Game):
|
||||
ret=[]
|
||||
backupdir = os.path.join(settings.backup_dir,game.savegame_name,'finished')
|
||||
|
||||
if os.path.isdir(backupdir):
|
||||
for basename in os.listdir(backupdir):
|
||||
filename = os.path.join(backupdir,basename)
|
||||
if (self.is_archive(filename)):
|
||||
ret.append(filename)
|
||||
return ret
|
||||
|
||||
def get_backups(self,game:Game):
|
||||
ret = []
|
||||
for backupdir in [os.path.join(settings.backup_dir,game.savegame_name,i) for i in ('live','finished')]:
|
||||
if os.path.isdir(backupdir):
|
||||
for basename in os.listdir(backupdir):
|
||||
filename = os.path.join(backupdir,basename)
|
||||
@ -338,3 +322,37 @@ class ArchiverManager(GObject):
|
||||
|
||||
return ret
|
||||
|
||||
def get_live_backups_for_type(self,game:Game,type:SavegameType):
|
||||
ret = []
|
||||
backupdir = os.path.join(settings.backup_dir,game.savegame_name,type.value,'live')
|
||||
if (os.path.isdir(backupdir)):
|
||||
for basename in os.listdir(backupdir):
|
||||
filename = os.path.join(backupdir,basename)
|
||||
if (self.is_archive(filename)):
|
||||
ret.append(filename)
|
||||
return ret
|
||||
|
||||
def get_finished_backups(self,game:Game):
|
||||
ret=[]
|
||||
for sgtype in VALID_SAVEGAME_TYPES:
|
||||
backupdir = os.path.join(settings.backup_dir,game.savegame_name,sgtype.value,'finished')
|
||||
|
||||
if os.path.isdir(backupdir):
|
||||
for basename in os.listdir(backupdir):
|
||||
filename = os.path.join(backupdir,basename)
|
||||
if (self.is_archive(filename)):
|
||||
ret.append(filename)
|
||||
|
||||
return ret
|
||||
|
||||
def get_backups(self,game:Game):
|
||||
ret = []
|
||||
for sgtype in VALID_SAVEGAME_TYPES:
|
||||
for backupdir in [os.path.join(settings.backup_dir,game.savegame_name,sgtype.value,i) for i in ('live','finished')]:
|
||||
if os.path.isdir(backupdir):
|
||||
for basename in os.listdir(backupdir):
|
||||
filename = os.path.join(backupdir,basename)
|
||||
if (self.is_archive(filename)):
|
||||
ret.append(filename)
|
||||
return ret
|
||||
|
||||
@ -20,7 +20,7 @@ import os
|
||||
|
||||
COMMANDS = {}
|
||||
|
||||
_mods = ['commandbase']
|
||||
_mods = []
|
||||
|
||||
for _f in os.listdir(os.path.dirname(__file__)):
|
||||
if _f.startswith('_'):
|
||||
@ -35,14 +35,11 @@ for _f in os.listdir(os.path.dirname(__file__)):
|
||||
if _m not in _mods:
|
||||
exec("\n".join([
|
||||
"from . import " + _m,
|
||||
"_mods += _m",
|
||||
"_mods.append(_m)",
|
||||
"_mod = " + _m]))
|
||||
if hasattr(_mod,"COMMANDS") and len(_mod.COMMANDS) > 0:
|
||||
for _cmd in _mod.COMMANDS:
|
||||
COMMANDS[_cmd.get_id()] = _cmd
|
||||
del _cmd
|
||||
if hasattr(_mod,"COMMANDS"): #and _mod.COMMANDS:
|
||||
COMMANDS.update(_mod.COMMANDS)
|
||||
|
||||
del _mods
|
||||
del _f
|
||||
del _m
|
||||
del _mod
|
||||
|
||||
@ -16,8 +16,11 @@
|
||||
# along with this program. If not, see <https://www.gnu.org/licenses/>. #
|
||||
###############################################################################
|
||||
|
||||
from sgbackup import __version__ as VERSION, Command
|
||||
from sgbackup import __version__ as VERSION
|
||||
from ..command import Command
|
||||
|
||||
import logging
|
||||
logger = logging.getLogger(__name__)
|
||||
class VersionCommand(Command):
|
||||
def __init__(self):
|
||||
super().__init__('version', 'Version', 'Show version information.')
|
||||
@ -33,6 +36,39 @@ class VersionCommand(Command):
|
||||
return 0
|
||||
# VersionCommand class
|
||||
|
||||
COMMANDS = [
|
||||
VersionCommand(),
|
||||
]
|
||||
class SynopsisCommand(Command):
|
||||
def __init__(self):
|
||||
super().__init__('synopsis','Synopsis', 'Show usage information.')
|
||||
self.logger = logger.getChild('SynopsisCommand')
|
||||
|
||||
def get_synopsis(self):
|
||||
return "sgbackup synopsis [COMMAND] ..."
|
||||
|
||||
def get_sgbackup_synopsis(self):
|
||||
return "sgbackup COMMAND1 [OPTIONS1] [-- COMMAND2 [OPTIONS2]] ..."
|
||||
|
||||
def get_help(self):
|
||||
return super().get_help()
|
||||
|
||||
def execute(self,argv):
|
||||
error_code = 0
|
||||
if not argv:
|
||||
print(self.get_sgbackup_synopsis())
|
||||
|
||||
for i in argv:
|
||||
try:
|
||||
print(COMMANDS[i].get_synopsis())
|
||||
except:
|
||||
self.logger.error("No such command {command}".foramt(command=i))
|
||||
error_code = 4
|
||||
|
||||
return error_code
|
||||
|
||||
|
||||
__synopsis = SynopsisCommand()
|
||||
|
||||
COMMANDS = {
|
||||
'version':VersionCommand(),
|
||||
'synopsis': __synopsis,
|
||||
'usage': __synopsis
|
||||
}
|
||||
|
||||
@ -144,6 +144,19 @@ class SavegameType(StrEnum):
|
||||
return st.UNSET
|
||||
|
||||
|
||||
VALID_SAVEGAME_TYPES = [
|
||||
SavegameType.WINDOWS,
|
||||
SavegameType.LINUX,
|
||||
SavegameType.MACOS,
|
||||
SavegameType.STEAM_LINUX,
|
||||
SavegameType.STEAM_MACOS,
|
||||
SavegameType.STEAM_WINDOWS,
|
||||
#SavegameType.EPIC_LINUX,
|
||||
#SavegameType.EPIC_WINDOWS,
|
||||
#SavegameType.GOG_LINUX,
|
||||
#SavegameType.GOG_WINDOWS,
|
||||
]
|
||||
|
||||
SAVEGAME_TYPE_ICONS = {
|
||||
SavegameType.UNSET : None,
|
||||
SavegameType.WINDOWS: 'windows-svgrepo-com-symbolic',
|
||||
|
||||
@ -28,7 +28,7 @@ from pathlib import Path
|
||||
from ..settings import settings
|
||||
from ._settingsdialog import SettingsDialog
|
||||
from ._gamedialog import GameDialog
|
||||
from ..game import Game,GameManager,SAVEGAME_TYPE_ICONS
|
||||
from ..game import Game,GameManager,SAVEGAME_TYPE_ICONS,SavegameType
|
||||
from ._steam import SteamLibrariesDialog,NewSteamAppsDialog,NoNewSteamAppsDialog
|
||||
from ..steam import Steam
|
||||
from ._backupdialog import BackupSingleDialog,BackupManyDialog
|
||||
@ -532,12 +532,42 @@ class BackupViewData(GObject):
|
||||
self.__filename = filename
|
||||
|
||||
basename = os.path.basename(filename)
|
||||
self.__is_live = (os.path.basename(os.path.dirname(filename)) == 'live')
|
||||
parts = basename.split('.')
|
||||
self.__savegame_name = parts[0]
|
||||
self.__timestamp = DateTime.strptime(parts[2],"%Y%m%d-%H%M%S")
|
||||
|
||||
self.__extension = '.' + '.'.join(parts[3:])
|
||||
self.__savegame_name = parts[0]
|
||||
self.__timestamp = DateTime.strptime(parts[1],"%Y%m%d-%H%M%S")
|
||||
self.__is_live = parts[3] == 'live'
|
||||
self.__sgtype = SavegameType.from_string(parts[2])
|
||||
|
||||
|
||||
WINDOWS_TYPES = [
|
||||
SavegameType.WINDOWS,
|
||||
SavegameType.STEAM_WINDOWS,
|
||||
SavegameType.EPIC_WINDOWS,
|
||||
SavegameType.GOG_WINDOWS,
|
||||
]
|
||||
LINUX_TYPES = [
|
||||
SavegameType.LINUX,
|
||||
SavegameType.STEAM_LINUX,
|
||||
SavegameType.EPIC_LINUX,
|
||||
SavegameType.GOG_LINUX,
|
||||
]
|
||||
MACOS_TYPES = [
|
||||
SavegameType.MACOS,
|
||||
SavegameType.STEAM_MACOS,
|
||||
]
|
||||
|
||||
if self.__sgtype in WINDOWS_TYPES:
|
||||
self.__sgos = 'windows'
|
||||
elif self.__sgtype in LINUX_TYPES:
|
||||
self.__sgos = 'linux'
|
||||
elif self.__sgtype in MACOS_TYPES:
|
||||
self.__sgos = 'macos'
|
||||
else:
|
||||
self.__sgos = ''
|
||||
|
||||
self.__extension = '.' + '.'.join(parts[5:])
|
||||
|
||||
|
||||
@property
|
||||
def game(self)->Game:
|
||||
@ -548,6 +578,24 @@ class BackupViewData(GObject):
|
||||
"""
|
||||
return self.__game
|
||||
|
||||
@Property
|
||||
def savegame_type(self)->SavegameType:
|
||||
return self.__sgtype
|
||||
|
||||
@Property(type=str)
|
||||
def savegame_type_icon_name(self)->str:
|
||||
return SAVEGAME_TYPE_ICONS[self.__sgtype]
|
||||
|
||||
@Property(type=str)
|
||||
def savegame_os_icon_name(self)->str:
|
||||
if self.__sgos:
|
||||
return SAVEGAME_TYPE_ICONS[self.__sgos]
|
||||
return ""
|
||||
|
||||
@Property(type=str)
|
||||
def ostype_icon_name(self):
|
||||
pass
|
||||
|
||||
@Property(type=str)
|
||||
def savegame_name(self)->str:
|
||||
"""
|
||||
@ -618,6 +666,16 @@ class BackupView(Gtk.Box):
|
||||
self.__liststore = Gio.ListStore()
|
||||
selection = Gtk.SingleSelection.new(self.__liststore)
|
||||
|
||||
sgtype_factory = Gtk.SignalListItemFactory()
|
||||
sgtype_factory.connect('setup',self._on_sgtype_column_setup)
|
||||
sgtype_factory.connect('bind',self._on_sgtype_column_bind)
|
||||
sgtype_column = Gtk.ColumnViewColumn.new("",sgtype_factory)
|
||||
|
||||
sgos_factory = Gtk.SignalListItemFactory()
|
||||
sgos_factory.connect('setup',self._on_sgos_column_setup)
|
||||
sgos_factory.connect('bind',self._on_sgos_column_bind)
|
||||
sgos_column = Gtk.ColumnViewColumn.new("OS",sgos_factory)
|
||||
|
||||
live_factory = Gtk.SignalListItemFactory()
|
||||
live_factory.connect('setup',self._on_live_column_setup)
|
||||
live_factory.connect('bind',self._on_live_column_bind)
|
||||
@ -640,6 +698,8 @@ class BackupView(Gtk.Box):
|
||||
size_column = Gtk.ColumnViewColumn.new("Size",size_factory)
|
||||
|
||||
self.__columnview = Gtk.ColumnView.new(selection)
|
||||
self.__columnview.append_column(sgtype_column)
|
||||
self.__columnview.append_column(sgos_column)
|
||||
self.__columnview.append_column(live_column)
|
||||
self.__columnview.append_column(sgname_column)
|
||||
self.__columnview.append_column(timestamp_column)
|
||||
@ -662,6 +722,27 @@ class BackupView(Gtk.Box):
|
||||
"""
|
||||
return self.__gameview
|
||||
|
||||
def _on_sgtype_column_setup(self,factory,item):
|
||||
icon = Gtk.Image()
|
||||
icon.set_pixel_size(16)
|
||||
item.set_child(icon)
|
||||
|
||||
def _on_sgtype_column_bind(self,factory,item):
|
||||
icon = item.get_child()
|
||||
data = item.get_item()
|
||||
icon.set_from_icon_name(data.savegame_type_icon_name)
|
||||
|
||||
def _on_sgos_column_setup(self,factory,item):
|
||||
icon = Gtk.Image()
|
||||
icon.set_pixel_size(16)
|
||||
item.set_child(icon)
|
||||
|
||||
def _on_sgos_column_bind(self,factory,item):
|
||||
icon = item.get_child()
|
||||
data = item.get_item()
|
||||
if data.savegame_os_icon_name:
|
||||
icon.set_from_icon_name(data.savegame_os_icon_name)
|
||||
|
||||
def _on_live_column_setup(self,factory,item):
|
||||
checkbutton = Gtk.CheckButton()
|
||||
checkbutton.set_sensitive(False)
|
||||
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 17 KiB After Width: | Height: | Size: 5.6 KiB |
@ -1,5 +1,5 @@
|
||||
[loggers]
|
||||
keys=root,console,file
|
||||
keys=root
|
||||
|
||||
[handlers]
|
||||
keys=consoleHandler,fileHandler
|
||||
@ -8,30 +8,19 @@ keys=consoleHandler,fileHandler
|
||||
keys=consoleFormatter,fileFormatter
|
||||
|
||||
[logger_root]
|
||||
level=INFO
|
||||
level=DEBUG
|
||||
handlers=consoleHandler,fileHandler
|
||||
|
||||
[logger_console]
|
||||
level=DEBUG
|
||||
handlers=consoleHandler
|
||||
propagate=0
|
||||
qualname=console
|
||||
|
||||
[logger_file]
|
||||
level=DEBUG
|
||||
handlers=fileHandler
|
||||
propagate=0
|
||||
qualname=file
|
||||
|
||||
[handler_consoleHandler]
|
||||
class=StreamHandler
|
||||
formatter=consoleFormatter
|
||||
level=DEBUG
|
||||
level=INFO
|
||||
|
||||
[handler_fileHandler]
|
||||
class=handlers.RotatingFileHandler
|
||||
args=('sgbackup.log','a',10485760,10,'UTF-8',False,)
|
||||
formatter=fileFormatter
|
||||
level=DEBUG
|
||||
|
||||
[formatter_consoleFormatter]
|
||||
format=[%(levelname)s:%(name)s] %(message)s
|
||||
|
||||
@ -21,17 +21,48 @@ import logging
|
||||
from . import gui
|
||||
from .gui import Application
|
||||
from .steam import SteamLibrary
|
||||
|
||||
import sys
|
||||
from . import commands
|
||||
|
||||
logger=logging.getLogger(__name__)
|
||||
|
||||
def cli_main():
|
||||
logger.debug("Running cli_main()")
|
||||
return 0
|
||||
argc = len(sys.argv)
|
||||
if argc < 2:
|
||||
return commands.COMMANDS['synopsis'].execute([])
|
||||
|
||||
#def curses_main():
|
||||
# logger.debug("Running curses_main()")
|
||||
# return 0
|
||||
commands_to_execute = []
|
||||
last = 0
|
||||
command = None
|
||||
|
||||
for i in range(1,len(sys.argv)):
|
||||
if (sys.argv[i] == '--'):
|
||||
if command is not None:
|
||||
commands_to_execute.append((command,sys.argv[last+1:i] if last < i else []))
|
||||
command = None
|
||||
continue
|
||||
if command is None:
|
||||
try:
|
||||
command = commands.COMMANDS[sys.argv[i]]
|
||||
last = i
|
||||
except:
|
||||
logger.error("No such command \"{command}\"!".format(command=sys.argv[i]))
|
||||
return 4
|
||||
|
||||
if command is not None:
|
||||
commands_to_execute.append((command,sys.argv[last+1:] if (last+1) < len(sys.argv) else []))
|
||||
command=None
|
||||
elif not commands_to_execute:
|
||||
return commands.COMMANDS['synopsis'].execute([])
|
||||
|
||||
for cmd,argv in commands_to_execute:
|
||||
ec = cmd.execute(argv)
|
||||
if ec:
|
||||
logger.error('sgbackup aborted due to an error!')
|
||||
return ec
|
||||
|
||||
return 0
|
||||
|
||||
def gui_main():
|
||||
logger.debug("Running gui_main()")
|
||||
|
||||
Loading…
Reference in New Issue
Block a user