/* Fannyapp - Application to view the cover plan of the Fanny-Leicht-Gymnasium ins Stuttgart Vaihingen, Germany Copyright (C) 2019 Itsblue Development This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero 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 Affero General Public License for more details. You should have received a copy of the GNU Affero General Public License along with this program. If not, see . */ #include "headers/serverconn.h" #define http https ServerConn * pGlobalServConn = nullptr; ServerConn::ServerConn(QObject *parent) : QObject(parent) { qDebug("+----- ServerConn constructor -----+"); pGlobalServConn = this; this->fileHelper = new FileHelper(); connect(this->fileHelper, &FileHelper::shareError, [=](){qWarning() << "share error";}); connect(this->fileHelper, &FileHelper::shareFinished, [=](){qWarning() << "share finished";}); connect(this->fileHelper, &FileHelper::shareNoAppAvailable, [=](){qWarning() << "share no app available";}); // get local work path #if defined (Q_OS_IOS) QString docLocationRoot = QStandardPaths::standardLocations(QStandardPaths::DocumentsLocation).value(0); qDebug() << "iOS: QStandardPaths::DocumentsLocation: " << docLocationRoot; #elif defined(Q_OS_ANDROID) QString docLocationRoot = QStandardPaths::standardLocations(QStandardPaths::AppDataLocation).value(0); #else QString docLocationRoot = QStandardPaths::standardLocations(QStandardPaths::AppDataLocation).value(0); #endif mDocumentsWorkPath = docLocationRoot.append("/tmp_pdf_files"); if (!QDir(mDocumentsWorkPath).exists()) { if (!QDir("").mkpath(mDocumentsWorkPath)) { pGlobalAppSettings->writeSetting("localDocPathError", "true"); } } // check login state int perm = pGlobalAppSettings->loadSetting("permanent").toInt(); qDebug() << "+----- login state: " << perm << " -----+"; if(perm == 1){ // permanent login -> restore login this->username = pGlobalAppSettings->loadSetting("username"); this->password = pGlobalAppSettings->loadSetting("password"); this->setState("loggedIn"); } else { this->setState("notLoggedIn"); } } int ServerConn::login(QString username, QString password, bool permanent) { // add the data to the request QUrlQuery pdata; pdata.addQueryItem("username", username); pdata.addQueryItem("password", password); // send the request QVariantMap ret = this->senddata(QUrl("https://www.fanny-leicht.de/j34/templates/g5_helium/intern/events.php"), pdata); if(ret["status"].toInt() == 200){ // if not 200 was returned -> user data was correct // store username and password in the class variables this->username = username; this->password = password; if(permanent){ // if the user wants to say logged in, store the username and password to the settings file pGlobalAppSettings->writeSetting("permanent", "1"); pGlobalAppSettings->writeSetting("username", username); pGlobalAppSettings->writeSetting("password", password); } // set state to loggedIn this->setState("loggedIn"); qDebug() << "+----- logged in -----+"; // return success return(200); } else { // if not 200 was returned -> error -> return the return code this->setState("notLoggedIn"); // -> reset the stored credentinals pGlobalAppSettings->writeSetting("permanent", "0"); pGlobalAppSettings->writeSetting("username", ""); pGlobalAppSettings->writeSetting("password", ""); return(ret["status"].toInt()); } } int ServerConn::logout() { // reset the data stored in the class this->username = ""; this->password = ""; // reset the data stored in the settings pGlobalAppSettings->writeSetting("permanent", "0"); pGlobalAppSettings->writeSetting("username", ""); pGlobalAppSettings->writeSetting("password", ""); this->setState("notLoggedIn"); qDebug() << "+----- logout -----+"; // return success return(200); } int ServerConn::getEvents(QString day) { // day: 0-today; 1-tomorrow if(this->state != "loggedIn"){ return(401); } // add the data to the request QUrlQuery pdata; pdata.addQueryItem("username", this->username); pdata.addQueryItem("password", this->password); pdata.addQueryItem("mode", pGlobalAppSettings->loadSetting("teacherMode") == "true" ? "1":"0"); pdata.addQueryItem("day", day); // send the request QVariantMap ret = this->senddata(QUrl("https://www.fanny-leicht.de/j34/templates/g5_helium/intern/events.php"), pdata); if(ret["status"].toInt() != 200){ // if the request didn't result in a success, clear the old events, as they are probaply incorrect and return the error code this->m_events.clear(); if(ret["status"].toInt() == 401){ // if the stats code is 401 -> userdata is incorrect qDebug() << "+----- checkconn: user data is incorrect -----+"; logout(); } return ret["status"].toInt(); } // get the filers list for later usage QList filtersList = pGlobalAppSettings->readFilters(); // remove all elements from the filters list, that do not match the current mode ( teacher / student ) of the app for(int i = 0; i < filtersList.length(); i++){ QStringList filterList = filtersList[i]; if( !(pGlobalAppSettings->loadSetting("teacherMode") == "true" ? filterList[2] == "t":filterList[2] == "s") ){ filtersList.removeAt(i); i = i-1; } } // list to be returned QList tmpEvents; QStringList tmpEventHeader; //qDebug() << jsonString; QJsonDocument jsonFilters = QJsonDocument::fromJson(ret["text"].toString().toUtf8()); // array with tghe whole response in it QJsonObject JsonArray = jsonFilters.object(); // get the version of the json format QString version = JsonArray.value("version").toString(); QStringList versionList = version.split("."); if(versionList.length() < 3){ return(900); } int versionMajor = version.split(".")[0].toInt(); int versionMinor = version.split(".")[1].toInt(); //int versionRevision = version.split(".")[2].toInt(); if(versionMajor > this->apiVersion[0] || versionMinor > this->apiVersion[1]){ return(904); } // get parse the document out of the return QJsonObject dataArray = JsonArray.value("data").toObject(); // get the header data tmpEventHeader.append(dataArray.value("targetDate").toString()); tmpEventHeader.append(dataArray.value("refreshDate").toString()); tmpEventHeader.append(dataArray.value("stewardingClass").toString()); // expand the length of the header list to seven to prevent list out of range errors while (tmpEventHeader.length() < 7) { tmpEventHeader.append(""); } // append the header to the temporyry event list tmpEvents.append(tmpEventHeader); // get the event data QJsonArray eventsArray = dataArray.value("events").toArray(); for(int i=0; i the server encountered a parse error tmpEvents.clear(); ret["status"].setValue(900); } else if(tmpEvents.length() == 2) { // remove the last (in this case the second) element, as it is unnecessary (it is the legend -> not needed when there is no data) tmpEvents.takeLast(); // set return code to 'no data' (901) ret["status"].setValue(901); } this->m_events = tmpEvents; return(ret["status"].toInt()); } int ServerConn::openEventPdf(QString day) { // day: 0-today; 1-tomorrow if(this->state != "loggedIn"){ return 401; } if(pGlobalAppSettings->loadSetting("localDocPathError") == "true") { // we have no local document path to work with -> this is not going to work! return 905; } // add the data to the request QUrlQuery pdata; pdata.addQueryItem("username", this->username); pdata.addQueryItem("password", this->password); pdata.addQueryItem("mode", pGlobalAppSettings->loadSetting("teacherMode") == "true" ? "1":"0"); pdata.addQueryItem("day", day); pdata.addQueryItem("asPdf", "true"); // send the request QVariantMap ret = this->senddata(QUrl("https://www.fanny-leicht.de/j34/templates/g5_helium/intern/events.php"), pdata, true); if(ret["status"].toInt() != 200){ // if the request didn't result in a success, clear the old events, as they are probaply incorrect and return the error code this->m_events.clear(); if(ret["status"].toInt() == 401){ // if the stats code is 401 -> userdata is incorrect qDebug() << "+----- checkconn: user data is incorrect -----+"; logout(); } return ret["status"].toInt(); } QString path = QStandardPaths::writableLocation(QStandardPaths::DownloadLocation); QString filname = (QString(pGlobalAppSettings->loadSetting("teacherMode") == "true" ? "l":"s") + QString(day == "0" ? "heute":"morgen" )); QFile file(mDocumentsWorkPath + "/" + filname + ".pdf"); file.remove(); file.open(QIODevice::ReadWrite); file.write(ret["data"].toByteArray()); file.close(); this->fileHelper->viewFile(mDocumentsWorkPath + "/" + filname + ".pdf", "SMorgen", "application/pdf", 1); return 200; } int ServerConn::getFoodPlan() { // list with all data keys which need to be read from the API QStringList foodplanDataKeys = { "cookteam", "date", "mainDish", "mainDishVeg", "garnish", "dessert" }; QString foodplanDateKey = "date"; QString url = "http://www.treffpunkt-fanny.de/images/stories/dokumente/Essensplaene/api/TFfoodplanAPI.php?dataCount=10&dataMode=days&dateFormat=U&dataFromTime=now"; // construct the URL with all requested fields foreach(QString foodplanDataKey, foodplanDataKeys){ url.append("&fields[]="+foodplanDataKey); } QUrlQuery pdata; // send the request to the server QVariantMap ret = this->senddata(QUrl(url), pdata); if(ret["status"].toInt() != 200){ // if the request didn't result in a success, return the error code // if the request failed but there is still old data available if(!this->m_weekplan.isEmpty()){ // set the status code to 902 (old data) ret["status"].setValue(902); } return(ret["status"].toInt()); } // list to be returned // m_weekplan is a list, that contains a list for each day, which contains: cookteam, date, main dish, vagi main dish, garnish(Beilage) and Dessert. QList tmpWeekplan; //qDebug() << jsonString; QJsonDocument jsonDoc = QJsonDocument::fromJson(ret["text"].toString().toUtf8()); //qDebug() << ret["text"].toString(); // array with the whole response in it QJsonArray foodplanDays = jsonDoc.array(); foreach(QJsonValue foodplanDay, foodplanDays){ QStringList tmpFoodplanDayList; foreach(QString foodplanDataKey, foodplanDataKeys){ QString dataValue = foodplanDay.toObject().value(foodplanDataKey).toString(); if(foodplanDataKey == foodplanDateKey){ QDateTime date; date.setSecsSinceEpoch(uint(dataValue.toInt())); date.setTimeSpec(Qt::UTC); // get the current date and time QDateTime currentDateTime = QDateTime::currentDateTimeUtc(); //---------- convert the date to a readable string ---------- QString readableDateString; if(date.date() == currentDateTime.date()){ // the given day is today readableDateString = "Heute"; } else if (date.toTime_t() < ( currentDateTime.toTime_t() + ( 48 * 60 * 60 ) )) { // the given day is tomorrow readableDateString = "Morgen"; } else { readableDateString = date.toString("dddd, d.M.yy"); } dataValue = readableDateString; } tmpFoodplanDayList.append( dataValue ); } tmpWeekplan.append(tmpFoodplanDayList); } // write data to global foodplan this->m_weekplan = tmpWeekplan; // check if there is any valid data if(this->m_weekplan.isEmpty()){ // no data return(901); } // success return(200); } QVariantMap ServerConn::senddata(QUrl serviceUrl, QUrlQuery pdata, bool raw) { // create network manager QNetworkAccessManager * networkManager = new QNetworkAccessManager(); QVariantMap ret; //this is a custom type to store the return-data // Create network request QNetworkRequest request(serviceUrl); request.setHeader(QNetworkRequest::ContentTypeHeader, "application/x-www-form-urlencoded"); QSslConfiguration config = QSslConfiguration::defaultConfiguration(); config.setProtocol(QSsl::TlsV1_2); request.setSslConfiguration(config); //send a POST request with the given url and data to the server QNetworkReply *reply; // loop to wait until the request has finished before processing the data QEventLoop loop; // timer to cancel the request after 3 seconds QTimer timer; timer.setSingleShot(true); // quit the loop when the request finised loop.connect(networkManager, SIGNAL(finished(QNetworkReply*)), SLOT(quit())); // or the timer timed out loop.connect(&timer, SIGNAL(timeout()), &loop, SLOT(quit())); // start the timer timer.start(10000); this->updateDownloadProgress(0, 1); reply = networkManager->post(request, pdata.toString(QUrl::FullyEncoded).toUtf8()); connect(reply, &QNetworkReply::sslErrors, this, [=](){ reply->ignoreSslErrors(); }); connect(reply, SIGNAL(downloadProgress(qint64, qint64)), this, SLOT(updateDownloadProgress(qint64, qint64))); // start the loop loop.exec(); if(!timer.isActive()) { // timeout return {{"status", 0}}; } //get the status code QVariant status_code = reply->attribute(QNetworkRequest::HttpStatusCodeAttribute); ret.insert("status", status_code.toInt()); //get the full text response if(!raw) { ret.insert("text", QString::fromUtf8(reply->readAll())); } else { ret.insert("data", reply->readAll()); } // delete the reply object reply->deleteLater(); // delete the newtwork access manager object networkManager->deleteLater(); //return the data return(ret); } void ServerConn::updateDownloadProgress(qint64 read, qint64 total) { double progress; if(total <= 0) progress = 0; else progress = (double(read) / double(total)); if(progress < 0) progress = 0; this->downloadProgress = progress; emit this->downloadProgressChanged(); } QString ServerConn::getState() { return(this->state); } void ServerConn::setState(QString state) { if(state != this->state){ qDebug() << "+----- serverconn has new state: " << state << " -----+"; this->state = state; this->stateChanged(this->state); } } double ServerConn::getDownloadProgress() { return this->downloadProgress; } ServerConn::~ServerConn() { qDebug("+----- ServerConn destruktor -----+"); }