mirror of
https://github.com/alexta69/metube.git
synced 2026-09-21 13:35:01 +00:00
Merge PR #1058: DEFAULT_FOLDER pre-selects a download folder
This commit is contained in:
@@ -62,6 +62,7 @@ Certain values can be set via environment variables, using the `-e` parameter on
|
|||||||
* __CUSTOM_DIRS__: Whether to enable downloading videos into custom directories within the __DOWNLOAD_DIR__ (or __AUDIO_DOWNLOAD_DIR__). When enabled, a dropdown appears next to the Add button to specify the download directory. Defaults to `true`.
|
* __CUSTOM_DIRS__: Whether to enable downloading videos into custom directories within the __DOWNLOAD_DIR__ (or __AUDIO_DOWNLOAD_DIR__). When enabled, a dropdown appears next to the Add button to specify the download directory. Defaults to `true`.
|
||||||
* __CREATE_CUSTOM_DIRS__: Whether to support automatically creating directories within the __DOWNLOAD_DIR__ (or __AUDIO_DOWNLOAD_DIR__) if they do not exist. When enabled, the download directory selector supports free-text input, and the specified directory will be created recursively. Defaults to `true`.
|
* __CREATE_CUSTOM_DIRS__: Whether to support automatically creating directories within the __DOWNLOAD_DIR__ (or __AUDIO_DOWNLOAD_DIR__) if they do not exist. When enabled, the download directory selector supports free-text input, and the specified directory will be created recursively. Defaults to `true`.
|
||||||
* __CUSTOM_DIRS_EXCLUDE_REGEX__: Regular expression to exclude some custom directories from the dropdown. Empty regex disables exclusion. Defaults to `(^|/)[.@].*$`, which means directories starting with `.` or `@`.
|
* __CUSTOM_DIRS_EXCLUDE_REGEX__: Regular expression to exclude some custom directories from the dropdown. Empty regex disables exclusion. Defaults to `(^|/)[.@].*$`, which means directories starting with `.` or `@`.
|
||||||
|
* __DEFAULT_FOLDER__: Custom directory to pre-select in the download folder field, relative to __DOWNLOAD_DIR__ (or __AUDIO_DOWNLOAD_DIR__), for when most downloads go to the same place. It is only a starting value — the field stays editable, so any other folder can still be picked per download. Requires __CUSTOM_DIRS__; ignored with a warning otherwise. Defaults to empty, i.e. the base download directory.
|
||||||
* __DOWNLOAD_DIRS_INDEXABLE__: If `true`, the download directories (__DOWNLOAD_DIR__ and __AUDIO_DOWNLOAD_DIR__) are indexable on the web server. Defaults to `false`.
|
* __DOWNLOAD_DIRS_INDEXABLE__: If `true`, the download directories (__DOWNLOAD_DIR__ and __AUDIO_DOWNLOAD_DIR__) are indexable on the web server. Defaults to `false`.
|
||||||
* __STATE_DIR__: Path to where MeTube will store its persistent state files (`queue.json`, `pending.json`, `completed.json`, `subscriptions.json`). Defaults to `/downloads/.metube` in the Docker image, and `.` otherwise.
|
* __STATE_DIR__: Path to where MeTube will store its persistent state files (`queue.json`, `pending.json`, `completed.json`, `subscriptions.json`). Defaults to `/downloads/.metube` in the Docker image, and `.` otherwise.
|
||||||
* __TEMP_DIR__: Path where intermediary download files will be saved. Defaults to `/downloads` in the Docker image, and `.` otherwise.
|
* __TEMP_DIR__: Path where intermediary download files will be saved. Defaults to `/downloads` in the Docker image, and `.` otherwise.
|
||||||
|
|||||||
+14
@@ -62,6 +62,7 @@ class Config:
|
|||||||
'CUSTOM_DIRS': 'true',
|
'CUSTOM_DIRS': 'true',
|
||||||
'CREATE_CUSTOM_DIRS': 'true',
|
'CREATE_CUSTOM_DIRS': 'true',
|
||||||
'CUSTOM_DIRS_EXCLUDE_REGEX': r'(^|/)[.@].*$',
|
'CUSTOM_DIRS_EXCLUDE_REGEX': r'(^|/)[.@].*$',
|
||||||
|
'DEFAULT_FOLDER': '',
|
||||||
'DELETE_FILE_ON_TRASHCAN': 'false',
|
'DELETE_FILE_ON_TRASHCAN': 'false',
|
||||||
'STATE_DIR': '.',
|
'STATE_DIR': '.',
|
||||||
'URL_PREFIX': '',
|
'URL_PREFIX': '',
|
||||||
@@ -127,6 +128,18 @@ class Config:
|
|||||||
if val and not val.endswith('/'):
|
if val and not val.endswith('/'):
|
||||||
setattr(self, attr, val + '/')
|
setattr(self, attr, val + '/')
|
||||||
|
|
||||||
|
# DEFAULT_FOLDER only pre-fills the form's folder field, which the UI
|
||||||
|
# does not even show without CUSTOM_DIRS. Sending one anyway would fail
|
||||||
|
# every download on the server's own folder check, so drop it and say so
|
||||||
|
# rather than leaving the user with a form that cannot submit.
|
||||||
|
self.DEFAULT_FOLDER = self.DEFAULT_FOLDER.strip().strip('/')
|
||||||
|
if self.DEFAULT_FOLDER and not self.CUSTOM_DIRS:
|
||||||
|
log.warning(
|
||||||
|
'Ignoring DEFAULT_FOLDER "%s" because CUSTOM_DIRS is not enabled',
|
||||||
|
self.DEFAULT_FOLDER,
|
||||||
|
)
|
||||||
|
self.DEFAULT_FOLDER = ''
|
||||||
|
|
||||||
# Convert relative addresses to absolute addresses to prevent the failure of file address comparison
|
# Convert relative addresses to absolute addresses to prevent the failure of file address comparison
|
||||||
if self.YTDL_OPTIONS_FILE and self.YTDL_OPTIONS_FILE.startswith('.'):
|
if self.YTDL_OPTIONS_FILE and self.YTDL_OPTIONS_FILE.startswith('.'):
|
||||||
self.YTDL_OPTIONS_FILE = str(Path(self.YTDL_OPTIONS_FILE).resolve())
|
self.YTDL_OPTIONS_FILE = str(Path(self.YTDL_OPTIONS_FILE).resolve())
|
||||||
@@ -187,6 +200,7 @@ class Config:
|
|||||||
_FRONTEND_KEYS = (
|
_FRONTEND_KEYS = (
|
||||||
'CUSTOM_DIRS',
|
'CUSTOM_DIRS',
|
||||||
'CREATE_CUSTOM_DIRS',
|
'CREATE_CUSTOM_DIRS',
|
||||||
|
'DEFAULT_FOLDER',
|
||||||
'OUTPUT_TEMPLATE_CHAPTER',
|
'OUTPUT_TEMPLATE_CHAPTER',
|
||||||
'PUBLIC_HOST_URL',
|
'PUBLIC_HOST_URL',
|
||||||
'PUBLIC_HOST_AUDIO_URL',
|
'PUBLIC_HOST_AUDIO_URL',
|
||||||
|
|||||||
@@ -115,6 +115,28 @@ class ConfigTests(unittest.TestCase):
|
|||||||
self.assertNotIn("HOST", safe)
|
self.assertNotIn("HOST", safe)
|
||||||
self.assertEqual(safe["ALLOW_YTDL_OPTIONS_OVERRIDES"], False)
|
self.assertEqual(safe["ALLOW_YTDL_OPTIONS_OVERRIDES"], False)
|
||||||
|
|
||||||
|
def test_default_folder_empty_by_default(self):
|
||||||
|
with patch.dict(os.environ, _base_env(), clear=False):
|
||||||
|
c = Config()
|
||||||
|
self.assertEqual(c.DEFAULT_FOLDER, "")
|
||||||
|
|
||||||
|
def test_default_folder_is_trimmed_and_reaches_the_frontend(self):
|
||||||
|
with patch.dict(os.environ, _base_env(DEFAULT_FOLDER=" /youtube/ "), clear=False):
|
||||||
|
c = Config()
|
||||||
|
self.assertEqual(c.DEFAULT_FOLDER, "youtube")
|
||||||
|
self.assertEqual(c.frontend_safe()["DEFAULT_FOLDER"], "youtube")
|
||||||
|
|
||||||
|
def test_default_folder_ignored_without_custom_dirs(self):
|
||||||
|
# The folder field is not shown at all without CUSTOM_DIRS, and sending
|
||||||
|
# a folder anyway is rejected by the download path check.
|
||||||
|
with patch.dict(
|
||||||
|
os.environ,
|
||||||
|
_base_env(DEFAULT_FOLDER="youtube", CUSTOM_DIRS="false"),
|
||||||
|
clear=False,
|
||||||
|
):
|
||||||
|
c = Config()
|
||||||
|
self.assertEqual(c.DEFAULT_FOLDER, "")
|
||||||
|
|
||||||
def test_allow_ytdl_options_overrides_boolean_loaded(self):
|
def test_allow_ytdl_options_overrides_boolean_loaded(self):
|
||||||
with patch.dict(os.environ, _base_env(ALLOW_YTDL_OPTIONS_OVERRIDES="true"), clear=False):
|
with patch.dict(os.environ, _base_env(ALLOW_YTDL_OPTIONS_OVERRIDES="true"), clear=False):
|
||||||
c = Config()
|
c = Config()
|
||||||
|
|||||||
@@ -149,6 +149,25 @@ describe('App', () => {
|
|||||||
expect(app).toBeTruthy();
|
expect(app).toBeTruthy();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('pre-fills the download folder from DEFAULT_FOLDER', () => {
|
||||||
|
const fixture = TestBed.createComponent(App);
|
||||||
|
fixture.detectChanges();
|
||||||
|
|
||||||
|
downloads.configurationChanged.next({ DEFAULT_FOLDER: 'youtube' });
|
||||||
|
|
||||||
|
expect(fixture.componentInstance.folder).toBe('youtube');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('does not overwrite a folder the user already typed', () => {
|
||||||
|
const fixture = TestBed.createComponent(App);
|
||||||
|
fixture.detectChanges();
|
||||||
|
fixture.componentInstance.folder = 'music';
|
||||||
|
|
||||||
|
downloads.configurationChanged.next({ DEFAULT_FOLDER: 'youtube' });
|
||||||
|
|
||||||
|
expect(fixture.componentInstance.folder).toBe('music');
|
||||||
|
});
|
||||||
|
|
||||||
it('asIsOrder returns a stable comparator value (insertion order preserved)', () => {
|
it('asIsOrder returns a stable comparator value (insertion order preserved)', () => {
|
||||||
const fixture = TestBed.createComponent(App);
|
const fixture = TestBed.createComponent(App);
|
||||||
const app = fixture.componentInstance;
|
const app = fixture.componentInstance;
|
||||||
|
|||||||
@@ -437,6 +437,12 @@ export class App implements AfterViewInit, OnInit, OnDestroy {
|
|||||||
if (!Number.isNaN(playlistItemLimit) && playlistItemLimit > 0) {
|
if (!Number.isNaN(playlistItemLimit) && playlistItemLimit > 0) {
|
||||||
this.playlistItemLimit = playlistItemLimit;
|
this.playlistItemLimit = playlistItemLimit;
|
||||||
}
|
}
|
||||||
|
// Pre-fill the download folder, unless the user has already typed one
|
||||||
|
// this session. The server drops DEFAULT_FOLDER when CUSTOM_DIRS is
|
||||||
|
// off, so there is nothing to guard against here.
|
||||||
|
if (!this.folder) {
|
||||||
|
this.folder = String(config['DEFAULT_FOLDER'] ?? '');
|
||||||
|
}
|
||||||
// Set chapter template from backend config if not already set by cookie
|
// Set chapter template from backend config if not already set by cookie
|
||||||
if (!this.chapterTemplate) {
|
if (!this.chapterTemplate) {
|
||||||
this.chapterTemplate = config['OUTPUT_TEMPLATE_CHAPTER'];
|
this.chapterTemplate = config['OUTPUT_TEMPLATE_CHAPTER'];
|
||||||
|
|||||||
Reference in New Issue
Block a user