diff --git a/CHANGELOG.md b/CHANGELOG.md index e252faa..e92e895 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,17 +1,32 @@ # Changelog -## 0.3.1 -Fixed that online version always was newer. Changed some small layouts from the menu. +## 0.4.2 +- Cleaning: Removed old, unused code. +- The program now replicates the functionality of the original Visual Basic program but rewritten in Python with a terminal-based UI: + - Features include: Backup, Restore, and Update. -## 0.3.0 -Adding an update function. Checks the latest_version.txt file from gitlab and can pull the newest ftl-savemanager.py file and replace the local version. -If .gitignore file is found in the same folder update is denied, it also ask if the user wants to update before doing so. +### 0.4.1 +- Added a check to ensure the system is Linux and all required dependencies are installed; exits gracefully if not. +- Removed the old UI, as maintaining two versions is unnecessary. +- Updated file selection to use simple_term_menu for improved functionality. + +### 0.4.0 +- Introduced a new terminal UI using simple_term_menu for the main menu. +- This adds a new dependency (simple_term_menu) that must be installed for the UI to function. +- The program checks if the package is available. If it is, the new UI is loaded; otherwise, it falls back to the simpler version. + +## 0.3.1 +- Fixed an issue where the online version was always considered newer, regardless of the actual version numbers. +- Improved menu layout with minor changes for better usability. +### 0.3.0 +- Added an update function. The program now checks the latest_version.txt file from GitLab and can download and replace the local ftl-savemanager.py file with the latest version. +- If a .gitignore file is detected in the same folder, the update process is blocked. +- The program prompts the user for confirmation before performing the update. ## 0.2.1 -Switching to Semantic versioning, MAJOR.MINOR.PATCH - -## 0.2 -Reorgenized: The functions have been reorganized into two classes, Backup and UI. +- Adopted semantic versioning (MAJOR.MINOR.PATCH) for better version tracking. +### 0.2 +- Reorganized the codebase: functions have been split into two classes, Backup and UI. ## 0.1 -Initial working script +- Initial working script. diff --git a/README.md b/README.md index de0e2bf..c66750c 100644 --- a/README.md +++ b/README.md @@ -1,12 +1,50 @@ # FTL Save File Manager -Recreating the program I wrote in school in VS Basic but in Python. -It is a program enabling saving and loading in FTL (Faster Then Light) game. +## Description +It is a program enabling saving and loading*1 in FTL (Faster Then Light) game. +Backup works after a jump, restore only works while in game are in the main menu. It is considered as a project to learn and be more confident with Python as well as Git. +*1 ATM only the current game state, does not include the profile file (ae_prof.sav) which has information about total archiements and unlockes. -## How it works -It has a very simple menu after starting, with option such as backup, restore backup, delete backup files and quite the program. -Import to notice how to backup and restore. -Backup can be done after a jump, restore does only work when the game is in main menu. +## Status +As of version 0.4.2 has the program now replicates the functionality of the original Visual Basic I once wrote in high school but rewritten in Python with a terminal-based UI: + - Features include: Backup, Restore, and Update. + +# How it works +It uses TerminalMenu to show a simple gui in the terminal, you select the save game folder and location to backup saves. +Only work on linux and dependencies are requests and simple_terminal_menu + +# FTL Save File Manager +## Description +FTL Save File Manager is a utility designed to manage save files for the game FTL: Faster Than Light. + +- Backup: Automatically saves your progress after a jump. +- Restore: Allows you to restore save files, but only while in the game's main menu. +This project also serves as a learning experience to enhance Python programming skills and gain confidence with Git. + +**Note: Currently, only the active game state is managed. The profile file (ae_prof.sav), which contains achievements and unlocks, is not included in backups.** + +# Status +As of version 0.4.2, the program fully replicates the functionality of the original Visual Basic program I developed in high school. It has been rewritten in Python and now features a terminal-based UI: + +***Features:*** +- Backup +- Restore +- Update + +# How It Works +FTL Save File Manager uses simple-term-menu to provide a straightforward terminal-based interface. + +## Steps: +1. Select the save game folder. +2. Choose a backup location. +3. Manage your save files easily through the menu. + +## Requirements +Operating System: Linux + +Dependencies: +- requests +- simple-term-menu diff --git a/ftl-savemanager.py b/ftl-savemanager.py index ff5afe9..28f1f71 100644 --- a/ftl-savemanager.py +++ b/ftl-savemanager.py @@ -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() diff --git a/latest_version.txt b/latest_version.txt index 9e11b32..2b7c5ae 100644 --- a/latest_version.txt +++ b/latest_version.txt @@ -1 +1 @@ -0.3.1 +0.4.2