/************************************************************************ MeOS - Orienteering Software Copyright (C) 2009-2023 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 ************************************************************************/ #include "stdafx.h" #include #include #include #include "oEvent.h" #include "xmlparser.h" #include "restbed/restbed" #include "meosexception.h" #include "restserver.h" #include "infoserver.h" #include "oListInfo.h" #include "TabList.h" #include "generalresult.h" #include "HTMLWriter.h" #include "RunnerDB.h" #include "image.h" extern Image image; using namespace restbed; vector< shared_ptr > RestServer::startedServers; const wstring &wideParam(const string ¶m) { return gdioutput::fromUTF8(param); } shared_ptr RestServer::construct() { shared_ptr obj(new RestServer()); startedServers.push_back(obj); return obj; } void RestServer::remove(shared_ptr server) { //std::remove(startedServers.begin(), startedServers.end(), server); for (size_t k = 0; k < startedServers.size(); k++) { if (startedServers[k] == server) { startedServers.erase(startedServers.begin() + k); break; } } } RestServer::RestServer() : hasAnyRequest(false) { } RestServer::~RestServer() { stop(); } class MeOSResource : public restbed::Resource { RestServer *server; public: MeOSResource(RestServer *server) : server(server) {} ~MeOSResource() { } RestServer &getServer() const { return *server; } }; static void method_handler(const shared_ptr< restbed::Session > session) { RestServer &server = dynamic_cast(*session->get_resource()).getServer(); server.handleRequest(session); } void RestServer::handleRequest(const shared_ptr &session) { const auto request = session->get_request(); string path = request->get_path(); size_t content_length = request->get_header("Content-Length", 0); chrono::time_point start, end; start = chrono::system_clock::now(); auto param = request->get_query_parameters(); if (path == "/" && !root.empty()) { param = rootMap; } auto answer = RestServer::addRequest(param); { unique_lock mlock(lock); if (!waitForCompletion.wait_for(mlock, 10s, [answer] {return answer->isCompleted(); })) { answer->answer = "Error (MeOS): Internal timeout"; } end = chrono::system_clock::now(); chrono::duration elapsed_seconds = end - start; responseTimes.push_back(int(1000 * elapsed_seconds.count())); } session->fetch(content_length, [request, answer](const shared_ptr< Session > session, const Bytes & body) { if (answer->image.empty()) { session->close(restbed::OK, answer->answer, { { "Content-Length", itos(answer->answer.length()) }, { "Connection", "close" }, { "Access-Control-Allow-Origin", "*" } }); } else { session->close(restbed::OK, answer->image, { { "Content-Type", "image/png"}, { "Content-Length", itos(answer->image.size()) }, { "Connection", "close" }, { "Access-Control-Allow-Origin", "*" } }); } }); } void RestServer::startThread(int port) { auto settings = make_shared(); settings->set_port(port); auto resource = make_shared(this); resource->set_path("/meos"); resource->set_method_handler("GET", method_handler); restService->publish(resource); auto resourceRoot = make_shared(this); resourceRoot->set_path("/"); resourceRoot->set_method_handler("GET", method_handler); restService->publish(resourceRoot); restService->start(settings); } void RestServer::setRootMap(const string &rmap) { root = rmap; vector sp; rootMap.clear(); split(rmap, "&", sp); for (string arg : sp) { vector sp2; split(arg, "=", sp2); if (sp2.size() == 1) rootMap.emplace(sp2[0], ""); else if (sp2.size() == 2) rootMap.emplace(sp2[0], sp2[1]); } } void RestServer::startService(int port) { if (service) throw meosException("Server started"); restService.reset(new restbed::Service()); service = make_shared(&RestServer::startThread, this, port); } void RestServer::stop() { if (restService) restService->stop(); if (service && service->joinable()) service->join(); restService.reset(); service.reset(); } void RestServer::computeRequested(oEvent &ref) { for (auto &server : startedServers) { server->compute(ref); } } void RestServer::compute(oEvent &ref) { auto rq = getRequest(); if (!rq) return; try { computeInternal(ref, rq); } catch (meosException &ex) { rq->answer = "Error (MeOS): Error: " + ref.gdiBase().toUTF8(lang.tl(ex.wwhat())); } catch (std::exception &ex) { rq->answer = "Error (MeOS): General Error: " + string(ex.what()); } catch (...) { rq->answer = "Error (MeOS): Unknown internal error."; } { lock_guard lg(lock); rq->state = true; } waitForCompletion.notify_all(); } extern wchar_t programPath[MAX_PATH]; void RestServer::computeInternal(oEvent &ref, shared_ptr &rq) { if (rq->parameters.empty()) { rq->answer = "" "" "MeOS Information Service" "" "" "\"MeOS\"" "

" + ref.gdiBase().toUTF8(lang.tl(getMeosFullVersion())) + "

" "

    \n"; rq->answer += "

    " + ref.gdiBase().toUTF8(lang.tl("Listor")) + "

    "; vector lists; TabList::getPublicLists(ref, lists); map listMap; ref.getListTypes(listMap, false); for (auto &lp : lists) { wstring n = lp.getName(); if (n.empty()) { n = listMap[lp.listCode].getName(); } lp.setName(n); int keyCand = lp.listCode * 100; bool done = false; for (int i = 0; i < 100; i++) { if (!listCache.count(keyCand + i)) { keyCand += i; listCache[keyCand].first = lp; done = true; break; } else if(listCache[keyCand + i].first == lp) { keyCand = keyCand + i; done = true; break; } } if (!done) { listCache[keyCand].first = lp; listCache[keyCand].second.reset(); } rq->answer += "
  • " + ref.gdiBase().toUTF8(n) + "
  • \n"; } // "
  • Resultat
  • " // "
  • Startlista
  • " rq->answer += "
\n"; string entryLinks = "

" + ref.gdiBase().toUTF8(lang.tl(L"Direktanmälan", true)) + "

"; entryLinks += ""+ref.gdiBase().toUTF8(lang.tl("Anmäl")) + "
"; entryLinks += "
"; rq->answer += entryLinks; vector runners; ref.getRunners(-1, -1, runners, false); string sRunnerId, sRunnerBib, sRunnerCard, sRunnerName, sRunnerClub, sRunnerClubId; for (pRunner r : runners) { if (sRunnerId.empty()) sRunnerId = itos(r->getId()); if (sRunnerClub.empty()) { sRunnerName = gdioutput::toUTF8(r->getName()); sRunnerClub = gdioutput::toUTF8(r->getClub()); if (r->getClubId()) sRunnerClubId = gdioutput::toUTF8(r->getClubRef()->getExtIdentifierString()); } if (sRunnerCard.empty() && r->getCardNo() > 0) sRunnerCard = itos(r->getCardNo()); if (sRunnerBib.empty() && !r->getBib().empty()) sRunnerBib = gdioutput::recodeToNarrow(r->getBib()); } if (sRunnerId.empty()) sRunnerId = "1"; if (sRunnerCard.empty()) sRunnerCard = "12345"; if (sRunnerBib.empty()) sRunnerBib = "100"; if (sRunnerClub.empty()) sRunnerClub = "Lost and Found"; if (sRunnerName.empty()) sRunnerName = "Charles Kinsley"; string sRunnerDbName, sRunnerDbId, sRunnerDbClub; //oe.getRunnerDatabase(). HINSTANCE hInst = GetModuleHandle(0); HRSRC hRes = FindResource(hInst, MAKEINTRESOURCE(132), RT_HTML); HGLOBAL res = LoadResource(hInst, hRes); char *html = (char *)LockResource(res); int resSize = SizeofResource(hInst, hRes); if (html) { string htmlS; while (*html != 0 && resSize > 0) { if (*html == '*') htmlS += "<"; else if (*html == '#') htmlS += "&"; else if (*html == '%') { string key; char *pKey = html + 1; resSize--; while (*pKey != '%' && resSize > 0) { key += *pKey; ++pKey; resSize--; } if (key == "runnerid") { htmlS += sRunnerId; } else if (key == "card") { htmlS += sRunnerCard; } else if (key == "bib") { htmlS += sRunnerBib; } else if (key == "name") { htmlS += sRunnerName; } else if (key == "club") { htmlS += sRunnerClub; } else if (key == "dbname") { htmlS += sRunnerName.substr(0, sRunnerName.size() / 2); } else if (key == "dbclub") { htmlS += sRunnerClub; } else if (key == "clubid") { htmlS += sRunnerClubId; } html = pKey; } else htmlS += *html; ++html; resSize--; } rq->answer += htmlS; } rq->answer += "\n\n"; } else if (rq->parameters.count("entry") > 0) { newEntry(ref, rq->parameters, rq->answer); } else if (rq->parameters.count("get") > 0) { string what = rq->parameters.find("get")->second; getData(ref, what, rq->parameters, rq->answer); } else if (rq->parameters.count("lookup") > 0) { string what = rq->parameters.find("lookup")->second; lookup(ref, what, rq->parameters, rq->answer); } else if (rq->parameters.count("difference")) { string what = rq->parameters.find("difference")->second; int id = -2; if (what == "zero") id = -1; else id = atoi(what.c_str()); difference(ref, id, rq->answer); } else if (rq->parameters.count("page") > 0) { string what = rq->parameters.find("page")->second; auto &writer = HTMLWriter::getWriter(HTMLWriter::TemplateType::Page, what); writer.getPage(ref, rq->answer); } else if (rq->parameters.count("enter") > 0) { auto &writer = HTMLWriter::getWriter(HTMLWriter::TemplateType::Page, "entryform"); writer.getPage(ref, rq->answer); } else if (rq->parameters.count("image") > 0) { string imageId = rq->parameters.find("image")->second; if (imageCache.count(imageId)) { rq->image = imageCache[imageId]; } else if (imageId == "meos") { imageCache[imageId] = rq->image = Image::loadResourceToMemory(MAKEINTRESOURCE(513), _T("PNG")); } else if (imageId.length() > 2 && imageId[0] == 'I' && imageId[1] == 'D') { uint64_t imgId = _strtoui64(imageId.c_str() + 2, nullptr, 10); ref.loadImage(imgId); rq->image = image.getRawData(imgId); } else { wchar_t fn[260]; if (imageId.find_first_of("\\/.?*") == string::npos) { wstring par = wideParam(imageId) + L".png"; getUserFile(fn, par.c_str()); ifstream fin; fin.open(fn, ios::binary); if (fin.good()) { fin.seekg(0, ios::end); int p2 = (int)fin.tellg(); fin.seekg(0, ios::beg); rq->image.resize(p2); fin.read((char *)&rq->image[0], rq->image.size()); fin.close(); imageCache[imageId] = rq->image; } } } } else if (rq->parameters.count("html") > 0) { string stype = rq->parameters.count("type") ? rq->parameters.find("type")->second : _EmptyString; int type = atoi(stype.c_str()); auto res = listCache.find(type); if (res != listCache.end()) { gdioutput gdiPrint("print", ref.gdiBase().getScale()); gdiPrint.clearPage(false); if (!res->second.second) { res->second.second = make_shared(); ref.generateListInfo(res->second.first, *res->second.second); } ref.generateList(gdiPrint, true, *res->second.second, false); //wstring exportFile = getTempFile(); ostringstream fout; HTMLWriter::write(gdiPrint, fout, ref.getName(), 30, res->second.first, ref); rq->answer = fout.str(); //ifstream fin(exportFile.c_str()); /*string rbf; while (std::getline(fin, rbf)) { rq->answer += rbf; } removeTempFile(exportFile);*/ } else { rq->answer = "Error (MeOS): Unknown list"; } } else { rq->answer = "Error (MeOS): Unknown request"; } } void writePerson(const GeneralResult::GeneralResultInfo &res, xmlbuffer &reslist, bool includeTeam, bool includeCourse, vector< pair > &rProp) { int clsId = res.src->getClassId(true); rProp.push_back(make_pair("cls", itow(clsId))); if (includeCourse) { pRunner pr = dynamic_cast(res.src); if (pr && pr->getCourse(false)) rProp.push_back(make_pair("course", itow(pr->getCourse(false)->getId()))); } bool hasTeam = res.src->getTeam() != 0 && clsId == res.src->getClassId(false) && // No Qualification/Final (clsId == 0 || res.src->getClassRef(false)->getQualificationFinal() == 0); // // No Qualification/Final if (includeTeam && hasTeam) rProp.push_back(make_pair("team", itow(res.src->getTeam()->getId()))); if (hasTeam) rProp.push_back(make_pair("leg", itow(1+pRunner(res.src)->getLegNumber()))); rProp.push_back(make_pair("stat", itow(res.status))); if (res.score > 0) rProp.push_back(make_pair("score", itow(res.score))); rProp.push_back(make_pair("st", itow(10 * (res.src->getStartTime() + res.src->getEvent()->getZeroTimeNum())))); if (res.src->getClassRef(false) == 0 || !res.src->getClassRef(true)->getNoTiming()) { if (res.time > 0) rProp.push_back(make_pair("rt", itow(10 * res.time))); if (res.place > 0) rProp.push_back(make_pair("place", itow(res.place))); } auto &subTag = reslist.startTag("person", rProp); rProp.clear(); rProp.push_back(make_pair("id", itow(res.src->getId()))); subTag.write("name", rProp, res.src->getName()); rProp.clear(); if (res.src->getClubRef()) { rProp.push_back(make_pair("id", itow(res.src->getClubId()))); subTag.write("org", rProp, res.src->getClub()); rProp.clear(); } subTag.endTag(); } void RestServer::getData(oEvent &oe, const string &what, const multimap ¶m, string &answer) { xmlbuffer out; out.setComplete(true); bool okRequest = false; if (what == "iofresult") { wstring exportFile = getTempFile(); bool useUTC = false; set cls; if (param.count("class") > 0) getSelection(param.find("class")->second, cls); oe.exportIOFSplits(oEvent::IOF30, exportFile.c_str(), false, useUTC, cls, -1, false, false, true, false, false); ifstream fin(exportFile.c_str()); string rbf; while (std::getline(fin, rbf)) { answer += rbf; } removeTempFile(exportFile); okRequest = true; } else if (what == "iofstart") { wstring exportFile = getTempFile(); bool useUTC = false; set cls; if (param.count("class") > 0) getSelection(param.find("class")->second, cls); oe.exportIOFStartlist(oEvent::IOF30, exportFile.c_str(), useUTC, cls, false, true, false, false); ifstream fin(exportFile.c_str()); string rbf; while (std::getline(fin, rbf)) { answer += rbf; } removeTempFile(exportFile); okRequest = true; } else if (what == "competition") { InfoCompetition cmp(0); cmp.synchronize(oe); cmp.serialize(out, false); okRequest = true; } else if (what == "class") { vector cls; oe.getClasses(cls, true); set ctrlW; vector ctrl; oe.getControls(ctrl, true); for (size_t k = 0; k < ctrl.size(); k++) { if (ctrl[k]->isValidRadio()) { vector cc; ctrl[k]->getCourseControls(cc); ctrlW.insert(cc.begin(), cc.end()); } } for (auto c : cls) { InfoClass iCls(c->getId()); iCls.synchronize(false, *c, ctrlW); iCls.serialize(out, false); } okRequest = true; } else if (what == "organization") { vector clb; oe.getClubs(clb, true); for (auto c : clb) { InfoOrganization iClb(c->getId()); iClb.synchronize(*c); iClb.serialize(out, false); } okRequest = true; } else if (what == "competitor") { vector r; set selection; if (param.count("class") > 0) getSelection(param.find("class")->second, selection); oe.getRunners(selection.size() == 1 ? *selection.begin() : 0, -1, r, true); { vector r2; r2.reserve(r.size()); for (pRunner tr : r) { pClass cls = tr->getClassRef(true); if (cls && cls->getQualificationFinal() && tr->getLegNumber() != 0) continue; if (selection.empty() || (cls && selection.count(cls->getId()))) r2.push_back(tr); } r.swap(r2); } for (auto c : r) { InfoCompetitor iR(c->getId()); iR.synchronize(false, false, *c); iR.serialize(out, false); } okRequest = true; } else if (what == "team") { vector teams; set selection; if (param.count("class") > 0) getSelection(param.find("class")->second, selection); oe.getTeams(selection.size() == 1 ? *selection.begin() : 0, teams, true); if (selection.size() > 1) { vector r2; r2.reserve(teams.size()); for (pTeam tr : teams) { if (selection.count(tr->getClassId(false))) r2.push_back(tr); } teams.swap(r2); } for (auto c : teams) { InfoTeam iT(c->getId()); iT.synchronize(*c); iT.serialize(out, false); } okRequest = true; } else if (what == "control") { vector ctrl; oe.synchronizeList(oListId::oLControlId); oe.getControls(ctrl, true); vector< pair > prop(1); prop[0].first = "id"; for (pControl c : ctrl) { int nd = c->getNumberDuplicates(); for (int k = 0; k < nd; k++) { prop[0].second = itow(oControl::getCourseControlIdFromIdIndex(c->getId(), k)); if (nd > 1) out.write("control", prop, c->getName() + L"-" + itow(k+1)); else out.write("control", prop, c->getName()); } } okRequest = true; } else if (what == "result") { okRequest = true; vector r; set selection; pClass sampleClass = 0; if (param.count("class") > 0) { getSelection(param.find("class")->second, selection); if (!selection.empty()) sampleClass = oe.getClass(*selection.begin()); } oe.autoSynchronizeLists(true); oe.reEvaluateAll(selection, false); if (sampleClass == 0) { vector tt; oe.getClasses(tt, false); if (!tt.empty()) sampleClass = tt[0]; } bool includePreliminary = true; if (param.count("preliminary")) { const string& prl = param.find("preliminary")->second; includePreliminary = atoi(prl.c_str()) > 0 || _stricmp(prl.c_str(), "true") == 0; } string resTag; if (param.count("module") > 0) resTag = param.find("module")->second; pair controlId(oPunch::PunchStart, oPunch::PunchFinish); bool totalResult = false; if (param.count("total")) { const string &tot = param.find("total")->second; totalResult = atoi(tot.c_str()) > 0 || _stricmp(tot.c_str(), "true") == 0; } if (param.count("to")) { const string &cid = param.find("to")->second; controlId.second = atoi(cid.c_str()); if (controlId.second == 0) controlId.second = oControl::getControlIdByName(oe, cid); if (controlId.second == 0) throw meosException("Unknown control: " + cid); } if (param.count("from")) { const string &cid = param.find("from")->second; controlId.first = atoi(cid.c_str()); if (controlId.first == 0) controlId.first = oControl::getControlIdByName(oe, cid); if (controlId.first == 0) throw meosException("Unknown control: " + cid); } oListInfo::ResultType resType = oListInfo::Classwise; wstring resTypeStr = L"classindividual"; ClassType ct = sampleClass ? sampleClass->getClassType() : ClassType::oClassIndividual; bool team = ct == oClassPatrol || ct == oClassRelay; int limit = 100000; if (param.count("limit")) { limit = atoi(param.find("limit")->second.c_str()); if (limit <= 0) throw meosException("Invalid limit: " + param.find("limit")->second); } bool inclRunnersInForest = param.count("allrunners")>0; if (param.count("type")) { string type = param.find("type")->second; if (type == "GlobalIndividual") { resType = oListInfo::Global; team = false; } else if (type == "ClassIndividual") { resType = oListInfo::Classwise; team = false; } else if (type == "CourseIndividual") { resType = oListInfo::Coursewise; team = false; } else if (type == "LegIndividual") { resType = oListInfo::Legwise; team = false; } else if (type == "GlobalTeam") { resType = oListInfo::Global; team = true; } else if (type == "ClassTeam") { resType = oListInfo::Classwise; team = true; } else throw meosException("Unknown type: " + type); string2Wide(type, resTypeStr); resTypeStr = canonizeName(resTypeStr.c_str()); } else if (team) { resTypeStr = L"classteam"; } int inputNumber = 0; if (param.count("argument")) { const string &arg = param.find("argument")->second; inputNumber = atoi(arg.c_str()); } vector results; vector< pair > prop, noProp, rProp; prop.push_back(make_pair("type", resTypeStr)); int leg = -1; if (param.count("leg") > 0) { string legs = param.find("leg")->second; leg = atoi(legs.c_str())-1; if (leg < 0 || leg > 32) throw meosException("Invalid leg: " + legs); } if (ct == oClassRelay) { if (leg == -1) prop.push_back(make_pair("leg", L"Last")); else prop.push_back(make_pair("leg", itow(leg + 1))); } if (!resTag.empty()) prop.push_back(make_pair("module", oe.gdiBase().widen(resTag) + L"(" + itow(inputNumber) + L")")); if (controlId.first != oPunch::PunchStart) { pair idIx = oControl::getIdIndexFromCourseControlId(controlId.first); pControl ctrl = oe.getControl(idIx.first); if (ctrl == 0) throw meosException("Unknown control: " + itos(idIx.first)); wstring loc = ctrl->getName(); if (idIx.second > 0) loc += L"-" + itow(idIx.second + 1); prop.push_back(make_pair("from", loc)); } if (controlId.second == oPunch::PunchFinish) prop.push_back(make_pair("to", L"Finish")); else { pair idIx = oControl::getIdIndexFromCourseControlId(controlId.second); pControl ctrl = oe.getControl(idIx.first); if (ctrl == 0) throw meosException("Unknown control: " + itos(idIx.first)); wstring loc = ctrl->getName(); if (idIx.second > 0) loc += L"-" + itow(idIx.second+1); prop.push_back(make_pair("to", loc)); } if (!team) { oe.getRunners(selection.size() == 1 ? *selection.begin() : 0, -1, r, false); { vector r2; r2.reserve(r.size()); for (pRunner tr : r) { pClass cls = tr->getClassRef(true); if (cls && cls->getQualificationFinal() && tr->getLegNumber() != 0) continue; if (selection.empty() || (cls && selection.count(cls->getId()))) r2.push_back(tr); } r.swap(r2); } GeneralResult::calculateIndividualResults(r, controlId, totalResult, inclRunnersInForest, includePreliminary, resTag, resType, inputNumber, oe, results); if (resType == oListInfo::Classwise) sort(results.begin(), results.end()); /*else if (resType == oListInfo::Coursewise) { sort(results.begin(), results.end(), [](const GeneralResult::GeneralResultInfo &a, const GeneralResult::GeneralResultInfo &b)-> bool { pCourse ac = dynamic_cast(*a.src).getCourse(false); pCourse bc = dynamic_cast(*b.src).getCourse(false); if (ac != bc) return ac->getId() < bc->getId(); return a.compareResult(b); } ); }*/ auto &reslist = out.startTag("results", prop); int place = -1; int cClass = -1; int counter = 0; for (auto &res : results) { if (res.src->getClassId(true) != cClass) { counter = 0; place = 1; cClass = res.src->getClassId(true); } if (++counter > limit && (place != res.place || res.status != StatusOK)) continue; place = res.place; if (!includePreliminary && res.src->getStatus() == StatusUnknown) res.status = StatusUnknown; writePerson(res, reslist, true, resType == oListInfo::Coursewise, rProp); } reslist.endTag(); } else { //Teams vector teams; oe.getTeams(selection.size() == 1 ? *selection.begin() : 0, teams, true); if (selection.size() > 1) { vector r2; r2.reserve(teams.size()); for (pTeam tr : teams) { if (selection.count(tr->getClassId(true))) r2.push_back(tr); } teams.swap(r2); } auto context = GeneralResult::calculateTeamResults(teams, leg, controlId, totalResult, resTag, resType, inputNumber, oe, results); sort(results.begin(), results.end()); auto &reslist = out.startTag("results", prop); int place = -1; int cClass = -1; int counter = 0; for (const auto &res : results) { pTeam team = pTeam(res.src); if (leg >= team->getNumRunners()) continue; if (res.src->getClassId(true) != cClass) { counter = 0; place = 1; cClass = res.src->getClassId(true); } if (++counter > limit && (place != res.place || res.status != StatusOK)) continue; place = res.place; rProp.push_back(make_pair("cls", itow(res.src->getClassId(true)))); rProp.push_back(make_pair("stat", itow(res.status))); if (res.score > 0) rProp.push_back(make_pair("score", itow(res.score))); if (res.src->getStartTime() > 0) rProp.push_back(make_pair("st", itow(10 * (res.src->getStartTime() + oe.getZeroTimeNum())))); if (res.src->getClassRef(false) == 0 || !res.src->getClassRef(true)->getNoTiming()) { if (res.time > 0) rProp.push_back(make_pair("rt", itow(10 * res.time))); if (res.place > 0) rProp.push_back(make_pair("place", itow(res.place))); } auto &subTag = reslist.startTag("team", rProp); rProp.clear(); rProp.push_back(make_pair("id", itow(res.src->getId()))); subTag.write("name", rProp, res.src->getName()); rProp.clear(); if (res.src->getClubRef()) { rProp.push_back(make_pair("id", itow(res.src->getClubId()))); subTag.write("org", rProp, res.src->getClub()); rProp.clear(); } int subRes = res.getNumSubresult(*context); GeneralResult::GeneralResultInfo out; for (int k = 0; k < subRes; k++) { if (res.getSubResult(*context, k, out)) { writePerson(out, subTag, false, false, rProp); } } subTag.endTag(); } reslist.endTag(); } } else if (what == "status") { InfoMeosStatus iStatus; if (oe.empty()) { iStatus.setEventNameId(L""); // no event iStatus.setOnDatabase(false); } else { iStatus.setEventNameId(oe.getNameId(0)); // id of event iStatus.setOnDatabase(oe.isClient()); // onDatabase } iStatus.serialize(out, false); okRequest = true; } else if (what == "entryclass") { okRequest = true; vector classes; xmlparser mem; mem.openMemoryOutput(false); mem.startTag("EntryClasses", "xmlns", "http://www.melin.nu/mop"); if (epClass != EntryPermissionClass::None) { oe.getClasses(classes, true); for (auto cls : classes) { if (epClass == EntryPermissionClass::Any || cls->getAllowQuickEntry()) { mem.startTag("Class", "id", itow(cls->getId())); mem.write("Name", cls->getName()); for (auto &f : cls->getAllFees()) mem.write("Fee", f.first); pCourse crs = cls->getCourse(true); if (crs) { int len = crs->getLength(); if (len > 0) mem.write("Length", len); } int lowAge = cls->getDCI().getInt("LowAge"); if (lowAge > 0) mem.write("MinAge", lowAge); int highAge = cls->getDCI().getInt("HighAge"); if (highAge > 0) mem.write("MaxAge", highAge); auto sex = cls->getDCI().getString("Sex"); if (!sex.empty()) mem.write("Sex", sex); auto start = cls->getStart(); if (!start.empty()) mem.write("Start", start); if (cls->getNumberMaps() > 0) { int numMaps = cls->getNumRemainingMaps(false); mem.write("AvailableStarts", numMaps); } mem.endTag(); } } } mem.endTag(); mem.getMemoryOutput(answer); } if (out.size() > 0) { xmlparser mem; mem.openMemoryOutput(false); mem.startTag("MOPComplete", "xmlns", "http://www.melin.nu/mop"); out.commit(mem, 100000); mem.endTag(); mem.getMemoryOutput(answer); } else if (!okRequest) { answer = "Error (MeOS): Unknown command '" + what + "'"; } } void RestServer::getSelection(const string ¶m, set &sel) { vector sw; split(param, ";,", sw); for (auto &s : sw) { int id = atoi(s.c_str()); if (id > 0) sel.insert(id); } } shared_ptr RestServer::getRequest() { shared_ptr res; if (hasAnyRequest) { lock_guard lg(lock); if (!requests.empty()) { res = requests.front(); requests.pop_front(); } if (requests.empty()) hasAnyRequest = false; } return res; } shared_ptr RestServer::addRequest(multimap ¶m) { auto rq = make_shared(); rq->parameters.swap(param); lock_guard lg(lock); requests.push_back(rq); hasAnyRequest = true; return rq; } void RestServer::getStatistics(Statistics &s) { lock_guard lg(lock); s.numRequests = responseTimes.size(); s.maxResponseTime = 0; s.averageResponseTime = 0; for (int t : responseTimes) { s.maxResponseTime = max(s.maxResponseTime, t); s.averageResponseTime += t; } if (s.numRequests > 0) { s.averageResponseTime /= s.numRequests; } } void RestServer::lookup(oEvent &oe, const string &what, const multimap ¶m, string &answer) { bool okRequest = false; if (what == "competitor") { vector runners; if (param.count("card") > 0) { int card = atoi(param.find("card")->second.c_str()); int time = 0; if (param.count("running") && param.find("running")->second == "true") { time = oe.getRelativeTime(getLocalTime()); if (time < 0) time = 0; } oe.getRunnersByCardNo(card, false, oEvent::CardLookupProperty::Any, runners); } if (param.count("name") > 0) { wstring club; if (param.count("club")) club = wideParam(param.find("club")->second); pRunner r = oe.getRunnerByName(wideParam(param.find("name")->second), club); if (r) runners.push_back(r); } if (param.count("id") > 0) { pRunner r = oe.getRunner(atoi(param.find("id")->second.c_str()), 0); if (r) runners.push_back(r); } if (param.count("bib") > 0) { pRunner r = oe.getRunnerByBibOrStartNo(wideParam(param.find("bib")->second), true); if (r) runners.push_back(r); } xmlparser xml; xml.openMemoryOutput(false); xml.startTag("Competitors", "xmlns", "http://www.melin.nu/mop"); for (auto r : runners) { xml.startTag("Competitor", "id", itos(r->getId())); xml.write("Name", r->getName()); xml.write("ExternalId", r->getExtIdentifierString()); xml.write("Club", { make_pair("id", itow(r->getClubId())) }, r->getClub()); xml.write("Class", { make_pair("id", itow(r->getClassId(true))) }, r->getClass(true)); xml.write("Card", r->getCardNo()); xml.write("Status", {make_pair("code", itow(r->getStatusComputed(true)))}, r->getStatusS(true, true)); xml.write("Start", r->getStartTimeS()); if (r->getFinishTime() > 0 && r->getStatusComputed(true) != StatusNoTiming && !r->noTiming()) { xml.write("Finish", r->getFinishTimeS(true, SubSecond::Auto)); xml.write("RunningTime", r->getRunningTimeS(true, SubSecond::Auto)); xml.write("Place", r->getPlaceS()); xml.write("TimeAfter", formatTime(r->getTimeAfter())); } if (r->getTeam()) { xml.write("Team", { make_pair("id", itow(r->getTeam()->getId())) }, r->getTeam()->getName()); pClass cls = r->getClassRef(false); if (cls) xml.write("Leg", cls->getLegNumber(r->getLegNumber())); } if ((r->getFinishTime() > 0 || r->getCard() != nullptr) && r->getCourse(false) && r->getStatusComputed(true) != StatusNoTiming && !r->noTiming()) { auto &sd = r->getSplitTimes(false); vector after; r->getLegTimeAfter(after); vector afterAcc; r->getLegTimeAfterAcc(afterAcc); vector delta; r->getSplitAnalysis(delta); auto crs = r->getCourse(true); int ix = 0; xml.startTag("Splits"); vector> analysis = { make_pair("lost", L""), make_pair("behind", L""), make_pair("mistake", L""), make_pair("leg", L""), make_pair("total", L""), }; for (auto &s : sd) { auto ctrl = crs->getControl(ix); if (ctrl) { xml.startTag("Control", "number", itow(ix+1)); xml.write("Name", ctrl->getName()); if (s.hasTime()) { xml.write("Time", formatTime(s.getTime(true) - r->getStartTime())); if (size_t(ix) < delta.size() && size_t(ix) < after.size() && size_t(ix) < afterAcc.size()) { if (after[ix] > 0) analysis[0].second = formatTime(after[ix]); else analysis[0].second = L""; if (afterAcc[ix] > 0) analysis[1].second = formatTime(afterAcc[ix]); else analysis[1].second = L""; if (delta[ix] > 0) analysis[2].second = formatTime(delta[ix]); else analysis[2].second = L""; int place = r->getLegPlace(ix); analysis[3].second = place > 0 ? itow(place) : L""; int placeAcc = r->getLegPlaceAcc(ix); analysis[4].second = placeAcc > 0 ? itow(placeAcc) : L""; xml.write("Analysis", analysis, L""); } } else if (!s.isMissing()) xml.write("Time", "Unknown"); xml.endTag(); ix++; } } xml.endTag(); } xml.endTag(); } xml.endTag(); xml.getMemoryOutput(answer); } else if (what == "dbcompetitor") { vector wdb; if (param.count("card") > 0) { int card = atoi(param.find("card")->second.c_str()); auto res = oe.getRunnerDatabase().getRunnerByCard(card); if (res) wdb.push_back(res); } if (param.count("name") > 0) { int clubId = 0; if (param.count("club")) { wstring club = wideParam(param.find("club")->second); pClub clb = oe.getRunnerDatabase().getClub(club); if (clb) clubId = clb->getId(); } wstring name = wideParam(param.find("name")->second); auto res = oe.getRunnerDatabase().getRunnerSuggestions(name, clubId, 20); for (auto &r : res) wdb.push_back(r.first); } if (param.count("id") > 0) { long long id = oBase::converExtIdentifierString(wideParam(param.find("id")->second)); auto res = oe.getRunnerDatabase().getRunnerById(id); if (res) wdb.push_back(res); } xmlparser xml; xml.openMemoryOutput(false); xml.startTag("DatabaseCompetitors", "xmlns", "http://www.melin.nu/mop"); for (auto &r : wdb) { wstring name = r->getGivenName() + L" " + r->getFamilyName(); wchar_t bf[16]; oBase::converExtIdentifierString(r->getExtId(), bf); xml.startTag("Competitor", "id", bf); xml.write("Name", name); if (r->dbe().clubNo > 0) { wstring club; if (oe.getRunnerDatabase().getClub(r->dbe().clubNo, club)) { xml.write("Club", { make_pair("id", itow(r->dbe().clubNo)) }, club); } } xml.write("Card", r->dbe().cardNo); xml.write("Nationality", r->getNationality()); wstring sex = r->getSex(); if (!sex.empty()) xml.write("Sex", sex); if (r->dbe().getBirthYear() > 0) xml.write("BirthYear", itow(r->dbe().getBirthYear())); xml.endTag(); } xml.endTag(); xml.getMemoryOutput(answer); } else if (what == "dbclub") { vector clubs; if (param.count("name") > 0) { wstring club = wideParam(param.find("name")->second); clubs = oe.getRunnerDatabase().getClubSuggestions(club, 20); } if (param.count("id") > 0) { long long id = oBase::converExtIdentifierString(wideParam(param.find("id")->second)); auto res = oe.getRunnerDatabase().getClub(int(id)); if (res) clubs.push_back(res); } xmlparser xml; xml.openMemoryOutput(false); xml.startTag("DatabaseClubs", "xmlns", "http://www.melin.nu/mop"); for (auto &c : clubs) { xml.startTag("Club", "id", c->getExtIdentifierString()); xml.write("Name", c->getName()); xml.endTag(); } xml.endTag(); xml.getMemoryOutput(answer); } } void RestServer::setEntryPermission(EntryPermissionClass epClass, EntryPermissionType epType) { this->epClass = epClass; this->epType = epType; } void RestServer::newEntry(oEvent &oe, const multimap ¶m, string &answer) { xmlparser xml; xml.openMemoryOutput(false); xml.startTag("Answer", "xmlns", "http://www.melin.nu/mop"); bool permissionDenied = false; wstring error; if (epClass == EntryPermissionClass::None || epType == EntryPermissionType::None) permissionDenied = true; if (!permissionDenied) { oe.synchronizeList({ oListId::oLClassId, oListId::oLRunnerId }); wstring name, club; long long extId = 0; int clubId = 0; RunnerWDBEntry *dbr = nullptr; if (param.count("id")) { extId = oBase::converExtIdentifierString(wideParam(param.find("id")->second)); dbr = oe.getRunnerDatabase().getRunnerById(extId); if (dbr) { clubId = dbr->dbe().clubNo; } } else if(param.count("name")) name = wideParam(param.find("name")->second); if (param.count("club")) club = wideParam(param.find("club")->second); pClub existingClub = oe.getClub(club); if (epType != EntryPermissionType::Any) { if (extId == 0) { int clubExtId = 0; if (existingClub) clubExtId = (int)existingClub->getExtIdentifier(); else if (!club.empty()) { pClub dbClub = oe.getRunnerDatabase().getClub(club); if (dbClub) { clubExtId = dbClub->getId(); } } dbr = oe.getRunnerDatabase().getRunnerByName(name, clubExtId, 0); if (dbr) extId = dbr->getExtId(); } } if (existingClub) clubId = existingClub->getId(); int classId = 0; if (param.count("class")) classId = atoi(param.find("class")->second.c_str()); auto cls = oe.getClass(classId); if (cls == nullptr) { error = L"Okänd klass"; } else if (epClass != EntryPermissionClass::Any && !cls->getAllowQuickEntry()) { permissionDenied = true; } else { int nm = cls->getNumRemainingMaps(false); if (nm != numeric_limits::min() && nm<=0) { error = L"Klassen är full"; } } if (epType != EntryPermissionType::Any && extId == 0) { error = L"Anmälan måste hanteras manuellt"; } if (epType == EntryPermissionType::InDbExistingClub && clubId == 0) { error = L"Anmälan måste hanteras manuellt"; } bool noTiming = false; if (param.count("notiming")) noTiming = true; int cardNo = 0; if (param.count("card")) cardNo = atoi(param.find("card")->second.c_str()); if (cardNo <= 0) { error = L"Ogiltigt bricknummer X#" + itow(cardNo); } else { vector runners; oe.getRunnersByCardNo(cardNo, true, oEvent::CardLookupProperty::CardInUse, runners); for (auto r : runners) { if (!r->getCard()) { error = L"Bricknummret är upptaget (X)#" + r->getCompleteIdentification(); } } } if (!permissionDenied && error.empty()) { pRunner r = oe.addRunner(name, club, classId, cardNo, L"", true); if (r && dbr) { r->init(*dbr, true); } if (r) { int cf = 0; if (cardNo > 0 && oe.isHiredCard(cardNo)) { cf = oe.getBaseCardFee(); r->getDI().setInt("CardFee", cf); } r->setFlag(oRunner::FlagAddedViaAPI, true); r->addClassDefaultFee(true); if (noTiming) r->setStatus(StatusNoTiming, true, oBase::ChangeType::Update, false); r->synchronize(); r->markClassChanged(-1); xml.write("Status", "OK"); vector < pair > rentCard; if (cf != 0) { rentCard.emplace_back("hiredCard", L"true"); } xml.write("Fee", rentCard, itow(r->getDCI().getInt("Fee") + max(cf, 0))); xml.write("Info", r->getClass(true) + L", " + r->getCompleteIdentification(false)); if (r->getStatus() == StatusNoTiming) xml.write("NoTiming", "true"); } } } if (permissionDenied) { xml.write("Status", "Failed"); xml.write("Info", lang.tl("Permission denied")); } else if (!error.empty()) { xml.write("Status", "Failed"); xml.write("Info", lang.tl(error)); } xml.endTag(); xml.getMemoryOutput(answer); } vector> RestServer::getPermissionsPersons() { vector> res; res.emplace_back(lang.tl("Anyone"), size_t(EntryPermissionType::Any)); res.emplace_back(lang.tl("Från löpardatabasen"), size_t(EntryPermissionType::InDbAny)); res.emplace_back(lang.tl("Från löpardatabasen i befintliga klubbar"), size_t(EntryPermissionType::InDbExistingClub)); return res; } vector> RestServer::getPermissionsClass() { vector> res; res.emplace_back(lang.tl("Alla"), size_t(EntryPermissionClass::Any)); res.emplace_back(lang.tl("Med direktanmälan"), size_t(EntryPermissionClass::DirectEntry)); return res; } //constexpr int largePrime = 5000011; //constexpr int smallPrime = 101; constexpr int largePrime = 5000011; constexpr int smallPrime = 101; constexpr int idOffset = 100; int RestServer::InfoServerContainer::getNextInstanceId() { int newInstanceId = (thisInstanceId - idOffset + instanceIncrementor) % (largePrime * smallPrime); return newInstanceId + idOffset; } int RestServer::getNewInstanceId() { int limit = 10; if (isContainers.size() > 10) { isContainers.pop_back(); } set usedBaseId; for (auto &c : isContainers) { int baseId = (c.nextInstanceId - idOffset) % smallPrime; usedBaseId.insert(baseId); OutputDebugString((L"used: " + itow(baseId) + L"\n").c_str()); } if (!randGen) { random_device r; randGen = make_shared(r()); } int first = (*randGen)() % smallPrime; int off = 1 + (*randGen)() % (smallPrime-1); for (int i = 0; i < smallPrime; i++) { int s = (first + i * off) % smallPrime; if (!usedBaseId.count(s)) { int id = s + smallPrime * ((*randGen)() % 32767) + idOffset; int f = smallPrime * ((113 + (*randGen)() % 32767) % (largePrime - 1) + 1); OutputDebugString((L"add: " + itow(s) + L", " + itow(f) + L"\n").c_str()); isContainers.emplace_front(id, f); return id; } } throw meosException("No server instance found"); } xmlbuffer * RestServer::getMOPXML(oEvent &oe, int id, int &nextId) { for (auto it = isContainers.begin(); it != isContainers.end(); ++it) { auto &c = *it; if (c.thisInstanceId == id) { nextId = c.nextInstanceId; if (it != isContainers.begin()) isContainers.splice(isContainers.begin(), isContainers, it); return c.lastData.get(); } else if (c.nextInstanceId == id) { if (it != isContainers.begin()) isContainers.splice(isContainers.begin(), isContainers, it); oe.autoSynchronizeLists(true); if (!c.cmpModel) { c.cmpModel = make_shared(id); if (c.classes.empty()) { vector classes; oe.getClasses(classes, false); for (pClass pc : classes) { if (!pc->isQualificationFinalBaseClass()) c.classes.insert(pc->getId()); } } if (c.controls.empty()) { vector ctrl; vector ids; oe.getControls(ctrl, true); for (size_t k = 0; k < ctrl.size(); k++) { if (ctrl[k]->isValidRadio()) { ctrl[k]->getCourseControls(ids); c.controls.insert(ids.begin(), ids.end()); } } } } c.cmpModel->synchronize(oe, false, c.classes, c.controls, true); c.lastData = make_shared(); c.cmpModel->getDiffXML(*c.lastData); c.cmpModel->commitComplete(); if (c.lastData->size() == 0) { nextId = c.nextInstanceId; return c.lastData.get(); } c.thisInstanceId = id; nextId = c.nextInstanceId = c.getNextInstanceId(); return c.lastData.get(); } } return nullptr; } void RestServer::difference(oEvent &oe, int id, string &answer) { string type; if (id == -1) { id = getNewInstanceId(); } int nextId; xmlbuffer *bf = getMOPXML(oe, id, nextId); if (bf) { xmlparser mem; mem.openMemoryOutput(false); if (bf->isComplete()) type = "MOPComplete"; else type = "MOPDiff"; mem.startTag(type.c_str(), { L"xmlns", L"http://www.melin.nu/mop", L"nextdifference", itow(nextId)}); bf->commitCopy(mem); mem.endTag(); mem.getMemoryOutput(answer); } else { answer = "Error (MeOS): Unknown difference state. Use litteral 'zero' (?difference=zero) to get complete competition"; } }