2024-12-03 18:31:18 +00:00
|
|
|
import os
|
|
|
|
import time
|
2024-12-03 22:50:34 +00:00
|
|
|
import sys
|
2024-12-03 18:31:18 +00:00
|
|
|
from datetime import datetime
|
2024-12-06 17:12:31 +00:00
|
|
|
version = "0.4.2"
|
2024-12-03 22:50:34 +00:00
|
|
|
gitlab_url = "https://gitlab.com/python_projects3802849/ftl-save-manager/-/raw/main"
|
|
|
|
settings_location = "settings.txt"
|
2024-12-06 17:12:31 +00:00
|
|
|
dependencies = ("simple_term_menu", "requests")
|
|
|
|
|
|
|
|
if sys.platform != "linux":
|
|
|
|
print(f"{sys.platform} is not supported, only linux.")
|
|
|
|
exit()
|
|
|
|
|
|
|
|
def check_package_installed(package_name):
|
|
|
|
"""Check if a package is installed."""
|
|
|
|
try:
|
|
|
|
__import__(package_name)
|
|
|
|
return True
|
|
|
|
except ImportError as e:
|
|
|
|
print(f"{package_name} not found. Error: {e}")
|
|
|
|
return False
|
|
|
|
|
|
|
|
|
|
|
|
for item in dependencies:
|
|
|
|
if check_package_installed(item) == False:
|
|
|
|
print(f"One or more dependencies are missing, please ensure follwing packaged are installed:\nrequests\nsimple-term-menu")
|
|
|
|
exit()
|
|
|
|
|
|
|
|
|
|
|
|
import requests # todo: add check if installed, if not disable update function.
|
|
|
|
from simple_term_menu import TerminalMenu
|
2024-12-03 18:31:18 +00:00
|
|
|
|
|
|
|
class BackupApp:
|
|
|
|
""""Backup class, copy FTL continue file to backup location and restores it."""
|
|
|
|
def __init__(self, settings_file_path):
|
|
|
|
self.backup_files = {"1": "initiating"} # initiating variable
|
|
|
|
self.settings_path = settings_file_path
|
|
|
|
self.settings = self.read_settings()
|
|
|
|
|
|
|
|
if self.settings:
|
|
|
|
self.game_path = self.settings[0]
|
|
|
|
self.backup_path = self.settings[1]
|
|
|
|
else:
|
|
|
|
self.game_path, self.backup_path = self.ask_for_path()
|
|
|
|
self.write_settings([self.game_path, self.backup_path])
|
|
|
|
|
2024-12-03 22:50:34 +00:00
|
|
|
print(f"Game location: {self.game_path}.\nSave location: {self.backup_path}")
|
2024-12-03 18:31:18 +00:00
|
|
|
|
|
|
|
def read_settings(self):
|
|
|
|
"""Reading the settings file."""
|
|
|
|
if os.path.exists(self.settings_path):
|
|
|
|
with open(self.settings_path, "r") as f:
|
|
|
|
data = f.readlines()
|
|
|
|
for i in range(len(data)):
|
|
|
|
data[i] = data[i].strip() #stripping white space
|
|
|
|
return(data)
|
|
|
|
else:
|
|
|
|
print("settings file does not exit.")
|
2024-12-03 22:50:34 +00:00
|
|
|
return False
|
2024-12-03 18:31:18 +00:00
|
|
|
|
|
|
|
def ask_for_path(self):
|
|
|
|
"""Uses get_path to request path from the user."""
|
2024-12-06 17:12:31 +00:00
|
|
|
game_path = self.get_path("Game save path", True) #
|
2024-12-03 18:31:18 +00:00
|
|
|
backup_path = self.get_path("Backup path")
|
|
|
|
return(game_path, backup_path)
|
|
|
|
|
2024-12-06 17:12:31 +00:00
|
|
|
def get_path(self, str, ftl = False): # improvment: function that checks path or file
|
2024-12-03 18:31:18 +00:00
|
|
|
"""Get path from user and check if exists."""
|
|
|
|
while True:
|
|
|
|
path = input(f"please enter {str}: ")
|
|
|
|
if ftl:
|
|
|
|
profile_file = path + "/ae_prof.sav"
|
|
|
|
if os.path.isfile(profile_file):
|
|
|
|
return path
|
|
|
|
else:
|
|
|
|
print("Path is not valid, try again.")
|
|
|
|
continue
|
|
|
|
if os.path.exists(path):
|
|
|
|
return path
|
|
|
|
break
|
|
|
|
else:
|
|
|
|
print("Path not valid, try again.")
|
|
|
|
continue
|
|
|
|
return path
|
|
|
|
|
2024-12-06 17:12:31 +00:00
|
|
|
def write_settings(self, list): # improvment: more generic name, write_file or write_list_as_file
|
2024-12-03 18:31:18 +00:00
|
|
|
"""Write the settings (path)"""
|
|
|
|
with open(self.settings_path, "w") as f:
|
|
|
|
for item in list:
|
|
|
|
f.write(item + "\n")
|
|
|
|
print("write settings.")
|
|
|
|
|
|
|
|
def copy_file(self, src, dst):
|
|
|
|
""""Simply copies a file from a to b."""
|
|
|
|
if not os.path.isfile(src):
|
|
|
|
print(f"File to copy {src} does not exist.")
|
|
|
|
return False
|
|
|
|
os.system(f"cp {src} {dst}")
|
|
|
|
print(f"Wrote: {dst}")
|
|
|
|
|
|
|
|
def backup(self):
|
|
|
|
"""Copies the game file to the backup folder and adds date-time stamp."""
|
|
|
|
now = datetime.now()
|
|
|
|
formatted_now = now.strftime("%Y-%m-%d_%H-%M")
|
|
|
|
self.copy_file(f"{self.game_path}/continue.sav", f"{self.backup_path}/{formatted_now}.bkup")
|
2024-12-03 22:50:34 +00:00
|
|
|
time.sleep(0.5)
|
2024-12-03 18:31:18 +00:00
|
|
|
|
2024-12-06 17:12:31 +00:00
|
|
|
def restore_backup(self, file):
|
|
|
|
self.copy_file(f"{self.backup_path}/{file}", f"{self.game_path}/continue.sav")
|
2024-12-03 18:31:18 +00:00
|
|
|
|
2024-12-06 17:12:31 +00:00
|
|
|
def delete_backup_file(self): # improvment: here also, more generic, delete_file.
|
2024-12-03 18:31:18 +00:00
|
|
|
"""Deltes all .bkup files in the backup folder."""
|
|
|
|
if self.yes_no("Backup"):
|
|
|
|
for file in os.listdir(self.backup_path):
|
|
|
|
if file[-5:] == ".bkup":
|
|
|
|
try:
|
|
|
|
print(f"Deleting {file}.")
|
|
|
|
os.remove(f"{self.backup_path}/{file}")
|
2024-12-03 22:50:34 +00:00
|
|
|
time.sleep(0.3)
|
2024-12-03 18:31:18 +00:00
|
|
|
except Exception as e:
|
|
|
|
print(f"Failed to delete {file}. Reason: {e}.")
|
2024-12-03 22:50:34 +00:00
|
|
|
#time.sleep(2)
|
2024-12-03 18:31:18 +00:00
|
|
|
|
2024-12-06 17:12:31 +00:00
|
|
|
def yes_no(self, str): # improvment: more generic, take two string.
|
2024-12-03 18:31:18 +00:00
|
|
|
"""Ask user y/n question regaring deletion of a string."""
|
|
|
|
while True:
|
|
|
|
choice = input(f"Are you sure you want to delete all {str} files? (y/n)")
|
|
|
|
if choice == "y":
|
2024-12-03 22:50:34 +00:00
|
|
|
return True
|
2024-12-03 18:31:18 +00:00
|
|
|
elif choice == "n":
|
|
|
|
print(f"Deleting {str} files aborted")
|
2024-12-03 22:50:34 +00:00
|
|
|
#time.sleep(1)
|
|
|
|
return False
|
2024-12-03 18:31:18 +00:00
|
|
|
else:
|
|
|
|
print("Not a valid option, try again")
|
|
|
|
|
2024-12-06 17:12:31 +00:00
|
|
|
class Updater: # improvment: if dependency not aviable disable this
|
2024-12-03 22:50:34 +00:00
|
|
|
"""Update local version from gitlab repo."""
|
|
|
|
def __init__(self, version, update_url, local_file):
|
|
|
|
self.current_version = version
|
|
|
|
self.update_url = update_url
|
|
|
|
self.local_file = local_file
|
|
|
|
self.git = os.path.exists(".gitignore") # Checking if the program inside git env
|
|
|
|
|
|
|
|
def check_for_update(self):
|
|
|
|
"""Check if a new version is available."""
|
|
|
|
try:
|
|
|
|
print("Checking for updates...")
|
|
|
|
response = requests.get(f"{self.update_url}/latest_version.txt")
|
|
|
|
response.raise_for_status()
|
|
|
|
latest_version = response.text.strip()
|
|
|
|
|
|
|
|
if self.compare_SemVer(latest_version):
|
2024-12-06 17:12:31 +00:00
|
|
|
print(f"New version {latest_version} available online.\nCurrent version: {self.current_version}")
|
2024-12-03 22:50:34 +00:00
|
|
|
return True
|
|
|
|
else:
|
2024-12-06 17:12:31 +00:00
|
|
|
print(f"You are up-to-date, the latest version is: {latest_version}.")
|
|
|
|
time.sleep(2)
|
2024-12-03 22:50:34 +00:00
|
|
|
return False
|
|
|
|
except Exception as e:
|
|
|
|
print(f"Error checking for updates: {e}")
|
|
|
|
return False
|
|
|
|
|
|
|
|
def compare_SemVer(self, latest_version):
|
|
|
|
"""Compare two Semantic versioning strings."""
|
|
|
|
local_version = self.convert_str_list_to_int(self.current_version.split("."))
|
|
|
|
online_version = self.convert_str_list_to_int(latest_version.split("."))
|
|
|
|
return local_version < online_version
|
|
|
|
|
|
|
|
def convert_str_list_to_int(self, list_str):
|
|
|
|
"""Converts a list with strings to a list with intengers."""
|
|
|
|
return [int(i) for i in list_str]
|
|
|
|
|
2024-12-06 17:12:31 +00:00
|
|
|
def download_update(self, latest_version): # improvment: silly it takes the latest_version as variable
|
2024-12-03 22:50:34 +00:00
|
|
|
"""Download the new version and overwrite the current file."""
|
|
|
|
try:
|
|
|
|
print(f"Downloading version {latest_version}...")
|
|
|
|
response = requests.get(f"{self.update_url}/{self.local_file}")
|
|
|
|
response.raise_for_status()
|
|
|
|
with open(self.local_file, "wb") as file:
|
|
|
|
file.write(response.content)
|
|
|
|
print("Update downloaded.")
|
|
|
|
return True
|
|
|
|
except Exception as e:
|
|
|
|
print(f"Error downloading update: {e}")
|
|
|
|
return False
|
|
|
|
|
|
|
|
def restart_program(self):
|
|
|
|
"""Restart the current program."""
|
|
|
|
print("Restarting the program...")
|
|
|
|
os.execv(sys.executable, ["python"] + sys.argv)
|
|
|
|
|
|
|
|
def run_update(self):
|
|
|
|
"""Main method to check, update, and restart."""
|
|
|
|
if self.git:
|
|
|
|
print("Updating only works outside git env.")
|
2024-12-06 17:12:31 +00:00
|
|
|
time.sleep(2)
|
2024-12-03 22:50:34 +00:00
|
|
|
else:
|
|
|
|
latest_version = self.check_for_update()
|
|
|
|
if latest_version:
|
|
|
|
if self.yes_no("Do you want to update the program?"):
|
|
|
|
success = self.download_update(latest_version)
|
|
|
|
if success:
|
|
|
|
self.restart_program()
|
|
|
|
else:
|
|
|
|
print("Update cancelled")
|
|
|
|
return
|
|
|
|
|
2024-12-06 17:12:31 +00:00
|
|
|
def yes_no(self, str): # improvment: have two yes_no function, should merge them
|
2024-12-03 22:50:34 +00:00
|
|
|
"""Ask user y/n question"""
|
|
|
|
while True:
|
|
|
|
choice = input(f"{str} (y/n)")
|
|
|
|
if choice == "y":
|
|
|
|
return True
|
|
|
|
elif choice == "n":
|
|
|
|
return False
|
|
|
|
else:
|
|
|
|
print("Not a valid option, try again")
|
|
|
|
|
2024-12-06 17:12:31 +00:00
|
|
|
class TerminalUI:
|
|
|
|
def __init__(self, title, options):
|
|
|
|
self.main_menu_title = title
|
|
|
|
self.main_menu_options = options
|
|
|
|
self.main_menu_keys = list(self.main_menu_options.keys()) # terminal ui takes a list for the menu
|
|
|
|
self.main_menu = TerminalMenu(
|
|
|
|
menu_entries = self.main_menu_keys,
|
|
|
|
title = self.main_menu_title,
|
|
|
|
menu_cursor = "> ",
|
|
|
|
menu_cursor_style = ("fg_gray", "bold"),
|
|
|
|
menu_highlight_style = ("bg_gray", "fg_black"),
|
|
|
|
cycle_cursor = True,
|
|
|
|
clear_screen = True,
|
|
|
|
)
|
|
|
|
|
|
|
|
def show_main_menu(self):
|
|
|
|
while True:
|
|
|
|
selected_index = self.main_menu.show()
|
|
|
|
selected_key = None
|
|
|
|
if type(selected_index) == int: selected_key = self.main_menu_keys[selected_index]
|
|
|
|
|
|
|
|
if selected_index == None:
|
|
|
|
break
|
|
|
|
elif selected_key == "[r] Restore backup":
|
|
|
|
self.restore_file_menu(app.backup_path)
|
|
|
|
elif selected_key in self.main_menu_options:
|
|
|
|
self.main_menu_options[selected_key]()
|
|
|
|
|
|
|
|
def restore_file_menu(self, folder):
|
|
|
|
menu_options = list(self.list_files(folder))
|
|
|
|
folder_menu = TerminalMenu(menu_entries = menu_options,
|
|
|
|
title = f"Content of {folder} (Q or Esc to go back.)\n",
|
|
|
|
menu_cursor = "> ",
|
|
|
|
menu_cursor_style = ("fg_red", "bold"),
|
|
|
|
menu_highlight_style = ("bg_gray", "fg_black"),
|
|
|
|
cycle_cursor = True,
|
|
|
|
clear_screen = True)
|
|
|
|
|
|
|
|
menu_entry_index = folder_menu.show()
|
|
|
|
if menu_entry_index == None:
|
|
|
|
return
|
|
|
|
else:
|
|
|
|
app.restore_backup(menu_options[menu_entry_index])
|
|
|
|
|
|
|
|
def list_files(self, directory):
|
|
|
|
return (file for file in sorted(os.listdir(directory), reverse = True) if os.path.isfile(os.path.join(directory, file)) and file.endswith((".bkup")))
|
|
|
|
|
|
|
|
def yes_no_menu(self, message): # oh
|
|
|
|
menu_options = ["[y] yes", "[n] no"]
|
|
|
|
menu = TerminalMenu(menu_entries = menu_options,
|
|
|
|
title = f"{message}",
|
|
|
|
menu_cursor = "> ",
|
|
|
|
menu_cursor_style = ("fg_red", "bold"),
|
|
|
|
menu_highlight_style = ("bg_gray", "fg_black"),
|
|
|
|
cycle_cursor = True,
|
|
|
|
clear_screen = True)
|
|
|
|
menu_entry_index = menu.show()
|
|
|
|
if menu_entry_index == 0:
|
|
|
|
return True
|
|
|
|
elif menu_entry_index == 1:
|
|
|
|
return False
|
|
|
|
|
2024-12-03 22:50:34 +00:00
|
|
|
up = Updater(version, gitlab_url, "ftl-savemanager.py")
|
|
|
|
app = BackupApp(settings_location)
|
2024-12-03 18:31:18 +00:00
|
|
|
|
2024-12-06 17:12:31 +00:00
|
|
|
|
|
|
|
main_menu_options = {
|
|
|
|
"[b] Backup now": app.backup,
|
|
|
|
"[r] Restore backup": "restore_file_menu",
|
|
|
|
"[d] Delete backup files": app.delete_backup_file,
|
|
|
|
"[u] Look for update": up.run_update,
|
|
|
|
"[q] Quit": exit
|
2024-12-03 18:31:18 +00:00
|
|
|
}
|
|
|
|
|
2024-12-06 17:12:31 +00:00
|
|
|
title = f"ftl-savemanager v{version} (Press Q or Esc to quit).\n"
|
|
|
|
tui = TerminalUI(title, main_menu_options)
|
|
|
|
|
|
|
|
|
2024-12-03 18:31:18 +00:00
|
|
|
|
2024-12-06 17:12:31 +00:00
|
|
|
tui.show_main_menu()
|