class_name DeckManager extends RefCounted ## DeckManager - Handles deck persistence (save/load) const DECKS_DIR = "user://decks/" ## Save a deck to file ## Returns true on success static func save_deck(deck: Deck, filename: String) -> bool: # Ensure directory exists DirAccess.make_dir_recursive_absolute(DECKS_DIR) var path = DECKS_DIR + _sanitize_filename(filename) + ".json" var file = FileAccess.open(path, FileAccess.WRITE) if not file: push_error("DeckManager: Failed to open file for writing: " + path) return false var data = deck.to_dict() file.store_string(JSON.stringify(data, "\t")) file.close() return true ## Load a deck from file ## Returns null on failure static func load_deck(filename: String) -> Deck: var path = DECKS_DIR + _sanitize_filename(filename) + ".json" if not FileAccess.file_exists(path): push_error("DeckManager: File not found: " + path) return null var file = FileAccess.open(path, FileAccess.READ) if not file: push_error("DeckManager: Failed to open file for reading: " + path) return null var json_text = file.get_as_text() file.close() var json = JSON.new() var error = json.parse(json_text) if error != OK: push_error("DeckManager: JSON parse error: " + json.get_error_message()) return null var data = json.get_data() if not data is Dictionary: push_error("DeckManager: Invalid deck data format") return null var deck = Deck.new() deck.from_dict(data) return deck ## Delete a deck file ## Returns true on success static func delete_deck(filename: String) -> bool: var path = DECKS_DIR + _sanitize_filename(filename) + ".json" if not FileAccess.file_exists(path): return false var dir = DirAccess.open(DECKS_DIR) if dir: return dir.remove(_sanitize_filename(filename) + ".json") == OK return false ## List all saved decks ## Returns array of deck names (without .json extension) static func list_decks() -> Array[String]: var decks: Array[String] = [] # Ensure directory exists DirAccess.make_dir_recursive_absolute(DECKS_DIR) var dir = DirAccess.open(DECKS_DIR) if not dir: return decks dir.list_dir_begin() var filename = dir.get_next() while filename != "": if not dir.current_is_dir() and filename.ends_with(".json"): decks.append(filename.trim_suffix(".json")) filename = dir.get_next() dir.list_dir_end() decks.sort() return decks ## Check if a deck exists static func deck_exists(filename: String) -> bool: var path = DECKS_DIR + _sanitize_filename(filename) + ".json" return FileAccess.file_exists(path) ## Generate a unique deck name static func generate_unique_name(base_name: String = "New Deck") -> String: var name = base_name var counter = 1 while deck_exists(name): counter += 1 name = "%s %d" % [base_name, counter] return name ## Sanitize filename to prevent path traversal static func _sanitize_filename(filename: String) -> String: # Remove path separators and dangerous characters var sanitized = filename.replace("/", "_").replace("\\", "_") sanitized = sanitized.replace("..", "_").replace(":", "_") # Trim whitespace sanitized = sanitized.strip_edges() # Ensure not empty if sanitized.is_empty(): sanitized = "deck" return sanitized