1
0
Fork 0
mirror of https://gitlab.com/niansa/qcommsy.git synced 2025-03-06 20:53:33 +01:00

Initial commit

This commit is contained in:
niansa 2020-08-09 11:03:52 +02:00
commit 1d26801a26
10 changed files with 1231 additions and 0 deletions

73
.gitignore vendored Normal file
View file

@ -0,0 +1,73 @@
# This file is used to ignore files which are generated
# ----------------------------------------------------------------------------
*~
*.autosave
*.a
*.core
*.moc
*.o
*.obj
*.orig
*.rej
*.so
*.so.*
*_pch.h.cpp
*_resource.rc
*.qm
.#*
*.*#
core
!core/
tags
.DS_Store
.directory
*.debug
Makefile*
*.prl
*.app
moc_*.cpp
ui_*.h
qrc_*.cpp
Thumbs.db
*.res
*.rc
/.qmake.cache
/.qmake.stash
# qtcreator generated files
*.pro.user*
# xemacs temporary files
*.flc
# Vim temporary files
.*.swp
# Visual Studio generated files
*.ib_pdb_index
*.idb
*.ilk
*.pdb
*.sln
*.suo
*.vcproj
*vcproj.*.*.user
*.ncb
*.sdf
*.opensdf
*.vcxproj
*vcxproj.*
# MinGW generated files
*.Debug
*.Release
# Python byte code
*.pyc
# Binaries
# --------
*.dll
*.exe

39
QCommsy.pro Normal file
View file

@ -0,0 +1,39 @@
QT += core gui network
greaterThan(QT_MAJOR_VERSION, 4): QT += widgets
CONFIG += c++17
# The following define makes your compiler emit warnings if you use
# any Qt feature that has been marked deprecated (the exact warnings
# depend on your compiler). Please consult the documentation of the
# deprecated API in order to know how to port your code away from it.
DEFINES += QT_DEPRECATED_WARNINGS
# You can also make your code fail to compile if it uses deprecated APIs.
# In order to do so, uncomment the following line.
# You can also select to disable deprecated APIs only up to a certain version of Qt.
#DEFINES += QT_DISABLE_DEPRECATED_BEFORE=0x060000 # disables all the APIs deprecated before Qt 6.0.0
SOURCES += \
main.cpp
HEADERS += \
libcommsy.hpp \
libcommsyauth.hpp
FORMS += \
failure.ui \
info.ui \
login.ui \
offline.ui \
overview.ui
LIBS += -lcurlpp -lcurl
LIBS += -lfuse
LIBS += -lgumbo
# Default rules for deployment.
qnx: target.path = /tmp/$${TARGET}/bin
else: unix:!android: target.path = /opt/$${TARGET}/bin
!isEmpty(target.path): INSTALLS += target

98
failure.ui Normal file
View file

@ -0,0 +1,98 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>failureWindow</class>
<widget class="QMainWindow" name="failureWindow">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>323</width>
<height>489</height>
</rect>
</property>
<property name="windowTitle">
<string>QCommsy</string>
</property>
<widget class="QWidget" name="centralwidget">
<layout class="QVBoxLayout" name="verticalLayout">
<item>
<widget class="QLabel" name="headerText">
<property name="font">
<font>
<pointsize>72</pointsize>
</font>
</property>
<property name="text">
<string>🙈</string>
</property>
<property name="alignment">
<set>Qt::AlignCenter</set>
</property>
</widget>
</item>
<item>
<spacer name="verticalSpacer">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>20</width>
<height>40</height>
</size>
</property>
</spacer>
</item>
<item>
<widget class="QLabel" name="descriptionText">
<property name="text">
<string/>
</property>
<property name="alignment">
<set>Qt::AlignCenter</set>
</property>
<property name="wordWrap">
<bool>true</bool>
</property>
</widget>
</item>
<item>
<spacer name="verticalSpacer_2">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>20</width>
<height>40</height>
</size>
</property>
</spacer>
</item>
<item>
<layout class="QHBoxLayout" name="horizontalLayout_2">
<item>
<widget class="QPushButton" name="logoutButton">
<property name="text">
<string>Abmelden</string>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="retryButton">
<property name="text">
<string>nochmal versuchen</string>
</property>
<property name="checkable">
<bool>true</bool>
</property>
</widget>
</item>
</layout>
</item>
</layout>
</widget>
</widget>
<resources/>
<connections/>
</ui>

66
info.ui Normal file
View file

@ -0,0 +1,66 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>infoWindow</class>
<widget class="QMainWindow" name="infoWindow">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>323</width>
<height>489</height>
</rect>
</property>
<property name="windowTitle">
<string>QCommsy</string>
</property>
<widget class="QWidget" name="centralwidget">
<layout class="QVBoxLayout" name="verticalLayout">
<item>
<spacer name="verticalSpacer_2">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>20</width>
<height>40</height>
</size>
</property>
</spacer>
</item>
<item>
<widget class="QLabel" name="versionInfo">
<property name="text">
<string/>
</property>
<property name="wordWrap">
<bool>true</bool>
</property>
</widget>
</item>
<item>
<spacer name="verticalSpacer">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>20</width>
<height>40</height>
</size>
</property>
</spacer>
</item>
<item>
<widget class="QPushButton" name="backButton">
<property name="text">
<string>Okay</string>
</property>
</widget>
</item>
</layout>
</widget>
</widget>
<resources/>
<connections/>
</ui>

389
libcommsy.hpp Normal file
View file

@ -0,0 +1,389 @@
#include <iostream>
#include <string>
#include <sstream>
#include <fstream>
#include <vector>
#include <map>
#include <regex>
#include <exception>
#include <csignal>
#include <cstdio>
#include <unistd.h>
#include <signal.h>
#include <execinfo.h>
#include <curlpp/cURLpp.hpp>
#include <curlpp/Easy.hpp>
#include <curlpp/Infos.hpp>
#include <curlpp/Options.hpp>
#include <nlohmann/json.hpp>
using json = nlohmann::json;
#include <gumbo.h>
class invalidSIDError : public std::exception {};
class invalidRoomError : public std::exception {};
class invalidPostError : public std::exception {};
class connectionFailError : public std::exception {};
class parsingNoSuchIDError : public std::exception {};
class parsingNoSuchTagError : public std::exception {};
class descDownloadError : public std::exception {};
static std::string server_url;
static std::string server_sid;
static std::string room;
std::string ltrim(const std::string& s) {
static const std::regex lws{"^[[:space:]]*", std::regex_constants::extended};
return std::regex_replace(s, lws, "");
}
std::string rtrim(const std::string& s) {
static const std::regex tws{"[[:space:]]*$", std::regex_constants::extended};
return std::regex_replace(s, tws, "");
}
std::string trim(const std::string& s) {
return ltrim(rtrim(s));
}
std::string get_filename(const std::string& path) {
return path.substr(path.find_last_of("/\\") + 1);
}
std::string clean_spaces(const std::string& s) {
static const std::regex tws{"[ ]{2,}", std::regex_constants::extended};
std::string newstr = std::regex_replace(s, tws, "");
std::replace(newstr.begin(), newstr.end(), '\n', ' ');
newstr.erase(0, 4);
return newstr;
}
std::vector<std::string> merge_strvects(std::vector<std::string> base, const std::vector<std::string> &addition) {
base.insert(base.end(), addition.begin(), addition.end());
return base;
}
static long curlreq(std::stringstream &responsebuffer, std::string SID, std::string URL) {
// Initialise variables
curlpp::Cleanup cleaner;
curlpp::Easy request;
// Set the writer callback to enable cURL to write result in a memory area
request.setOpt(new curlpp::options::WriteStream(&responsebuffer));
// Setting the URL to retrive.
request.setOpt(new curlpp::options::Url(URL));
// Set SID cookie
std::list<std::string> header;
header.push_back("Cookie: SID=" + SID);
request.setOpt(new curlpp::options::HttpHeader(header));
// Perform request
request.perform();
// Return result
return curlpp::infos::ResponseCode::get(request);
}
void gumbo_search_by_attr(std::vector<GumboNode *> &elemvect, GumboNode* node, std::string attrname, std::string searchword, GumboTag expectedtag) {
if (node->type != GUMBO_NODE_ELEMENT) {
return;
}
GumboAttribute* hclass;
if (node->v.element.tag == expectedtag &&
(hclass = gumbo_get_attribute(&node->v.element.attributes, attrname.c_str()))) {
if (hclass->value == searchword) {
elemvect.push_back(node);
}
}
GumboVector* children = &node->v.element.children;
for (unsigned int i = 0; i < children->length; ++i) {
gumbo_search_by_attr(elemvect, static_cast<GumboNode*>(children->data[i]), attrname, searchword, expectedtag);
}
}
void gumbo_search_by_class(std::vector<GumboNode *> &elemvect, GumboNode* node, std::string searchword, GumboTag expectedtag) {
return gumbo_search_by_attr(elemvect, node, "class", searchword, expectedtag);
}
GumboNode *gumbo_search_by_id(GumboNode* node, std::string searchword, GumboTag expectedtag) {
std::vector<GumboNode *> elemvect;
gumbo_search_by_attr(elemvect, node, "id", searchword, expectedtag);
// Use first node found
if (elemvect.size() > 0) {
return elemvect[0];
}
// If no nodes were found, panic()
throw parsingNoSuchIDError();
return new GumboNode;
}
void gumbo_search_by_tag(std::vector<GumboNode *> &elemvect, GumboNode* node, GumboTag searchedtag) {
if (node->type != GUMBO_NODE_ELEMENT) {
return;
}
if (node->v.element.tag == searchedtag) {
elemvect.push_back(node);
}
GumboVector* children = &node->v.element.children;
for (unsigned int i = 0; i < children->length; ++i) {
gumbo_search_by_tag(elemvect, static_cast<GumboNode*>(children->data[i]), searchedtag);
}
}
static std::string gumbo_cleantext(GumboNode* node) {
if (node->type == GUMBO_NODE_TEXT) {
return std::string(node->v.text.text);
} else if (node->type == GUMBO_NODE_ELEMENT &&
node->v.element.tag != GUMBO_TAG_SCRIPT &&
node->v.element.tag != GUMBO_TAG_STYLE) {
std::string contents = "";
GumboVector* children = &node->v.element.children;
for (unsigned int i = 0; i < children->length; ++i) {
const std::string text = gumbo_cleantext(reinterpret_cast<GumboNode*> (children->data[i]));
if (i != 0 && !text.empty()) {
contents.append(" ");
}
contents.append(text);
}
return contents;
} else {
return "";
}
}
std::vector<std::string> gumbo_get_attr(GumboNode *node, std::string attrkey, GumboTag expected_tag) {
std::vector<std::string> attrvals;
GumboNode *childnode;
GumboVector* children = &node->v.element.children;
std::vector<std::string> toappend;
// Check if current element is already the right one
if (node->v.element.tag == expected_tag) {
// Return this elements wanted attribute key
return {gumbo_get_attribute(&node->v.element.attributes, attrkey.c_str())->value};
}
// Check if This is a node element
else if (node->type != GUMBO_NODE_ELEMENT) {
return {};
}
// Iterate through child nodes
for (unsigned int it = 0; it < children->length; ++it) {
childnode = reinterpret_cast<GumboNode*> (children->data[it]);
if (childnode->v.element.tag == expected_tag) { // If node is the expected tag; use it
attrvals.push_back(gumbo_get_attribute(&childnode->v.element.attributes, attrkey.c_str())->value);
} else if (childnode->type == GUMBO_NODE_ELEMENT) { // Else; iterate through its child nodes
toappend = gumbo_get_attr(childnode, attrkey, expected_tag);
attrvals = merge_strvects(attrvals, toappend);
}
}
// Return the final result
return attrvals;
}
std::string gumbo_find_text_by_tag(GumboNode *node, GumboTag searchtag) {
GumboNode *childnode;
GumboVector* children = &node->v.element.children;
// Iterate through childs
for (unsigned int it = 0; it < children->length; ++it) {
childnode = reinterpret_cast<GumboNode*> (children->data[it]);
if (childnode->v.element.tag == searchtag) { // If node is the expected tag; check content
return trim(gumbo_cleantext(childnode));
}
}
throw parsingNoSuchTagError();
return "";
}
auto get_posts(GumboNode *node) {
std::vector<GumboNode *> posts;
gumbo_search_by_class(posts, node, "uk-comment", GUMBO_TAG_ARTICLE);
return posts;
}
std::string get_post_name(GumboNode *node) {
std::vector<GumboNode *> titlenodes;
gumbo_search_by_class(titlenodes, node, "uk-comment-title", GUMBO_TAG_H4);
return trim(gumbo_cleantext(titlenodes[0]));
}
std::string get_post_id(GumboNode *node) {
return gumbo_get_attr(node, "data-item-id", GUMBO_TAG_ARTICLE)[0];
}
std::string get_post_meta(GumboNode *node) {
std::vector<GumboNode *> metanodes;
gumbo_search_by_class(metanodes, node, "uk-comment-meta", GUMBO_TAG_DIV);
return clean_spaces(trim(gumbo_cleantext(metanodes[1])));
}
std::string get_post_url(GumboNode *node) {
std::vector<GumboNode *> titlenodes;
gumbo_search_by_class(titlenodes, node, "uk-comment-title", GUMBO_TAG_H4);
return gumbo_get_attr(titlenodes[0], "href", GUMBO_TAG_A)[0];
}
std::vector<std::map<std::string, std::string>> get_post_files(GumboNode *node) {
std::vector<GumboNode *> metanodes;
std::vector<std::string> fileurls;
std::vector<std::string> filenames;
std::vector<std::map<std::string, std::string>> filenameurlmap;
std::map<std::string, std::string> tmpmap;
// Get meta nodes
gumbo_search_by_class(metanodes, node, "uk-comment-meta", GUMBO_TAG_DIV);
// Get URLs
fileurls = gumbo_get_attr(metanodes[2], "href", GUMBO_TAG_A);
// Get filenames
filenames = gumbo_get_attr(metanodes[2], "title", GUMBO_TAG_A);
// Generate map
auto urlit = fileurls.begin();
auto nameit = filenames.begin();
while (true) {
// Break if last item was reached
if (urlit == fileurls.end() or nameit == filenames.end()) {
break;
}
// Generate temporary map
tmpmap = {};
tmpmap[*nameit] = *urlit;
// Append it to the result vector map
filenameurlmap.push_back(tmpmap);
// Get next item in both vectors
urlit++; nameit++;
}
return filenameurlmap;
}
std::string get_post_desc(std::string post_url) {
std::string material_id;
std::stringstream httpcontent;
GumboOutput *post_document;
GumboNode *desc_node;
std::vector<GumboNode *> results;
// Get material ID
material_id = get_filename(post_url);
// Download post
long statuscode = curlreq(httpcontent, server_sid, post_url);
// Check statuscode
if (statuscode != 200) {
throw descDownloadError();
}
// Parse post
post_document = gumbo_parse(httpcontent.str().c_str());
// Get description element
desc_node = gumbo_search_by_id(post_document->root, "description" + material_id, GUMBO_TAG_DIV);
// Extract description
gumbo_search_by_tag(results, desc_node, GUMBO_TAG_P);
// Cencenate occurencies
std::string result_string;
for (auto it = results.begin(); it != results.end(); it++) {
result_string.append(trim(gumbo_cleantext(*it)) + "\n");
}
// Return first occurence
return result_string;
}
struct commsyPost {
std::string name;
std::string id;
std::string description;
std::string meta;
std::string url;
std::map<std::string, std::string> files;
};
#define libCommsy_NAME "libcommsy"
#define libCommsy_VERSION "1.0"
class libCommsy {
public:
std::vector<commsyPost> posts;
unsigned long numposts;
bool postExists(unsigned long postID) {
return postID < numposts;
}
commsyPost *getPost(unsigned long postID) {
// Check if post exists
if (not postExists(postID)) {
throw invalidPostError();
}
// Return post pointer
return &posts[postID];
}
std::string *getDescription(unsigned long postID) {
// Get post
commsyPost *thispost = getPost(postID);
// Check if post description was downloaded already
if (thispost->description.empty()) {
// Download post
thispost->description = get_post_desc(server_url + thispost->url);
}
// Return it
return &thispost->description;
}
libCommsy(std::string _server_url, std::string _server_sid, std::string _room) {
// Define required variables
server_url = _server_url;
server_sid = _server_sid;
room = _room;
std::string lastID;
std::stringstream httpcontent;
GumboOutput *document;
long statuscode;
numposts = 0;
while (1) {
// Check connection and download feed
try {
statuscode = curlreq(httpcontent, server_sid, server_url + "/room/" + room + "/feed/10/date?lastId=" + lastID);
} catch (std::exception&) {
throw connectionFailError();
}
if (statuscode == 302) {
throw invalidSIDError();
} else if (statuscode == 500) {
throw invalidRoomError();
} else if (statuscode != 200) {
throw connectionFailError();
}
// Do some stuff
document = gumbo_parse(httpcontent.str().c_str());
httpcontent.str(std::string()); // Clear buffer just in case we need it later
// Get posts
auto gumboPosts = get_posts(document->root);
if (gumboPosts.size() == 0) {
// Stop fetching more data
break;
}
// Map posts and their corresponding URL to a number
for (auto it = gumboPosts.begin(); it != gumboPosts.end(); it++) {
// Create post struct
commsyPost thispost;
{
// Get posts name
thispost.name = get_post_name(*it);
// Get posts ID
thispost.id = get_post_id(*it);
// Get posts meta string
thispost.meta = get_post_meta(*it);
// Get posts URL
thispost.url = get_post_url(*it);
// Get posts files
auto files = get_post_files(*it);
for (const auto& filemap : files) {
thispost.files.insert(filemap.begin(), filemap.end());
}
}
// Append to posts vector
posts.push_back(thispost);
// Increment post counter
numposts++;
// Get lastID
lastID = posts.back().id;
}
}
}
};

56
libcommsyauth.hpp Normal file
View file

@ -0,0 +1,56 @@
#include <exception>
#include <QString>
#include <QUrl>
#include <QUrlQuery>
#include <QNetworkAccessManager>
#include <QNetworkReply>
#include <QNetworkRequest>
#include <QNetworkCookie>
#define libCommsyAuth_NAME "libcommsyauth"
#define libCommsyAuth_VERSION "0.1-alpha"
class authFailureError : public std::exception {};
class libCommsyAuth : QObject {
public:
QNetworkReply *connect(const QUrl& serverBaseUrl) {
// Initialise variables
QNetworkAccessManager mNetMan(this);
QNetworkRequest mNetReq;
// Initialise request
//mNetReq->setTransferTimeout(15000);
mNetReq.setUrl(serverBaseUrl);
mNetReq.setAttribute(QNetworkRequest::FollowRedirectsAttribute, true);
return mNetMan.get(mNetReq);
}
QString getAuthUrl(const QNetworkReply *reply) {
return reply->url().toString();
}
QNetworkReply *sendAuth(const QString& authUrl, const QString& username, const QString& password) {
QNetworkAccessManager mNetMan(this);
QNetworkRequest mNetReq;
QUrlQuery mNetQuery;
// Set auth credentials
mNetQuery.addQueryItem("user_id", username);
mNetQuery.addQueryItem("password", password);
// Create request
mNetReq.setUrl(authUrl + "&mod=context&fct=login");
mNetReq.setHeader(QNetworkRequest::ContentTypeHeader, "application/x-www-form-urlencoded");
// Perform the request
return mNetMan.post(mNetReq, mNetQuery.toString(QUrl::FullyEncoded).toUtf8());
}
QString getSID(QNetworkReply *reply) {
auto cookies = QNetworkCookie::parseCookies(reply->rawHeader("Set-Cookie"));
for (const auto& cookie : cookies) {
if (cookie.name() == "SID") {
return cookie.value();
}
}
throw authFailureError();
}
};

130
login.ui Normal file
View file

@ -0,0 +1,130 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>loginWindow</class>
<widget class="QMainWindow" name="loginWindow">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>323</width>
<height>489</height>
</rect>
</property>
<property name="windowTitle">
<string>QCommsy</string>
</property>
<widget class="QWidget" name="centralwidget">
<layout class="QVBoxLayout" name="verticalLayout">
<item>
<spacer name="verticalSpacer_2">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>20</width>
<height>40</height>
</size>
</property>
</spacer>
</item>
<item>
<widget class="QLabel" name="failureText">
<property name="font">
<font>
<pointsize>8</pointsize>
</font>
</property>
<property name="styleSheet">
<string notr="true">color: red;</string>
</property>
<property name="text">
<string/>
</property>
</widget>
</item>
<item>
<widget class="QLineEdit" name="adressLine">
<property name="inputMethodHints">
<set>Qt::ImhNoAutoUppercase|Qt::ImhNoPredictiveText|Qt::ImhPreferLowercase|Qt::ImhUrlCharactersOnly</set>
</property>
<property name="text">
<string>https://unterricht.sh.schulcommsy.de</string>
</property>
<property name="placeholderText">
<string>Server Adresse</string>
</property>
</widget>
</item>
<item>
<widget class="QLineEdit" name="usernameLine">
<property name="text">
<string/>
</property>
<property name="placeholderText">
<string>Kennung</string>
</property>
</widget>
</item>
<item>
<widget class="QLineEdit" name="roomLine">
<property name="inputMethodHints">
<set>Qt::ImhDigitsOnly</set>
</property>
<property name="text">
<string/>
</property>
<property name="maxLength">
<number>7</number>
</property>
<property name="placeholderText">
<string>Raum</string>
</property>
</widget>
</item>
<item>
<widget class="QLineEdit" name="passwordLine">
<property name="text">
<string/>
</property>
<property name="echoMode">
<enum>QLineEdit::Password</enum>
</property>
<property name="placeholderText">
<string>SID</string>
</property>
</widget>
</item>
<item>
<widget class="QCheckBox" name="saveBox">
<property name="text">
<string>Daten speichern</string>
</property>
</widget>
</item>
<item>
<spacer name="verticalSpacer">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>20</width>
<height>40</height>
</size>
</property>
</spacer>
</item>
<item>
<widget class="QPushButton" name="loginButton">
<property name="text">
<string>Anmelden</string>
</property>
</widget>
</item>
</layout>
</widget>
</widget>
<resources/>
<connections/>
</ui>

225
main.cpp Normal file
View file

@ -0,0 +1,225 @@
#include "ui_overview.h"
#include "ui_info.h"
#include "ui_login.h"
#include "ui_failure.h"
#include "ui_offline.h"
#include <QMainWindow>
#include <QApplication>
#include <QPushButton>
#include <QString>
#include <QUrl>
#include <QMainWindow>
#include <QStyleFactory>
#include <QDesktopServices>
#include <QSettings>
#include <limits.h>
#include "libcommsy.hpp"
#include "libcommsyauth.hpp"
#define QCommsy_NAME "QCommsy"
#define QCommsy_VERSION "0.1-alpha"
QSettings *settings;
class _UILoader {
libCommsy *connector = nullptr;
libCommsyAuth *auther;
bool cacheInvalidate = true;
public:
_UILoader() {
auther = new libCommsyAuth();
}
void reconnect() {
// Destroy old connector
if (connector != nullptr) {
if (not cacheInvalidate) {
return;
}
delete connector;
connector = nullptr;
}
cacheInvalidate = false;
// Connect to commsy
connector = new libCommsy(settings->value("server_url").toString().toStdString(), settings->value("server_sid").toString().toStdString(), settings->value("server_room").toString().toStdString());
}
void logout() {
settings->remove("server_sid");
}
void overviewWindow(QMainWindow *w, bool catchInvalidRoomError = true);
void failureWindow(QMainWindow *w);
void loginWindow(QMainWindow *w, const QString& failure = "");
void infoWindow(QMainWindow *w);
void offlineWindow(QMainWindow *w);
};
void _UILoader::infoWindow(QMainWindow *w) {
Ui::infoWindow *thisui = new Ui::infoWindow;
// Show initial UI
thisui->setupUi(w);
w->show();
// Update version info
thisui->versionInfo->setText(
"<b>App frontend:</b> " QCommsy_NAME " version " QCommsy_VERSION "<br />"
"<b>Authentication backend:</b> " libCommsyAuth_NAME " version " libCommsyAuth_VERSION "<br />"
"<b>Scrapping backend:</b> " libCommsy_NAME " version " libCommsy_VERSION
);
// Add button handlers
w->connect(thisui->backButton, &QPushButton::pressed, [this, w] () {
loginWindow(w);
});
}
void _UILoader::failureWindow(QMainWindow *w) {
Ui::failureWindow *thisui = new Ui::failureWindow;
QString reason;
// Show initial UI
thisui->setupUi(w);
w->show();
// Get error message
reason = "Ein interner Fehler ist aufgetreten<br />"
"<i>Dies ist sehr wahrscheinlich ein Programmfehler. Sollte das zu häufig passieren, kontaktiere bitte den Entwickler</i>";
// Show error message
thisui->descriptionText->setText("<b>Verbindung fehlgeschlagen</b><br />" + reason);
// Add button handlers
w->connect(thisui->retryButton, &QPushButton::pressed, [this, w] () {
loginWindow(w);
});
w->connect(thisui->logoutButton, &QPushButton::pressed, [this, w] () {
logout();
loginWindow(w);
});
}
void _UILoader::offlineWindow(QMainWindow *w) {
Ui::offlineWindow *thisui = new Ui::offlineWindow;
QString reason;
// Show initial UI
thisui->setupUi(w);
w->show();
// Add button handlers
w->connect(thisui->logoutButton, &QPushButton::pressed, [this, w] () {
logout();
loginWindow(w);
});
}
void _UILoader::loginWindow(QMainWindow *w, const QString& failure) {
Ui::loginWindow *thisui = new Ui::loginWindow;
// Check if logged in already
if (settings->contains("server_sid")) {
return overviewWindow(w);
}
// Show initial UI
thisui->setupUi(w);
w->show();
// Restore login data if wanted
bool restoreForm = settings->value("server_save", false).toBool();
if (restoreForm) {
thisui->saveBox->setCheckState(Qt::Checked);
//... TODO
}
// Set failure text
thisui->failureText->setText(failure);
// Add button handlers
w->connect(thisui->loginButton, &QPushButton::pressed, [this, thisui, w] () {
// Check input lines
if (thisui->adressLine->text().isEmpty() or thisui->usernameLine->text().isEmpty() or thisui->roomLine->text().isEmpty() or thisui->passwordLine->text().isEmpty()) {
return;
}
// Save basic server credentials
settings->setValue("server_save", thisui->saveBox->checkState() == Qt::Checked);
settings->setValue("server_url", thisui->adressLine->text());
settings->setValue("server_room", thisui->roomLine->text());
// Connect
QNetworkReply *netReply = auther->connect(thisui->adressLine->text());
w->connect(netReply, &QNetworkReply::readyRead, [this, thisui, w, &netReply] () { // SIGSEGV
// Get SID
netReply = auther->sendAuth(auther->getAuthUrl(netReply), thisui->usernameLine->text(), thisui->passwordLine->text());
w->connect(netReply, &QNetworkReply::readyRead, [this, thisui, w, netReply] () {
// Login
try {
settings->setValue("server_sid", auther->getSID(netReply));
cacheInvalidate = true;
overviewWindow(w, false);
} catch (authFailureError&) {
thisui->failureText->setText("Die eingegebenen Anmeldedaten sind ungültig");
} catch (invalidRoomError&) {
thisui->failureText->setText("Der angegebene Raum existiert nicht");
}
});
});
});
}
void _UILoader::overviewWindow(QMainWindow *w, bool catchInvalidRoomError) {
Ui::overviewWindow *thisui = new Ui::overviewWindow;
// Connect if required
try {
reconnect();
} catch (invalidSIDError&) {
logout();
return loginWindow(w, "Die Sitzung ist abgelaufen - bitte erneut anmelden");
} catch (invalidRoomError& exception) {
if (catchInvalidRoomError) {
logout();
return loginWindow(w, "Der Raum existiert nicht mehr");
} else {
throw exception;
}
} catch (connectionFailError&) {
return offlineWindow(w);
} catch (std::exception&) {
return failureWindow(w);
}
// Show initial UI
thisui->setupUi(w);
w->show();
// Refresh view
thisui->postCounter->setText(QString::number(connector->numposts) + " Einträge");
thisui->postList->clear();
for (const auto& thispost : connector->posts) {
thisui->postList->addItem(QString::fromStdString(thispost.name));
}
// Add button handlers
w->connect(thisui->postList, &QListWidget::activated, [this, thisui] (QModelIndex index) {
QDesktopServices::openUrl(QUrl(settings->value("server_url").toString() + QString::fromStdString(connector->getPost(index.row())->url)));
});
w->connect(thisui->logoutButton, &QPushButton::pressed, [this, w] () {
logout();
loginWindow(w);
});
w->connect(thisui->infoButton, &QPushButton::pressed, [this, w] () {
infoWindow(w);
});
}
int main(int argc, char *argv[]) {
// Initalise settings storage
QCoreApplication::setOrganizationName("QCommsy");
QCoreApplication::setOrganizationDomain("qcommsy.github.io");
QCoreApplication::setApplicationName("QCommsy");
settings = new QSettings();
//Initialise variables
QApplication a(argc, argv);
QMainWindow w;
_UILoader UILoader;
// Set theme
settings->clear();
a.setStyle(QStyleFactory::create("windows"));
// Load initial window
UILoader.loginWindow(&w);
// Run forever
return a.exec();
}

84
offline.ui Normal file
View file

@ -0,0 +1,84 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>offlineWindow</class>
<widget class="QMainWindow" name="offlineWindow">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>323</width>
<height>489</height>
</rect>
</property>
<property name="windowTitle">
<string>QCommsy</string>
</property>
<widget class="QWidget" name="centralwidget">
<layout class="QVBoxLayout" name="verticalLayout">
<item>
<widget class="QLabel" name="label">
<property name="font">
<font>
<pointsize>72</pointsize>
</font>
</property>
<property name="text">
<string>📴</string>
</property>
<property name="alignment">
<set>Qt::AlignCenter</set>
</property>
</widget>
</item>
<item>
<spacer name="verticalSpacer">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>20</width>
<height>40</height>
</size>
</property>
</spacer>
</item>
<item>
<widget class="QLabel" name="descriptionText">
<property name="text">
<string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;&lt;span style=&quot; font-weight:600;&quot;&gt;Entweder dein Handy oder der Server ist offline&lt;br/&gt;&lt;/span&gt;Wenn du dir ganz sicher bist, dass du eine funktionierende Internet Verbindung hast, ist sehr wahrscheinlich der Server wegen Wartungsarbeiten zurzeit offline.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
</property>
<property name="alignment">
<set>Qt::AlignCenter</set>
</property>
<property name="wordWrap">
<bool>true</bool>
</property>
</widget>
</item>
<item>
<spacer name="verticalSpacer_2">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>20</width>
<height>40</height>
</size>
</property>
</spacer>
</item>
<item>
<widget class="QPushButton" name="logoutButton">
<property name="text">
<string>Abmelden</string>
</property>
</widget>
</item>
</layout>
</widget>
</widget>
<resources/>
<connections/>
</ui>

71
overview.ui Normal file
View file

@ -0,0 +1,71 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>overviewWindow</class>
<widget class="QMainWindow" name="overviewWindow">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>323</width>
<height>489</height>
</rect>
</property>
<property name="windowTitle">
<string>QCommsy</string>
</property>
<widget class="QWidget" name="centralwidget">
<layout class="QVBoxLayout" name="verticalLayout">
<item>
<widget class="QListWidget" name="postList">
<property name="editTriggers">
<set>QAbstractItemView::NoEditTriggers</set>
</property>
</widget>
</item>
<item>
<layout class="QHBoxLayout" name="horizontalLayout_2">
<item>
<widget class="QLabel" name="postCounter">
<property name="text">
<string>QCommsy</string>
</property>
</widget>
</item>
<item>
<spacer name="horizontalSpacer">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>40</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
<item>
<widget class="QPushButton" name="logoutButton">
<property name="text">
<string>Abmelden</string>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="infoButton">
<property name="text">
<string>Info</string>
</property>
<property name="checkable">
<bool>true</bool>
</property>
</widget>
</item>
</layout>
</item>
</layout>
</widget>
</widget>
<resources/>
<connections/>
</ui>