feat: prevent adding duplicate library items
Block saving a new item when one already exists, so no duplicate is created and existing progress is never overwritten. Matches by provider id (external_source/external_id) when available, otherwise by media type, year and title. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
3f07c78316
commit
767b0b50b0
3 changed files with 47 additions and 0 deletions
|
|
@ -416,6 +416,32 @@ void Database::loadCustomFields(MediaItem &item) {
|
|||
item.customFields.insert(q.value(0).toString(), q.value(1).toString());
|
||||
}
|
||||
|
||||
std::optional<Database::DuplicateMatch> 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<MediaItem> Database::loadItem(int id) {
|
||||
QSqlDatabase db = QSqlDatabase::database(m_connName);
|
||||
QSqlQuery q(db);
|
||||
|
|
|
|||
|
|
@ -43,6 +43,13 @@ public:
|
|||
bool deleteItem(int id);
|
||||
std::optional<MediaItem> 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<DuplicateMatch> findDuplicate(const MediaItem &item);
|
||||
|
||||
// Returns items (with full segment trees) matching the criteria.
|
||||
QVector<MediaItem> queryItems(const FilterCriteria &c);
|
||||
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue