/************************************************************************ 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 ************************************************************************/ #include "stdafx.h" #include "oEvent.h" #include "xmlparser.h" #include #include "restbed/restbed" #include "meosexception.h" #include "restserver.h" #include "infoserver.h" #include #include "oListInfo.h" #include "TabList.h" #include "generalresult.h" using namespace restbed; vector< shared_ptr > RestServer::startedServers; 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(); 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(); 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())); } // lock.unlock(); session->fetch(content_length, [request, answer](const shared_ptr< Session > session, const Bytes & body) { //fprintf(stdout, "%.*s\n", (int)body.size(), body.data()); /*while (!answer->state) { std::this_thread::yield(); }*/ session->close(restbed::OK, answer->answer, { { "Content-Length", itos(answer->answer.length()) },{ "Connection", "close" } }); }); } 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); restService->start(settings); } 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(); } 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"; 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"; 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 htmlS += *html; ++html; resSize--; } rq->answer += htmlS; } rq->answer += "\n\n"; } 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("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(), ref.gdiBase().getCP()); gdiPrint.clearPage(false); if (!res->second.second) { res->second.second = make_shared(); ref.generateListInfo(res->second.first, gdiPrint.getLineHeight(), *res->second.second); } ref.generateList(gdiPrint, true, *res->second.second, false); wstring exportFile = getTempFile(); gdiPrint.writeHTML(exportFile, ref.getName(), 30); 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); 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); 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(*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, *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.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()); } if (sampleClass == 0) { vector tt; oe.getClasses(tt, false); if (!tt.empty()) sampleClass = tt[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); } 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, 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 (const 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; 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(); } } 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; } }