mirror of
https://github.com/Dictionarry-Hub/profilarr.git
synced 2026-01-31 06:40:50 +01:00
refactor: media management can now contain multiple configs for each setting
This commit is contained in:
@@ -39,6 +39,7 @@ import { migration as migration034 } from './migrations/034_add_sync_status.ts';
|
||||
import { migration as migration035 } from './migrations/035_add_job_skipped_status.ts';
|
||||
import { migration as migration036 } from './migrations/036_create_auth_tables.ts';
|
||||
import { migration as migration037 } from './migrations/037_add_session_metadata.ts';
|
||||
import { migration as migration038 } from './migrations/038_add_media_management_config_names.ts';
|
||||
|
||||
export interface Migration {
|
||||
version: number;
|
||||
@@ -296,7 +297,8 @@ export function loadMigrations(): Migration[] {
|
||||
migration034,
|
||||
migration035,
|
||||
migration036,
|
||||
migration037
|
||||
migration037,
|
||||
migration038
|
||||
];
|
||||
|
||||
// Sort by version number
|
||||
|
||||
@@ -0,0 +1,60 @@
|
||||
import type { Migration } from '../migrations.ts';
|
||||
|
||||
/**
|
||||
* Migration 038: Add config name columns to arr_sync_media_management
|
||||
*
|
||||
* With multi-config support, each database can have multiple naming, quality definitions,
|
||||
* and media settings configs. We need to store which specific config to use, not just
|
||||
* which database.
|
||||
*
|
||||
* Adds:
|
||||
* - naming_config_name: Name of the naming config to sync
|
||||
* - quality_definitions_config_name: Name of the quality definitions config to sync
|
||||
* - media_settings_config_name: Name of the media settings config to sync
|
||||
*/
|
||||
|
||||
export const migration: Migration = {
|
||||
version: 38,
|
||||
name: 'Add media management config names',
|
||||
|
||||
up: `
|
||||
ALTER TABLE arr_sync_media_management ADD COLUMN naming_config_name TEXT;
|
||||
ALTER TABLE arr_sync_media_management ADD COLUMN quality_definitions_config_name TEXT;
|
||||
ALTER TABLE arr_sync_media_management ADD COLUMN media_settings_config_name TEXT;
|
||||
`,
|
||||
|
||||
down: `
|
||||
-- SQLite doesn't support DROP COLUMN directly, so we recreate the table
|
||||
CREATE TABLE arr_sync_media_management_new (
|
||||
instance_id INTEGER PRIMARY KEY,
|
||||
naming_database_id INTEGER,
|
||||
quality_definitions_database_id INTEGER,
|
||||
media_settings_database_id INTEGER,
|
||||
trigger TEXT NOT NULL DEFAULT 'none',
|
||||
cron TEXT,
|
||||
should_sync INTEGER NOT NULL DEFAULT 0,
|
||||
next_run_at TEXT,
|
||||
sync_status TEXT NOT NULL DEFAULT 'idle',
|
||||
last_error TEXT,
|
||||
last_synced_at TEXT,
|
||||
FOREIGN KEY (instance_id) REFERENCES arr_instances(id) ON DELETE CASCADE,
|
||||
FOREIGN KEY (naming_database_id) REFERENCES database_instances(id) ON DELETE SET NULL,
|
||||
FOREIGN KEY (quality_definitions_database_id) REFERENCES database_instances(id) ON DELETE SET NULL,
|
||||
FOREIGN KEY (media_settings_database_id) REFERENCES database_instances(id) ON DELETE SET NULL
|
||||
);
|
||||
|
||||
INSERT INTO arr_sync_media_management_new (
|
||||
instance_id, naming_database_id, quality_definitions_database_id,
|
||||
media_settings_database_id, trigger, cron, should_sync, next_run_at,
|
||||
sync_status, last_error, last_synced_at
|
||||
)
|
||||
SELECT
|
||||
instance_id, naming_database_id, quality_definitions_database_id,
|
||||
media_settings_database_id, trigger, cron, should_sync, next_run_at,
|
||||
sync_status, last_error, last_synced_at
|
||||
FROM arr_sync_media_management;
|
||||
|
||||
DROP TABLE arr_sync_media_management;
|
||||
ALTER TABLE arr_sync_media_management_new RENAME TO arr_sync_media_management;
|
||||
`
|
||||
};
|
||||
@@ -29,8 +29,11 @@ export interface DelayProfilesSyncData {
|
||||
|
||||
export interface MediaManagementSyncData {
|
||||
namingDatabaseId: number | null;
|
||||
namingConfigName: string | null;
|
||||
qualityDefinitionsDatabaseId: number | null;
|
||||
qualityDefinitionsConfigName: string | null;
|
||||
mediaSettingsDatabaseId: number | null;
|
||||
mediaSettingsConfigName: string | null;
|
||||
trigger: SyncTrigger;
|
||||
cron: string | null;
|
||||
nextRunAt?: string | null;
|
||||
@@ -60,8 +63,11 @@ interface DelayProfileConfigRow {
|
||||
interface MediaManagementRow {
|
||||
instance_id: number;
|
||||
naming_database_id: number | null;
|
||||
naming_config_name: string | null;
|
||||
quality_definitions_database_id: number | null;
|
||||
quality_definitions_config_name: string | null;
|
||||
media_settings_database_id: number | null;
|
||||
media_settings_config_name: string | null;
|
||||
trigger: string;
|
||||
cron: string | null;
|
||||
}
|
||||
@@ -176,8 +182,11 @@ export const arrSyncQueries = {
|
||||
|
||||
return {
|
||||
namingDatabaseId: row?.naming_database_id ?? null,
|
||||
namingConfigName: row?.naming_config_name ?? null,
|
||||
qualityDefinitionsDatabaseId: row?.quality_definitions_database_id ?? null,
|
||||
qualityDefinitionsConfigName: row?.quality_definitions_config_name ?? null,
|
||||
mediaSettingsDatabaseId: row?.media_settings_database_id ?? null,
|
||||
mediaSettingsConfigName: row?.media_settings_config_name ?? null,
|
||||
trigger: (row?.trigger as SyncTrigger) ?? 'manual',
|
||||
cron: row?.cron ?? null
|
||||
};
|
||||
@@ -186,25 +195,34 @@ export const arrSyncQueries = {
|
||||
saveMediaManagementSync(instanceId: number, data: MediaManagementSyncData): void {
|
||||
db.execute(
|
||||
`INSERT INTO arr_sync_media_management
|
||||
(instance_id, naming_database_id, quality_definitions_database_id, media_settings_database_id, trigger, cron, next_run_at)
|
||||
VALUES (?, ?, ?, ?, ?, ?, ?)
|
||||
(instance_id, naming_database_id, naming_config_name, quality_definitions_database_id, quality_definitions_config_name, media_settings_database_id, media_settings_config_name, trigger, cron, next_run_at)
|
||||
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
||||
ON CONFLICT(instance_id) DO UPDATE SET
|
||||
naming_database_id = ?,
|
||||
naming_config_name = ?,
|
||||
quality_definitions_database_id = ?,
|
||||
quality_definitions_config_name = ?,
|
||||
media_settings_database_id = ?,
|
||||
media_settings_config_name = ?,
|
||||
trigger = ?,
|
||||
cron = ?,
|
||||
next_run_at = ?`,
|
||||
instanceId,
|
||||
data.namingDatabaseId,
|
||||
data.namingConfigName,
|
||||
data.qualityDefinitionsDatabaseId,
|
||||
data.qualityDefinitionsConfigName,
|
||||
data.mediaSettingsDatabaseId,
|
||||
data.mediaSettingsConfigName,
|
||||
data.trigger,
|
||||
data.cron,
|
||||
data.nextRunAt ?? null,
|
||||
data.namingDatabaseId,
|
||||
data.namingConfigName,
|
||||
data.qualityDefinitionsDatabaseId,
|
||||
data.qualityDefinitionsConfigName,
|
||||
data.mediaSettingsDatabaseId,
|
||||
data.mediaSettingsConfigName,
|
||||
data.trigger,
|
||||
data.cron,
|
||||
data.nextRunAt ?? null
|
||||
@@ -252,15 +270,15 @@ export const arrSyncQueries = {
|
||||
databaseId
|
||||
);
|
||||
db.execute(
|
||||
'UPDATE arr_sync_media_management SET naming_database_id = NULL WHERE naming_database_id = ?',
|
||||
'UPDATE arr_sync_media_management SET naming_database_id = NULL, naming_config_name = NULL WHERE naming_database_id = ?',
|
||||
databaseId
|
||||
);
|
||||
db.execute(
|
||||
'UPDATE arr_sync_media_management SET quality_definitions_database_id = NULL WHERE quality_definitions_database_id = ?',
|
||||
'UPDATE arr_sync_media_management SET quality_definitions_database_id = NULL, quality_definitions_config_name = NULL WHERE quality_definitions_database_id = ?',
|
||||
databaseId
|
||||
);
|
||||
db.execute(
|
||||
'UPDATE arr_sync_media_management SET media_settings_database_id = NULL WHERE media_settings_database_id = ?',
|
||||
'UPDATE arr_sync_media_management SET media_settings_database_id = NULL, media_settings_config_name = NULL WHERE media_settings_database_id = ?',
|
||||
databaseId
|
||||
);
|
||||
},
|
||||
|
||||
@@ -336,14 +336,17 @@ CREATE TABLE arr_sync_delay_profiles_config (
|
||||
-- ==============================================================================
|
||||
-- TABLE: arr_sync_media_management
|
||||
-- Purpose: Store media management sync configuration (one per instance)
|
||||
-- Migration: 015_create_arr_sync_tables.ts, 016_add_should_sync_flags.ts, 029_add_database_id_foreign_keys.ts, 034_add_sync_status.ts
|
||||
-- Migration: 015_create_arr_sync_tables.ts, 016_add_should_sync_flags.ts, 029_add_database_id_foreign_keys.ts, 034_add_sync_status.ts, 038_add_media_management_config_names.ts
|
||||
-- ==============================================================================
|
||||
|
||||
CREATE TABLE arr_sync_media_management (
|
||||
instance_id INTEGER PRIMARY KEY,
|
||||
naming_database_id INTEGER, -- Database to use for naming settings
|
||||
naming_config_name TEXT, -- Name of the naming config to sync (Migration 038)
|
||||
quality_definitions_database_id INTEGER, -- Database to use for quality definitions
|
||||
quality_definitions_config_name TEXT, -- Name of the quality definitions config to sync (Migration 038)
|
||||
media_settings_database_id INTEGER, -- Database to use for media settings
|
||||
media_settings_config_name TEXT, -- Name of the media settings config to sync (Migration 038)
|
||||
trigger TEXT NOT NULL DEFAULT 'none', -- 'none', 'manual', 'on_pull', 'on_change', 'schedule'
|
||||
cron TEXT, -- Cron expression for schedule trigger
|
||||
should_sync INTEGER NOT NULL DEFAULT 0, -- Flag for pending sync (Migration 016) - deprecated
|
||||
|
||||
118
src/lib/server/pcd/queries/mediaManagement/combined.ts
Normal file
118
src/lib/server/pcd/queries/mediaManagement/combined.ts
Normal file
@@ -0,0 +1,118 @@
|
||||
/**
|
||||
* Combined getters for media management configs
|
||||
* Provides backward compatibility for the syncer which expects combined objects
|
||||
*
|
||||
* NOTE: This returns the first available config for each type.
|
||||
* The syncer will need to be updated later to support selecting specific named configs.
|
||||
*/
|
||||
|
||||
import type { PCDCache } from '../../cache.ts';
|
||||
import type { MediaSettings, RadarrNaming, SonarrNaming } from '$lib/shared/mediaManagement.ts';
|
||||
import { getRadarrByName as getRadarrMediaSettings, getSonarrByName as getSonarrMediaSettings } from './media-settings/read.ts';
|
||||
import { getRadarrByName as getRadarrNaming, getSonarrByName as getSonarrNaming } from './naming/read.ts';
|
||||
import { getRadarrByName as getRadarrQualityDefs, getSonarrByName as getSonarrQualityDefs } from './quality-definitions/read.ts';
|
||||
import { list as listMediaSettings } from './media-settings/read.ts';
|
||||
import { list as listNaming } from './naming/read.ts';
|
||||
import { list as listQualityDefs } from './quality-definitions/read.ts';
|
||||
import type { QualityDefinitionEntry } from './quality-definitions/types.ts';
|
||||
|
||||
export interface QualityDefinition {
|
||||
quality_name: string;
|
||||
min_size: number;
|
||||
max_size: number | null;
|
||||
preferred_size: number | null;
|
||||
}
|
||||
|
||||
export interface RadarrCombined {
|
||||
mediaSettings: MediaSettings | null;
|
||||
naming: RadarrNaming | null;
|
||||
qualityDefinitions: QualityDefinition[];
|
||||
}
|
||||
|
||||
export interface SonarrCombined {
|
||||
mediaSettings: MediaSettings | null;
|
||||
naming: SonarrNaming | null;
|
||||
qualityDefinitions: QualityDefinition[];
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all Radarr media management configs (returns first available of each type)
|
||||
*/
|
||||
export async function getRadarr(cache: PCDCache): Promise<RadarrCombined> {
|
||||
// Get the first available media settings config
|
||||
const mediaSettingsList = await listMediaSettings(cache);
|
||||
const radarrMediaSettings = mediaSettingsList.find(c => c.arr_type === 'radarr');
|
||||
const mediaSettings = radarrMediaSettings
|
||||
? await getRadarrMediaSettings(cache, radarrMediaSettings.name)
|
||||
: null;
|
||||
|
||||
// Get the first available naming config
|
||||
const namingList = await listNaming(cache);
|
||||
const radarrNaming = namingList.find(c => c.arr_type === 'radarr');
|
||||
const naming = radarrNaming
|
||||
? await getRadarrNaming(cache, radarrNaming.name)
|
||||
: null;
|
||||
|
||||
// Get quality definitions (not yet refactored to multi-config)
|
||||
const qualityDefinitions = await getQualityDefinitions(cache, 'radarr');
|
||||
|
||||
return {
|
||||
mediaSettings,
|
||||
naming,
|
||||
qualityDefinitions
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all Sonarr media management configs (returns first available of each type)
|
||||
*/
|
||||
export async function getSonarr(cache: PCDCache): Promise<SonarrCombined> {
|
||||
// Get the first available media settings config
|
||||
const mediaSettingsList = await listMediaSettings(cache);
|
||||
const sonarrMediaSettings = mediaSettingsList.find(c => c.arr_type === 'sonarr');
|
||||
const mediaSettings = sonarrMediaSettings
|
||||
? await getSonarrMediaSettings(cache, sonarrMediaSettings.name)
|
||||
: null;
|
||||
|
||||
// Get the first available naming config
|
||||
const namingList = await listNaming(cache);
|
||||
const sonarrNaming = namingList.find(c => c.arr_type === 'sonarr');
|
||||
const naming = sonarrNaming
|
||||
? await getSonarrNaming(cache, sonarrNaming.name)
|
||||
: null;
|
||||
|
||||
// Get quality definitions (not yet refactored to multi-config)
|
||||
const qualityDefinitions = await getQualityDefinitions(cache, 'sonarr');
|
||||
|
||||
return {
|
||||
mediaSettings,
|
||||
naming,
|
||||
qualityDefinitions
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Get quality definitions for an arr type (returns first available config)
|
||||
*/
|
||||
async function getQualityDefinitions(cache: PCDCache, arrType: 'radarr' | 'sonarr'): Promise<QualityDefinition[]> {
|
||||
const qualityDefsList = await listQualityDefs(cache);
|
||||
const config = qualityDefsList.find(c => c.arr_type === arrType);
|
||||
|
||||
if (!config) {
|
||||
return [];
|
||||
}
|
||||
|
||||
const getByName = arrType === 'radarr' ? getRadarrQualityDefs : getSonarrQualityDefs;
|
||||
const fullConfig = await getByName(cache, config.name);
|
||||
|
||||
if (!fullConfig) {
|
||||
return [];
|
||||
}
|
||||
|
||||
return fullConfig.entries.map((entry: QualityDefinitionEntry) => ({
|
||||
quality_name: entry.quality_name,
|
||||
min_size: entry.min_size,
|
||||
max_size: entry.max_size,
|
||||
preferred_size: entry.preferred_size
|
||||
}));
|
||||
}
|
||||
@@ -1,229 +0,0 @@
|
||||
/**
|
||||
* Media Management get queries
|
||||
*/
|
||||
|
||||
import type { PCDCache } from '../../cache.ts';
|
||||
import type {
|
||||
MediaManagementData,
|
||||
RadarrMediaManagementData,
|
||||
SonarrMediaManagementData,
|
||||
QualityDefinition,
|
||||
RadarrNaming,
|
||||
SonarrNaming,
|
||||
MediaSettings,
|
||||
PropersRepacks
|
||||
} from './types.ts';
|
||||
import {
|
||||
colonReplacementFromDb,
|
||||
multiEpisodeStyleFromDb,
|
||||
radarrColonReplacementFromDb
|
||||
} from './types.ts';
|
||||
|
||||
/**
|
||||
* Get Radarr media management data
|
||||
*/
|
||||
export async function getRadarr(cache: PCDCache): Promise<RadarrMediaManagementData> {
|
||||
const db = cache.kb;
|
||||
|
||||
const [qualityDefinitions, naming, mediaSettings] = await Promise.all([
|
||||
getRadarrQualityDefinitions(db),
|
||||
getRadarrNaming(db),
|
||||
getRadarrMediaSettings(db)
|
||||
]);
|
||||
|
||||
return { qualityDefinitions, naming, mediaSettings };
|
||||
}
|
||||
|
||||
/**
|
||||
* Get Sonarr media management data
|
||||
*/
|
||||
export async function getSonarr(cache: PCDCache): Promise<SonarrMediaManagementData> {
|
||||
const db = cache.kb;
|
||||
|
||||
const [qualityDefinitions, naming, mediaSettings] = await Promise.all([
|
||||
getSonarrQualityDefinitions(db),
|
||||
getSonarrNaming(db),
|
||||
getSonarrMediaSettings(db)
|
||||
]);
|
||||
|
||||
return { qualityDefinitions, naming, mediaSettings };
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all media management data for a PCD database
|
||||
*/
|
||||
export async function get(cache: PCDCache): Promise<MediaManagementData> {
|
||||
const db = cache.kb;
|
||||
|
||||
// Fetch all data in parallel
|
||||
const [
|
||||
radarrQualityDefs,
|
||||
sonarrQualityDefs,
|
||||
radarrNaming,
|
||||
sonarrNaming,
|
||||
radarrMediaSettings,
|
||||
sonarrMediaSettings
|
||||
] = await Promise.all([
|
||||
getRadarrQualityDefinitions(db),
|
||||
getSonarrQualityDefinitions(db),
|
||||
getRadarrNaming(db),
|
||||
getSonarrNaming(db),
|
||||
getRadarrMediaSettings(db),
|
||||
getSonarrMediaSettings(db)
|
||||
]);
|
||||
|
||||
return {
|
||||
qualityDefinitions: {
|
||||
radarr: radarrQualityDefs,
|
||||
sonarr: sonarrQualityDefs
|
||||
},
|
||||
naming: {
|
||||
radarr: radarrNaming,
|
||||
sonarr: sonarrNaming
|
||||
},
|
||||
mediaSettings: {
|
||||
radarr: radarrMediaSettings,
|
||||
sonarr: sonarrMediaSettings
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Get Radarr quality definitions with quality names
|
||||
*/
|
||||
async function getRadarrQualityDefinitions(db: PCDCache['kb']): Promise<QualityDefinition[]> {
|
||||
const rows = await db
|
||||
.selectFrom('radarr_quality_definitions as rqd')
|
||||
.innerJoin('qualities as q', 'q.name', 'rqd.quality_name')
|
||||
.select(['rqd.quality_name', 'rqd.min_size', 'rqd.max_size', 'rqd.preferred_size'])
|
||||
.orderBy('rqd.quality_name')
|
||||
.execute();
|
||||
|
||||
return rows.map((row) => ({
|
||||
quality_name: row.quality_name,
|
||||
min_size: row.min_size,
|
||||
max_size: row.max_size,
|
||||
preferred_size: row.preferred_size
|
||||
}));
|
||||
}
|
||||
|
||||
/**
|
||||
* Get Sonarr quality definitions with quality names
|
||||
*/
|
||||
async function getSonarrQualityDefinitions(db: PCDCache['kb']): Promise<QualityDefinition[]> {
|
||||
const rows = await db
|
||||
.selectFrom('sonarr_quality_definitions as sqd')
|
||||
.innerJoin('qualities as q', 'q.name', 'sqd.quality_name')
|
||||
.select(['sqd.quality_name', 'sqd.min_size', 'sqd.max_size', 'sqd.preferred_size'])
|
||||
.orderBy('sqd.quality_name')
|
||||
.execute();
|
||||
|
||||
return rows.map((row) => ({
|
||||
quality_name: row.quality_name,
|
||||
min_size: row.min_size,
|
||||
max_size: row.max_size,
|
||||
preferred_size: row.preferred_size
|
||||
}));
|
||||
}
|
||||
|
||||
/**
|
||||
* Get Radarr naming settings
|
||||
*/
|
||||
async function getRadarrNaming(db: PCDCache['kb']): Promise<RadarrNaming | null> {
|
||||
const row = await db
|
||||
.selectFrom('radarr_naming')
|
||||
.select([
|
||||
'id',
|
||||
'rename',
|
||||
'movie_format',
|
||||
'movie_folder_format',
|
||||
'replace_illegal_characters',
|
||||
'colon_replacement_format'
|
||||
])
|
||||
.executeTakeFirst();
|
||||
|
||||
if (!row) return null;
|
||||
|
||||
return {
|
||||
id: row.id,
|
||||
rename: row.rename === 1,
|
||||
movie_format: row.movie_format,
|
||||
movie_folder_format: row.movie_folder_format,
|
||||
replace_illegal_characters: row.replace_illegal_characters === 1,
|
||||
colon_replacement_format: radarrColonReplacementFromDb(row.colon_replacement_format as number)
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Get Sonarr naming settings
|
||||
*/
|
||||
async function getSonarrNaming(db: PCDCache['kb']): Promise<SonarrNaming | null> {
|
||||
const row = await db
|
||||
.selectFrom('sonarr_naming')
|
||||
.select([
|
||||
'id',
|
||||
'rename',
|
||||
'standard_episode_format',
|
||||
'daily_episode_format',
|
||||
'anime_episode_format',
|
||||
'series_folder_format',
|
||||
'season_folder_format',
|
||||
'replace_illegal_characters',
|
||||
'colon_replacement_format',
|
||||
'custom_colon_replacement_format',
|
||||
'multi_episode_style'
|
||||
])
|
||||
.executeTakeFirst();
|
||||
|
||||
if (!row) return null;
|
||||
|
||||
return {
|
||||
id: row.id,
|
||||
rename: row.rename === 1,
|
||||
standard_episode_format: row.standard_episode_format,
|
||||
daily_episode_format: row.daily_episode_format,
|
||||
anime_episode_format: row.anime_episode_format,
|
||||
series_folder_format: row.series_folder_format,
|
||||
season_folder_format: row.season_folder_format,
|
||||
replace_illegal_characters: row.replace_illegal_characters === 1,
|
||||
colon_replacement_format: colonReplacementFromDb(row.colon_replacement_format as number),
|
||||
custom_colon_replacement_format: row.custom_colon_replacement_format,
|
||||
multi_episode_style: multiEpisodeStyleFromDb(row.multi_episode_style as number)
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Get Radarr media settings
|
||||
*/
|
||||
async function getRadarrMediaSettings(db: PCDCache['kb']): Promise<MediaSettings | null> {
|
||||
const row = await db
|
||||
.selectFrom('radarr_media_settings')
|
||||
.select(['id', 'propers_repacks', 'enable_media_info'])
|
||||
.executeTakeFirst();
|
||||
|
||||
if (!row) return null;
|
||||
|
||||
return {
|
||||
id: row.id,
|
||||
propers_repacks: row.propers_repacks as PropersRepacks,
|
||||
enable_media_info: row.enable_media_info === 1
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Get Sonarr media settings
|
||||
*/
|
||||
async function getSonarrMediaSettings(db: PCDCache['kb']): Promise<MediaSettings | null> {
|
||||
const row = await db
|
||||
.selectFrom('sonarr_media_settings')
|
||||
.select(['id', 'propers_repacks', 'enable_media_info'])
|
||||
.executeTakeFirst();
|
||||
|
||||
if (!row) return null;
|
||||
|
||||
return {
|
||||
id: row.id,
|
||||
propers_repacks: row.propers_repacks as PropersRepacks,
|
||||
enable_media_info: row.enable_media_info === 1
|
||||
};
|
||||
}
|
||||
@@ -1,45 +0,0 @@
|
||||
/**
|
||||
* Media Management queries
|
||||
*/
|
||||
|
||||
// Export all types
|
||||
export type {
|
||||
ArrType,
|
||||
QualityDefinition,
|
||||
QualityDefinitionsData,
|
||||
RadarrNaming,
|
||||
SonarrNaming,
|
||||
NamingData,
|
||||
MediaSettings,
|
||||
MediaSettingsData,
|
||||
MediaManagementData,
|
||||
RadarrMediaManagementData,
|
||||
SonarrMediaManagementData,
|
||||
PropersRepacks
|
||||
} from './types.ts';
|
||||
|
||||
// Export constants and helpers
|
||||
export { PROPERS_REPACKS_OPTIONS, getPropersRepacksLabel } from './types.ts';
|
||||
|
||||
// Export query functions
|
||||
export { get, getRadarr, getSonarr } from './get.ts';
|
||||
|
||||
// Export update functions
|
||||
export type {
|
||||
UpdateMediaSettingsInput,
|
||||
UpdateMediaSettingsOptions,
|
||||
UpdateSonarrNamingInput,
|
||||
UpdateSonarrNamingOptions,
|
||||
UpdateRadarrNamingInput,
|
||||
UpdateRadarrNamingOptions,
|
||||
UpdateQualityDefinitionInput,
|
||||
UpdateQualityDefinitionsOptions
|
||||
} from './update.ts';
|
||||
export {
|
||||
updateRadarrMediaSettings,
|
||||
updateSonarrMediaSettings,
|
||||
updateSonarrNaming,
|
||||
updateRadarrNaming,
|
||||
updateRadarrQualityDefinitions,
|
||||
updateSonarrQualityDefinitions
|
||||
} from './update.ts';
|
||||
@@ -0,0 +1,94 @@
|
||||
/**
|
||||
* Create media settings config operations
|
||||
*/
|
||||
|
||||
import type { PCDCache } from '../../../cache.ts';
|
||||
import { writeOperation, type OperationLayer } from '../../../writer.ts';
|
||||
import type { PropersRepacks } from '$lib/shared/mediaManagement.ts';
|
||||
|
||||
export interface CreateMediaSettingsInput {
|
||||
name: string;
|
||||
propersRepacks: PropersRepacks;
|
||||
enableMediaInfo: boolean;
|
||||
}
|
||||
|
||||
export interface CreateMediaSettingsOptions {
|
||||
databaseId: number;
|
||||
cache: PCDCache;
|
||||
layer: OperationLayer;
|
||||
input: CreateMediaSettingsInput;
|
||||
}
|
||||
|
||||
export async function createRadarrMediaSettings(options: CreateMediaSettingsOptions) {
|
||||
const { databaseId, cache, layer, input } = options;
|
||||
const db = cache.kb;
|
||||
|
||||
// Check if name already exists
|
||||
const existing = await db
|
||||
.selectFrom('radarr_media_settings')
|
||||
.where('name', '=', input.name)
|
||||
.select('name')
|
||||
.executeTakeFirst();
|
||||
|
||||
if (existing) {
|
||||
throw new Error(`A radarr media settings config with name "${input.name}" already exists`);
|
||||
}
|
||||
|
||||
const insertQuery = db
|
||||
.insertInto('radarr_media_settings')
|
||||
.values({
|
||||
name: input.name,
|
||||
propers_repacks: input.propersRepacks,
|
||||
enable_media_info: input.enableMediaInfo ? 1 : 0
|
||||
})
|
||||
.compile();
|
||||
|
||||
return writeOperation({
|
||||
databaseId,
|
||||
layer,
|
||||
description: `create-radarr-media-settings-${input.name}`,
|
||||
queries: [insertQuery],
|
||||
metadata: {
|
||||
operation: 'create',
|
||||
entity: 'radarr_media_settings',
|
||||
name: input.name
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
export async function createSonarrMediaSettings(options: CreateMediaSettingsOptions) {
|
||||
const { databaseId, cache, layer, input } = options;
|
||||
const db = cache.kb;
|
||||
|
||||
// Check if name already exists
|
||||
const existing = await db
|
||||
.selectFrom('sonarr_media_settings')
|
||||
.where('name', '=', input.name)
|
||||
.select('name')
|
||||
.executeTakeFirst();
|
||||
|
||||
if (existing) {
|
||||
throw new Error(`A sonarr media settings config with name "${input.name}" already exists`);
|
||||
}
|
||||
|
||||
const insertQuery = db
|
||||
.insertInto('sonarr_media_settings')
|
||||
.values({
|
||||
name: input.name,
|
||||
propers_repacks: input.propersRepacks,
|
||||
enable_media_info: input.enableMediaInfo ? 1 : 0
|
||||
})
|
||||
.compile();
|
||||
|
||||
return writeOperation({
|
||||
databaseId,
|
||||
layer,
|
||||
description: `create-sonarr-media-settings-${input.name}`,
|
||||
queries: [insertQuery],
|
||||
metadata: {
|
||||
operation: 'create',
|
||||
entity: 'sonarr_media_settings',
|
||||
name: input.name
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
/**
|
||||
* Media settings queries index
|
||||
*/
|
||||
|
||||
export * from './types.ts';
|
||||
export * from './read.ts';
|
||||
export * from './create.ts';
|
||||
export * from './update.ts';
|
||||
export * from './remove.ts';
|
||||
@@ -0,0 +1,82 @@
|
||||
/**
|
||||
* Media settings read operations (list and get)
|
||||
*/
|
||||
|
||||
import type { PCDCache } from '../../../cache.ts';
|
||||
import type { MediaSettings, PropersRepacks } from '$lib/shared/mediaManagement.ts';
|
||||
import type { MediaSettingsListItem } from './types.ts';
|
||||
|
||||
export async function list(cache: PCDCache): Promise<MediaSettingsListItem[]> {
|
||||
const db = cache.kb;
|
||||
|
||||
const [radarrRows, sonarrRows] = await Promise.all([
|
||||
db.selectFrom('radarr_media_settings').select(['name', 'propers_repacks', 'enable_media_info', 'updated_at']).execute(),
|
||||
db.selectFrom('sonarr_media_settings').select(['name', 'propers_repacks', 'enable_media_info', 'updated_at']).execute()
|
||||
]);
|
||||
|
||||
const items: MediaSettingsListItem[] = [];
|
||||
|
||||
for (const row of radarrRows) {
|
||||
items.push({
|
||||
name: row.name,
|
||||
arr_type: 'radarr',
|
||||
propers_repacks: row.propers_repacks,
|
||||
enable_media_info: row.enable_media_info === 1,
|
||||
updated_at: row.updated_at
|
||||
});
|
||||
}
|
||||
|
||||
for (const row of sonarrRows) {
|
||||
items.push({
|
||||
name: row.name,
|
||||
arr_type: 'sonarr',
|
||||
propers_repacks: row.propers_repacks,
|
||||
enable_media_info: row.enable_media_info === 1,
|
||||
updated_at: row.updated_at
|
||||
});
|
||||
}
|
||||
|
||||
return items;
|
||||
}
|
||||
|
||||
export async function getRadarrByName(
|
||||
cache: PCDCache,
|
||||
name: string
|
||||
): Promise<MediaSettings | null> {
|
||||
const db = cache.kb;
|
||||
|
||||
const row = await db
|
||||
.selectFrom('radarr_media_settings')
|
||||
.select(['name', 'propers_repacks', 'enable_media_info'])
|
||||
.where('name', '=', name)
|
||||
.executeTakeFirst();
|
||||
|
||||
if (!row) return null;
|
||||
|
||||
return {
|
||||
name: row.name,
|
||||
propers_repacks: row.propers_repacks as PropersRepacks,
|
||||
enable_media_info: row.enable_media_info === 1
|
||||
};
|
||||
}
|
||||
|
||||
export async function getSonarrByName(
|
||||
cache: PCDCache,
|
||||
name: string
|
||||
): Promise<MediaSettings | null> {
|
||||
const db = cache.kb;
|
||||
|
||||
const row = await db
|
||||
.selectFrom('sonarr_media_settings')
|
||||
.select(['name', 'propers_repacks', 'enable_media_info'])
|
||||
.where('name', '=', name)
|
||||
.executeTakeFirst();
|
||||
|
||||
if (!row) return null;
|
||||
|
||||
return {
|
||||
name: row.name,
|
||||
propers_repacks: row.propers_repacks as PropersRepacks,
|
||||
enable_media_info: row.enable_media_info === 1
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,57 @@
|
||||
/**
|
||||
* Remove media settings config operations
|
||||
*/
|
||||
|
||||
import type { PCDCache } from '../../../cache.ts';
|
||||
import { writeOperation, type OperationLayer } from '../../../writer.ts';
|
||||
|
||||
export interface RemoveMediaSettingsOptions {
|
||||
databaseId: number;
|
||||
cache: PCDCache;
|
||||
layer: OperationLayer;
|
||||
name: string;
|
||||
}
|
||||
|
||||
export async function removeRadarrMediaSettings(options: RemoveMediaSettingsOptions) {
|
||||
const { databaseId, cache, layer, name } = options;
|
||||
const db = cache.kb;
|
||||
|
||||
const deleteQuery = db
|
||||
.deleteFrom('radarr_media_settings')
|
||||
.where('name', '=', name)
|
||||
.compile();
|
||||
|
||||
return writeOperation({
|
||||
databaseId,
|
||||
layer,
|
||||
description: `delete-radarr-media-settings-${name}`,
|
||||
queries: [deleteQuery],
|
||||
metadata: {
|
||||
operation: 'delete',
|
||||
entity: 'radarr_media_settings',
|
||||
name
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
export async function removeSonarrMediaSettings(options: RemoveMediaSettingsOptions) {
|
||||
const { databaseId, cache, layer, name } = options;
|
||||
const db = cache.kb;
|
||||
|
||||
const deleteQuery = db
|
||||
.deleteFrom('sonarr_media_settings')
|
||||
.where('name', '=', name)
|
||||
.compile();
|
||||
|
||||
return writeOperation({
|
||||
databaseId,
|
||||
layer,
|
||||
description: `delete-sonarr-media-settings-${name}`,
|
||||
queries: [deleteQuery],
|
||||
metadata: {
|
||||
operation: 'delete',
|
||||
entity: 'sonarr_media_settings',
|
||||
name
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
/**
|
||||
* Media settings query-specific types
|
||||
*/
|
||||
|
||||
export type ArrType = 'radarr' | 'sonarr';
|
||||
|
||||
export interface MediaSettingsListItem {
|
||||
name: string;
|
||||
arr_type: ArrType;
|
||||
propers_repacks: string;
|
||||
enable_media_info: boolean;
|
||||
updated_at: string;
|
||||
}
|
||||
@@ -0,0 +1,101 @@
|
||||
/**
|
||||
* Update media settings config operations
|
||||
*/
|
||||
|
||||
import type { PCDCache } from '../../../cache.ts';
|
||||
import { writeOperation, type OperationLayer } from '../../../writer.ts';
|
||||
import type { PropersRepacks } from '$lib/shared/mediaManagement.ts';
|
||||
|
||||
export interface UpdateMediaSettingsInput {
|
||||
name: string;
|
||||
propersRepacks: PropersRepacks;
|
||||
enableMediaInfo: boolean;
|
||||
}
|
||||
|
||||
export interface UpdateMediaSettingsOptions {
|
||||
databaseId: number;
|
||||
cache: PCDCache;
|
||||
layer: OperationLayer;
|
||||
currentName: string;
|
||||
input: UpdateMediaSettingsInput;
|
||||
}
|
||||
|
||||
export async function updateRadarrMediaSettings(options: UpdateMediaSettingsOptions) {
|
||||
const { databaseId, cache, layer, currentName, input } = options;
|
||||
const db = cache.kb;
|
||||
|
||||
// If renaming, check if new name already exists
|
||||
if (input.name !== currentName) {
|
||||
const existing = await db
|
||||
.selectFrom('radarr_media_settings')
|
||||
.where('name', '=', input.name)
|
||||
.select('name')
|
||||
.executeTakeFirst();
|
||||
|
||||
if (existing) {
|
||||
throw new Error(`A radarr media settings config with name "${input.name}" already exists`);
|
||||
}
|
||||
}
|
||||
|
||||
const updateQuery = db
|
||||
.updateTable('radarr_media_settings')
|
||||
.set({
|
||||
name: input.name,
|
||||
propers_repacks: input.propersRepacks,
|
||||
enable_media_info: input.enableMediaInfo ? 1 : 0
|
||||
})
|
||||
.where('name', '=', currentName)
|
||||
.compile();
|
||||
|
||||
return writeOperation({
|
||||
databaseId,
|
||||
layer,
|
||||
description: `update-radarr-media-settings-${input.name}`,
|
||||
queries: [updateQuery],
|
||||
metadata: {
|
||||
operation: 'update',
|
||||
entity: 'radarr_media_settings',
|
||||
name: input.name
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
export async function updateSonarrMediaSettings(options: UpdateMediaSettingsOptions) {
|
||||
const { databaseId, cache, layer, currentName, input } = options;
|
||||
const db = cache.kb;
|
||||
|
||||
// If renaming, check if new name already exists
|
||||
if (input.name !== currentName) {
|
||||
const existing = await db
|
||||
.selectFrom('sonarr_media_settings')
|
||||
.where('name', '=', input.name)
|
||||
.select('name')
|
||||
.executeTakeFirst();
|
||||
|
||||
if (existing) {
|
||||
throw new Error(`A sonarr media settings config with name "${input.name}" already exists`);
|
||||
}
|
||||
}
|
||||
|
||||
const updateQuery = db
|
||||
.updateTable('sonarr_media_settings')
|
||||
.set({
|
||||
name: input.name,
|
||||
propers_repacks: input.propersRepacks,
|
||||
enable_media_info: input.enableMediaInfo ? 1 : 0
|
||||
})
|
||||
.where('name', '=', currentName)
|
||||
.compile();
|
||||
|
||||
return writeOperation({
|
||||
databaseId,
|
||||
layer,
|
||||
description: `update-sonarr-media-settings-${input.name}`,
|
||||
queries: [updateQuery],
|
||||
metadata: {
|
||||
operation: 'update',
|
||||
entity: 'sonarr_media_settings',
|
||||
name: input.name
|
||||
}
|
||||
});
|
||||
}
|
||||
130
src/lib/server/pcd/queries/mediaManagement/naming/create.ts
Normal file
130
src/lib/server/pcd/queries/mediaManagement/naming/create.ts
Normal file
@@ -0,0 +1,130 @@
|
||||
/**
|
||||
* Create naming config operations
|
||||
*/
|
||||
|
||||
import type { PCDCache } from '../../../cache.ts';
|
||||
import { writeOperation, type OperationLayer } from '../../../writer.ts';
|
||||
import type { RadarrColonReplacementFormat, ColonReplacementFormat, MultiEpisodeStyle } from '$lib/shared/mediaManagement.ts';
|
||||
import { radarrColonReplacementToDb, colonReplacementToDb, multiEpisodeStyleToDb } from '$lib/shared/mediaManagement.ts';
|
||||
|
||||
export interface CreateRadarrNamingInput {
|
||||
name: string;
|
||||
rename: boolean;
|
||||
movieFormat: string;
|
||||
movieFolderFormat: string;
|
||||
replaceIllegalCharacters: boolean;
|
||||
colonReplacementFormat: RadarrColonReplacementFormat;
|
||||
}
|
||||
|
||||
export interface CreateRadarrNamingOptions {
|
||||
databaseId: number;
|
||||
cache: PCDCache;
|
||||
layer: OperationLayer;
|
||||
input: CreateRadarrNamingInput;
|
||||
}
|
||||
|
||||
export async function createRadarrNaming(options: CreateRadarrNamingOptions) {
|
||||
const { databaseId, cache, layer, input } = options;
|
||||
const db = cache.kb;
|
||||
|
||||
// Check if name already exists
|
||||
const existing = await db
|
||||
.selectFrom('radarr_naming')
|
||||
.where('name', '=', input.name)
|
||||
.select('name')
|
||||
.executeTakeFirst();
|
||||
|
||||
if (existing) {
|
||||
throw new Error(`A radarr naming config with name "${input.name}" already exists`);
|
||||
}
|
||||
|
||||
const insertQuery = db
|
||||
.insertInto('radarr_naming')
|
||||
.values({
|
||||
name: input.name,
|
||||
rename: input.rename ? 1 : 0,
|
||||
movie_format: input.movieFormat,
|
||||
movie_folder_format: input.movieFolderFormat,
|
||||
replace_illegal_characters: input.replaceIllegalCharacters ? 1 : 0,
|
||||
colon_replacement_format: radarrColonReplacementToDb(input.colonReplacementFormat)
|
||||
})
|
||||
.compile();
|
||||
|
||||
return writeOperation({
|
||||
databaseId,
|
||||
layer,
|
||||
description: `create-radarr-naming-${input.name}`,
|
||||
queries: [insertQuery],
|
||||
metadata: {
|
||||
operation: 'create',
|
||||
entity: 'radarr_naming',
|
||||
name: input.name
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
export interface CreateSonarrNamingInput {
|
||||
name: string;
|
||||
rename: boolean;
|
||||
standardEpisodeFormat: string;
|
||||
dailyEpisodeFormat: string;
|
||||
animeEpisodeFormat: string;
|
||||
seriesFolderFormat: string;
|
||||
seasonFolderFormat: string;
|
||||
replaceIllegalCharacters: boolean;
|
||||
colonReplacementFormat: ColonReplacementFormat;
|
||||
customColonReplacementFormat: string | null;
|
||||
multiEpisodeStyle: MultiEpisodeStyle;
|
||||
}
|
||||
|
||||
export interface CreateSonarrNamingOptions {
|
||||
databaseId: number;
|
||||
cache: PCDCache;
|
||||
layer: OperationLayer;
|
||||
input: CreateSonarrNamingInput;
|
||||
}
|
||||
|
||||
export async function createSonarrNaming(options: CreateSonarrNamingOptions) {
|
||||
const { databaseId, cache, layer, input } = options;
|
||||
const db = cache.kb;
|
||||
|
||||
// Check if name already exists
|
||||
const existing = await db
|
||||
.selectFrom('sonarr_naming')
|
||||
.where('name', '=', input.name)
|
||||
.select('name')
|
||||
.executeTakeFirst();
|
||||
|
||||
if (existing) {
|
||||
throw new Error(`A sonarr naming config with name "${input.name}" already exists`);
|
||||
}
|
||||
|
||||
const insertQuery = db
|
||||
.insertInto('sonarr_naming')
|
||||
.values({
|
||||
name: input.name,
|
||||
rename: input.rename ? 1 : 0,
|
||||
standard_episode_format: input.standardEpisodeFormat,
|
||||
daily_episode_format: input.dailyEpisodeFormat,
|
||||
anime_episode_format: input.animeEpisodeFormat,
|
||||
series_folder_format: input.seriesFolderFormat,
|
||||
season_folder_format: input.seasonFolderFormat,
|
||||
replace_illegal_characters: input.replaceIllegalCharacters ? 1 : 0,
|
||||
colon_replacement_format: colonReplacementToDb(input.colonReplacementFormat),
|
||||
custom_colon_replacement_format: input.customColonReplacementFormat,
|
||||
multi_episode_style: multiEpisodeStyleToDb(input.multiEpisodeStyle)
|
||||
})
|
||||
.compile();
|
||||
|
||||
return writeOperation({
|
||||
databaseId,
|
||||
layer,
|
||||
description: `create-sonarr-naming-${input.name}`,
|
||||
queries: [insertQuery],
|
||||
metadata: {
|
||||
operation: 'create',
|
||||
entity: 'sonarr_naming',
|
||||
name: input.name
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
/**
|
||||
* Naming queries index
|
||||
*/
|
||||
|
||||
export * from './types.ts';
|
||||
export * from './read.ts';
|
||||
export * from './create.ts';
|
||||
export * from './update.ts';
|
||||
export * from './remove.ts';
|
||||
115
src/lib/server/pcd/queries/mediaManagement/naming/read.ts
Normal file
115
src/lib/server/pcd/queries/mediaManagement/naming/read.ts
Normal file
@@ -0,0 +1,115 @@
|
||||
/**
|
||||
* Naming read operations (list and get)
|
||||
*/
|
||||
|
||||
import type { PCDCache } from '../../../cache.ts';
|
||||
import type { RadarrNaming, SonarrNaming } from '$lib/shared/mediaManagement.ts';
|
||||
import {
|
||||
radarrColonReplacementFromDb,
|
||||
colonReplacementFromDb,
|
||||
multiEpisodeStyleFromDb
|
||||
} from '$lib/shared/mediaManagement.ts';
|
||||
import type { NamingListItem } from './types.ts';
|
||||
|
||||
export async function list(cache: PCDCache): Promise<NamingListItem[]> {
|
||||
const db = cache.kb;
|
||||
|
||||
const [radarrRows, sonarrRows] = await Promise.all([
|
||||
db.selectFrom('radarr_naming').select(['name', 'rename', 'updated_at']).execute(),
|
||||
db.selectFrom('sonarr_naming').select(['name', 'rename', 'updated_at']).execute()
|
||||
]);
|
||||
|
||||
const items: NamingListItem[] = [];
|
||||
|
||||
for (const row of radarrRows) {
|
||||
items.push({
|
||||
name: row.name,
|
||||
arr_type: 'radarr',
|
||||
rename: row.rename === 1,
|
||||
updated_at: row.updated_at
|
||||
});
|
||||
}
|
||||
|
||||
for (const row of sonarrRows) {
|
||||
items.push({
|
||||
name: row.name,
|
||||
arr_type: 'sonarr',
|
||||
rename: row.rename === 1,
|
||||
updated_at: row.updated_at
|
||||
});
|
||||
}
|
||||
|
||||
return items;
|
||||
}
|
||||
|
||||
export async function getRadarrByName(
|
||||
cache: PCDCache,
|
||||
name: string
|
||||
): Promise<RadarrNaming | null> {
|
||||
const db = cache.kb;
|
||||
|
||||
const row = await db
|
||||
.selectFrom('radarr_naming')
|
||||
.select([
|
||||
'name',
|
||||
'rename',
|
||||
'movie_format',
|
||||
'movie_folder_format',
|
||||
'replace_illegal_characters',
|
||||
'colon_replacement_format'
|
||||
])
|
||||
.where('name', '=', name)
|
||||
.executeTakeFirst();
|
||||
|
||||
if (!row) return null;
|
||||
|
||||
return {
|
||||
name: row.name,
|
||||
rename: row.rename === 1,
|
||||
movie_format: row.movie_format,
|
||||
movie_folder_format: row.movie_folder_format,
|
||||
replace_illegal_characters: row.replace_illegal_characters === 1,
|
||||
colon_replacement_format: radarrColonReplacementFromDb(row.colon_replacement_format as number)
|
||||
};
|
||||
}
|
||||
|
||||
export async function getSonarrByName(
|
||||
cache: PCDCache,
|
||||
name: string
|
||||
): Promise<SonarrNaming | null> {
|
||||
const db = cache.kb;
|
||||
|
||||
const row = await db
|
||||
.selectFrom('sonarr_naming')
|
||||
.select([
|
||||
'name',
|
||||
'rename',
|
||||
'standard_episode_format',
|
||||
'daily_episode_format',
|
||||
'anime_episode_format',
|
||||
'series_folder_format',
|
||||
'season_folder_format',
|
||||
'replace_illegal_characters',
|
||||
'colon_replacement_format',
|
||||
'custom_colon_replacement_format',
|
||||
'multi_episode_style'
|
||||
])
|
||||
.where('name', '=', name)
|
||||
.executeTakeFirst();
|
||||
|
||||
if (!row) return null;
|
||||
|
||||
return {
|
||||
name: row.name,
|
||||
rename: row.rename === 1,
|
||||
standard_episode_format: row.standard_episode_format,
|
||||
daily_episode_format: row.daily_episode_format,
|
||||
anime_episode_format: row.anime_episode_format,
|
||||
series_folder_format: row.series_folder_format,
|
||||
season_folder_format: row.season_folder_format,
|
||||
replace_illegal_characters: row.replace_illegal_characters === 1,
|
||||
colon_replacement_format: colonReplacementFromDb(row.colon_replacement_format as number),
|
||||
custom_colon_replacement_format: row.custom_colon_replacement_format,
|
||||
multi_episode_style: multiEpisodeStyleFromDb(row.multi_episode_style as number)
|
||||
};
|
||||
}
|
||||
64
src/lib/server/pcd/queries/mediaManagement/naming/remove.ts
Normal file
64
src/lib/server/pcd/queries/mediaManagement/naming/remove.ts
Normal file
@@ -0,0 +1,64 @@
|
||||
/**
|
||||
* Remove naming config operations
|
||||
*/
|
||||
|
||||
import type { PCDCache } from '../../../cache.ts';
|
||||
import { writeOperation, type OperationLayer } from '../../../writer.ts';
|
||||
|
||||
export interface RemoveRadarrNamingOptions {
|
||||
databaseId: number;
|
||||
cache: PCDCache;
|
||||
layer: OperationLayer;
|
||||
name: string;
|
||||
}
|
||||
|
||||
export async function removeRadarrNaming(options: RemoveRadarrNamingOptions) {
|
||||
const { databaseId, cache, layer, name } = options;
|
||||
const db = cache.kb;
|
||||
|
||||
const deleteQuery = db
|
||||
.deleteFrom('radarr_naming')
|
||||
.where('name', '=', name)
|
||||
.compile();
|
||||
|
||||
return writeOperation({
|
||||
databaseId,
|
||||
layer,
|
||||
description: `delete-radarr-naming-${name}`,
|
||||
queries: [deleteQuery],
|
||||
metadata: {
|
||||
operation: 'delete',
|
||||
entity: 'radarr_naming',
|
||||
name
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
export interface RemoveSonarrNamingOptions {
|
||||
databaseId: number;
|
||||
cache: PCDCache;
|
||||
layer: OperationLayer;
|
||||
name: string;
|
||||
}
|
||||
|
||||
export async function removeSonarrNaming(options: RemoveSonarrNamingOptions) {
|
||||
const { databaseId, cache, layer, name } = options;
|
||||
const db = cache.kb;
|
||||
|
||||
const deleteQuery = db
|
||||
.deleteFrom('sonarr_naming')
|
||||
.where('name', '=', name)
|
||||
.compile();
|
||||
|
||||
return writeOperation({
|
||||
databaseId,
|
||||
layer,
|
||||
description: `delete-sonarr-naming-${name}`,
|
||||
queries: [deleteQuery],
|
||||
metadata: {
|
||||
operation: 'delete',
|
||||
entity: 'sonarr_naming',
|
||||
name
|
||||
}
|
||||
});
|
||||
}
|
||||
12
src/lib/server/pcd/queries/mediaManagement/naming/types.ts
Normal file
12
src/lib/server/pcd/queries/mediaManagement/naming/types.ts
Normal file
@@ -0,0 +1,12 @@
|
||||
/**
|
||||
* Naming query-specific types
|
||||
*/
|
||||
|
||||
export type ArrType = 'radarr' | 'sonarr';
|
||||
|
||||
export interface NamingListItem {
|
||||
name: string;
|
||||
arr_type: ArrType;
|
||||
rename: boolean;
|
||||
updated_at: string;
|
||||
}
|
||||
138
src/lib/server/pcd/queries/mediaManagement/naming/update.ts
Normal file
138
src/lib/server/pcd/queries/mediaManagement/naming/update.ts
Normal file
@@ -0,0 +1,138 @@
|
||||
/**
|
||||
* Update naming config operations
|
||||
*/
|
||||
|
||||
import type { PCDCache } from '../../../cache.ts';
|
||||
import { writeOperation, type OperationLayer } from '../../../writer.ts';
|
||||
import type { RadarrColonReplacementFormat, ColonReplacementFormat, MultiEpisodeStyle } from '$lib/shared/mediaManagement.ts';
|
||||
import { radarrColonReplacementToDb, colonReplacementToDb, multiEpisodeStyleToDb } from '$lib/shared/mediaManagement.ts';
|
||||
|
||||
export interface UpdateRadarrNamingInput {
|
||||
name: string;
|
||||
rename: boolean;
|
||||
movieFormat: string;
|
||||
movieFolderFormat: string;
|
||||
replaceIllegalCharacters: boolean;
|
||||
colonReplacementFormat: RadarrColonReplacementFormat;
|
||||
}
|
||||
|
||||
export interface UpdateRadarrNamingOptions {
|
||||
databaseId: number;
|
||||
cache: PCDCache;
|
||||
layer: OperationLayer;
|
||||
currentName: string;
|
||||
input: UpdateRadarrNamingInput;
|
||||
}
|
||||
|
||||
export async function updateRadarrNaming(options: UpdateRadarrNamingOptions) {
|
||||
const { databaseId, cache, layer, currentName, input } = options;
|
||||
const db = cache.kb;
|
||||
|
||||
// If renaming, check if new name already exists
|
||||
if (input.name !== currentName) {
|
||||
const existing = await db
|
||||
.selectFrom('radarr_naming')
|
||||
.where('name', '=', input.name)
|
||||
.select('name')
|
||||
.executeTakeFirst();
|
||||
|
||||
if (existing) {
|
||||
throw new Error(`A radarr naming config with name "${input.name}" already exists`);
|
||||
}
|
||||
}
|
||||
|
||||
const updateQuery = db
|
||||
.updateTable('radarr_naming')
|
||||
.set({
|
||||
name: input.name,
|
||||
rename: input.rename ? 1 : 0,
|
||||
movie_format: input.movieFormat,
|
||||
movie_folder_format: input.movieFolderFormat,
|
||||
replace_illegal_characters: input.replaceIllegalCharacters ? 1 : 0,
|
||||
colon_replacement_format: radarrColonReplacementToDb(input.colonReplacementFormat)
|
||||
})
|
||||
.where('name', '=', currentName)
|
||||
.compile();
|
||||
|
||||
return writeOperation({
|
||||
databaseId,
|
||||
layer,
|
||||
description: `update-radarr-naming-${input.name}`,
|
||||
queries: [updateQuery],
|
||||
metadata: {
|
||||
operation: 'update',
|
||||
entity: 'radarr_naming',
|
||||
name: input.name
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
export interface UpdateSonarrNamingInput {
|
||||
name: string;
|
||||
rename: boolean;
|
||||
standardEpisodeFormat: string;
|
||||
dailyEpisodeFormat: string;
|
||||
animeEpisodeFormat: string;
|
||||
seriesFolderFormat: string;
|
||||
seasonFolderFormat: string;
|
||||
replaceIllegalCharacters: boolean;
|
||||
colonReplacementFormat: ColonReplacementFormat;
|
||||
customColonReplacementFormat: string | null;
|
||||
multiEpisodeStyle: MultiEpisodeStyle;
|
||||
}
|
||||
|
||||
export interface UpdateSonarrNamingOptions {
|
||||
databaseId: number;
|
||||
cache: PCDCache;
|
||||
layer: OperationLayer;
|
||||
currentName: string;
|
||||
input: UpdateSonarrNamingInput;
|
||||
}
|
||||
|
||||
export async function updateSonarrNaming(options: UpdateSonarrNamingOptions) {
|
||||
const { databaseId, cache, layer, currentName, input } = options;
|
||||
const db = cache.kb;
|
||||
|
||||
// If renaming, check if new name already exists
|
||||
if (input.name !== currentName) {
|
||||
const existing = await db
|
||||
.selectFrom('sonarr_naming')
|
||||
.where('name', '=', input.name)
|
||||
.select('name')
|
||||
.executeTakeFirst();
|
||||
|
||||
if (existing) {
|
||||
throw new Error(`A sonarr naming config with name "${input.name}" already exists`);
|
||||
}
|
||||
}
|
||||
|
||||
const updateQuery = db
|
||||
.updateTable('sonarr_naming')
|
||||
.set({
|
||||
name: input.name,
|
||||
rename: input.rename ? 1 : 0,
|
||||
standard_episode_format: input.standardEpisodeFormat,
|
||||
daily_episode_format: input.dailyEpisodeFormat,
|
||||
anime_episode_format: input.animeEpisodeFormat,
|
||||
series_folder_format: input.seriesFolderFormat,
|
||||
season_folder_format: input.seasonFolderFormat,
|
||||
replace_illegal_characters: input.replaceIllegalCharacters ? 1 : 0,
|
||||
colon_replacement_format: colonReplacementToDb(input.colonReplacementFormat),
|
||||
custom_colon_replacement_format: input.customColonReplacementFormat,
|
||||
multi_episode_style: multiEpisodeStyleToDb(input.multiEpisodeStyle)
|
||||
})
|
||||
.where('name', '=', currentName)
|
||||
.compile();
|
||||
|
||||
return writeOperation({
|
||||
databaseId,
|
||||
layer,
|
||||
description: `update-sonarr-naming-${input.name}`,
|
||||
queries: [updateQuery],
|
||||
metadata: {
|
||||
operation: 'update',
|
||||
entity: 'sonarr_naming',
|
||||
name: input.name
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,101 @@
|
||||
/**
|
||||
* Quality definitions create operations
|
||||
*/
|
||||
|
||||
import type { PCDCache } from '../../../cache.ts';
|
||||
import { writeOperation, type OperationLayer } from '../../../writer.ts';
|
||||
import type { QualityDefinitionEntry } from './types.ts';
|
||||
|
||||
export interface CreateQualityDefinitionsInput {
|
||||
name: string;
|
||||
entries: QualityDefinitionEntry[];
|
||||
}
|
||||
|
||||
export interface CreateQualityDefinitionsOptions {
|
||||
databaseId: number;
|
||||
cache: PCDCache;
|
||||
layer: OperationLayer;
|
||||
input: CreateQualityDefinitionsInput;
|
||||
}
|
||||
|
||||
export async function createRadarrQualityDefinitions(options: CreateQualityDefinitionsOptions) {
|
||||
const { databaseId, cache, layer, input } = options;
|
||||
const db = cache.kb;
|
||||
|
||||
// Check if name already exists
|
||||
const existing = await db
|
||||
.selectFrom('radarr_quality_definitions')
|
||||
.where('name', '=', input.name)
|
||||
.select('name')
|
||||
.executeTakeFirst();
|
||||
|
||||
if (existing) {
|
||||
throw new Error(`A radarr quality definitions config with name "${input.name}" already exists`);
|
||||
}
|
||||
|
||||
const queries = input.entries.map(entry =>
|
||||
db
|
||||
.insertInto('radarr_quality_definitions')
|
||||
.values({
|
||||
name: input.name,
|
||||
quality_name: entry.quality_name,
|
||||
min_size: entry.min_size,
|
||||
max_size: entry.max_size,
|
||||
preferred_size: entry.preferred_size
|
||||
})
|
||||
.compile()
|
||||
);
|
||||
|
||||
return writeOperation({
|
||||
databaseId,
|
||||
layer,
|
||||
description: `create-radarr-quality-definitions-${input.name}`,
|
||||
queries,
|
||||
metadata: {
|
||||
operation: 'create',
|
||||
entity: 'radarr_quality_definitions',
|
||||
name: input.name
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
export async function createSonarrQualityDefinitions(options: CreateQualityDefinitionsOptions) {
|
||||
const { databaseId, cache, layer, input } = options;
|
||||
const db = cache.kb;
|
||||
|
||||
// Check if name already exists
|
||||
const existing = await db
|
||||
.selectFrom('sonarr_quality_definitions')
|
||||
.where('name', '=', input.name)
|
||||
.select('name')
|
||||
.executeTakeFirst();
|
||||
|
||||
if (existing) {
|
||||
throw new Error(`A sonarr quality definitions config with name "${input.name}" already exists`);
|
||||
}
|
||||
|
||||
const queries = input.entries.map(entry =>
|
||||
db
|
||||
.insertInto('sonarr_quality_definitions')
|
||||
.values({
|
||||
name: input.name,
|
||||
quality_name: entry.quality_name,
|
||||
min_size: entry.min_size,
|
||||
max_size: entry.max_size,
|
||||
preferred_size: entry.preferred_size
|
||||
})
|
||||
.compile()
|
||||
);
|
||||
|
||||
return writeOperation({
|
||||
databaseId,
|
||||
layer,
|
||||
description: `create-sonarr-quality-definitions-${input.name}`,
|
||||
queries,
|
||||
metadata: {
|
||||
operation: 'create',
|
||||
entity: 'sonarr_quality_definitions',
|
||||
name: input.name
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
/**
|
||||
* Quality definitions queries
|
||||
*/
|
||||
|
||||
export * from './types.ts';
|
||||
export * from './read.ts';
|
||||
export * from './create.ts';
|
||||
export * from './update.ts';
|
||||
export * from './remove.ts';
|
||||
@@ -0,0 +1,124 @@
|
||||
/**
|
||||
* Quality definitions read operations
|
||||
*/
|
||||
|
||||
import type { PCDCache } from '../../../cache.ts';
|
||||
import type { QualityDefinitionListItem, QualityDefinitionsConfig, QualityDefinitionEntry, ArrType } from './types.ts';
|
||||
|
||||
/**
|
||||
* Get available qualities for an arr type from quality_api_mappings
|
||||
* Returns quality names that can be used for that arr type
|
||||
*/
|
||||
export async function getAvailableQualities(cache: PCDCache, arrType: ArrType): Promise<string[]> {
|
||||
const rows = await cache.kb
|
||||
.selectFrom('quality_api_mappings')
|
||||
.where('arr_type', '=', arrType)
|
||||
.select(['quality_name'])
|
||||
.orderBy('quality_name')
|
||||
.execute();
|
||||
|
||||
return rows.map(row => row.quality_name);
|
||||
}
|
||||
|
||||
/**
|
||||
* List all quality definitions configs
|
||||
* Returns distinct config names with quality counts
|
||||
*/
|
||||
export async function list(cache: PCDCache): Promise<QualityDefinitionListItem[]> {
|
||||
// Get radarr configs
|
||||
const radarrRows = await cache.kb
|
||||
.selectFrom('radarr_quality_definitions')
|
||||
.select(['name'])
|
||||
.select((eb) => eb.fn.count('quality_name').as('quality_count'))
|
||||
.select((eb) => eb.fn.max('updated_at').as('updated_at'))
|
||||
.groupBy('name')
|
||||
.execute();
|
||||
|
||||
// Get sonarr configs
|
||||
const sonarrRows = await cache.kb
|
||||
.selectFrom('sonarr_quality_definitions')
|
||||
.select(['name'])
|
||||
.select((eb) => eb.fn.count('quality_name').as('quality_count'))
|
||||
.select((eb) => eb.fn.max('updated_at').as('updated_at'))
|
||||
.groupBy('name')
|
||||
.execute();
|
||||
|
||||
const result: QualityDefinitionListItem[] = [];
|
||||
|
||||
for (const row of radarrRows) {
|
||||
result.push({
|
||||
name: row.name,
|
||||
arr_type: 'radarr',
|
||||
quality_count: Number(row.quality_count),
|
||||
updated_at: row.updated_at ?? ''
|
||||
});
|
||||
}
|
||||
|
||||
for (const row of sonarrRows) {
|
||||
result.push({
|
||||
name: row.name,
|
||||
arr_type: 'sonarr',
|
||||
quality_count: Number(row.quality_count),
|
||||
updated_at: row.updated_at ?? ''
|
||||
});
|
||||
}
|
||||
|
||||
// Sort by updated_at desc
|
||||
result.sort((a, b) => b.updated_at.localeCompare(a.updated_at));
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a Radarr quality definitions config by name
|
||||
*/
|
||||
export async function getRadarrByName(cache: PCDCache, name: string): Promise<QualityDefinitionsConfig | null> {
|
||||
const rows = await cache.kb
|
||||
.selectFrom('radarr_quality_definitions')
|
||||
.where('name', '=', name)
|
||||
.select(['quality_name', 'min_size', 'max_size', 'preferred_size'])
|
||||
.execute();
|
||||
|
||||
if (rows.length === 0) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const entries: QualityDefinitionEntry[] = rows.map(row => ({
|
||||
quality_name: row.quality_name,
|
||||
min_size: row.min_size,
|
||||
max_size: row.max_size,
|
||||
preferred_size: row.preferred_size
|
||||
}));
|
||||
|
||||
return {
|
||||
name,
|
||||
entries
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a Sonarr quality definitions config by name
|
||||
*/
|
||||
export async function getSonarrByName(cache: PCDCache, name: string): Promise<QualityDefinitionsConfig | null> {
|
||||
const rows = await cache.kb
|
||||
.selectFrom('sonarr_quality_definitions')
|
||||
.where('name', '=', name)
|
||||
.select(['quality_name', 'min_size', 'max_size', 'preferred_size'])
|
||||
.execute();
|
||||
|
||||
if (rows.length === 0) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const entries: QualityDefinitionEntry[] = rows.map(row => ({
|
||||
quality_name: row.quality_name,
|
||||
min_size: row.min_size,
|
||||
max_size: row.max_size,
|
||||
preferred_size: row.preferred_size
|
||||
}));
|
||||
|
||||
return {
|
||||
name,
|
||||
entries
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,57 @@
|
||||
/**
|
||||
* Quality definitions remove operations
|
||||
*/
|
||||
|
||||
import type { PCDCache } from '../../../cache.ts';
|
||||
import { writeOperation, type OperationLayer } from '../../../writer.ts';
|
||||
|
||||
export interface RemoveQualityDefinitionsOptions {
|
||||
databaseId: number;
|
||||
cache: PCDCache;
|
||||
layer: OperationLayer;
|
||||
name: string;
|
||||
}
|
||||
|
||||
export async function removeRadarrQualityDefinitions(options: RemoveQualityDefinitionsOptions) {
|
||||
const { databaseId, cache, layer, name } = options;
|
||||
const db = cache.kb;
|
||||
|
||||
const deleteQuery = db
|
||||
.deleteFrom('radarr_quality_definitions')
|
||||
.where('name', '=', name)
|
||||
.compile();
|
||||
|
||||
return writeOperation({
|
||||
databaseId,
|
||||
layer,
|
||||
description: `remove-radarr-quality-definitions-${name}`,
|
||||
queries: [deleteQuery],
|
||||
metadata: {
|
||||
operation: 'delete',
|
||||
entity: 'radarr_quality_definitions',
|
||||
name
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
export async function removeSonarrQualityDefinitions(options: RemoveQualityDefinitionsOptions) {
|
||||
const { databaseId, cache, layer, name } = options;
|
||||
const db = cache.kb;
|
||||
|
||||
const deleteQuery = db
|
||||
.deleteFrom('sonarr_quality_definitions')
|
||||
.where('name', '=', name)
|
||||
.compile();
|
||||
|
||||
return writeOperation({
|
||||
databaseId,
|
||||
layer,
|
||||
description: `remove-sonarr-quality-definitions-${name}`,
|
||||
queries: [deleteQuery],
|
||||
metadata: {
|
||||
operation: 'delete',
|
||||
entity: 'sonarr_quality_definitions',
|
||||
name
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
/**
|
||||
* Quality definitions types
|
||||
*/
|
||||
|
||||
export type ArrType = 'radarr' | 'sonarr';
|
||||
|
||||
export interface QualityDefinitionListItem {
|
||||
name: string;
|
||||
arr_type: ArrType;
|
||||
quality_count: number;
|
||||
updated_at: string;
|
||||
}
|
||||
|
||||
export interface QualityDefinitionEntry {
|
||||
quality_name: string;
|
||||
min_size: number;
|
||||
max_size: number;
|
||||
preferred_size: number;
|
||||
}
|
||||
|
||||
export interface QualityDefinitionsConfig {
|
||||
name: string;
|
||||
entries: QualityDefinitionEntry[];
|
||||
}
|
||||
@@ -0,0 +1,120 @@
|
||||
/**
|
||||
* Quality definitions update operations
|
||||
*/
|
||||
|
||||
import type { PCDCache } from '../../../cache.ts';
|
||||
import { writeOperation, type OperationLayer } from '../../../writer.ts';
|
||||
import type { QualityDefinitionEntry } from './types.ts';
|
||||
|
||||
export interface UpdateQualityDefinitionsInput {
|
||||
name: string;
|
||||
entries: QualityDefinitionEntry[];
|
||||
}
|
||||
|
||||
export interface UpdateQualityDefinitionsOptions {
|
||||
databaseId: number;
|
||||
cache: PCDCache;
|
||||
layer: OperationLayer;
|
||||
currentName: string;
|
||||
input: UpdateQualityDefinitionsInput;
|
||||
}
|
||||
|
||||
export async function updateRadarrQualityDefinitions(options: UpdateQualityDefinitionsOptions) {
|
||||
const { databaseId, cache, layer, currentName, input } = options;
|
||||
const db = cache.kb;
|
||||
|
||||
// If renaming, check if new name already exists
|
||||
if (input.name !== currentName) {
|
||||
const existing = await db
|
||||
.selectFrom('radarr_quality_definitions')
|
||||
.where('name', '=', input.name)
|
||||
.select('name')
|
||||
.executeTakeFirst();
|
||||
|
||||
if (existing) {
|
||||
throw new Error(`A radarr quality definitions config with name "${input.name}" already exists`);
|
||||
}
|
||||
}
|
||||
|
||||
// Delete all existing entries for this config
|
||||
const deleteQuery = db
|
||||
.deleteFrom('radarr_quality_definitions')
|
||||
.where('name', '=', currentName)
|
||||
.compile();
|
||||
|
||||
// Insert all new entries
|
||||
const insertQueries = input.entries.map(entry =>
|
||||
db
|
||||
.insertInto('radarr_quality_definitions')
|
||||
.values({
|
||||
name: input.name,
|
||||
quality_name: entry.quality_name,
|
||||
min_size: entry.min_size,
|
||||
max_size: entry.max_size,
|
||||
preferred_size: entry.preferred_size
|
||||
})
|
||||
.compile()
|
||||
);
|
||||
|
||||
return writeOperation({
|
||||
databaseId,
|
||||
layer,
|
||||
description: `update-radarr-quality-definitions-${input.name}`,
|
||||
queries: [deleteQuery, ...insertQueries],
|
||||
metadata: {
|
||||
operation: 'update',
|
||||
entity: 'radarr_quality_definitions',
|
||||
name: input.name
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
export async function updateSonarrQualityDefinitions(options: UpdateQualityDefinitionsOptions) {
|
||||
const { databaseId, cache, layer, currentName, input } = options;
|
||||
const db = cache.kb;
|
||||
|
||||
// If renaming, check if new name already exists
|
||||
if (input.name !== currentName) {
|
||||
const existing = await db
|
||||
.selectFrom('sonarr_quality_definitions')
|
||||
.where('name', '=', input.name)
|
||||
.select('name')
|
||||
.executeTakeFirst();
|
||||
|
||||
if (existing) {
|
||||
throw new Error(`A sonarr quality definitions config with name "${input.name}" already exists`);
|
||||
}
|
||||
}
|
||||
|
||||
// Delete all existing entries for this config
|
||||
const deleteQuery = db
|
||||
.deleteFrom('sonarr_quality_definitions')
|
||||
.where('name', '=', currentName)
|
||||
.compile();
|
||||
|
||||
// Insert all new entries
|
||||
const insertQueries = input.entries.map(entry =>
|
||||
db
|
||||
.insertInto('sonarr_quality_definitions')
|
||||
.values({
|
||||
name: input.name,
|
||||
quality_name: entry.quality_name,
|
||||
min_size: entry.min_size,
|
||||
max_size: entry.max_size,
|
||||
preferred_size: entry.preferred_size
|
||||
})
|
||||
.compile()
|
||||
);
|
||||
|
||||
return writeOperation({
|
||||
databaseId,
|
||||
layer,
|
||||
description: `update-sonarr-quality-definitions-${input.name}`,
|
||||
queries: [deleteQuery, ...insertQueries],
|
||||
metadata: {
|
||||
operation: 'update',
|
||||
entity: 'sonarr_quality_definitions',
|
||||
name: input.name
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -1,97 +0,0 @@
|
||||
/**
|
||||
* Media Management query-specific types
|
||||
*/
|
||||
|
||||
// ============================================================================
|
||||
// QUALITY DEFINITIONS
|
||||
// ============================================================================
|
||||
|
||||
export interface QualityDefinition {
|
||||
quality_name: string;
|
||||
min_size: number;
|
||||
max_size: number;
|
||||
preferred_size: number;
|
||||
}
|
||||
|
||||
export interface QualityDefinitionsData {
|
||||
radarr: QualityDefinition[];
|
||||
sonarr: QualityDefinition[];
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// NAMING SETTINGS
|
||||
// ============================================================================
|
||||
|
||||
// Re-export naming types from shared
|
||||
export type {
|
||||
RadarrNaming,
|
||||
SonarrNaming,
|
||||
ColonReplacementFormat,
|
||||
MultiEpisodeStyle,
|
||||
RadarrColonReplacementFormat
|
||||
} from '$lib/shared/mediaManagement.ts';
|
||||
export {
|
||||
COLON_REPLACEMENT_OPTIONS,
|
||||
getColonReplacementLabel,
|
||||
colonReplacementFromDb,
|
||||
colonReplacementToDb,
|
||||
RADARR_COLON_REPLACEMENT_OPTIONS,
|
||||
getRadarrColonReplacementLabel,
|
||||
radarrColonReplacementFromDb,
|
||||
radarrColonReplacementToDb,
|
||||
MULTI_EPISODE_STYLE_OPTIONS,
|
||||
getMultiEpisodeStyleLabel,
|
||||
multiEpisodeStyleFromDb,
|
||||
multiEpisodeStyleToDb
|
||||
} from '$lib/shared/mediaManagement.ts';
|
||||
|
||||
// Import types for local use in interfaces
|
||||
import type { RadarrNaming, SonarrNaming } from '$lib/shared/mediaManagement.ts';
|
||||
|
||||
export interface NamingData {
|
||||
radarr: RadarrNaming | null;
|
||||
sonarr: SonarrNaming | null;
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// MEDIA SETTINGS
|
||||
// ============================================================================
|
||||
|
||||
// Re-export from shared for convenience
|
||||
export type { PropersRepacks, MediaSettings } from '$lib/shared/mediaManagement.ts';
|
||||
export { PROPERS_REPACKS_OPTIONS, getPropersRepacksLabel } from '$lib/shared/mediaManagement.ts';
|
||||
|
||||
import type { MediaSettings } from '$lib/shared/mediaManagement.ts';
|
||||
|
||||
export interface MediaSettingsData {
|
||||
radarr: MediaSettings | null;
|
||||
sonarr: MediaSettings | null;
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// COMBINED DATA
|
||||
// ============================================================================
|
||||
|
||||
export interface MediaManagementData {
|
||||
qualityDefinitions: QualityDefinitionsData;
|
||||
naming: NamingData;
|
||||
mediaSettings: MediaSettingsData;
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// ARR-TYPE SPECIFIC DATA
|
||||
// ============================================================================
|
||||
|
||||
export type ArrType = 'radarr' | 'sonarr';
|
||||
|
||||
export interface RadarrMediaManagementData {
|
||||
qualityDefinitions: QualityDefinition[];
|
||||
naming: RadarrNaming | null;
|
||||
mediaSettings: MediaSettings | null;
|
||||
}
|
||||
|
||||
export interface SonarrMediaManagementData {
|
||||
qualityDefinitions: QualityDefinition[];
|
||||
naming: SonarrNaming | null;
|
||||
mediaSettings: MediaSettings | null;
|
||||
}
|
||||
@@ -1,503 +0,0 @@
|
||||
/**
|
||||
* Media Management update operations
|
||||
* Uses writeOperation() to append SQL operations to PCD layers
|
||||
*/
|
||||
|
||||
import type { PCDCache } from '../../cache.ts';
|
||||
import { writeOperation, type OperationLayer } from '../../writer.ts';
|
||||
import { logger } from '$logger/logger.ts';
|
||||
import type {
|
||||
PropersRepacks,
|
||||
ColonReplacementFormat,
|
||||
MultiEpisodeStyle,
|
||||
MediaSettings,
|
||||
SonarrNaming,
|
||||
RadarrNaming,
|
||||
RadarrColonReplacementFormat
|
||||
} from '$lib/shared/mediaManagement.ts';
|
||||
import {
|
||||
colonReplacementToDb,
|
||||
multiEpisodeStyleToDb,
|
||||
radarrColonReplacementToDb
|
||||
} from '$lib/shared/mediaManagement.ts';
|
||||
|
||||
// ============================================================================
|
||||
// MEDIA SETTINGS
|
||||
// ============================================================================
|
||||
|
||||
export interface UpdateMediaSettingsInput {
|
||||
propers_repacks: PropersRepacks;
|
||||
enable_media_info: boolean;
|
||||
}
|
||||
|
||||
export interface UpdateMediaSettingsOptions {
|
||||
databaseId: number;
|
||||
cache: PCDCache;
|
||||
layer: OperationLayer;
|
||||
current: MediaSettings;
|
||||
input: UpdateMediaSettingsInput;
|
||||
}
|
||||
|
||||
/**
|
||||
* Update Radarr media settings
|
||||
*/
|
||||
export async function updateRadarrMediaSettings(options: UpdateMediaSettingsOptions) {
|
||||
const { databaseId, cache, layer, current, input } = options;
|
||||
const db = cache.kb;
|
||||
|
||||
const query = db
|
||||
.updateTable('radarr_media_settings')
|
||||
.set({
|
||||
propers_repacks: input.propers_repacks,
|
||||
enable_media_info: input.enable_media_info ? 1 : 0
|
||||
})
|
||||
.where('id', '=', current.id)
|
||||
// Value guards
|
||||
.where('propers_repacks', '=', current.propers_repacks)
|
||||
.where('enable_media_info', '=', current.enable_media_info ? 1 : 0)
|
||||
.compile();
|
||||
|
||||
// Track changes
|
||||
const changes: Record<string, { from: unknown; to: unknown }> = {};
|
||||
if (current.propers_repacks !== input.propers_repacks) {
|
||||
changes.propers_repacks = { from: current.propers_repacks, to: input.propers_repacks };
|
||||
}
|
||||
if (current.enable_media_info !== input.enable_media_info) {
|
||||
changes.enable_media_info = { from: current.enable_media_info, to: input.enable_media_info };
|
||||
}
|
||||
|
||||
await logger.info('Save radarr media settings', {
|
||||
source: 'MediaManagement',
|
||||
meta: { id: current.id, changes }
|
||||
});
|
||||
|
||||
return await writeOperation({
|
||||
databaseId,
|
||||
layer,
|
||||
description: 'update-radarr-media-settings',
|
||||
queries: [query],
|
||||
metadata: {
|
||||
operation: 'update',
|
||||
entity: 'radarr_media_settings',
|
||||
name: 'media-settings'
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Update Sonarr media settings
|
||||
*/
|
||||
export async function updateSonarrMediaSettings(options: UpdateMediaSettingsOptions) {
|
||||
const { databaseId, cache, layer, current, input } = options;
|
||||
const db = cache.kb;
|
||||
|
||||
const query = db
|
||||
.updateTable('sonarr_media_settings')
|
||||
.set({
|
||||
propers_repacks: input.propers_repacks,
|
||||
enable_media_info: input.enable_media_info ? 1 : 0
|
||||
})
|
||||
.where('id', '=', current.id)
|
||||
// Value guards
|
||||
.where('propers_repacks', '=', current.propers_repacks)
|
||||
.where('enable_media_info', '=', current.enable_media_info ? 1 : 0)
|
||||
.compile();
|
||||
|
||||
// Track changes
|
||||
const changes: Record<string, { from: unknown; to: unknown }> = {};
|
||||
if (current.propers_repacks !== input.propers_repacks) {
|
||||
changes.propers_repacks = { from: current.propers_repacks, to: input.propers_repacks };
|
||||
}
|
||||
if (current.enable_media_info !== input.enable_media_info) {
|
||||
changes.enable_media_info = { from: current.enable_media_info, to: input.enable_media_info };
|
||||
}
|
||||
|
||||
await logger.info('Save sonarr media settings', {
|
||||
source: 'MediaManagement',
|
||||
meta: { id: current.id, changes }
|
||||
});
|
||||
|
||||
return await writeOperation({
|
||||
databaseId,
|
||||
layer,
|
||||
description: 'update-sonarr-media-settings',
|
||||
queries: [query],
|
||||
metadata: {
|
||||
operation: 'update',
|
||||
entity: 'sonarr_media_settings',
|
||||
name: 'media-settings'
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// SONARR NAMING
|
||||
// ============================================================================
|
||||
|
||||
export interface UpdateSonarrNamingInput {
|
||||
rename: boolean;
|
||||
replace_illegal_characters: boolean;
|
||||
colon_replacement_format: ColonReplacementFormat;
|
||||
custom_colon_replacement_format: string | null;
|
||||
standard_episode_format: string;
|
||||
daily_episode_format: string;
|
||||
anime_episode_format: string;
|
||||
series_folder_format: string;
|
||||
season_folder_format: string;
|
||||
multi_episode_style: MultiEpisodeStyle;
|
||||
}
|
||||
|
||||
export interface UpdateSonarrNamingOptions {
|
||||
databaseId: number;
|
||||
cache: PCDCache;
|
||||
layer: OperationLayer;
|
||||
current: SonarrNaming;
|
||||
input: UpdateSonarrNamingInput;
|
||||
}
|
||||
|
||||
/**
|
||||
* Update Sonarr naming settings
|
||||
*/
|
||||
export async function updateSonarrNaming(options: UpdateSonarrNamingOptions) {
|
||||
const { databaseId, cache, layer, current, input } = options;
|
||||
const db = cache.kb;
|
||||
|
||||
const query = db
|
||||
.updateTable('sonarr_naming')
|
||||
.set({
|
||||
rename: input.rename ? 1 : 0,
|
||||
replace_illegal_characters: input.replace_illegal_characters ? 1 : 0,
|
||||
colon_replacement_format: colonReplacementToDb(input.colon_replacement_format),
|
||||
custom_colon_replacement_format: input.custom_colon_replacement_format,
|
||||
standard_episode_format: input.standard_episode_format,
|
||||
daily_episode_format: input.daily_episode_format,
|
||||
anime_episode_format: input.anime_episode_format,
|
||||
series_folder_format: input.series_folder_format,
|
||||
season_folder_format: input.season_folder_format,
|
||||
multi_episode_style: multiEpisodeStyleToDb(input.multi_episode_style)
|
||||
})
|
||||
.where('id', '=', current.id)
|
||||
// Value guards - check key fields match expected values
|
||||
.where('rename', '=', current.rename ? 1 : 0)
|
||||
.where('replace_illegal_characters', '=', current.replace_illegal_characters ? 1 : 0)
|
||||
.compile();
|
||||
|
||||
// Track changes
|
||||
const changes: Record<string, { from: unknown; to: unknown }> = {};
|
||||
if (current.rename !== input.rename) {
|
||||
changes.rename = { from: current.rename, to: input.rename };
|
||||
}
|
||||
if (current.replace_illegal_characters !== input.replace_illegal_characters) {
|
||||
changes.replace_illegal_characters = {
|
||||
from: current.replace_illegal_characters,
|
||||
to: input.replace_illegal_characters
|
||||
};
|
||||
}
|
||||
if (current.colon_replacement_format !== input.colon_replacement_format) {
|
||||
changes.colon_replacement_format = {
|
||||
from: current.colon_replacement_format,
|
||||
to: input.colon_replacement_format
|
||||
};
|
||||
}
|
||||
if (current.custom_colon_replacement_format !== input.custom_colon_replacement_format) {
|
||||
changes.custom_colon_replacement_format = {
|
||||
from: current.custom_colon_replacement_format,
|
||||
to: input.custom_colon_replacement_format
|
||||
};
|
||||
}
|
||||
if (current.standard_episode_format !== input.standard_episode_format) {
|
||||
changes.standard_episode_format = {
|
||||
from: current.standard_episode_format,
|
||||
to: input.standard_episode_format
|
||||
};
|
||||
}
|
||||
if (current.daily_episode_format !== input.daily_episode_format) {
|
||||
changes.daily_episode_format = {
|
||||
from: current.daily_episode_format,
|
||||
to: input.daily_episode_format
|
||||
};
|
||||
}
|
||||
if (current.anime_episode_format !== input.anime_episode_format) {
|
||||
changes.anime_episode_format = {
|
||||
from: current.anime_episode_format,
|
||||
to: input.anime_episode_format
|
||||
};
|
||||
}
|
||||
if (current.series_folder_format !== input.series_folder_format) {
|
||||
changes.series_folder_format = {
|
||||
from: current.series_folder_format,
|
||||
to: input.series_folder_format
|
||||
};
|
||||
}
|
||||
if (current.season_folder_format !== input.season_folder_format) {
|
||||
changes.season_folder_format = {
|
||||
from: current.season_folder_format,
|
||||
to: input.season_folder_format
|
||||
};
|
||||
}
|
||||
if (current.multi_episode_style !== input.multi_episode_style) {
|
||||
changes.multi_episode_style = {
|
||||
from: current.multi_episode_style,
|
||||
to: input.multi_episode_style
|
||||
};
|
||||
}
|
||||
|
||||
await logger.info('Save sonarr naming settings', {
|
||||
source: 'MediaManagement',
|
||||
meta: { id: current.id, changes }
|
||||
});
|
||||
|
||||
return await writeOperation({
|
||||
databaseId,
|
||||
layer,
|
||||
description: 'update-sonarr-naming',
|
||||
queries: [query],
|
||||
metadata: {
|
||||
operation: 'update',
|
||||
entity: 'sonarr_naming',
|
||||
name: 'naming-settings'
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// RADARR NAMING
|
||||
// ============================================================================
|
||||
|
||||
export interface UpdateRadarrNamingInput {
|
||||
rename: boolean;
|
||||
replace_illegal_characters: boolean;
|
||||
colon_replacement_format: RadarrColonReplacementFormat;
|
||||
movie_format: string;
|
||||
movie_folder_format: string;
|
||||
}
|
||||
|
||||
export interface UpdateRadarrNamingOptions {
|
||||
databaseId: number;
|
||||
cache: PCDCache;
|
||||
layer: OperationLayer;
|
||||
current: RadarrNaming;
|
||||
input: UpdateRadarrNamingInput;
|
||||
}
|
||||
|
||||
/**
|
||||
* Update Radarr naming settings
|
||||
*/
|
||||
export async function updateRadarrNaming(options: UpdateRadarrNamingOptions) {
|
||||
const { databaseId, cache, layer, current, input } = options;
|
||||
const db = cache.kb;
|
||||
|
||||
const query = db
|
||||
.updateTable('radarr_naming')
|
||||
.set({
|
||||
rename: input.rename ? 1 : 0,
|
||||
replace_illegal_characters: input.replace_illegal_characters ? 1 : 0,
|
||||
colon_replacement_format: radarrColonReplacementToDb(input.colon_replacement_format),
|
||||
movie_format: input.movie_format,
|
||||
movie_folder_format: input.movie_folder_format
|
||||
})
|
||||
.where('id', '=', current.id)
|
||||
// Value guards - check key fields match expected values
|
||||
.where('rename', '=', current.rename ? 1 : 0)
|
||||
.where('replace_illegal_characters', '=', current.replace_illegal_characters ? 1 : 0)
|
||||
.compile();
|
||||
|
||||
// Track changes
|
||||
const changes: Record<string, { from: unknown; to: unknown }> = {};
|
||||
if (current.rename !== input.rename) {
|
||||
changes.rename = { from: current.rename, to: input.rename };
|
||||
}
|
||||
if (current.replace_illegal_characters !== input.replace_illegal_characters) {
|
||||
changes.replace_illegal_characters = {
|
||||
from: current.replace_illegal_characters,
|
||||
to: input.replace_illegal_characters
|
||||
};
|
||||
}
|
||||
if (current.colon_replacement_format !== input.colon_replacement_format) {
|
||||
changes.colon_replacement_format = {
|
||||
from: current.colon_replacement_format,
|
||||
to: input.colon_replacement_format
|
||||
};
|
||||
}
|
||||
if (current.movie_format !== input.movie_format) {
|
||||
changes.movie_format = { from: current.movie_format, to: input.movie_format };
|
||||
}
|
||||
if (current.movie_folder_format !== input.movie_folder_format) {
|
||||
changes.movie_folder_format = {
|
||||
from: current.movie_folder_format,
|
||||
to: input.movie_folder_format
|
||||
};
|
||||
}
|
||||
|
||||
await logger.info('Save radarr naming settings', {
|
||||
source: 'MediaManagement',
|
||||
meta: { id: current.id, changes }
|
||||
});
|
||||
|
||||
return await writeOperation({
|
||||
databaseId,
|
||||
layer,
|
||||
description: 'update-radarr-naming',
|
||||
queries: [query],
|
||||
metadata: {
|
||||
operation: 'update',
|
||||
entity: 'radarr_naming',
|
||||
name: 'naming-settings'
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// QUALITY DEFINITIONS
|
||||
// ============================================================================
|
||||
|
||||
import type { QualityDefinition } from './types.ts';
|
||||
|
||||
export interface UpdateQualityDefinitionInput {
|
||||
quality_name: string;
|
||||
min_size: number;
|
||||
max_size: number;
|
||||
preferred_size: number;
|
||||
}
|
||||
|
||||
export interface UpdateQualityDefinitionsOptions {
|
||||
databaseId: number;
|
||||
cache: PCDCache;
|
||||
layer: OperationLayer;
|
||||
current: QualityDefinition[];
|
||||
input: UpdateQualityDefinitionInput[];
|
||||
}
|
||||
|
||||
/**
|
||||
* Update Radarr quality definitions
|
||||
*/
|
||||
export async function updateRadarrQualityDefinitions(options: UpdateQualityDefinitionsOptions) {
|
||||
const { databaseId, cache, layer, current, input } = options;
|
||||
const db = cache.kb;
|
||||
|
||||
// Track changes per quality definition
|
||||
const changes: Record<string, { from: unknown; to: unknown }> = {};
|
||||
|
||||
// Build queries for each changed definition
|
||||
const queries = input.map((def) => {
|
||||
const currentDef = current.find((c) => c.quality_name === def.quality_name);
|
||||
if (!currentDef) {
|
||||
throw new Error(`Quality definition not found for quality_name: ${def.quality_name}`);
|
||||
}
|
||||
|
||||
// Track changes for this definition
|
||||
const defChanges: Record<string, { from: unknown; to: unknown }> = {};
|
||||
if (currentDef.min_size !== def.min_size) {
|
||||
defChanges.min_size = { from: currentDef.min_size, to: def.min_size };
|
||||
}
|
||||
if (currentDef.max_size !== def.max_size) {
|
||||
defChanges.max_size = { from: currentDef.max_size, to: def.max_size };
|
||||
}
|
||||
if (currentDef.preferred_size !== def.preferred_size) {
|
||||
defChanges.preferred_size = { from: currentDef.preferred_size, to: def.preferred_size };
|
||||
}
|
||||
if (Object.keys(defChanges).length > 0) {
|
||||
changes[currentDef.quality_name] = defChanges as { from: unknown; to: unknown };
|
||||
}
|
||||
|
||||
return (
|
||||
db
|
||||
.updateTable('radarr_quality_definitions')
|
||||
.set({
|
||||
min_size: def.min_size,
|
||||
max_size: def.max_size,
|
||||
preferred_size: def.preferred_size
|
||||
})
|
||||
.where('quality_name', '=', def.quality_name)
|
||||
// Value guards
|
||||
.where('min_size', '=', currentDef.min_size)
|
||||
.where('max_size', '=', currentDef.max_size)
|
||||
.where('preferred_size', '=', currentDef.preferred_size)
|
||||
.compile()
|
||||
);
|
||||
});
|
||||
|
||||
await logger.info('Save radarr quality definitions', {
|
||||
source: 'MediaManagement',
|
||||
meta: { changes }
|
||||
});
|
||||
|
||||
return await writeOperation({
|
||||
databaseId,
|
||||
layer,
|
||||
description: 'update-radarr-quality-definitions',
|
||||
queries,
|
||||
metadata: {
|
||||
operation: 'update',
|
||||
entity: 'radarr_quality_definitions',
|
||||
name: 'quality-definitions'
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Update Sonarr quality definitions
|
||||
*/
|
||||
export async function updateSonarrQualityDefinitions(options: UpdateQualityDefinitionsOptions) {
|
||||
const { databaseId, cache, layer, current, input } = options;
|
||||
const db = cache.kb;
|
||||
|
||||
// Track changes per quality definition
|
||||
const changes: Record<string, { from: unknown; to: unknown }> = {};
|
||||
|
||||
// Build queries for each changed definition
|
||||
const queries = input.map((def) => {
|
||||
const currentDef = current.find((c) => c.quality_name === def.quality_name);
|
||||
if (!currentDef) {
|
||||
throw new Error(`Quality definition not found for quality_name: ${def.quality_name}`);
|
||||
}
|
||||
|
||||
// Track changes for this definition
|
||||
const defChanges: Record<string, { from: unknown; to: unknown }> = {};
|
||||
if (currentDef.min_size !== def.min_size) {
|
||||
defChanges.min_size = { from: currentDef.min_size, to: def.min_size };
|
||||
}
|
||||
if (currentDef.max_size !== def.max_size) {
|
||||
defChanges.max_size = { from: currentDef.max_size, to: def.max_size };
|
||||
}
|
||||
if (currentDef.preferred_size !== def.preferred_size) {
|
||||
defChanges.preferred_size = { from: currentDef.preferred_size, to: def.preferred_size };
|
||||
}
|
||||
if (Object.keys(defChanges).length > 0) {
|
||||
changes[currentDef.quality_name] = defChanges as { from: unknown; to: unknown };
|
||||
}
|
||||
|
||||
return (
|
||||
db
|
||||
.updateTable('sonarr_quality_definitions')
|
||||
.set({
|
||||
min_size: def.min_size,
|
||||
max_size: def.max_size,
|
||||
preferred_size: def.preferred_size
|
||||
})
|
||||
.where('quality_name', '=', def.quality_name)
|
||||
// Value guards
|
||||
.where('min_size', '=', currentDef.min_size)
|
||||
.where('max_size', '=', currentDef.max_size)
|
||||
.where('preferred_size', '=', currentDef.preferred_size)
|
||||
.compile()
|
||||
);
|
||||
});
|
||||
|
||||
await logger.info('Save sonarr quality definitions', {
|
||||
source: 'MediaManagement',
|
||||
meta: { changes }
|
||||
});
|
||||
|
||||
return await writeOperation({
|
||||
databaseId,
|
||||
layer,
|
||||
description: 'update-sonarr-quality-definitions',
|
||||
queries,
|
||||
metadata: {
|
||||
operation: 'update',
|
||||
entity: 'sonarr_quality_definitions',
|
||||
name: 'quality-definitions'
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -260,6 +260,7 @@ export interface DelayProfilesTable {
|
||||
// ============================================================================
|
||||
|
||||
export interface RadarrQualityDefinitionsTable {
|
||||
name: string;
|
||||
quality_name: string;
|
||||
min_size: number;
|
||||
max_size: number;
|
||||
@@ -269,6 +270,7 @@ export interface RadarrQualityDefinitionsTable {
|
||||
}
|
||||
|
||||
export interface SonarrQualityDefinitionsTable {
|
||||
name: string;
|
||||
quality_name: string;
|
||||
min_size: number;
|
||||
max_size: number;
|
||||
@@ -278,7 +280,7 @@ export interface SonarrQualityDefinitionsTable {
|
||||
}
|
||||
|
||||
export interface RadarrNamingTable {
|
||||
id: number;
|
||||
name: string;
|
||||
rename: number;
|
||||
movie_format: string;
|
||||
movie_folder_format: string;
|
||||
@@ -289,7 +291,7 @@ export interface RadarrNamingTable {
|
||||
}
|
||||
|
||||
export interface SonarrNamingTable {
|
||||
id: number;
|
||||
name: string;
|
||||
rename: number;
|
||||
standard_episode_format: string;
|
||||
daily_episode_format: string;
|
||||
@@ -305,7 +307,7 @@ export interface SonarrNamingTable {
|
||||
}
|
||||
|
||||
export interface RadarrMediaSettingsTable {
|
||||
id: number;
|
||||
name: string;
|
||||
propers_repacks: string;
|
||||
enable_media_info: number;
|
||||
created_at: Generated<string>;
|
||||
@@ -313,7 +315,7 @@ export interface RadarrMediaSettingsTable {
|
||||
}
|
||||
|
||||
export interface SonarrMediaSettingsTable {
|
||||
id: number;
|
||||
name: string;
|
||||
propers_repacks: string;
|
||||
enable_media_info: number;
|
||||
created_at: Generated<string>;
|
||||
|
||||
@@ -17,9 +17,11 @@
|
||||
import { BaseSyncer, type SyncResult } from '../base.ts';
|
||||
import { arrSyncQueries } from '$db/queries/arrSync.ts';
|
||||
import { getCache, type PCDCache } from '$pcd/cache.ts';
|
||||
import { getRadarr, getSonarr } from '$pcd/queries/mediaManagement/get.ts';
|
||||
import type { QualityDefinition } from '$pcd/queries/mediaManagement/combined.ts';
|
||||
import { getRadarrByName as getRadarrMediaSettings, getSonarrByName as getSonarrMediaSettings } from '$pcd/queries/mediaManagement/media-settings/read.ts';
|
||||
import { getRadarrByName as getRadarrNaming, getSonarrByName as getSonarrNaming } from '$pcd/queries/mediaManagement/naming/read.ts';
|
||||
import { getRadarrByName as getRadarrQualityDefs, getSonarrByName as getSonarrQualityDefs } from '$pcd/queries/mediaManagement/quality-definitions/read.ts';
|
||||
import type { MediaSettings, RadarrNaming, SonarrNaming } from '$lib/shared/mediaManagement.ts';
|
||||
import type { QualityDefinition } from '$pcd/queries/mediaManagement/types.ts';
|
||||
import { colonReplacementToDb, multiEpisodeStyleToDb } from '$lib/shared/mediaManagement.ts';
|
||||
import type {
|
||||
ArrType,
|
||||
@@ -58,16 +60,19 @@ export class MediaManagementSyncer extends BaseSyncer {
|
||||
source: 'Sync:MediaManagement',
|
||||
meta: {
|
||||
instanceId: this.instanceId,
|
||||
hasMediaSettings: !!syncConfig.mediaSettingsDatabaseId,
|
||||
hasNaming: !!syncConfig.namingDatabaseId,
|
||||
hasQualityDefs: !!syncConfig.qualityDefinitionsDatabaseId
|
||||
hasMediaSettings: !!syncConfig.mediaSettingsDatabaseId && !!syncConfig.mediaSettingsConfigName,
|
||||
hasNaming: !!syncConfig.namingDatabaseId && !!syncConfig.namingConfigName,
|
||||
hasQualityDefs: !!syncConfig.qualityDefinitionsDatabaseId && !!syncConfig.qualityDefinitionsConfigName
|
||||
}
|
||||
});
|
||||
|
||||
// Sync media settings if configured
|
||||
if (syncConfig.mediaSettingsDatabaseId) {
|
||||
// Sync media settings if configured (both database and config name required)
|
||||
if (syncConfig.mediaSettingsDatabaseId && syncConfig.mediaSettingsConfigName) {
|
||||
try {
|
||||
const synced = await this.syncMediaSettings(syncConfig.mediaSettingsDatabaseId);
|
||||
const synced = await this.syncMediaSettings(
|
||||
syncConfig.mediaSettingsDatabaseId,
|
||||
syncConfig.mediaSettingsConfigName
|
||||
);
|
||||
if (synced) totalSynced++;
|
||||
} catch (error) {
|
||||
const msg = error instanceof Error ? error.message : 'Unknown error';
|
||||
@@ -79,10 +84,13 @@ export class MediaManagementSyncer extends BaseSyncer {
|
||||
}
|
||||
}
|
||||
|
||||
// Sync naming if configured
|
||||
if (syncConfig.namingDatabaseId) {
|
||||
// Sync naming if configured (both database and config name required)
|
||||
if (syncConfig.namingDatabaseId && syncConfig.namingConfigName) {
|
||||
try {
|
||||
const synced = await this.syncNaming(syncConfig.namingDatabaseId);
|
||||
const synced = await this.syncNaming(
|
||||
syncConfig.namingDatabaseId,
|
||||
syncConfig.namingConfigName
|
||||
);
|
||||
if (synced) totalSynced++;
|
||||
} catch (error) {
|
||||
const msg = error instanceof Error ? error.message : 'Unknown error';
|
||||
@@ -94,10 +102,13 @@ export class MediaManagementSyncer extends BaseSyncer {
|
||||
}
|
||||
}
|
||||
|
||||
// Sync quality definitions if configured
|
||||
if (syncConfig.qualityDefinitionsDatabaseId) {
|
||||
// Sync quality definitions if configured (both database and config name required)
|
||||
if (syncConfig.qualityDefinitionsDatabaseId && syncConfig.qualityDefinitionsConfigName) {
|
||||
try {
|
||||
const synced = await this.syncQualityDefinitions(syncConfig.qualityDefinitionsDatabaseId);
|
||||
const synced = await this.syncQualityDefinitions(
|
||||
syncConfig.qualityDefinitionsDatabaseId,
|
||||
syncConfig.qualityDefinitionsConfigName
|
||||
);
|
||||
if (synced) totalSynced++;
|
||||
} catch (error) {
|
||||
const msg = error instanceof Error ? error.message : 'Unknown error';
|
||||
@@ -128,7 +139,7 @@ export class MediaManagementSyncer extends BaseSyncer {
|
||||
// Media Settings
|
||||
// =========================================================================
|
||||
|
||||
private async syncMediaSettings(databaseId: number): Promise<boolean> {
|
||||
private async syncMediaSettings(databaseId: number, configName: string): Promise<boolean> {
|
||||
const cache = getCache(databaseId);
|
||||
if (!cache) {
|
||||
await logger.warn(`PCD cache not found for database ${databaseId}`, {
|
||||
@@ -138,18 +149,18 @@ export class MediaManagementSyncer extends BaseSyncer {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Fetch from PCD
|
||||
// Fetch from PCD by config name
|
||||
let mediaSettings: MediaSettings | null = null;
|
||||
if (this.instanceType === 'radarr') {
|
||||
mediaSettings = (await getRadarr(cache)).mediaSettings;
|
||||
mediaSettings = await getRadarrMediaSettings(cache, configName);
|
||||
} else if (this.instanceType === 'sonarr') {
|
||||
mediaSettings = (await getSonarr(cache)).mediaSettings;
|
||||
mediaSettings = await getSonarrMediaSettings(cache, configName);
|
||||
}
|
||||
|
||||
if (!mediaSettings) {
|
||||
await logger.debug('No media settings found in PCD', {
|
||||
await logger.debug(`Media settings config "${configName}" not found in PCD`, {
|
||||
source: 'Sync:MediaSettings',
|
||||
meta: { instanceId: this.instanceId }
|
||||
meta: { instanceId: this.instanceId, configName }
|
||||
});
|
||||
return false;
|
||||
}
|
||||
@@ -168,6 +179,7 @@ export class MediaManagementSyncer extends BaseSyncer {
|
||||
source: 'Sync:MediaSettings',
|
||||
meta: {
|
||||
instanceId: this.instanceId,
|
||||
configName,
|
||||
propersRepacks: updatedConfig.downloadPropersAndRepacks,
|
||||
enableMediaInfo: updatedConfig.enableMediaInfo
|
||||
}
|
||||
@@ -190,7 +202,7 @@ export class MediaManagementSyncer extends BaseSyncer {
|
||||
// Naming
|
||||
// =========================================================================
|
||||
|
||||
private async syncNaming(databaseId: number): Promise<boolean> {
|
||||
private async syncNaming(databaseId: number, configName: string): Promise<boolean> {
|
||||
const cache = getCache(databaseId);
|
||||
if (!cache) {
|
||||
await logger.warn(`PCD cache not found for database ${databaseId}`, {
|
||||
@@ -201,9 +213,9 @@ export class MediaManagementSyncer extends BaseSyncer {
|
||||
}
|
||||
|
||||
if (this.instanceType === 'radarr') {
|
||||
return this.syncRadarrNaming(cache, databaseId);
|
||||
return this.syncRadarrNaming(cache, configName);
|
||||
} else if (this.instanceType === 'sonarr') {
|
||||
return this.syncSonarrNaming(cache, databaseId);
|
||||
return this.syncSonarrNaming(cache, configName);
|
||||
}
|
||||
|
||||
await logger.warn(`Unsupported instance type for naming sync: ${this.instanceType}`, {
|
||||
@@ -214,16 +226,14 @@ export class MediaManagementSyncer extends BaseSyncer {
|
||||
}
|
||||
|
||||
private async syncRadarrNaming(
|
||||
cache: ReturnType<typeof getCache>,
|
||||
databaseId: number
|
||||
cache: PCDCache,
|
||||
configName: string
|
||||
): Promise<boolean> {
|
||||
if (!cache) return false;
|
||||
|
||||
const naming = (await getRadarr(cache)).naming;
|
||||
const naming = await getRadarrNaming(cache, configName);
|
||||
if (!naming) {
|
||||
await logger.debug('No Radarr naming found in PCD', {
|
||||
await logger.debug(`Radarr naming config "${configName}" not found in PCD`, {
|
||||
source: 'Sync:Naming',
|
||||
meta: { instanceId: this.instanceId, databaseId }
|
||||
meta: { instanceId: this.instanceId, configName }
|
||||
});
|
||||
return false;
|
||||
}
|
||||
@@ -245,6 +255,7 @@ export class MediaManagementSyncer extends BaseSyncer {
|
||||
source: 'Sync:Naming',
|
||||
meta: {
|
||||
instanceId: this.instanceId,
|
||||
configName,
|
||||
renameMovies: updatedConfig.renameMovies,
|
||||
colonReplacementFormat: updatedConfig.colonReplacementFormat
|
||||
}
|
||||
@@ -255,16 +266,14 @@ export class MediaManagementSyncer extends BaseSyncer {
|
||||
}
|
||||
|
||||
private async syncSonarrNaming(
|
||||
cache: ReturnType<typeof getCache>,
|
||||
databaseId: number
|
||||
cache: PCDCache,
|
||||
configName: string
|
||||
): Promise<boolean> {
|
||||
if (!cache) return false;
|
||||
|
||||
const naming = (await getSonarr(cache)).naming;
|
||||
const naming = await getSonarrNaming(cache, configName);
|
||||
if (!naming) {
|
||||
await logger.debug('No Sonarr naming found in PCD', {
|
||||
await logger.debug(`Sonarr naming config "${configName}" not found in PCD`, {
|
||||
source: 'Sync:Naming',
|
||||
meta: { instanceId: this.instanceId, databaseId }
|
||||
meta: { instanceId: this.instanceId, configName }
|
||||
});
|
||||
return false;
|
||||
}
|
||||
@@ -291,6 +300,7 @@ export class MediaManagementSyncer extends BaseSyncer {
|
||||
source: 'Sync:Naming',
|
||||
meta: {
|
||||
instanceId: this.instanceId,
|
||||
configName,
|
||||
renameEpisodes: updatedConfig.renameEpisodes,
|
||||
colonReplacementFormat: updatedConfig.colonReplacementFormat,
|
||||
multiEpisodeStyle: updatedConfig.multiEpisodeStyle
|
||||
@@ -305,7 +315,7 @@ export class MediaManagementSyncer extends BaseSyncer {
|
||||
// Quality Definitions
|
||||
// =========================================================================
|
||||
|
||||
private async syncQualityDefinitions(databaseId: number): Promise<boolean> {
|
||||
private async syncQualityDefinitions(databaseId: number, configName: string): Promise<boolean> {
|
||||
const cache = getCache(databaseId);
|
||||
if (!cache) {
|
||||
await logger.warn(`PCD cache not found for database ${databaseId}`, {
|
||||
@@ -315,18 +325,29 @@ export class MediaManagementSyncer extends BaseSyncer {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Fetch quality definitions from PCD
|
||||
let pcdDefinitions: QualityDefinition[] = [];
|
||||
if (this.instanceType === 'radarr') {
|
||||
pcdDefinitions = (await getRadarr(cache)).qualityDefinitions;
|
||||
} else if (this.instanceType === 'sonarr') {
|
||||
pcdDefinitions = (await getSonarr(cache)).qualityDefinitions;
|
||||
// Fetch quality definitions from PCD by config name
|
||||
const getByName = this.instanceType === 'radarr' ? getRadarrQualityDefs : getSonarrQualityDefs;
|
||||
const qualityDefsConfig = await getByName(cache, configName);
|
||||
|
||||
if (!qualityDefsConfig) {
|
||||
await logger.debug(`Quality definitions config "${configName}" not found in PCD`, {
|
||||
source: 'Sync:QualityDefinitions',
|
||||
meta: { instanceId: this.instanceId, configName }
|
||||
});
|
||||
return false;
|
||||
}
|
||||
|
||||
const pcdDefinitions: QualityDefinition[] = qualityDefsConfig.entries.map(entry => ({
|
||||
quality_name: entry.quality_name,
|
||||
min_size: entry.min_size,
|
||||
max_size: entry.max_size,
|
||||
preferred_size: entry.preferred_size
|
||||
}));
|
||||
|
||||
if (pcdDefinitions.length === 0) {
|
||||
await logger.debug('No quality definitions found in PCD', {
|
||||
await logger.debug(`Quality definitions config "${configName}" has no entries`, {
|
||||
source: 'Sync:QualityDefinitions',
|
||||
meta: { instanceId: this.instanceId }
|
||||
meta: { instanceId: this.instanceId, configName }
|
||||
});
|
||||
return false;
|
||||
}
|
||||
@@ -369,23 +390,24 @@ export class MediaManagementSyncer extends BaseSyncer {
|
||||
}
|
||||
|
||||
// Update the definition
|
||||
// PCD uses 0 for "unlimited", Radarr API uses null
|
||||
arrDef.minSize = pcdDef.min_size;
|
||||
arrDef.maxSize = pcdDef.max_size;
|
||||
arrDef.preferredSize = pcdDef.preferred_size;
|
||||
arrDef.maxSize = pcdDef.max_size === 0 ? null : pcdDef.max_size;
|
||||
arrDef.preferredSize = pcdDef.preferred_size === 0 ? null : pcdDef.preferred_size;
|
||||
updatedCount++;
|
||||
}
|
||||
|
||||
if (updatedCount === 0) {
|
||||
await logger.debug('No quality definitions matched for update', {
|
||||
source: 'Sync:QualityDefinitions',
|
||||
meta: { instanceId: this.instanceId }
|
||||
meta: { instanceId: this.instanceId, configName }
|
||||
});
|
||||
return false;
|
||||
}
|
||||
|
||||
await logger.debug(`Updating ${updatedCount} quality definitions`, {
|
||||
source: 'Sync:QualityDefinitions',
|
||||
meta: { instanceId: this.instanceId, updatedCount }
|
||||
meta: { instanceId: this.instanceId, configName, updatedCount }
|
||||
});
|
||||
|
||||
// PUT the full array back
|
||||
|
||||
Reference in New Issue
Block a user