mirror of
https://github.com/kikootwo/ReadMeABook.git
synced 2026-06-30 01:50:11 +00:00
Add Transmission/NZBGet and per-client paths and much more
Extend multi-download-client support to include Transmission and NZBGet and introduce per-client custom download paths. Adds protocol mapping and new client types, Transmission/NZBGet integration services, API CRUD and validation changes, UI components/modal updates and live path previews, and manager routing by protocol. Includes DB migrations (download_path on download_history, interactive_search_access on users), schema updates, and related processor/service fixes and tests to ensure backward compatibility and proper path resolution.
This commit is contained in:
@@ -5,6 +5,7 @@
|
||||
* Groups indexers by their category configuration to minimize API calls.
|
||||
* Indexers with identical categories are grouped together for a single search.
|
||||
* Supports separate audiobook and ebook category configurations per indexer.
|
||||
* Indexers with no categories for a given type are skipped (effectively disabled).
|
||||
*/
|
||||
|
||||
export type CategoryType = 'audiobook' | 'ebook';
|
||||
@@ -25,22 +26,33 @@ export interface IndexerGroup {
|
||||
indexers: IndexerConfig[];
|
||||
}
|
||||
|
||||
export interface GroupingResult {
|
||||
groups: IndexerGroup[];
|
||||
skippedIndexers: IndexerConfig[]; // Indexers skipped due to no categories for the type
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the appropriate categories from an indexer based on the category type.
|
||||
*
|
||||
* Returns empty array when the field is explicitly set to [] (user disabled this type).
|
||||
* Falls back to defaults only when the field is undefined/missing (legacy configs).
|
||||
*
|
||||
* @param indexer - The indexer configuration
|
||||
* @param type - The category type ('audiobook' or 'ebook')
|
||||
* @returns Array of category IDs
|
||||
* @returns Array of category IDs (empty = disabled for this type)
|
||||
*/
|
||||
export function getCategoriesForType(indexer: IndexerConfig, type: CategoryType): number[] {
|
||||
if (type === 'ebook') {
|
||||
return indexer.ebookCategories && indexer.ebookCategories.length > 0
|
||||
? indexer.ebookCategories
|
||||
: [7020]; // Default ebook category
|
||||
// Field exists (even if empty) — respect it
|
||||
if (Array.isArray(indexer.ebookCategories)) {
|
||||
return indexer.ebookCategories;
|
||||
}
|
||||
// Field missing — legacy config, use default
|
||||
return [7020];
|
||||
}
|
||||
|
||||
// Audiobook - check new field first, then legacy field
|
||||
if (indexer.audiobookCategories && indexer.audiobookCategories.length > 0) {
|
||||
// Audiobook — check new field first, then legacy field
|
||||
if (Array.isArray(indexer.audiobookCategories)) {
|
||||
return indexer.audiobookCategories;
|
||||
}
|
||||
if (indexer.categories && indexer.categories.length > 0) {
|
||||
@@ -52,57 +64,49 @@ export function getCategoriesForType(indexer: IndexerConfig, type: CategoryType)
|
||||
/**
|
||||
* Groups indexers by their category configuration.
|
||||
* Indexers with identical category arrays are grouped together.
|
||||
* Indexers with no categories for the specified type are skipped.
|
||||
*
|
||||
* @param indexers - Array of indexer configurations
|
||||
* @param type - The category type to group by ('audiobook' or 'ebook')
|
||||
* @returns Array of groups, each containing indexers with matching categories
|
||||
* @returns GroupingResult with groups and skipped indexers
|
||||
*
|
||||
* @example
|
||||
* const indexers = [
|
||||
* { id: 1, audiobookCategories: [3030], ebookCategories: [7020] },
|
||||
* { id: 2, audiobookCategories: [3030], ebookCategories: [7020] },
|
||||
* { id: 2, audiobookCategories: [3030], ebookCategories: [] },
|
||||
* { id: 3, audiobookCategories: [3030, 3010], ebookCategories: [7020] },
|
||||
* ];
|
||||
*
|
||||
* const audiobookGroups = groupIndexersByCategories(indexers, 'audiobook');
|
||||
* // Result:
|
||||
* // [
|
||||
* // { categories: [3030], indexerIds: [1, 2], indexers: [...] },
|
||||
* // { categories: [3030, 3010], indexerIds: [3], indexers: [...] }
|
||||
* // ]
|
||||
*
|
||||
* const ebookGroups = groupIndexersByCategories(indexers, 'ebook');
|
||||
* // Result:
|
||||
* // [
|
||||
* // { categories: [7020], indexerIds: [1, 2, 3], indexers: [...] }
|
||||
* // ]
|
||||
* const result = groupIndexersByCategories(indexers, 'ebook');
|
||||
* // result.groups: [{ categories: [7020], indexerIds: [1, 3], indexers: [...] }]
|
||||
* // result.skippedIndexers: [{ id: 2, ... }] (no ebook categories)
|
||||
*/
|
||||
export function groupIndexersByCategories(
|
||||
indexers: IndexerConfig[],
|
||||
type: CategoryType = 'audiobook'
|
||||
): IndexerGroup[] {
|
||||
// Map to track unique category combinations
|
||||
// Key: sorted category IDs as string (e.g., "3030,3010")
|
||||
// Value: array of indexers with those categories
|
||||
): GroupingResult {
|
||||
const groupMap = new Map<string, IndexerConfig[]>();
|
||||
const skippedIndexers: IndexerConfig[] = [];
|
||||
|
||||
for (const indexer of indexers) {
|
||||
// Get categories for the specified type
|
||||
const categories = getCategoriesForType(indexer, type);
|
||||
|
||||
// Skip indexers with no categories for this type (effectively disabled)
|
||||
if (categories.length === 0) {
|
||||
skippedIndexers.push(indexer);
|
||||
continue;
|
||||
}
|
||||
|
||||
// Sort categories to ensure consistent grouping
|
||||
// [3030, 3010] and [3010, 3030] should be the same group
|
||||
const sortedCategories = [...categories].sort((a, b) => a - b);
|
||||
const key = sortedCategories.join(',');
|
||||
|
||||
// Add indexer to group
|
||||
if (!groupMap.has(key)) {
|
||||
groupMap.set(key, []);
|
||||
}
|
||||
groupMap.get(key)!.push(indexer);
|
||||
}
|
||||
|
||||
// Convert map to array of groups
|
||||
const groups: IndexerGroup[] = [];
|
||||
for (const [key, indexersInGroup] of groupMap.entries()) {
|
||||
const categories = key.split(',').map(Number);
|
||||
@@ -115,7 +119,7 @@ export function groupIndexersByCategories(
|
||||
});
|
||||
}
|
||||
|
||||
return groups;
|
||||
return { groups, skippedIndexers };
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
Reference in New Issue
Block a user