File indexing completed on 2024-05-12 16:46:38

0001 /***************************************************************************
0002     Copyright (C) 2021 Robby Stephenson <robby@periapsis.org>
0003  ***************************************************************************/
0004 
0005 /***************************************************************************
0006  *                                                                         *
0007  *   This program is free software; you can redistribute it and/or         *
0008  *   modify it under the terms of the GNU General Public License as        *
0009  *   published by the Free Software Foundation; either version 2 of        *
0010  *   the License or (at your option) version 3 or any later version        *
0011  *   accepted by the membership of KDE e.V. (or its successor approved     *
0012  *   by the membership of KDE e.V.), which shall act as a proxy            *
0013  *   defined in Section 14 of version 3 of the license.                    *
0014  *                                                                         *
0015  *   This program is distributed in the hope that it will be useful,       *
0016  *   but WITHOUT ANY WARRANTY; without even the implied warranty of        *
0017  *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the         *
0018  *   GNU General Public License for more details.                          *
0019  *                                                                         *
0020  *   You should have received a copy of the GNU General Public License     *
0021  *   along with this program.  If not, see <http://www.gnu.org/licenses/>. *
0022  *                                                                         *
0023  ***************************************************************************/
0024 
0025 #include "mergeconflictresolver.h"
0026 #include "../collectionfactory.h"
0027 #include "../tellico_debug.h"
0028 
0029 using namespace Tellico;
0030 
0031 bool Merge::mergeEntry(Data::EntryPtr e1, Data::EntryPtr e2, Merge::ConflictResolver* resolver_) {
0032   if(!e1 || !e2) {
0033     myDebug() << "bad entry pointer";
0034     return false;
0035   }
0036   bool ret = true;
0037   foreach(Data::FieldPtr field, e1->collection()->fields()) {
0038     if(e2->field(field).isEmpty()) {
0039       continue;
0040     }
0041     // never try to merge entry id, creation date or mod date. Those are unique to each entry
0042     if(field->name() == QLatin1String("id") ||
0043        field->name() == QLatin1String("cdate") ||
0044        field->name() == QLatin1String("mdate")) {
0045       continue;
0046     }
0047 //    myLog() << "reading field: " << field->name();
0048     if(e1->field(field) == e2->field(field)) {
0049       continue;
0050     } else if(e1->field(field).isEmpty()) {
0051 //      myLog() << e1->title() << ": updating field(" << field->name() << ") to " << e2->field(field);
0052       e1->setField(field, e2->field(field));
0053       ret = true;
0054     } else if(field->type() == Data::Field::Table) {
0055       // if field F is a table-type field (album tracks, files, etc.), merge rows (keep their position)
0056       // if e1's F val in [row i, column j] empty, replace with e2's val at same position
0057       // if different (non-empty) vals at same position, CONFLICT!
0058       QStringList vals1 = FieldFormat::splitTable(e1->field(field));
0059       QStringList vals2 = FieldFormat::splitTable(e2->field(field));
0060       while(vals1.count() < vals2.count()) {
0061         vals1 += QString();
0062       }
0063       for(int i = 0; i < vals2.count(); ++i) {
0064         if(vals2[i].isEmpty()) {
0065           continue;
0066         }
0067         if(vals1[i].isEmpty()) {
0068           vals1[i] = vals2[i];
0069           ret = true;
0070         } else {
0071           QStringList parts1 = FieldFormat::splitRow(vals1[i]);
0072           QStringList parts2 = FieldFormat::splitRow(vals2[i]);
0073           bool changedPart = false;
0074           while(parts1.count() < parts2.count()) {
0075             parts1 += QString();
0076           }
0077           for(int j = 0; j < parts2.count(); ++j) {
0078             if(parts2[j].isEmpty()) {
0079               continue;
0080             }
0081             if(parts1[j].isEmpty()) {
0082               parts1[j] = parts2[j];
0083               changedPart = true;
0084             } else if(resolver_ && parts1[j] != parts2[j]) {
0085               const int resolverResponse = resolver_->resolve(e1, e2, field, parts1[j], parts2[j]);
0086               if(resolverResponse == Merge::ConflictResolver::CancelMerge) {
0087                 return false; // cancel all the merge right now
0088               } else if(resolverResponse == Merge::ConflictResolver::KeepSecond) {
0089                 parts1[j] = parts2[j];
0090                 changedPart = true;
0091               }
0092             }
0093           }
0094           if(changedPart) {
0095             vals1[i] = parts1.join(FieldFormat::columnDelimiterString());
0096             ret = true;
0097           }
0098         }
0099       }
0100       if(ret) {
0101         e1->setField(field, vals1.join(FieldFormat::rowDelimiterString()));
0102       }
0103 // remove the merging due to user comments
0104 // maybe in the future have a more intelligent way
0105 #if 0
0106     } else if(field->hasFlag(Data::Field::AllowMultiple)) {
0107       // if field F allows multiple values and not a Table (see above case),
0108       // e1's F values = (e1's F values) U (e2's F values) (union)
0109       // replace e1's field with union of e1's and e2's values for this field
0110       QStringList items1 = e1->fields(field, false);
0111       QStringList items2 = e2->fields(field, false);
0112       foreach(const QString& item2, items2) {
0113         // possible to have one value formatted and the other one not...
0114         if(!items1.contains(item2) && !items1.contains(Field::format(item2, field->formatType()))) {
0115           items1.append(item2);
0116         }
0117       }
0118 // not sure if I think it should be sorted or not
0119 //      items1.sort();
0120       e1->setField(field, items1.join(FieldFormat::delimiterString()));
0121       ret = true;
0122 #endif
0123     } else if(resolver_) {
0124       const int resolverResponse = resolver_->resolve(e1, e2, field);
0125       if(resolverResponse == Merge::ConflictResolver::CancelMerge) {
0126         return false; // cancel all the merge right now
0127       } else if(resolverResponse == Merge::ConflictResolver::KeepSecond) {
0128         e1->setField(field, e2->field(field));
0129       }
0130     } else {
0131 //      myDebug() << "Keeping value of" << field->name() << "for" << e1->field(QStringLiteral("title"));
0132     }
0133   }
0134   return ret;
0135 }
0136 
0137 QPair<Tellico::Data::FieldList, Tellico::Data::FieldList> Merge::mergeFields(Data::CollPtr coll_,
0138                                                                                 Data::FieldList fields_,
0139                                                                                 Data::EntryList entries_) {
0140   Data::FieldList modified, created;
0141   foreach(Data::FieldPtr field, fields_) {
0142     // don't add a field if it's a default field and not in the current collection
0143     if(coll_->hasField(field->name()) || CollectionFactory::isDefaultField(coll_->type(), field->name())) {
0144       // special case for choice fields, since we might want to add a value
0145       if(field->type() == Data::Field::Choice && coll_->hasField(field->name())) {
0146         // a2 are the existing fields in the collection, keep them in the same order
0147         QStringList a1 = coll_->fieldByName(field->name())->allowed();
0148         foreach(const QString& newAllowedValue, field->allowed()) {
0149           if(!a1.contains(newAllowedValue)) {
0150             // could be slow for large merges, but we do only want to add new value
0151             // IF that value is actually used by an entry
0152             foreach(Data::EntryPtr entry, entries_) {
0153               if(entry->field(field) == newAllowedValue) {
0154                 a1 += newAllowedValue;
0155                 break;
0156               }
0157             }
0158           }
0159         }
0160         if(a1.count() != coll_->fieldByName(field->name())->allowed().count()) {
0161           Data::FieldPtr f(new Data::Field(*coll_->fieldByName(field->name())));
0162           f->setAllowed(a1);
0163           modified.append(f);
0164         }
0165       }
0166       continue;
0167     }
0168     // add field if any values are not empty
0169     foreach(Data::EntryPtr entry, entries_) {
0170       if(!entry->field(field).isEmpty()) {
0171         created.append(Data::FieldPtr(new Data::Field(*field)));
0172         break;
0173       }
0174     }
0175   }
0176   return qMakePair(modified, created);
0177 }