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,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.

View file

@ -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

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()

View file

@ -1 +1 @@
0.3.1
0.4.2