/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * Copyright (C) 2015 Simon Stürz * * Copyright (C) 2014 Michael Zanetti * * * * This file is part of guh. * * * * This library is free software; you can redistribute it and/or * * modify it under the terms of the GNU Lesser General Public * * License as published by the Free Software Foundation; either * * version 2.1 of the License, or (at your option) any later version. * * * * This library 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 * * Lesser General Public License for more details. * * * * You should have received a copy of the GNU Lesser General Public * * License along with this library; If not, see * * . * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ #include "httpdaemon.h" #include "plugin/device.h" #include "plugin/deviceclass.h" #include "plugin/deviceplugin.h" #include "types/statetype.h" #include "extern-plugininfo.h" #include #include #include #include #include #include HttpDaemon::HttpDaemon(Device *device, DevicePlugin *parent): QTcpServer(parent), disabled(false), m_plugin(parent), m_device(device) { listen(QHostAddress::Any, device->paramValue(httpportParamTypeId).toInt()); } HttpDaemon::~HttpDaemon() { close(); } void HttpDaemon::incomingConnection(qintptr socket) { if (disabled) return; // When a new client connects, the server constructs a QTcpSocket and all // communication with the client is done over this QTcpSocket. QTcpSocket // works asynchronously, this means that all the communication is done // in the two slots readClient() and discardClient(). QTcpSocket* s = new QTcpSocket(this); connect(s, SIGNAL(readyRead()), this, SLOT(readClient())); connect(s, SIGNAL(disconnected()), this, SLOT(discardClient())); s->setSocketDescriptor(socket); } void HttpDaemon::actionExecuted(const ActionTypeId &actionTypeId) { m_actionList.append(qMakePair(actionTypeId, QDateTime::currentDateTime())); } void HttpDaemon::readClient() { if (disabled) return; // This slot is called when the client sent data to the server. The // server looks if it was a get request and sends a very simple HTML // document back. QTcpSocket* socket = (QTcpSocket*)sender(); if (socket->canReadLine()) { QByteArray data = socket->readLine(); QStringList tokens = QString(data).split(QRegExp("[ \r\n][ \r\n]*")); QUrl url("http://foo.bar" + tokens[1]); QUrlQuery query(url); if (url.path() == "/setstate") { qCDebug(dcMockDevice) << "Set state value" << query.queryItems().first().second; emit setState(StateTypeId(query.queryItems().first().first), QVariant(query.queryItems().first().second)); } else if (url.path() == "/generateevent") { emit triggerEvent(EventTypeId(query.queryItemValue("eventtypeid"))); } else if (url.path() == "/actionhistory") { qCDebug(dcMockDevice) << "Get action history called"; QTextStream os(socket); os.setAutoDetectUnicode(true); os << generateHeader(); for (int i = 0; i < m_actionList.count(); ++i) { os << m_actionList.at(i).first.toString() << '\n'; qCDebug(dcMockDevice) << " " << m_actionList.at(i).first.toString(); } socket->close(); return; } else if (url.path() == "/clearactionhistory") { qCDebug(dcMockDevice) << "Clear action history"; m_actionList.clear(); } else if (url.path() == "/disappear") { qCDebug(dcMockDevice) << "Should disappear"; emit disappear(); } if (tokens[0] == "GET") { QTextStream os(socket); os.setAutoDetectUnicode(true); os << generateWebPage(); socket->close(); if (socket->state() == QTcpSocket::UnconnectedState) delete socket; } } } void HttpDaemon::discardClient() { QTcpSocket* socket = (QTcpSocket*)sender(); socket->deleteLater(); } QString HttpDaemon::generateHeader() { QString contentHeader( "HTTP/1.0 200 Ok\r\n" "Content-Type: text/html; charset=\"utf-8\"\r\n" "\r\n" ); return contentHeader; } QString HttpDaemon::generateWebPage() { DeviceClass deviceClass = m_plugin->supportedDevices().first(); QString body = QString( "" "" "

Mock device Controller

\n" "
" "

Device Information

" "Name: %1
" "ID: %2
" "DeviceClass ID: %3
").arg(m_device->name()).arg(m_device->id().toString()).arg(deviceClass.id().toString()); body.append("
"); body.append("

States

"); body.append(""); for (int i = 0; i < deviceClass.stateTypes().count(); ++i) { body.append(""); body.append(""); const StateType &stateType = deviceClass.stateTypes().at(i); body.append(""); body.append(QString("").arg(stateType.id().toString()).arg(m_device->states().at(i).value().toString())); body.append(""); body.append(""); body.append(""); } body.append("
" + stateType.name() + "
"); body.append("
"); body.append("

Events

"); body.append(""); for (int i = 0; i < deviceClass.eventTypes().count(); ++i) { const EventType &eventType = deviceClass.eventTypes().at(i); body.append(QString( "" "" "" "" "" "" ); } body.append("
%1").arg(eventType.name()).arg(eventType.id().toString())); if (!eventType.name().endsWith(" changed")) { body.append(""); } body.append("
"); body.append("
"); body.append("

Actions

"); body.append(""); body.append(""); for (int i = 0; i < m_actionList.count(); ++i) { ActionTypeId actionTypeId = ActionTypeId(m_actionList.at(i).first); QDateTime timestamp = m_actionList.at(i).second; QString actionName; foreach (const ActionType &at, deviceClass.actionTypes()) { if (at.id() == actionTypeId) { actionName = at.name(); break; } } body.append(QString( "" "" "" "" "" ).arg(actionName).arg(actionTypeId.toString()).arg(timestamp.toString())); } body.append("
NameType IDTimestamp
%1%2%3
"); body.append("\n"); return generateHeader() + body; }