diff --git a/src/core/Database.cpp b/src/core/Database.cpp index 2409b76..1d8ead0 100644 --- a/src/core/Database.cpp +++ b/src/core/Database.cpp @@ -416,6 +416,32 @@ void Database::loadCustomFields(MediaItem &item) { item.customFields.insert(q.value(0).toString(), q.value(1).toString()); } +std::optional Database::findDuplicate(const MediaItem &item) { + QSqlDatabase db = QSqlDatabase::database(m_connName); + QSqlQuery q(db); + // Provider-backed entries carry a stable external id; match on that first. + if (!item.externalId.isEmpty() && !item.externalSource.isEmpty()) { + q.prepare(QStringLiteral( + "SELECT id,title FROM media WHERE external_source=? AND external_id=? " + "AND id<>? LIMIT 1")); + q.addBindValue(item.externalSource); + q.addBindValue(item.externalId); + q.addBindValue(item.id); + } else { + // Manual entries: same media type, year and title (case-insensitive). + q.prepare(QStringLiteral( + "SELECT id,title FROM media WHERE type=? AND year=? " + "AND LOWER(TRIM(title))=LOWER(?) AND id<>? LIMIT 1")); + q.addBindValue(mediaTypeToString(item.type)); + q.addBindValue(item.year); + q.addBindValue(item.title.trimmed()); + q.addBindValue(item.id); + } + if (!q.exec() || !q.next()) + return std::nullopt; + return DuplicateMatch{ q.value(0).toInt(), q.value(1).toString() }; +} + std::optional Database::loadItem(int id) { QSqlDatabase db = QSqlDatabase::database(m_connName); QSqlQuery q(db); diff --git a/src/core/Database.h b/src/core/Database.h index 6c3f490..619559c 100644 --- a/src/core/Database.h +++ b/src/core/Database.h @@ -43,6 +43,13 @@ public: bool deleteItem(int id); std::optional loadItem(int id); + // Identifies an existing library item that a given item would duplicate. + struct DuplicateMatch { int id; QString title; }; + // Returns such a match, or nullopt. The item's own id is excluded (so an + // item never matches itself). Matches by (external_source, external_id) + // when both are set, otherwise by (type, title, year). + std::optional findDuplicate(const MediaItem &item); + // Returns items (with full segment trees) matching the criteria. QVector queryItems(const FilterCriteria &c); diff --git a/src/ui/EditDialog.cpp b/src/ui/EditDialog.cpp index 2948ea8..3588323 100644 --- a/src/ui/EditDialog.cpp +++ b/src/ui/EditDialog.cpp @@ -455,6 +455,20 @@ void EditDialog::accept() } m_item.segments = rebuilt; + // Block adding an item that already exists, so no duplicate is created and + // no existing progress gets overwritten. Only new items are checked; an + // edit keeps its own id and is therefore never flagged. + if (m_item.id < 0) { + if (auto dup = m_db->findDuplicate(m_item)) { + QMessageBox::warning(this, windowTitle(), + QStringLiteral("„%1\" ist bereits in deiner Bibliothek vorhanden.\n\n" + "Es wurde nichts hinzugefügt, damit dein bestehender " + "Fortschritt nicht überschrieben wird.") + .arg(dup->title)); + return; + } + } + if (!m_item.dateAdded.isValid()) m_item.dateAdded = QDateTime::currentDateTime();