mirror of
https://github.com/kikootwo/ReadMeABook.git
synced 2026-06-04 05:10:11 +00:00
4b90b35748
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.
75 lines
2.1 KiB
TypeScript
75 lines
2.1 KiB
TypeScript
/**
|
|
* File Hash Utility
|
|
* Documentation: documentation/fixes/file-hash-matching.md
|
|
*
|
|
* Generates deterministic hashes of audio file collections for accurate library matching.
|
|
* Used to match RMAB-organized audiobooks with Audiobookshelf library items.
|
|
*/
|
|
|
|
import crypto from 'crypto';
|
|
import path from 'path';
|
|
import { AUDIO_EXTENSIONS } from '../constants/audio-formats';
|
|
|
|
/**
|
|
* Generates a SHA256 hash of audio filenames for library matching.
|
|
*
|
|
* Process:
|
|
* 1. Extract basenames from file paths
|
|
* 2. Filter to supported audio extensions
|
|
* 3. Normalize to lowercase
|
|
* 4. Sort alphabetically
|
|
* 5. Generate SHA256 hash
|
|
*
|
|
* @param filePaths - Array of absolute or relative file paths
|
|
* @returns 64-character hex string (SHA256 hash) or empty string if no audio files
|
|
*
|
|
* @example
|
|
* ```typescript
|
|
* const hash = generateFilesHash([
|
|
* '/path/to/Chapter 01.mp3',
|
|
* '/path/to/Chapter 02.mp3',
|
|
* '/path/to/cover.jpg' // Filtered out (not audio)
|
|
* ]);
|
|
* // Returns: "abc123def456..." (64 chars)
|
|
* ```
|
|
*/
|
|
export function generateFilesHash(filePaths: string[]): string {
|
|
if (!filePaths || filePaths.length === 0) {
|
|
return '';
|
|
}
|
|
|
|
// Extract basenames and filter to audio files only
|
|
const audioBasenames = filePaths
|
|
.map((filePath) => {
|
|
// Normalize path separators to forward slashes for cross-platform consistency
|
|
const normalizedPath = filePath.replace(/\\/g, '/');
|
|
return path.posix.basename(normalizedPath);
|
|
})
|
|
.filter((basename) => {
|
|
const ext = path.extname(basename).toLowerCase();
|
|
return (AUDIO_EXTENSIONS as readonly string[]).includes(ext);
|
|
})
|
|
.map((basename) => basename.toLowerCase()) // Normalize case
|
|
.sort(); // Sort alphabetically for deterministic hash
|
|
|
|
// No audio files found
|
|
if (audioBasenames.length === 0) {
|
|
return '';
|
|
}
|
|
|
|
// Generate SHA256 hash
|
|
const hash = crypto
|
|
.createHash('sha256')
|
|
.update(JSON.stringify(audioBasenames))
|
|
.digest('hex');
|
|
|
|
return hash;
|
|
}
|
|
|
|
/**
|
|
* Validates if a hash string is a valid SHA256 hash
|
|
*/
|
|
export function isValidHash(hash: string): boolean {
|
|
return /^[a-f0-9]{64}$/i.test(hash);
|
|
}
|