feat: channel flat file structure option (#258)#442
Conversation
- Add "Flat file structure (no video subfolders)" toggle to channel settings dialog and manual download settings dialog - Add skip_video_folder column to channels table via migration - Update yt-dlp command builder to omit video subfolder level in output/thumbnail paths when enabled - Update post-processor to move individual files instead of entire directories in flat mode - Update video deletion to remove matching files individually instead of deleting the parent directory - Update download executor cleanup to handle flat-mode in-progress files - Plumb skipVideoFolder through channel download grouper, download module, and API route with validation - Add skipVideoFolder to the manual download API override settings - Document flat file structure in folder structure, usage guide, and API integration docs - Add tests for channel settings toggle, download settings toggle, download module, and deletion module
📊 Test Coverage ReportBackend Coverage
Frontend Coverage
Coverage Requirements
Coverage report generated for commit c2408fc |
Code Review: feat: channel flat file structure option (#258)Overall this is a well-structured feature addition with good test coverage and documentation. The implementation is mostly clean, but there are a few issues worth addressing. Issues1. Potential race condition in flat-mode post-processor file movement (
|
PR Review: feat: channel flat file structure option (#258)Overall this is a well-structured feature with good test coverage. The end-to-end plumbing (DB migration → model → settings module → download builder → post-processor → deletion) is consistent and the new toggle is validated at the API boundary. A few issues worth addressing before merge: Bugs / Correctness1. JSDoc mismatch in The JSDoc documents a second // JSDoc says: @param {string} youtubeId - YouTube video ID
isFlat(filePath) { // <-- only one paramMinor, but it will confuse future readers. 2. Heuristic flat-mode detection is fragile in
isFlat(filePath) {
const parentDir = path.dirname(filePath);
return !isVideoDirectory(parentDir);
}This relies on a naming convention that could fail in edge cases:
A safer approach would be to store the mode on the 3. Stale The const hasOverride = useCustomSettings || allowRedownload ||
(mode === 'manual' && subfolderOverride !== null) ||
(mode === 'manual' && audioFormat !== null) ||
(mode === 'manual' && skipVideoFolder); // <-- always active once toggledThe test "triggers override when only skipVideoFolder is toggled (no custom settings)" explicitly tests this behavior, but the UX may surprise users: toggling flat mode on, then disabling custom settings, still sends 4. File-matching pattern in flat-mode cleanup is slightly over-broad Both fileName.includes(`[${youtubeId}]`) || fileName.includes(` - ${youtubeId}`)The second pattern ( Code Quality / Design5. Null vs. false inconsistency between channel and manual-download settings
This is functionally correct (null and false both mean "don't skip"), but the trichotomy (null / false / true) on the channel side vs. the boolean on the manual side makes the semantics harder to reason about at a glance. A comment in 6.
Minor / Nitpicks
Summary
The feature logic is sound and test coverage is solid. The main concern is the heuristic flat-mode detection in deletion (item 2) — if a video's |
- Filter post-processor file moves by video ID to avoid concurrent downloads potentially moving files belonging to other videos - Align skip_video_folder loading pattern with hasOwnProperty convention used by other nullable channel settings (e.g. default_rating) - Remove stale @param from isFlat() JSDoc - Add clarifying comments for fallback filename match pattern and skipVideoFolder grouping behavior - Add descriptive comment to skip_video_folder migration column
PR Review: Channel Flat File Structure OptionOverall this is a well-structured feature with good test coverage across both frontend and backend. The approach of plumbing skipVideoFolder through the entire stack is correct. A few issues worth addressing before merge: Bug: File move filter inconsistency in post-processorIn videoDownloadPostProcessFiles.js (flat mode file move), files are filtered by the bracketed form only. But in both videoDeletionModule.js and downloadExecutor.js, the matching pattern also includes a dash fallback. The post-processor only handles the bracketed form [ID], skipping the dash fallback - ID. If yt-dlp ever uses the dash naming pattern, those files would not be moved from temp to final in flat mode but could still be matched by the deletion code. The filter should be consistent across all three locations. Fragile: isFlat() heuristic in videoDeletionModuleThe isFlat() method infers flat vs. nested mode from the directory naming convention at deletion time. If skip_video_folder was changed on a channel after videos were downloaded (e.g., switched from flat to nested or vice versa), deletion will use the wrong strategy for existing records. Deleting the wrong set of files (or not deleting the right ones) is a significant correctness risk. Consider querying the video channel skip_video_folder setting from the database, or storing skip_video_folder on the Video record at download time. The heuristic approach also relies on the assumption that channel folder names never match the isVideoDirectory pattern, which may not hold universally. UX: Hidden skipVideoFolder state in DownloadSettingsDialogWhen a user enables custom settings and turns on the flat file structure toggle, then disables custom settings (which hides the toggle), and confirms the download, the download still uses skipVideoFolder: true (verified by the test triggers override when only skipVideoFolder is toggled). The state persists invisibly. Users who disable custom settings would reasonably expect all custom options to be cleared. At minimum this should be called out in the UI, or skipVideoFolder should be reset when useCustomSettings is toggled off. Potential race condition: concurrent flat-mode downloads for the same channelIn videoDownloadPostProcessFiles.js flat mode, files are moved individually from the temp channel directory to the final channel directory. If two downloads run concurrently for the same channel in flat mode, both post-processors will readdir the same temp directory and filter by their own [id]. This filtering should prevent them from moving each other's files, but there is still a window where one process could read files that the other is in the process of moving. The file-by-file move (vs. atomic directory move in nested mode) loses the atomicity guarantee. Worth documenting as a known caveat at minimum. Minor: hasActiveFilters() naming is slightly misleadingskipVideoFolder is a file storage structure setting, not a filter. It has been added to ChannelFilterConfig.hasActiveFilters() so that channels with different structural settings get separate download groups, which is correct behavior, but the method name no longer accurately reflects its purpose. Consider requiresOwnGroup() or adding a comment explaining the broader intent. Minor: Model definition missing commentThe migration adds a descriptive comment to the column (When true, videos are stored directly in the channel folder without per-video subfolders) but the corresponding field in server/models/channel.js omits it. For consistency with other fields in that model that have comments, it should be included. Minor: Formatting inconsistency in ChannelSettingsDialog.tsxThe skip_video_folder hasOwnProperty call has the null fallback on the same line as the ternary, while other settings in the same block have the null on its own line. Small style nit but inconsistent with the surrounding code. What is done well
|
- Rename hasFilters() to hasGroupingCriteria() to reflect that skipVideoFolder is a structural setting, not a download filter - skipVideoFolder appeared to persist in the confirm payload even after disabling custom settings; now explicitly sends false when custom settings are off - Remove skipVideoFolder from hasOverride trigger since it should not independently force an override without custom settings enabled - Broaden flat-mode file matching to also catch " - ID" filename patterns alongside the bracketed [ID] form - Add column comment to skip_video_folder model field
PR Review: Channel Flat File Structure OptionOverall this is a well-scoped feature with good test coverage and consistent patterns throughout the stack. A few issues worth addressing before merge. Bug:
|
|
Note, the callout from Claude about |
Uh oh!
There was an error while loading. Please reload this page.