/************************************************************************ MeOS - Orienteering Software Copyright (C) 2009-2020 Melin Software HB This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License fro more details. You should have received a copy of the GNU General Public License along with this program. If not, see . Melin Software HB - software@melin.nu - www.melin.nu Eksoppsvägen 16, SE-75646 UPPSALA, Sweden ************************************************************************/ #include "stdafx.h" #include #include #include #include #include #include #include "oEvent.h" #include "oDataContainer.h" #include "metalist.h" #include "generalresult.h" #include "meosException.h" #include "meos.h" #include "meos_util.h" #include "intkeymapimpl.hpp" #include "MeOSFeatures.h" void oEvent::merge(oEvent &src, int &numAdd, int &numRemove, int &numUpdate) { numAdd = 0; numRemove = 0; numUpdate = 0; wstring mergeTag = src.getMergeTag(); wstring mergeTime = getMergeInfo(mergeTag); wstring baseTime, reverseMerge; string addMinTime = src.getLastModified(); if (src.currentNameId == currentNameId) { // Get base version time if (baseTime.empty()) { baseTime = src.getDCI().getString("ImportStamp"); if (baseTime.empty()) baseTime = getDCI().getString("ImportStamp"); } reverseMerge = src.getMergeInfo(getMergeTag()); if (reverseMerge.empty()) reverseMerge = baseTime; } if (mergeTime.empty()) mergeTime = baseTime; string previousMergeTime(mergeTime.begin(), mergeTime.end()); string thisMergeTime; string bt(reverseMerge.begin(), reverseMerge.end()); set rControl, rRunner, rTeam, rCourse, rClub, rClass; auto updateNewItem = [&addMinTime, &numAdd](oBase *pNew, const oBase &src) { if (pNew) { numAdd++; pNew->merge(src); pNew->synchronize(); if (pNew->Modified.getStamp() < addMinTime) pNew->Modified.setStamp(addMinTime); } }; auto mergeItem = [&numUpdate](oBase *pExisting, const oBase &src) { numUpdate++; string oldStamp = pExisting->Modified.getStamp(); pExisting->merge(src); if (pExisting->Modified.getStamp() < oldStamp) pExisting->Modified.setStamp(oldStamp); }; auto computeRemove = [&bt](const auto &list, const set &existing, set &remove) { for (auto &c : list) { if (!c.isRemoved() && !existing.count(c.Id) && c.getStamp() < bt) remove.insert(c.Id); } }; { map ctrl; for (oControl &c : Controls) { if (!c.isRemoved()) ctrl[c.Id] = &c; } set srcControl; for (const oControl &c : src.Controls) { const string &stmp = c.getStamp(); if (!c.isRemoved()) { if (stmp > previousMergeTime) { if (stmp > thisMergeTime) thisMergeTime = stmp; auto mc = ctrl.find(c.Id); if (mc != ctrl.end()) { mergeItem(mc->second, c); } else { pControl pNew = addControl(c); updateNewItem(pNew, c); } } srcControl.insert(c.Id); } } computeRemove(Controls, srcControl, rControl); } { map crs; for (oCourse &c : Courses) { if (!c.isRemoved()) crs[c.Id] = &c; } set srcCourse; for (const oCourse &c : src.Courses) { const string &stmp = c.getStamp(); if (!c.isRemoved()) { bool okMerge = stmp > previousMergeTime; if (stmp > thisMergeTime) thisMergeTime = stmp; auto mc = crs.find(c.Id); if (mc != crs.end()) { if (okMerge) mergeItem(mc->second, c); } else if (okMerge) { pCourse pNew = addCourse(c); updateNewItem(pNew, c); } srcCourse.insert(c.Id); } } computeRemove(Courses, srcCourse, rCourse); } { map cls; map clsN; for (oClass &c : Classes) { if (!c.isRemoved()) { cls[c.Id] = &c; clsN[c.Name] = &c; } } set srcClass; for (oClass &c : src.Classes) { const string &stmp = c.getStamp(); bool merged = false; if (!c.isRemoved()) { bool okMerge = stmp > previousMergeTime; if (stmp > thisMergeTime) thisMergeTime = stmp; auto mc = cls.find(c.Id); if (mc != cls.end()) { if (compareClassName(mc->second->Name, c.Name)) { if (okMerge) mergeItem(mc->second, c); merged = true; } } auto updateIdCls = [&](int id) { pClass other = src.getClass(id); if (other) other->changeId(c.Id); c.changeId(id); }; if (!merged) { auto mcN = clsN.find(c.Name); if (mcN != clsN.end()) { if (okMerge) mergeItem(mcN->second, c); merged = true; updateIdCls(mcN->second->Id); } } if (!merged && okMerge) { if (cls.count(c.Id)) { int newId = max(getFreeClassId(), src.getFreeClassId()); c.changeId(newId); } pClass pNew = addClass(c); updateNewItem(pNew, c); } srcClass.insert(c.Id); } } computeRemove(Classes, srcClass, rClass); } { // Removing card not supported --> maybe too riskful... map crd; map, pCard> crdByHash; for (oCard &c : Cards) { if (!c.isRemoved()) { crd[c.Id] = &c; crdByHash[c.getCardHash()] = &c; } } for (oCard &c : src.Cards) { const string &stmp = c.getStamp(); if (!c.isRemoved() && stmp > previousMergeTime) { if (stmp > thisMergeTime) thisMergeTime = stmp; bool merged = false; auto mc = crd.find(c.Id); if (mc != crd.end()) { auto p1 = c.getNumPunches() > 0 ? c.getPunchByIndex(c.getNumPunches() - 1)->getTimeInt() : 0; auto c2 = mc->second; auto p2 = c2->getNumPunches() > 0 ? c2->getPunchByIndex(c2->getNumPunches() - 1)->getTimeInt() : 0; if (p1 == p2) mergeItem(mc->second, c), merged = true; } auto updateIdCrd = [&](int id) { pCard other = src.getCard(id); if (other) other->changeId(c.Id); c.changeId(id); }; if (!merged) { auto mcN = crdByHash.find(c.getCardHash()); if (mcN != crdByHash.end()) { mergeItem(mcN->second, c); merged = true; updateIdCrd(mcN->second->Id); } } if (!merged) { if (crd.count(c.Id)) { int newId = max(getFreeCardId(), src.getFreeCardId()); c.changeId(newId); } pCard pNew = addCard(c); updateNewItem(pNew, c); } } } } { map clb; map clbByExt; map clbByName; for (oClub &c : Clubs) { if (!c.isRemoved()) { clb[c.Id] = &c; if (c.getExtIdentifier() != 0) clbByExt[c.getExtIdentifier()] = &c; clbByName[c.getName()] = &c; } } set srcClub; for (oClub &c : src.Clubs) { const string &stmp = c.getStamp(); if (!c.isRemoved()) { bool okMerge = stmp > previousMergeTime; if (stmp > thisMergeTime) thisMergeTime = stmp; bool merged = false; auto mc = clb.find(c.Id); if (mc != clb.end()) { if ((c.getExtIdentifier() != 0 && c.getExtIdentifier() == mc->second->getExtIdentifier()) || c.getName() == mc->second->getName()) { if (okMerge) mergeItem(mc->second, c); merged = true; } } auto updateIdClb = [&](int id) { pClub other = src.getClub(id); if (other) other->changeId(c.Id); c.changeId(id); }; if (!merged && c.getExtIdentifier() != 0) { auto mcN = clbByExt.find(c.getExtIdentifier()); if (mcN != clbByExt.end()) { if (okMerge) mergeItem(mcN->second, c); merged = true; updateIdClb(mcN->second->Id); } } if (!merged) { auto mcN = clbByName.find(c.getName()); if (mcN != clbByName.end()) { if (okMerge) mergeItem(mcN->second, c); merged = true; updateIdClb(mcN->second->Id); } } if (!merged && okMerge) { if (clb.count(c.Id)) { int newId = max(getFreeClubId(), src.getFreeClubId()); c.changeId(newId); } pClub pNew = addClub(c); updateNewItem(pNew, c); } srcClub.insert(c.Id); } } computeRemove(Clubs, srcClub, rClub); } { map rn; map rnByExt; map, pRunner> rnByCardName; for (oRunner &r : Runners) { if (!r.isRemoved()) { rn[r.Id] = &r; if (r.getExtIdentifier() != 0) rnByExt[r.getExtIdentifier()] = &r; rnByCardName[make_pair(r.getCardNo(), r.sName)] = &r; } } set srcRunner; for (oRunner &r : src.Runners) { const string &stmp = r.getStamp(); if (!r.isRemoved()) { bool okMerge = stmp > previousMergeTime; if (stmp > thisMergeTime) thisMergeTime = stmp; bool merged = false; auto mc = rn.find(r.Id); if (mc != rn.end()) { if ((r.getExtIdentifier() != 0 && r.getExtIdentifier() == mc->second->getExtIdentifier()) || (r.sName == mc->second->sName && r.getClubId() == mc->second->getClubId()) || r.getCardNo() == mc->second->getCardNo() || mc->second->isVacant()) { if (okMerge) mergeItem(mc->second, r); merged = true; } } auto updateIdR = [&](int id) { pRunner other = src.getRunner(id, 0); if (other) other->changeId(src.Id); r.changeId(id); }; if (!merged && r.getExtIdentifier() != 0) { auto mcN = rnByExt.find(r.getExtIdentifier()); if (mcN != rnByExt.end()) { if (okMerge) mergeItem(mcN->second, r); merged = true; updateIdR(mcN->second->Id); } } if (!merged) { auto mcN = rnByCardName.find(make_pair(r.getCardNo(), r.sName)); if (mcN != rnByCardName.end()) { if (okMerge) mergeItem(mcN->second, r); merged = true; updateIdR(mcN->second->Id); } } if (!merged && okMerge) { if (rn.count(r.Id)) { int newId = max(getFreeRunnerId(), src.getFreeRunnerId()); r.changeId(newId); } pRunner pNew = addRunner(r, false); updateNewItem(pNew, r); } srcRunner.insert(r.Id); } } computeRemove(Runners, srcRunner, rRunner); } { map tm; map, pTeam> tmByClassName; for (oTeam &t : Teams) { if (!t.isRemoved()) { tm[t.Id] = &t; tmByClassName[make_pair(t.getClassId(false), t.getName())] = &t; } } set srcTeam; for (oTeam &t : src.Teams) { const string &stmp = t.getStamp(); if (!t.isRemoved()) { bool okMerge = stmp > previousMergeTime; if (stmp > thisMergeTime) thisMergeTime = stmp; bool merged = false; auto mc = tm.find(t.Id); if (mc != tm.end()) { if (t.getClubId() == mc->second->getClubId()) { if (okMerge) mergeItem(mc->second, t); merged = true; } } auto updateIdT = [&](int id) { pTeam other = src.getTeam(id); if (other) other->changeId(src.Id); t.changeId(id); }; if (!merged) { auto mcN = tmByClassName.find(make_pair(t.getClassId(false), t.getName())); if (mcN != tmByClassName.end()) { if (okMerge) mergeItem(mcN->second, t); merged = true; updateIdT(mcN->second->Id); } } if (!merged && okMerge) { if (tm.count(t.Id)) { int newId = max(getFreeTeamId(), src.getFreeTeamId()); t.changeId(newId); } pTeam pNew = addTeam(t, false); updateNewItem(pNew, t); } srcTeam.insert(t.Id); } } computeRemove(Teams, srcTeam, rTeam); } auto removeEnts = [&numRemove](const set &ids, auto get) { for (int id : ids) { pBase b = get(id); if (b && b->canRemove()) { b->remove(); numRemove++; } } }; removeEnts(rTeam, [this](int id) -> pBase {return getTeam(id); }); removeEnts(rRunner, [this](int id) -> pBase {return getRunner(id, 0); }); removeEnts(rClub, [this](int id) -> pBase {return getClub(id); }); removeEnts(rClass, [this](int id) -> pBase {return getClass(id); }); removeEnts(rCourse, [this](int id) -> pBase {return getCourse(id); }); removeEnts(rControl, [this](int id) -> pBase {return getControl(id); }); wstring mtOut(thisMergeTime.begin(), thisMergeTime.end()); addMergeInfo(mergeTag, mtOut); synchronize(); } wstring oEvent::cloneCompetition(bool cloneRunners, bool cloneTimes, bool cloneCourses, bool cloneResult, bool addToDate) { if (cloneResult) { cloneTimes = true; cloneCourses = true; } if (cloneTimes) cloneRunners = true; oEvent ce(gdibase); ce.newCompetition(Name); ce.ZeroTime = ZeroTime; ce.Date = Date; if (addToDate) { SYSTEMTIME st; convertDateYMS(Date, st, false); __int64 absD = SystemTimeToInt64Second(st); absD += 3600 * 24; ce.Date = convertSystemDate(Int64SecondToSystemTime(absD)); } int len = Name.length(); if (len > 2 && isdigit(Name[len - 1]) && !isdigit(Name[len - 2])) { ++ce.Name[len - 1]; // E1 -> E2 } else ce.Name += L" E2"; memcpy(ce.oData, oData, sizeof(oData)); for (oClubList::iterator it = Clubs.begin(); it != Clubs.end(); ++it) { if (it->isRemoved()) continue; pClub pc = ce.addClub(it->name, it->Id); memcpy(pc->oData, it->oData, sizeof(pc->oData)); } if (cloneCourses) { for (oControlList::iterator it = Controls.begin(); it != Controls.end(); ++it) { if (it->isRemoved()) continue; pControl pc = ce.addControl(it->Id, 100, it->Name); pc->setNumbers(it->codeNumbers()); pc->Status = it->Status; memcpy(pc->oData, it->oData, sizeof(pc->oData)); } for (oCourseList::iterator it = Courses.begin(); it != Courses.end(); ++it) { if (it->isRemoved()) continue; pCourse pc = ce.addCourse(it->Name, it->Length, it->Id); pc->importControls(it->getControls(), false, false); pc->legLengths = it->legLengths; memcpy(pc->oData, it->oData, sizeof(pc->oData)); } } for (oClassList::iterator it = Classes.begin(); it != Classes.end(); ++it) { if (it->isRemoved()) continue; pClass pc = ce.addClass(it->Name, 0, it->Id); memcpy(pc->oData, it->oData, sizeof(pc->oData)); pc->setNumStages(it->getNumStages()); pc->legInfo = it->legInfo; if (cloneCourses) { pc->Course = ce.getCourse(it->getCourseId()); pc->MultiCourse = it->MultiCourse; // Points to wrong competition, but valid for now... } } if (cloneRunners) { for (oRunnerList::iterator it = Runners.begin(); it != Runners.end(); ++it) { if (it->isRemoved()) continue; oRunner r(&ce, it->Id); r.sName = it->sName; r.getRealName(r.sName, r.tRealName); r.StartNo = it->StartNo; r.cardNumber = it->cardNumber; r.Club = ce.getClub(it->getClubId()); r.Class = ce.getClass(it->getClassId(false)); if (cloneCourses) r.Course = ce.getCourse(it->getCourseId()); pRunner pr = ce.addRunner(r, false); pr->decodeMultiR(it->codeMultiR()); memcpy(pr->oData, it->oData, sizeof(pr->oData)); if (cloneTimes) { pr->startTime = it->startTime; } if (cloneResult) { if (it->Card) { pr->Card = ce.addCard(*it->Card); pr->Card->tOwner = pr; } pr->FinishTime = it->FinishTime; pr->status = it->status; } } for (oTeamList::iterator it = Teams.begin(); it != Teams.end(); ++it) { if (it->skip()) continue; oTeam t(&ce, it->Id); t.sName = it->sName; t.StartNo = it->StartNo; t.Club = ce.getClub(it->getClubId()); t.Class = ce.getClass(it->getClassId(false)); if (cloneTimes) t.startTime = it->startTime; pTeam pt = ce.addTeam(t, false); memcpy(pt->oData, it->oData, sizeof(pt->oData)); pt->Runners.resize(it->Runners.size()); for (size_t k = 0; kRunners.size(); k++) { int id = it->Runners[k] ? it->Runners[k]->Id : 0; if (id) pt->Runners[k] = ce.getRunner(id, 0); } t.apply(ChangeType::Update, nullptr); } for (oRunnerList::iterator it = ce.Runners.begin(); it != ce.Runners.end(); ++it) { it->createMultiRunner(false, false); } } vector changedClass, changedClassNoResult, assignedVacant, newEntries, notTransfered, noAssign; set dummy; transferResult(ce, dummy, TransferAnyway, false, changedClass, changedClassNoResult, assignedVacant, newEntries, notTransfered, noAssign); vector newEntriesT, notTransferedT, noAssignT; transferResult(ce, TransferAnyway, newEntriesT, notTransferedT, noAssignT); int eventNumberCurrent = getStageNumber(); if (eventNumberCurrent <= 0) { eventNumberCurrent = 1; setStageNumber(eventNumberCurrent); } ce.getDI().setString("PreEvent", currentNameId); ce.setStageNumber(eventNumberCurrent + 1); getDI().setString("PostEvent", ce.currentNameId); int nf = getMeOSFeatures().getNumFeatures(); for (int k = 0; k < nf; k++) { MeOSFeatures::Feature f = getMeOSFeatures().getFeature(k); if (getMeOSFeatures().hasFeature(f)) ce.getMeOSFeatures().useFeature(f, true, ce); } // Transfer lists and list configurations. if (listContainer) { loadGeneralResults(false, false); swap(ce.generalResults, generalResults); try { ce.listContainer = new MetaListContainer(&ce, *listContainer); ce.save(); } catch (...) { swap(ce.generalResults, generalResults); throw; } swap(ce.generalResults, generalResults); } return ce.CurrentFile; } void oEvent::transferListsAndSave(const oEvent &src) { src.loadGeneralResults(false, false); swap(src.generalResults, generalResults); try { src.getListContainer().synchronizeTo(getListContainer()); save(); } catch (...) { swap(src.generalResults, generalResults); throw; } swap(src.generalResults, generalResults); } bool checkTargetClass(pRunner target, pRunner source, const oClassList &Classes, const vector &targetVacant, vector &changedClass, oEvent::ChangedClassMethod changeClassMethod) { if (changeClassMethod == oEvent::TransferAnyway) return true; if (!compareClassName(target->getClass(false), source->getClass(false))) { // Store all vacant positions in the right class int targetClass = -1; if (target->getStatus() == StatusOK) { // There is already a result. Do not change class! return false; } if (changeClassMethod == oEvent::TransferNoResult) return false; // Do not allow change class, do not transfer result for (oClassList::const_iterator cit = Classes.begin(); cit != Classes.end(); ++cit) { if (cit->isRemoved()) continue; if (compareClassName(cit->getName(), source->getClass(false))) { targetClass = cit->getId(); if (targetClass == source->getClassId(false) || cit->getName() == source->getClass(false)) break; // Assume exact match } } if (targetClass != -1) { set vacantIx; for (size_t j = 0; j < targetVacant.size(); j++) { if (!targetVacant[j]) continue; if (targetVacant[j]->getClassId(false) == targetClass) vacantIx.insert(j); } int posToUse = -1; if (vacantIx.size() == 1) posToUse = *vacantIx.begin(); else if (vacantIx.size() > 1) { wstring srcBib = source->getBib(); if (srcBib.length() > 0) { for (set::iterator tit = vacantIx.begin(); tit != vacantIx.end(); ++tit) { if (targetVacant[*tit]->getBib() == srcBib) { posToUse = *tit; break; } } } if (posToUse == -1) posToUse = *vacantIx.begin(); } if (posToUse != -1) { // Change class or change class vacant changedClass.push_back(target); int oldStart = target->getStartTime(); wstring oldBib = target->getBib(); int oldSN = target->getStartNo(); int oldClass = target->getClassId(false); pRunner tgt = targetVacant[posToUse]; target->cloneStartTime(tgt); target->setBib(tgt->getBib(), 0, false); target->setStartNo(tgt->getStartNo(), oBase::ChangeType::Update); target->setClassId(tgt->getClassId(false), false); tgt->setStartTime(oldStart, true, oBase::ChangeType::Update); tgt->setBib(oldBib, 0, false); tgt->setStartNo(oldSN, oBase::ChangeType::Update); tgt->setClassId(oldClass, false); return true; // Changed to correct class } else if (changeClassMethod == oEvent::ChangeClass) { // Simpliy change class target->setClassId(targetClass, false); return true; } } return false; // Wrong class, ChangeClass (but failed) } return true; // Same class, OK } void oEvent::transferResult(oEvent &ce, const set &allowNewEntries, ChangedClassMethod changeClassMethod, bool transferAllNoCompete, vector &changedClass, vector &changedClassNoResult, vector &assignedVacant, vector &newEntries, vector ¬Transfered, vector &noAssignmentTarget) { inthashmap processed(ce.Runners.size()); inthashmap used(Runners.size()); changedClass.clear(); changedClassNoResult.clear(); assignedVacant.clear(); newEntries.clear(); notTransfered.clear(); noAssignmentTarget.clear(); vector targetRunners; vector targetVacant; targetRunners.reserve(ce.Runners.size()); for (oRunnerList::iterator it = ce.Runners.begin(); it != ce.Runners.end(); ++it) { if (!it->skip()) { if (!it->isVacant()) targetRunners.push_back(&*it); else targetVacant.push_back(&*it); } } calculateResults({}, ResultType::TotalResult); // Lookup by id for (size_t k = 0; k < targetRunners.size(); k++) { pRunner it = targetRunners[k]; pRunner r = getRunner(it->Id, 0); if (!r) continue; __int64 id1 = r->getExtIdentifier(); __int64 id2 = it->getExtIdentifier(); if (id1>0 && id2>0 && id1 != id2) continue; wstring cnA = canonizeName(it->sName.c_str()); wstring cnB = canonizeName(r->sName.c_str()); wstring ccnA = canonizeName(it->getClub().c_str()); wstring ccnB = canonizeName(r->getClub().c_str()); if ((id1>0 && id1 == id2) || (r->cardNumber>0 && r->cardNumber == it->cardNumber) || (it->sName == r->sName) || (cnA == cnB && ccnA == ccnB)) { processed.insert(it->Id, 1); used.insert(r->Id, 1); if (checkTargetClass(it, r, ce.Classes, targetVacant, changedClass, changeClassMethod)) it->setInputData(*r); else { it->resetInputData(); changedClassNoResult.push_back(it); } } } if (processed.size() < int(targetRunners.size())) { // Lookup by card int v; for (size_t k = 0; k < targetRunners.size(); k++) { pRunner it = targetRunners[k]; if (processed.lookup(it->Id, v)) continue; if (it->getCardNo() > 0) { pRunner r = getRunnerByCardNo(it->getCardNo(), 0, CardLookupProperty::Any); if (!r || used.lookup(r->Id, v)) continue; __int64 id1 = r->getExtIdentifier(); __int64 id2 = it->getExtIdentifier(); if (id1>0 && id2>0 && id1 != id2) continue; if ((id1>0 && id1 == id2) || (it->sName == r->sName && it->getClub() == r->getClub())) { processed.insert(it->Id, 1); used.insert(r->Id, 1); if (checkTargetClass(it, r, ce.Classes, targetVacant, changedClass, changeClassMethod)) it->setInputData(*r); else { it->resetInputData(); changedClassNoResult.push_back(it); } } } } } int v = -1; // Store remaining runners vector remainingRunners; for (oRunnerList::iterator it2 = Runners.begin(); it2 != Runners.end(); ++it2) { if (it2->skip() || used.lookup(it2->Id, v)) continue; if (it2->isVacant()) continue; // Ignore vacancies on source side remainingRunners.push_back(&*it2); } if (processed.size() < int(targetRunners.size()) && !remainingRunners.empty()) { // Lookup by name / ext id vector cnd; for (size_t k = 0; k < targetRunners.size(); k++) { pRunner it = targetRunners[k]; if (processed.lookup(it->Id, v)) continue; __int64 id1 = it->getExtIdentifier(); cnd.clear(); for (size_t j = 0; j < remainingRunners.size(); j++) { pRunner src = remainingRunners[j]; if (!src) continue; if (id1 > 0) { __int64 id2 = src->getExtIdentifier(); if (id2 == id1) { cnd.clear(); cnd.push_back(j); break; //This is the one, if they have the same Id there will be a unique match below } } if (it->sName == src->sName && it->getClub() == src->getClub()) cnd.push_back(j); } if (cnd.size() == 1) { pRunner &src = remainingRunners[cnd[0]]; processed.insert(it->Id, 1); used.insert(src->Id, 1); if (checkTargetClass(it, src, ce.Classes, targetVacant, changedClass, changeClassMethod)) { it->setInputData(*src); } else { it->resetInputData(); changedClassNoResult.push_back(it); } src = 0; } else if (cnd.size() > 0) { // More than one candidate int winnerIx = -1; int point = -1; for (size_t j = 0; j < cnd.size(); j++) { pRunner src = remainingRunners[cnd[j]]; int p = 0; if (src->getClass(false) == it->getClass(false)) p += 1; if (src->getBirthYear() == it->getBirthYear()) p += 2; if (p > point) { winnerIx = cnd[j]; point = p; } } if (winnerIx != -1) { processed.insert(it->Id, 1); pRunner winner = remainingRunners[winnerIx]; remainingRunners[winnerIx] = 0; used.insert(winner->Id, 1); if (checkTargetClass(it, winner, ce.Classes, targetVacant, changedClass, changeClassMethod)) { it->setInputData(*winner); } else { it->resetInputData(); changedClassNoResult.push_back(it); } } } } } // Transfer vacancies for (size_t k = 0; k < remainingRunners.size(); k++) { pRunner src = remainingRunners[k]; if (!src || used.lookup(src->Id, v)) continue; bool forceSkip = src->hasFlag(oAbstractRunner::FlagTransferSpecified) && !src->hasFlag(oAbstractRunner::FlagTransferNew); if (forceSkip) { notTransfered.push_back(src); continue; } pRunner targetVacant = ce.getRunner(src->getId(), 0); if (targetVacant && targetVacant->isVacant() && compareClassName(targetVacant->getClass(false), src->getClass(false))) { targetVacant->setName(src->getName(), false); targetVacant->setClub(src->getClub()); targetVacant->setCardNo(src->getCardNo(), false); targetVacant->cloneData(src); assignedVacant.push_back(targetVacant); } else { pClass dstClass = ce.getClass(src->getClassId(false)); if (dstClass && compareClassName(dstClass->getName(), src->getClass(false))) { if ((!src->hasFlag(oAbstractRunner::FlagTransferSpecified) && allowNewEntries.count(src->getClassId(false))) || src->hasFlag(oAbstractRunner::FlagTransferNew)) { if (src->getClubId() > 0) ce.getClubCreate(src->getClubId(), src->getClub()); pRunner dst = ce.addRunner(src->getName(), src->getClubId(), src->getClassId(false), src->getCardNo(), src->getBirthYear(), true); dst->cloneData(src); dst->setInputData(*src); newEntries.push_back(dst); } else if (transferAllNoCompete) { if (src->getClubId() > 0) ce.getClubCreate(src->getClubId(), src->getClub()); pRunner dst = ce.addRunner(src->getName(), src->getClubId(), src->getClassId(false), 0, src->getBirthYear(), true); dst->cloneData(src); dst->setInputData(*src); dst->setStatus(StatusNotCompetiting, true, ChangeType::Update); notTransfered.push_back(dst); } else notTransfered.push_back(src); } } } // Runners on target side not assigned a result for (size_t k = 0; k < targetRunners.size(); k++) { if (targetRunners[k] && !processed.count(targetRunners[k]->Id)) { noAssignmentTarget.push_back(targetRunners[k]); if (targetRunners[k]->inputStatus == StatusUnknown || (targetRunners[k]->inputStatus == StatusOK && targetRunners[k]->inputTime == 0)) { targetRunners[k]->inputStatus = StatusNotCompetiting; } } } } void oEvent::transferResult(oEvent &ce, ChangedClassMethod changeClassMethod, vector &newEntries, vector ¬Transfered, vector &noAssignmentTarget) { inthashmap processed(ce.Teams.size()); inthashmap used(Teams.size()); newEntries.clear(); notTransfered.clear(); noAssignmentTarget.clear(); vector targetTeams; targetTeams.reserve(ce.Teams.size()); for (oTeamList::iterator it = ce.Teams.begin(); it != ce.Teams.end(); ++it) { if (!it->skip()) { targetTeams.push_back(&*it); } } calculateTeamResults(set(), ResultType::TotalResult); // Lookup by id for (size_t k = 0; k < targetTeams.size(); k++) { pTeam it = targetTeams[k]; pTeam t = getTeam(it->Id); if (!t) continue; __int64 id1 = t->getExtIdentifier(); __int64 id2 = it->getExtIdentifier(); if (id1>0 && id2>0 && id1 != id2) continue; if ((id1>0 && id1 == id2) || (it->sName == t->sName && it->getClub() == t->getClub())) { processed.insert(it->Id, 1); used.insert(t->Id, 1); it->setInputData(*t); //checkTargetClass(it, r, ce.Classes, targetVacant, changedClass); } } int v = -1; // Store remaining runners vector remainingTeams; for (oTeamList::iterator it2 = Teams.begin(); it2 != Teams.end(); ++it2) { if (it2->skip() || used.lookup(it2->Id, v)) continue; if (it2->isVacant()) continue; // Ignore vacancies on source side remainingTeams.push_back(&*it2); } if (processed.size() < int(targetTeams.size()) && !remainingTeams.empty()) { // Lookup by name / ext id vector cnd; for (size_t k = 0; k < targetTeams.size(); k++) { pTeam it = targetTeams[k]; if (processed.lookup(it->Id, v)) continue; __int64 id1 = it->getExtIdentifier(); cnd.clear(); for (size_t j = 0; j < remainingTeams.size(); j++) { pTeam src = remainingTeams[j]; if (!src) continue; if (id1 > 0) { __int64 id2 = src->getExtIdentifier(); if (id2 == id1) { cnd.clear(); cnd.push_back(j); break; //This is the one, if they have the same Id there will be a unique match below } } if (it->sName == src->sName && it->getClub() == src->getClub()) cnd.push_back(j); } if (cnd.size() == 1) { pTeam &src = remainingTeams[cnd[0]]; processed.insert(it->Id, 1); used.insert(src->Id, 1); it->setInputData(*src); //checkTargetClass(it, src, ce.Classes, targetVacant, changedClass); src = 0; } else if (cnd.size() > 0) { // More than one candidate int winnerIx = -1; int point = -1; for (size_t j = 0; j < cnd.size(); j++) { pTeam src = remainingTeams[cnd[j]]; int p = 0; if (src->getClass(false) == it->getClass(false)) p += 1; if (p > point) { winnerIx = cnd[j]; point = p; } } if (winnerIx != -1) { processed.insert(it->Id, 1); pTeam winner = remainingTeams[winnerIx]; remainingTeams[winnerIx] = 0; used.insert(winner->Id, 1); it->setInputData(*winner); //checkTargetClass(it, winner, ce.Classes, targetVacant, changedClass); } } } } /* // Transfer vacancies for (size_t k = 0; k < remainingRunners.size(); k++) { pRunner src = remainingRunners[k]; if (!src || used.lookup(src->Id, v)) continue; pRunner targetVacant = ce.getRunner(src->getId(), 0); if (targetVacant && targetVacant->isVacant() && compareClassName(targetVacant->getClass(), src->getClass()) ) { targetVacant->setName(src->getName()); targetVacant->setClub(src->getClub()); targetVacant->setCardNo(src->getCardNo(), false); targetVacant->cloneData(src); assignedVacant.push_back(targetVacant); } else { pClass dstClass = ce.getClass(src->getClassId()); if (dstClass && compareClassName(dstClass->getName(), src->getClass())) { if (allowNewEntries.count(src->getClassId())) { if (src->getClubId() > 0) ce.getClubCreate(src->getClubId(), src->getClub()); pRunner dst = ce.addRunner(src->getName(), src->getClubId(), src->getClassId(), src->getCardNo(), src->getBirthYear(), true); dst->cloneData(src); dst->setInputData(*src); newEntries.push_back(dst); } else if (transferAllNoCompete) { if (src->getClubId() > 0) ce.getClubCreate(src->getClubId(), src->getClub()); pRunner dst = ce.addRunner(src->getName(), src->getClubId(), src->getClassId(), 0, src->getBirthYear(), true); dst->cloneData(src); dst->setInputData(*src); dst->setStatus(StatusNotCompetiting); notTransfered.push_back(dst); } else notTransfered.push_back(src); } } } // Runners on target side not assigned a result for (size_t k = 0; k < targetRunners.size(); k++) { if (targetRunners[k] && !processed.count(targetRunners[k]->Id)) { noAssignmentTarget.push_back(targetRunners[k]); if (targetRunners[k]->inputStatus == StatusUnknown || (targetRunners[k]->inputStatus == StatusOK && targetRunners[k]->inputTime == 0)) { targetRunners[k]->inputStatus = StatusNotCompetiting; } } }*/ } void oAbstractRunner::merge(const oBase &input) { const oAbstractRunner &src = dynamic_cast(input); setName(src.sName, false); setStartTime(src.startTime, true, ChangeType::Update, false); setFinishTime(src.FinishTime); setStatus(src.status, true, ChangeType::Update); setStartNo(src.StartNo, ChangeType::Update); setClubId(src.getClubId()); setClassId(src.getClassId(false), false); setInputPlace(src.inputPlace); if (inputTime != src.inputTime) { inputTime = src.inputTime; updateChanged(); } setInputStatus(src.inputStatus); setInputPoints(src.inputPoints); } void oRunner::merge(const oBase &input) { oAbstractRunner::merge(input); const oRunner &src = dynamic_cast(input); setCourseId(src.getCourseId()); if (src.getCardId() != 0) setCard(src.getCardId()); setCardNo(src.getCardNo(), false); if (memcmp(oData, src.oData, sizeof(oData)) != 0) { memcpy(oData, src.oData, sizeof(oData)); updateChanged(); } synchronize(true); } void oTeam::merge(const oBase &input) { oAbstractRunner::merge(input); const oTeam &src = dynamic_cast(input); bool same = src.Runners.size() == Runners.size(); vector r(src.Runners.size()); for (size_t i = 0; i < src.Runners.size(); i++) { if (src.Runners[i]) { r[i] = src.Runners[i]->Id; src.Runners[i]->tInTeam = nullptr; } if (same) { int rc = Runners[i] ? Runners[i]->Id : 0; if (rc != r[i]) same = false; } } importRunners(r); if (!same) updateChanged(); if (memcmp(oData, src.oData, sizeof(oData)) != 0) { memcpy(oData, src.oData, sizeof(oData)); updateChanged(); } synchronize(true); } void oControl::merge(const oBase &input) { const oControl &src = dynamic_cast(input); if (src.Name.length() > 0) setName(src.Name); setNumbers(src.codeNumbers()); setStatus(src.getStatus()); if (memcmp(oData, src.oData, sizeof(oData)) != 0) { memcpy(oData, src.oData, sizeof(oData)); updateChanged(); } synchronize(true); } void oCourse::merge(const oBase &input) { const oCourse &src = dynamic_cast(input); if (src.Name.length() > 0) setName(src.Name); setLength(src.Length); importControls(src.getControls(), true, false); importLegLengths(src.getLegLengths(), true); if (memcmp(oData, src.oData, sizeof(oData)) != 0) { memcpy(oData, src.oData, sizeof(oData)); updateChanged(); } synchronize(true); } void oClass::merge(const oBase &input) { const oClass &src = dynamic_cast(input); if (src.Name.length() > 0) setName(src.Name, true); setCourse(oe->getCourse(src.getCourseId())); if (src.MultiCourse.size() > 0) { vector> mcCopy = MultiCourse; set cid; vector< vector > multi; parseCourses(src.codeMultiCourse(), multi, cid); importCourses(multi); if (mcCopy != MultiCourse) updateChanged(); } else { setNumStages(0); } if (src.legInfo.size() > 0) { if (codeLegMethod() != src.codeLegMethod()) { importLegMethod(src.codeLegMethod()); updateChanged(); } } if (memcmp(oData, src.oData, sizeof(oData)) != 0) { memcpy(oData, src.oData, sizeof(oData)); updateChanged(); } synchronize(true); } void oClub::merge(const oBase &input) { const oClub &src = dynamic_cast(input); setName(src.getName()); if (memcmp(oData, src.oData, sizeof(oData)) != 0) { memcpy(oData, src.oData, sizeof(oData)); updateChanged(); } synchronize(true); } void oCard::merge(const oBase &input) { const oCard &src = dynamic_cast(input); setCardNo(src.getCardNo()); if (readId != src.readId) { readId = src.readId; updateChanged(); } if (getPunchString() != src.getPunchString()) { importPunches(src.getPunchString()); updateChanged(); } synchronize(true); } void oPunch::merge(const oBase &input) { const oPunch &src = dynamic_cast(input); // Not implemented } void oFreePunch::merge(const oBase &input) { const oFreePunch &src = dynamic_cast(input); // Not implemented } void oEvent::merge(const oBase &srcIn) { } wstring oEvent::getMergeTag(bool forceReset) { wstring sm = getDCI().getString("MergeTag"); if (sm.empty() || forceReset) { static const char alphanum[] = "0123456789" "ABCDEFGHIJKLMNOPQRSTUVWXYZ" "abcdefghijklmnopqrstuvwxyz"; random_device r; mt19937 e1(r()); wchar_t s[13]; for (int i = 0; i < 12; ++i) { s[i] = alphanum[rand() % (sizeof(alphanum) - 1)]; } s[12] = 0; getDI().setString("MergeTag", s); synchronize(true); sm = s; } return sm; } wstring oEvent::getMergeInfo(const wstring &tag) const { wstring mv = getDCI().getString("MergeInfo"); vector mvv; split(mv, L":", mvv); for (size_t j = 0; j + 1 < mvv.size(); j += 2) { if (mvv[j] == tag) return mvv[j + 1]; } return L""; } void oEvent::addMergeInfo(const wstring &tag, const wstring &version) { wstring mv = getDCI().getString("MergeInfo"); vector mvv; split(mv, L":", mvv); bool ok = false; for (size_t j = 0; j + 1 < mvv.size(); j += 2) { if (mvv[j] == tag) mvv[j + 1] = version, ok = true; } if (!ok) { mvv.push_back(tag); mvv.push_back(version); } unsplit(mvv, L":", mv); getDI().setString("MergeInfo", mv); } string oEvent::getLastModified() const { string s; auto maxModified = [&s](auto &cnt) { for (auto &b : cnt) if (!b.isRemoved() && b.getStamp() > s) s = b.getStamp(); }; maxModified(Controls); maxModified(Courses); maxModified(Classes); maxModified(Cards); maxModified(Clubs); maxModified(Runners); maxModified(Teams); // maxModified(punches); xxx ignored return s; }