New UI with simple-term-menu, now replicates original Visual Basic program

Finished replicating the original program, Features include: Backup, Restore, and Update.. But there is still more I want to add, such as better settings management, update, and more.
This commit is contained in:
Mr Finchum 2024-12-06 17:12:31 +00:00
parent 4286ea5ba3
commit 16a4e50f66
4 changed files with 183 additions and 101 deletions

View file

@ -1,11 +1,34 @@
import os
import time
import sys
import requests
from datetime import datetime
version = "0.3.1"
version = "0.4.2"
gitlab_url = "https://gitlab.com/python_projects3802849/ftl-save-manager/-/raw/main"
settings_location = "settings.txt"
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
class BackupApp:
""""Backup class, copy FTL continue file to backup location and restores it."""
@ -37,11 +60,11 @@ class BackupApp:
def ask_for_path(self):
"""Uses get_path to request path from the user."""
game_path = self.get_path("Game save path", True)
game_path = self.get_path("Game save path", True) #
backup_path = self.get_path("Backup path")
return(game_path, backup_path)
def get_path(self, str, ftl = False):
def get_path(self, str, ftl = False): # improvment: function that checks path or file
"""Get path from user and check if exists."""
while True:
path = input(f"please enter {str}: ")
@ -60,7 +83,7 @@ class BackupApp:
continue
return path
def write_settings(self, list):
def write_settings(self, list): # improvment: more generic name, write_file or write_list_as_file
"""Write the settings (path)"""
with open(self.settings_path, "w") as f:
for item in list:
@ -75,18 +98,6 @@ class BackupApp:
os.system(f"cp {src} {dst}")
print(f"Wrote: {dst}")
def list_backup(self):
""""List all files inside the backup folder."""
files = os.listdir(self.backup_path)
files.reverse() # this way the newest file is the top one.
self.backup_files.clear()
i = 1
for file in files:
new_item = {f"{i}": file}
print(f"[{i}] {file}")
self.backup_files.update(new_item)
i += 1
def backup(self):
"""Copies the game file to the backup folder and adds date-time stamp."""
now = datetime.now()
@ -94,26 +105,11 @@ class BackupApp:
self.copy_file(f"{self.game_path}/continue.sav", f"{self.backup_path}/{formatted_now}.bkup")
time.sleep(0.5)
def restore(self):
"""Copies a selected backup file to the game folder and renames it."""
print("Select a file to restore (back [b]):")
self.list_backup()
while True:
choice = input("Enter number (b = back): ")
if choice in self.backup_files:
self.copy_file(f"{self.backup_path}/{self.backup_files[choice]}", f"{self.game_path}/continue.sav")
print(f"{self.backup_files[choice]} restored.")
time.sleep(0.5)
break
elif choice == "b":
break
else:
print("not a valid option")
continue
def restore_backup(self, file):
self.copy_file(f"{self.backup_path}/{file}", f"{self.game_path}/continue.sav")
def delete_backup_file(self):
def delete_backup_file(self): # improvment: here also, more generic, delete_file.
"""Deltes all .bkup files in the backup folder."""
# Making a yes/no function would be cleaner
if self.yes_no("Backup"):
for file in os.listdir(self.backup_path):
if file[-5:] == ".bkup":
@ -125,7 +121,7 @@ class BackupApp:
print(f"Failed to delete {file}. Reason: {e}.")
#time.sleep(2)
def yes_no(self, str):
def yes_no(self, str): # improvment: more generic, take two string.
"""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)")
@ -138,32 +134,7 @@ class BackupApp:
else:
print("Not a valid option, try again")
class SimpleUI:
"""'UI for the backup class, simply displayes a menu and accepts letter to choice option."""
def __init__(self, options, names):
self.options = options
self.names = names
def menu(self):
""""Use dict to select an option (function)."""
self.print_menu()
while True:
choice = input("Select Option: ")
print("\n")
if choice in self.options:
self.options[choice]()
self.print_menu()
else:
print("Try again")
continue
def print_menu(self):
#print("\033[H\033[J", end="")
print("Menu options")
for key in self.options:
print(f"{key}: {self.names[key]}")
class Updater:
class Updater: # improvment: if dependency not aviable disable this
"""Update local version from gitlab repo."""
def __init__(self, version, update_url, local_file):
self.current_version = version
@ -180,10 +151,11 @@ class Updater:
latest_version = response.text.strip()
if self.compare_SemVer(latest_version):
print(f"New version {latest_version} available.\nCurrent version: {self.current_version}")
print(f"New version {latest_version} available online.\nCurrent version: {self.current_version}")
return True
else:
print("You are already on the latest version.")
print(f"You are up-to-date, the latest version is: {latest_version}.")
time.sleep(2)
return False
except Exception as e:
print(f"Error checking for updates: {e}")
@ -199,7 +171,7 @@ class Updater:
"""Converts a list with strings to a list with intengers."""
return [int(i) for i in list_str]
def download_update(self, latest_version):
def download_update(self, latest_version): # improvment: silly it takes the latest_version as variable
"""Download the new version and overwrite the current file."""
try:
print(f"Downloading version {latest_version}...")
@ -222,6 +194,7 @@ class Updater:
"""Main method to check, update, and restart."""
if self.git:
print("Updating only works outside git env.")
time.sleep(2)
else:
latest_version = self.check_for_update()
if latest_version:
@ -233,7 +206,7 @@ class Updater:
print("Update cancelled")
return
def yes_no(self, str):
def yes_no(self, str): # improvment: have two yes_no function, should merge them
"""Ask user y/n question"""
while True:
choice = input(f"{str} (y/n)")
@ -244,27 +217,83 @@ class Updater:
else:
print("Not a valid option, try again")
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
up = Updater(version, gitlab_url, "ftl-savemanager.py")
app = BackupApp(settings_location)
# Designing the menu options from the backupapp and the layout
menu_options = {
"b": app.backup,
"r": app.restore,
"d": app.delete_backup_file,
"u": up.run_update,
"q": exit
}
menu_names = {
"b": "Backup now",
"r": "Restore backup",
"d": "Delete backup files",
"u": "Update",
"q": "Exit"
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
}
# Loading the ui and the options
sui = SimpleUI(menu_options, menu_names)
title = f"ftl-savemanager v{version} (Press Q or Esc to quit).\n"
tui = TerminalUI(title, main_menu_options)
# starting the ui i.e. program
sui.menu()
tui.show_main_menu()