#include <sys/socket.h> // For socket functions #include <netinet/in.h> // For sockaddr_in #include <cstdlib> // For exit() and EXIT_FAILURE #include <iostream> // For cout #include <fstream> #include <unistd.h> // For read #include <string> #include <cmath> #include <pgsql/libpq-fe.h> using namespace std; struct requestDetails{ string method; // HTTP paringu meetod string path; // aadress string HTTP_version; // HTTP versioon string acceptation; // Mida brauser võtab vastuseks }; struct requestDetails splitRequest( char* ); void sendResponse(int, struct requestDetails); string createSoloCharts(string, string, string); string createChartsOfOneYear(string, int&, string); string createChartsThroughYears(string, int&, string); PGresult* kohadJoel(string); int countWordsInLine(string, char); void loadJSScripts(); string addChart(string, string, int&); void replaceInString(string&, string, string); void replaceInString(string&, string, int); void replaceSymbols(string&); struct requestDetails splitRequest( char *startBuf ){ /* * Jagab suur HTTP paringu vaikeste osadeks * ehk taidab requestDetails struktuuri * ja tuhistab bufferi. */ struct requestDetails details; string buf(startBuf); // String class char[] asemel details.method = buf.substr(0, buf.find(' ')); // Salvestame meie meetodi buf.erase(0, buf.find(' ')+1); // Emaldame meetodi kogu paringust ehk salvestame 'GET' details.path = buf.substr(0, buf.find(' ')); // Salvestame aadressi, mida kasutaja tahab saada buf.erase(0, buf.find(' ')+1); // Eemaldame aadressi kogu paringust details.HTTP_version = buf.substr(0, buf.find('\r')); // Salvestame HTTP versioon (1.1) buf.erase(0, buf.find('\n')+1); // Emaldame HTTP versioon buf.erase(0, buf.find("Accept:")); // Emaldame koik 'Accept:' sonani buf.erase(0, buf.find(' ')+1); // Emaldame 'Accept:' sona details.acceptation = buf.substr(0, buf.find('\r')); // Salvestame, mida kasutaja votab vastu buf.erase(0, buf.find('\n')+1); // Emaldame, mida kasutaja votab vastu buf.clear(); // Tuhistame bufferi return details; } void sendResponse(int connection, struct requestDetails details){ /* * Votab vastu paringu detailid ja vastavalt sellele koostab paringu. */ string response; string aastad; if(details.path != "/favicon.ico"){ response = "HTTP/1.1 200 OK\r\n\ Server: KIRILL\r\n\ Keep-Alive: timeout=5, max=999\r\n\ "; // Koostab vastus, et fail on leitud ning ta saadab teda if(details.acceptation.find("text/html") != string::npos)response += "Content-type: text/html\r\n\r\n"; else if(details.acceptation.find("text/css") != string::npos)response += "Content-type: text/css\r\n\r\n"; else{ if(details.path.find(".js") != string::npos) response += "Content-type: text/javascript\r\n\r\n"; } } else { response = "HTTP/1.1 404 NOT FOUND\r\n\ Content-type: text/html\r\n\r\n"; // Saadakse, kui fail pole leitud } if(details.path == "/" || details.path == "index.html"){ // Kui kasutaja tuleb main lehele ifstream file; file.open("html/index.html", fstream::in); // Avame faili index.html if(!file.is_open()) cout << "No index.html", exit(1); // Akki index.html puudub string abi; while(!file.eof()){ // <- Loeme iga submol index.html lopuni char c = file.get(); // <- if(!file.eof()) response += c; // Lisame vastuseks } } else if(details.path.find("/response?", 0) != string::npos){ // Kui kasutaja taidas vormi // Vaatame punkti, mida ta valis. Edasi salvestame. string answer = details.path.substr(details.path.find("answer=", 0)+7); aastad = details.path.substr(details.path.find("aastad=", 0)+7, details.path.find("&answer=", 0)-details.path.find("aastad=", 0)-7); // Valmistame koik jogede nimed string rivers = details.path.substr(details.path.find("nimed=", 0)+6); // Salvestame nimed stringi rivers rivers.erase(rivers.find('&'), string::npos); // Eemaldame &aastad... while(rivers.find('+') != string::npos) // '+' asemel paneme tuhiku rivers.replace(rivers.find('+'), 1, " "); replaceSymbols(rivers); // Eesti sumbolite jaoks response += createSoloCharts(rivers, aastad, answer); // Valmistame graafikud ja lisame vastuseks } else { // Siin me uurime leida faili, mida tahab kasutaja. // Loeme faili ning lisame vastuseks // Kui fail puudub, siis kirjutame sellest string fileName = details.path.substr(1, string::npos); ifstream r(fileName); if(r.is_open()) while(!r.eof()){ if(r.peek() == -1) break; response += r.get(); } else response += "No such file"; }; response += "\r\n\r\n"; // Naitame, et HTTP vastus lopeb ning enam midagi ei tule. send(connection, response.c_str(), response.size(), 0); // Saadame kasutajale } string createSoloCharts(string rivers, string aastadeVahemik, string valik){ cout << rivers << " - " << aastadeVahemik << endl; // Funktsioon koostab graafikud antud valiku alusel // Votab vastu jogede nimed formaadis "Tallinn, Võhandu, Pärnu" ehk ', ' nimede vahel // Votab vastu uks aasta ehk Nt: "1956" voi aastade vahemikku Nt: "1956-2000" (1. arv vaiksem 2.) int wordsCount = countWordsInLine(rivers, ' '); // Loeme mitu jogede nimed meil on loadJSScripts(); // Lisame "response.js" faili vajalikud pluginid string response; string words[wordsCount]; // eemaldame ',' for(int i = 0; i < wordsCount; i++){ words[i].assign(rivers, 0, rivers.find(',')); rivers.erase(0, rivers.find(',')+2); } ///////////////////// int chartCounter = 0; // Loeme kui palju graafikud on uldse // Lisame uldise html osa alguse response = "<!DOCTYPE html>\n\ <html>\n\ <head>\n\ <title>Projekt</title>\n\ <meta charset=\"utf-8\">\n\ <link rel=\"stylesheet\" type=\"text/css\" href=\"css/chartist.css\">\n\ <link rel=\"stylesheet\" type=\"text/css\" href=\"css/CSS.css\">\n\ </head>\n\ <body>\n"; // Kasutaja valiku alusel teeme graafikud ja lisame vastuseks for(int i = 0; i < wordsCount; i++){ if(valik == "a1") response.append(createChartsThroughYears(words[i], chartCounter, aastadeVahemik)); if(valik == "a2") response.append(createChartsOfOneYear(words[i], chartCounter, aastadeVahemik)); } // Lisame uldise html osa loppu response += "<script type=\"text/javascript\" src=\"../scripts/chartist.js\"></script>\n\ <script type=\"text/javascript\" src=\"../scripts/chartist-plugin-pointlabels.js\"></script>\n\ <script type=\"text/javascript\" src=\"../scripts/response.js\"></script>\n\ </body>\n\ </html>"; return response; } string createChartsOfOneYear(string name, int &pos, string aastadeVahemik){ // Loob chartist.js graafiku, mis vastab mingi joe nimele ja valitud aastale // ja mida ta kirjutab/lisab "response.js", // ning tagastab html koodi selle graafiku jaoks (selleks oligi vaja chartCounter) PGconn *conn; PGresult *res; ofstream file("scripts/response.js", ofstream::app); // Avame faili, kus salvestame graafikud // Uhendame DB'ga conn = PQconnectdb("dbname=ewis host=ekleer.pld.ttu.ee user=read_ewis password=RO-A11ik45-2022"); if (PQstatus(conn) == CONNECTION_BAD) { // Kui ei onnestunud uhendada printf("We were unable to connect to the database\n"); exit(1); } res = kohadJoel(name); // Leiame koik kohad, kus oli tehtud mooteid int riversPlaceCount = PQntuples(res); // Loeme mitu neid oli kokku string riversPlaceNames[riversPlaceCount]; // Loome massiivi kohtade (valglade) nimedele string valgla[riversPlaceCount]; // Loome massiivi valglade pindalate jaoks for(int i = 0; i < riversPlaceCount; i++){ // Taidame molema massiivi riversPlaceNames[i] = PQgetvalue(res, i, 0); valgla[i] = PQgetvalue(res, i, 1); } string HTMLcode; // Tsukel on selle jaoks, et teha iga valgla kohta graafiku for(int totalCounter = 0; totalCounter < riversPlaceCount; totalCounter++){ HTMLcode.append(addChart(riversPlaceNames[totalCounter], valgla[totalCounter], pos)); //SQL paring string command = "SELECT DISTINCT seire_jogi_hydrol.hkuu, round(AVG(seire_jogi_hydrol.vaartus))\ FROM seire_jogi_hydrol\ INNER JOIN seire_jogi_hydrol_jaamad USING (id_jaam)\ INNER JOIN joe_andmed\ ON joe_andmed.id_jogi/10 = seire_jogi_hydrol_jaamad.id_jogi\ AND joe_andmed.id_peajogi = joe_andmed.id_jogi\ AND seire_jogi_hydrol_jaamad.lavendi_nimi = '{lavendi_nimi}'\ AND seire_jogi_hydrol.haasta = {aasta}\ GROUP BY seire_jogi_hydrol.hkuu\ ORDER BY seire_jogi_hydrol.hkuu;"; replaceInString(command, "{lavendi_nimi}", riversPlaceNames[totalCounter]); replaceInString(command, "{aasta}", aastadeVahemik.substr(0, aastadeVahemik.find('-'))); res = PQexec(conn, command.c_str()); // Maaksimum vooluhulk on sellejaoks, et naidat graafikul int maxValue = 0; for(int i = 0; i < PQntuples(res); i++){ if(maxValue < stoi(PQgetvalue(res, i, 1), nullptr)){ maxValue = stoi(PQgetvalue(res, i, 1), nullptr); } } if (PQresultStatus(res) != PGRES_TUPLES_OK) { // Kui paring onnestus cout << "We did not get any data!\n"; PQfinish(conn); return 0; } else { // Edasi koostakse JS script graafiku jaoks int rec_count = PQntuples(res); string toWrite; toWrite = "\nChartist.Line('#chart{pos}', {\n\tlabels: ["; replaceInString(toWrite, "{pos}", pos); pos++; if(rec_count > 0){ toWrite.append(PQgetvalue(res, 0, 0)); for(int i = 1; i < rec_count; i++){ toWrite += "+1, "; toWrite.append(PQgetvalue(res, i, 0)); } toWrite += "+1],\n\t\n\tseries: [\n\t\t{ name: 'Vooluhulk (m^3/s)', data: ["; toWrite.append(PQgetvalue(res, 0, 1)); for(int i = 1; i < rec_count; i++){ toWrite += ", "; toWrite.append(PQgetvalue(res, i, 1)); } } else { toWrite += "null],\n\n\tseries: [\n\t\t{ name: 'Vooluhulk (m^3/s)', data: ["; toWrite += "null"; } double sum = 0; for(int i = 0; i < rec_count; i++) sum += strtod(PQgetvalue(res, i, 1), NULL); sum = sum /(rec_count*sqrt(2)); if(rec_count > 0){ toWrite += "]},\n\t\n\t\t{ name: 'Maksimum-5%', data: ["; toWrite += " {max-5%}"; replaceInString(toWrite, "{max-5%}", to_string(0.001+(long)maxValue*0.95)); for(int i = 1; i < rec_count; i++){ toWrite += ", {max-5%}"; replaceInString(toWrite, "{max-5%}", to_string((long)maxValue*0.95)); } toWrite += "]},\n\t\n\t\t{ name: 'Maksimum+5%', data: ["; toWrite += " {max+5%}"; replaceInString(toWrite, "{max+5%}", to_string(0.001+(long)maxValue*1.05)); for(int i = 1; i < rec_count; i++){ toWrite += ", {max+5%}"; replaceInString(toWrite, "{max+5%}", to_string((long)maxValue*1.05)); } } else { toWrite += "]},\n\t\n\t\t{ name: 'Maksimum-5%', data: ["; toWrite += "null"; toWrite += "]},\n\t\n\t\t{ name: 'Maksimum+5%', data: ["; toWrite += "null"; } toWrite += "]}\n\t]\n}, {\n\tseries:{'Maksimum-5%': { showPoint: true, showLine: true, showArea: false},\n\t\ 'Maksimum+5%': { showPoint: true, showLine: true, showArea: false}},\n\t\ axisX: {showLabel: false},\n\twidth: 800,\n\theight: 600,\n"; toWrite += "\tplugins: [\n\t\tChartist.plugins.ctAccessibility({\n\t\t\tcaption:' Jõe voolu hulk "; if(rec_count > 0) toWrite += to_string(stoi(PQgetvalue(res, 0, 0), nullptr)+1); toWrite += "-"; if(rec_count > 0) toWrite += to_string(stoi(PQgetvalue(res, rec_count-1, 0), nullptr)+1); toWrite += "',\n\t\t\tseriesHeader: 'Kuud',\n"; toWrite += "\t\t\tsummary: 'Voolu hulgad, mis olid DB\\'s, teie valitud aasta eest.',\n"; toWrite += "\t\tvisuallyHiddenStyles: 'position: relative; text-align: center; top: 100%; width: 80%; left: 10%; font-size: 24px; overflow-x: auto;'\n\t\t}),\n"; toWrite += "\nChartist.plugins.ctThreshold({ threshold:"; if(rec_count > 0) toWrite += to_string(sum); else toWrite += to_string(0); toWrite += "}),"; toWrite += "\t\tChartist.plugins.ctAxisTitle({\n\ \n\t\t\taxisX: { axisTitle: 'Kuud', axisClass: 'ct-axis-title',\ \n\t\t\t\toffset: { x: 0, y: 20 }, textAnchor: 'middle'},\n\ \n\t\t\taxisY: { axisTitle: 'Vooluhulk (m^3/s)', axisClass: 'ct-axis-title',\ \n\t\t\t\toffset: { x: 0, y: 12 }, textAnchor: 'middle', flipTitle: true }\n\t\t})\n\t]\n});\n"; file.write(toWrite.c_str(), toWrite.length()); } } file.close(); PQclear(res); PQfinish(conn); return HTMLcode; } string createChartsThroughYears(string name, int &pos, string aastadeVahemik){ // Loob chartist.js graafikuid, mis vastab mingi joe nimele ja valitud aastade vahemikule // ja mida ta kirjutab/lisab "response.js", // ning tagastab html koodi selle graafiku jaoks (selleks oligi vaja chartCounter) PGconn *conn; PGresult *res; ofstream file("scripts/response.js", ofstream::app); conn = PQconnectdb("dbname=ewis host=ekleer.pld.ttu.ee user=read_ewis password=RO-A11ik45-2022"); if (PQstatus(conn) == CONNECTION_BAD) { printf("We were unable to connect to the database\n"); exit(1); } res = kohadJoel(name); int riversPlaceCount = PQntuples(res); string riversPlaceNames[riversPlaceCount]; string valgla[riversPlaceCount]; for(int i = 0; i < riversPlaceCount; i++){ riversPlaceNames[i] = PQgetvalue(res, i, 0); valgla[i] = PQgetvalue(res, i, 1); } string HTMLcode; for(int totalCounter = 0; totalCounter < riversPlaceCount; totalCounter++){ HTMLcode.append(addChart(riversPlaceNames[totalCounter], valgla[totalCounter], pos)); string command = "SELECT DISTINCT seire_jogi_hydrol.haasta, AVG(seire_jogi_hydrol.vaartus)\ FROM seire_jogi_hydrol\ INNER JOIN seire_jogi_hydrol_jaamad USING (id_jaam)\ INNER JOIN joe_andmed\ ON joe_andmed.id_jogi/10 = seire_jogi_hydrol_jaamad.id_jogi\ AND joe_andmed.id_peajogi = joe_andmed.id_jogi\ AND seire_jogi_hydrol_jaamad.lavendi_nimi = '{lavendi_nimi}'\ AND seire_jogi_hydrol.haasta >= {min_aasta}\ AND seire_jogi_hydrol.haasta <= {max_aasta}\ GROUP BY seire_jogi_hydrol.haasta\ ORDER BY seire_jogi_hydrol.haasta;"; replaceInString(command, "{lavendi_nimi}", riversPlaceNames[totalCounter]); replaceInString(command, "{min_aasta}", aastadeVahemik.substr(0, aastadeVahemik.find('-'))); replaceInString(command, "{max_aasta}", aastadeVahemik.substr(aastadeVahemik.find('-')+1, string::npos)); res = PQexec(conn, command.c_str()); if (PQresultStatus(res) != PGRES_TUPLES_OK) { cout << "We did not get any data!\n"; PQfinish(conn); return 0; } else { int rec_count = PQntuples(res); string toWrite; toWrite = "\nChartist.Line('#chart{pos}', {\n\tlabels: ["; replaceInString(toWrite, "{pos}", pos); pos++; if(rec_count > 0){ toWrite.append(PQgetvalue(res, 0, 0)); for(int i = 1; i < rec_count; i++){ toWrite += ", "; toWrite.append(PQgetvalue(res, i, 0)); } toWrite += "],\n\t\n\tseries: [\n\t\t{ name: 'Vooluhulk (m^3/s)', data: ["; toWrite.append(PQgetvalue(res, 0, 1)); for(int i = 1; i < rec_count; i++){ toWrite += ", "; toWrite.append(PQgetvalue(res, i, 1)); } } else { toWrite += "null],\n\n\tseries: [\n\t\t{ name: 'Vooluhulk (m^3/s)', data: ["; toWrite += "null"; } double sum = 0; for(int i = 0; i < rec_count; i++) sum += strtod(PQgetvalue(res, i, 1), NULL); sum = sum /(rec_count*sqrt(2)); if(rec_count > 0){ toWrite += "]},\n\t\n\t\t{ name: 'Ohtlik punkt all', data: ["; toWrite.append(to_string(sum)); for(int i = 1; i < rec_count; i++){ toWrite += ", "; toWrite.append(to_string(sum)); } } else { toWrite += "]},\n\t\n\t\t{ name: 'Ohtlik punkt all', data: ["; toWrite += "null"; } double ule = sum*2; if(rec_count > 0){ toWrite += "]},\n\t\n\t\t{ name: 'Ohtlik punkt üleval', data: ["; toWrite.append(to_string(ule+0.001)); for(int i = 1; i < rec_count; i++){ toWrite += ", "; toWrite.append(to_string(ule)); } } else { toWrite += "]},\n\t\n\t\t{ name: 'Ohtlik punkt üleval', data: ["; toWrite += "null"; } toWrite += "]}\n\t]\n}, {\n\tseries:{'Ohtlik punkt all': { showPoint: false, showLine: true, showArea: true},\ 'Ohtlik punkt üleval': { showPoint: false, showLine: true, showArea: false},\ 'Vooluhulk (m^3/s)': { showPoint: false, showLine: true, showArea: false}},\n\t\ axisX: {showLabel: false},\n\twidth: 800,\n\theight: 600,\n"; toWrite += "\tplugins: [\n\t\tChartist.plugins.ctAccessibility({\n\t\t\tcaption:' Jõe voolu hulk "; if(rec_count > 0) toWrite += PQgetvalue(res, 0, 0); toWrite += "-"; if(rec_count > 0) toWrite += PQgetvalue(res, PQntuples(res)-1, 0); toWrite += "',\n\t\t\tseriesHeader: 'Aastad',\n"; toWrite += "\t\t\tsummary: 'Voolu hulgad, mis olid DB\\'s, ning nende vastavad aasta',\n"; toWrite += "\t\tvisuallyHiddenStyles: 'position: relative; text-align: center; top: 100%; width: 80%; left: 10%; font-size: 24px; overflow-x: auto;'\n\t\t}),\n"; toWrite += "\nChartist.plugins.ctThreshold({ threshold:"; if(rec_count > 0) toWrite += to_string(sum); else toWrite += to_string(0); toWrite += "}),"; toWrite += "\t\tChartist.plugins.ctAxisTitle({\n\ \n\t\t\taxisX: { axisTitle: 'Aastad', axisClass: 'ct-axis-title',\ \n\t\t\t\toffset: { x: 0, y: 20 }, textAnchor: 'middle'},\n\ \n\t\t\taxisY: { axisTitle: 'Vooluhulk (m^3/s)', axisClass: 'ct-axis-title',\ \n\t\t\t\toffset: { x: 0, y: 12 }, textAnchor: 'middle', flipTitle: true }\n\t\t})\n\t]\n});\n"; file.write(toWrite.c_str(), toWrite.length()); } } file.close(); PQclear(res); PQfinish(conn); return HTMLcode; } PGresult* kohadJoel(string name){ // Leiab valglad, kust oli tehtud moodetus PGconn *conn; PGresult *res; conn = PQconnectdb("dbname=ewis host=ekleer.pld.ttu.ee user=read_ewis password=RO-A11ik45-2022"); if (PQstatus(conn) == CONNECTION_BAD) { printf("We were unable to connect to the database\n"); exit(1); } string command = "SELECT DISTINCT seire_jogi_hydrol_jaamad.lavendi_nimi, round(seire_jogi_hydrol_jaamad.valgla_km2, 2)\ FROM seire_jogi_hydrol\ INNER JOIN seire_jogi_hydrol_jaamad USING (id_jaam)\ INNER JOIN joe_andmed\ ON joe_andmed.id_jogi/10 = seire_jogi_hydrol_jaamad.id_jogi\ AND joe_andmed.id_peajogi = joe_andmed.id_jogi\ AND joe_andmed.joenimi = '{name}'\ AND seire_jogi_hydrol_jaamad.lavendi_nimi IS NOT NULL\ ;"; command.replace(command.find("{name}"), 6, name); res = PQexec(conn, command.c_str()); return res; } int countWordsInLine(string rivers, char symbol){ // Loeb mitu sona string classis on, mille vahel on symbol int wordsCount = 1; size_t pos = 0; while(pos < string::npos){ if(rivers.find(symbol, pos) == string::npos) break; wordsCount++; pos = rivers.find(symbol, pos) + 1; } return wordsCount; } void loadJSScripts(){ // Lisab "response.js" faili vajalikud teised JS scriptid ja pluginid ofstream w("scripts/response.js"); ifstream r("scripts/begining.js"); string abi; while(!r.eof()){ if(r.peek() == -1) break; abi += r.get(); } w << abi; w.close(); } string addChart(string name, string pindala, int &pos){ // Teeb HTML <div> konteineri graafiku jaoks string response; response += "<h1>{name}({pindala} km^2)</h1>\n"; replaceInString(response, "{name}", name); replaceInString(response, "{pindala}", pindala); response += "<div class=\"ct-chart\" id=\"chart{pos}\"></div>\n"; replaceInString(response, "{pos}", pos); return response; } void replaceInString(string &whereFind, string whatFind, string replaceWith){ // Ootsib mingis stringis mingi teine string ja vahetab teise stringiga whereFind.replace(whereFind.find(whatFind), whatFind.length(), replaceWith); } void replaceInString(string &whereFind, string whatFind, int replaceWith){ // Ootsib mingis stringis mingi teine string ja vahetab teise numbriga whereFind.replace(whereFind.find(whatFind), whatFind.length(), to_string(replaceWith)); } void replaceSymbols( string &rivers ){ // Tehtud eestide sumbolite jaoks // Vahetab kahebaitilise ASCII koodi tavalise sumboliga while(rivers.find('%') != string::npos){ size_t pos = rivers.find('%'); if(rivers[pos + 3] == '%'){ string sub = rivers.substr(pos, 6); rivers.erase(pos, 6); if(sub == "%C3%A4") rivers.insert(pos, "ä"); else if(sub == "%C3%B5") rivers.insert(pos, "õ"); else if(sub == "%C3%BC") rivers.insert(pos, "ü"); else if(sub == "%C3%B6") rivers.insert(pos, "ö"); else if(sub == "%C5%A1") rivers.insert(pos, "š"); else if(sub == "%C5%BE") rivers.insert(pos, "ž"); } else { int c = stoi(rivers.substr(pos+1, 2), nullptr, 16); rivers[pos] = (char) c; rivers.erase(pos+1, 2); } } }