/************************************************************************ 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 for 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 ************************************************************************/ // oEvent.cpp: implementation of the oEvent class. // ////////////////////////////////////////////////////////////////////// #include "stdafx.h" #include #include "oEvent.h" #include "gdioutput.h" #include "oDataContainer.h" #include "csvparser.h" #include "TabAuto.h" #include "random.h" #include "SportIdent.h" #include "meosdb/sqltypes.h" #include "meosexception.h" #include "meos_util.h" #include "meos.h" #include typedef bool (__cdecl* OPENDB_FCN)(void); typedef int (__cdecl* SYNCHRONIZE_FCN)(oBase *obj); bool oEvent::connectToServer() { if (isThreadReconnecting()) return false; return true; } void oEvent::startReconnectDaemon() { if (isThreadReconnecting()) return; char bf[256]; msGetErrorState(bf); MySQLReconnect msqlr(lang.tl("warning:dbproblem#" + string(bf))); msqlr.interval=5; HasDBConnection = false; HasPendingDBConnection = true; tabAutoAddMachinge(msqlr); gdibase.setDBErrorState(false); gdibase.setWindowTitle(oe->getTitleName()); if (!isReadOnly()) { // Do not show in kiosk-mode gdibase.alert("warning:dbproblem#" + string(bf)); } } bool oEvent::msSynchronize(oBase *ob) { if (!HasDBConnection && !HasPendingDBConnection) return true; int ret = msSynchronizeRead(ob); char err[256]; if (msGetErrorState(err)) gdibase.addInfoBox("sqlerror", gdibase.widen(err), 15000); if (ret==0) { verifyConnection(); return false; } if (typeid(*ob)==typeid(oTeam)) { static_cast(ob)->apply(ChangeType::Quiet, nullptr); } if (ret==1) { gdibase.RemoveFirstInfoBox("sqlwarning"); gdibase.addInfoBox("sqlwarning", L"Varning: ändringar i X blev överskrivna#" + ob->getInfo(), 5000); } return ret!=0; } bool MEOSDB_API msSynchronizeList(oEvent *, oListId lid); bool oEvent::synchronizeList(initializer_list types) { if (!HasDBConnection) return true; resetSynchTimes(); msSynchronize(this); resetSQLChanged(true, false); vector toSync; bool hasCard = false; for (oListId t : types) { if (t == oListId::oLRunnerId && !hasCard) { hasCard = true; toSync.push_back(oListId::oLCardId); // Make always card sync before runners } else if (t == oListId::oLCardId) { if (hasCard) continue; else hasCard = true; } toSync.push_back(t); } for (oListId t : toSync) { if (!msSynchronizeList(this, t)) { verifyConnection(); return false; } if (t == oListId::oLPunchId) advanceInformationPunches.clear(); } reinitializeClasses(); reEvaluateChanged(); return true; } bool oEvent::synchronizeList(oListId id, bool preSyncEvent, bool postSyncEvent) { if (!HasDBConnection) return true; if (preSyncEvent && postSyncEvent && id == oListId::oLRunnerId) { resetSynchTimes(); synchronizeList(oListId::oLCardId, true, false); preSyncEvent = false; } if (preSyncEvent) { msSynchronize(this); resetSQLChanged(true, false); } if ( !msSynchronizeList(this, id) ) { verifyConnection(); return false; } if (id == oListId::oLPunchId) advanceInformationPunches.clear(); if (postSyncEvent) { reinitializeClasses(); reEvaluateChanged(); return true; } return true; } bool oEvent::needReEvaluate() { return sqlRunners.changed | sqlClasses.changed | sqlCourses.changed | sqlControls.changed | sqlCards.changed | sqlTeams.changed; } void oEvent::resetSQLChanged(bool resetAllTeamsRunners, bool cleanClasses) { if (empty()) return; sqlRunners.changed = false; sqlClasses.changed = false; sqlCourses.changed = false; sqlControls.changed = false; sqlClubs.changed = false; sqlCards.changed = false; sqlPunches.changed = false; sqlTeams.changed = false; if (resetAllTeamsRunners) { for (auto &r : Runners) r.sqlChanged = false; for (auto &t : Teams) t.sqlChanged = false; } if (cleanClasses) { // This data is used to redraw lists/speaker etc. for (list::iterator it=oe->Classes.begin(); it!=oe->Classes.end(); ++it) { it->sqlChangedControlLeg.clear(); it->sqlChangedLegControl.clear(); } globalModification = false; } } bool BaseIsRemoved(const oBase &ob){return ob.isRemoved();} namespace { bool isSet(int mask, oListId id) { return (mask & int(id)) != 0; } } //Returns true if data is changed. bool oEvent::autoSynchronizeLists(bool SyncPunches) { if (!HasDBConnection) return false; bool changed=false; string ot; int mask = getListMask(*this); if (mask == 0) return false; resetSynchTimes(); // Reset change data and store update status on objects // (which might be incorrectly changed during sql update) resetSQLChanged(true, false); //Synchronize ourself if (isSet(mask, oListId::oLEventId)) { ot=sqlUpdated; msSynchronize(this); if (sqlUpdated!=ot) { changed=true; gdibase.setWindowTitle(getTitleName()); } } //Controls if (isSet(mask, oListId::oLControlId)) { int oc = sqlControls.counter; ot = sqlControls.updated; synchronizeList(oListId::oLControlId, false, false); changed |= oc != sqlControls.counter; changed |= ot != sqlControls.updated; } //Courses if (isSet(mask, oListId::oLCourseId)) { int oc = sqlCourses.counter; ot = sqlCourses.updated; synchronizeList(oListId::oLCourseId, false, false); changed |= oc != sqlCourses.counter; changed |= ot != sqlCourses.updated; } //Classes if (isSet(mask, oListId::oLClassId)) { int oc = sqlClasses.counter; ot = sqlClasses.updated; synchronizeList(oListId::oLClassId, false, false); changed |= oc != sqlClasses.counter; changed |= ot != sqlClasses.updated; } //Clubs if (isSet(mask, oListId::oLClubId)) { int oc = sqlClubs.counter; ot = sqlClubs.updated; synchronizeList(oListId::oLClubId, false, false); changed |= oc != sqlClubs.counter; changed |= ot != sqlClubs.updated; } //Cards if (isSet(mask, oListId::oLCardId)) { int oc = sqlCards.counter; ot = sqlCards.updated; synchronizeList(oListId::oLCardId, false, false); changed |= oc != sqlCards.counter; changed |= ot != sqlCards.updated; } //Runners if (isSet(mask, oListId::oLRunnerId)) { int oc = sqlRunners.counter; ot = sqlRunners.updated; synchronizeList(oListId::oLRunnerId, false, false); changed |= oc != sqlRunners.counter; changed |= ot != sqlRunners.updated; } //Teams if (isSet(mask, oListId::oLTeamId)) { int oc = sqlTeams.counter; ot = sqlTeams.updated; synchronizeList(oListId::oLTeamId, false, false); changed |= oc != sqlTeams.counter; changed |= ot != sqlTeams.updated; } if (SyncPunches && isSet(mask, oListId::oLPunchId)) { //Punches int oc = sqlPunches.counter; ot = sqlPunches.updated; synchronizeList(oListId::oLPunchId, false, false); changed |= oc != sqlPunches.counter; changed |= ot != sqlPunches.updated; } if (changed) { if (needReEvaluate()) reEvaluateChanged(); reCalculateLeaderTimes(0); //Restore changed staus on object that might have been changed //during sql update, due to partial updates return true; } return false; } bool oEvent::connectToMySQL(const string &server, const string &user, const string &pwd, int port) { if (isThreadReconnecting()) return false; if (!connectToServer()) return false; MySQLServer=server; MySQLPassword=pwd; MySQLPort=port; MySQLUser=user; //Delete non-server competitions. list saved; list::iterator it; for (it=cinfo.begin(); it!=cinfo.end(); ++it) { if (it->Server.empty()) saved.push_back(*it); } cinfo = saved; if (!msConnectToServer(this)) { char bf[256]; msGetErrorState(bf); gdibase.alert(bf); return false; } for (it=cinfo.begin(); it!=cinfo.end(); ++it) { if (it->Name.size() > 1 && it->Name[0] == '%') it->Name = lang.tl(it->Name.substr(1)); } return true; } bool oEvent::uploadSynchronize() { if (isThreadReconnecting()) throw std::exception("Internt fel i anslutningen. Starta om MeOS"); wstring newId = makeValidFileName(currentNameId, true); currentNameId = newId; for (list::iterator it = cinfo.begin(); it != cinfo.end(); ++it) { if (it->FullPath == currentNameId && it->Server.length()>0) { gdioutput::AskAnswer ans = gdibase.askCancel(L"ask:overwrite_server"); if (ans == gdioutput::AnswerCancel) return false; else if (ans == gdioutput::AnswerNo) { int len = currentNameId.length(); wchar_t ex[10]; swprintf_s(ex, L"_%05XZ", (GetTickCount()/97) & 0xFFFFF); if (len > 0) { if (len< 7 || currentNameId[len-1] != 'Z') currentNameId += ex; else { wchar_t CurrentNameId[64]; wcscpy_s(CurrentNameId, currentNameId.c_str()); wcscpy_s(CurrentNameId + len - 7, 64 - len + 7, ex); currentNameId = CurrentNameId; } } } else { if (!gdibase.ask(L"ask:overwriteconfirm")) return false; } } } HasDBConnection=false; #ifdef BUILD_DB_DLL if (!msSynchronizeUpdate) throw std::exception("Internt fel. Starta om MeOS"); #endif if ( !msOpenDatabase(this) ){ char bf[256]; msGetErrorState(bf); string error = string("Kunde inte öppna databasen (X).#") + bf; throw std::exception(error.c_str()); } if ( !msSynchronizeUpdate(this) ) { char bf[256]; msGetErrorState(bf); string error = string("Kunde inte ladda upp tävlingen (X).#") + bf; throw std::exception(error.c_str()); } OpFailStatus stat = (OpFailStatus)msUploadRunnerDB(this); if (stat == opStatusFail) { char bf[256]; msGetErrorState(bf); string error = string("Kunde inte ladda upp löpardatabasen (X).#") + bf; throw meosException(error); } else if (stat == opStatusWarning) { char bf[256]; msGetErrorState(bf); gdibase.addInfoBox("", wstring(L"Kunde inte ladda upp löpardatabasen (X).#") + gdibase.widen(bf), 5000); } HasDBConnection=true; // Save local version of database saveRunnerDatabase(currentNameId.c_str(), false); return true; } //Load a (new) competition from the server. bool oEvent::readSynchronize(const CompetitionInfo &ci) { if (ci.Id<=0) throw std::exception("help:12290"); if (isThreadReconnecting()) return false; HasDBConnection=false; #ifdef BUILD_DB_DLL if (!msConnectToServer) return false; #endif MySQLServer=ci.Server; MySQLPassword=ci.ServerPassword; MySQLPort=ci.ServerPort; MySQLUser=ci.ServerUser; //Delete non-server competitions. list saved; list::iterator it; for (it=cinfo.begin(); it!=cinfo.end(); ++it) { if (it->Server.empty()) saved.push_back(*it); } cinfo=saved; if (!msConnectToServer(this)) { char bf[256]; msGetErrorState(bf); throw std::exception(bf); return false; } for (it=cinfo.begin(); it!=cinfo.end(); ++it) { if (it->Name.size() > 1 && it->Name[0] == '%') it->Name = lang.tl(it->Name.substr(1)); } newCompetition(L""); Id=ci.Id; currentNameId = ci.FullPath; wchar_t file[260]; swprintf_s(file, L"%s.dbmeos", currentNameId.c_str()); getUserFile(CurrentFile, file); if ( !msOpenDatabase(this) ) { char bf[256]; msGetErrorState(bf); throw std::exception(bf); } updateFreeId(); HasDBConnection=false; openRunnerDatabase(currentNameId.c_str()); int ret = msSynchronizeRead(this); if (ret == 0) { char bf[256]; msGetErrorState(bf); string err = string("Kunde inte öppna tävlingen (X)#") + bf; throw std::exception(err.c_str()); } else if (ret == 1) { // Warning char bf[256]; msGetErrorState(bf); wstring info = L"Databasvarning: X#" + lang.tl(bf); gdibase.addInfoBox("sqlerror", info, 15000); } // Cache database locally saveRunnerDatabase(currentNameId.c_str(), false); HasDBConnection=true; // Setup multirunner links for (oRunnerList::iterator it = Runners.begin(); it != Runners.end(); ++it) it->createMultiRunner(false,false); // Remove incorrect references for (oRunnerList::iterator it = Runners.begin(); it != Runners.end(); ++it) { if (it->multiRunner.size() > 0 ) { vector &pr = it->multiRunner; for (size_t k=0; ktParentRunner != &*it) { it->multiRunner.clear(); it->updateChanged(); it->synchronize(); break; } } } } // Check duplicates vector usedInTeam(qFreeRunnerId+1); bool teamCorrected = false; for (oTeamList::iterator it = Teams.begin(); it != Teams.end(); ++it) { if (it->correctionNeeded) { it->updateChanged(); teamCorrected = true; } for (size_t i = 0; i < it->Runners.size(); i++) { pRunner r = it->Runners[i]; if (r != 0) { int expectedIndex = -1; if (it->Class) expectedIndex = it->Class->getLegRunnerIndex(i); if (expectedIndex>=0 && expectedIndex != r->getMultiIndex()) { int baseLeg = it->Class->getLegRunner(i); it->setRunner(baseLeg, r->getMultiRunner(0), true); teamCorrected = true; } } } for (size_t i = 0; i < it->Runners.size(); i++) { pRunner r = it->Runners[i]; if (r != 0) { if (usedInTeam[r->Id]) { it->Runners[i] = nullptr; // Reset duplicate runners it->updateChanged(); teamCorrected = true; if (r->tInTeam == &*it) r->tInTeam = 0; } else usedInTeam[r->Id] = true; } } } usedInTeam.clear(); if (teamCorrected) { for (oTeamList::iterator it = Teams.begin(); it != Teams.end(); ++it) { if (!it->isRemoved()) { it->apply(oBase::ChangeType::Update, nullptr); it->synchronize(true); } } } reEvaluateAll(set(), false); vector out; checkChanged(out); assert(out.empty()); for (oRunnerList::iterator it = Runners.begin(); it != Runners.end(); ++it) { if (it->correctionNeeded) { it->createMultiRunner(true, true); if (it->tInTeam && it->Class) { pTeam t = it->tInTeam; int nr = min(int(it->Class->getNumStages())-1, int(it->multiRunner.size())); t->setRunner(0, &*it, true); for (int k=0; ksetRunner(k+1, it->multiRunner[k], true); t->updateChanged(); t->synchronize(); } it->synchronizeAll(); } } return true; } bool oEvent::reConnect(char *errorMsg256) { if (HasDBConnection) return true; if (isThreadReconnecting()){ strcpy_s(errorMsg256, 256, "Synkroniseringsfel."); return false; } #ifdef BUILD_DB_DLL if (!msReConnect) { strcpy_s(errorMsg256, 256, "Inte ansluten mot meosdb.dll"); return false; } #endif if (msReConnect()) { HasDBConnection = true; HasPendingDBConnection = false; //synchronize changed objects for (list::iterator it=oe->Cards.begin(); it!=oe->Cards.end(); ++it) if (it->isChanged()) it->synchronize(false); for (list::iterator it=oe->Clubs.begin(); it!=oe->Clubs.end(); ++it) if (it->isChanged()) it->synchronize(false); for (list::iterator it=oe->Controls.begin(); it!=oe->Controls.end(); ++it) if (it->isChanged()) it->synchronize(false); for (list::iterator it=oe->Courses.begin(); it!=oe->Courses.end(); ++it) if (it->isChanged()) it->synchronize(false); for (list::iterator it=oe->Classes.begin(); it!=oe->Classes.end(); ++it) if (it->isChanged()) it->synchronize(false); for (list::iterator it=oe->Runners.begin(); it!=oe->Runners.end(); ++it) if (it->isChanged()) it->synchronize(false); for (list::iterator it=oe->Teams.begin(); it!=oe->Teams.end(); ++it) if (it->isChanged()) it->synchronize(false); for (list::iterator it=oe->punches.begin(); it!=oe->punches.end(); ++it) if (it->isChanged()) it->synchronize(false); autoSynchronizeLists(true); return true; } msGetErrorState(errorMsg256); return false; } //Returns number of changed, non-saved elements. int oEvent::checkChanged(vector &out) const { int changed=0; wchar_t bf[256]; out.clear(); for (list::iterator it=oe->Cards.begin(); it!=oe->Cards.end(); ++it) if (it->isChanged()) { changed++; swprintf_s(bf, L"Card %d", it->cardNo); out.push_back(bf); it->synchronize(); } for (list::iterator it=oe->Clubs.begin(); it!=oe->Clubs.end(); ++it) if (it->isChanged()) { changed++; swprintf_s(bf, L"Club %ws", it->name.c_str()); out.push_back(bf); it->synchronize(); } for (list::iterator it=oe->Controls.begin(); it!=oe->Controls.end(); ++it) if (it->isChanged()) { changed++; swprintf_s(bf, L"Control %d", it->Numbers[0]); out.push_back(bf); it->synchronize(); } for (list::iterator it=oe->Courses.begin(); it!=oe->Courses.end(); ++it) if (it->isChanged()) { changed++; swprintf_s(bf, L"Course %s", it->Name.c_str()); out.push_back(bf); it->synchronize(); } for (list::iterator it=oe->Classes.begin(); it!=oe->Classes.end(); ++it) if (it->isChanged()) { changed++; swprintf_s(bf, L"Class %s", it->Name.c_str()); out.push_back(bf); it->synchronize(); } for (list::iterator it=oe->Runners.begin(); it!=oe->Runners.end(); ++it) if (it->isChanged()) { changed++; swprintf_s(bf, L"Runner %s", it->getName().c_str()); out.push_back(bf); it->synchronize(); } for (list::iterator it=oe->Teams.begin(); it!=oe->Teams.end(); ++it) if (it->isChanged()) { changed++; swprintf_s(bf, L"Team %s", it->getName().c_str()); out.push_back(bf); it->synchronize(); } for (list::iterator it=oe->punches.begin(); it!=oe->punches.end(); ++it) if (it->isChanged()) { changed++; swprintf_s(bf, L"Punch SI=%d, %d", it->CardNo, it->Type); out.push_back(bf); it->synchronize(); } return changed; } bool oEvent::verifyConnection() { if (!HasDBConnection) return false; if (isThreadReconnecting()) return false; #ifdef BUILD_DB_DLL if (!msMonitor) return false; #endif if (!msMonitor(this)) { startReconnectDaemon(); return false; } return true; } const string &oEvent::getServerName() const { return serverName; } void oEvent::closeDBConnection() { bool hadDB=HasDBConnection; if (isThreadReconnecting()) { //Don't know what to do?! } gdibase.setWaitCursor(true); if (HasDBConnection) { autoSynchronizeLists(true); } HasDBConnection=false; #ifdef BUILD_DB_DLL if (msResetConnection) msResetConnection(); #else msResetConnection(); #endif Id=0; if (!oe->empty() && hadDB) { save(); Name+=L" (Lokal kopia från: " + gdibase.widen(serverName) + L")"; wstring cn = currentNameId + L"." + gdibase.widen(serverName) + L".meos"; getUserFile(CurrentFile, cn.c_str()); serverName.clear(); save(); gdibase.setWindowTitle(Name); } else serverName.clear(); gdibase.setWaitCursor(false); } void oEvent::listConnectedClients(gdioutput &gdi) { gdi.addString("", 1, "Anslutna klienter:"); char bf[256]; gdi.fillRight(); gdi.pushX(); int x=gdi.getCX(); for (size_t k=0;k0) throw std::exception(bf); throw std::exception("Operationen misslyckades. Orsak okänd."); } clear(); }