/************************************************************************ MeOS - Orienteering Software Copyright (C) 2009-2017 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; #ifdef BUILD_DB_DLL if (msOpenDatabase) return true; #endif #ifdef BUILD_DB_DLL hMod=LoadLibrary("meosdb.dll"); msOpenDatabase = (SYNCHRONIZE_FCN)GetProcAddress(hMod, "msOpenDatabase"); msConnectToServer = (SYNCHRONIZE_FCN)GetProcAddress(hMod, "msConnectToServer"); msSynchronizeUpdate = (SYNCHRONIZE_FCN)GetProcAddress(hMod, "msSynchronizeUpdate"); msSynchronizeRead = (SYNCHRONIZE_FCN)GetProcAddress(hMod, "msSynchronizeRead"); msSynchronizeList = (SYNCHRONIZELIST_FCN)GetProcAddress(hMod, "msSynchronizeList"); msRemove = (SYNCHRONIZE_FCN)GetProcAddress(hMod, "msRemove"); msMonitor = (SYNCHRONIZE_FCN)GetProcAddress(hMod, "msMonitor"); msDropDatabase = (SYNCHRONIZE_FCN)GetProcAddress(hMod, "msDropDatabase"); msUploadRunnerDB = (SYNCHRONIZE_FCN)GetProcAddress(hMod, "msUploadRunnerDB"); msGetErrorState = (ERRORMESG_FCN)GetProcAddress(hMod, "msGetErrorState"); msResetConnection = (OPENDB_FCN)GetProcAddress(hMod, "msResetConnection"); msReConnect = (OPENDB_FCN)GetProcAddress(hMod, "msReConnect"); if (!msOpenDatabase || !msSynchronizeUpdate || !msSynchronizeRead || !msConnectToServer || !msReConnect || !msGetErrorState || !msMonitor || !msDropDatabase || !msUploadRunnerDB) { MessageBox(NULL, "Cannot load MySQL library.", NULL, MB_OK); // handle the error FreeLibrary(hMod); hMod=0; return false;//SOME_ERROR_CODE; } #else/* msOpenDatabase = (SYNCHRONIZE_FCN)::msOpenDatabase; msConnectToServer = (SYNCHRONIZE_FCN)::msConnectToServer; msSynchronizeUpdate = (SYNCHRONIZE_FCN)::msSynchronizeUpdate; msSynchronizeRead = (SYNCHRONIZE_FCN)::msSynchronizeRead; msSynchronizeList = (SYNCHRONIZELIST_FCN)::msSynchronizeList; msRemove = (SYNCHRONIZE_FCN)::msRemove; msMonitor = (SYNCHRONIZE_FCN)::msMonitor; msDropDatabase = (SYNCHRONIZE_FCN)::msDropDatabase; msUploadRunnerDB = (SYNCHRONIZE_FCN)::msUploadRunnerDB; msGetErrorState = (ERRORMESG_FCN)::msGetErrorState; msResetConnection = (OPENDB_FCN)::msResetConnection; msReConnect = (OPENDB_FCN)::msReConnect;*/ #endif 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 (!msSynchronizeRead) return true; 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(false, 0, false); } if (ret==1) { gdibase.RemoveFirstInfoBox("sqlwarning"); gdibase.addInfoBox("sqlwarning", L"Varning: ändringar i X blev överskrivna#" + ob->getInfo(), 5000); } return ret!=0; } void oEvent::resetChangeStatus(bool onlyChangable) { if (HasDBConnection) { if (!onlyChangable) { // The object here has no dependeces that makes it necessary to set/rest for (list::iterator it=oe->punches.begin(); it!=oe->punches.end(); ++it) it->resetChangeStatus(); //synchronize changed objects for (list::iterator it=oe->Cards.begin(); it!=oe->Cards.end(); ++it) it->resetChangeStatus(); for (list::iterator it=oe->Clubs.begin(); it!=oe->Clubs.end(); ++it) it->resetChangeStatus(); for (list::iterator it=oe->Controls.begin(); it!=oe->Controls.end(); ++it) it->resetChangeStatus(); for (list::iterator it=oe->Courses.begin(); it!=oe->Courses.end(); ++it) it->resetChangeStatus(); for (list::iterator it=oe->Classes.begin(); it!=oe->Classes.end(); ++it) it->resetChangeStatus(); } for (list::iterator it=oe->Runners.begin(); it!=oe->Runners.end(); ++it) it->resetChangeStatus(); for (list::iterator it=oe->Teams.begin(); it!=oe->Teams.end(); ++it) it->resetChangeStatus(); } } void oEvent::storeChangeStatus(bool onlyChangable) { if (HasDBConnection) { if (!onlyChangable) { // The object here has no dependeces that makes it necessary to set/rest for (list::iterator it=oe->punches.begin(); it!=oe->punches.end(); ++it) it->storeChangeStatus(); for (list::iterator it=oe->Cards.begin(); it!=oe->Cards.end(); ++it) it->storeChangeStatus(); for (list::iterator it=oe->Clubs.begin(); it!=oe->Clubs.end(); ++it) it->storeChangeStatus(); for (list::iterator it=oe->Controls.begin(); it!=oe->Controls.end(); ++it) it->storeChangeStatus(); for (list::iterator it=oe->Courses.begin(); it!=oe->Courses.end(); ++it) it->storeChangeStatus(); for (list::iterator it=oe->Classes.begin(); it!=oe->Classes.end(); ++it) it->storeChangeStatus(); } for (list::iterator it=oe->Runners.begin(); it!=oe->Runners.end(); ++it) it->storeChangeStatus(); for (list::iterator it=oe->Teams.begin(); it!=oe->Teams.end(); ++it) it->storeChangeStatus(); } } bool oEvent::synchronizeList(oListId id, bool preSyncEvent, bool postSyncEvent) { if (!HasDBConnection) return true; if (preSyncEvent) { msSynchronize(this); resetSQLChanged(true, false); } if ( !msSynchronizeList(this, id) ) { verifyConnection(); return false; } if (id == oLPunchId) advanceInformationPunches.clear(); if (postSyncEvent) { reinitializeClasses(); reEvaluateChanged(); resetChangeStatus(); return true; } return true; } bool oEvent::needReEvaluate() { return sqlChangedRunners | sqlChangedClasses | sqlChangedCourses | sqlChangedControls | sqlChangedCards | sqlChangedTeams; } void oEvent::resetSQLChanged(bool resetAllTeamsRunners, bool cleanClasses) { sqlChangedRunners = false; sqlChangedClasses = false; sqlChangedCourses = false; sqlChangedControls = false; sqlChangedClubs = false; sqlChangedCards = false; sqlChangedPunches = false; sqlChangedTeams = false; if (resetAllTeamsRunners) { for (list::iterator it=oe->Runners.begin(); it!=oe->Runners.end(); ++it) { it->storeChangeStatus(); it->sqlChanged = false; } for (list::iterator it=oe->Teams.begin(); it!=oe->Teams.end(); ++it) { it->storeChangeStatus(); it->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->storeChangeStatus(); it->sqlChangedControlLeg.clear(); it->sqlChangedLegControl.clear(); } globalModification = false; } } bool BaseIsRemoved(const oBase &ob){return ob.isRemoved();} //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; // Reset change data and store update status on objects // (which might be incorrectly changed during sql update) resetSQLChanged(true, false); //Synchronize ourself if (mask & oLEventId) { ot=sqlUpdated; msSynchronize(this); if (sqlUpdated!=ot) { changed=true; gdibase.setWindowTitle(getTitleName()); } } //Controls if (mask & oLControlId) { int oc = sqlCounterControls; ot = sqlUpdateControls; synchronizeList(oLControlId, false, false); changed |= oc!=sqlCounterControls; changed |= ot!=sqlUpdateControls; } //Courses if (mask & oLCourseId) { int oc = sqlCounterCourses; ot = sqlUpdateCourses; synchronizeList(oLCourseId, false, false); changed |= oc!=sqlCounterCourses; changed |= ot!=sqlUpdateCourses; } //Classes if (mask & oLClassId) { int oc = sqlCounterClasses; ot = sqlUpdateClasses; synchronizeList(oLClassId, false, false); changed |= oc!=sqlCounterClasses; changed |= ot!=sqlUpdateClasses; } //Clubs if (mask & oLClubId) { int oc = sqlCounterClubs; ot = sqlUpdateClubs; synchronizeList(oLClubId, false, false); changed |= oc!=sqlCounterClubs; changed |= ot!=sqlUpdateClubs; } //Cards if (mask & oLCardId) { int oc = sqlCounterCards; ot = sqlUpdateCards; synchronizeList(oLCardId, false, false); changed |= oc!=sqlCounterCards; changed |= ot!=sqlUpdateCards; } //Runners if (mask & oLRunnerId) { int oc = sqlCounterRunners; ot = sqlUpdateRunners; synchronizeList(oLRunnerId, false, false); changed |= oc!=sqlCounterRunners; changed |= ot!=sqlUpdateRunners; } //Teams if (mask & oLTeamId) { int oc = sqlCounterTeams; ot = sqlUpdateTeams; synchronizeList(oLTeamId, false, false); changed |= oc!=sqlCounterTeams; changed |= ot!=sqlUpdateTeams; } if (SyncPunches && (mask & oLPunchId)) { //Punches int oc = sqlCounterPunches; ot = sqlUpdatePunches; synchronizeList(oLPunchId, false, false); changed |= oc!=sqlCounterPunches; changed |= ot!=sqlUpdatePunches; } if (changed) { if (needReEvaluate()) reEvaluateChanged(); reCalculateLeaderTimes(0); //Restore changed staus on object that might have been changed //during sql update, due to partial updates resetChangeStatus(); 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); MessageBox(NULL, lang.tl(bf).c_str(), L"Error", MB_OK); 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] = 0; // 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) { it->apply(true, 0, false); } } 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(); }