From a116cec68d77bd6b8e0aff72c1b9d246c9ea37c6 Mon Sep 17 00:00:00 2001 From: "coderabbitai[bot]" <136622811+coderabbitai[bot]@users.noreply.github.com> Date: Wed, 22 Jul 2026 22:21:56 +0000 Subject: [PATCH] =?UTF-8?q?=F0=9F=93=9D=20Add=20docstrings=20to=20`agent/s?= =?UTF-8?q?ave-export-workflow`?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Docstrings generation was requested by @ChrisBeWithYou. * https://github.com/got-feedBack/feedBack-plugin-editor/pull/352#issuecomment-5041995965 The following files were modified: * `routes.py` * `src/create.js` * `src/file-ops.js` * `src/main.js` * `src/replace-audio.js` * `src/stem-tracks.js` * `tests/test_create_save_destination.py` --- routes.py | 37 ++++++++++++++++-- src/create.js | 16 ++++++-- src/file-ops.js | 55 ++++++++++++++++++++++++++- src/main.js | 5 ++- src/replace-audio.js | 4 ++ src/stem-tracks.js | 3 ++ tests/test_create_save_destination.py | 27 +++++++++++++ 7 files changed, 138 insertions(+), 9 deletions(-) diff --git a/routes.py b/routes.py index df00be3..3464df1 100644 --- a/routes.py +++ b/routes.py @@ -3955,6 +3955,16 @@ def _arr_to_data(arr, name): def setup(app, context): + """Initialize the editor plugin, register its routes, and manage editing sessions. + + Parameters: + app: FastAPI application to which the editor routes are registered. + context (dict): Host-provided configuration and service callbacks, including the + configuration directory and DLC directory resolver. + + Returns: + None + """ config_dir = context["config_dir"] get_dlc_dir = context["get_dlc_dir"] # Shared metadata cache (title/artist/…), populated by the core library @@ -4083,6 +4093,16 @@ async def close_editor_session(data: dict): @app.get("/api/plugins/editor/session/export") async def export_editor_session(session_id: str): + """ + Export the saved feedpak associated with an active editor session. + + Parameters: + session_id (str): Identifier of the active editor session. + + Returns: + FileResponse: The saved feedpak file. + JSONResponse: An error response when the session or saved feedpak is unavailable or invalid. + """ session = sessions.get(session_id) if not session: return JSONResponse({"error": "No active session"}, 404) @@ -8752,10 +8772,19 @@ async def build_song_endpoint(data: dict): {"error": f"invalid drum_parts[{_pi}].drum_tab: {_p_reason}"}, 400) def _build_sloppak(): - """Build a `.sloppak` for the create-mode session. - - Output filename is derived from title/artist for create-mode - sessions (no existing source filename to preserve). + """ + Builds a Sloppak package for the create-mode session. + + The package is staged in the session directory when the destination is + ``"session"``; otherwise, it is written to the configured DLC directory + using a title- and artist-derived filename. + + Raises: + RuntimeError: If the master audio, an additional audio track, or the DLC + directory required for a library build is unavailable. + + Returns: + Path: The path to the created package. """ resolved = _resolve_storage_url(audio_url) if audio_url else None audio_file = str(resolved) if resolved else "" diff --git a/src/create.js b/src/create.js index 9a367f2..14fbcf9 100644 --- a/src/create.js +++ b/src/create.js @@ -2368,7 +2368,10 @@ async function _editorDoBlankCreate() { // Apply a create-mode import result (from convert-gp OR import-xml-project) to // the editor and open it. The two import sources return the same shape -// (_song_to_dict + session_id + create_mode), so this is shared. +/** + * Applies an imported creation result to the editor as a new session. + * @param {Object} data - Backend result containing session metadata, arrangements, timing data, and optional audio, drum, and track-session data. + */ export async function editorApplyCreateResult(data) { // Persist the audio URL the backend actually used (e.g. GP8-extracted or the // project's uploaded mix) so editorBuild() reuses it on later builds. @@ -2538,7 +2541,9 @@ export function editorEofFilesSelected(input) { // EOF import path for editorDoCreate: upload the (optional) audio, POST the // arrangement XML(s) + audio URL + metadata, then open the result. Album art and -// the preview clip are baked later at Build (editorBuild), from their own inputs. +/** + * Imports EOF arrangement files into a new editor session, optionally including staged audio and metadata. + */ async function _editorDoEofCreate() { const status = document.getElementById('editor-create-status'); const btn = document.getElementById('editor-create-go'); @@ -2577,7 +2582,12 @@ async function _editorDoEofCreate() { // Packages the current create session. The default is an explicit library export; // Save passes destination:"session" so the backend prepares a temporary package -// for the user-selected file without publishing it. +/** + * Builds the current create-mode project for library export or session saving. + * @param {Object} [options={}] - Build options. + * @param {string} [options.destination] - Set to `session` to prepare a package for saving; other values export to the library. + * @return {boolean} `true` if the build succeeds, `false` otherwise. + */ export async function editorBuild(options = {}) { if (!S.sessionId || !S.createMode) return false; const destination = options.destination === 'session' ? 'session' : 'library'; diff --git a/src/file-ops.js b/src/file-ops.js index 552e9c1..f81a6e5 100644 --- a/src/file-ops.js +++ b/src/file-ops.js @@ -45,15 +45,31 @@ let externalSaveSessionId = null; let packLoadController = null; let packLoadGeneration = 0; +/** + * Determines whether an external save handle belongs to the specified session. + * @param {*} handleSessionId - The session identifier associated with the handle. + * @param {*} sessionId - The current session identifier. + * @return {boolean} `true` if the handle session identifier is present and matches the current session, `false` otherwise. + */ export function _externalSaveHandleIsCurrentPure(handleSessionId, sessionId) { return !!handleSessionId && handleSessionId === sessionId; } +/** + * Gets the external save handle associated with the active session. + * @return {FileSystemFileHandle|null} The current session's external save handle, or `null` when none is associated with the session. + */ function _currentExternalSaveHandle() { return _externalSaveHandleIsCurrentPure(externalSaveSessionId, S.sessionId) ? externalSaveHandle : null; } +/** + * Retrieves the exported feedpak for a session. + * @param {string} [sessionId=S.sessionId] - The session identifier to export. + * @returns {Promise} The exported feedpak data. + * @throws {Error} If the export request fails. + */ async function _exportBlob(sessionId = S.sessionId) { const resp = await fetch('/api/plugins/editor/session/export?session_id=' + encodeURIComponent(sessionId || '')); @@ -65,6 +81,11 @@ async function _exportBlob(sessionId = S.sessionId) { return resp.blob(); } +/** + * Writes the current session export to a selected file or starts a download. + * @param {FileSystemFileHandle|null} handle - Destination file handle, or `null` to download the export. + * @param {string} sessionId - Session identifier to export. + */ async function _writeExternalCopy(handle, sessionId = S.sessionId) { const blob = await _exportBlob(sessionId); if (handle) { @@ -86,6 +107,10 @@ async function _writeExternalCopy(handle, sessionId = S.sessionId) { setTimeout(() => URL.revokeObjectURL(url), 0); } +/** + * Mirrors the current session to its external save destination. + * @return {boolean} `true` if the copy succeeds or no destination is configured, `false` if the session changes or the copy fails. + */ async function _mirrorExternalCopy() { const sessionId = S.sessionId; const handle = _currentExternalSaveHandle(); @@ -137,6 +162,13 @@ export function _suggestedSaveNamePure(filename, title, artist) { return `${t || 'Untitled'}_${a || 'Unknown'}.feedpak`; } +/** + * Loads a feedpak into the editor and resets session-specific state. + * @param {string} filename - The feedpak filename to load. + * @param {Object} [options] - Load options. + * @param {boolean} [options.skipGuard=false] - Whether to skip the session-transition guard. + * @return {Promise} `true` when the feedpak is loaded successfully, `false` if loading fails, is canceled, or is superseded. + */ export async function loadCDLC(filename, options = {}) { if (!options.skipGuard && !(await guardSessionTransition('opening another feedpak'))) return false; const oldSessionId = S.sessionId; @@ -784,6 +816,12 @@ export function _buildSaveBody(forceFullSnapshot) { return _stripBeatsFromSaveBody(body); } +/** + * Saves the current editor session and optionally mirrors it to an external location. + * @param {Object} [options={}] - Save options. + * @param {boolean} [options.skipExternal=false] - Whether to skip writing an external copy. + * @return {boolean} `true` if the session is saved successfully, `false` otherwise. + */ export async function saveCDLC(options = {}) { if (!S.sessionId) return false; // A new document has no library file to overwrite. Package it inside the @@ -909,6 +947,11 @@ export const editorSaveAsSloppakConfirm = async () => { } }; +/** + * Saves the current session to a user-selected file or downloads a copy. + * + * @returns {boolean} `true` if the session is saved successfully to a selected file, `false` otherwise. + */ export async function editorSaveAs() { if (!S.sessionId) return false; const sessionId = S.sessionId; @@ -972,11 +1015,21 @@ export async function editorSaveAs() { // session must choose its first destination (native picker where available, // download fallback elsewhere); later saves reuse that handle. Loaded projects // already have a source path and save in place. Bind the handle to sessionId so -// starting another project can never overwrite the previous project's file. +/** + * Determines whether saving requires selecting an external destination. + * @param {boolean} hasHandle - Whether the current session has an external save handle. + * @param {boolean} _hasPickerApi - Whether the file picker API is available. + * @param {boolean} sessionNeedsLocation - Whether the session requires a save location. + * @returns {boolean} `true` if no external handle exists and the session requires a save location, `false` otherwise. + */ export function _saveShouldPickPure(hasHandle, _hasPickerApi, sessionNeedsLocation) { // Without the native API, editorSaveAs falls back to a browser download. return !hasHandle && !!sessionNeedsLocation; } +/** + * Saves the current editor session, prompting for an external destination when a new document requires one. + * @returns {boolean} `true` if the session is saved successfully, `false` otherwise. + */ export async function editorSave() { if (!S.sessionId) return false; const hasPicker = typeof window !== 'undefined' && typeof window.showSaveFilePicker === 'function'; diff --git a/src/main.js b/src/main.js index 1e8a409..72d171e 100644 --- a/src/main.js +++ b/src/main.js @@ -823,7 +823,10 @@ window.editorYtUrlInput = editorYtUrlInput; // The editor-owned Back button is a destructive document transition, just // like New/Open. Keep the screen mounted when the user cancels or when Save -// (including a cancelled first-save picker) does not complete durably. +/** + * Navigates from the editor to the home screen after confirming the session transition. + * @return {boolean} `true` if navigation is allowed, `false` if the transition is denied. + */ async function _editorLeaveToHome() { if (!(await guardSessionTransition('returning to the library'))) return false; if (typeof window.showScreen === 'function') window.showScreen('home'); diff --git a/src/replace-audio.js b/src/replace-audio.js index a6333ae..e4a7f33 100644 --- a/src/replace-audio.js +++ b/src/replace-audio.js @@ -74,6 +74,10 @@ async function _uploadReplaceAudio() { }); } +/** + * Replaces the session audio with a user-selected file or YouTube URL. + * @returns {Promise} + */ export async function editorApplyReplaceAudio() { if (!S.sessionId) return; const sessionId = S.sessionId; diff --git a/src/stem-tracks.js b/src/stem-tracks.js index 1665259..6dc1263 100644 --- a/src/stem-tracks.js +++ b/src/stem-tracks.js @@ -211,6 +211,9 @@ function _onPairChange(e) { })(); } +/** + * Imports selected audio files as stem tracks and updates the session with the resulting stems and pairings. + */ function _onImportPicked(e) { const input = e.target; if (!input || !input.files || !input.files.length || !S.sessionId) return; diff --git a/tests/test_create_save_destination.py b/tests/test_create_save_destination.py index 2066ae7..b4c6c16 100644 --- a/tests/test_create_save_destination.py +++ b/tests/test_create_save_destination.py @@ -17,6 +17,15 @@ def __init__(self): self.routes = {} def _register(self, path): + """ + Create a decorator that registers a route handler for the specified path. + + Parameters: + path: The route path associated with the handler. + + Returns: + A decorator that stores a handler in the app's route registry and returns it unchanged. + """ def decorator(fn): self.routes[path] = fn return fn @@ -31,6 +40,15 @@ def post(self, path, *args, **kwargs): @pytest.fixture() def build_routes(tmp_path): + """ + Create an isolated test environment with registered editor routes and a session workspace. + + Parameters: + tmp_path (Path): Temporary directory used for test files and configuration. + + Yields: + tuple: The imported routes module, fake application, DLC directory, and session directory. + """ added = [] if "lib" not in sys.modules: lib = types.ModuleType("lib") @@ -81,6 +99,15 @@ def build_routes(tmp_path): def _payload(**extra): + """ + Build the default JSON payload used by editor build route tests. + + Parameters: + extra (dict): Additional fields that override or extend the default payload. + + Returns: + dict: The resulting request payload. + """ data = { "session_id": "create", "audio_url": "/api/plugins/editor/cache/source.ogg",