feat(pinchflat): Media Profiles — named download presets per Library source
A MediaProfile bundles { kind, quality, options, outputTemplate, extraArgs }
that a Source points at via Source.profileId, overriding the global defaults
field-by-field for that source's downloads.
Backend mirrors CommandTemplate: profiles.ts (createJsonStore, PROFILES_MAX,
isProfileLike + sanitize), CRUD IPC (profiles:list/save/remove) + preload +
useProfiles store; Source.profileId + sources:set-profile. Pure resolution
in @shared/profileResolve.ts (profile → global, per field; unit-tested).
Applied in the sources-store enqueueItems; a per-download collectionOutput-
Template override threads to buildArgs so a profile's outputTemplate wins.
UI: ProfileManager (mirrors TemplateManager) in a new Settings → Advanced →
Media profiles card, plus a per-source Profile picker in the Library source
detail. Both live-verified in the preview; 354 tests pass.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
@@ -7,7 +7,14 @@
|
||||
*/
|
||||
|
||||
import { isAbsolute } from 'path'
|
||||
import type { HistoryEntry, ErrorLogEntry, CommandTemplate, Source, MediaItem } from '@shared/ipc'
|
||||
import type {
|
||||
HistoryEntry,
|
||||
ErrorLogEntry,
|
||||
CommandTemplate,
|
||||
MediaProfile,
|
||||
Source,
|
||||
MediaItem
|
||||
} from '@shared/ipc'
|
||||
|
||||
// --- Path-traversal sanitization (audit S4) ---------------------------------
|
||||
|
||||
@@ -82,6 +89,17 @@ export function isTemplateLike(o: unknown): o is CommandTemplate {
|
||||
return typeof t.id === 'string' || typeof t.id === 'number'
|
||||
}
|
||||
|
||||
/**
|
||||
* A persisted profiles.json row must at least be an object carrying an id; the
|
||||
* rest is coerced by profiles.ts's sanitize(), so a weak shape check plus
|
||||
* normalisation there is enough (same approach as isTemplateLike).
|
||||
*/
|
||||
export function isProfileLike(o: unknown): o is MediaProfile {
|
||||
if (!o || typeof o !== 'object') return false
|
||||
const p = o as Record<string, unknown>
|
||||
return typeof p.id === 'string' || typeof p.id === 'number'
|
||||
}
|
||||
|
||||
/** A persisted sources.json row must have the right shape or it's dropped on read. */
|
||||
export function isValidSource(o: unknown): o is Source {
|
||||
if (!o || typeof o !== 'object') return false
|
||||
|
||||
Reference in New Issue
Block a user