Unify media_key and item_key — use item_key everywhere
media_key was a lossy shortening of item_key (libraryId:lastSegment) that introduced a real collision bug: two TV episodes from different series with the same filename would share the same media_key and each other's tags. - DB migration converts existing media_tags rows from short format to full item_key by joining against media_items; ambiguous/orphaned rows are dropped - media_tags column renamed media_key → item_key - Removed itemKeyToMediaKey() from scanner; reconcileAndPrune now passes item_key directly to reKeyMediaItem - DB reader functions (tv, movies, games) now expose item_key on returned entities; frontend components use entity.item_key instead of constructing the short libraryId:id form - MixedView now constructs the full mixed_file: item_key format - Tag API renamed mediaKey param → itemKey throughout Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -556,22 +556,6 @@ function detectMoves(
|
||||
* Tags on deleted items are intentionally left as orphans — harmless and
|
||||
* recoverable if the file reappears.
|
||||
*/
|
||||
/**
|
||||
* Converts an item_key (used in media_items) to the media_key format used in
|
||||
* media_tags. The UI constructs media_keys as `${libraryId}:${shortId}` where
|
||||
* shortId is only the terminal path segment — e.g.:
|
||||
* "lib1:movie:Inception%20(2010)" → "lib1:Inception%20(2010)"
|
||||
* "lib1:tv_episode:Show:S1:ep.mkv" → "lib1:ep.mkv"
|
||||
* "lib1:mixed_file:dir%2Ffile.mp4" → "lib1:dir%2Ffile.mp4"
|
||||
*/
|
||||
function itemKeyToMediaKey(itemKey: string): string {
|
||||
const firstColon = itemKey.indexOf(':')
|
||||
const lastColon = itemKey.lastIndexOf(':')
|
||||
const libraryId = itemKey.slice(0, firstColon)
|
||||
const shortId = itemKey.slice(lastColon + 1)
|
||||
return `${libraryId}:${shortId}`
|
||||
}
|
||||
|
||||
function reconcileAndPrune(
|
||||
db: Database.Database,
|
||||
libraryId: string,
|
||||
@@ -583,11 +567,8 @@ function reconcileAndPrune(
|
||||
// Apply moves first (outside transaction so console.log is visible as they happen)
|
||||
for (const { oldKey, newKey } of moves) {
|
||||
renameItem.run(newKey, oldKey)
|
||||
// Convert item_keys to the media_key format actually used in media_tags
|
||||
const oldMediaKey = itemKeyToMediaKey(oldKey)
|
||||
const newMediaKey = itemKeyToMediaKey(newKey)
|
||||
if (oldMediaKey !== newMediaKey) {
|
||||
reKeyMediaItem(oldMediaKey, newMediaKey)
|
||||
if (oldKey !== newKey) {
|
||||
reKeyMediaItem(oldKey, newKey)
|
||||
}
|
||||
console.log(`[scanner] fingerprint match: renamed "${oldKey}" → "${newKey}"`)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user