mirror of
https://github.com/YACReader/yacreader
synced 2025-07-14 02:54:46 -04:00
fixed compilation for Qt5.5 in OSX
This commit is contained in:
122
YACReaderLibrary/server/controllers/comiccontroller.cpp
Normal file
122
YACReaderLibrary/server/controllers/comiccontroller.cpp
Normal file
@ -0,0 +1,122 @@
|
||||
#include "comiccontroller.h"
|
||||
|
||||
#include "db_helper.h"
|
||||
#include "yacreader_libraries.h"
|
||||
|
||||
#include "template.h"
|
||||
#include "../static.h"
|
||||
|
||||
#include "comic_db.h"
|
||||
#include "comic.h"
|
||||
|
||||
#include "QsLog.h"
|
||||
|
||||
#include <typeinfo>
|
||||
|
||||
ComicController::ComicController() {}
|
||||
|
||||
void ComicController::service(HttpRequest& request, HttpResponse& response)
|
||||
{
|
||||
HttpSession session=Static::sessionStore->getSession(request,response,false);
|
||||
|
||||
QString path = QUrl::fromPercentEncoding(request.getPath()).toUtf8();
|
||||
QStringList pathElements = path.split('/');
|
||||
qulonglong libraryId = pathElements.at(2).toLongLong();
|
||||
QString libraryName = DBHelper::getLibraryName(libraryId);
|
||||
qulonglong comicId = pathElements.at(4).toULongLong();
|
||||
|
||||
bool remoteComic = path.endsWith("remote");
|
||||
|
||||
//TODO
|
||||
//if(pathElements.size() == 6)
|
||||
//{
|
||||
// QString action = pathElements.at(5);
|
||||
// if(!action.isEmpty() && (action == "close"))
|
||||
// {
|
||||
// session.dismissCurrentComic();
|
||||
// response.write("",true);
|
||||
// return;
|
||||
// }
|
||||
//}
|
||||
|
||||
YACReaderLibraries libraries = DBHelper::getLibraries();
|
||||
|
||||
ComicDB comic = DBHelper::getComicInfo(libraryId, comicId);
|
||||
|
||||
if(!remoteComic)
|
||||
session.setDownloadedComic(comic.info.hash);
|
||||
|
||||
Comic * comicFile = FactoryComic::newComic(libraries.getPath(libraryId)+comic.path);
|
||||
|
||||
if(comicFile != NULL)
|
||||
{
|
||||
QThread * thread = NULL;
|
||||
|
||||
thread = new QThread();
|
||||
|
||||
comicFile->moveToThread(thread);
|
||||
|
||||
connect(comicFile, SIGNAL(errorOpening()), thread, SLOT(quit()));
|
||||
connect(comicFile, SIGNAL(errorOpening(QString)), thread, SLOT(quit()));
|
||||
connect(comicFile, SIGNAL(imagesLoaded()), thread, SLOT(quit()));
|
||||
connect(thread, SIGNAL(started()), comicFile, SLOT(process()));
|
||||
connect(thread, SIGNAL(finished()), thread, SLOT(deleteLater()));
|
||||
|
||||
comicFile->load(libraries.getPath(libraryId)+comic.path);
|
||||
|
||||
if(thread != NULL)
|
||||
thread->start();
|
||||
|
||||
if(remoteComic)
|
||||
{
|
||||
QLOG_INFO() << "remote comic requested";
|
||||
session.setCurrentRemoteComic(comic.id, comicFile);
|
||||
|
||||
}
|
||||
else
|
||||
{
|
||||
QLOG_INFO() << "comic requested";
|
||||
session.setCurrentComic(comic.id, comicFile);
|
||||
}
|
||||
|
||||
response.setHeader("Content-Type", "plain/text; charset=utf-8");
|
||||
//TODO this field is not used by the client!
|
||||
response.writeText(QString("library:%1\r\n").arg(libraryName));
|
||||
response.writeText(QString("libraryId:%1\r\n").arg(libraryId));
|
||||
if(remoteComic) //send previous and next comics id
|
||||
{
|
||||
QList<LibraryItem *> siblings = DBHelper::getFolderComicsFromLibrary(libraryId, comic.parentId, true);
|
||||
bool found = false;
|
||||
int i;
|
||||
for(i = 0; i < siblings.length(); i++)
|
||||
{
|
||||
if (siblings.at(i)->id == comic.id)
|
||||
{
|
||||
found = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if(found)
|
||||
{
|
||||
if(i>0)
|
||||
response.writeText(QString("previousComic:%1\r\n").arg(siblings.at(i-1)->id));
|
||||
if(i<siblings.length()-1)
|
||||
response.writeText(QString("nextComic:%1\r\n").arg(siblings.at(i+1)->id));
|
||||
}
|
||||
else
|
||||
{
|
||||
//ERROR
|
||||
}
|
||||
qDeleteAll(siblings);
|
||||
}
|
||||
response.writeText(comic.toTXT(),true);
|
||||
}
|
||||
else
|
||||
{
|
||||
//delete comicFile;
|
||||
response.setStatus(404,"not found");
|
||||
response.write("404 not found",true);
|
||||
}
|
||||
//response.write(t.toLatin1(),true);
|
||||
|
||||
}
|
23
YACReaderLibrary/server/controllers/comiccontroller.h
Normal file
23
YACReaderLibrary/server/controllers/comiccontroller.h
Normal file
@ -0,0 +1,23 @@
|
||||
#ifndef COMICCONTROLLER_H
|
||||
#define COMICCONTROLLER_H
|
||||
|
||||
#include "httprequest.h"
|
||||
#include "httpresponse.h"
|
||||
#include "httprequesthandler.h"
|
||||
|
||||
#include <QThread>
|
||||
class Comic;
|
||||
class QString;
|
||||
|
||||
class ComicController : public HttpRequestHandler {
|
||||
Q_OBJECT
|
||||
Q_DISABLE_COPY(ComicController);
|
||||
public:
|
||||
/** Constructor */
|
||||
ComicController();
|
||||
|
||||
/** Generates the response */
|
||||
void service(HttpRequest& request, HttpResponse& response);
|
||||
};
|
||||
|
||||
#endif // COMICCONTROLLER_H
|
@ -0,0 +1,26 @@
|
||||
#include "comicdownloadinfocontroller.h"
|
||||
|
||||
#include "db_helper.h"
|
||||
#include "yacreader_libraries.h"
|
||||
|
||||
#include "comic_db.h"
|
||||
|
||||
ComicDownloadInfoController::ComicDownloadInfoController() {}
|
||||
|
||||
|
||||
void ComicDownloadInfoController::service(HttpRequest& request, HttpResponse& response)
|
||||
{
|
||||
response.setHeader("Content-Type", "plain/text; charset=utf-8");
|
||||
|
||||
QString path = QUrl::fromPercentEncoding(request.getPath()).toUtf8();
|
||||
QStringList pathElements = path.split('/');
|
||||
|
||||
qulonglong libraryId = pathElements.at(2).toLongLong();
|
||||
qulonglong comicId = pathElements.at(4).toULongLong();
|
||||
|
||||
ComicDB comic = DBHelper::getComicInfo(libraryId, comicId);
|
||||
|
||||
//TODO: check if the comic wasn't found;
|
||||
response.writeText(QString("fileName:%1\r\n").arg(comic.getFileName()));
|
||||
response.writeText(QString("fileSize:%1\r\n").arg(comic.getFileSize()),true);
|
||||
}
|
@ -0,0 +1,19 @@
|
||||
#ifndef COMICDOWNLOADINFOCONTROLLER_H
|
||||
#define COMICDOWNLOADINFOCONTROLLER_H
|
||||
|
||||
#include "httprequest.h"
|
||||
#include "httpresponse.h"
|
||||
#include "httprequesthandler.h"
|
||||
|
||||
class ComicDownloadInfoController : public HttpRequestHandler {
|
||||
Q_OBJECT
|
||||
Q_DISABLE_COPY(ComicDownloadInfoController);
|
||||
public:
|
||||
/** Constructor **/
|
||||
ComicDownloadInfoController();
|
||||
|
||||
/** Generates the response */
|
||||
void service(HttpRequest& request, HttpResponse& response);
|
||||
};
|
||||
|
||||
#endif // COMICDOWNLOADINFOCONTROLLER_H
|
88
YACReaderLibrary/server/controllers/covercontroller.cpp
Normal file
88
YACReaderLibrary/server/controllers/covercontroller.cpp
Normal file
@ -0,0 +1,88 @@
|
||||
#include "covercontroller.h"
|
||||
#include "db_helper.h" //get libraries
|
||||
#include "yacreader_libraries.h"
|
||||
|
||||
#include "template.h"
|
||||
#include "../static.h"
|
||||
|
||||
CoverController::CoverController() {}
|
||||
|
||||
void CoverController::service(HttpRequest& request, HttpResponse& response)
|
||||
{
|
||||
|
||||
HttpSession session=Static::sessionStore->getSession(request,response,false);
|
||||
|
||||
response.setHeader("Content-Type", "image/jpeg");
|
||||
response.setHeader("Connection","close");
|
||||
//response.setHeader("Content-Type", "plain/text; charset=ISO-8859-1");
|
||||
|
||||
YACReaderLibraries libraries = DBHelper::getLibraries();
|
||||
|
||||
QString path = QUrl::fromPercentEncoding(request.getPath()).toUtf8();
|
||||
QStringList pathElements = path.split('/');
|
||||
QString libraryName = DBHelper::getLibraryName(pathElements.at(2).toInt());
|
||||
QString fileName = pathElements.at(4);
|
||||
|
||||
bool folderCover = request.getParameter("folderCover").length()>0;
|
||||
|
||||
//response.writeText(path+"<br/>");
|
||||
//response.writeText(libraryName+"<br/>");
|
||||
//response.writeText(libraries.value(libraryName)+"/.yacreaderlibrary/covers/"+fileName+"<br/>");
|
||||
|
||||
//QFile file(libraries.value(libraryName)+"/.yacreaderlibrary/covers/"+fileName);
|
||||
//if (file.exists()) {
|
||||
// if (file.open(QIODevice::ReadOnly))
|
||||
// {
|
||||
// qDebug("StaticFileController: Open file %s",qPrintable(file.fileName()));
|
||||
// // Return the file content, do not store in cache
|
||||
// while (!file.atEnd() && !file.error()) {
|
||||
// response.write(file.read(131072));
|
||||
// }
|
||||
// }
|
||||
|
||||
// file.close();
|
||||
//}
|
||||
|
||||
QImage img(libraries.getPath(libraryName)+"/.yacreaderlibrary/covers/"+fileName);
|
||||
if (!img.isNull()) {
|
||||
|
||||
int width = 80, height = 120;
|
||||
if(session.getDisplayType()=="@2x")
|
||||
{
|
||||
width = 160;
|
||||
height = 240;
|
||||
}
|
||||
|
||||
if(float(img.width())/img.height() < 0.66666)
|
||||
img = img.scaledToWidth(width,Qt::SmoothTransformation);
|
||||
else
|
||||
img = img.scaledToHeight(height,Qt::SmoothTransformation);
|
||||
|
||||
QImage destImg(width,height,QImage::Format_RGB32);
|
||||
destImg.fill(Qt::black);
|
||||
QPainter p(&destImg);
|
||||
|
||||
p.drawImage((width-img.width())/2,(height-img.height())/2,img);
|
||||
|
||||
if(folderCover)
|
||||
{
|
||||
if(session.getDisplayType()=="@2x")
|
||||
p.drawImage(0,0,QImage(":/images/f_overlayed_retina.png"));
|
||||
else
|
||||
p.drawImage(0,0,QImage(":/images/f_overlayed.png"));
|
||||
}
|
||||
|
||||
QByteArray ba;
|
||||
QBuffer buffer(&ba);
|
||||
buffer.open(QIODevice::WriteOnly);
|
||||
destImg.save(&buffer, "JPG");
|
||||
response.write(ba,true);
|
||||
}
|
||||
//DONE else, hay que devolver un 404
|
||||
else
|
||||
{
|
||||
response.setStatus(404,"not found");
|
||||
response.write("404 not found",true);
|
||||
}
|
||||
}
|
||||
|
20
YACReaderLibrary/server/controllers/covercontroller.h
Normal file
20
YACReaderLibrary/server/controllers/covercontroller.h
Normal file
@ -0,0 +1,20 @@
|
||||
#ifndef COVERCONTROLLER_H
|
||||
#define COVERCONTROLLER_H
|
||||
|
||||
#include "httprequest.h"
|
||||
#include "httpresponse.h"
|
||||
#include "httprequesthandler.h"
|
||||
|
||||
class CoverController : public HttpRequestHandler {
|
||||
Q_OBJECT
|
||||
Q_DISABLE_COPY(CoverController);
|
||||
public:
|
||||
|
||||
/** Constructor */
|
||||
CoverController();
|
||||
|
||||
/** Generates the response */
|
||||
void service(HttpRequest& request, HttpResponse& response);
|
||||
};
|
||||
|
||||
#endif // COVERCONTROLLER_H
|
62
YACReaderLibrary/server/controllers/dumpcontroller.cpp
Normal file
62
YACReaderLibrary/server/controllers/dumpcontroller.cpp
Normal file
@ -0,0 +1,62 @@
|
||||
/**
|
||||
@file
|
||||
@author Stefan Frings
|
||||
*/
|
||||
|
||||
#include "dumpcontroller.h"
|
||||
#include <QVariant>
|
||||
#include <QDateTime>
|
||||
|
||||
DumpController::DumpController(){}
|
||||
|
||||
void DumpController::service(HttpRequest& request, HttpResponse& response) {
|
||||
|
||||
response.setHeader("Content-Type", "text/html; charset=ISO-8859-1");
|
||||
response.setCookie(HttpCookie("firstCookie","hello",600));
|
||||
response.setCookie(HttpCookie("secondCookie","world",600));
|
||||
|
||||
QByteArray body("<html><body>");
|
||||
body.append("<b>Request:</b>");
|
||||
body.append("<br>Method: ");
|
||||
body.append(request.getMethod());
|
||||
body.append("<br>Path: ");
|
||||
body.append(request.getPath());
|
||||
body.append("<br>Version: ");
|
||||
body.append(request.getVersion());
|
||||
|
||||
body.append("<p><b>Headers:</b>");
|
||||
QMapIterator<QByteArray,QByteArray> i(request.getHeaderMap());
|
||||
while (i.hasNext()) {
|
||||
i.next();
|
||||
body.append("<br>");
|
||||
body.append(i.key());
|
||||
body.append("=");
|
||||
body.append(i.value());
|
||||
}
|
||||
|
||||
body.append("<p><b>Parameters:</b>");
|
||||
i=QMapIterator<QByteArray,QByteArray>(request.getParameterMap());
|
||||
while (i.hasNext()) {
|
||||
i.next();
|
||||
body.append("<br>");
|
||||
body.append(i.key());
|
||||
body.append("=");
|
||||
body.append(i.value());
|
||||
}
|
||||
|
||||
body.append("<p><b>Cookies:</b>");
|
||||
i=QMapIterator<QByteArray,QByteArray>(request.getCookieMap());
|
||||
while (i.hasNext()) {
|
||||
i.next();
|
||||
body.append("<br>");
|
||||
body.append(i.key());
|
||||
body.append("=");
|
||||
body.append(i.value());
|
||||
}
|
||||
|
||||
body.append("<p><b>Body:</b><br>");
|
||||
body.append(request.getBody());
|
||||
|
||||
body.append("</body></html>");
|
||||
response.write(body,true);
|
||||
}
|
29
YACReaderLibrary/server/controllers/dumpcontroller.h
Normal file
29
YACReaderLibrary/server/controllers/dumpcontroller.h
Normal file
@ -0,0 +1,29 @@
|
||||
/**
|
||||
@file
|
||||
@author Stefan Frings
|
||||
*/
|
||||
|
||||
#ifndef DUMPCONTROLLER_H
|
||||
#define DUMPCONTROLLER_H
|
||||
|
||||
#include "httprequest.h"
|
||||
#include "httpresponse.h"
|
||||
#include "httprequesthandler.h"
|
||||
|
||||
/**
|
||||
This controller dumps the received HTTP request in the response.
|
||||
*/
|
||||
|
||||
class DumpController : public HttpRequestHandler {
|
||||
Q_OBJECT
|
||||
Q_DISABLE_COPY(DumpController);
|
||||
public:
|
||||
|
||||
/** Constructor */
|
||||
DumpController();
|
||||
|
||||
/** Generates the response */
|
||||
void service(HttpRequest& request, HttpResponse& response);
|
||||
};
|
||||
|
||||
#endif // DUMPCONTROLLER_H
|
26
YACReaderLibrary/server/controllers/errorcontroller.cpp
Normal file
26
YACReaderLibrary/server/controllers/errorcontroller.cpp
Normal file
@ -0,0 +1,26 @@
|
||||
#include "errorcontroller.h"
|
||||
|
||||
#include "template.h"
|
||||
#include "../static.h"
|
||||
|
||||
|
||||
ErrorController::ErrorController(int errorCode)
|
||||
:error(errorCode)
|
||||
{}
|
||||
|
||||
void ErrorController::service(HttpRequest& request, HttpResponse& response)
|
||||
{
|
||||
Q_UNUSED(request)
|
||||
switch(error)
|
||||
{
|
||||
case 300:
|
||||
response.setStatus(300,"redirect");
|
||||
response.write("<html> <head> <meta http-equiv=\"refresh\" content=\"0; URL=/\"> </head> <body> </body> </html>", true);
|
||||
break;
|
||||
case 404:
|
||||
response.setStatus(404,"not found");
|
||||
response.write("404 not found",true);
|
||||
break;
|
||||
}
|
||||
|
||||
}
|
22
YACReaderLibrary/server/controllers/errorcontroller.h
Normal file
22
YACReaderLibrary/server/controllers/errorcontroller.h
Normal file
@ -0,0 +1,22 @@
|
||||
#ifndef ERRORCONTROLLER_H
|
||||
#define ERRORCONTROLLER_H
|
||||
|
||||
#include "httprequest.h"
|
||||
#include "httpresponse.h"
|
||||
#include "httprequesthandler.h"
|
||||
|
||||
class ErrorController : public HttpRequestHandler {
|
||||
Q_OBJECT
|
||||
Q_DISABLE_COPY(ErrorController);
|
||||
public:
|
||||
|
||||
/** Constructor */
|
||||
ErrorController(int errorCode);
|
||||
|
||||
/** Generates the response */
|
||||
void service(HttpRequest& request, HttpResponse& response);
|
||||
private:
|
||||
int error;
|
||||
};
|
||||
|
||||
#endif // ERRORCONTROLLER_H
|
38
YACReaderLibrary/server/controllers/fileuploadcontroller.cpp
Normal file
38
YACReaderLibrary/server/controllers/fileuploadcontroller.cpp
Normal file
@ -0,0 +1,38 @@
|
||||
/**
|
||||
@file
|
||||
@author Stefan Frings
|
||||
*/
|
||||
|
||||
#include "fileuploadcontroller.h"
|
||||
|
||||
FileUploadController::FileUploadController() {}
|
||||
|
||||
void FileUploadController::service(HttpRequest& request, HttpResponse& response) {
|
||||
|
||||
if (request.getParameter("action")=="show") {
|
||||
response.setHeader("Content-Type", "image/jpeg");
|
||||
QTemporaryFile* file=request.getUploadedFile("file1");
|
||||
if (file) {
|
||||
while (!file->atEnd() && !file->error()) {
|
||||
QByteArray buffer=file->read(65536);
|
||||
response.write(buffer);
|
||||
}
|
||||
}
|
||||
else {
|
||||
response.write("upload failed");
|
||||
}
|
||||
}
|
||||
|
||||
else {
|
||||
response.setHeader("Content-Type", "text/html; charset=ISO-8859-1");
|
||||
response.write("<html><body>");
|
||||
response.write("Upload a JPEG image file<p>");
|
||||
response.write("<form method=\"post\" enctype=\"multipart/form-data\">");
|
||||
response.write(" <input type=\"hidden\" name=\"action\" value=\"show\">");
|
||||
response.write(" File: <input type=\"file\" name=\"file1\"><br>");
|
||||
response.write(" <input type=\"submit\">");
|
||||
response.write("</form>");
|
||||
response.write("</body></html>",true);
|
||||
}
|
||||
}
|
||||
|
30
YACReaderLibrary/server/controllers/fileuploadcontroller.h
Normal file
30
YACReaderLibrary/server/controllers/fileuploadcontroller.h
Normal file
@ -0,0 +1,30 @@
|
||||
/**
|
||||
@file
|
||||
@author Stefan Frings
|
||||
*/
|
||||
|
||||
#ifndef FILEUPLOADCONTROLLER_H
|
||||
#define FILEUPLOADCONTROLLER_H
|
||||
|
||||
#include "httprequest.h"
|
||||
#include "httpresponse.h"
|
||||
#include "httprequesthandler.h"
|
||||
|
||||
/**
|
||||
This controller displays a HTML form for file upload and recieved the file.
|
||||
*/
|
||||
|
||||
|
||||
class FileUploadController : public HttpRequestHandler {
|
||||
Q_OBJECT
|
||||
Q_DISABLE_COPY(FileUploadController);
|
||||
public:
|
||||
|
||||
/** Constructor */
|
||||
FileUploadController();
|
||||
|
||||
/** Generates the response */
|
||||
void service(HttpRequest& request, HttpResponse& response);
|
||||
};
|
||||
|
||||
#endif // FILEUPLOADCONTROLLER_H
|
321
YACReaderLibrary/server/controllers/foldercontroller.cpp
Normal file
321
YACReaderLibrary/server/controllers/foldercontroller.cpp
Normal file
@ -0,0 +1,321 @@
|
||||
#include "foldercontroller.h"
|
||||
#include "controllers/errorcontroller.h"
|
||||
|
||||
#include "db_helper.h" //get libraries
|
||||
#include "comic_db.h"
|
||||
|
||||
#include "folder.h"
|
||||
|
||||
#include "template.h"
|
||||
#include "../static.h"
|
||||
|
||||
#include "qnaturalsorting.h"
|
||||
|
||||
#include "QsLog.h"
|
||||
|
||||
struct LibraryItemSorter
|
||||
{
|
||||
bool operator()(const LibraryItem * a,const LibraryItem * b) const
|
||||
{
|
||||
return naturalSortLessThanCI(a->name,b->name);
|
||||
}
|
||||
};
|
||||
|
||||
FolderController::FolderController() {}
|
||||
|
||||
void FolderController::service(HttpRequest& request, HttpResponse& response)
|
||||
{
|
||||
HttpSession session=Static::sessionStore->getSession(request,response,false);
|
||||
|
||||
response.setHeader("Content-Type", "text/html; charset=utf-8");
|
||||
response.setHeader("Connection","close");
|
||||
|
||||
//QString y = session.get("xxx").toString();
|
||||
//response.writeText(QString("session xxx : %1 <br/>").arg(y));
|
||||
|
||||
Template t=Static::templateLoader->getTemplate("folder_"+session.getDeviceType(),request.getHeader("Accept-Language"));
|
||||
t.enableWarnings();
|
||||
QString path = QUrl::fromPercentEncoding(request.getPath()).toUtf8();
|
||||
QStringList pathElements = path.split('/');
|
||||
int libraryId = pathElements.at(2).toInt();
|
||||
QString libraryName = DBHelper::getLibraryName(libraryId);
|
||||
qulonglong folderId = pathElements.at(4).toULongLong();
|
||||
|
||||
folderId = qMax<qulonglong>(1,folderId);
|
||||
|
||||
QString folderName = DBHelper::getFolderName(libraryId,folderId);
|
||||
if(folderName.isEmpty())
|
||||
{
|
||||
ErrorController(300).service(request,response);
|
||||
return;
|
||||
}
|
||||
|
||||
if(folderId!=1)
|
||||
t.setVariable("folder.name",folderName);
|
||||
else
|
||||
t.setVariable("folder.name",libraryName);
|
||||
QList<LibraryItem *> folderContent = DBHelper::getFolderSubfoldersFromLibrary(libraryId,folderId);
|
||||
QList<LibraryItem *> folderComics = DBHelper::getFolderComicsFromLibrary(libraryId,folderId);
|
||||
|
||||
//response.writeText(libraryName);
|
||||
|
||||
folderContent.append(folderComics);
|
||||
|
||||
qSort(folderContent.begin(),folderContent.end(),LibraryItemSorter());
|
||||
folderComics.clear();
|
||||
|
||||
//qulonglong backId = DBHelper::getParentFromComicFolderId(libraryName,folderId);
|
||||
|
||||
int page = 0;
|
||||
QByteArray p = request.getParameter("page");
|
||||
if(p.length() != 0)
|
||||
page = p.toInt();
|
||||
|
||||
// /comicIdi/pagei/comicIdj/pagej/....../comicIdn/pagen
|
||||
//QString currentPath = session.get("currentPath").toString();
|
||||
//QStringList pathSize = currentPath.split("/").last().toInt;
|
||||
|
||||
bool fromUp = false;
|
||||
|
||||
QMultiMap<QByteArray,QByteArray> map = request.getParameterMap();
|
||||
if(map.contains("up"))
|
||||
fromUp = true;
|
||||
|
||||
//int upPage = 0;
|
||||
|
||||
if(folderId == 1)
|
||||
{
|
||||
session.clearNavigationPath();
|
||||
session.pushNavigationItem(QPair<qulonglong,quint32>(folderId,page));
|
||||
t.setVariable(QString("upurl"),"/");
|
||||
}
|
||||
else
|
||||
{
|
||||
if(fromUp)
|
||||
session.popNavigationItem();
|
||||
else //drill down or direct access
|
||||
{
|
||||
QStack<QPair<qulonglong, quint32> > path = session.getNavigationPath();
|
||||
bool found=false;
|
||||
for(QStack<QPair<qulonglong, quint32> >::const_iterator itr = path.begin(); itr!=path.end(); itr++)
|
||||
if(itr->first == folderId)
|
||||
{
|
||||
found = true;
|
||||
break;
|
||||
}
|
||||
|
||||
if(found)
|
||||
{
|
||||
while(session.topNavigationItem().first != folderId)
|
||||
session.popNavigationItem();
|
||||
|
||||
session.updateTopItem(QPair<qulonglong,quint32>(folderId,page));
|
||||
}
|
||||
else
|
||||
session.pushNavigationItem(QPair<qulonglong,quint32>(folderId,page));
|
||||
}
|
||||
|
||||
QStack<QPair<qulonglong, quint32> > path = session.getNavigationPath();
|
||||
if(path.count()>1)
|
||||
{
|
||||
QPair<qulonglong, quint32> parentItem = path.at(path.count()-2);
|
||||
qulonglong upParent = parentItem.first;
|
||||
quint32 upPage = parentItem.second;
|
||||
t.setVariable(QString("upurl"),"/library/" + QString::number(libraryId) + "/folder/" +QString("%1?page=%2&up=true").arg(upParent).arg(upPage));
|
||||
} else
|
||||
t.setVariable(QString("upurl"),"/");
|
||||
}
|
||||
|
||||
int elementsPerPage = 24;
|
||||
|
||||
int numFolders = folderContent.length();
|
||||
//int numComics = folderComics.length();
|
||||
int totalLength = folderContent.length() + folderComics.length();
|
||||
|
||||
// int numFolderPages = numFolders / elementsPerPage + ((numFolders%elementsPerPage)>0?1:0);
|
||||
int numPages = totalLength / elementsPerPage + ((totalLength%elementsPerPage)>0?1:0);
|
||||
|
||||
//response.writeText(QString("Number of pages : %1 <br/>").arg(numPages));
|
||||
|
||||
if(page < 0)
|
||||
page = 0;
|
||||
else if(page >= numPages)
|
||||
page = numPages-1;
|
||||
|
||||
int indexCurrentPage = page*elementsPerPage;
|
||||
int numFoldersAtCurrentPage = qMax(0,qMin(numFolders - indexCurrentPage, elementsPerPage));
|
||||
|
||||
//PATH
|
||||
QStack<QPair<qulonglong,quint32> > foldersPath = session.getNavigationPath();
|
||||
t.setVariable(QString("library.name"),libraryName);
|
||||
t.setVariable(QString("library.url"),QString("/library/%1/folder/1").arg(libraryId));
|
||||
t.loop("path",foldersPath.count()-1);
|
||||
for(int i = 1; i < foldersPath.count(); i++){
|
||||
t.setVariable(QString("path%1.url").arg(i-1),QString("/library/%1/folder/%2").arg(libraryId).arg(foldersPath[i].first));
|
||||
t.setVariable(QString("path%1.name").arg(i-1),DBHelper::getFolderName(libraryId,foldersPath[i].first));
|
||||
}
|
||||
|
||||
if(folderContent.length() > 0)
|
||||
{
|
||||
t.loop("element",numFoldersAtCurrentPage);
|
||||
int i = 0;
|
||||
while(i<numFoldersAtCurrentPage)
|
||||
{
|
||||
LibraryItem * item = folderContent.at(i + (page*elementsPerPage));
|
||||
t.setVariable(QString("element%1.name").arg(i),folderContent.at(i + (page*elementsPerPage))->name);
|
||||
if(item->isDir())
|
||||
{
|
||||
t.setVariable(QString("element%1.class").arg(i),"folder");
|
||||
|
||||
QList<LibraryItem *> children = DBHelper::getFolderComicsFromLibrary(libraryId, item->id);
|
||||
if(children.length()>0)
|
||||
{
|
||||
const ComicDB * comic = static_cast<ComicDB*>(children.at(0));
|
||||
t.setVariable(QString("element%1.image.url").arg(i),QString("/library/%1/cover/%2.jpg?folderCover=true").arg(libraryId).arg(comic->info.hash));
|
||||
}
|
||||
else
|
||||
t.setVariable(QString("element%1.image.url").arg(i),"/images/f.png");
|
||||
|
||||
t.setVariable(QString("element%1.browse").arg(i),QString("<a class =\"browseButton\" href=\"%1\">BROWSE</a>").arg(QString("/library/%1/folder/%2").arg(libraryId).arg(item->id)));
|
||||
t.setVariable(QString("element%1.cover.browse").arg(i),QString("<a href=\"%1\">").arg(QString("/library/%1/folder/%2").arg(libraryId).arg(item->id)));
|
||||
t.setVariable(QString("element%1.cover.browse.end").arg(i),"</a>");
|
||||
//t.setVariable(QString("element%1.url").arg(i),"/library/"+libraryName+"/folder/"+QString("%1").arg(folderContent.at(i + (page*10))->id));
|
||||
//t.setVariable(QString("element%1.downloadurl").arg(i),"/library/"+libraryName+"/folder/"+QString("%1/info").arg(folderContent.at(i + (page*elementsPerPage))->id));
|
||||
|
||||
t.setVariable(QString("element%1.download").arg(i),QString("<a onclick=\"this.innerHTML='IMPORTING';this.className='importedButton';\" class =\"importButton\" href=\"%1\">IMPORT</a>").arg("/library/"+QString::number(libraryId)+"/folder/"+QString("%1/info").arg(folderContent.at(i + (page*elementsPerPage))->id)));
|
||||
t.setVariable(QString("element%1.read").arg(i),"");
|
||||
|
||||
t.setVariable(QString("element%1.size").arg(i),"");
|
||||
t.setVariable(QString("element%1.pages").arg(i),"");
|
||||
t.setVariable(QString("element%1.status").arg(i),"");
|
||||
}
|
||||
else
|
||||
{
|
||||
t.setVariable(QString("element%1.class").arg(i),"cover");
|
||||
const ComicDB * comic = (ComicDB *)item;
|
||||
t.setVariable(QString("element%1.browse").arg(i),"");
|
||||
//t.setVariable(QString("element%1.downloadurl").arg(i),"/library/"+libraryName+"/comic/"+QString("%1").arg(comic->id));
|
||||
if(!session.isComicOnDevice(comic->info.hash) && !session.isComicDownloaded(comic->info.hash))
|
||||
t.setVariable(QString("element%1.download").arg(i),QString("<a onclick=\"this.innerHTML='IMPORTING';this.className='importedButton';\" class =\"importButton\" href=\"%1\">IMPORT</a>").arg("/library/"+QString::number(libraryId)+"/comic/"+QString("%1").arg(comic->id)));
|
||||
else if (session.isComicOnDevice(comic->info.hash))
|
||||
t.setVariable(QString("element%1.download").arg(i),QString("<div class=\"importedButton\">IMPORTED</div>"));
|
||||
else
|
||||
t.setVariable(QString("element%1.download").arg(i),QString("<div class=\"importedButton\">IMPORTING</div>"));
|
||||
|
||||
//t.setVariable(QString("element%1.image.url").arg(i),"/images/f.png");
|
||||
|
||||
t.setVariable(QString("element%1.read").arg(i),QString("<a class =\"readButton\" href=\"%1\">READ</a>").arg("/library/"+QString::number(libraryId)+"/comic/"+QString("%1").arg(comic->id)+"/remote"));
|
||||
|
||||
t.setVariable(QString("element%1.image.url").arg(i),QString("/library/%1/cover/%2.jpg").arg(libraryId).arg(comic->info.hash));
|
||||
|
||||
t.setVariable(QString("element%1.size").arg(i),"<span class=\"comicSize\">" + QString::number(comic->info.hash.right(comic->info.hash.length()-40).toInt()/1024.0/1024.0,'f',2)+"Mb</span>");
|
||||
if(comic->info.hasBeenOpened)
|
||||
t.setVariable(QString("element%1.pages").arg(i),QString("<span class=\"numPages\">%1/%2 pages</span>").arg(comic->info.currentPage).arg(comic->info.numPages.toInt()));
|
||||
else
|
||||
t.setVariable(QString("element%1.pages").arg(i),QString("<span class=\"numPages\">%1 pages</span>").arg(comic->info.numPages.toInt()));
|
||||
|
||||
if(comic->info.read)
|
||||
t.setVariable(QString("element%1.status").arg(i), QString("<div class=\"mark\"><img src=\"/images/readMark.png\" style = \"width: 15px\"/> </div>"));
|
||||
else if(comic->info.hasBeenOpened)
|
||||
t.setVariable(QString("element%1.status").arg(i), QString("<div class=\"mark\"><img src=\"/images/readingMark.png\" style = \"width: 15px\"/> </div>"));
|
||||
else
|
||||
t.setVariable(QString("element%1.status").arg(i),"");
|
||||
|
||||
t.setVariable(QString("element%1.cover.browse").arg(i),"");
|
||||
t.setVariable(QString("element%1.cover.browse.end").arg(i),"");
|
||||
}
|
||||
i++;
|
||||
}
|
||||
} else
|
||||
{
|
||||
t.loop("element",0);
|
||||
}
|
||||
|
||||
if(numPages > 1)
|
||||
{
|
||||
t.setCondition("pageIndex",true);
|
||||
|
||||
QMap<QString,int> indexCount;
|
||||
|
||||
QString firstChar;
|
||||
int xyz = 1;
|
||||
for(QList<LibraryItem *>::const_iterator itr=folderContent.constBegin();itr!=folderContent.constEnd();itr++)
|
||||
{
|
||||
firstChar = QString((*itr)->name[0]).toUpper();
|
||||
firstChar = firstChar.normalized(QString::NormalizationForm_D).at(0);//TODO _D or _KD??
|
||||
bool ok;
|
||||
/*int dec = */firstChar.toInt(&ok, 10);
|
||||
if(ok)
|
||||
firstChar = "#";
|
||||
//response.writeText(QString("%1 - %2 <br />").arg((*itr)->name).arg(xyz));
|
||||
if(indexCount.contains(firstChar))
|
||||
indexCount.insert(firstChar, indexCount.value(firstChar)+1);
|
||||
else
|
||||
indexCount.insert(firstChar, 1);
|
||||
|
||||
xyz++;
|
||||
}
|
||||
|
||||
QList<QString> index = indexCount.keys();
|
||||
if(index.length()>1)
|
||||
{
|
||||
t.setCondition("alphaIndex",true);
|
||||
|
||||
qSort(index.begin(),index.end(),naturalSortLessThanCI);
|
||||
t.loop("index",index.length());
|
||||
int i=0;
|
||||
int count=0;
|
||||
int indexPage=0;
|
||||
for(QList<QString>::const_iterator itr=index.constBegin();itr!=index.constEnd();itr++)
|
||||
{
|
||||
//response.writeText(QString("%1 - %2 <br />").arg(*itr).arg(count));
|
||||
t.setVariable(QString("index%1.indexname").arg(i), *itr);
|
||||
t.setVariable(QString("index%1.url").arg(i),QString("/library/%1/folder/%2?page=%3").arg(libraryId).arg(folderId).arg(indexPage));
|
||||
i++;
|
||||
count += indexCount.value(*itr);
|
||||
indexPage = count/elementsPerPage;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
t.loop("index",0);
|
||||
t.setCondition("alphaIndex",false);
|
||||
|
||||
}
|
||||
|
||||
t.loop("page",numPages);
|
||||
int z = 0;
|
||||
while(z < numPages)
|
||||
{
|
||||
|
||||
t.setVariable(QString("page%1.url").arg(z),QString("/library/%1/folder/%2?page=%3").arg(libraryId).arg(folderId).arg(z));
|
||||
t.setVariable(QString("page%1.number").arg(z),QString("%1").arg(z+1));
|
||||
if(page == z)
|
||||
t.setVariable(QString("page%1.current").arg(z),"current");
|
||||
else
|
||||
t.setVariable(QString("page%1.current").arg(z),"");
|
||||
z++;
|
||||
}
|
||||
|
||||
t.setVariable("page.first",QString("/library/%1/folder/%2?page=%3").arg(libraryId).arg(folderId).arg(0));
|
||||
t.setVariable("page.previous",QString("/library/%1/folder/%2?page=%3").arg(libraryId).arg(folderId).arg((page==0)?page:page-1));
|
||||
t.setVariable("page.next",QString("/library/%1/folder/%2?page=%3").arg(libraryId).arg(folderId).arg((page==numPages-1)?page:page+1));
|
||||
t.setVariable("page.last",QString("/library/%1/folder/%2?page=%3").arg(libraryId).arg(folderId).arg(numPages-1));
|
||||
t.setCondition("index", true);
|
||||
}
|
||||
else
|
||||
{
|
||||
|
||||
t.loop("page",0);
|
||||
t.loop("index",0);
|
||||
t.setCondition("index", false);
|
||||
t.setCondition("pageIndex",false);
|
||||
t.setCondition("alphaIndex",false);
|
||||
}
|
||||
|
||||
t.setVariable("page",QString("%1").arg(page+1));
|
||||
t.setVariable("pages",QString("%1").arg(numPages));
|
||||
|
||||
response.writeText(t, true);
|
||||
|
||||
}
|
20
YACReaderLibrary/server/controllers/foldercontroller.h
Normal file
20
YACReaderLibrary/server/controllers/foldercontroller.h
Normal file
@ -0,0 +1,20 @@
|
||||
#ifndef FOLDERCONTROLLER_H
|
||||
#define FOLDERCONTROLLER_H
|
||||
|
||||
#include "httprequest.h"
|
||||
#include "httpresponse.h"
|
||||
#include "httprequesthandler.h"
|
||||
|
||||
class FolderController : public HttpRequestHandler {
|
||||
Q_OBJECT
|
||||
Q_DISABLE_COPY(FolderController);
|
||||
public:
|
||||
|
||||
/** Constructor */
|
||||
FolderController();
|
||||
|
||||
/** Generates the response */
|
||||
void service(HttpRequest& request, HttpResponse& response);
|
||||
};
|
||||
|
||||
#endif // FOLDERCONTROLLER_H
|
48
YACReaderLibrary/server/controllers/folderinfocontroller.cpp
Normal file
48
YACReaderLibrary/server/controllers/folderinfocontroller.cpp
Normal file
@ -0,0 +1,48 @@
|
||||
#include "folderinfocontroller.h"
|
||||
#include "db_helper.h" //get libraries
|
||||
|
||||
#include "folder.h"
|
||||
#include "comic_db.h"
|
||||
|
||||
#include "template.h"
|
||||
#include "../static.h"
|
||||
|
||||
|
||||
FolderInfoController::FolderInfoController() {}
|
||||
|
||||
void FolderInfoController::service(HttpRequest& request, HttpResponse& response)
|
||||
{
|
||||
response.setHeader("Content-Type", "plain/text; charset=utf-8");
|
||||
|
||||
QString path = QUrl::fromPercentEncoding(request.getPath()).toUtf8();
|
||||
QStringList pathElements = path.split('/');
|
||||
int libraryId = pathElements.at(2).toInt();
|
||||
QString libraryName = DBHelper::getLibraryName(libraryId);
|
||||
qulonglong parentId = pathElements.at(4).toULongLong();
|
||||
|
||||
serviceComics(libraryId, parentId, response);
|
||||
|
||||
response.writeText("",true);
|
||||
}
|
||||
|
||||
void FolderInfoController::serviceComics(const int &library, const qulonglong &folderId, HttpResponse &response)
|
||||
{
|
||||
QList<LibraryItem *> folderContent = DBHelper::getFolderSubfoldersFromLibrary(library,folderId);
|
||||
QList<LibraryItem *> folderComics = DBHelper::getFolderComicsFromLibrary(library,folderId);
|
||||
|
||||
ComicDB * currentComic;
|
||||
for(QList<LibraryItem *>::const_iterator itr = folderComics.constBegin();itr!=folderComics.constEnd();itr++)
|
||||
{
|
||||
currentComic = (ComicDB *)(*itr);
|
||||
response.writeText(QString("/library/%1/comic/%2:%3:%4\r\n").arg(library).arg(currentComic->id).arg(currentComic->getFileName()).arg(currentComic->getFileSize()));
|
||||
delete currentComic;
|
||||
}
|
||||
|
||||
Folder * currentFolder;
|
||||
for(QList<LibraryItem *>::const_iterator itr = folderContent.constBegin();itr!=folderContent.constEnd();itr++)
|
||||
{
|
||||
currentFolder = (Folder *)(*itr);
|
||||
serviceComics(library, currentFolder->id, response);
|
||||
delete currentFolder;
|
||||
}
|
||||
}
|
23
YACReaderLibrary/server/controllers/folderinfocontroller.h
Normal file
23
YACReaderLibrary/server/controllers/folderinfocontroller.h
Normal file
@ -0,0 +1,23 @@
|
||||
#ifndef FOLDERINFOCONTROLLER_H
|
||||
#define FOLDERINFOCONTROLLER_H
|
||||
|
||||
#include "httprequest.h"
|
||||
#include "httpresponse.h"
|
||||
#include "httprequesthandler.h"
|
||||
|
||||
class FolderInfoController : public HttpRequestHandler {
|
||||
Q_OBJECT
|
||||
Q_DISABLE_COPY(FolderInfoController);
|
||||
public:
|
||||
|
||||
/** Constructor */
|
||||
FolderInfoController();
|
||||
|
||||
/** Generates the response */
|
||||
void service(HttpRequest& request, HttpResponse& response);
|
||||
|
||||
private:
|
||||
void serviceComics(const int &library, const qulonglong & folderId, HttpResponse& response);
|
||||
};
|
||||
|
||||
#endif // FOLDERINFOCONTROLLER_H
|
64
YACReaderLibrary/server/controllers/formcontroller.cpp
Normal file
64
YACReaderLibrary/server/controllers/formcontroller.cpp
Normal file
@ -0,0 +1,64 @@
|
||||
/**
|
||||
@file
|
||||
@author Stefan Frings
|
||||
*/
|
||||
|
||||
#include "formcontroller.h"
|
||||
#include <QStringList>
|
||||
|
||||
FormController::FormController() {}
|
||||
|
||||
void FormController::service(HttpRequest& request, HttpResponse& response) {
|
||||
|
||||
response.setHeader("Content-Type", "text/html; charset=utf-8");
|
||||
|
||||
QString data(request.getBody());
|
||||
|
||||
QStringList list = data.split("\n");
|
||||
|
||||
response.write("<html><body>");
|
||||
response.writeText("á é í ó ú ñ -> \\ /Device type: "+list.first());
|
||||
|
||||
//test background proccesing
|
||||
/*int i=0;
|
||||
int j=0;
|
||||
while(i<1000000000)
|
||||
{
|
||||
if(request.getBody().length()>1)
|
||||
j++;
|
||||
else
|
||||
i++;
|
||||
if(i%1000000 == 0)
|
||||
response.write("<p> lista </p>");
|
||||
}*/
|
||||
|
||||
response.write("<p> lista </p>");
|
||||
|
||||
response.write("<ul>");
|
||||
|
||||
for(int i=1;i<list.length();i++)
|
||||
{
|
||||
response.writeText("<li>"+list.at(i)+"</li>");
|
||||
}
|
||||
response.write("</ul></body></html>",true);
|
||||
|
||||
/*if (request.getParameter("action")=="show") {
|
||||
response.write("<html><body>");
|
||||
response.write("Name = ");
|
||||
response.write(request.getParameter("name"));
|
||||
response.write("<br>City = ");
|
||||
response.write(request.getParameter("city"));
|
||||
response.write("</body></html>",true);
|
||||
}
|
||||
else {
|
||||
response.write("<html><body>");
|
||||
response.write("<form method=\"post\">");
|
||||
response.write(" <input type=\"hidden\" name=\"action\" value=\"show\">");
|
||||
response.write(" Name: <input type=\"text\" name=\"name\"><br>");
|
||||
response.write(" City: <input type=\"text\" name=\"city\"><br>");
|
||||
response.write(" <input type=\"submit\">");
|
||||
response.write("</form>");
|
||||
response.write("</body></html>",true);
|
||||
}*/
|
||||
}
|
||||
|
30
YACReaderLibrary/server/controllers/formcontroller.h
Normal file
30
YACReaderLibrary/server/controllers/formcontroller.h
Normal file
@ -0,0 +1,30 @@
|
||||
/**
|
||||
@file
|
||||
@author Stefan Frings
|
||||
*/
|
||||
|
||||
#ifndef FORMCONTROLLER_H
|
||||
#define FORMCONTROLLER_H
|
||||
|
||||
#include "httprequest.h"
|
||||
#include "httpresponse.h"
|
||||
#include "httprequesthandler.h"
|
||||
|
||||
/**
|
||||
This controller displays a HTML form and dumps the submitted input.
|
||||
*/
|
||||
|
||||
|
||||
class FormController : public HttpRequestHandler {
|
||||
Q_OBJECT
|
||||
Q_DISABLE_COPY(FormController);
|
||||
public:
|
||||
|
||||
/** Constructor */
|
||||
FormController();
|
||||
|
||||
/** Generates the response */
|
||||
void service(HttpRequest& request, HttpResponse& response);
|
||||
};
|
||||
|
||||
#endif // FORMCONTROLLER_H
|
40
YACReaderLibrary/server/controllers/librariescontroller.cpp
Normal file
40
YACReaderLibrary/server/controllers/librariescontroller.cpp
Normal file
@ -0,0 +1,40 @@
|
||||
#include "librariescontroller.h"
|
||||
#include "db_helper.h" //get libraries
|
||||
#include "yacreader_libraries.h"
|
||||
|
||||
#include "template.h"
|
||||
#include "../static.h"
|
||||
|
||||
#include "QsLog.h"
|
||||
|
||||
LibrariesController::LibrariesController() {}
|
||||
|
||||
void LibrariesController::service(HttpRequest& request, HttpResponse& response)
|
||||
{
|
||||
HttpSession session=Static::sessionStore->getSession(request,response,false);
|
||||
|
||||
response.setHeader("Content-Type", "text/html; charset=utf-8");
|
||||
response.setHeader("Connection","close");
|
||||
|
||||
session.clearNavigationPath();
|
||||
|
||||
Template t=Static::templateLoader->getTemplate("libraries_"+session.getDeviceType(),request.getHeader("Accept-Language"));
|
||||
t.enableWarnings();
|
||||
|
||||
YACReaderLibraries libraries = DBHelper::getLibraries();
|
||||
QList<QString> names = DBHelper::getLibrariesNames();
|
||||
|
||||
t.loop("library",names.length());
|
||||
|
||||
int currentId = 0;
|
||||
int i = 0;
|
||||
foreach (QString name,names) {
|
||||
currentId = libraries.getId(name);
|
||||
t.setVariable(QString("library%1.name").arg(i),QString::number(currentId));
|
||||
t.setVariable(QString("library%1.label").arg(i),name);
|
||||
i++;
|
||||
}
|
||||
|
||||
response.setStatus(200,"OK");
|
||||
response.writeText(t,true);
|
||||
}
|
25
YACReaderLibrary/server/controllers/librariescontroller.h
Normal file
25
YACReaderLibrary/server/controllers/librariescontroller.h
Normal file
@ -0,0 +1,25 @@
|
||||
#ifndef LIBRARIESCONTROLLER_H
|
||||
#define LIBRARIESCONTROLLER_H
|
||||
|
||||
#include "httprequest.h"
|
||||
#include "httpresponse.h"
|
||||
#include "httprequesthandler.h"
|
||||
|
||||
/**
|
||||
This controller displays a HTML form and dumps the submitted input.
|
||||
*/
|
||||
|
||||
|
||||
class LibrariesController : public HttpRequestHandler {
|
||||
Q_OBJECT
|
||||
Q_DISABLE_COPY(LibrariesController);
|
||||
public:
|
||||
|
||||
/** Constructor */
|
||||
LibrariesController();
|
||||
|
||||
/** Generates the response */
|
||||
void service(HttpRequest& request, HttpResponse& response);
|
||||
};
|
||||
|
||||
#endif // LIBRARIESCONTROLLER_H
|
96
YACReaderLibrary/server/controllers/pagecontroller.cpp
Normal file
96
YACReaderLibrary/server/controllers/pagecontroller.cpp
Normal file
@ -0,0 +1,96 @@
|
||||
#include "pagecontroller.h"
|
||||
|
||||
#include "../static.h"
|
||||
|
||||
#include "comic.h"
|
||||
#include "comiccontroller.h"
|
||||
#include <QDataStream>
|
||||
#include <QPointer>
|
||||
|
||||
#include <QsLog.h>
|
||||
|
||||
#include "db_helper.h"
|
||||
|
||||
PageController::PageController() {}
|
||||
|
||||
void PageController::service(HttpRequest& request, HttpResponse& response)
|
||||
{
|
||||
HttpSession session=Static::sessionStore->getSession(request,response,false);
|
||||
|
||||
QString path = QUrl::fromPercentEncoding(request.getPath()).toUtf8();
|
||||
bool remote = path.endsWith("remote");
|
||||
|
||||
//QByteArray path2=request.getPath();
|
||||
//qDebug("PageController: request to -> %s ",path2.data());
|
||||
|
||||
QStringList pathElements = path.split('/');
|
||||
QString libraryName = DBHelper::getLibraryName(pathElements.at(2).toInt());
|
||||
qulonglong comicId = pathElements.at(4).toULongLong();
|
||||
unsigned int page = pathElements.at(6).toUInt();
|
||||
|
||||
//qDebug("lib name : %s",pathElements.at(2).data());
|
||||
|
||||
Comic * comicFile;
|
||||
qulonglong currentComicId;
|
||||
if(remote)
|
||||
{
|
||||
QLOG_INFO() << "se recupera comic remoto para servir páginas";
|
||||
comicFile = session.getCurrentRemoteComic();
|
||||
currentComicId = session.getCurrentRemoteComicId();
|
||||
}
|
||||
else
|
||||
{
|
||||
QLOG_INFO() << "se recupera comic para servir páginas";
|
||||
comicFile = session.getCurrentComic();
|
||||
currentComicId = session.getCurrentComicId();
|
||||
}
|
||||
|
||||
if(currentComicId != 0 && !QPointer<Comic>(comicFile).isNull())
|
||||
{
|
||||
if(comicId == currentComicId && page < comicFile->numPages())
|
||||
{
|
||||
if(comicFile->pageIsLoaded(page))
|
||||
{
|
||||
//qDebug("PageController: La página estaba cargada -> %s ",path.data());
|
||||
response.setHeader("Content-Type", "image/jpeg");
|
||||
response.setHeader("Transfer-Encoding","chunked");
|
||||
QByteArray pageData = comicFile->getRawPage(page);
|
||||
QDataStream data(pageData);
|
||||
char buffer[4096];
|
||||
while (!data.atEnd()) {
|
||||
int len = data.readRawData(buffer,4096);
|
||||
response.write(QByteArray(buffer,len));
|
||||
}
|
||||
//response.write(pageData,true);
|
||||
response.write(QByteArray(),true);
|
||||
}
|
||||
else
|
||||
{
|
||||
//qDebug("PageController: La página NO estaba cargada 404 -> %s ",path.data());
|
||||
response.setStatus(404,"not found"); //TODO qué mensaje enviar
|
||||
response.write("404 not found",true);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if(comicId != currentComicId)
|
||||
{
|
||||
//delete comicFile;
|
||||
if(remote)
|
||||
session.dismissCurrentRemoteComic();
|
||||
else
|
||||
session.dismissCurrentComic();
|
||||
}
|
||||
response.setStatus(404,"not found"); //TODO qué mensaje enviar
|
||||
response.write("404 not found",true);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
response.setStatus(404,"not found");
|
||||
response.write("404 not found",true);
|
||||
}
|
||||
|
||||
//response.write(t.toLatin1(),true);
|
||||
|
||||
}
|
20
YACReaderLibrary/server/controllers/pagecontroller.h
Normal file
20
YACReaderLibrary/server/controllers/pagecontroller.h
Normal file
@ -0,0 +1,20 @@
|
||||
#ifndef PAGECONTROLLER_H
|
||||
#define PAGECONTROLLER_H
|
||||
|
||||
#include "httprequest.h"
|
||||
#include "httpresponse.h"
|
||||
#include "httprequesthandler.h"
|
||||
|
||||
class PageController : public HttpRequestHandler {
|
||||
Q_OBJECT
|
||||
Q_DISABLE_COPY(PageController);
|
||||
public:
|
||||
|
||||
/** Constructor */
|
||||
PageController();
|
||||
|
||||
/** Generates the response */
|
||||
void service(HttpRequest& request, HttpResponse& response);
|
||||
};
|
||||
|
||||
#endif // PAGECONTROLLER_H
|
31
YACReaderLibrary/server/controllers/sessioncontroller.cpp
Normal file
31
YACReaderLibrary/server/controllers/sessioncontroller.cpp
Normal file
@ -0,0 +1,31 @@
|
||||
/**
|
||||
@file
|
||||
@author Stefan Frings
|
||||
*/
|
||||
|
||||
#include "sessioncontroller.h"
|
||||
#include "../static.h"
|
||||
#include <QVariant>
|
||||
#include <QDateTime>
|
||||
|
||||
SessionController::SessionController(){}
|
||||
|
||||
void SessionController::service(HttpRequest& request, HttpResponse& response) {
|
||||
|
||||
response.setHeader("Content-Type", "text/html; charset=ISO-8859-1");
|
||||
|
||||
// Get current session, or create a new one
|
||||
HttpSession session=Static::sessionStore->getSession(request,response);
|
||||
if (!session.contains("startTime")) {
|
||||
response.write("<html><body>New session started. Reload this page now.</body></html>");
|
||||
session.set("startTime",QDateTime::currentDateTime());
|
||||
}
|
||||
|
||||
else {
|
||||
QDateTime startTime=session.get("startTime").toDateTime();
|
||||
response.write("<html><body>Your session started ");
|
||||
response.write(startTime.toString().toLatin1());
|
||||
response.write("</body></html>");
|
||||
}
|
||||
|
||||
}
|
29
YACReaderLibrary/server/controllers/sessioncontroller.h
Normal file
29
YACReaderLibrary/server/controllers/sessioncontroller.h
Normal file
@ -0,0 +1,29 @@
|
||||
/**
|
||||
@file
|
||||
@author Stefan Frings
|
||||
*/
|
||||
|
||||
#ifndef SESSIONCONTROLLER_H
|
||||
#define SESSIONCONTROLLER_H
|
||||
|
||||
#include "httprequest.h"
|
||||
#include "httpresponse.h"
|
||||
#include "httprequesthandler.h"
|
||||
|
||||
/**
|
||||
This controller demonstrates how to use sessions.
|
||||
*/
|
||||
|
||||
class SessionController : public HttpRequestHandler {
|
||||
Q_OBJECT
|
||||
Q_DISABLE_COPY(SessionController);
|
||||
public:
|
||||
|
||||
/** Constructor */
|
||||
SessionController();
|
||||
|
||||
/** Generates the response */
|
||||
void service(HttpRequest& request, HttpResponse& response);
|
||||
};
|
||||
|
||||
#endif // SESSIONCONTROLLER_H
|
55
YACReaderLibrary/server/controllers/synccontroller.cpp
Normal file
55
YACReaderLibrary/server/controllers/synccontroller.cpp
Normal file
@ -0,0 +1,55 @@
|
||||
#include "synccontroller.h"
|
||||
|
||||
#include "QsLog.h"
|
||||
#include <QUrl>
|
||||
|
||||
#include "comic_db.h"
|
||||
#include "db_helper.h"
|
||||
|
||||
SyncController::SyncController()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
void SyncController::service(HttpRequest &request, HttpResponse &response)
|
||||
{
|
||||
QString postData = QString::fromUtf8(request.getBody());
|
||||
|
||||
QLOG_INFO() << "POST DATA: " << postData;
|
||||
|
||||
if(postData.length()>0) {
|
||||
QList<QString> data = postData.split("\n");
|
||||
|
||||
qulonglong libraryId;
|
||||
qulonglong comicId;
|
||||
int currentPage;
|
||||
QString hash;
|
||||
foreach(QString comicInfo, data)
|
||||
{
|
||||
QList<QString> comicInfoProgress = comicInfo.split("\t");
|
||||
|
||||
if(comicInfoProgress.length() == 4)
|
||||
{
|
||||
libraryId = comicInfoProgress.at(0).toULongLong();
|
||||
comicId = comicInfoProgress.at(1).toULongLong();
|
||||
hash = comicInfoProgress.at(2);
|
||||
currentPage = comicInfoProgress.at(3).toInt();
|
||||
|
||||
ComicInfo info;
|
||||
info.currentPage = currentPage;
|
||||
info.hash = hash; //TODO remove the hash check and add UUIDs for libraries
|
||||
info.id = comicId;
|
||||
DBHelper::updateFromRemoteClient(libraryId,info);
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
response.setStatus(412,"No comic info received");
|
||||
response.writeText("",true);
|
||||
return;
|
||||
}
|
||||
|
||||
response.write("OK",true);
|
||||
}
|
||||
|
21
YACReaderLibrary/server/controllers/synccontroller.h
Normal file
21
YACReaderLibrary/server/controllers/synccontroller.h
Normal file
@ -0,0 +1,21 @@
|
||||
#ifndef SYNCCONTROLLER_H
|
||||
#define SYNCCONTROLLER_H
|
||||
|
||||
#include <QObject>
|
||||
|
||||
#include "httprequest.h"
|
||||
#include "httpresponse.h"
|
||||
#include "httprequesthandler.h"
|
||||
|
||||
class SyncController : public HttpRequestHandler {
|
||||
Q_OBJECT
|
||||
Q_DISABLE_COPY(SyncController);
|
||||
public:
|
||||
/** Constructor */
|
||||
SyncController();
|
||||
|
||||
/** Generates the response */
|
||||
void service(HttpRequest& request, HttpResponse& response);
|
||||
};
|
||||
|
||||
#endif // SYNCCONTROLLER_H
|
31
YACReaderLibrary/server/controllers/templatecontroller.cpp
Normal file
31
YACReaderLibrary/server/controllers/templatecontroller.cpp
Normal file
@ -0,0 +1,31 @@
|
||||
/**
|
||||
@file
|
||||
@author Stefan Frings
|
||||
*/
|
||||
|
||||
#include "templatecontroller.h"
|
||||
#include "template.h"
|
||||
#include "../static.h"
|
||||
|
||||
TemplateController::TemplateController(){}
|
||||
|
||||
void TemplateController::service(HttpRequest& request, HttpResponse& response) {
|
||||
|
||||
response.setHeader("Content-Type", "text/html; charset=ISO-8859-1");
|
||||
|
||||
Template t=Static::templateLoader->getTemplate("demo",request.getHeader("Accept-Language"));
|
||||
t.enableWarnings();
|
||||
t.setVariable("path",request.getPath());
|
||||
QMap<QByteArray,QByteArray> headers=request.getHeaderMap();
|
||||
QMapIterator<QByteArray,QByteArray> iterator(headers);
|
||||
t.loop("header",headers.size());
|
||||
int i=0;
|
||||
while (iterator.hasNext()) {
|
||||
iterator.next();
|
||||
t.setVariable(QString("header%1.name").arg(i),QString(iterator.key()));
|
||||
t.setVariable(QString("header%1.value").arg(i),QString(iterator.value()));
|
||||
++i;
|
||||
}
|
||||
|
||||
response.write(t.toLatin1(),true);
|
||||
}
|
30
YACReaderLibrary/server/controllers/templatecontroller.h
Normal file
30
YACReaderLibrary/server/controllers/templatecontroller.h
Normal file
@ -0,0 +1,30 @@
|
||||
/**
|
||||
@file
|
||||
@author Stefan Frings
|
||||
*/
|
||||
|
||||
#ifndef TEMPLATECONTROLLER_H
|
||||
#define TEMPLATECONTROLLER_H
|
||||
|
||||
#include "httprequest.h"
|
||||
#include "httpresponse.h"
|
||||
#include "httprequesthandler.h"
|
||||
|
||||
/**
|
||||
This controller generates a website using the template engine.
|
||||
It generates a Latin1 (ISO-8859-1) encoded website from a UTF-8 encoded template file.
|
||||
*/
|
||||
|
||||
class TemplateController : public HttpRequestHandler {
|
||||
Q_OBJECT
|
||||
Q_DISABLE_COPY(TemplateController);
|
||||
public:
|
||||
|
||||
/** Constructor */
|
||||
TemplateController();
|
||||
|
||||
/** Generates the response */
|
||||
void service(HttpRequest& request, HttpResponse& response);
|
||||
};
|
||||
|
||||
#endif // TEMPLATECONTROLLER_H
|
@ -0,0 +1,46 @@
|
||||
#include "updatecomiccontroller.h"
|
||||
|
||||
#include "db_helper.h"
|
||||
#include "yacreader_libraries.h"
|
||||
|
||||
#include "template.h"
|
||||
#include "../static.h"
|
||||
|
||||
#include "comic_db.h"
|
||||
#include "comic.h"
|
||||
|
||||
#include "QsLog.h"
|
||||
|
||||
UpdateComicController::UpdateComicController(){}
|
||||
|
||||
void UpdateComicController::service(HttpRequest &request, HttpResponse &response)
|
||||
{
|
||||
HttpSession session=Static::sessionStore->getSession(request,response,false);
|
||||
|
||||
QString path = QUrl::fromPercentEncoding(request.getPath()).toUtf8();
|
||||
QStringList pathElements = path.split('/');
|
||||
qulonglong libraryId = pathElements.at(2).toULongLong();
|
||||
QString libraryName = DBHelper::getLibraryName(libraryId);
|
||||
qulonglong comicId = pathElements.at(4).toULongLong();
|
||||
|
||||
QString postData = QString::fromUtf8(request.getBody());
|
||||
|
||||
QLOG_INFO() << "POST DATA: " << postData;
|
||||
|
||||
if(postData.length()>0) {
|
||||
QList<QString> data = postData.split("\n");
|
||||
int currentPage = data.at(0).split(":").at(1).toInt();
|
||||
ComicInfo info;
|
||||
info.currentPage = currentPage;
|
||||
info.id = comicId;
|
||||
DBHelper::updateProgress(libraryId,info);
|
||||
}
|
||||
else
|
||||
{
|
||||
response.setStatus(412,"No comic info received");
|
||||
response.writeText("",true);
|
||||
return;
|
||||
}
|
||||
|
||||
response.write("OK",true);
|
||||
}
|
22
YACReaderLibrary/server/controllers/updatecomiccontroller.h
Normal file
22
YACReaderLibrary/server/controllers/updatecomiccontroller.h
Normal file
@ -0,0 +1,22 @@
|
||||
#ifndef UPDATECOMICCONTROLLER_H
|
||||
#define UPDATECOMICCONTROLLER_H
|
||||
|
||||
|
||||
#include "httprequest.h"
|
||||
#include "httpresponse.h"
|
||||
#include "httprequesthandler.h"
|
||||
|
||||
|
||||
class UpdateComicController : public HttpRequestHandler
|
||||
{
|
||||
Q_OBJECT
|
||||
Q_DISABLE_COPY(UpdateComicController);
|
||||
|
||||
public:
|
||||
UpdateComicController();
|
||||
|
||||
/** Generates the response */
|
||||
void service(HttpRequest& request, HttpResponse& response);
|
||||
};
|
||||
|
||||
#endif // UPDATECOMICCONTROLLER_H
|
4
YACReaderLibrary/server/documentcache.h
Normal file
4
YACReaderLibrary/server/documentcache.h
Normal file
@ -0,0 +1,4 @@
|
||||
#ifndef DOCUMENTCACHE_H
|
||||
#define DOCUMENTCACHE_H
|
||||
|
||||
#endif // DOCUMENTCACHE_H
|
12
YACReaderLibrary/server/lib/bfHttpServer/bfHttpServer.pri
Normal file
12
YACReaderLibrary/server/lib/bfHttpServer/bfHttpServer.pri
Normal file
@ -0,0 +1,12 @@
|
||||
INCLUDEPATH += $$PWD
|
||||
DEPENDPATH += $$PWD
|
||||
|
||||
HEADERS += $$PWD/httplistener.h $$PWD/httpconnectionhandler.h $$PWD/httpconnectionhandlerpool.h $$PWD/httprequest.h $$PWD/httpresponse.h $$PWD/httpcookie.h $$PWD/httprequesthandler.h
|
||||
HEADERS += $$PWD/httpsession.h $$PWD/httpsessionstore.h
|
||||
HEADERS += $$PWD/staticfilecontroller.h
|
||||
|
||||
SOURCES += $$PWD/httplistener.cpp $$PWD/httpconnectionhandler.cpp $$PWD/httpconnectionhandlerpool.cpp $$PWD/httprequest.cpp $$PWD/httpresponse.cpp $$PWD/httpcookie.cpp $$PWD/httprequesthandler.cpp
|
||||
SOURCES += $$PWD/httpsession.cpp $$PWD/httpsessionstore.cpp
|
||||
SOURCES += $$PWD/staticfilecontroller.cpp
|
||||
|
||||
OTHER_FILES += $$PWD/../doc/readme.txt
|
@ -0,0 +1,170 @@
|
||||
/**
|
||||
@file
|
||||
@author Stefan Frings
|
||||
*/
|
||||
|
||||
#include "httpconnectionhandler.h"
|
||||
#include "httpresponse.h"
|
||||
#include <QTimer>
|
||||
#include <QCoreApplication>
|
||||
|
||||
HttpConnectionHandler::HttpConnectionHandler(QSettings* settings, HttpRequestHandler* requestHandler)
|
||||
: QThread()
|
||||
{
|
||||
Q_ASSERT(settings!=0);
|
||||
Q_ASSERT(requestHandler!=0);
|
||||
this->settings=settings;
|
||||
this->requestHandler=requestHandler;
|
||||
currentRequest=0;
|
||||
busy = false;
|
||||
// execute signals in my own thread
|
||||
moveToThread(this);
|
||||
socket.moveToThread(this);
|
||||
readTimer.moveToThread(this);
|
||||
connect(&socket, SIGNAL(readyRead()), SLOT(read()));
|
||||
connect(&socket, SIGNAL(disconnected()), SLOT(disconnected()));
|
||||
connect(&readTimer, SIGNAL(timeout()), SLOT(readTimeout()));
|
||||
readTimer.setSingleShot(true);
|
||||
qDebug("HttpConnectionHandler (%p): constructed", this);
|
||||
this->start();
|
||||
}
|
||||
|
||||
|
||||
HttpConnectionHandler::~HttpConnectionHandler() {
|
||||
socket.close();
|
||||
quit();
|
||||
wait();
|
||||
qDebug("HttpConnectionHandler (%p): destroyed", this);
|
||||
}
|
||||
|
||||
|
||||
void HttpConnectionHandler::run() {
|
||||
qDebug("HttpConnectionHandler (%p): thread started", this);
|
||||
try {
|
||||
exec();
|
||||
}
|
||||
catch (...) {
|
||||
qCritical("HttpConnectionHandler (%p): an uncatched exception occured in the thread",this);
|
||||
}
|
||||
qDebug("HttpConnectionHandler (%p): thread stopped", this);
|
||||
// Change to the main thread, otherwise deleteLater() would not work
|
||||
moveToThread(QCoreApplication::instance()->thread());
|
||||
}
|
||||
|
||||
|
||||
void HttpConnectionHandler::handleConnection(tSocketDescriptor socketDescriptor) {
|
||||
qDebug("HttpConnectionHandler (%p): handle new connection", this);
|
||||
busy = true;
|
||||
Q_ASSERT(socket.isOpen()==false); // if not, then the handler is already busy
|
||||
|
||||
if (!socket.setSocketDescriptor(socketDescriptor)) {
|
||||
qCritical("HttpConnectionHandler (%p): cannot initialize socket: %s", this,qPrintable(socket.errorString()));
|
||||
return;
|
||||
}
|
||||
// Start timer for read timeout
|
||||
int readTimeout=settings->value("readTimeout",10000).toInt();
|
||||
readTimer.start(readTimeout);
|
||||
// delete previous request
|
||||
delete currentRequest;
|
||||
currentRequest=0;
|
||||
}
|
||||
|
||||
|
||||
bool HttpConnectionHandler::isBusy() {
|
||||
return busy;
|
||||
}
|
||||
|
||||
void HttpConnectionHandler::setBusy() {
|
||||
this->busy = true;
|
||||
}
|
||||
|
||||
|
||||
void HttpConnectionHandler::readTimeout() {
|
||||
qDebug("HttpConnectionHandler (%p): read timeout occured",this);
|
||||
|
||||
//Commented out because QWebView cannot handle this.
|
||||
//socket.write("HTTP/1.1 408 request timeout\r\nConnection: close\r\n\r\n408 request timeout\r\n");
|
||||
|
||||
socket.disconnectFromHost();
|
||||
delete currentRequest;
|
||||
currentRequest=0;
|
||||
}
|
||||
|
||||
|
||||
void HttpConnectionHandler::disconnected() {
|
||||
qDebug("HttpConnectionHandler (%p): disconnected", this);
|
||||
socket.close();
|
||||
readTimer.stop();
|
||||
busy = false;
|
||||
}
|
||||
|
||||
void HttpConnectionHandler::read() {
|
||||
while (socket.bytesAvailable()) {
|
||||
#ifdef SUPERVERBOSE
|
||||
qDebug("HttpConnectionHandler (%p): read input",this);
|
||||
#endif
|
||||
|
||||
// Create new HttpRequest object if necessary
|
||||
if (!currentRequest) {
|
||||
currentRequest=new HttpRequest(settings);
|
||||
}
|
||||
|
||||
// Collect data for the request object
|
||||
while (socket.bytesAvailable() && currentRequest->getStatus()!=HttpRequest::complete && currentRequest->getStatus()!=HttpRequest::abort) {
|
||||
currentRequest->readFromSocket(socket);
|
||||
if (currentRequest->getStatus()==HttpRequest::waitForBody) {
|
||||
// Restart timer for read timeout, otherwise it would
|
||||
// expire during large file uploads.
|
||||
int readTimeout=settings->value("readTimeout",10000).toInt();
|
||||
readTimer.start(readTimeout);
|
||||
}
|
||||
}
|
||||
|
||||
// If the request is aborted, return error message and close the connection
|
||||
if (currentRequest->getStatus()==HttpRequest::abort) {
|
||||
socket.write("HTTP/1.1 413 entity too large\r\nConnection: close\r\n\r\n413 Entity too large\r\n");
|
||||
socket.disconnectFromHost();
|
||||
delete currentRequest;
|
||||
currentRequest=0;
|
||||
return;
|
||||
}
|
||||
|
||||
// If the request is complete, let the request mapper dispatch it
|
||||
if (currentRequest->getStatus()==HttpRequest::complete) {
|
||||
readTimer.stop();
|
||||
qDebug("HttpConnectionHandler (%p): received request",this);
|
||||
HttpResponse response(&socket);
|
||||
//response.setHeader("Connection","close"); No funciona bien con NSURLConnection
|
||||
try {
|
||||
requestHandler->service(*currentRequest, response);
|
||||
}
|
||||
catch (...) {
|
||||
qCritical("HttpConnectionHandler (%p): An uncatched exception occured in the request handler",this);
|
||||
}
|
||||
|
||||
// Finalize sending the response if not already done
|
||||
if (!response.hasSentLastPart()) {
|
||||
response.write(QByteArray(),true);
|
||||
}
|
||||
|
||||
//socket.disconnectFromHost(); //CAMBIADO s<>lo se van a soportar conexiones NO persistentes
|
||||
|
||||
// Close the connection after delivering the response, if requested
|
||||
if (QString::compare(currentRequest->getHeader("Connection"),"close",Qt::CaseInsensitive)==0) {
|
||||
socket.disconnectFromHost();
|
||||
}
|
||||
else {
|
||||
// Start timer for next request
|
||||
int readTimeout=settings->value("readTimeout",10000).toInt();
|
||||
readTimer.start(readTimeout);
|
||||
}
|
||||
// Prepare for next request
|
||||
delete currentRequest;
|
||||
currentRequest=0;
|
||||
}
|
||||
else
|
||||
{
|
||||
qDebug("HttpConnectionHandler (%p): received request",this);
|
||||
}
|
||||
}
|
||||
}
|
103
YACReaderLibrary/server/lib/bfHttpServer/httpconnectionhandler.h
Normal file
103
YACReaderLibrary/server/lib/bfHttpServer/httpconnectionhandler.h
Normal file
@ -0,0 +1,103 @@
|
||||
/**
|
||||
@file
|
||||
@author Stefan Frings
|
||||
*/
|
||||
|
||||
#ifndef HTTPCONNECTIONHANDLER_H
|
||||
#define HTTPCONNECTIONHANDLER_H
|
||||
|
||||
#include <QTcpSocket>
|
||||
#include <QSettings>
|
||||
#include <QTimer>
|
||||
#include <QThread>
|
||||
#include "httprequest.h"
|
||||
#include "httprequesthandler.h"
|
||||
|
||||
/**
|
||||
The connection handler accepts incoming connections and dispatches incoming requests to to a
|
||||
request mapper. Since HTTP clients can send multiple requests before waiting for the response,
|
||||
the incoming requests are queued and processed one after the other.
|
||||
<p>
|
||||
Example for the required configuration settings:
|
||||
<code><pre>
|
||||
readTimeout=60000
|
||||
maxRequestSize=16000
|
||||
maxMultiPartSize=1000000
|
||||
</pre></code>
|
||||
<p>
|
||||
The readTimeout value defines the maximum time to wait for a complete HTTP request.
|
||||
@see HttpRequest for description of config settings maxRequestSize and maxMultiPartSize
|
||||
*/
|
||||
|
||||
#if QT_VERSION >= 0x050000
|
||||
typedef qintptr tSocketDescriptor;
|
||||
#else
|
||||
typedef int tSocketDescriptor;
|
||||
#endif
|
||||
|
||||
class HttpConnectionHandler : public QThread {
|
||||
Q_OBJECT
|
||||
Q_DISABLE_COPY(HttpConnectionHandler)
|
||||
public:
|
||||
|
||||
/**
|
||||
Constructor.
|
||||
@param settings Configuration settings of the HTTP webserver
|
||||
@param requestHandler handler that will process each incomin HTTP request
|
||||
*/
|
||||
HttpConnectionHandler(QSettings* settings, HttpRequestHandler* requestHandler);
|
||||
|
||||
/** Destructor */
|
||||
virtual ~HttpConnectionHandler();
|
||||
|
||||
/** Returns true, if this handler is in use. */
|
||||
bool isBusy();
|
||||
|
||||
/** Mark this handler as busy */
|
||||
void setBusy();
|
||||
|
||||
private:
|
||||
|
||||
/** Configuration settings */
|
||||
QSettings* settings;
|
||||
|
||||
/** TCP socket of the current connection */
|
||||
QTcpSocket socket;
|
||||
|
||||
/** Time for read timeout detection */
|
||||
QTimer readTimer;
|
||||
|
||||
/** Storage for the current incoming HTTP request */
|
||||
HttpRequest* currentRequest;
|
||||
|
||||
/** Dispatches received requests to services */
|
||||
HttpRequestHandler* requestHandler;
|
||||
|
||||
/** This shows the busy-state from a very early time */
|
||||
bool busy;
|
||||
|
||||
/** Executes the htreads own event loop */
|
||||
void run();
|
||||
|
||||
public slots:
|
||||
|
||||
/**
|
||||
Received from from the listener, when the handler shall start processing a new connection.
|
||||
@param socketDescriptor references the accepted connection.
|
||||
*/
|
||||
void handleConnection(tSocketDescriptor socketDescriptor);
|
||||
|
||||
private slots:
|
||||
|
||||
/** Received from the socket when a read-timeout occured */
|
||||
void readTimeout();
|
||||
|
||||
/** Received from the socket when incoming data can be read */
|
||||
void read();
|
||||
|
||||
/** Received from the socket when a connection has been closed */
|
||||
void disconnected();
|
||||
|
||||
};
|
||||
|
||||
#endif // HTTPCONNECTIONHANDLER_H
|
@ -0,0 +1,64 @@
|
||||
#include "httpconnectionhandlerpool.h"
|
||||
|
||||
HttpConnectionHandlerPool::HttpConnectionHandlerPool(QSettings* settings, HttpRequestHandler* requestHandler)
|
||||
: QObject()
|
||||
{
|
||||
Q_ASSERT(settings!=0);
|
||||
this->settings=settings;
|
||||
this->requestHandler=requestHandler;
|
||||
cleanupTimer.start(settings->value("cleanupInterval",10000).toInt());
|
||||
connect(&cleanupTimer, SIGNAL(timeout()), SLOT(cleanup()));
|
||||
}
|
||||
|
||||
|
||||
HttpConnectionHandlerPool::~HttpConnectionHandlerPool() {
|
||||
foreach(HttpConnectionHandler* handler, pool) {
|
||||
connect(handler,SIGNAL(finished()),handler,SLOT(deleteLater()));
|
||||
handler->quit();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
HttpConnectionHandler* HttpConnectionHandlerPool::getConnectionHandler() {
|
||||
HttpConnectionHandler* freeHandler=0;
|
||||
mutex.lock();
|
||||
// find a free handler in pool
|
||||
foreach(HttpConnectionHandler* handler, pool) {
|
||||
if (!handler->isBusy()) {
|
||||
freeHandler=handler;
|
||||
freeHandler->setBusy();
|
||||
break;
|
||||
}
|
||||
}
|
||||
// create a new handler, if necessary
|
||||
if (!freeHandler) {
|
||||
int maxConnectionHandlers=settings->value("maxThreads",1000).toInt();
|
||||
if (pool.count()<maxConnectionHandlers) {
|
||||
freeHandler=new HttpConnectionHandler(settings,requestHandler);
|
||||
freeHandler->setBusy();
|
||||
pool.append(freeHandler);
|
||||
}
|
||||
}
|
||||
mutex.unlock();
|
||||
return freeHandler;
|
||||
}
|
||||
|
||||
|
||||
|
||||
void HttpConnectionHandlerPool::cleanup() {
|
||||
int maxIdleHandlers=settings->value("minThreads",50).toInt();
|
||||
int idleCounter=0;
|
||||
mutex.lock();
|
||||
foreach(HttpConnectionHandler* handler, pool) {
|
||||
if (!handler->isBusy()) {
|
||||
if (++idleCounter > maxIdleHandlers) {
|
||||
pool.removeOne(handler);
|
||||
qDebug("HttpConnectionHandlerPool: Removed connection handler (%p), pool size is now %i",handler,pool.size());
|
||||
connect(handler,SIGNAL(finished()),handler,SLOT(deleteLater()));
|
||||
handler->quit();
|
||||
break; // remove only one handler in each interval
|
||||
}
|
||||
}
|
||||
}
|
||||
mutex.unlock();
|
||||
}
|
@ -0,0 +1,73 @@
|
||||
#ifndef HTTPCONNECTIONHANDLERPOOL_H
|
||||
#define HTTPCONNECTIONHANDLERPOOL_H
|
||||
|
||||
#include <QList>
|
||||
#include <QTimer>
|
||||
#include <QObject>
|
||||
#include <QMutex>
|
||||
#include "httpconnectionhandler.h"
|
||||
|
||||
/**
|
||||
Pool of http connection handlers. Connection handlers are created on demand and idle handlers are
|
||||
cleaned up in regular time intervals.
|
||||
<p>
|
||||
Example for the required configuration settings:
|
||||
<code><pre>
|
||||
minThreads=1
|
||||
maxThreads=100
|
||||
cleanupInterval=1000
|
||||
maxRequestSize=16000
|
||||
maxMultiPartSize=1000000
|
||||
</pre></code>
|
||||
The pool is empty initially and grows with the number of concurrent
|
||||
connections. A timer removes one idle connection handler at each
|
||||
interval, but it leaves some spare handlers in memory to improve
|
||||
performance.
|
||||
@see HttpConnectionHandler for description of config settings readTimeout
|
||||
@see HttpRequest for description of config settings maxRequestSize and maxMultiPartSize
|
||||
*/
|
||||
|
||||
class HttpConnectionHandlerPool : public QObject {
|
||||
Q_OBJECT
|
||||
Q_DISABLE_COPY(HttpConnectionHandlerPool)
|
||||
public:
|
||||
|
||||
/**
|
||||
Constructor.
|
||||
@param settings Configuration settings for the HTTP server. Must not be 0.
|
||||
@param requestHandler The handler that will process each received HTTP request.
|
||||
@warning The requestMapper gets deleted by the destructor of this pool
|
||||
*/
|
||||
HttpConnectionHandlerPool(QSettings* settings, HttpRequestHandler* requestHandler);
|
||||
|
||||
/** Destructor */
|
||||
virtual ~HttpConnectionHandlerPool();
|
||||
|
||||
/** Get a free connection handler, or 0 if not available. */
|
||||
HttpConnectionHandler* getConnectionHandler();
|
||||
|
||||
private:
|
||||
|
||||
/** Settings for this pool */
|
||||
QSettings* settings;
|
||||
|
||||
/** Will be assigned to each Connectionhandler during their creation */
|
||||
HttpRequestHandler* requestHandler;
|
||||
|
||||
/** Pool of connection handlers */
|
||||
QList<HttpConnectionHandler*> pool;
|
||||
|
||||
/** Timer to clean-up unused connection handler */
|
||||
QTimer cleanupTimer;
|
||||
|
||||
/** Used to synchronize threads */
|
||||
QMutex mutex;
|
||||
|
||||
private slots:
|
||||
|
||||
/** Received from the clean-up timer. */
|
||||
void cleanup();
|
||||
|
||||
};
|
||||
|
||||
#endif // HTTPCONNECTIONHANDLERPOOL_H
|
199
YACReaderLibrary/server/lib/bfHttpServer/httpcookie.cpp
Normal file
199
YACReaderLibrary/server/lib/bfHttpServer/httpcookie.cpp
Normal file
@ -0,0 +1,199 @@
|
||||
/**
|
||||
@file
|
||||
@author Stefan Frings
|
||||
*/
|
||||
|
||||
#include "httpcookie.h"
|
||||
|
||||
HttpCookie::HttpCookie() {
|
||||
version=1;
|
||||
maxAge=0;
|
||||
secure=false;
|
||||
}
|
||||
|
||||
HttpCookie::HttpCookie(const QByteArray name, const QByteArray value, const int maxAge, const QByteArray path, const QByteArray comment, const QByteArray domain, const bool secure) {
|
||||
this->name=name;
|
||||
this->value=value;
|
||||
this->maxAge=maxAge;
|
||||
this->path=path;
|
||||
this->comment=comment;
|
||||
this->domain=domain;
|
||||
this->secure=secure;
|
||||
this->version=1;
|
||||
}
|
||||
|
||||
HttpCookie::HttpCookie(const QByteArray source) {
|
||||
version=1;
|
||||
maxAge=0;
|
||||
secure=false;
|
||||
QList<QByteArray> list=splitCSV(source);
|
||||
foreach(QByteArray part, list) {
|
||||
|
||||
// Split the part into name and value
|
||||
QByteArray name;
|
||||
QByteArray value;
|
||||
int posi=part.indexOf('=');
|
||||
if (posi) {
|
||||
name=part.left(posi).trimmed();
|
||||
value=part.mid(posi+1).trimmed();
|
||||
}
|
||||
else {
|
||||
name=part.trimmed();
|
||||
value="";
|
||||
}
|
||||
|
||||
// Set fields
|
||||
if (name=="Comment") {
|
||||
comment=value;
|
||||
}
|
||||
else if (name=="Domain") {
|
||||
domain=value;
|
||||
}
|
||||
else if (name=="Max-Age") {
|
||||
maxAge=value.toInt();
|
||||
}
|
||||
else if (name=="Path") {
|
||||
path=value;
|
||||
}
|
||||
else if (name=="Secure") {
|
||||
secure=true;
|
||||
}
|
||||
else if (name=="Version") {
|
||||
version=value.toInt();
|
||||
}
|
||||
else {
|
||||
if (this->name.isEmpty()) {
|
||||
this->name=name;
|
||||
this->value=value;
|
||||
}
|
||||
else {
|
||||
qWarning("HttpCookie: Ignoring unknown %s=%s",name.data(),value.data());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
QByteArray HttpCookie::toByteArray() const {
|
||||
QByteArray buffer(name);
|
||||
buffer.append('=');
|
||||
buffer.append(value);
|
||||
if (!comment.isEmpty()) {
|
||||
buffer.append("; Comment=");
|
||||
buffer.append(comment);
|
||||
}
|
||||
if (!domain.isEmpty()) {
|
||||
buffer.append("; Domain=");
|
||||
buffer.append(domain);
|
||||
}
|
||||
if (maxAge!=0) {
|
||||
buffer.append("; Max-Age=");
|
||||
buffer.append(QByteArray::number(maxAge));
|
||||
}
|
||||
if (!path.isEmpty()) {
|
||||
buffer.append("; Path=");
|
||||
buffer.append(path);
|
||||
}
|
||||
if (secure) {
|
||||
buffer.append("; Secure");
|
||||
}
|
||||
buffer.append("; Version=");
|
||||
buffer.append(QByteArray::number(version));
|
||||
return buffer;
|
||||
}
|
||||
|
||||
void HttpCookie::setName(const QByteArray name){
|
||||
this->name=name;
|
||||
}
|
||||
|
||||
void HttpCookie::setValue(const QByteArray value){
|
||||
this->value=value;
|
||||
}
|
||||
|
||||
void HttpCookie::setComment(const QByteArray comment){
|
||||
this->comment=comment;
|
||||
}
|
||||
|
||||
void HttpCookie::setDomain(const QByteArray domain){
|
||||
this->domain=domain;
|
||||
}
|
||||
|
||||
void HttpCookie::setMaxAge(const int maxAge){
|
||||
this->maxAge=maxAge;
|
||||
}
|
||||
|
||||
void HttpCookie::setPath(const QByteArray path){
|
||||
this->path=path;
|
||||
}
|
||||
|
||||
void HttpCookie::setSecure(const bool secure){
|
||||
this->secure=secure;
|
||||
}
|
||||
|
||||
QByteArray HttpCookie::getName() const {
|
||||
return name;
|
||||
}
|
||||
|
||||
QByteArray HttpCookie::getValue() const {
|
||||
return value;
|
||||
}
|
||||
|
||||
QByteArray HttpCookie::getComment() const {
|
||||
return comment;
|
||||
}
|
||||
|
||||
QByteArray HttpCookie::getDomain() const {
|
||||
return domain;
|
||||
}
|
||||
|
||||
int HttpCookie::getMaxAge() const {
|
||||
return maxAge;
|
||||
}
|
||||
|
||||
QByteArray HttpCookie::getPath() const {
|
||||
return path;
|
||||
}
|
||||
|
||||
bool HttpCookie::getSecure() const {
|
||||
return secure;
|
||||
}
|
||||
|
||||
int HttpCookie::getVersion() const {
|
||||
return version;
|
||||
}
|
||||
|
||||
QList<QByteArray> HttpCookie::splitCSV(const QByteArray source) {
|
||||
bool inString=false;
|
||||
QList<QByteArray> list;
|
||||
QByteArray buffer;
|
||||
for (int i=0; i<source.size(); ++i) {
|
||||
char c=source.at(i);
|
||||
if (inString==false) {
|
||||
if (c=='\"') {
|
||||
inString=true;
|
||||
}
|
||||
else if (c==';') {
|
||||
QByteArray trimmed=buffer.trimmed();
|
||||
if (!trimmed.isEmpty()) {
|
||||
list.append(trimmed);
|
||||
}
|
||||
buffer.clear();
|
||||
}
|
||||
else {
|
||||
buffer.append(c);
|
||||
}
|
||||
}
|
||||
else {
|
||||
if (c=='\"') {
|
||||
inString=false;
|
||||
}
|
||||
else {
|
||||
buffer.append(c);
|
||||
}
|
||||
}
|
||||
}
|
||||
QByteArray trimmed=buffer.trimmed();
|
||||
if (!trimmed.isEmpty()) {
|
||||
list.append(trimmed);
|
||||
}
|
||||
return list;
|
||||
}
|
110
YACReaderLibrary/server/lib/bfHttpServer/httpcookie.h
Normal file
110
YACReaderLibrary/server/lib/bfHttpServer/httpcookie.h
Normal file
@ -0,0 +1,110 @@
|
||||
/**
|
||||
@file
|
||||
@author Stefan Frings
|
||||
*/
|
||||
|
||||
#ifndef HTTPCOOKIE_H
|
||||
#define HTTPCOOKIE_H
|
||||
|
||||
#include <QList>
|
||||
#include <QByteArray>
|
||||
|
||||
/**
|
||||
HTTP cookie as defined in RFC 2109. This class can also parse
|
||||
RFC 2965 cookies, but skips fields that are not defined in RFC
|
||||
2109.
|
||||
*/
|
||||
|
||||
class HttpCookie
|
||||
{
|
||||
public:
|
||||
|
||||
/** Creates an empty cookie */
|
||||
HttpCookie();
|
||||
|
||||
/**
|
||||
Create a cookie and set name/value pair.
|
||||
@param name name of the cookie
|
||||
@param value value of the cookie
|
||||
@param maxAge maximum age of the cookie in seconds. 0=discard immediately
|
||||
@param path Path for that the cookie will be sent, default="/" which means the whole domain
|
||||
@param comment Optional comment, may be displayed by the web browser somewhere
|
||||
@param domain Optional domain for that the cookie will be sent. Defaults to the current domain
|
||||
@param secure If true, the cookie will only be sent on secure connections
|
||||
*/
|
||||
HttpCookie(const QByteArray name, const QByteArray value, const int maxAge, const QByteArray path="/", const QByteArray comment=QByteArray(), const QByteArray domain=QByteArray(), const bool secure=false);
|
||||
|
||||
/**
|
||||
Create a cookie from a string.
|
||||
@param source String as received in a HTTP Cookie2 header.
|
||||
*/
|
||||
HttpCookie(const QByteArray source);
|
||||
|
||||
/** Convert this cookie to a string that may be used in a Set-Cookie2 header. */
|
||||
QByteArray toByteArray() const ;
|
||||
|
||||
/**
|
||||
Split a string list into parts, where each part is delimited by semicolon.
|
||||
Semicolons within double quotes are skipped. Double quotes are removed.
|
||||
*/
|
||||
static QList<QByteArray> splitCSV(const QByteArray source);
|
||||
|
||||
/** Set the name of this cookie */
|
||||
void setName(const QByteArray name);
|
||||
|
||||
/** Set the value of this cookie */
|
||||
void setValue(const QByteArray value);
|
||||
|
||||
/** Set the comment of this cookie */
|
||||
void setComment(const QByteArray comment);
|
||||
|
||||
/** Set the domain of this cookie */
|
||||
void setDomain(const QByteArray domain);
|
||||
|
||||
/** Set the maximum age of this cookie in seconds. 0=discard immediately */
|
||||
void setMaxAge(const int maxAge);
|
||||
|
||||
/** Set the path for that the cookie will be sent, default="/" which means the whole domain */
|
||||
void setPath(const QByteArray path);
|
||||
|
||||
/** Set secure mode, so that the cokkie will only be sent on secure connections */
|
||||
void setSecure(const bool secure);
|
||||
|
||||
/** Get the name of this cookie */
|
||||
QByteArray getName() const;
|
||||
|
||||
/** Get the value of this cookie */
|
||||
QByteArray getValue() const;
|
||||
|
||||
/** Get the comment of this cookie */
|
||||
QByteArray getComment() const;
|
||||
|
||||
/** Get the domain of this cookie */
|
||||
QByteArray getDomain() const;
|
||||
|
||||
/** Set the maximum age of this cookie in seconds. */
|
||||
int getMaxAge() const;
|
||||
|
||||
/** Set the path of this cookie */
|
||||
QByteArray getPath() const;
|
||||
|
||||
/** Get the secure flag of this cookie */
|
||||
bool getSecure() const;
|
||||
|
||||
/** Returns always 1 */
|
||||
int getVersion() const;
|
||||
|
||||
private:
|
||||
|
||||
QByteArray name;
|
||||
QByteArray value;
|
||||
QByteArray comment;
|
||||
QByteArray domain;
|
||||
int maxAge;
|
||||
QByteArray path;
|
||||
bool secure;
|
||||
int version;
|
||||
|
||||
};
|
||||
|
||||
#endif // HTTPCOOKIE_H
|
68
YACReaderLibrary/server/lib/bfHttpServer/httplistener.cpp
Normal file
68
YACReaderLibrary/server/lib/bfHttpServer/httplistener.cpp
Normal file
@ -0,0 +1,68 @@
|
||||
/**
|
||||
@file
|
||||
@author Stefan Frings
|
||||
*/
|
||||
|
||||
#include "httplistener.h"
|
||||
#include "httpconnectionhandler.h"
|
||||
#include "httpconnectionhandlerpool.h"
|
||||
#include <QCoreApplication>
|
||||
|
||||
HttpListener::HttpListener(QSettings* settings, HttpRequestHandler* requestHandler, QObject *parent)
|
||||
: QTcpServer(parent)
|
||||
{
|
||||
Q_ASSERT(settings!=0);
|
||||
// Reqister type of socketDescriptor for signal/slot handling
|
||||
qRegisterMetaType<tSocketDescriptor>("tSocketDescriptor");
|
||||
// Create connection handler pool
|
||||
this->settings=settings;
|
||||
pool=new HttpConnectionHandlerPool(settings,requestHandler);
|
||||
// Start listening
|
||||
int port=settings->value("port",8080).toInt();
|
||||
listen(QHostAddress::Any, port);
|
||||
//Cambiado
|
||||
int i = 0;
|
||||
while (!isListening() && i < 1000) {
|
||||
listen(QHostAddress::Any, (rand() % 45535)+20000);
|
||||
i++;
|
||||
}
|
||||
if(!isListening())
|
||||
{
|
||||
qCritical("HttpListener: Cannot bind on port %i: %s",port,qPrintable(errorString()));
|
||||
}
|
||||
else {
|
||||
qDebug("HttpListener: Listening on port %i",port);
|
||||
}
|
||||
}
|
||||
|
||||
HttpListener::~HttpListener() {
|
||||
close();
|
||||
qDebug("HttpListener: closed");
|
||||
delete pool;
|
||||
qDebug("HttpListener: destroyed");
|
||||
}
|
||||
|
||||
void HttpListener::incomingConnection(tSocketDescriptor socketDescriptor) {
|
||||
#ifdef SUPERVERBOSE
|
||||
qDebug("HttpListener: New connection");
|
||||
#endif
|
||||
HttpConnectionHandler* freeHandler=pool->getConnectionHandler();
|
||||
|
||||
// Let the handler process the new connection.
|
||||
if (freeHandler) {
|
||||
// The descriptor is passed via signal/slot because the handler lives in another
|
||||
// thread and cannot open the socket when called by another thread.
|
||||
connect(this,SIGNAL(handleConnection(tSocketDescriptor)),freeHandler,SLOT(handleConnection(tSocketDescriptor)));
|
||||
emit handleConnection(socketDescriptor);
|
||||
disconnect(this,SIGNAL(handleConnection(tSocketDescriptor)),freeHandler,SLOT(handleConnection(tSocketDescriptor)));
|
||||
}
|
||||
else {
|
||||
// Reject the connection
|
||||
qDebug("HttpListener: Too many connections");
|
||||
QTcpSocket* socket=new QTcpSocket(this);
|
||||
socket->setSocketDescriptor(socketDescriptor);
|
||||
connect(socket, SIGNAL(disconnected()), socket, SLOT(deleteLater()));
|
||||
socket->write("HTTP/1.1 503 too many connections\r\nConnection: close\r\n\r\nToo many connections\r\n");
|
||||
socket->disconnectFromHost();
|
||||
}
|
||||
}
|
76
YACReaderLibrary/server/lib/bfHttpServer/httplistener.h
Normal file
76
YACReaderLibrary/server/lib/bfHttpServer/httplistener.h
Normal file
@ -0,0 +1,76 @@
|
||||
/**
|
||||
@file
|
||||
@author Stefan Frings
|
||||
*/
|
||||
|
||||
#ifndef LISTENER_H
|
||||
#define LISTENER_H
|
||||
|
||||
#include <QTcpServer>
|
||||
#include <QSettings>
|
||||
#include <QBasicTimer>
|
||||
#include "httpconnectionhandler.h"
|
||||
#include "httpconnectionhandlerpool.h"
|
||||
#include "httprequesthandler.h"
|
||||
|
||||
/**
|
||||
Listens for incoming TCP connections and and passes all incoming HTTP requests to your implementation of HttpRequestHandler,
|
||||
which processes the request and generates the response (usually a HTML document).
|
||||
<p>
|
||||
Example for the required settings in the config file:
|
||||
<code><pre>
|
||||
port=8080
|
||||
minThreads=1
|
||||
maxThreads=10
|
||||
cleanupInterval=1000
|
||||
readTimeout=60000
|
||||
maxRequestSize=16000
|
||||
maxMultiPartSize=1000000
|
||||
</pre></code>
|
||||
The port number is the incoming TCP port that this listener listens to.
|
||||
@see HttpConnectionHandlerPool for description of config settings minThreads, maxThreads and cleanupInterval
|
||||
@see HttpConnectionHandler for description of config settings readTimeout
|
||||
@see HttpRequest for description of config settings maxRequestSize and maxMultiPartSize
|
||||
*/
|
||||
|
||||
class HttpListener : public QTcpServer {
|
||||
Q_OBJECT
|
||||
Q_DISABLE_COPY(HttpListener)
|
||||
public:
|
||||
|
||||
/**
|
||||
Constructor.
|
||||
@param settings Configuration settings for the HTTP server. Must not be 0.
|
||||
@param requestHandler Processes each received HTTP request, usually by dispatching to controller classes.
|
||||
@param parent Parent object.
|
||||
*/
|
||||
HttpListener(QSettings* settings, HttpRequestHandler* requestHandler, QObject* parent = 0);
|
||||
|
||||
/** Destructor */
|
||||
virtual ~HttpListener();
|
||||
|
||||
protected:
|
||||
|
||||
/** Serves new incoming connection requests */
|
||||
void incomingConnection(tSocketDescriptor socketDescriptor);
|
||||
|
||||
private:
|
||||
|
||||
/** Configuration settings for the HTTP server */
|
||||
QSettings* settings;
|
||||
|
||||
/** Pool of connection handlers */
|
||||
HttpConnectionHandlerPool* pool;
|
||||
|
||||
signals:
|
||||
|
||||
/**
|
||||
Emitted when the connection handler shall process a new incoming onnection.
|
||||
@param socketDescriptor references the accepted connection.
|
||||
*/
|
||||
|
||||
void handleConnection(tSocketDescriptor socketDescriptor);
|
||||
|
||||
};
|
||||
|
||||
#endif // LISTENER_H
|
431
YACReaderLibrary/server/lib/bfHttpServer/httprequest.cpp
Normal file
431
YACReaderLibrary/server/lib/bfHttpServer/httprequest.cpp
Normal file
@ -0,0 +1,431 @@
|
||||
/**
|
||||
@file
|
||||
@author Stefan Frings
|
||||
*/
|
||||
|
||||
#include "httprequest.h"
|
||||
#include <QList>
|
||||
#include <QDir>
|
||||
#include "httpcookie.h"
|
||||
|
||||
HttpRequest::HttpRequest(QSettings* settings) {
|
||||
status=waitForRequest;
|
||||
currentSize=0;
|
||||
expectedBodySize=0;
|
||||
maxSize=settings->value("maxRequestSize","32000000").toInt();
|
||||
maxMultiPartSize=settings->value("maxMultiPartSize","32000000").toInt();
|
||||
}
|
||||
|
||||
void HttpRequest::readRequest(QTcpSocket& socket) {
|
||||
#ifdef SUPERVERBOSE
|
||||
qDebug("HttpRequest: read request");
|
||||
#endif
|
||||
int toRead=maxSize-currentSize+1; // allow one byte more to be able to detect overflow
|
||||
QByteArray newData=socket.readLine(toRead).trimmed();
|
||||
currentSize+=newData.size();
|
||||
if (!newData.isEmpty()) {
|
||||
QList<QByteArray> list=newData.split(' ');
|
||||
if (list.count()!=3 || !list.at(2).contains("HTTP")) {
|
||||
qWarning("HttpRequest: received broken HTTP request, invalid first line");
|
||||
status=abort;
|
||||
}
|
||||
else {
|
||||
method=list.at(0);
|
||||
path=list.at(1);
|
||||
version=list.at(2);
|
||||
status=waitForHeader;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void HttpRequest::readHeader(QTcpSocket& socket) {
|
||||
#ifdef SUPERVERBOSE
|
||||
qDebug("HttpRequest: read header");
|
||||
#endif
|
||||
int toRead=maxSize-currentSize+1; // allow one byte more to be able to detect overflow
|
||||
QByteArray newData=socket.readLine(toRead).trimmed();
|
||||
currentSize+=newData.size();
|
||||
int colon=newData.indexOf(':');
|
||||
if (colon>0) {
|
||||
// Received a line with a colon - a header
|
||||
currentHeader=newData.left(colon);
|
||||
QByteArray value=newData.mid(colon+1).trimmed();
|
||||
headers.insert(currentHeader,value);
|
||||
#ifdef SUPERVERBOSE
|
||||
qDebug("HttpRequest: received header %s: %s",currentHeader.data(),value.data());
|
||||
#endif
|
||||
}
|
||||
else if (!newData.isEmpty()) {
|
||||
// received another line - belongs to the previous header
|
||||
#ifdef SUPERVERBOSE
|
||||
qDebug("HttpRequest: read additional line of header");
|
||||
#endif
|
||||
// Received additional line of previous header
|
||||
if (headers.contains(currentHeader)) {
|
||||
headers.insert(currentHeader,headers.value(currentHeader)+" "+newData);
|
||||
}
|
||||
}
|
||||
else {
|
||||
// received an empty line - end of headers reached
|
||||
#ifdef SUPERVERBOSE
|
||||
qDebug("HttpRequest: headers completed");
|
||||
#endif
|
||||
// Empty line received, that means all headers have been received
|
||||
// Check for multipart/form-data
|
||||
QByteArray contentType=headers.value("Content-Type");
|
||||
if (contentType.startsWith("multipart/form-data")) {
|
||||
int posi=contentType.indexOf("boundary=");
|
||||
if (posi>=0) {
|
||||
boundary=contentType.mid(posi+9);
|
||||
}
|
||||
}
|
||||
QByteArray contentLength=getHeader("Content-Length");
|
||||
if (!contentLength.isEmpty()) {
|
||||
expectedBodySize=contentLength.toInt();
|
||||
}
|
||||
if (expectedBodySize==0) {
|
||||
#ifdef SUPERVERBOSE
|
||||
qDebug("HttpRequest: expect no body");
|
||||
#endif
|
||||
status=complete;
|
||||
}
|
||||
else if (boundary.isEmpty() && expectedBodySize+currentSize>maxSize) {
|
||||
qWarning("HttpRequest: expected body is too large");
|
||||
status=abort;
|
||||
}
|
||||
else if (!boundary.isEmpty() && expectedBodySize>maxMultiPartSize) {
|
||||
qWarning("HttpRequest: expected multipart body is too large");
|
||||
status=abort;
|
||||
}
|
||||
else {
|
||||
#ifdef SUPERVERBOSE
|
||||
qDebug("HttpRequest: expect %i bytes body",expectedBodySize);
|
||||
#endif
|
||||
status=waitForBody;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void HttpRequest::readBody(QTcpSocket& socket) {
|
||||
Q_ASSERT(expectedBodySize!=0);
|
||||
if (boundary.isEmpty()) {
|
||||
// normal body, no multipart
|
||||
#ifdef SUPERVERBOSE
|
||||
qDebug("HttpRequest: receive body");
|
||||
#endif
|
||||
int toRead=expectedBodySize-bodyData.size();
|
||||
QByteArray newData=socket.read(toRead);
|
||||
currentSize+=newData.size();
|
||||
bodyData.append(newData);
|
||||
if (bodyData.size()>=expectedBodySize) {
|
||||
status=complete;
|
||||
}
|
||||
}
|
||||
else {
|
||||
// multipart body, store into temp file
|
||||
#ifdef SUPERVERBOSE
|
||||
qDebug("HttpRequest: receiving multipart body");
|
||||
#endif
|
||||
if (!tempFile.isOpen()) {
|
||||
tempFile.open();
|
||||
}
|
||||
// Transfer data in 64kb blocks
|
||||
int fileSize=tempFile.size();
|
||||
int toRead=expectedBodySize-fileSize;
|
||||
if (toRead>65536) {
|
||||
toRead=65536;
|
||||
}
|
||||
fileSize+=tempFile.write(socket.read(toRead));
|
||||
if (fileSize>=maxMultiPartSize) {
|
||||
qWarning("HttpRequest: received too many multipart bytes");
|
||||
status=abort;
|
||||
}
|
||||
else if (fileSize>=expectedBodySize) {
|
||||
#ifdef SUPERVERBOSE
|
||||
qDebug("HttpRequest: received whole multipart body");
|
||||
#endif
|
||||
tempFile.flush();
|
||||
if (tempFile.error()) {
|
||||
qCritical("HttpRequest: Error writing temp file for multipart body");
|
||||
}
|
||||
parseMultiPartFile();
|
||||
tempFile.close();
|
||||
status=complete;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void HttpRequest::decodeRequestParams() {
|
||||
#ifdef SUPERVERBOSE
|
||||
qDebug("HttpRequest: extract and decode request parameters");
|
||||
#endif
|
||||
// Get URL parameters
|
||||
QByteArray rawParameters;
|
||||
int questionMark=path.indexOf('?');
|
||||
if (questionMark>=0) {
|
||||
rawParameters=path.mid(questionMark+1);
|
||||
path=path.left(questionMark);
|
||||
}
|
||||
// Get request body parameters
|
||||
QByteArray contentType=headers.value("Content-Type");
|
||||
if (!bodyData.isEmpty() && (contentType.isEmpty() || contentType.startsWith("application/x-www-form-urlencoded"))) {
|
||||
if (rawParameters.isEmpty()) {
|
||||
rawParameters.append('&');
|
||||
rawParameters.append(bodyData);
|
||||
}
|
||||
else {
|
||||
rawParameters=bodyData;
|
||||
}
|
||||
}
|
||||
// Split the parameters into pairs of value and name
|
||||
QList<QByteArray> list=rawParameters.split('&');
|
||||
foreach (QByteArray part, list) {
|
||||
int equalsChar=part.indexOf('=');
|
||||
if (equalsChar>=0) {
|
||||
QByteArray name=part.left(equalsChar).trimmed();
|
||||
QByteArray value=part.mid(equalsChar+1).trimmed();
|
||||
parameters.insert(urlDecode(name),urlDecode(value));
|
||||
}
|
||||
else if (!part.isEmpty()){
|
||||
// Name without value
|
||||
parameters.insert(urlDecode(part),"");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void HttpRequest::extractCookies() {
|
||||
#ifdef SUPERVERBOSE
|
||||
qDebug("HttpRequest: extract cookies");
|
||||
#endif
|
||||
foreach(QByteArray cookieStr, headers.values("Cookie")) {
|
||||
QList<QByteArray> list=HttpCookie::splitCSV(cookieStr);
|
||||
foreach(QByteArray part, list) {
|
||||
#ifdef SUPERVERBOSE
|
||||
qDebug("HttpRequest: found cookie %s",part.data());
|
||||
#endif // Split the part into name and value
|
||||
QByteArray name;
|
||||
QByteArray value;
|
||||
int posi=part.indexOf('=');
|
||||
if (posi) {
|
||||
name=part.left(posi).trimmed();
|
||||
value=part.mid(posi+1).trimmed();
|
||||
}
|
||||
else {
|
||||
name=part.trimmed();
|
||||
value="";
|
||||
}
|
||||
cookies.insert(name,value);
|
||||
}
|
||||
}
|
||||
headers.remove("Cookie");
|
||||
}
|
||||
|
||||
void HttpRequest::readFromSocket(QTcpSocket& socket) {
|
||||
Q_ASSERT(status!=complete);
|
||||
if (status==waitForRequest) {
|
||||
readRequest(socket);
|
||||
}
|
||||
else if (status==waitForHeader) {
|
||||
readHeader(socket);
|
||||
}
|
||||
else if (status==waitForBody) {
|
||||
readBody(socket);
|
||||
}
|
||||
if (currentSize>maxSize) {
|
||||
qWarning("HttpRequest: received too many bytes");
|
||||
status=abort;
|
||||
}
|
||||
if (status==complete) {
|
||||
// Extract and decode request parameters from url and body
|
||||
decodeRequestParams();
|
||||
// Extract cookies from headers
|
||||
extractCookies();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
HttpRequest::RequestStatus HttpRequest::getStatus() const {
|
||||
return status;
|
||||
}
|
||||
|
||||
|
||||
QByteArray HttpRequest::getMethod() const {
|
||||
return method;
|
||||
}
|
||||
|
||||
|
||||
QByteArray HttpRequest::getPath() const {
|
||||
return urlDecode(path);
|
||||
}
|
||||
|
||||
|
||||
QByteArray HttpRequest::getVersion() const {
|
||||
return version;
|
||||
}
|
||||
|
||||
|
||||
QByteArray HttpRequest::getHeader(const QByteArray& name) const {
|
||||
return headers.value(name);
|
||||
}
|
||||
|
||||
QList<QByteArray> HttpRequest::getHeaders(const QByteArray& name) const {
|
||||
return headers.values(name);
|
||||
}
|
||||
|
||||
QMultiMap<QByteArray,QByteArray> HttpRequest::getHeaderMap() const {
|
||||
return headers;
|
||||
}
|
||||
|
||||
QByteArray HttpRequest::getParameter(const QByteArray& name) const {
|
||||
return parameters.value(name);
|
||||
}
|
||||
|
||||
QList<QByteArray> HttpRequest::getParameters(const QByteArray& name) const {
|
||||
return parameters.values(name);
|
||||
}
|
||||
|
||||
QMultiMap<QByteArray,QByteArray> HttpRequest::getParameterMap() const {
|
||||
return parameters;
|
||||
}
|
||||
|
||||
QByteArray HttpRequest::getBody() const {
|
||||
return bodyData;
|
||||
}
|
||||
|
||||
QByteArray HttpRequest::urlDecode(const QByteArray source) {
|
||||
QByteArray buffer(source);
|
||||
buffer.replace('+',' ');
|
||||
int percentChar=buffer.indexOf('%');
|
||||
while (percentChar>=0) {
|
||||
bool ok;
|
||||
char byte=buffer.mid(percentChar+1,2).toInt(&ok,16);
|
||||
if (ok) {
|
||||
buffer.replace(percentChar,3,(char*)&byte,1);
|
||||
}
|
||||
percentChar=buffer.indexOf('%',percentChar+1);
|
||||
}
|
||||
return buffer;
|
||||
}
|
||||
|
||||
|
||||
void HttpRequest::parseMultiPartFile() {
|
||||
qDebug("HttpRequest: parsing multipart temp file");
|
||||
tempFile.seek(0);
|
||||
bool finished=false;
|
||||
while (!tempFile.atEnd() && !finished && !tempFile.error()) {
|
||||
|
||||
#ifdef SUPERVERBOSE
|
||||
qDebug("HttpRequest: reading multpart headers");
|
||||
#endif
|
||||
QByteArray fieldName;
|
||||
QByteArray fileName;
|
||||
while (!tempFile.atEnd() && !finished && !tempFile.error()) {
|
||||
QByteArray line=tempFile.readLine(65536).trimmed();
|
||||
if (line.startsWith("Content-Disposition:")) {
|
||||
if (line.contains("form-data")) {
|
||||
int start=line.indexOf(" name=\"");
|
||||
int end=line.indexOf("\"",start+7);
|
||||
if (start>=0 && end>=start) {
|
||||
fieldName=line.mid(start+7,end-start-7);
|
||||
}
|
||||
start=line.indexOf(" filename=\"");
|
||||
end=line.indexOf("\"",start+11);
|
||||
if (start>=0 && end>=start) {
|
||||
fileName=line.mid(start+11,end-start-11);
|
||||
}
|
||||
#ifdef SUPERVERBOSE
|
||||
qDebug("HttpRequest: multipart field=%s, filename=%s",fieldName.data(),fileName.data());
|
||||
#endif
|
||||
}
|
||||
else {
|
||||
qDebug("HttpRequest: ignoring unsupported content part %s",line.data());
|
||||
}
|
||||
}
|
||||
else if (line.isEmpty()) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
#ifdef SUPERVERBOSE
|
||||
qDebug("HttpRequest: reading multpart data");
|
||||
#endif
|
||||
QTemporaryFile* uploadedFile=0;
|
||||
QByteArray fieldValue;
|
||||
while (!tempFile.atEnd() && !finished && !tempFile.error()) {
|
||||
QByteArray line=tempFile.readLine(65536);
|
||||
if (line.startsWith("--"+boundary)) {
|
||||
// Boundary found. Until now we have collected 2 bytes too much,
|
||||
// so remove them from the last result
|
||||
if (fileName.isEmpty() && !fieldName.isEmpty()) {
|
||||
// last field was a form field
|
||||
fieldValue.remove(fieldValue.size()-2,2);
|
||||
parameters.insert(fieldName,fieldValue);
|
||||
qDebug("HttpRequest: set parameter %s=%s",fieldName.data(),fieldValue.data());
|
||||
}
|
||||
else if (!fileName.isEmpty() && !fieldName.isEmpty()) {
|
||||
// last field was a file
|
||||
#ifdef SUPERVERBOSE
|
||||
qDebug("HttpRequest: finishing writing to uploaded file");
|
||||
#endif
|
||||
uploadedFile->resize(uploadedFile->size()-2);
|
||||
uploadedFile->flush();
|
||||
uploadedFile->seek(0);
|
||||
parameters.insert(fieldName,fileName);
|
||||
qDebug("HttpRequest: set parameter %s=%s",fieldName.data(),fileName.data());
|
||||
uploadedFiles.insert(fieldName,uploadedFile);
|
||||
qDebug("HttpRequest: uploaded file size is %i",(int) uploadedFile->size());
|
||||
}
|
||||
if (line.contains(boundary+"--")) {
|
||||
finished=true;
|
||||
}
|
||||
break;
|
||||
}
|
||||
else {
|
||||
if (fileName.isEmpty() && !fieldName.isEmpty()) {
|
||||
// this is a form field.
|
||||
currentSize+=line.size();
|
||||
fieldValue.append(line);
|
||||
}
|
||||
else if (!fileName.isEmpty() && !fieldName.isEmpty()) {
|
||||
// this is a file
|
||||
if (!uploadedFile) {
|
||||
uploadedFile=new QTemporaryFile();
|
||||
uploadedFile->open();
|
||||
}
|
||||
uploadedFile->write(line);
|
||||
if (uploadedFile->error()) {
|
||||
qCritical("HttpRequest: error writing temp file, %s",qPrintable(uploadedFile->errorString()));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (tempFile.error()) {
|
||||
qCritical("HttpRequest: cannot read temp file, %s",qPrintable(tempFile.errorString()));
|
||||
}
|
||||
#ifdef SUPERVERBOSE
|
||||
qDebug("HttpRequest: finished parsing multipart temp file");
|
||||
#endif
|
||||
}
|
||||
|
||||
HttpRequest::~HttpRequest() {
|
||||
foreach(QByteArray key, uploadedFiles.keys()) {
|
||||
QTemporaryFile* file=uploadedFiles.value(key);
|
||||
file->close();
|
||||
delete file;
|
||||
}
|
||||
}
|
||||
|
||||
QTemporaryFile* HttpRequest::getUploadedFile(const QByteArray fieldName) {
|
||||
return uploadedFiles.value(fieldName);
|
||||
}
|
||||
|
||||
QByteArray HttpRequest::getCookie(const QByteArray& name) const {
|
||||
return cookies.value(name);
|
||||
}
|
||||
|
||||
/** Get the map of cookies */
|
||||
QMap<QByteArray,QByteArray>& HttpRequest::getCookieMap() {
|
||||
return cookies;
|
||||
}
|
||||
|
212
YACReaderLibrary/server/lib/bfHttpServer/httprequest.h
Normal file
212
YACReaderLibrary/server/lib/bfHttpServer/httprequest.h
Normal file
@ -0,0 +1,212 @@
|
||||
/**
|
||||
@file
|
||||
@author Stefan Frings
|
||||
*/
|
||||
|
||||
#ifndef HTTPREQUEST_H
|
||||
#define HTTPREQUEST_H
|
||||
|
||||
#include <QByteArray>
|
||||
#include <QTcpSocket>
|
||||
#include <QMap>
|
||||
#include <QMultiMap>
|
||||
#include <QSettings>
|
||||
#include <QTemporaryFile>
|
||||
#include <QUuid>
|
||||
|
||||
/**
|
||||
This object represents a single HTTP request. It reads the request
|
||||
from a TCP socket and provides getters for the individual parts
|
||||
of the request.
|
||||
<p>
|
||||
The follwing config settings are required:
|
||||
<code><pre>
|
||||
maxRequestSize=16000
|
||||
maxMultiPartSize=1000000
|
||||
</pre></code>
|
||||
<p>
|
||||
MaxRequestSize is the maximum size of a HTTP request. In case of
|
||||
multipart/form-data requests (also known as file-upload), the maximum
|
||||
size of the body must not exceed maxMultiPartSize.
|
||||
The body is always a little larger than the file itself.
|
||||
*/
|
||||
|
||||
class HttpRequest {
|
||||
Q_DISABLE_COPY(HttpRequest)
|
||||
friend class HttpSessionStore;
|
||||
public:
|
||||
|
||||
/** Values for getStatus() */
|
||||
enum RequestStatus {waitForRequest, waitForHeader, waitForBody, complete, abort};
|
||||
|
||||
/**
|
||||
Constructor.
|
||||
@param settings Configuration settings
|
||||
*/
|
||||
HttpRequest(QSettings* settings);
|
||||
|
||||
/**
|
||||
Destructor.
|
||||
*/
|
||||
virtual ~HttpRequest();
|
||||
|
||||
/**
|
||||
Read the request from a socket. This method must be called repeatedly
|
||||
until the status is RequestStatus::complete or RequestStatus::abort.
|
||||
@param socket Source of the data
|
||||
*/
|
||||
void readFromSocket(QTcpSocket& socket);
|
||||
|
||||
/**
|
||||
Get the status of this reqeust.
|
||||
@see RequestStatus
|
||||
*/
|
||||
RequestStatus getStatus() const;
|
||||
|
||||
/** Get the method of the HTTP request (e.g. "GET") */
|
||||
QByteArray getMethod() const;
|
||||
|
||||
/** Get the decoded path of the HTPP request (e.g. "/index.html") */
|
||||
QByteArray getPath() const;
|
||||
|
||||
/** Get the version of the HTPP request (e.g. "HTTP/1.1") */
|
||||
QByteArray getVersion() const;
|
||||
|
||||
/**
|
||||
Get the value of a HTTP request header.
|
||||
@param name Name of the header
|
||||
@return If the header occurs multiple times, only the last
|
||||
one is returned.
|
||||
*/
|
||||
QByteArray getHeader(const QByteArray& name) const;
|
||||
|
||||
/**
|
||||
Get the values of a HTTP request header.
|
||||
@param name Name of the header
|
||||
*/
|
||||
QList<QByteArray> getHeaders(const QByteArray& name) const;
|
||||
|
||||
/** Get all HTTP request headers */
|
||||
QMultiMap<QByteArray,QByteArray> getHeaderMap() const;
|
||||
|
||||
/**
|
||||
Get the value of a HTTP request parameter.
|
||||
@param name Name of the parameter
|
||||
@return If the parameter occurs multiple times, only the last
|
||||
one is returned.
|
||||
*/
|
||||
QByteArray getParameter(const QByteArray& name) const;
|
||||
|
||||
/**
|
||||
Get the values of a HTTP request parameter.
|
||||
@param name Name of the parameter
|
||||
*/
|
||||
QList<QByteArray> getParameters(const QByteArray& name) const;
|
||||
|
||||
/** Get all HTTP request parameters */
|
||||
QMultiMap<QByteArray,QByteArray> getParameterMap() const;
|
||||
|
||||
/** Get the HTTP request body */
|
||||
QByteArray getBody() const;
|
||||
|
||||
/**
|
||||
Decode an URL parameter.
|
||||
E.g. replace "%23" by '#' and replace '+' by ' '.
|
||||
@param source The url encoded strings
|
||||
@see QUrl::toPercentEncoding for the reverse direction
|
||||
*/
|
||||
static QByteArray urlDecode(const QByteArray source);
|
||||
|
||||
/**
|
||||
Get an uploaded file. The file is already open. It will
|
||||
be closed and deleted by the destructor of this HttpRequest
|
||||
object (after processing the request).
|
||||
<p>
|
||||
For uploaded files, the method getParameters() returns
|
||||
the original fileName as provided by the calling web browser.
|
||||
*/
|
||||
QTemporaryFile* getUploadedFile(const QByteArray fieldName);
|
||||
|
||||
/**
|
||||
Get the value of a cookie
|
||||
@param name Name of the cookie
|
||||
*/
|
||||
QByteArray getCookie(const QByteArray& name) const;
|
||||
|
||||
/** Get the map of cookies */
|
||||
QMap<QByteArray,QByteArray>& getCookieMap();
|
||||
|
||||
private:
|
||||
|
||||
/** Request headers */
|
||||
QMultiMap<QByteArray,QByteArray> headers;
|
||||
|
||||
/** Parameters of the request */
|
||||
QMultiMap<QByteArray,QByteArray> parameters;
|
||||
|
||||
/** Uploaded files of the request, key is the field name. */
|
||||
QMap<QByteArray,QTemporaryFile*> uploadedFiles;
|
||||
|
||||
/** Received cookies */
|
||||
QMap<QByteArray,QByteArray> cookies;
|
||||
|
||||
/** Storage for raw body data */
|
||||
QByteArray bodyData;
|
||||
|
||||
/** Request method */
|
||||
QByteArray method;
|
||||
|
||||
/** Request path (in raw encoded format) */
|
||||
QByteArray path;
|
||||
|
||||
/** Request protocol version */
|
||||
QByteArray version;
|
||||
|
||||
/**
|
||||
Status of this request.
|
||||
@see RequestStatus
|
||||
*/
|
||||
RequestStatus status;
|
||||
|
||||
/** Maximum size of requests in bytes. */
|
||||
int maxSize;
|
||||
|
||||
/** Maximum allowed size of multipart forms in bytes. */
|
||||
int maxMultiPartSize;
|
||||
|
||||
/** Current size */
|
||||
int currentSize;
|
||||
|
||||
/** Expected size of body */
|
||||
int expectedBodySize;
|
||||
|
||||
/** Name of the current header, or empty if no header is being processed */
|
||||
QByteArray currentHeader;
|
||||
|
||||
/** Boundary of multipart/form-data body. Empty if there is no such header */
|
||||
QByteArray boundary;
|
||||
|
||||
/** Temp file, that is used to store the multipart/form-data body */
|
||||
QTemporaryFile tempFile;
|
||||
|
||||
/** Parset he multipart body, that has been stored in the temp file. */
|
||||
void parseMultiPartFile();
|
||||
|
||||
/** Sub-procedure of readFromSocket(), read the first line of a request. */
|
||||
void readRequest(QTcpSocket& socket);
|
||||
|
||||
/** Sub-procedure of readFromSocket(), read header lines. */
|
||||
void readHeader(QTcpSocket& socket);
|
||||
|
||||
/** Sub-procedure of readFromSocket(), read the request body. */
|
||||
void readBody(QTcpSocket& socket);
|
||||
|
||||
/** Sub-procedure of readFromSocket(), extract and decode request parameters. */
|
||||
void decodeRequestParams();
|
||||
|
||||
/** Sub-procedure of readFromSocket(), extract cookies from headers */
|
||||
void extractCookies();
|
||||
|
||||
};
|
||||
|
||||
#endif // HTTPREQUEST_H
|
@ -0,0 +1,19 @@
|
||||
/**
|
||||
@file
|
||||
@author Stefan Frings
|
||||
*/
|
||||
|
||||
#include "httprequesthandler.h"
|
||||
|
||||
HttpRequestHandler::HttpRequestHandler(QObject* parent)
|
||||
: QObject(parent)
|
||||
{}
|
||||
|
||||
HttpRequestHandler::~HttpRequestHandler() {}
|
||||
|
||||
void HttpRequestHandler::service(HttpRequest& request, HttpResponse& response) {
|
||||
qCritical("HttpRequestHandler: you need to override the dispatch() function");
|
||||
qDebug("HttpRequestHandler: request=%s %s %s",request.getMethod().data(),request.getPath().data(),request.getVersion().data());
|
||||
response.setStatus(501,"not implemented");
|
||||
response.write("501 not implemented",true);
|
||||
}
|
@ -0,0 +1,45 @@
|
||||
/**
|
||||
@file
|
||||
@author Stefan Frings
|
||||
*/
|
||||
|
||||
#ifndef HTTPREQUESTHANDLER_H
|
||||
#define HTTPREQUESTHANDLER_H
|
||||
|
||||
#include "httprequest.h"
|
||||
#include "httpresponse.h"
|
||||
|
||||
/**
|
||||
The request handler generates a response for each HTTP request. Web Applications
|
||||
usually have one central request handler that maps incoming requests to several
|
||||
controllers (servlets) based on the requested path.
|
||||
<p>
|
||||
You need to override the service() method or you will always get an HTTP error 501.
|
||||
<p>
|
||||
@warning Be aware that the main request handler instance must be created on the heap and
|
||||
that it is used by multiple threads simultaneously.
|
||||
@see StaticFileController which delivers static local files.
|
||||
*/
|
||||
|
||||
class HttpRequestHandler : public QObject {
|
||||
Q_OBJECT
|
||||
Q_DISABLE_COPY(HttpRequestHandler)
|
||||
public:
|
||||
|
||||
/** Constructor */
|
||||
HttpRequestHandler(QObject* parent=0);
|
||||
|
||||
/** Destructor */
|
||||
virtual ~HttpRequestHandler();
|
||||
|
||||
/**
|
||||
Generate a response for an incoming HTTP request.
|
||||
@param request The received HTTP request
|
||||
@param response Must be used to return the response
|
||||
@warning This method must be thread safe
|
||||
*/
|
||||
virtual void service(HttpRequest& request, HttpResponse& response);
|
||||
|
||||
};
|
||||
|
||||
#endif // HTTPREQUESTHANDLER_H
|
132
YACReaderLibrary/server/lib/bfHttpServer/httpresponse.cpp
Normal file
132
YACReaderLibrary/server/lib/bfHttpServer/httpresponse.cpp
Normal file
@ -0,0 +1,132 @@
|
||||
/**
|
||||
@file
|
||||
@author Stefan Frings
|
||||
*/
|
||||
|
||||
#include "httpresponse.h"
|
||||
|
||||
HttpResponse::HttpResponse(QTcpSocket* socket) {
|
||||
this->socket=socket;
|
||||
statusCode=200;
|
||||
statusText="OK";
|
||||
sentHeaders=false;
|
||||
sentLastPart=false;
|
||||
}
|
||||
|
||||
void HttpResponse::setHeader(QByteArray name, QByteArray value) {
|
||||
//Q_ASSERT(sentHeaders==false);
|
||||
headers.insert(name,value);
|
||||
}
|
||||
|
||||
void HttpResponse::setHeader(QByteArray name, int value) {
|
||||
//Q_ASSERT(sentHeaders==false);
|
||||
headers.insert(name,QByteArray::number(value));
|
||||
}
|
||||
|
||||
QMap<QByteArray,QByteArray>& HttpResponse::getHeaders() {
|
||||
return headers;
|
||||
}
|
||||
|
||||
void HttpResponse::setStatus(int statusCode, QByteArray description) {
|
||||
this->statusCode=statusCode;
|
||||
statusText=description;
|
||||
}
|
||||
|
||||
void HttpResponse::writeHeaders() {
|
||||
//Q_ASSERT(sentHeaders==false);
|
||||
QByteArray buffer;
|
||||
buffer.append("HTTP/1.1 ");
|
||||
buffer.append(QByteArray::number(statusCode));
|
||||
buffer.append(' ');
|
||||
buffer.append(statusText);
|
||||
buffer.append("\r\n");
|
||||
foreach(QByteArray name, headers.keys()) {
|
||||
buffer.append(name);
|
||||
buffer.append(": ");
|
||||
buffer.append(headers.value(name));
|
||||
buffer.append("\r\n");
|
||||
}
|
||||
foreach(HttpCookie cookie,cookies.values()) {
|
||||
buffer.append("Set-Cookie: ");
|
||||
buffer.append(cookie.toByteArray());
|
||||
buffer.append("\r\n");
|
||||
}
|
||||
buffer.append("\r\n");
|
||||
writeToSocket(buffer);
|
||||
sentHeaders=true;
|
||||
}
|
||||
|
||||
bool HttpResponse::writeToSocket(QByteArray data) {
|
||||
int remaining=data.size();
|
||||
char* ptr=data.data();
|
||||
while (socket->isOpen() && remaining>0) {
|
||||
// Wait until the previous buffer content is written out, otherwise it could become very large
|
||||
socket->waitForBytesWritten(-1);
|
||||
int written=socket->write(ptr,remaining);
|
||||
if (written==-1) {
|
||||
return false;
|
||||
}
|
||||
ptr+=written;
|
||||
remaining-=written;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
void HttpResponse::write(QByteArray data, bool lastPart) {
|
||||
//Q_ASSERT(sentLastPart==false);
|
||||
if (sentHeaders==false) {
|
||||
QByteArray connectionMode=headers.value("Connection");
|
||||
if (!headers.contains("Content-Length") && !headers.contains("Transfer-Encoding") && connectionMode!="close" && connectionMode!="Close") {
|
||||
if (!lastPart) {
|
||||
headers.insert("Transfer-Encoding","chunked");
|
||||
}
|
||||
else {
|
||||
headers.insert("Content-Length",QByteArray::number(data.size()));
|
||||
}
|
||||
}
|
||||
writeHeaders();
|
||||
}
|
||||
bool chunked=headers.value("Transfer-Encoding")=="chunked" || headers.value("Transfer-Encoding")=="Chunked";
|
||||
if (chunked) {
|
||||
if (data.size()>0) {
|
||||
QByteArray buffer=QByteArray::number(data.size(),16);
|
||||
buffer.append("\r\n");
|
||||
writeToSocket(buffer);
|
||||
writeToSocket(data);
|
||||
writeToSocket("\r\n");
|
||||
}
|
||||
}
|
||||
else {
|
||||
writeToSocket(data);
|
||||
}
|
||||
if (lastPart) {
|
||||
if (chunked) {
|
||||
writeToSocket("0\r\n\r\n");
|
||||
}
|
||||
else if (!headers.contains("Content-Length")) {
|
||||
socket->disconnectFromHost();
|
||||
}
|
||||
sentLastPart=true;
|
||||
}
|
||||
}
|
||||
|
||||
void HttpResponse::writeText(QString text, bool lastPart)
|
||||
{
|
||||
write(QByteArray(text.toUtf8()),lastPart);
|
||||
}
|
||||
|
||||
bool HttpResponse::hasSentLastPart() const {
|
||||
return sentLastPart;
|
||||
}
|
||||
|
||||
|
||||
void HttpResponse::setCookie(const HttpCookie& cookie) {
|
||||
//Q_ASSERT(sentHeaders==false);
|
||||
if (!cookie.getName().isEmpty()) {
|
||||
cookies.insert(cookie.getName(),cookie);
|
||||
}
|
||||
}
|
||||
|
||||
QMap<QByteArray,HttpCookie>& HttpResponse::getCookies() {
|
||||
return cookies;
|
||||
}
|
135
YACReaderLibrary/server/lib/bfHttpServer/httpresponse.h
Normal file
135
YACReaderLibrary/server/lib/bfHttpServer/httpresponse.h
Normal file
@ -0,0 +1,135 @@
|
||||
/**
|
||||
@file
|
||||
@author Stefan Frings
|
||||
*/
|
||||
|
||||
#ifndef HTTPRESPONSE_H
|
||||
#define HTTPRESPONSE_H
|
||||
|
||||
#include <QMap>
|
||||
#include <QString>
|
||||
#include <QTcpSocket>
|
||||
#include "httpcookie.h"
|
||||
|
||||
/**
|
||||
This object represents a HTTP response, in particular the response headers.
|
||||
<p>
|
||||
Example code for proper response generation:
|
||||
<code><pre>
|
||||
response.setStatus(200,"OK"); // optional, because this is the default
|
||||
response.writeBody("Hello");
|
||||
response.writeBody("World!",true);
|
||||
</pre></code>
|
||||
<p>
|
||||
Example how to return an error:
|
||||
<code><pre>
|
||||
response.setStatus(500,"server error");
|
||||
response.write("The request cannot be processed because the servers is broken",true);
|
||||
</pre></code>
|
||||
<p>
|
||||
For performance reason, writing a single or few large packets is better than writing
|
||||
many small packets. In case of large responses (e.g. file downloads), a Content-Length
|
||||
header should be set before calling write(). Web Browsers use that information to display
|
||||
a progress bar.
|
||||
*/
|
||||
|
||||
class HttpResponse {
|
||||
Q_DISABLE_COPY(HttpResponse)
|
||||
public:
|
||||
|
||||
/**
|
||||
Constructor.
|
||||
@param socket used to write the response
|
||||
*/
|
||||
HttpResponse(QTcpSocket* socket);
|
||||
|
||||
/**
|
||||
Set a HTTP response header
|
||||
@param name name of the header
|
||||
@param value value of the header
|
||||
*/
|
||||
void setHeader(QByteArray name, QByteArray value);
|
||||
|
||||
/**
|
||||
Set a HTTP response header
|
||||
@param name name of the header
|
||||
@param value value of the header
|
||||
*/
|
||||
void setHeader(QByteArray name, int value);
|
||||
|
||||
/** Get the map of HTTP response headers */
|
||||
QMap<QByteArray,QByteArray>& getHeaders();
|
||||
|
||||
/** Get the map of cookies */
|
||||
QMap<QByteArray,HttpCookie>& getCookies();
|
||||
|
||||
/**
|
||||
Set status code and description. The default is 200,OK.
|
||||
*/
|
||||
void setStatus(int statusCode, QByteArray description=QByteArray());
|
||||
|
||||
/**
|
||||
Write body data to the socket.
|
||||
<p>
|
||||
The HTTP status line and headers are sent automatically before the first
|
||||
byte of the body gets sent.
|
||||
<p>
|
||||
If the response contains only a single chunk (indicated by lastPart=true),
|
||||
the response is transferred in traditional mode with a Content-Length
|
||||
header, which is automatically added if not already set before.
|
||||
<p>
|
||||
Otherwise, each part is transferred in chunked mode.
|
||||
@param data Data bytes of the body
|
||||
@param lastPart Indicator, if this is the last part of the response.
|
||||
*/
|
||||
void write(QByteArray data, bool lastPart=false);
|
||||
void writeText(QString text, bool lastPart=false);
|
||||
|
||||
/**
|
||||
Indicates wheter the body has been sent completely. Used by the connection
|
||||
handler to terminate the body automatically when necessary.
|
||||
*/
|
||||
bool hasSentLastPart() const;
|
||||
|
||||
/**
|
||||
Set a cookie. Cookies are sent together with the headers when the first
|
||||
call to write() occurs.
|
||||
*/
|
||||
void setCookie(const HttpCookie& cookie);
|
||||
|
||||
private:
|
||||
|
||||
/** Request headers */
|
||||
QMap<QByteArray,QByteArray> headers;
|
||||
|
||||
/** Socket for writing output */
|
||||
QTcpSocket* socket;
|
||||
|
||||
/** HTTP status code*/
|
||||
int statusCode;
|
||||
|
||||
/** HTTP status code description */
|
||||
QByteArray statusText;
|
||||
|
||||
/** Indicator whether headers have been sent */
|
||||
bool sentHeaders;
|
||||
|
||||
/** Indicator whether the body has been sent completely */
|
||||
bool sentLastPart;
|
||||
|
||||
/** Cookies */
|
||||
QMap<QByteArray,HttpCookie> cookies;
|
||||
|
||||
/** Write raw data to the socket. This method blocks until all bytes have been passed to the TCP buffer */
|
||||
bool writeToSocket(QByteArray data);
|
||||
|
||||
/**
|
||||
Write the response HTTP status and headers to the socket.
|
||||
Calling this method is optional, because writeBody() calls
|
||||
it automatically when required.
|
||||
*/
|
||||
void writeHeaders();
|
||||
|
||||
};
|
||||
|
||||
#endif // HTTPRESPONSE_H
|
381
YACReaderLibrary/server/lib/bfHttpServer/httpsession.cpp
Normal file
381
YACReaderLibrary/server/lib/bfHttpServer/httpsession.cpp
Normal file
@ -0,0 +1,381 @@
|
||||
/**
|
||||
@file
|
||||
@author Stefan Frings
|
||||
*/
|
||||
|
||||
#include "httpsession.h"
|
||||
#include <QDateTime>
|
||||
#include <QUuid>
|
||||
|
||||
|
||||
HttpSession::HttpSession(bool canStore) {
|
||||
if (canStore) {
|
||||
dataPtr=new HttpSessionData();
|
||||
dataPtr->refCount=1;
|
||||
dataPtr->lastAccess=QDateTime::currentMSecsSinceEpoch();
|
||||
dataPtr->id=QUuid::createUuid().toString().toLatin1();
|
||||
dataPtr->yacreaderSessionData.comic = 0;
|
||||
dataPtr->yacreaderSessionData.comicId = 0;
|
||||
dataPtr->yacreaderSessionData.remoteComic = 0;
|
||||
dataPtr->yacreaderSessionData.remoteComicId = 0;
|
||||
#ifdef SUPERVERBOSE
|
||||
qDebug("HttpSession: created new session data with id %s",dataPtr->id.data());
|
||||
#endif
|
||||
}
|
||||
else {
|
||||
dataPtr=0;
|
||||
}
|
||||
}
|
||||
|
||||
HttpSession::HttpSession(const HttpSession& other) {
|
||||
dataPtr=other.dataPtr;
|
||||
if (dataPtr) {
|
||||
dataPtr->lock.lockForWrite();
|
||||
dataPtr->refCount++;
|
||||
#ifdef SUPERVERBOSE
|
||||
qDebug("HttpSession: refCount of %s is %i",dataPtr->id.data(),dataPtr->refCount);
|
||||
#endif
|
||||
dataPtr->lock.unlock();
|
||||
}
|
||||
}
|
||||
|
||||
HttpSession& HttpSession::operator= (const HttpSession& other) {
|
||||
HttpSessionData* oldPtr=dataPtr;
|
||||
dataPtr=other.dataPtr;
|
||||
if (dataPtr) {
|
||||
dataPtr->lock.lockForWrite();
|
||||
dataPtr->refCount++;
|
||||
#ifdef SUPERVERBOSE
|
||||
qDebug("HttpSession: refCount of %s is %i",dataPtr->id.data(),dataPtr->refCount);
|
||||
#endif
|
||||
dataPtr->lastAccess=QDateTime::currentMSecsSinceEpoch();
|
||||
dataPtr->lock.unlock();
|
||||
}
|
||||
if (oldPtr) {
|
||||
int refCount;
|
||||
oldPtr->lock.lockForRead();
|
||||
refCount=oldPtr->refCount--;
|
||||
#ifdef SUPERVERBOSE
|
||||
qDebug("HttpSession: refCount of %s is %i",oldPtr->id.data(),oldPtr->refCount);
|
||||
#endif
|
||||
oldPtr->lock.unlock();
|
||||
if (refCount==0) {
|
||||
delete oldPtr;
|
||||
}
|
||||
}
|
||||
return *this;
|
||||
}
|
||||
|
||||
HttpSession::~HttpSession() {
|
||||
if (dataPtr) {
|
||||
int refCount;
|
||||
dataPtr->lock.lockForRead();
|
||||
refCount=--dataPtr->refCount;
|
||||
#ifdef SUPERVERBOSE
|
||||
qDebug("HttpSession: refCount of %s is %i",dataPtr->id.data(),dataPtr->refCount);
|
||||
#endif
|
||||
dataPtr->lock.unlock();
|
||||
if (refCount==0) {
|
||||
qDebug("HttpSession: deleting data");
|
||||
delete dataPtr;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
QByteArray HttpSession::getId() const {
|
||||
if (dataPtr) {
|
||||
return dataPtr->id;
|
||||
}
|
||||
else {
|
||||
return QByteArray();
|
||||
}
|
||||
}
|
||||
|
||||
bool HttpSession::isNull() const {
|
||||
return dataPtr==0;
|
||||
}
|
||||
|
||||
void HttpSession::set(const QByteArray& key, const QVariant& value) {
|
||||
if (dataPtr) {
|
||||
dataPtr->lock.lockForWrite();
|
||||
dataPtr->values.insert(key,value);
|
||||
dataPtr->lock.unlock();
|
||||
}
|
||||
}
|
||||
|
||||
void HttpSession::remove(const QByteArray& key) {
|
||||
if (dataPtr) {
|
||||
dataPtr->lock.lockForWrite();
|
||||
dataPtr->values.remove(key);
|
||||
dataPtr->lock.unlock();
|
||||
}
|
||||
}
|
||||
|
||||
QVariant HttpSession::get(const QByteArray& key) const {
|
||||
QVariant value;
|
||||
if (dataPtr) {
|
||||
dataPtr->lock.lockForRead();
|
||||
value=dataPtr->values.value(key);
|
||||
dataPtr->lock.unlock();
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
bool HttpSession::contains(const QByteArray& key) const {
|
||||
bool found=false;
|
||||
if (dataPtr) {
|
||||
dataPtr->lock.lockForRead();
|
||||
found=dataPtr->values.contains(key);
|
||||
dataPtr->lock.unlock();
|
||||
}
|
||||
return found;
|
||||
}
|
||||
|
||||
QMap<QByteArray,QVariant> HttpSession::getAll() const {
|
||||
QMap<QByteArray,QVariant> values;
|
||||
if (dataPtr) {
|
||||
dataPtr->lock.lockForRead();
|
||||
values=dataPtr->values;
|
||||
dataPtr->lock.unlock();
|
||||
}
|
||||
return values;
|
||||
}
|
||||
|
||||
qint64 HttpSession::getLastAccess() const {
|
||||
qint64 value=0;
|
||||
if (dataPtr) {
|
||||
dataPtr->lock.lockForRead();
|
||||
value=dataPtr->lastAccess;
|
||||
dataPtr->lock.unlock();
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
|
||||
void HttpSession::setLastAccess() {
|
||||
if (dataPtr) {
|
||||
dataPtr->lock.lockForRead();
|
||||
dataPtr->lastAccess=QDateTime::currentMSecsSinceEpoch();
|
||||
dataPtr->lock.unlock();
|
||||
}
|
||||
}
|
||||
|
||||
//A<>ADIDO
|
||||
//sets
|
||||
bool HttpSession::isComicOnDevice(const QString & hash)
|
||||
{
|
||||
if(dataPtr)
|
||||
return dataPtr->yacreaderSessionData.comicsOnDevice.contains(hash);
|
||||
else
|
||||
return false;
|
||||
}
|
||||
bool HttpSession::isComicDownloaded(const QString & hash)
|
||||
{
|
||||
if(dataPtr)
|
||||
return dataPtr->yacreaderSessionData.downloadedComics.contains(hash);
|
||||
else
|
||||
return false;
|
||||
}
|
||||
void HttpSession::setComicOnDevice(const QString & hash)
|
||||
{
|
||||
if(dataPtr)
|
||||
{
|
||||
dataPtr->yacreaderSessionData.comicsOnDevice.insert(hash);
|
||||
}
|
||||
}
|
||||
void HttpSession::setComicsOnDevice(const QSet<QString> & set)
|
||||
{
|
||||
if(dataPtr)
|
||||
{
|
||||
dataPtr->yacreaderSessionData.comicsOnDevice = set;
|
||||
}
|
||||
}
|
||||
void HttpSession::setDownloadedComic(const QString & hash)
|
||||
{
|
||||
if(dataPtr)
|
||||
{
|
||||
dataPtr->yacreaderSessionData.downloadedComics.insert(hash);
|
||||
}
|
||||
}
|
||||
QSet<QString> HttpSession::getComicsOnDevice()
|
||||
{
|
||||
if(dataPtr)
|
||||
return dataPtr->yacreaderSessionData.comicsOnDevice ;
|
||||
else
|
||||
return QSet<QString>();
|
||||
}
|
||||
QSet<QString> HttpSession::getDownloadedComics()
|
||||
{
|
||||
if(dataPtr)
|
||||
return dataPtr->yacreaderSessionData.downloadedComics ;
|
||||
else
|
||||
return QSet<QString>();
|
||||
}
|
||||
|
||||
void HttpSession::clearComics()
|
||||
{
|
||||
if(dataPtr)
|
||||
{
|
||||
dataPtr->yacreaderSessionData.comicsOnDevice.clear();
|
||||
dataPtr->yacreaderSessionData.downloadedComics.clear();
|
||||
}
|
||||
}
|
||||
//current comic (import)
|
||||
qulonglong HttpSession::getCurrentComicId()
|
||||
{
|
||||
if(dataPtr)
|
||||
return dataPtr->yacreaderSessionData.comicId ;
|
||||
else
|
||||
return 0;
|
||||
}
|
||||
Comic* HttpSession::getCurrentComic()
|
||||
{
|
||||
if(dataPtr)
|
||||
{
|
||||
return dataPtr->yacreaderSessionData.comic ;
|
||||
}
|
||||
else
|
||||
return 0;
|
||||
}
|
||||
void HttpSession::dismissCurrentComic()
|
||||
{
|
||||
if(dataPtr)
|
||||
{
|
||||
if(dataPtr->yacreaderSessionData.comic != 0)
|
||||
{
|
||||
dataPtr->yacreaderSessionData.comic->deleteLater();
|
||||
dataPtr->yacreaderSessionData.comic = 0;
|
||||
}
|
||||
dataPtr->yacreaderSessionData.comicId = 0;
|
||||
}
|
||||
}
|
||||
void HttpSession::setCurrentComic(qulonglong id, Comic * comic)
|
||||
{
|
||||
if(dataPtr)
|
||||
{
|
||||
dismissCurrentComic();
|
||||
dataPtr->yacreaderSessionData.comicId = id;
|
||||
dataPtr->yacreaderSessionData.comic = comic;
|
||||
}
|
||||
}
|
||||
|
||||
//current comic (read)
|
||||
qulonglong HttpSession::getCurrentRemoteComicId()
|
||||
{
|
||||
if(dataPtr)
|
||||
return dataPtr->yacreaderSessionData.remoteComicId ;
|
||||
else
|
||||
return 0;
|
||||
}
|
||||
Comic* HttpSession::getCurrentRemoteComic()
|
||||
{
|
||||
if(dataPtr)
|
||||
{
|
||||
return dataPtr->yacreaderSessionData.remoteComic ;
|
||||
}
|
||||
else
|
||||
return 0;
|
||||
}
|
||||
void HttpSession::dismissCurrentRemoteComic()
|
||||
{
|
||||
if(dataPtr)
|
||||
{
|
||||
if(dataPtr->yacreaderSessionData.remoteComic != 0)
|
||||
{
|
||||
dataPtr->yacreaderSessionData.remoteComic->deleteLater();
|
||||
dataPtr->yacreaderSessionData.remoteComic = 0;
|
||||
}
|
||||
dataPtr->yacreaderSessionData.remoteComicId = 0;
|
||||
}
|
||||
}
|
||||
void HttpSession::setCurrentRemoteComic(qulonglong id, Comic * comic)
|
||||
{
|
||||
if(dataPtr)
|
||||
{
|
||||
dismissCurrentRemoteComic();
|
||||
dataPtr->yacreaderSessionData.remoteComicId = id;
|
||||
dataPtr->yacreaderSessionData.remoteComic = comic;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
QString HttpSession::getDeviceType()
|
||||
{
|
||||
if(dataPtr)
|
||||
{
|
||||
return dataPtr->yacreaderSessionData.device;
|
||||
}
|
||||
return "";
|
||||
}
|
||||
QString HttpSession::getDisplayType()
|
||||
{
|
||||
if(dataPtr)
|
||||
{
|
||||
return dataPtr->yacreaderSessionData.display;
|
||||
}
|
||||
return "";
|
||||
}
|
||||
void HttpSession::setDeviceType(const QString & device)
|
||||
{
|
||||
if(dataPtr)
|
||||
{
|
||||
//dataPtr->yacreaderSessionData.comicsOnDevice.clear(); //TODO crear un m<>todo clear que limpie la sesi<73>n completamente
|
||||
//dataPtr->yacreaderSessionData.downloadedComics.clear();
|
||||
dataPtr->yacreaderSessionData.device = device;
|
||||
}
|
||||
}
|
||||
void HttpSession::setDisplayType(const QString & display)
|
||||
{
|
||||
if(dataPtr)
|
||||
{
|
||||
dataPtr->yacreaderSessionData.display = display;
|
||||
}
|
||||
}
|
||||
|
||||
void HttpSession::clearNavigationPath()
|
||||
{
|
||||
if(dataPtr)
|
||||
dataPtr->yacreaderSessionData.navigationPath.clear();
|
||||
}
|
||||
|
||||
QPair<qulonglong, quint32> HttpSession::popNavigationItem()
|
||||
{
|
||||
if(dataPtr && !(dataPtr->yacreaderSessionData.navigationPath.isEmpty()))
|
||||
return dataPtr->yacreaderSessionData.navigationPath.pop();
|
||||
return QPair<qulonglong, quint32>();
|
||||
}
|
||||
|
||||
QPair<qulonglong, quint32> HttpSession::topNavigationItem()
|
||||
{
|
||||
if(dataPtr && !(dataPtr->yacreaderSessionData.navigationPath.isEmpty()))
|
||||
return dataPtr->yacreaderSessionData.navigationPath.top();
|
||||
return QPair<qulonglong, quint32>();
|
||||
}
|
||||
|
||||
void HttpSession::pushNavigationItem(const QPair<qulonglong, quint32> &item)
|
||||
{
|
||||
if(dataPtr)
|
||||
dataPtr->yacreaderSessionData.navigationPath.push(item);
|
||||
}
|
||||
|
||||
void HttpSession::updateTopItem(const QPair<qulonglong, quint32> &item)
|
||||
{
|
||||
if(dataPtr && !(dataPtr->yacreaderSessionData.navigationPath.isEmpty()))
|
||||
{
|
||||
dataPtr->yacreaderSessionData.navigationPath.pop();
|
||||
dataPtr->yacreaderSessionData.navigationPath.push(item);
|
||||
} else if(dataPtr)
|
||||
{
|
||||
dataPtr->yacreaderSessionData.navigationPath.push(item);
|
||||
}
|
||||
}
|
||||
|
||||
QStack<QPair<qulonglong, quint32> > HttpSession::getNavigationPath()
|
||||
{
|
||||
if(dataPtr)
|
||||
return dataPtr->yacreaderSessionData.navigationPath;
|
||||
else
|
||||
return QStack<QPair<qulonglong, quint32> >();
|
||||
}
|
||||
|
193
YACReaderLibrary/server/lib/bfHttpServer/httpsession.h
Normal file
193
YACReaderLibrary/server/lib/bfHttpServer/httpsession.h
Normal file
@ -0,0 +1,193 @@
|
||||
/**
|
||||
@file
|
||||
@author Stefan Frings
|
||||
*/
|
||||
|
||||
#ifndef HTTPSESSION_H
|
||||
#define HTTPSESSION_H
|
||||
|
||||
#include <QByteArray>
|
||||
#include <QVariant>
|
||||
#include <QReadWriteLock>
|
||||
|
||||
#include <QSet>
|
||||
#include <QString>
|
||||
#include "comic.h"
|
||||
|
||||
/**
|
||||
This class stores data for a single HTTP session.
|
||||
A session can store any number of key/value pairs. This class uses implicit
|
||||
sharing for read and write access. This class is thread safe.
|
||||
@see HttpSessionStore should be used to create and get instances of this class.
|
||||
*/
|
||||
|
||||
class HttpSession {
|
||||
|
||||
public:
|
||||
|
||||
/**
|
||||
Constructor.
|
||||
@param canStore The session can store data, if this parameter is true.
|
||||
Otherwise all calls to set() and remove() do not have any effect.
|
||||
*/
|
||||
HttpSession(bool canStore=false);
|
||||
|
||||
/**
|
||||
Copy constructor. Creates another HttpSession object that shares the
|
||||
data of the other object.
|
||||
*/
|
||||
HttpSession(const HttpSession& other);
|
||||
|
||||
/**
|
||||
Copy operator. Detaches from the current shared data and attaches to
|
||||
the data of the other object.
|
||||
*/
|
||||
HttpSession& operator= (const HttpSession& other);
|
||||
|
||||
|
||||
/**
|
||||
Destructor. Detaches from the shared data.
|
||||
*/
|
||||
virtual ~HttpSession();
|
||||
|
||||
/** Get the unique ID of this session. This method is thread safe. */
|
||||
QByteArray getId() const;
|
||||
|
||||
/**
|
||||
Null sessions cannot store data. All calls to set() and remove()
|
||||
do not have any effect.This method is thread safe.
|
||||
*/
|
||||
bool isNull() const;
|
||||
|
||||
/** Set a value. This method is thread safe. */
|
||||
void set(const QByteArray& key, const QVariant& value);
|
||||
|
||||
/** Remove a value. This method is thread safe. */
|
||||
void remove(const QByteArray& key);
|
||||
|
||||
/** Get a value. This method is thread safe. */
|
||||
QVariant get(const QByteArray& key) const;
|
||||
|
||||
/** Check if a key exists. This method is thread safe. */
|
||||
bool contains(const QByteArray& key) const;
|
||||
|
||||
/**
|
||||
Get a copy of all data stored in this session.
|
||||
Changes to the session do not affect the copy and vice versa.
|
||||
This method is thread safe.
|
||||
*/
|
||||
QMap<QByteArray,QVariant> getAll() const;
|
||||
|
||||
/**
|
||||
Get the timestamp of last access. That is the time when the last
|
||||
HttpSessionStore::getSession() has been called.
|
||||
This method is thread safe.
|
||||
*/
|
||||
qint64 getLastAccess() const;
|
||||
|
||||
/**
|
||||
Set the timestamp of last access, to renew the timeout period.
|
||||
Called by HttpSessionStore::getSession().
|
||||
This method is thread safe.
|
||||
*/
|
||||
void setLastAccess();
|
||||
|
||||
//A<>ADIDO
|
||||
//sets
|
||||
void setComicsOnDevice(const QSet<QString> & set);
|
||||
void setComicOnDevice(const QString & hash);
|
||||
void setDownloadedComic(const QString & hash);
|
||||
bool isComicOnDevice(const QString & hash);
|
||||
bool isComicDownloaded(const QString & hash);
|
||||
QSet<QString> getComicsOnDevice();
|
||||
QSet<QString> getDownloadedComics();
|
||||
void clearComics();
|
||||
|
||||
//current comic (import)
|
||||
qulonglong getCurrentComicId();
|
||||
Comic * getCurrentComic();
|
||||
void dismissCurrentComic();
|
||||
void setCurrentComic(qulonglong id, Comic * comic);
|
||||
|
||||
//current comic (read)
|
||||
qulonglong getCurrentRemoteComicId();
|
||||
Comic * getCurrentRemoteComic();
|
||||
void dismissCurrentRemoteComic();
|
||||
void setCurrentRemoteComic(qulonglong id, Comic * comic);
|
||||
|
||||
//device identification
|
||||
QString getDeviceType();
|
||||
QString getDisplayType();
|
||||
void setDeviceType(const QString & device);
|
||||
void setDisplayType(const QString & display);
|
||||
|
||||
|
||||
/*int popPage();
|
||||
void pushPage(int page);
|
||||
int topPage();
|
||||
|
||||
void clearFoldersPath();
|
||||
int popFolder();
|
||||
void pushFolder(int page);
|
||||
int topFolder();
|
||||
QStack<int> getFoldersPath();*/
|
||||
|
||||
void clearNavigationPath();
|
||||
QPair<qulonglong, quint32> popNavigationItem();
|
||||
QPair<qulonglong, quint32> topNavigationItem();
|
||||
void pushNavigationItem(const QPair<qulonglong, quint32> & item);
|
||||
void updateTopItem(const QPair<qulonglong, quint32> & item);
|
||||
|
||||
//TODO replace QPair by a custom class for storing folderId, page and folderName(save some DB accesses)
|
||||
QStack<QPair<qulonglong, quint32> > getNavigationPath();
|
||||
|
||||
|
||||
|
||||
|
||||
private:
|
||||
|
||||
struct YACReaderSessionData {
|
||||
//c<>mics disponibles en dispositivo
|
||||
QSet<QString> comicsOnDevice;
|
||||
//c<>mics que han sido descargados o est<73>n siendo descargados en esta sesi<73>n
|
||||
QSet<QString> downloadedComics;
|
||||
//c<>mic actual que est<73> siendo descargado
|
||||
QString device;
|
||||
QString display;
|
||||
qulonglong comicId;
|
||||
qulonglong remoteComicId;
|
||||
|
||||
//folder_id, page_number
|
||||
QStack<QPair<qulonglong, quint32> > navigationPath;
|
||||
|
||||
Comic * comic;
|
||||
Comic * remoteComic;
|
||||
};
|
||||
|
||||
struct HttpSessionData {
|
||||
|
||||
/** Unique ID */
|
||||
QByteArray id;
|
||||
|
||||
/** Timestamp of last access, set by the HttpSessionStore */
|
||||
qint64 lastAccess;
|
||||
|
||||
/** Reference counter */
|
||||
int refCount;
|
||||
|
||||
/** Used to synchronize threads */
|
||||
QReadWriteLock lock;
|
||||
|
||||
/** Storage for the key/value pairs; */
|
||||
QMap<QByteArray,QVariant> values;
|
||||
|
||||
YACReaderSessionData yacreaderSessionData;
|
||||
|
||||
};
|
||||
|
||||
/** Pointer to the shared data. */
|
||||
HttpSessionData* dataPtr;
|
||||
|
||||
};
|
||||
|
||||
#endif // HTTPSESSION_H
|
109
YACReaderLibrary/server/lib/bfHttpServer/httpsessionstore.cpp
Normal file
109
YACReaderLibrary/server/lib/bfHttpServer/httpsessionstore.cpp
Normal file
@ -0,0 +1,109 @@
|
||||
/**
|
||||
@file
|
||||
@author Stefan Frings
|
||||
*/
|
||||
|
||||
#include "httpsessionstore.h"
|
||||
#include <QDateTime>
|
||||
#include <QUuid>
|
||||
|
||||
HttpSessionStore::HttpSessionStore(QSettings* settings, QObject* parent)
|
||||
:QObject(parent)
|
||||
{
|
||||
this->settings=settings;
|
||||
connect(&cleanupTimer,SIGNAL(timeout()),this,SLOT(timerEvent()));
|
||||
cleanupTimer.start(60000);
|
||||
cookieName=settings->value("cookieName","sessionid").toByteArray();
|
||||
expirationTime=settings->value("expirationTime",864000000).toInt();
|
||||
qDebug("HttpSessionStore: Sessions expire after %i milliseconds",expirationTime);
|
||||
}
|
||||
|
||||
HttpSessionStore::~HttpSessionStore()
|
||||
{
|
||||
cleanupTimer.stop();
|
||||
}
|
||||
|
||||
QByteArray HttpSessionStore::getSessionId(HttpRequest& request, HttpResponse& response) {
|
||||
// The session ID in the response has priority because this one will be used in the next request.
|
||||
mutex.lock();
|
||||
// Get the session ID from the response cookie
|
||||
QByteArray sessionId=response.getCookies().value(cookieName).getValue();
|
||||
if (sessionId.isEmpty()) {
|
||||
// Get the session ID from the request cookie
|
||||
sessionId=request.getCookie(cookieName);
|
||||
}
|
||||
// Clear the session ID if there is no such session in the storage.
|
||||
if (!sessionId.isEmpty()) {
|
||||
if (!sessions.contains(sessionId)) {
|
||||
qDebug("HttpSessionStore: received invalid session cookie with ID %s",sessionId.data());
|
||||
sessionId.clear();
|
||||
}
|
||||
}
|
||||
mutex.unlock();
|
||||
return sessionId;
|
||||
}
|
||||
|
||||
HttpSession HttpSessionStore::getSession(HttpRequest& request, HttpResponse& response, bool allowCreate) {
|
||||
QByteArray sessionId=getSessionId(request,response);
|
||||
mutex.lock();
|
||||
if (!sessionId.isEmpty()) {
|
||||
HttpSession session=sessions.value(sessionId);
|
||||
if (!session.isNull()) {
|
||||
mutex.unlock();
|
||||
session.setLastAccess();
|
||||
return session;
|
||||
}
|
||||
}
|
||||
// Need to create a new session
|
||||
if (allowCreate) {
|
||||
QByteArray cookieName=settings->value("cookieName","sessionid").toByteArray();
|
||||
QByteArray cookiePath=settings->value("cookiePath","/").toByteArray();
|
||||
QByteArray cookieComment=settings->value("cookieComment").toByteArray();
|
||||
QByteArray cookieDomain=settings->value("cookieDomain").toByteArray();
|
||||
HttpSession session(true);
|
||||
qDebug("HttpSessionStore: create new session with ID %s",session.getId().data());
|
||||
sessions.insert(session.getId(),session);
|
||||
response.setCookie(HttpCookie(cookieName,session.getId(),expirationTime/1000,cookiePath,cookieComment,cookieDomain));
|
||||
mutex.unlock();
|
||||
return session;
|
||||
}
|
||||
// Return a null session
|
||||
mutex.unlock();
|
||||
return HttpSession();
|
||||
}
|
||||
|
||||
HttpSession HttpSessionStore::getSession(const QByteArray id) {
|
||||
mutex.lock();
|
||||
HttpSession session=sessions.value(id);
|
||||
mutex.unlock();
|
||||
session.setLastAccess();
|
||||
return session;
|
||||
}
|
||||
|
||||
void HttpSessionStore::timerEvent() {
|
||||
// Todo: find a way to delete sessions only if no controller is accessing them
|
||||
mutex.lock();
|
||||
qint64 now=QDateTime::currentMSecsSinceEpoch();
|
||||
QMap<QByteArray,HttpSession>::iterator i = sessions.begin();
|
||||
while (i != sessions.end()) {
|
||||
QMap<QByteArray,HttpSession>::iterator prev = i;
|
||||
++i;
|
||||
HttpSession session=prev.value();
|
||||
qint64 lastAccess=session.getLastAccess();
|
||||
if (now-lastAccess>expirationTime) { //TODO cleaning up will cause current opened comic to be deleted, so clients won't be able to download it
|
||||
//If the cleaning occurs in the midle of a download it going to cause issues
|
||||
//Temporal fix: use a big expirationTime = 10 days
|
||||
qDebug("HttpSessionStore: session %s expired",session.getId().data());
|
||||
sessions.erase(prev);
|
||||
}
|
||||
}
|
||||
mutex.unlock();
|
||||
}
|
||||
|
||||
|
||||
/** Delete a session */
|
||||
void HttpSessionStore::removeSession(HttpSession session) {
|
||||
mutex.lock();
|
||||
sessions.remove(session.getId());
|
||||
mutex.unlock();
|
||||
}
|
104
YACReaderLibrary/server/lib/bfHttpServer/httpsessionstore.h
Normal file
104
YACReaderLibrary/server/lib/bfHttpServer/httpsessionstore.h
Normal file
@ -0,0 +1,104 @@
|
||||
/**
|
||||
@file
|
||||
@author Stefan Frings
|
||||
*/
|
||||
|
||||
#ifndef HTTPSESSIONSTORE_H
|
||||
#define HTTPSESSIONSTORE_H
|
||||
|
||||
#include <QObject>
|
||||
#include <QMap>
|
||||
#include <QTimer>
|
||||
#include <QMutex>
|
||||
#include "httpsession.h"
|
||||
#include "httpresponse.h"
|
||||
#include "httprequest.h"
|
||||
|
||||
/**
|
||||
Stores HTTP sessions and deletes them when they have expired.
|
||||
The following configuration settings are required in the config file:
|
||||
<code><pre>
|
||||
expirationTime=3600000
|
||||
cookieName=sessionid
|
||||
</pre></code>
|
||||
The following additional configurations settings are optionally:
|
||||
<code><pre>
|
||||
cookiePath=/
|
||||
cookieComment=Session ID
|
||||
cookieDomain=stefanfrings.de
|
||||
</pre></code>
|
||||
*/
|
||||
|
||||
class HttpSessionStore : public QObject {
|
||||
Q_OBJECT
|
||||
Q_DISABLE_COPY(HttpSessionStore)
|
||||
public:
|
||||
|
||||
/** Constructor. */
|
||||
HttpSessionStore(QSettings* settings, QObject* parent);
|
||||
|
||||
/** Destructor */
|
||||
virtual ~HttpSessionStore();
|
||||
|
||||
/**
|
||||
Get the ID of the current HTTP session, if it is valid.
|
||||
This method is thread safe.
|
||||
@warning Sessions may expire at any time, so subsequent calls of
|
||||
getSession() might return a new session with a different ID.
|
||||
@param request Used to get the session cookie
|
||||
@param response Used to get and set the new session cookie
|
||||
@return Empty string, if there is no valid session.
|
||||
*/
|
||||
QByteArray getSessionId(HttpRequest& request, HttpResponse& response);
|
||||
|
||||
/**
|
||||
Get the session of a HTTP request, eventually create a new one.
|
||||
This method is thread safe. New sessions can only be created before
|
||||
the first byte has been written to the HTTP response.
|
||||
@param request Used to get the session cookie
|
||||
@param response Used to get and set the new session cookie
|
||||
@param allowCreate can be set to false, to disable the automatic creation of a new session.
|
||||
@return If autoCreate is disabled, the function returns a null session if there is no session.
|
||||
@see HttpSession::isNull()
|
||||
*/
|
||||
HttpSession getSession(HttpRequest& request, HttpResponse& response, bool allowCreate=true);
|
||||
|
||||
/**
|
||||
Get a HTTP session by it's ID number.
|
||||
This method is thread safe.
|
||||
@return If there is no such session, the function returns a null session.
|
||||
@param id ID number of the session
|
||||
@see HttpSession::isNull()
|
||||
*/
|
||||
HttpSession getSession(const QByteArray id);
|
||||
|
||||
/** Delete a session */
|
||||
void removeSession(HttpSession session);
|
||||
|
||||
private:
|
||||
|
||||
/** Configuration settings */
|
||||
QSettings* settings;
|
||||
|
||||
/** Storage for the sessions */
|
||||
QMap<QByteArray,HttpSession> sessions;
|
||||
|
||||
/** Timer to remove expired sessions */
|
||||
QTimer cleanupTimer;
|
||||
|
||||
/** Name of the session cookie */
|
||||
QByteArray cookieName;
|
||||
|
||||
/** Time when sessions expire (in ms)*/
|
||||
int expirationTime;
|
||||
|
||||
/** Used to synchronize threads */
|
||||
QMutex mutex;
|
||||
|
||||
private slots:
|
||||
|
||||
/** Called every minute to cleanup expired sessions. */
|
||||
void timerEvent();
|
||||
};
|
||||
|
||||
#endif // HTTPSESSIONSTORE_H
|
@ -0,0 +1,235 @@
|
||||
/**
|
||||
@file
|
||||
@author Stefan Frings
|
||||
*/
|
||||
|
||||
#include "staticfilecontroller.h"
|
||||
#include <QFileInfo>
|
||||
#include <QDir>
|
||||
#include <QDateTime>
|
||||
#include "httpsession.h"
|
||||
#include "static.h"
|
||||
#include <QApplication>
|
||||
|
||||
|
||||
StaticFileController::StaticFileController(QSettings* settings, QObject* parent)
|
||||
:HttpRequestHandler(parent)
|
||||
{
|
||||
maxAge=settings->value("maxAge","60000").toInt();
|
||||
encoding=settings->value("encoding","UTF-8").toString();
|
||||
docroot=settings->value("path","./server/docroot").toString();
|
||||
// Convert relative path to absolute, based on the directory of the config file.
|
||||
#ifdef Q_OS_WIN32
|
||||
if (QDir::isRelativePath(docroot) && settings->format()!=QSettings::NativeFormat)
|
||||
#else
|
||||
if (QDir::isRelativePath(docroot))
|
||||
#endif
|
||||
{
|
||||
#if defined Q_OS_UNIX && ! defined Q_OS_MAC
|
||||
QFileInfo configFile(QString(DATADIR)+"/yacreader");
|
||||
docroot=QFileInfo(QString(DATADIR)+"/yacreader",docroot).absoluteFilePath();
|
||||
#else
|
||||
QFileInfo configFile(QApplication::applicationDirPath());
|
||||
docroot=QFileInfo(QApplication::applicationDirPath(),docroot).absoluteFilePath();
|
||||
#endif
|
||||
}
|
||||
qDebug("StaticFileController: docroot=%s, encoding=%s, maxAge=%i",qPrintable(docroot),qPrintable(encoding),maxAge);
|
||||
maxCachedFileSize=settings->value("maxCachedFileSize","65536").toInt();
|
||||
cache.setMaxCost(settings->value("cacheSize","1000000").toInt());
|
||||
cacheTimeout=settings->value("cacheTime","60000").toInt();
|
||||
qDebug("StaticFileController: cache timeout=%i, size=%i",cacheTimeout,cache.maxCost());
|
||||
}
|
||||
|
||||
|
||||
void StaticFileController::service(HttpRequest& request, HttpResponse& response) {
|
||||
QByteArray path=request.getPath();
|
||||
// Forbid access to files outside the docroot directory
|
||||
if (path.startsWith("/..")) {
|
||||
qWarning("StaticFileController: somebody attempted to access a file outside the docroot directory");
|
||||
response.setStatus(403,"forbidden");
|
||||
response.write("403 forbidden",true);
|
||||
}
|
||||
|
||||
//TODO(DONE) carga sensible al dispositivo y a la localizaci<63>n
|
||||
QString stringPath = path;
|
||||
QStringList paths = QString(path).split('/');
|
||||
QString fileName = paths.last();
|
||||
stringPath.remove(fileName);
|
||||
HttpSession session=Static::sessionStore->getSession(request,response,false);
|
||||
QString device = session.getDeviceType();
|
||||
QString display = session.getDisplayType();
|
||||
if(fileName.endsWith(".png"))
|
||||
fileName = getDeviceAwareFileName(fileName, device, display, request.getHeader("Accept-Language"), stringPath);
|
||||
else
|
||||
fileName = getDeviceAwareFileName(fileName, device, request.getHeader("Accept-Language"), stringPath);
|
||||
QString newPath = stringPath.append(fileName);
|
||||
path = newPath.toLocal8Bit();
|
||||
|
||||
//CAMBIADO
|
||||
response.setHeader("Connection","close");
|
||||
//END_TODO
|
||||
|
||||
// Check if we have the file in cache
|
||||
//qint64 now=QDateTime::currentMSecsSinceEpoch();
|
||||
// mutex.lock();
|
||||
// CacheEntry* entry=cache.object(path);
|
||||
//if (entry && (cacheTimeout==0 || entry->created>now-cacheTimeout)) {
|
||||
// QByteArray document=entry->document; //copy the cached document, because other threads may destroy the cached entry immediately after mutex unlock.
|
||||
// mutex.unlock();
|
||||
// qDebug("StaticFileController: Cache hit for %s",path.data());
|
||||
// setContentType(path,response);
|
||||
// response.setHeader("Cache-Control","max-age="+QByteArray::number(maxAge/1000));
|
||||
// response.write(document);
|
||||
//}
|
||||
//else {
|
||||
|
||||
// mutex.unlock();
|
||||
//qDebug("StaticFileController: Cache miss for %s",path.data());
|
||||
// The file is not in cache.
|
||||
// If the filename is a directory, append index.html.
|
||||
if (QFileInfo(docroot+path).isDir()) {
|
||||
path+="/index.html";
|
||||
}
|
||||
|
||||
|
||||
QFile file(docroot+path);
|
||||
if (file.exists()) {
|
||||
qDebug("StaticFileController: Open file %s",qPrintable(file.fileName()));
|
||||
if (file.open(QIODevice::ReadOnly)) {
|
||||
setContentType(path,response);
|
||||
//response.setHeader("Cache-Control","max-age="+QByteArray::number(maxAge/1000));
|
||||
//if (file.size()<=maxCachedFileSize) {
|
||||
// // Return the file content and store it also in the cache
|
||||
// entry=new CacheEntry();
|
||||
// while (!file.atEnd() && !file.error()) {
|
||||
// QByteArray buffer=file.read(65536);
|
||||
// response.write(buffer);
|
||||
// entry->document.append(buffer);
|
||||
// }
|
||||
// entry->created=now;
|
||||
// mutex.lock();
|
||||
// cache.insert(request.getPath(),entry,entry->document.size());
|
||||
// mutex.unlock();
|
||||
//}
|
||||
//else {
|
||||
// Return the file content, do not store in cache*/
|
||||
while (!file.atEnd() && !file.error()) {
|
||||
response.write(file.read(131072));
|
||||
//}
|
||||
}
|
||||
file.close();
|
||||
}
|
||||
else {
|
||||
qWarning("StaticFileController: Cannot open existing file %s for reading",qPrintable(file.fileName()));
|
||||
response.setStatus(403,"forbidden");
|
||||
response.write("403 forbidden",true);
|
||||
}
|
||||
}
|
||||
else {
|
||||
response.setStatus(404,"not found");
|
||||
response.write("404 not found",true);
|
||||
}
|
||||
//}
|
||||
}
|
||||
|
||||
void StaticFileController::setContentType(QString fileName, HttpResponse& response) const {
|
||||
if (fileName.endsWith(".png")) {
|
||||
response.setHeader("Content-Type", "image/png");
|
||||
}
|
||||
else if (fileName.endsWith(".jpg")) {
|
||||
response.setHeader("Content-Type", "image/jpeg");
|
||||
}
|
||||
else if (fileName.endsWith(".gif")) {
|
||||
response.setHeader("Content-Type", "image/gif");
|
||||
}
|
||||
else if (fileName.endsWith(".pdf")) {
|
||||
response.setHeader("Content-Type", "application/pdf");
|
||||
}
|
||||
else if (fileName.endsWith(".txt")) {
|
||||
response.setHeader("Content-Type", qPrintable("text/plain; charset="+encoding));
|
||||
}
|
||||
else if (fileName.endsWith(".html") || fileName.endsWith(".htm")) {
|
||||
response.setHeader("Content-Type", qPrintable("text/html; charset="+encoding));
|
||||
}
|
||||
else if (fileName.endsWith(".css")) {
|
||||
response.setHeader("Content-Type", "text/css");
|
||||
}
|
||||
else if (fileName.endsWith(".js")) {
|
||||
response.setHeader("Content-Type", "text/javascript");
|
||||
}
|
||||
// Todo: add all of your content types
|
||||
}
|
||||
|
||||
bool StaticFileController::exists(QString localizedName, QString path) const
|
||||
{
|
||||
QString fileName=docroot+"/"+path + localizedName;
|
||||
QFile file(fileName);
|
||||
return file.exists();
|
||||
}
|
||||
|
||||
//retorna fileName si no se encontr<74> alternativa traducida <20> fileName-locale.extensi<73>n si se encontr<74>
|
||||
QString StaticFileController::getLocalizedFileName(QString fileName, QString locales, QString path) const
|
||||
{
|
||||
QSet<QString> tried; // used to suppress duplicate attempts
|
||||
QStringList locs=locales.split(',',QString::SkipEmptyParts);
|
||||
QStringList fileNameParts = fileName.split('.');
|
||||
QString file = fileNameParts.first();
|
||||
QString extension = fileNameParts.last();
|
||||
// Search for exact match
|
||||
foreach (QString loc,locs) {
|
||||
loc.replace(QRegExp(";.*"),"");
|
||||
loc.replace('-','_');
|
||||
QString localizedName=file+"-"+loc.trimmed()+"."+extension;
|
||||
if (!tried.contains(localizedName)) {
|
||||
if(exists(localizedName, path))
|
||||
return localizedName;
|
||||
tried.insert(localizedName);
|
||||
}
|
||||
}
|
||||
|
||||
// Search for correct language but any country
|
||||
foreach (QString loc,locs) {
|
||||
loc.replace(QRegExp("[;_-].*"),"");
|
||||
QString localizedName=file+"-"+loc.trimmed()+"."+extension;
|
||||
if (!tried.contains(localizedName)) {
|
||||
if(exists(localizedName, path))
|
||||
return localizedName;
|
||||
tried.insert(localizedName);
|
||||
}
|
||||
}
|
||||
|
||||
return fileName;
|
||||
}
|
||||
|
||||
QString StaticFileController::getDeviceAwareFileName(QString fileName, QString device, QString locales, QString path) const
|
||||
{
|
||||
QFileInfo fi(fileName);
|
||||
QString baseName = fi.baseName();
|
||||
QString extension = fi.completeSuffix();
|
||||
|
||||
QString completeFileName = getLocalizedFileName(baseName+"_"+device+"."+extension,locales,path);
|
||||
|
||||
if(QFile(docroot+"/"+path+completeFileName).exists())
|
||||
return completeFileName; //existe un archivo espec<65>fico para este dispositivo y locales
|
||||
else
|
||||
return getLocalizedFileName(fileName,locales,path); //no hay archivo espec<65>fico para el dispositivo, pero puede haberlo para estas locales
|
||||
}
|
||||
|
||||
QString StaticFileController::getDeviceAwareFileName(QString fileName, QString device, QString display, QString locales, QString path) const
|
||||
{
|
||||
QFileInfo fi(fileName);
|
||||
QString baseName = fi.baseName();
|
||||
QString extension = fi.completeSuffix();
|
||||
|
||||
QString completeFileName = baseName+display+"."+extension;
|
||||
if(QFile(docroot+"/"+path+completeFileName).exists())
|
||||
return completeFileName;
|
||||
else
|
||||
{
|
||||
completeFileName = baseName+"_"+device+display+"."+extension;
|
||||
if((QFile(docroot+"/"+path+completeFileName).exists()))
|
||||
return completeFileName;
|
||||
}
|
||||
|
||||
return fileName;
|
||||
}
|
@ -0,0 +1,92 @@
|
||||
/**
|
||||
@file
|
||||
@author Stefan Frings
|
||||
*/
|
||||
|
||||
#ifndef STATICFILECONTROLLER_H
|
||||
#define STATICFILECONTROLLER_H
|
||||
|
||||
#include "httprequest.h"
|
||||
#include "httpresponse.h"
|
||||
#include "httprequesthandler.h"
|
||||
#include <QCache>
|
||||
#include <QMutex>
|
||||
|
||||
/**
|
||||
Delivers static files. It is usually called by the applications main request handler when
|
||||
the caller request a path that is mapped to static files.
|
||||
<p>
|
||||
The following settings are required in the config file:
|
||||
<code><pre>
|
||||
path=docroot
|
||||
encoding=UTF-8
|
||||
maxAge=60000
|
||||
cacheTime=60000
|
||||
cacheSize=1000000
|
||||
maxCachedFileSize=65536
|
||||
</pre></code>
|
||||
The path is relative to the directory of the config file. In case of windows, if the
|
||||
settings are in the registry, the path is relative to the current working directory.
|
||||
<p>
|
||||
The encoding is sent to the web browser in case of text and html files.
|
||||
<p>
|
||||
The cache improves performance of small files when loaded from a network
|
||||
drive. Large files are not cached. Files are cached as long as possible,
|
||||
when cacheTime=0. The maxAge value (in msec!) controls the remote browsers cache.
|
||||
<p>
|
||||
Do not instantiate this class in each request, because this would make the file cache
|
||||
useless. Better create one instance during start-up and call it when the application
|
||||
received a related HTTP request.
|
||||
*/
|
||||
|
||||
class StaticFileController : public HttpRequestHandler {
|
||||
Q_OBJECT
|
||||
Q_DISABLE_COPY(StaticFileController);
|
||||
public:
|
||||
|
||||
/** Constructor */
|
||||
StaticFileController(QSettings* settings, QObject* parent = 0);
|
||||
|
||||
/** Generates the response */
|
||||
void service(HttpRequest& request, HttpResponse& response);
|
||||
|
||||
private:
|
||||
|
||||
/** Encoding of text files */
|
||||
QString encoding;
|
||||
|
||||
/** Root directory of documents */
|
||||
QString docroot;
|
||||
|
||||
/** Maximum age of files in the browser cache */
|
||||
int maxAge;
|
||||
|
||||
struct CacheEntry {
|
||||
QByteArray document;
|
||||
qint64 created;
|
||||
};
|
||||
|
||||
/** Timeout for each cached file */
|
||||
int cacheTimeout;
|
||||
|
||||
|
||||
/** Maximum size of files in cache, larger files are not cached */
|
||||
int maxCachedFileSize;
|
||||
|
||||
/** Cache storage */
|
||||
QCache<QString,CacheEntry> cache;
|
||||
|
||||
/** Used to synchronize cache access for threads */
|
||||
QMutex mutex;
|
||||
|
||||
/** Set a content-type header in the response depending on the ending of the filename */
|
||||
void setContentType(QString file, HttpResponse& response) const;
|
||||
|
||||
QString getLocalizedFileName(QString fileName, QString locales, QString path) const;
|
||||
QString getDeviceAwareFileName(QString fileName, QString device, QString locales, QString path) const;
|
||||
QString getDeviceAwareFileName(QString fileName, QString device, QString display, QString locales, QString path) const;
|
||||
|
||||
bool exists(QString localizedName, QString path) const;
|
||||
};
|
||||
|
||||
#endif // STATICFILECONTROLLER_H
|
5
YACReaderLibrary/server/lib/bfLogging/bfLogging.pri
Normal file
5
YACReaderLibrary/server/lib/bfLogging/bfLogging.pri
Normal file
@ -0,0 +1,5 @@
|
||||
INCLUDEPATH += $$PWD
|
||||
DEPENDPATH += $$PWD
|
||||
|
||||
HEADERS += $$PWD/logmessage.h $$PWD/logger.h $$PWD/filelogger.h $$PWD/dualfilelogger.h
|
||||
SOURCES += $$PWD/logmessage.cpp $$PWD/logger.cpp $$PWD/filelogger.cpp $$PWD/dualfilelogger.cpp
|
20
YACReaderLibrary/server/lib/bfLogging/dualfilelogger.cpp
Normal file
20
YACReaderLibrary/server/lib/bfLogging/dualfilelogger.cpp
Normal file
@ -0,0 +1,20 @@
|
||||
/**
|
||||
@file
|
||||
@author Stefan Frings
|
||||
*/
|
||||
|
||||
#include "dualfilelogger.h"
|
||||
|
||||
|
||||
DualFileLogger::DualFileLogger(QSettings* firstSettings, QSettings* secondSettings, const int refreshInterval, QObject* parent)
|
||||
:Logger(parent)
|
||||
{
|
||||
firstLogger=new FileLogger(firstSettings, refreshInterval, this);
|
||||
secondLogger=new FileLogger(secondSettings, refreshInterval, this);
|
||||
}
|
||||
|
||||
|
||||
void DualFileLogger::log(const QtMsgType type, const QString& message) {
|
||||
firstLogger->log(type, message);
|
||||
secondLogger->log(type, message);
|
||||
}
|
58
YACReaderLibrary/server/lib/bfLogging/dualfilelogger.h
Normal file
58
YACReaderLibrary/server/lib/bfLogging/dualfilelogger.h
Normal file
@ -0,0 +1,58 @@
|
||||
/**
|
||||
@file
|
||||
@author Stefan Frings
|
||||
*/
|
||||
|
||||
#ifndef DUALFILELOGGER_H
|
||||
#define DUALFILELOGGER_H
|
||||
|
||||
#include "logger.h"
|
||||
#include "filelogger.h"
|
||||
#include <QString>
|
||||
#include <QSettings>
|
||||
#include <QtGlobal>
|
||||
|
||||
/**
|
||||
Logs messages into two log files simultaneously.
|
||||
May be used to create two logfiles with different configuration settings.
|
||||
@see FileLogger for a description of the two underlying loggers.
|
||||
*/
|
||||
|
||||
class DualFileLogger : public Logger {
|
||||
Q_OBJECT
|
||||
Q_DISABLE_COPY(DualFileLogger)
|
||||
public:
|
||||
|
||||
/**
|
||||
Constructor.
|
||||
@param firstSettings Configuration settings for the first log file, usually stored in an INI file.
|
||||
Must not be 0.
|
||||
Settings are read from the current group, so the caller must have called settings->beginGroup().
|
||||
Because the group must not change during runtime, it is recommended to provide a
|
||||
separate QSettings instance to the logger that is not used by other parts of the program.
|
||||
@param secondSettings Same as firstSettings, but for the second log file.
|
||||
@param refreshInterval Interval of checking for changed config settings in msec, or 0=disabled
|
||||
@param parent Parent object.
|
||||
*/
|
||||
DualFileLogger(QSettings* firstSettings, QSettings* secondSettings, const int refreshInterval=10000, QObject *parent = 0);
|
||||
|
||||
/**
|
||||
Decorate and log a message.
|
||||
This method is thread safe.
|
||||
@param type Message type (level)
|
||||
@param message Message text
|
||||
@see LogMessage for a description of the message decoration.
|
||||
*/
|
||||
virtual void log(const QtMsgType type, const QString& message);
|
||||
|
||||
private:
|
||||
|
||||
/** First logger */
|
||||
FileLogger* firstLogger;
|
||||
|
||||
/** Second logger */
|
||||
FileLogger* secondLogger;
|
||||
|
||||
};
|
||||
|
||||
#endif // DUALFILELOGGER_H
|
174
YACReaderLibrary/server/lib/bfLogging/filelogger.cpp
Normal file
174
YACReaderLibrary/server/lib/bfLogging/filelogger.cpp
Normal file
@ -0,0 +1,174 @@
|
||||
/**
|
||||
@file
|
||||
@author Stefan Frings
|
||||
*/
|
||||
|
||||
#include "filelogger.h"
|
||||
#include <QTime>
|
||||
#include <QStringList>
|
||||
#include <QThread>
|
||||
#include <QtGlobal>
|
||||
#include <QFile>
|
||||
#include <QTimerEvent>
|
||||
#include <QDir>
|
||||
#include <QFileInfo>
|
||||
#include <stdio.h>
|
||||
#include "yacreader_global.h"
|
||||
|
||||
void FileLogger::refreshSettings() {
|
||||
mutex.lock();
|
||||
// Save old file name for later comparision with new settings
|
||||
QString oldFileName=fileName;
|
||||
|
||||
// Load new config settings
|
||||
settings->sync();
|
||||
fileName=settings->value("fileName","server_log.log").toString();
|
||||
// Convert relative fileName to absolute, based on the directory of the config file.
|
||||
#ifdef Q_OS_WIN32
|
||||
if (QDir::isRelativePath(fileName) && settings->format()!=QSettings::NativeFormat)
|
||||
#else
|
||||
if (QDir::isRelativePath(fileName))
|
||||
#endif
|
||||
{
|
||||
QFileInfo configFile(YACReader::getSettingsPath());
|
||||
fileName=QFileInfo(YACReader::getSettingsPath(),fileName).absoluteFilePath();
|
||||
}
|
||||
maxSize=settings->value("maxSize",1048576).toLongLong();
|
||||
maxBackups=settings->value("maxBackups",1).toInt();
|
||||
msgFormat=settings->value("msgFormat","{timestamp} {type} {msg}").toString();
|
||||
timestampFormat=settings->value("timestampFormat","yyyy-MM-dd hh:mm:ss.zzz").toString();
|
||||
minLevel=static_cast<QtMsgType>(settings->value("minLevel",0).toInt());
|
||||
bufferSize=settings->value("bufferSize",0).toInt();
|
||||
|
||||
// Create new file if the filename has been changed
|
||||
if (oldFileName!=fileName) {
|
||||
fprintf(stderr,"Logging to %s\n",qPrintable(fileName));
|
||||
close();
|
||||
open();
|
||||
}
|
||||
mutex.unlock();
|
||||
}
|
||||
|
||||
|
||||
FileLogger::FileLogger(QSettings* settings, const int refreshInterval, QObject* parent)
|
||||
: Logger(parent)
|
||||
{
|
||||
Q_ASSERT(settings!=0);
|
||||
Q_ASSERT(refreshInterval>=0);
|
||||
this->settings=settings;
|
||||
file=0;
|
||||
if (refreshInterval>0) {
|
||||
refreshTimer.start(refreshInterval,this);
|
||||
}
|
||||
flushTimer.start(1000,this);
|
||||
refreshSettings();
|
||||
}
|
||||
|
||||
|
||||
FileLogger::~FileLogger() {
|
||||
close();
|
||||
}
|
||||
|
||||
|
||||
void FileLogger::write(const LogMessage* logMessage) {
|
||||
// Try to write to the file
|
||||
if (file) {
|
||||
|
||||
// Write the message
|
||||
file->write(qPrintable(logMessage->toString(msgFormat,timestampFormat)));
|
||||
|
||||
// Flush error messages immediately, to ensure that no important message
|
||||
// gets lost when the program terinates abnormally.
|
||||
if (logMessage->getType()>=QtCriticalMsg) {
|
||||
file->flush();
|
||||
}
|
||||
|
||||
// Check for success
|
||||
if (file->error()) {
|
||||
close();
|
||||
qWarning("Cannot write to log file %s: %s",qPrintable(fileName),qPrintable(file->errorString()));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// Fall-back to the super class method, if writing failed
|
||||
if (!file) {
|
||||
Logger::write(logMessage);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
void FileLogger::open() {
|
||||
if (fileName.isEmpty()) {
|
||||
qWarning("Name of logFile is empty");
|
||||
}
|
||||
else {
|
||||
file=new QFile(fileName);
|
||||
if (!file->open(QIODevice::WriteOnly | QIODevice::Append | QIODevice::Text)) {
|
||||
qWarning("Cannot open log file %s: %s",qPrintable(fileName),qPrintable(file->errorString()));
|
||||
file=0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void FileLogger::close() {
|
||||
if (file) {
|
||||
file->close();
|
||||
delete file;
|
||||
file=0;
|
||||
}
|
||||
}
|
||||
|
||||
void FileLogger::rotate() {
|
||||
// count current number of existing backup files
|
||||
int count=0;
|
||||
forever {
|
||||
QFile bakFile(QString("%1.%2").arg(fileName).arg(count+1));
|
||||
if (bakFile.exists()) {
|
||||
++count;
|
||||
}
|
||||
else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Remove all old backup files that exceed the maximum number
|
||||
while (maxBackups>0 && count>=maxBackups) {
|
||||
QFile::remove(QString("%1.%2").arg(fileName).arg(count));
|
||||
--count;
|
||||
}
|
||||
|
||||
// Rotate backup files
|
||||
for (int i=count; i>0; --i) {
|
||||
QFile::rename(QString("%1.%2").arg(fileName).arg(i),QString("%1.%2").arg(fileName).arg(i+1));
|
||||
}
|
||||
|
||||
// Backup the current logfile
|
||||
QFile::rename(fileName,fileName+".1");
|
||||
}
|
||||
|
||||
|
||||
void FileLogger::timerEvent(QTimerEvent* event) {
|
||||
if (!event) {
|
||||
return;
|
||||
}
|
||||
else if (event->timerId()==refreshTimer.timerId()) {
|
||||
refreshSettings();
|
||||
}
|
||||
else if (event->timerId()==flushTimer.timerId() && file) {
|
||||
mutex.lock();
|
||||
|
||||
// Flush the I/O buffer
|
||||
file->flush();
|
||||
|
||||
// Rotate the file if it is too large
|
||||
if (maxSize>0 && file->size()>=maxSize) {
|
||||
close();
|
||||
rotate();
|
||||
open();
|
||||
}
|
||||
|
||||
mutex.unlock();
|
||||
}
|
||||
}
|
122
YACReaderLibrary/server/lib/bfLogging/filelogger.h
Normal file
122
YACReaderLibrary/server/lib/bfLogging/filelogger.h
Normal file
@ -0,0 +1,122 @@
|
||||
/**
|
||||
@file
|
||||
@author Stefan Frings
|
||||
*/
|
||||
|
||||
#ifndef FILELOGGER_H
|
||||
#define FILELOGGER_H
|
||||
|
||||
#include <QtGlobal>
|
||||
#include <QSettings>
|
||||
#include <QFile>
|
||||
#include <QMutex>
|
||||
#include <QBasicTimer>
|
||||
#include "logger.h"
|
||||
|
||||
/**
|
||||
Logger that uses a text file for output. Settings are read from a
|
||||
config file using a QSettings object. Config settings can be changed at runtime.
|
||||
<p>
|
||||
Example for the configuration settings:
|
||||
<code><pre>
|
||||
fileName=logs/QtWebApp.log
|
||||
maxSize=1000000
|
||||
maxBackups=2
|
||||
minLevel=0
|
||||
msgformat={timestamp} {typeNr} {type} thread={thread}: {msg}
|
||||
timestampFormat=dd.MM.yyyy hh:mm:ss.zzz
|
||||
bufferSize=0
|
||||
</pre></code>
|
||||
|
||||
- fileName is the name of the log file, relative to the directory of the settings file.
|
||||
In case of windows, if the settings are in the registry, the path is relative to the current
|
||||
working directory.
|
||||
- maxSize is the maximum size of that file in bytes. The file will be backed up and
|
||||
replaced by a new file if it becomes larger than this limit. Please note that
|
||||
the actual file size may become a little bit larger than this limit. Default is 0=unlimited.
|
||||
- maxBackups defines the number of backup files to keep. Default is 0=unlimited.
|
||||
- minLevel defines the minimum type of messages that are written (together with buffered messages) into the file. Defaults is 0=debug.
|
||||
- msgFormat defines the decoration of log messages, see LogMessage class. Default is "{timestamp} {type} {msg}".
|
||||
- timestampFormat defines the format of timestamps, see QDateTime::toString(). Default is "yyyy-MM-dd hh:mm:ss.zzz".
|
||||
- bufferSize defines the size of the buffer. Default is 0=disabled.
|
||||
|
||||
@see set() describes how to set logger variables
|
||||
@see LogMessage for a description of the message decoration.
|
||||
@see Logger for a descrition of the buffer.
|
||||
*/
|
||||
|
||||
class FileLogger : public Logger {
|
||||
Q_OBJECT
|
||||
Q_DISABLE_COPY(FileLogger)
|
||||
public:
|
||||
|
||||
/**
|
||||
Constructor.
|
||||
@param settings Configuration settings, usually stored in an INI file. Must not be 0.
|
||||
Settings are read from the current group, so the caller must have called settings->beginGroup().
|
||||
Because the group must not change during runtime, it is recommended to provide a
|
||||
separate QSettings instance to the logger that is not used by other parts of the program.
|
||||
@param refreshInterval Interval of checking for changed config settings in msec, or 0=disabled
|
||||
@param parent Parent object
|
||||
*/
|
||||
FileLogger(QSettings* settings, const int refreshInterval=10000, QObject* parent = 0);
|
||||
|
||||
/**
|
||||
Destructor. Closes the file.
|
||||
*/
|
||||
virtual ~FileLogger();
|
||||
|
||||
/** Write a message to the log file */
|
||||
virtual void write(const LogMessage* logMessage);
|
||||
|
||||
protected:
|
||||
|
||||
/**
|
||||
Handler for timer events.
|
||||
Refreshes config settings or synchronizes I/O buffer, depending on the event.
|
||||
This method is thread-safe.
|
||||
@param event used to distinguish between the two timers.
|
||||
*/
|
||||
void timerEvent(QTimerEvent* event);
|
||||
|
||||
private:
|
||||
|
||||
/** Configured name of the log file */
|
||||
QString fileName;
|
||||
|
||||
/** Configured maximum size of the file in bytes, or 0=unlimited */
|
||||
long maxSize;
|
||||
|
||||
/** Configured maximum number of backup files, or 0=unlimited */
|
||||
int maxBackups;
|
||||
|
||||
/** Pointer to the configuration settings */
|
||||
QSettings* settings;
|
||||
|
||||
/** Output file, or 0=disabled */
|
||||
QFile* file;
|
||||
|
||||
/** Timer for refreshing configuration settings */
|
||||
QBasicTimer refreshTimer;
|
||||
|
||||
/** Timer for flushing the file I/O buffer */
|
||||
QBasicTimer flushTimer;
|
||||
|
||||
/** Open the output file */
|
||||
void open();
|
||||
|
||||
/** Close the output file */
|
||||
void close();
|
||||
|
||||
/** Rotate files and delete some backups if there are too many */
|
||||
void rotate();
|
||||
|
||||
/**
|
||||
Refreshes the configuration settings.
|
||||
This method is thread-safe.
|
||||
*/
|
||||
void refreshSettings();
|
||||
|
||||
};
|
||||
|
||||
#endif // FILELOGGER_H
|
172
YACReaderLibrary/server/lib/bfLogging/logger.cpp
Normal file
172
YACReaderLibrary/server/lib/bfLogging/logger.cpp
Normal file
@ -0,0 +1,172 @@
|
||||
/**
|
||||
@file
|
||||
@author Stefan Frings
|
||||
*/
|
||||
|
||||
#include "logger.h"
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <QMutex>
|
||||
#include <QDateTime>
|
||||
#include <QThread>
|
||||
|
||||
Logger* Logger::defaultLogger=0;
|
||||
|
||||
|
||||
QThreadStorage<QHash<QString,QString>*> Logger::logVars;
|
||||
|
||||
|
||||
QThreadStorage<QList<LogMessage*>*> Logger::buffers;
|
||||
|
||||
|
||||
QMutex Logger::mutex;
|
||||
|
||||
|
||||
Logger::Logger(QObject* parent)
|
||||
: QObject(parent),
|
||||
msgFormat("{timestamp} {type} {msg}"),
|
||||
timestampFormat("dd.MM.yyyy hh:mm:ss.zzz"),
|
||||
minLevel(QtDebugMsg),
|
||||
bufferSize(0)
|
||||
{}
|
||||
|
||||
|
||||
Logger::Logger(const QString msgFormat, const QString timestampFormat, const QtMsgType minLevel, const int bufferSize, QObject* parent)
|
||||
:QObject(parent) {
|
||||
this->msgFormat=msgFormat;
|
||||
this->timestampFormat=timestampFormat;
|
||||
this->minLevel=minLevel;
|
||||
this->bufferSize=bufferSize;
|
||||
}
|
||||
|
||||
|
||||
void Logger::msgHandler(const QtMsgType type, const QString &message, const QString &file, const QString &function, const int line) {
|
||||
static QMutex recursiveMutex(QMutex::Recursive);
|
||||
static QMutex nonRecursiveMutex(QMutex::NonRecursive);
|
||||
|
||||
// Prevent multiple threads from calling this method simultaneoulsy.
|
||||
// But allow recursive calls, which is required to prevent a deadlock
|
||||
// if the logger itself produces an error message.
|
||||
recursiveMutex.lock();
|
||||
|
||||
// Fall back to stderr when this method has been called recursively.
|
||||
if (defaultLogger && nonRecursiveMutex.tryLock()) {
|
||||
defaultLogger->log(type, message, file, function, line);
|
||||
nonRecursiveMutex.unlock();
|
||||
}
|
||||
else {
|
||||
fputs(qPrintable(message),stderr);
|
||||
fflush(stderr);
|
||||
}
|
||||
|
||||
// Abort the program after logging a fatal message
|
||||
if (type>=QtFatalMsg) {
|
||||
//abort();
|
||||
}
|
||||
|
||||
recursiveMutex.unlock();
|
||||
}
|
||||
|
||||
|
||||
#if QT_VERSION >= 0x050000
|
||||
void Logger::msgHandler5(const QtMsgType type, const QMessageLogContext &context, const QString &message) {
|
||||
(void)(context); // suppress "unused parameter" warning
|
||||
msgHandler(type,message,context.file,context.function,context.line);
|
||||
}
|
||||
#else
|
||||
void Logger::msgHandler4(const QtMsgType type, const char* message) {
|
||||
msgHandler(type,message);
|
||||
}
|
||||
#endif
|
||||
|
||||
|
||||
Logger::~Logger() {
|
||||
if (defaultLogger==this) {
|
||||
#if QT_VERSION >= 0x050000
|
||||
qInstallMessageHandler(0);
|
||||
#else
|
||||
qInstallMsgHandler(0);
|
||||
#endif
|
||||
defaultLogger=0;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void Logger::write(const LogMessage* logMessage) {
|
||||
fputs(qPrintable(logMessage->toString(msgFormat,timestampFormat)),stderr);
|
||||
fflush(stderr);
|
||||
}
|
||||
|
||||
|
||||
void Logger::installMsgHandler() {
|
||||
defaultLogger=this;
|
||||
#if QT_VERSION >= 0x050000
|
||||
qInstallMessageHandler(msgHandler5);
|
||||
#else
|
||||
qInstallMsgHandler(msgHandler4);
|
||||
#endif
|
||||
}
|
||||
|
||||
|
||||
void Logger::set(const QString& name, const QString& value) {
|
||||
mutex.lock();
|
||||
if (!logVars.hasLocalData()) {
|
||||
logVars.setLocalData(new QHash<QString,QString>);
|
||||
}
|
||||
logVars.localData()->insert(name,value);
|
||||
mutex.unlock();
|
||||
}
|
||||
|
||||
|
||||
void Logger::clear(const bool buffer, const bool variables) {
|
||||
mutex.lock();
|
||||
if (buffer && buffers.hasLocalData()) {
|
||||
QList<LogMessage*>* buffer=buffers.localData();
|
||||
while (buffer && !buffer->isEmpty()) {
|
||||
LogMessage* logMessage=buffer->takeLast();
|
||||
delete logMessage;
|
||||
}
|
||||
}
|
||||
if (variables && logVars.hasLocalData()) {
|
||||
logVars.localData()->clear();
|
||||
}
|
||||
mutex.unlock();
|
||||
}
|
||||
|
||||
|
||||
void Logger::log(const QtMsgType type, const QString& message, const QString &file, const QString &function, const int line) {
|
||||
mutex.lock();
|
||||
|
||||
// If the buffer is enabled, write the message into it
|
||||
if (bufferSize>0) {
|
||||
// Create new thread local buffer, if necessary
|
||||
if (!buffers.hasLocalData()) {
|
||||
buffers.setLocalData(new QList<LogMessage*>());
|
||||
}
|
||||
QList<LogMessage*>* buffer=buffers.localData();
|
||||
// Append the decorated log message
|
||||
LogMessage* logMessage=new LogMessage(type,message,logVars.localData(),file,function,line);
|
||||
buffer->append(logMessage);
|
||||
// Delete oldest message if the buffer became too large
|
||||
if (buffer->size()>bufferSize) {
|
||||
delete buffer->takeFirst();
|
||||
}
|
||||
// If the type of the message is high enough, print the whole buffer
|
||||
if (type>=minLevel) {
|
||||
while (!buffer->isEmpty()) {
|
||||
LogMessage* logMessage=buffer->takeFirst();
|
||||
write(logMessage);
|
||||
delete logMessage;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Buffer is disabled, print the message if the type is high enough
|
||||
else {
|
||||
if (type>=minLevel) {
|
||||
LogMessage logMessage(type,message,logVars.localData(),file,function,line);
|
||||
write(&logMessage);
|
||||
}
|
||||
}
|
||||
mutex.unlock();
|
||||
}
|
183
YACReaderLibrary/server/lib/bfLogging/logger.h
Normal file
183
YACReaderLibrary/server/lib/bfLogging/logger.h
Normal file
@ -0,0 +1,183 @@
|
||||
/**
|
||||
@file
|
||||
@author Stefan Frings
|
||||
*/
|
||||
|
||||
#ifndef LOGGER_H
|
||||
#define LOGGER_H
|
||||
|
||||
#include <QObject>
|
||||
|
||||
#include <QtGlobal>
|
||||
#include <QThreadStorage>
|
||||
#include <QHash>
|
||||
#include <QStringList>
|
||||
#include <QMutex>
|
||||
#include "logmessage.h"
|
||||
|
||||
/**
|
||||
Decorates and writes log messages to the console, stderr.
|
||||
<p>
|
||||
The decorator uses a predefined msgFormat string to enrich log messages
|
||||
with additional information (e.g. timestamp).
|
||||
<p>
|
||||
The msgFormat string and also the message text may contain additional
|
||||
variable names in the form <i>{name}</i> that are filled by values
|
||||
taken from a static thread local dictionary.
|
||||
<p>
|
||||
The logger keeps a configurable number of messages in a ring-buffer.
|
||||
A log message with a severity >= minLevel flushes the buffer,
|
||||
so the stored messages get written out. If the buffer is disabled, then
|
||||
only messages with severity >= minLevel are written out.
|
||||
<p>
|
||||
If you enable the buffer and use minLevel=2, then the application logs
|
||||
only errors together with some buffered debug messages. But as long no
|
||||
error occurs, nothing gets written out.
|
||||
<p>
|
||||
Each thread has it's own buffer.
|
||||
<p>
|
||||
The logger can be registered to handle messages from
|
||||
the static global functions qDebug(), qWarning(), qCritical() and qFatal().
|
||||
|
||||
@see set() describes how to set logger variables
|
||||
@see LogMessage for a description of the message decoration.
|
||||
@warning You should prefer a derived class, for example FileLogger,
|
||||
because logging to the console is less useful.
|
||||
*/
|
||||
|
||||
class Logger : public QObject {
|
||||
Q_OBJECT
|
||||
Q_DISABLE_COPY(Logger)
|
||||
public:
|
||||
|
||||
/**
|
||||
Constructor.
|
||||
Uses the same defaults as the other constructor.
|
||||
@param parent Parent object
|
||||
*/
|
||||
Logger(QObject* parent);
|
||||
|
||||
|
||||
/**
|
||||
Constructor.
|
||||
@param msgFormat Format of the decoration, e.g. "{timestamp} {type} thread={thread}: {msg}"
|
||||
@param timestampFormat Format of timestamp, e.g. "dd.MM.yyyy hh:mm:ss.zzz"
|
||||
@param minLevel Minimum severity that genertes an output (0=debug, 1=warning, 2=critical, 3=fatal).
|
||||
@param bufferSize Size of the backtrace buffer, number of messages per thread. 0=disabled.
|
||||
@param parent Parent object
|
||||
@see LogMessage for a description of the message decoration.
|
||||
*/
|
||||
Logger(const QString msgFormat="{timestamp} {type} {msg}", const QString timestampFormat="dd.MM.yyyy hh:mm:ss.zzz", const QtMsgType minLevel=QtDebugMsg, const int bufferSize=0, QObject* parent = 0);
|
||||
|
||||
/** Destructor */
|
||||
virtual ~Logger();
|
||||
|
||||
/**
|
||||
Decorate and log the message, if type>=minLevel.
|
||||
This method is thread safe.
|
||||
@param type Message type (level)
|
||||
@param message Message text
|
||||
@param file Name of the source file where the message was generated (usually filled with the macro __FILE__)
|
||||
@param function Name of the function where the message was generated (usually filled with the macro __LINE__)
|
||||
@param line Line Number of the source file, where the message was generated (usually filles with the macro __func__ or __FUNCTION__)
|
||||
@see LogMessage for a description of the message decoration.
|
||||
*/
|
||||
virtual void log(const QtMsgType type, const QString& message, const QString &file="", const QString &function="", const int line=0);
|
||||
|
||||
/**
|
||||
Installs this logger as the default message handler, so it
|
||||
can be used through the global static logging functions (e.g. qDebug()).
|
||||
*/
|
||||
void installMsgHandler();
|
||||
|
||||
/**
|
||||
Sets a thread-local variable that may be used to decorate log messages.
|
||||
This method is thread safe.
|
||||
@param name Name of the variable
|
||||
@param value Value of the variable
|
||||
*/
|
||||
static void set(const QString& name, const QString& value);
|
||||
|
||||
/**
|
||||
Clear the thread-local data of the current thread.
|
||||
@param buffer Whether to clear the backtrace buffer
|
||||
@param variables Whether to clear the log variables
|
||||
*/
|
||||
static void clear(const bool buffer=true, const bool variables=true);
|
||||
|
||||
protected:
|
||||
|
||||
/** Format string for message decoration */
|
||||
QString msgFormat;
|
||||
|
||||
/** Format string of timestamps */
|
||||
QString timestampFormat;
|
||||
|
||||
/** Minimum level of message types that are written out */
|
||||
QtMsgType minLevel;
|
||||
|
||||
/** Size of backtrace buffer, number of messages per thread. 0=disabled */
|
||||
int bufferSize;
|
||||
|
||||
/** Used to synchronize access to the static members */
|
||||
static QMutex mutex;
|
||||
|
||||
/**
|
||||
Decorate and write a log message to stderr. Override this method
|
||||
to provide a different output medium.
|
||||
*/
|
||||
virtual void write(const LogMessage* logMessage);
|
||||
|
||||
private:
|
||||
|
||||
/** Pointer to the default logger, used by msgHandler() */
|
||||
static Logger* defaultLogger;
|
||||
|
||||
/**
|
||||
Message Handler for the global static logging functions (e.g. qDebug()).
|
||||
Forward calls to the default logger.
|
||||
<p>
|
||||
In case of a fatal message, the program will abort.
|
||||
Variables in the in the message are replaced by their values.
|
||||
This method is thread safe.
|
||||
@param type Message type (level)
|
||||
@param message Message text
|
||||
@param file Name of the source file where the message was generated (usually filled with the macro __FILE__)
|
||||
@param function Name of the function where the message was generated (usually filled with the macro __LINE__)
|
||||
@param line Line Number of the source file, where the message was generated (usually filles with the macro __func__ or __FUNCTION__)
|
||||
*/
|
||||
static void msgHandler(const QtMsgType type, const QString &message, const QString &file="", const QString &function="", const int line=0);
|
||||
|
||||
|
||||
#if QT_VERSION >= 0x050000
|
||||
|
||||
/**
|
||||
Wrapper for QT version 5.
|
||||
@param type Message type (level)
|
||||
@param context Message context
|
||||
@param message Message text
|
||||
@see msgHandler()
|
||||
*/
|
||||
static void msgHandler5(const QtMsgType type, const QMessageLogContext& context, const QString &message);
|
||||
|
||||
#else
|
||||
|
||||
/**
|
||||
Wrapper for QT version 4.
|
||||
@param type Message type (level)
|
||||
@param message Message text
|
||||
@see msgHandler()
|
||||
*/
|
||||
static void msgHandler4(const QtMsgType type, const char * message);
|
||||
|
||||
#endif
|
||||
|
||||
/** Thread local variables to be used in log messages */
|
||||
static QThreadStorage<QHash<QString,QString>*> logVars;
|
||||
|
||||
/** Thread local backtrace buffers */
|
||||
static QThreadStorage<QList<LogMessage*>*> buffers;
|
||||
|
||||
};
|
||||
|
||||
#endif // LOGGER_H
|
75
YACReaderLibrary/server/lib/bfLogging/logmessage.cpp
Normal file
75
YACReaderLibrary/server/lib/bfLogging/logmessage.cpp
Normal file
@ -0,0 +1,75 @@
|
||||
/**
|
||||
@file
|
||||
@author Stefan Frings
|
||||
*/
|
||||
|
||||
#include "logmessage.h"
|
||||
#include <QThread>
|
||||
|
||||
LogMessage::LogMessage(const QtMsgType type, const QString& message, QHash<QString, QString>* logVars, const QString &file, const QString &function, const int line) {
|
||||
this->type=type;
|
||||
this->message=message;
|
||||
this->file=file;
|
||||
this->function=function;
|
||||
this->line=line;
|
||||
timestamp=QDateTime::currentDateTime();
|
||||
threadId=QThread::currentThreadId();
|
||||
|
||||
// Copy the logVars if not null,
|
||||
// so that later changes in the original do not affect the copy
|
||||
if (logVars) {
|
||||
this->logVars=*logVars;
|
||||
}
|
||||
}
|
||||
|
||||
QString LogMessage::toString(const QString& msgFormat, const QString& timestampFormat) const {
|
||||
QString decorated=msgFormat+"\n";
|
||||
decorated.replace("{msg}",message);
|
||||
|
||||
if (decorated.contains("{timestamp}")) {
|
||||
decorated.replace("{timestamp}",timestamp.toString(timestampFormat));
|
||||
}
|
||||
|
||||
QString typeNr;
|
||||
typeNr.setNum(type);
|
||||
decorated.replace("{typeNr}",typeNr);
|
||||
|
||||
switch (type) {
|
||||
case QtDebugMsg:
|
||||
decorated.replace("{type}","DEBUG");
|
||||
break;
|
||||
case QtWarningMsg:
|
||||
decorated.replace("{type}","WARNING");
|
||||
break;
|
||||
case QtCriticalMsg:
|
||||
decorated.replace("{type}","CRITICAL");
|
||||
break;
|
||||
case QtFatalMsg:
|
||||
decorated.replace("{type}","FATAL");
|
||||
break;
|
||||
default:
|
||||
decorated.replace("{type}",typeNr);
|
||||
}
|
||||
|
||||
decorated.replace("{file}",file);
|
||||
decorated.replace("{function}",function);
|
||||
decorated.replace("{line}",QString::number(line));
|
||||
|
||||
QString threadId;
|
||||
threadId.setNum((quint64)QThread::currentThreadId()); // change to (qint64) for 64bit Mac OS
|
||||
decorated.replace("{thread}",threadId);
|
||||
|
||||
// Fill in variables
|
||||
if (decorated.contains("{") && !logVars.isEmpty()) {
|
||||
QList<QString> keys=logVars.keys();
|
||||
foreach (QString key, keys) {
|
||||
decorated.replace("{"+key+"}",logVars.value(key));
|
||||
}
|
||||
}
|
||||
|
||||
return decorated;
|
||||
}
|
||||
|
||||
QtMsgType LogMessage::getType() const {
|
||||
return type;
|
||||
}
|
91
YACReaderLibrary/server/lib/bfLogging/logmessage.h
Normal file
91
YACReaderLibrary/server/lib/bfLogging/logmessage.h
Normal file
@ -0,0 +1,91 @@
|
||||
/**
|
||||
@file
|
||||
@author Stefan Frings
|
||||
*/
|
||||
|
||||
#ifndef LOGMESSAGE_H
|
||||
#define LOGMESSAGE_H
|
||||
|
||||
#include <QtGlobal>
|
||||
#include <QDateTime>
|
||||
#include <QHash>
|
||||
|
||||
/**
|
||||
Represents a single log message together with some data
|
||||
that are used to decorate the log message.
|
||||
|
||||
The following variables may be used in the message and in msgFormat:
|
||||
|
||||
- {timestamp} Date and time of creation
|
||||
- {typeNr} Type of the message in numeric format (0-3)
|
||||
- {type} Type of the message in string format (DEBUG, WARNING, CRITICAL, FATAL)
|
||||
- {thread} ID number of the thread
|
||||
- {msg} Message text (only useable in msgFormat)
|
||||
- {file} Filename where the message was generated #
|
||||
- {function} Function where the message was generated #
|
||||
- {line} Line number where the message was generated #
|
||||
- {xxx} For any user-defined logger variable
|
||||
|
||||
# The macros qDebug()...qFatal() dont fill these variable in case of QT versions before 5.0.
|
||||
*/
|
||||
|
||||
class LogMessage
|
||||
{
|
||||
Q_DISABLE_COPY(LogMessage)
|
||||
public:
|
||||
|
||||
/**
|
||||
Constructor. All parameters are copied, so that later changes to them do not
|
||||
affect this object.
|
||||
@param type Type of the message
|
||||
@param message Message text
|
||||
@param logVars Logger variables, 0 is allowed
|
||||
@param file Name of the source file where the message was generated
|
||||
@param function Name of the function where the message was generated
|
||||
@param line Line Number of the source file, where the message was generated
|
||||
*/
|
||||
LogMessage(const QtMsgType type, const QString& message, QHash<QString,QString>* logVars, const QString &file, const QString &function, const int line);
|
||||
|
||||
/**
|
||||
Returns the log message as decorated string.
|
||||
@param msgFormat Format of the decoration. May contain variables and static text,
|
||||
e.g. "{timestamp} {type} thread={thread}: {msg}".
|
||||
@param timestampFormat Format of timestamp, e.g. "dd.MM.yyyy hh:mm:ss.zzz", see QDateTime::toString().
|
||||
@see QDatetime for a description of the timestamp format pattern
|
||||
*/
|
||||
QString toString(const QString& msgFormat, const QString& timestampFormat) const;
|
||||
|
||||
/**
|
||||
Get the message type.
|
||||
*/
|
||||
QtMsgType getType() const;
|
||||
|
||||
private:
|
||||
|
||||
/** Logger variables */
|
||||
QHash<QString,QString> logVars;
|
||||
|
||||
/** Date and time of creation */
|
||||
QDateTime timestamp;
|
||||
|
||||
/** Type of the message */
|
||||
QtMsgType type;
|
||||
|
||||
/** ID number of the thread */
|
||||
Qt::HANDLE threadId;
|
||||
|
||||
/** Message text */
|
||||
QString message;
|
||||
|
||||
/** Filename where the message was generated */
|
||||
QString file;
|
||||
|
||||
/** Function name where the message was generated */
|
||||
QString function;
|
||||
|
||||
/** Line number where the message was generated */
|
||||
int line;
|
||||
|
||||
};
|
||||
|
||||
#endif // LOGMESSAGE_H
|
@ -0,0 +1,7 @@
|
||||
INCLUDEPATH += $$PWD
|
||||
DEPENDPATH += $$PWD
|
||||
|
||||
HEADERS += $$PWD/template.h $$PWD/templateloader.h $$PWD/templatecache.h
|
||||
SOURCES += $$PWD/template.cpp $$PWD/templateloader.cpp $$PWD/templatecache.cpp
|
||||
|
||||
OTHER_FILES += $$PWD/../doc/readme.txt
|
188
YACReaderLibrary/server/lib/bfTemplateEngine/template.cpp
Normal file
188
YACReaderLibrary/server/lib/bfTemplateEngine/template.cpp
Normal file
@ -0,0 +1,188 @@
|
||||
/**
|
||||
@file
|
||||
@author Stefan Frings
|
||||
*/
|
||||
|
||||
#include "template.h"
|
||||
#include <QFileInfo>
|
||||
|
||||
Template::Template(QString source, QString sourceName)
|
||||
: QString(source) {
|
||||
this->sourceName=sourceName;
|
||||
this->warnings=false;
|
||||
}
|
||||
|
||||
Template::Template(QFile& file, QTextCodec* textCodec) {
|
||||
this->warnings=false;
|
||||
sourceName=QFileInfo(file.fileName()).baseName();
|
||||
if (!file.isOpen()) {
|
||||
file.open(QFile::ReadOnly | QFile::Text);
|
||||
}
|
||||
QByteArray data=file.readAll();
|
||||
file.close();
|
||||
if (data.size()==0 || file.error()) {
|
||||
qCritical("Template: cannot read from %s, %s",qPrintable(sourceName),qPrintable(file.errorString()));
|
||||
append(textCodec->toUnicode(data));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
int Template::setVariable(QString name, QString value) {
|
||||
int count=0;
|
||||
QString variable="{"+name+"}";
|
||||
int start=indexOf(variable);
|
||||
while (start>=0) {
|
||||
replace(start, variable.length(), value);
|
||||
count++;
|
||||
start=indexOf(variable,start+value.length());
|
||||
}
|
||||
if (count==0 && warnings) {
|
||||
qWarning("Template: missing variable %s in %s",qPrintable(variable),qPrintable(sourceName));
|
||||
}
|
||||
return count;
|
||||
}
|
||||
|
||||
int Template::setCondition(QString name, bool value) {
|
||||
int count=0;
|
||||
QString startTag=QString("{if %1}").arg(name);
|
||||
QString elseTag=QString("{else %1}").arg(name);
|
||||
QString endTag=QString("{end %1}").arg(name);
|
||||
// search for if-else-end
|
||||
int start=indexOf(startTag);
|
||||
while (start>=0) {
|
||||
int end=indexOf(endTag,start+startTag.length());
|
||||
if (end>=0) {
|
||||
count++;
|
||||
int ellse=indexOf(elseTag,start+startTag.length());
|
||||
if (ellse>start && ellse<end) { // there is an else part
|
||||
if (value==true) {
|
||||
QString truePart=mid(start+startTag.length(), ellse-start-startTag.length());
|
||||
replace(start, end-start+endTag.length(), truePart);
|
||||
}
|
||||
else { // value==false
|
||||
QString falsePart=mid(ellse+elseTag.length(), end-ellse-elseTag.length());
|
||||
replace(start, end-start+endTag.length(), falsePart);
|
||||
}
|
||||
}
|
||||
else if (value==true) { // and no else part
|
||||
QString truePart=mid(start+startTag.length(), end-start-startTag.length());
|
||||
replace(start, end-start+endTag.length(), truePart);
|
||||
}
|
||||
else { // value==false and no else part
|
||||
replace(start, end-start+endTag.length(), "");
|
||||
}
|
||||
start=indexOf(startTag,start);
|
||||
}
|
||||
else {
|
||||
qWarning("Template: missing condition end %s in %s",qPrintable(endTag),qPrintable(sourceName));
|
||||
}
|
||||
}
|
||||
// search for ifnot-else-end
|
||||
QString startTag2="{ifnot "+name+"}";
|
||||
start=indexOf(startTag2);
|
||||
while (start>=0) {
|
||||
int end=indexOf(endTag,start+startTag2.length());
|
||||
if (end>=0) {
|
||||
count++;
|
||||
int ellse=indexOf(elseTag,start+startTag2.length());
|
||||
if (ellse>start && ellse<end) { // there is an else part
|
||||
if (value==false) {
|
||||
QString falsePart=mid(start+startTag2.length(), ellse-start-startTag2.length());
|
||||
replace(start, end-start+endTag.length(), falsePart);
|
||||
}
|
||||
else { // value==true
|
||||
QString truePart=mid(ellse+elseTag.length(), end-ellse-elseTag.length());
|
||||
replace(start, end-start+endTag.length(), truePart);
|
||||
}
|
||||
}
|
||||
else if (value==false) { // and no else part
|
||||
QString falsePart=mid(start+startTag2.length(), end-start-startTag2.length());
|
||||
replace(start, end-start+endTag.length(), falsePart);
|
||||
}
|
||||
else { // value==true and no else part
|
||||
replace(start, end-start+endTag.length(), "");
|
||||
}
|
||||
start=indexOf(startTag2,start);
|
||||
}
|
||||
else {
|
||||
qWarning("Template: missing condition end %s in %s",qPrintable(endTag),qPrintable(sourceName));
|
||||
}
|
||||
}
|
||||
if (count==0 && warnings) {
|
||||
qWarning("Template: missing condition %s or %s in %s",qPrintable(startTag),qPrintable(startTag2),qPrintable(sourceName));
|
||||
}
|
||||
return count;
|
||||
}
|
||||
|
||||
int Template::loop(QString name, int repetitions) {
|
||||
//Q_ASSERT(repetitions>=0);
|
||||
int count=0;
|
||||
QString startTag="{loop "+name+"}";
|
||||
QString elseTag="{else "+name+"}";
|
||||
QString endTag="{end "+name+"}";
|
||||
// search for loop-else-end
|
||||
int start=indexOf(startTag);
|
||||
while (start>=0) {
|
||||
int end=indexOf(endTag,start+startTag.length());
|
||||
if (end>=0) {
|
||||
count++;
|
||||
int ellse=indexOf(elseTag,start+startTag.length());
|
||||
if (ellse>start && ellse<end) { // there is an else part
|
||||
if (repetitions>0) {
|
||||
QString loopPart=mid(start+startTag.length(), ellse-start-startTag.length());
|
||||
QString insertMe;
|
||||
for (int i=0; i<repetitions; ++i) {
|
||||
// number variables, conditions and sub-loop within the loop
|
||||
QString nameNum=name+QString::number(i);
|
||||
QString s=loopPart;
|
||||
s.replace(QString("{%1.").arg(name), QString("{%1.").arg(nameNum));
|
||||
s.replace(QString("{if %1.").arg(name), QString("{if %1.").arg(nameNum));
|
||||
s.replace(QString("{ifnot %1.").arg(name), QString("{ifnot %1.").arg(nameNum));
|
||||
s.replace(QString("{else %1.").arg(name), QString("{else %1.").arg(nameNum));
|
||||
s.replace(QString("{end %1.").arg(name), QString("{end %1.").arg(nameNum));
|
||||
s.replace(QString("{loop %1.").arg(name), QString("{loop %1.").arg(nameNum));
|
||||
insertMe.append(s);
|
||||
}
|
||||
replace(start, end-start+endTag.length(), insertMe);
|
||||
}
|
||||
else { // repetitions==0
|
||||
QString elsePart=mid(ellse+elseTag.length(), end-ellse-elseTag.length());
|
||||
replace(start, end-start+endTag.length(), elsePart);
|
||||
}
|
||||
}
|
||||
else if (repetitions>0) { // and no else part
|
||||
QString loopPart=mid(start+startTag.length(), end-start-startTag.length());
|
||||
QString insertMe;
|
||||
for (int i=0; i<repetitions; ++i) {
|
||||
// number variables, conditions and sub-loop within the loop
|
||||
QString nameNum=name+QString::number(i);
|
||||
QString s=loopPart;
|
||||
s.replace(QString("{%1.").arg(name), QString("{%1.").arg(nameNum));
|
||||
s.replace(QString("{if %1.").arg(name), QString("{if %1.").arg(nameNum));
|
||||
s.replace(QString("{ifnot %1.").arg(name), QString("{ifnot %1.").arg(nameNum));
|
||||
s.replace(QString("{else %1.").arg(name), QString("{else %1.").arg(nameNum));
|
||||
s.replace(QString("{end %1.").arg(name), QString("{end %1.").arg(nameNum));
|
||||
s.replace(QString("{loop %1.").arg(name), QString("{loop %1.").arg(nameNum));
|
||||
insertMe.append(s);
|
||||
}
|
||||
replace(start, end-start+endTag.length(), insertMe);
|
||||
}
|
||||
else { // repetitions==0 and no else part
|
||||
replace(start, end-start+endTag.length(), "");
|
||||
}
|
||||
start=indexOf(startTag,start);
|
||||
}
|
||||
else {
|
||||
qWarning("Template: missing loop end %s in %s",qPrintable(endTag),qPrintable(sourceName));
|
||||
}
|
||||
}
|
||||
if (count==0 && warnings) {
|
||||
qWarning("Template: missing loop %s in %s",qPrintable(startTag),qPrintable(sourceName));
|
||||
}
|
||||
return count;
|
||||
}
|
||||
|
||||
void Template::enableWarnings(bool enable) {
|
||||
warnings=enable;
|
||||
}
|
||||
|
167
YACReaderLibrary/server/lib/bfTemplateEngine/template.h
Normal file
167
YACReaderLibrary/server/lib/bfTemplateEngine/template.h
Normal file
@ -0,0 +1,167 @@
|
||||
/**
|
||||
@file
|
||||
@author Stefan Frings
|
||||
*/
|
||||
|
||||
#ifndef TEMPLATE_H
|
||||
#define TEMPLATE_H
|
||||
|
||||
#include <QString>
|
||||
#include <QRegExp>
|
||||
#include <QIODevice>
|
||||
#include <QTextCodec>
|
||||
#include <QFile>
|
||||
#include <QString>
|
||||
|
||||
/**
|
||||
Enhanced version of QString for template processing. Templates
|
||||
are usually loaded from files, but may also be loaded from
|
||||
prepared Strings.
|
||||
Example template file:
|
||||
<p><code><pre>
|
||||
Hello {username}, how are you?
|
||||
|
||||
{if locked}
|
||||
Your account is locked.
|
||||
{else locked}
|
||||
Welcome on our system.
|
||||
{end locked}
|
||||
|
||||
The following users are on-line:
|
||||
Username Time
|
||||
{loop user}
|
||||
{user.name} {user.time}
|
||||
{end user}
|
||||
</pre></code></p>
|
||||
<p>
|
||||
Example code to fill this template:
|
||||
<p><code><pre>
|
||||
Template t(QFile("test.tpl"),QTextCode::codecForName("UTF-8"));
|
||||
t.setVariable("user", "Stefan");
|
||||
t.setCondition("locked",false);
|
||||
t.loop("user",2);
|
||||
t.setVariable("user0.name,"Markus");
|
||||
t.setVariable("user0.time,"8:30");
|
||||
t.setVariable("user1.name,"Roland");
|
||||
t.setVariable("user1.time,"8:45");
|
||||
</pre></code></p>
|
||||
<p>
|
||||
The code example above shows how variable within loops are numbered.
|
||||
Counting starts with 0. Loops can be nested, for example:
|
||||
<p><code><pre>
|
||||
<table>
|
||||
{loop row}
|
||||
<tr>
|
||||
{loop row.column}
|
||||
<td>{row.column.value}</td>
|
||||
{end row.column}
|
||||
</tr>
|
||||
{end row}
|
||||
</table>
|
||||
</pre></code></p>
|
||||
<p>
|
||||
Example code to fill this nested loop with 3 rows and 4 columns:
|
||||
<p><code><pre>
|
||||
t.loop("row",3);
|
||||
|
||||
t.loop("row0.column",4);
|
||||
t.setVariable("row0.column0.value","a");
|
||||
t.setVariable("row0.column1.value","b");
|
||||
t.setVariable("row0.column2.value","c");
|
||||
t.setVariable("row0.column3.value","d");
|
||||
|
||||
t.loop("row1.column",4);
|
||||
t.setVariable("row1.column0.value","e");
|
||||
t.setVariable("row1.column1.value","f");
|
||||
t.setVariable("row1.column2.value","g");
|
||||
t.setVariable("row1.column3.value","h");
|
||||
|
||||
t.loop("row2.column",4);
|
||||
t.setVariable("row2.column0.value","i");
|
||||
t.setVariable("row2.column1.value","j");
|
||||
t.setVariable("row2.column2.value","k");
|
||||
t.setVariable("row2.column3.value","l");
|
||||
</pre></code></p>
|
||||
@see TemplateLoader
|
||||
@see TemplateCache
|
||||
*/
|
||||
|
||||
class Template : public QString {
|
||||
public:
|
||||
|
||||
/**
|
||||
Constructor that reads the template from a string.
|
||||
@param source The template source text
|
||||
@param sourceName Name of the source file, used for logging
|
||||
*/
|
||||
Template(QString source, QString sourceName);
|
||||
|
||||
/**
|
||||
Constructor that reads the template from a file. Note that this class does not
|
||||
cache template files by itself, so using this constructor is only recommended
|
||||
to be used on local filesystem.
|
||||
@param file File that provides the source text
|
||||
@param textCodec Encoding of the source
|
||||
@see TemplateLoader
|
||||
@see TemplateCache
|
||||
*/
|
||||
Template(QFile& file, QTextCodec* textCodec);
|
||||
|
||||
/**
|
||||
Replace a variable by the given value.
|
||||
Affects tags with the syntax
|
||||
|
||||
- {name}
|
||||
|
||||
After settings the
|
||||
value of a variable, the variable does not exist anymore,
|
||||
it it cannot be changed multiple times.
|
||||
@param name name of the variable
|
||||
@param value new value
|
||||
@return The count of variables that have been processed
|
||||
*/
|
||||
int setVariable(QString name, QString value);
|
||||
|
||||
/**
|
||||
Set a condition. This affects tags with the syntax
|
||||
|
||||
- {if name}...{end name}
|
||||
- {if name}...{else name}...{end name}
|
||||
- {ifnot name}...{end name}
|
||||
- {ifnot name}...{else name}...{end name}
|
||||
|
||||
@param name Name of the condition
|
||||
@param value Value of the condition
|
||||
@return The count of conditions that have been processed
|
||||
*/
|
||||
int setCondition(QString name, bool value);
|
||||
|
||||
/**
|
||||
Set number of repetitions of a loop.
|
||||
This affects tags with the syntax
|
||||
|
||||
- {loop name}...{end name}
|
||||
- {loop name}...{else name}...{end name}
|
||||
|
||||
@param name Name of the loop
|
||||
@param repetitions The number of repetitions
|
||||
@return The number of loops that have been processed
|
||||
*/
|
||||
int loop(QString name, int repetitions);
|
||||
|
||||
/**
|
||||
Enable warnings for missing tags
|
||||
@param enable Warnings are enabled, if true
|
||||
*/
|
||||
void enableWarnings(bool enable=true);
|
||||
|
||||
private:
|
||||
|
||||
/** Name of the source file */
|
||||
QString sourceName;
|
||||
|
||||
/** Enables warnings, if true */
|
||||
bool warnings;
|
||||
};
|
||||
|
||||
#endif // TEMPLATE_H
|
@ -0,0 +1,30 @@
|
||||
#include "templatecache.h"
|
||||
#include <QDateTime>
|
||||
#include <QStringList>
|
||||
#include <QSet>
|
||||
|
||||
TemplateCache::TemplateCache(QSettings* settings, QObject* parent)
|
||||
:TemplateLoader(settings,parent)
|
||||
{
|
||||
cache.setMaxCost(settings->value("cacheSize","160000").toInt());//este tama<6D>o antes era 1000000
|
||||
cacheTimeout=settings->value("cacheTime","60000").toInt();
|
||||
qDebug("TemplateCache: timeout=%i, size=%i",cacheTimeout,cache.maxCost());
|
||||
}
|
||||
|
||||
QString TemplateCache::tryFile(QString localizedName) {
|
||||
qint64 now=QDateTime::currentMSecsSinceEpoch();
|
||||
// search in cache
|
||||
qDebug("TemplateCache: trying cached %s",qPrintable(localizedName));
|
||||
CacheEntry* entry=cache.object(localizedName);
|
||||
if (entry && (cacheTimeout==0 || entry->created>now-cacheTimeout)) {
|
||||
return entry->document;
|
||||
}
|
||||
// search on filesystem
|
||||
entry=new CacheEntry();
|
||||
entry->created=now;
|
||||
entry->document=TemplateLoader::tryFile(localizedName);
|
||||
// Store in cache even when the file did not exist, to remember that there is no such file
|
||||
cache.insert(localizedName,entry,entry->document.size());
|
||||
return entry->document;
|
||||
}
|
||||
|
77
YACReaderLibrary/server/lib/bfTemplateEngine/templatecache.h
Normal file
77
YACReaderLibrary/server/lib/bfTemplateEngine/templatecache.h
Normal file
@ -0,0 +1,77 @@
|
||||
#ifndef TEMPLATECACHE_H
|
||||
#define TEMPLATECACHE_H
|
||||
|
||||
#include "templateloader.h"
|
||||
#include <QCache>
|
||||
|
||||
/**
|
||||
Caching template loader, reduces the amount of I/O and improves performance
|
||||
on remote file systems. The cache has a limited size, it prefers to keep
|
||||
the last recently used files. Optionally, the maximum time of cached entries
|
||||
can be defined to enforce a reload of the template file after a while.
|
||||
<p>
|
||||
In case of local file system, the use of this cache is optionally, since
|
||||
the operating system caches files already.
|
||||
<p>
|
||||
Loads localized versions of template files. If the caller requests a file with the
|
||||
name "index" and the suffix is ".tpl" and the requested locale is "de_DE, de, en-US",
|
||||
then files are searched in the following order:
|
||||
|
||||
- index-de_DE.tpl
|
||||
- index-de.tpl
|
||||
- index-en_US.tpl
|
||||
- index-en.tpl
|
||||
- index.tpl
|
||||
<p>
|
||||
The following settings are required:
|
||||
<code><pre>
|
||||
path=.
|
||||
suffix=.tpl
|
||||
encoding=UTF-8
|
||||
cacheSize=1000000
|
||||
cacheTime=60000
|
||||
</pre></code>
|
||||
The path is relative to the directory of the config file. In case of windows, if the
|
||||
settings are in the registry, the path is relative to the current working directory.
|
||||
<p>
|
||||
Files are cached as long as possible, when cacheTime=0.
|
||||
@see TemplateLoader
|
||||
*/
|
||||
|
||||
class TemplateCache : public TemplateLoader {
|
||||
Q_OBJECT
|
||||
Q_DISABLE_COPY(TemplateCache);
|
||||
public:
|
||||
|
||||
/**
|
||||
Constructor.
|
||||
@param settings configurations settings
|
||||
@param parent Parent object
|
||||
*/
|
||||
TemplateCache(QSettings* settings, QObject* parent=0);
|
||||
|
||||
protected:
|
||||
|
||||
/**
|
||||
Try to get a file from cache or filesystem.
|
||||
@param localizedName Name of the template with locale to find
|
||||
@return The template document, or empty string if not found
|
||||
*/
|
||||
virtual QString tryFile(QString localizedName);
|
||||
|
||||
private:
|
||||
|
||||
struct CacheEntry {
|
||||
QString document;
|
||||
qint64 created;
|
||||
};
|
||||
|
||||
/** Timeout for each cached file */
|
||||
int cacheTimeout;
|
||||
|
||||
/** Cache storage */
|
||||
QCache<QString,CacheEntry> cache;
|
||||
|
||||
};
|
||||
|
||||
#endif // TEMPLATECACHE_H
|
109
YACReaderLibrary/server/lib/bfTemplateEngine/templateloader.cpp
Normal file
109
YACReaderLibrary/server/lib/bfTemplateEngine/templateloader.cpp
Normal file
@ -0,0 +1,109 @@
|
||||
/**
|
||||
@file
|
||||
@author Stefan Frings
|
||||
*/
|
||||
|
||||
#include "templateloader.h"
|
||||
#include <QFile>
|
||||
#include <QFileInfo>
|
||||
#include <QStringList>
|
||||
#include <QDir>
|
||||
#include <QSet>
|
||||
#include <QApplication>
|
||||
|
||||
TemplateLoader::TemplateLoader(QSettings* settings, QObject* parent)
|
||||
: QObject(parent)
|
||||
{
|
||||
templatePath=settings->value("path","./server/templates").toString();
|
||||
// Convert relative path to absolute, based on the directory of the config file.
|
||||
#ifdef Q_OS_WIN32
|
||||
if (QDir::isRelativePath(templatePath) && settings->format()!=QSettings::NativeFormat)
|
||||
#else
|
||||
if (QDir::isRelativePath(templatePath))
|
||||
#endif
|
||||
{
|
||||
#if defined Q_OS_UNIX && !defined Q_OS_MAC
|
||||
QFileInfo configFile(QString(DATADIR)+"/yacreader");
|
||||
templatePath=QFileInfo(QString(DATADIR)+"/yacreader",templatePath).absoluteFilePath();
|
||||
#else
|
||||
QFileInfo configFile(QApplication::applicationDirPath());
|
||||
templatePath=QFileInfo(QApplication::applicationDirPath(),templatePath).absoluteFilePath();
|
||||
#endif
|
||||
}
|
||||
fileNameSuffix=settings->value("suffix",".tpl").toString();
|
||||
QString encoding=settings->value("encoding").toString();
|
||||
if (encoding.isEmpty()) {
|
||||
textCodec=QTextCodec::codecForLocale();
|
||||
}
|
||||
else {
|
||||
textCodec=QTextCodec::codecForName(encoding.toLocal8Bit());
|
||||
}
|
||||
qDebug("TemplateLoader: path=%s, codec=%s",qPrintable(templatePath),textCodec->name().data());
|
||||
}
|
||||
|
||||
TemplateLoader::~TemplateLoader() {}
|
||||
|
||||
QString TemplateLoader::tryFile(QString localizedName) {
|
||||
QString fileName=templatePath+"/"+localizedName+fileNameSuffix;
|
||||
qDebug("TemplateCache: trying file %s",qPrintable(fileName));
|
||||
QFile file(fileName);
|
||||
if (file.exists()) {
|
||||
file.open(QIODevice::ReadOnly);
|
||||
QString document=textCodec->toUnicode(file.readAll());
|
||||
file.close();
|
||||
if (file.error()) {
|
||||
qCritical("TemplateLoader: cannot load file %s, %s",qPrintable(fileName),qPrintable(file.errorString()));
|
||||
return "";
|
||||
}
|
||||
else {
|
||||
return document;
|
||||
}
|
||||
}
|
||||
return "";
|
||||
}
|
||||
|
||||
Template TemplateLoader::getTemplate(QString templateName, QString locales) {
|
||||
mutex.lock();
|
||||
QSet<QString> tried; // used to suppress duplicate attempts
|
||||
QStringList locs=locales.split(',',QString::SkipEmptyParts);
|
||||
|
||||
// Search for exact match
|
||||
foreach (QString loc,locs) {
|
||||
loc.replace(QRegExp(";.*"),"");
|
||||
loc.replace('-','_');
|
||||
QString localizedName=templateName+"-"+loc.trimmed();
|
||||
if (!tried.contains(localizedName)) {
|
||||
QString document=tryFile(localizedName);
|
||||
if (!document.isEmpty()) {
|
||||
mutex.unlock();
|
||||
return Template(document,localizedName);
|
||||
}
|
||||
tried.insert(localizedName);
|
||||
}
|
||||
}
|
||||
|
||||
// Search for correct language but any country
|
||||
foreach (QString loc,locs) {
|
||||
loc.replace(QRegExp("[;_-].*"),"");
|
||||
QString localizedName=templateName+"-"+loc.trimmed();
|
||||
if (!tried.contains(localizedName)) {
|
||||
QString document=tryFile(localizedName);
|
||||
if (!document.isEmpty()) {
|
||||
mutex.unlock();
|
||||
return Template(document,localizedName);
|
||||
}
|
||||
tried.insert(localizedName);
|
||||
}
|
||||
}
|
||||
|
||||
// Search for default file
|
||||
QString document=tryFile(templateName);
|
||||
if (!document.isEmpty()) {
|
||||
mutex.unlock();
|
||||
return Template(document,templateName);
|
||||
}
|
||||
|
||||
qCritical("TemplateCache: cannot find template %s",qPrintable(templateName));
|
||||
mutex.unlock();
|
||||
return Template("",templateName);
|
||||
}
|
@ -0,0 +1,85 @@
|
||||
/**
|
||||
@file
|
||||
@author Stefan Frings
|
||||
*/
|
||||
|
||||
#ifndef TEMPLATELOADER_H
|
||||
#define TEMPLATELOADER_H
|
||||
|
||||
#include <QString>
|
||||
#include <QSettings>
|
||||
#include <QTextCodec>
|
||||
#include "template.h"
|
||||
#include <QMutex>
|
||||
|
||||
/**
|
||||
Loads localized versions of template files. If the caller requests a file with the
|
||||
name "index" and the suffix is ".tpl" and the requested locale is "de_DE, de, en-US",
|
||||
then files are searched in the following order:
|
||||
|
||||
- index-de_DE.tpl
|
||||
- index-de.tpl
|
||||
- index-en_US.tpl
|
||||
- index-en.tpl
|
||||
- index.tpl
|
||||
|
||||
The following settings are required:
|
||||
<code><pre>
|
||||
path=.
|
||||
suffix=.tpl
|
||||
encoding=UTF-8
|
||||
</pre></code>
|
||||
The path is relative to the directory of the config file. In case of windows, if the
|
||||
settings are in the registry, the path is relative to the current working directory.
|
||||
@see TemplateCache
|
||||
*/
|
||||
|
||||
class TemplateLoader : public QObject {
|
||||
Q_OBJECT
|
||||
Q_DISABLE_COPY(TemplateLoader);
|
||||
public:
|
||||
|
||||
/**
|
||||
Constructor.
|
||||
@param settings configurations settings
|
||||
@param parent parent object
|
||||
*/
|
||||
TemplateLoader(QSettings* settings, QObject* parent=0);
|
||||
|
||||
/** Destructor */
|
||||
virtual ~TemplateLoader();
|
||||
|
||||
/**
|
||||
Get a template for a given locale.
|
||||
This method is thread safe.
|
||||
@param templateName base name of the template file, without suffix and without locale
|
||||
@param locales Requested locale(s), e.g. "de_DE, en_EN". Strings in the format of
|
||||
the HTTP header Accept-Locale may be used. Badly formatted parts in the string are silently
|
||||
ignored.
|
||||
@return If the template cannot be loaded, an error message is logged and an empty template is returned.
|
||||
*/
|
||||
Template getTemplate(QString templateName, QString locales=QString());
|
||||
|
||||
protected:
|
||||
|
||||
/**
|
||||
Try to get a file from cache or filesystem.
|
||||
@param localizedName Name of the template with locale to find
|
||||
@return The template document, or empty string if not found
|
||||
*/
|
||||
virtual QString tryFile(QString localizedName);
|
||||
|
||||
/** Directory where the templates are searched */
|
||||
QString templatePath;
|
||||
|
||||
/** Suffix to the filenames */
|
||||
QString fileNameSuffix;
|
||||
|
||||
/** Codec for decoding the files */
|
||||
QTextCodec* textCodec;
|
||||
|
||||
/** Used to synchronize threads */
|
||||
QMutex mutex;
|
||||
};
|
||||
|
||||
#endif // TEMPLATELOADER_H
|
177
YACReaderLibrary/server/requestmapper.cpp
Normal file
177
YACReaderLibrary/server/requestmapper.cpp
Normal file
@ -0,0 +1,177 @@
|
||||
/**
|
||||
@file
|
||||
@author Stefan Frings
|
||||
*/
|
||||
|
||||
#include "requestmapper.h"
|
||||
#include "static.h"
|
||||
#include "staticfilecontroller.h"
|
||||
#include "controllers/dumpcontroller.h"
|
||||
#include "controllers/templatecontroller.h"
|
||||
#include "controllers/formcontroller.h"
|
||||
#include "controllers/fileuploadcontroller.h"
|
||||
#include "controllers/sessioncontroller.h"
|
||||
|
||||
#include "controllers/librariescontroller.h"
|
||||
#include "controllers/foldercontroller.h"
|
||||
#include "controllers/covercontroller.h"
|
||||
#include "controllers/comiccontroller.h"
|
||||
#include "controllers/folderinfocontroller.h"
|
||||
#include "controllers/pagecontroller.h"
|
||||
#include "controllers/updatecomiccontroller.h"
|
||||
#include "controllers/errorcontroller.h"
|
||||
#include "controllers/comicdownloadinfocontroller.h"
|
||||
#include "controllers/synccontroller.h"
|
||||
|
||||
#include "db_helper.h"
|
||||
#include "yacreader_libraries.h"
|
||||
|
||||
#include "QsLog.h"
|
||||
|
||||
RequestMapper::RequestMapper(QObject* parent)
|
||||
:HttpRequestHandler(parent) {}
|
||||
|
||||
void RequestMapper::loadSession(HttpRequest & request, HttpResponse& response)
|
||||
{
|
||||
HttpSession session=Static::sessionStore->getSession(request,response);
|
||||
if(session.contains("ySession")) //session is already alive check if it is needed to update comics
|
||||
{
|
||||
QString postData = QString::fromUtf8(request.getBody());
|
||||
|
||||
if(postData.contains("currentPage"))
|
||||
return;
|
||||
|
||||
if(postData.length()>0) {
|
||||
|
||||
QList<QString> data = postData.split("\n");
|
||||
if(data.length() > 2) {
|
||||
session.setDeviceType(data.at(0).split(":").at(1));
|
||||
session.setDisplayType(data.at(1).split(":").at(1));
|
||||
QList<QString> comics = data.at(2).split(":").at(1).split("\t");
|
||||
session.clearComics();
|
||||
foreach(QString hash,comics) {
|
||||
session.setComicOnDevice(hash);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if(data.length()>1)
|
||||
{
|
||||
session.setDeviceType(data.at(0).split(":").at(1));
|
||||
session.setDisplayType(data.at(1).split(":").at(1));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
session.set("ySession","ok");
|
||||
|
||||
QString postData = QString::fromUtf8(request.getBody());
|
||||
//response.writeText(postData);
|
||||
|
||||
QList<QString> data = postData.split("\n");
|
||||
|
||||
if(data.length() > 2)
|
||||
{
|
||||
session.setDeviceType(data.at(0).split(":").at(1));
|
||||
session.setDisplayType(data.at(1).split(":").at(1));
|
||||
QList<QString> comics = data.at(2).split(":").at(1).split("\t");
|
||||
foreach(QString hash,comics)
|
||||
{
|
||||
session.setComicOnDevice(hash);
|
||||
}
|
||||
}
|
||||
else //values by default, only for debug purposes.
|
||||
{
|
||||
session.setDeviceType("ipad");
|
||||
session.setDisplayType("@2x");
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
void RequestMapper::service(HttpRequest& request, HttpResponse& response) {
|
||||
QByteArray path=request.getPath();
|
||||
qDebug("RequestMapper: path=%s",path.data());
|
||||
|
||||
QRegExp folder("/library/.+/folder/[0-9]+/?");//get comic content
|
||||
QRegExp folderInfo("/library/.+/folder/[0-9]+/info/?"); //get folder info
|
||||
QRegExp comicDownloadInfo("/library/.+/comic/[0-9]+/?"); //get comic info (basic/download info)
|
||||
QRegExp comicFullInfo("/library/.+/comic/[0-9]+/info/?"); //get comic info (full info)
|
||||
QRegExp comicOpen("/library/.+/comic/[0-9]+/remote/?"); //the server will open for reading the comic
|
||||
QRegExp comicUpdate("/library/.+/comic/[0-9]+/update/?"); //get comic info
|
||||
QRegExp comicClose("/library/.+/comic/[0-9]+/close/?"); //the server will close the comic and free memory
|
||||
QRegExp cover("/library/.+/cover/[0-9a-f]+.jpg"); //get comic cover (navigation)
|
||||
QRegExp comicPage("/library/.+/comic/[0-9]+/page/[0-9]+/?"); //get comic page
|
||||
QRegExp comicPageRemote("/library/.+/comic/[0-9]+/page/[0-9]+/remote?"); //get comic page (remote reading)
|
||||
|
||||
QRegExp sync("/sync");
|
||||
|
||||
QRegExp library("/library/([0-9]+)/.+"); //permite verificar que la biblioteca solicitada existe
|
||||
|
||||
path = QUrl::fromPercentEncoding(path).toUtf8();
|
||||
|
||||
if(!sync.exactMatch(path)) //no session is needed for syncback info, until security will be added
|
||||
loadSession(request, response);
|
||||
|
||||
//primera petición, se ha hecho un post, se sirven las bibliotecas si la seguridad mediante login no está habilitada
|
||||
if(path == "/") //Don't send data to the server using '/' !!!!
|
||||
{
|
||||
LibrariesController().service(request, response);
|
||||
}
|
||||
else
|
||||
{
|
||||
if(sync.exactMatch(path))
|
||||
SyncController().service(request, response);
|
||||
else
|
||||
{
|
||||
//se comprueba que la sesión sea la correcta con el fin de evitar accesos no autorizados
|
||||
HttpSession session=Static::sessionStore->getSession(request,response,false);
|
||||
if(!session.isNull() && session.contains("ySession"))
|
||||
{
|
||||
if(library.indexIn(path)!=-1 && DBHelper::getLibraries().contains(library.cap(1).toInt()) )
|
||||
{
|
||||
//listar el contenido del folder
|
||||
if(folder.exactMatch(path))
|
||||
{
|
||||
FolderController().service(request, response);
|
||||
}
|
||||
else if (folderInfo.exactMatch(path))
|
||||
{
|
||||
FolderInfoController().service(request, response);
|
||||
}
|
||||
else if(cover.exactMatch(path))
|
||||
{
|
||||
CoverController().service(request, response);
|
||||
}
|
||||
else if(comicDownloadInfo.exactMatch(path))
|
||||
{
|
||||
ComicDownloadInfoController().service(request, response);
|
||||
}
|
||||
else if(comicFullInfo.exactMatch(path) || comicOpen.exactMatch(path))//start download or start remote reading
|
||||
{
|
||||
ComicController().service(request, response);
|
||||
}
|
||||
else if(comicPage.exactMatch(path) || comicPageRemote.exactMatch(path))
|
||||
{
|
||||
PageController().service(request,response);
|
||||
}
|
||||
else if(comicUpdate.exactMatch(path))
|
||||
{
|
||||
UpdateComicController().service(request, response);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
//response.writeText(library.cap(1));
|
||||
Static::staticFileController->service(request, response);
|
||||
}
|
||||
}
|
||||
else //acceso no autorizado, redirección
|
||||
{
|
||||
ErrorController(300).service(request,response);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
37
YACReaderLibrary/server/requestmapper.h
Normal file
37
YACReaderLibrary/server/requestmapper.h
Normal file
@ -0,0 +1,37 @@
|
||||
/**
|
||||
@file
|
||||
@author Stefan Frings
|
||||
*/
|
||||
|
||||
#ifndef REQUESTMAPPER_H
|
||||
#define REQUESTMAPPER_H
|
||||
|
||||
#include "httprequesthandler.h"
|
||||
|
||||
/**
|
||||
The request mapper dispatches incoming HTTP requests to controller classes
|
||||
depending on the requested path.
|
||||
*/
|
||||
|
||||
class RequestMapper : public HttpRequestHandler {
|
||||
Q_OBJECT
|
||||
Q_DISABLE_COPY(RequestMapper)
|
||||
public:
|
||||
|
||||
/**
|
||||
Constructor.
|
||||
@param parent Parent object
|
||||
*/
|
||||
RequestMapper(QObject* parent=0);
|
||||
|
||||
/**
|
||||
Dispatch a request to a controller.
|
||||
@param request The received HTTP request
|
||||
@param response Must be used to return the response
|
||||
*/
|
||||
void service(HttpRequest& request, HttpResponse& response);
|
||||
void loadSession(HttpRequest & request, HttpResponse& response);
|
||||
|
||||
};
|
||||
|
||||
#endif // REQUESTMAPPER_H
|
38
YACReaderLibrary/server/server.pri
Normal file
38
YACReaderLibrary/server/server.pri
Normal file
@ -0,0 +1,38 @@
|
||||
INCLUDEPATH += $$PWD
|
||||
DEPENDPATH += $$PWD
|
||||
|
||||
HEADERS += \
|
||||
$$PWD/static.h \
|
||||
$$PWD/startup.h \
|
||||
$$PWD/requestmapper.h \
|
||||
$$PWD/controllers/comiccontroller.h \
|
||||
$$PWD/controllers/errorcontroller.h \
|
||||
$$PWD/controllers/foldercontroller.h \
|
||||
$$PWD/controllers/folderinfocontroller.h \
|
||||
$$PWD/controllers/librariescontroller.h \
|
||||
$$PWD/controllers/pagecontroller.h \
|
||||
$$PWD/controllers/sessionmanager.h \
|
||||
$$PWD/controllers/covercontroller.h \
|
||||
$$PWD/controllers/updatecomiccontroller.h \
|
||||
$$PWD/controllers/comicdownloadinfocontroller.h \
|
||||
$$PWD/controllers/synccontroller.h
|
||||
|
||||
SOURCES += \
|
||||
$$PWD/static.cpp \
|
||||
$$PWD/startup.cpp \
|
||||
$$PWD/requestmapper.cpp \
|
||||
$$PWD/controllers/comiccontroller.cpp \
|
||||
$$PWD/controllers/errorcontroller.cpp \
|
||||
$$PWD/controllers/foldercontroller.cpp \
|
||||
$$PWD/controllers/folderinfocontroller.cpp \
|
||||
$$PWD/controllers/librariescontroller.cpp \
|
||||
$$PWD/controllers/pagecontroller.cpp \
|
||||
$$PWD/controllers/sessionmanager.cpp \
|
||||
$$PWD/controllers/covercontroller.cpp \
|
||||
$$PWD/controllers/updatecomiccontroller.cpp \
|
||||
$$PWD/controllers/comicdownloadinfocontroller.cpp \
|
||||
$$PWD/controllers/synccontroller.cpp
|
||||
|
||||
include(lib/bfLogging/bfLogging.pri)
|
||||
include(lib/bfHttpServer/bfHttpServer.pri)
|
||||
include(lib/bfTemplateEngine/bfTemplateEngine.pri)
|
89
YACReaderLibrary/server/startup.cpp
Normal file
89
YACReaderLibrary/server/startup.cpp
Normal file
@ -0,0 +1,89 @@
|
||||
/**
|
||||
@file
|
||||
@author Stefan Frings
|
||||
*/
|
||||
|
||||
#include "static.h"
|
||||
#include "startup.h"
|
||||
#include "dualfilelogger.h"
|
||||
#include "httplistener.h"
|
||||
#include "requestmapper.h"
|
||||
#include "staticfilecontroller.h"
|
||||
|
||||
#include "yacreader_global.h"
|
||||
|
||||
#include <QDir>
|
||||
#include <QApplication>
|
||||
|
||||
/** Name of this application */
|
||||
#define APPNAME "YACReaderLibrary"
|
||||
|
||||
/** Publisher of this application */
|
||||
#define ORGANISATION "YACReader"
|
||||
|
||||
/** Short description of this application */
|
||||
#define DESCRIPTION "Comic reader and organizer"
|
||||
|
||||
void Startup::start() {
|
||||
// Initialize the core application
|
||||
QCoreApplication* app = QApplication::instance();
|
||||
app->setApplicationName(APPNAME);
|
||||
app->setOrganizationName(ORGANISATION);
|
||||
QString configFileName=YACReader::getSettingsPath()+"/"+QCoreApplication::applicationName()+".ini";
|
||||
|
||||
// Configure logging into files
|
||||
QSettings* mainLogSettings=new QSettings(configFileName,QSettings::IniFormat,app);
|
||||
mainLogSettings->beginGroup("mainLogFile");
|
||||
//QSettings* debugLogSettings=new QSettings(configFileName,QSettings::IniFormat,app);
|
||||
//debugLogSettings->beginGroup("debugLogFile");
|
||||
Logger* logger=new FileLogger(mainLogSettings,10000,app);
|
||||
logger->installMsgHandler();
|
||||
|
||||
// Configure template loader and cache
|
||||
QSettings* templateSettings=new QSettings(configFileName,QSettings::IniFormat,app);
|
||||
templateSettings->beginGroup("templates");
|
||||
Static::templateLoader=new TemplateCache(templateSettings,app);
|
||||
|
||||
// Configure session store
|
||||
QSettings* sessionSettings=new QSettings(configFileName,QSettings::IniFormat,app);
|
||||
sessionSettings->beginGroup("sessions");
|
||||
Static::sessionStore=new HttpSessionStore(sessionSettings,app);
|
||||
|
||||
// Configure static file controller
|
||||
QSettings* fileSettings=new QSettings(configFileName,QSettings::IniFormat,app);
|
||||
fileSettings->beginGroup("docroot");
|
||||
Static::staticFileController=new StaticFileController(fileSettings,app);
|
||||
|
||||
// Configure and start the TCP listener
|
||||
qDebug("ServiceHelper: Starting service");
|
||||
QSettings* listenerSettings=new QSettings(configFileName,QSettings::IniFormat,app);
|
||||
listenerSettings->beginGroup("listener");
|
||||
listener = new HttpListener(listenerSettings,new RequestMapper(app),app);
|
||||
|
||||
qDebug("ServiceHelper: Service has started");
|
||||
}
|
||||
|
||||
|
||||
void Startup::stop() {
|
||||
qDebug("ServiceHelper: Service has been stopped");
|
||||
// QCoreApplication destroys all objects that have been created in start().
|
||||
if(listener!=nullptr)
|
||||
{
|
||||
listener->close();
|
||||
delete listener;
|
||||
listener = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Startup::Startup()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
QString Startup::getPort()
|
||||
{
|
||||
return QString("%1").arg(listener->serverPort());
|
||||
}
|
||||
|
||||
|
34
YACReaderLibrary/server/startup.h
Normal file
34
YACReaderLibrary/server/startup.h
Normal file
@ -0,0 +1,34 @@
|
||||
/**
|
||||
@file
|
||||
@author Stefan Frings
|
||||
*/
|
||||
|
||||
#ifndef STARTUP_H
|
||||
#define STARTUP_H
|
||||
|
||||
#include <QString>
|
||||
|
||||
class HttpListener;
|
||||
/**
|
||||
Helper class to install and run the application as a windows
|
||||
service.
|
||||
*/
|
||||
class Startup
|
||||
{
|
||||
private:
|
||||
//QTcpServer
|
||||
HttpListener * listener;
|
||||
public:
|
||||
|
||||
/** Constructor */
|
||||
Startup();
|
||||
/** Start the server */
|
||||
void start();
|
||||
/** Stop the server */
|
||||
void stop();
|
||||
|
||||
QString getPort();
|
||||
protected:
|
||||
};
|
||||
|
||||
#endif // STARTUP_H
|
63
YACReaderLibrary/server/static.cpp
Normal file
63
YACReaderLibrary/server/static.cpp
Normal file
@ -0,0 +1,63 @@
|
||||
/**
|
||||
@file
|
||||
@author Stefan Frings
|
||||
*/
|
||||
|
||||
#include "static.h"
|
||||
#include <QCoreApplication>
|
||||
#include <QDir>
|
||||
#include <QFile>
|
||||
#include <QString>
|
||||
|
||||
QString Static::configDir=0;
|
||||
|
||||
TemplateLoader* Static::templateLoader=0;
|
||||
|
||||
HttpSessionStore* Static::sessionStore=0;
|
||||
|
||||
StaticFileController* Static::staticFileController=0;
|
||||
|
||||
QString Static::getConfigFileName() {
|
||||
return QString("%1/%2.ini").arg(getConfigDir()).arg(QCoreApplication::applicationName());
|
||||
}
|
||||
|
||||
QString Static::getConfigDir() {
|
||||
if (!configDir.isNull()) {
|
||||
return configDir;
|
||||
}
|
||||
// Search config file
|
||||
#if defined Q_OS_UNIX && !defined Q_OS_MAC
|
||||
QString binDir=(QString(DATADIR)+"/yacreader");
|
||||
#else
|
||||
QString binDir=QCoreApplication::applicationDirPath();
|
||||
#endif
|
||||
QString organization=QCoreApplication::organizationName();
|
||||
QString configFileName=QCoreApplication::applicationName()+".ini";
|
||||
|
||||
QStringList searchList;
|
||||
searchList.append(QDir::cleanPath(binDir));
|
||||
searchList.append(QDir::cleanPath(binDir+"/../etc"));
|
||||
searchList.append(QDir::cleanPath(binDir+"/../../etc")); // for development under windows
|
||||
searchList.append(QDir::rootPath()+"etc/xdg/"+organization);
|
||||
searchList.append(QDir::rootPath()+"etc/opt");
|
||||
searchList.append(QDir::rootPath()+"etc");
|
||||
|
||||
foreach (QString dir, searchList) {
|
||||
QFile file(dir+"/"+configFileName);
|
||||
if (file.exists()) {
|
||||
// found
|
||||
configDir=dir;
|
||||
qDebug("Using config file %s",qPrintable(file.fileName()));
|
||||
return configDir;
|
||||
}
|
||||
}
|
||||
|
||||
// not found
|
||||
foreach (QString dir, searchList) {
|
||||
qWarning("%s/%s not found",qPrintable(dir),qPrintable(configFileName));
|
||||
}
|
||||
qWarning("Cannot find config file %s",qPrintable(configFileName)); //TODO establecer los valores por defecto
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
64
YACReaderLibrary/server/static.h
Normal file
64
YACReaderLibrary/server/static.h
Normal file
@ -0,0 +1,64 @@
|
||||
/**
|
||||
@file
|
||||
@author Stefan Frings
|
||||
*/
|
||||
|
||||
#ifndef STATIC_H
|
||||
#define STATIC_H
|
||||
|
||||
#include <QString>
|
||||
#include "templatecache.h"
|
||||
#include "httpsessionstore.h"
|
||||
#include "staticfilecontroller.h"
|
||||
|
||||
/**
|
||||
This class contains some static resources that are used by the application.
|
||||
*/
|
||||
|
||||
class Static
|
||||
{
|
||||
public:
|
||||
|
||||
/**
|
||||
Search the main config file and return its full path.
|
||||
On the first call, the INI file gets searched. If not found,
|
||||
the application aborts with an error message.
|
||||
<p>
|
||||
The filename is the applications name plus the ending ".ini". It is searched
|
||||
in the following directories:
|
||||
|
||||
- Same directory as the applications executable file
|
||||
- In ../etc relative to the applications executable file
|
||||
- In ../../etc relative to the applications executable file
|
||||
- In /etc/xdg/{organisation name} on the root drive
|
||||
- In /etc/opt on the root drive
|
||||
- In /etc on the root drive
|
||||
|
||||
*/
|
||||
static QString getConfigFileName();
|
||||
|
||||
/**
|
||||
Gets the directory where the main config file is located.
|
||||
On the first call, the INI file gets searched. If not found,
|
||||
the application aborts with an error message.
|
||||
@see getConfigFileName()
|
||||
*/
|
||||
static QString getConfigDir();
|
||||
|
||||
/** Cache for template files */
|
||||
static TemplateLoader* templateLoader;
|
||||
|
||||
/** Storage for session cookies */
|
||||
static HttpSessionStore* sessionStore;
|
||||
|
||||
/** Controller for static files */
|
||||
static StaticFileController* staticFileController;
|
||||
|
||||
private:
|
||||
|
||||
/** Directory of the main config file */
|
||||
static QString configDir;
|
||||
|
||||
};
|
||||
|
||||
#endif // STATIC_H
|
Reference in New Issue
Block a user