Remove folders from config endpoint, add lazy-loaded /api/system/folders endpoint
Co-authored-by: jbatesy <51190172+jbatesy@users.noreply.github.com>
This commit is contained in:
parent
5fabd40a47
commit
66bfc4cf99
7 changed files with 98 additions and 9 deletions
23
API.md
23
API.md
|
|
@ -2446,10 +2446,6 @@ or an error:
|
|||
}
|
||||
],
|
||||
"paused": false,
|
||||
"folders": [
|
||||
{"name": "folder1", "path": "folder1"},
|
||||
{"name": "folder2", "path": "folder2"}
|
||||
],
|
||||
"history_count": 150,
|
||||
"queue": [
|
||||
{
|
||||
|
|
@ -2464,11 +2460,28 @@ or an error:
|
|||
|
||||
**Notes**:
|
||||
- This endpoint combines multiple data sources into a single response for efficient initialization
|
||||
- The `folders` array includes available download folders up to the configured depth limit
|
||||
- The `queue` array contains active download items
|
||||
|
||||
---
|
||||
|
||||
### GET /api/system/folders
|
||||
**Purpose**: Retrieve available download folders. This endpoint is designed to be called lazily when the user interacts with the download path input.
|
||||
|
||||
**Response**:
|
||||
```json
|
||||
{
|
||||
"folders": [
|
||||
"folder1",
|
||||
"folder2"
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
**Notes**:
|
||||
- The `folders` array includes available download folders up to the configured depth limit
|
||||
|
||||
---
|
||||
|
||||
### POST /api/system/terminal
|
||||
**Purpose**: Stream yt-dlp CLI output via Server-Sent Events (SSE). Requires `YTP_CONSOLE_ENABLED=true`.
|
||||
|
||||
|
|
|
|||
|
|
@ -49,12 +49,33 @@ async def system_config(queue: DownloadQueue, config: Config, encoder: Encoder)
|
|||
"presets": Presets.get_instance().get_all(),
|
||||
"dl_fields": await DLFields.get_instance().get_all_serialized(),
|
||||
"paused": queue.is_paused(),
|
||||
"history_count": await queue.done.get_total_count(),
|
||||
},
|
||||
status=web.HTTPOk.status_code,
|
||||
dumps=encoder.encode,
|
||||
)
|
||||
|
||||
|
||||
@route("GET", "api/system/folders", "system.folders")
|
||||
async def system_folders(config: Config, encoder: Encoder) -> Response:
|
||||
"""
|
||||
Get the list of folders available for downloads.
|
||||
|
||||
Args:
|
||||
config (Config): The config instance.
|
||||
encoder (Encoder): The encoder instance.
|
||||
|
||||
Returns:
|
||||
Response: The response object.
|
||||
|
||||
"""
|
||||
return web.json_response(
|
||||
data={
|
||||
"folders": list_folders(
|
||||
path=Path(config.download_path),
|
||||
base=Path(config.download_path),
|
||||
depth_limit=config.download_path_depth - 1,
|
||||
),
|
||||
"history_count": await queue.done.get_total_count(),
|
||||
},
|
||||
status=web.HTTPOk.status_code,
|
||||
dumps=encoder.encode,
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@ from unittest.mock import AsyncMock, MagicMock, patch
|
|||
from app.library.config import Config
|
||||
from app.library.encoder import Encoder
|
||||
from app.library.UpdateChecker import UpdateChecker
|
||||
from app.routes.api.system import check_updates, system_config
|
||||
from app.routes.api.system import check_updates, system_config, system_folders
|
||||
|
||||
|
||||
class TestSystemConfigEndpoint:
|
||||
|
|
@ -31,7 +31,6 @@ class TestSystemConfigEndpoint:
|
|||
with (
|
||||
patch("app.routes.api.system.Presets") as mock_presets_cls,
|
||||
patch("app.routes.api.system.DLFields") as mock_dl_fields_cls,
|
||||
patch("app.routes.api.system.list_folders", return_value=[]),
|
||||
):
|
||||
mock_presets_cls.get_instance.return_value.get_all.return_value = []
|
||||
mock_dl_fields_cls.get_instance.return_value.get_all_serialized = AsyncMock(return_value=[])
|
||||
|
|
@ -46,7 +45,43 @@ class TestSystemConfigEndpoint:
|
|||
assert "history_count" in body, "Configuration response should include history_count"
|
||||
assert "presets" in body, "Configuration response should include presets"
|
||||
assert "dl_fields" in body, "Configuration response should include dl_fields"
|
||||
assert "folders" in body, "Configuration response should include folders"
|
||||
assert "folders" not in body, "Configuration response should not include folders"
|
||||
|
||||
|
||||
class TestSystemFoldersEndpoint:
|
||||
"""Tests for the system folders endpoint."""
|
||||
|
||||
def setup_method(self):
|
||||
"""Reset singletons before each test."""
|
||||
Config._reset_singleton()
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_system_folders_returns_folders(self):
|
||||
"""Test that the folders endpoint returns the folders list."""
|
||||
config = Config.get_instance()
|
||||
encoder = Encoder()
|
||||
|
||||
with patch("app.routes.api.system.list_folders", return_value=["folder1", "subfolder/nested"]):
|
||||
response = await system_folders(config, encoder)
|
||||
|
||||
assert 200 == response.status
|
||||
body = json.loads(response.body.decode("utf-8"))
|
||||
assert "folders" in body, "Folders response should include folders"
|
||||
assert ["folder1", "subfolder/nested"] == body["folders"]
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_system_folders_returns_empty_list(self):
|
||||
"""Test that the folders endpoint returns an empty list when no folders exist."""
|
||||
config = Config.get_instance()
|
||||
encoder = Encoder()
|
||||
|
||||
with patch("app.routes.api.system.list_folders", return_value=[]):
|
||||
response = await system_folders(config, encoder)
|
||||
|
||||
assert 200 == response.status
|
||||
body = json.loads(response.body.decode("utf-8"))
|
||||
assert "folders" in body, "Folders response should include folders"
|
||||
assert [] == body["folders"]
|
||||
|
||||
|
||||
class TestCheckUpdatesEndpoint:
|
||||
|
|
|
|||
|
|
@ -128,6 +128,7 @@
|
|||
:placeholder="getDefault('folder', '/')"
|
||||
:disabled="addInProgress"
|
||||
list="folders"
|
||||
@focus="config.loadFolders()"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -189,6 +189,7 @@
|
|||
v-model="form.folder"
|
||||
:disabled="addInProgress"
|
||||
list="folders"
|
||||
@focus="config.loadFolders()"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -319,6 +319,7 @@
|
|||
v-model="form.folder"
|
||||
:disabled="addInProgress"
|
||||
list="folders"
|
||||
@focus="config.loadFolders()"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -95,6 +95,21 @@ export const useConfigStore = defineStore('config', () => {
|
|||
}
|
||||
};
|
||||
|
||||
const loadFolders = async () => {
|
||||
try {
|
||||
const resp = await request('/api/system/folders', { timeout: 10 });
|
||||
if (!resp.ok) {
|
||||
return;
|
||||
}
|
||||
const data = await resp.json();
|
||||
if (Array.isArray(data.folders)) {
|
||||
state.folders = data.folders;
|
||||
}
|
||||
} catch (e: any) {
|
||||
console.error(`Failed to load folders: ${e}`);
|
||||
}
|
||||
};
|
||||
|
||||
const add = (key: string, value: any) => {
|
||||
if (key.includes('.')) {
|
||||
const [parentKey, subKey] = key.split('.') as [keyof ConfigState, string];
|
||||
|
|
@ -209,6 +224,7 @@ export const useConfigStore = defineStore('config', () => {
|
|||
isLoaded,
|
||||
patch,
|
||||
loadConfig,
|
||||
loadFolders,
|
||||
} as { [K in keyof ConfigState]: Ref<ConfigState[K]> } & {
|
||||
add: typeof add;
|
||||
get: typeof get;
|
||||
|
|
@ -218,5 +234,6 @@ export const useConfigStore = defineStore('config', () => {
|
|||
patch: typeof patch;
|
||||
isLoaded: typeof isLoaded;
|
||||
loadConfig: typeof loadConfig;
|
||||
loadFolders: typeof loadFolders;
|
||||
};
|
||||
});
|
||||
|
|
|
|||
Loading…
Reference in a new issue