This repository has been archived on 2022-08-16. You can view files and clone it, but cannot push or open issues or pull requests.
fanny-app/sources/serverconn.cpp

515 lines
17 KiB
C++

/*
Fannyapp - Application to view the cover plan of the Fanny-Leicht-Gymnasium ins Stuttgart Vaihingen, Germany
Copyright (C) 2019 Itsblue Development <development@itsblue.de>
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 <https://www.gnu.org/licenses/>.
*/
#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<QStringList> 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<QStringList> 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<eventsArray.count(); i++){
// get the current event-list out of the array
QJsonArray eventArray = eventsArray[i].toArray();
// lst to store the current event
QStringList tmpEventList;
// extract values from array
foreach(const QJsonValue & key, eventArray){
// and append them to the temporyry list
tmpEventList.append(key.toString());
}
while (tmpEventList.length() < 7) {
// enshure that the list contains at least seven items (to prevent getting list out of range errors)
tmpEventList.append("");
}
if(filtersList.isEmpty()){
// if there are no filters append the event immideatly
tmpEvents.append(tmpEventList);
}
else {
// if there is at least one filter, check if the event matches it
foreach(QStringList filter, filtersList){
// go through all filters and check if one of them matches the event
// always append the first row, as it is the legend
if((tmpEventList[0].contains(filter[0]) && tmpEventList[0].contains(filter[1])) || i == 0){
// append the eventList to the temporary event list
tmpEvents.append(tmpEventList);
// terminate the loop
break;
}
}
}
}
// check if there is any valid data
if(tmpEvents.length() == 0 || tmpEvents.length() == 1) {
// no data was delivered at all -> 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<QStringList> 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 -----+");
}