mirror of
https://github.com/YACReader/yacreader
synced 2025-05-27 10:50:27 -04:00
Merge
This commit is contained in:
parent
8b20014cc8
commit
f149f314e0
@ -1658,6 +1658,7 @@ void MainWindowViewer::sendComic()
|
||||
{
|
||||
YACReaderLocalClient * client = new YACReaderLocalClient;
|
||||
currentComicDB.info.hasBeenOpened = true;
|
||||
currentComicDB.info.lastTimeOpened = QDateTime::currentSecsSinceEpoch();
|
||||
viewer->updateComic(currentComicDB);
|
||||
int retries = 1;
|
||||
while(!client->sendComicInfo(libraryId,currentComicDB) && retries!=0)
|
||||
|
@ -5,8 +5,8 @@
|
||||
|
||||
#include "QsLog.h"
|
||||
|
||||
ComicsRemover::ComicsRemover(QModelIndexList & il, QList<QString> & ps, QObject *parent)
|
||||
:QObject(parent),indexList(il), paths(ps)
|
||||
ComicsRemover::ComicsRemover(QModelIndexList & il, QList<QString> & ps, qulonglong parentId, QObject *parent)
|
||||
:QObject(parent),indexList(il), paths(ps), parentId(parentId)
|
||||
{
|
||||
}
|
||||
|
||||
@ -29,6 +29,7 @@ void ComicsRemover::process()
|
||||
}
|
||||
|
||||
emit finished();
|
||||
emit removedItemsFromFolder(parentId);
|
||||
}
|
||||
|
||||
|
||||
|
@ -10,12 +10,13 @@ class ComicsRemover : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
explicit ComicsRemover(QModelIndexList & indexList, QList<QString> & paths, QObject *parent = 0);
|
||||
explicit ComicsRemover(QModelIndexList & indexList, QList<QString> & paths, qulonglong parentId, QObject *parent = 0);
|
||||
|
||||
signals:
|
||||
void remove(int);
|
||||
void removeError();
|
||||
void finished();
|
||||
void removedItemsFromFolder(qulonglong);
|
||||
|
||||
public slots:
|
||||
void process();
|
||||
@ -23,6 +24,7 @@ public slots:
|
||||
private:
|
||||
QModelIndexList indexList;
|
||||
QList<QString> paths;
|
||||
qulonglong parentId;
|
||||
};
|
||||
|
||||
class FoldersRemover : public QObject
|
||||
|
@ -582,7 +582,7 @@ void ComicModel::setupFavoritesModelData(const QString &databasePath)
|
||||
"ORDER BY cdrl.ordering");
|
||||
selectQuery.bindValue(":parentDefaultListId", 1);
|
||||
selectQuery.exec();
|
||||
setupModelData(selectQuery);
|
||||
setupModelDataForList(selectQuery);
|
||||
}
|
||||
db.close();
|
||||
QSqlDatabase::removeDatabase(_databasePath);
|
||||
@ -607,9 +607,11 @@ void ComicModel::setupReadingModelData(const QString &databasePath)
|
||||
QSqlQuery selectQuery(db);
|
||||
selectQuery.prepare("SELECT ci.number,ci.title,c.fileName,ci.numPages,c.id,c.parentId,c.path,ci.hash,ci.read,ci.isBis,ci.currentPage,ci.rating,ci.hasBeenOpened "
|
||||
"FROM comic c INNER JOIN comic_info ci ON (c.comicInfoId = ci.id) "
|
||||
"WHERE ci.hasBeenOpened = 1 AND ci.read = 0 AND ci.currentPage != ci.numPages AND ci.currentPage != 1");
|
||||
"WHERE ci.hasBeenOpened = 1 AND ci.read = 0 AND ci.currentPage != ci.numPages AND ci.currentPage != 1 "
|
||||
"ORDER BY ci.lastTimeOpened DESC");
|
||||
selectQuery.exec();
|
||||
setupModelData(selectQuery);
|
||||
|
||||
setupModelDataForList(selectQuery);
|
||||
}
|
||||
db.close();
|
||||
QSqlDatabase::removeDatabase(_databasePath);
|
||||
@ -928,7 +930,7 @@ void ComicModel::removeInTransaction(int row)
|
||||
|
||||
endRemoveRows();
|
||||
}
|
||||
|
||||
/*
|
||||
void ComicModel::remove(ComicDB * comic, int row)
|
||||
{
|
||||
beginRemoveRows(QModelIndex(),row,row);
|
||||
@ -944,7 +946,7 @@ void ComicModel::remove(ComicDB * comic, int row)
|
||||
QSqlDatabase::removeDatabase(_databasePath);
|
||||
endRemoveRows();
|
||||
}
|
||||
|
||||
*/
|
||||
/*ComicDB TableModel::getComic(int row)
|
||||
{
|
||||
return getComic(index(row,0));
|
||||
|
@ -65,7 +65,7 @@ public:
|
||||
//setComicInfoForSelectedComis(QList<QModelIndex> list); -->inserta la información común para los comics seleccionados
|
||||
QVector<YACReaderComicReadStatus> setComicsRead(QList<QModelIndex> list,YACReaderComicReadStatus read);
|
||||
qint64 asignNumbers(QList<QModelIndex> list,int startingNumber);
|
||||
void remove(ComicDB * comic, int row);
|
||||
//void remove(ComicDB * comic, int row);
|
||||
void removeInTransaction(int row);
|
||||
void reload(const ComicDB & comic);
|
||||
void resetComicRating(const QModelIndex & mi);
|
||||
|
@ -124,99 +124,103 @@ QSqlDatabase DataBaseManagement::loadDatabaseFromFile(QString filePath)
|
||||
|
||||
bool DataBaseManagement::createTables(QSqlDatabase & database)
|
||||
{
|
||||
bool success = true;
|
||||
bool success = true;
|
||||
|
||||
//FOLDER (representa una carpeta en disco)
|
||||
{
|
||||
QSqlQuery queryFolder(database);
|
||||
queryFolder.prepare("CREATE TABLE folder ("
|
||||
"id INTEGER PRIMARY KEY,"
|
||||
"parentId INTEGER NOT NULL,"
|
||||
"name TEXT NOT NULL,"
|
||||
"path TEXT NOT NULL,"
|
||||
//new 7.1 fields
|
||||
"finished BOOLEAN DEFAULT 0," //reading
|
||||
"completed BOOLEAN DEFAULT 1," //collecting
|
||||
//--
|
||||
"FOREIGN KEY(parentId) REFERENCES folder(id) ON DELETE CASCADE)");
|
||||
success = success && queryFolder.exec();
|
||||
{
|
||||
//COMIC INFO (representa la información de un cómic, cada cómic tendrá un idéntificador único formado por un hash sha1'de los primeros 512kb' + su tamaño en bytes)
|
||||
QSqlQuery queryComicInfo(database);
|
||||
queryComicInfo.prepare("CREATE TABLE comic_info ("
|
||||
"id INTEGER PRIMARY KEY,"
|
||||
"title TEXT,"
|
||||
|
||||
//COMIC INFO (representa la información de un cómic, cada cómic tendrá un idéntificador único formado por un hash sha1'de los primeros 512kb' + su tamaño en bytes)
|
||||
QSqlQuery queryComicInfo(database);
|
||||
queryComicInfo.prepare("CREATE TABLE comic_info ("
|
||||
"id INTEGER PRIMARY KEY,"
|
||||
"title TEXT,"
|
||||
"coverPage INTEGER DEFAULT 1,"
|
||||
"numPages INTEGER,"
|
||||
|
||||
"coverPage INTEGER DEFAULT 1,"
|
||||
"numPages INTEGER,"
|
||||
"number INTEGER,"
|
||||
"isBis BOOLEAN,"
|
||||
"count INTEGER,"
|
||||
|
||||
"number INTEGER,"
|
||||
"isBis BOOLEAN,"
|
||||
"count INTEGER,"
|
||||
"volume TEXT,"
|
||||
"storyArc TEXT,"
|
||||
"arcNumber INTEGER,"
|
||||
"arcCount INTEGER,"
|
||||
|
||||
"volume TEXT,"
|
||||
"storyArc TEXT,"
|
||||
"arcNumber INTEGER,"
|
||||
"arcCount INTEGER,"
|
||||
"genere TEXT,"
|
||||
|
||||
"genere TEXT,"
|
||||
"writer TEXT,"
|
||||
"penciller TEXT,"
|
||||
"inker TEXT,"
|
||||
"colorist TEXT,"
|
||||
"letterer TEXT,"
|
||||
"coverArtist TEXT,"
|
||||
|
||||
"writer TEXT,"
|
||||
"penciller TEXT,"
|
||||
"inker TEXT,"
|
||||
"colorist TEXT,"
|
||||
"letterer TEXT,"
|
||||
"coverArtist TEXT,"
|
||||
"date TEXT," //dd/mm/yyyy --> se mostrará en 3 campos diferentes
|
||||
"publisher TEXT,"
|
||||
"format TEXT,"
|
||||
"color BOOLEAN,"
|
||||
"ageRating BOOLEAN,"
|
||||
|
||||
"date TEXT," //dd/mm/yyyy --> se mostrará en 3 campos diferentes
|
||||
"publisher TEXT,"
|
||||
"format TEXT,"
|
||||
"color BOOLEAN,"
|
||||
"ageRating BOOLEAN,"
|
||||
"synopsis TEXT,"
|
||||
"characters TEXT,"
|
||||
"notes TEXT,"
|
||||
|
||||
"synopsis TEXT,"
|
||||
"characters TEXT,"
|
||||
"notes TEXT,"
|
||||
"hash TEXT UNIQUE NOT NULL,"
|
||||
"edited BOOLEAN DEFAULT 0,"
|
||||
"read BOOLEAN DEFAULT 0,"
|
||||
//new 7.0 fields
|
||||
|
||||
"hash TEXT UNIQUE NOT NULL,"
|
||||
"edited BOOLEAN DEFAULT 0,"
|
||||
"read BOOLEAN DEFAULT 0,"
|
||||
//new 7.0 fields
|
||||
|
||||
"hasBeenOpened BOOLEAN DEFAULT 0,"
|
||||
"rating INTEGER DEFAULT 0,"
|
||||
"currentPage INTEGER DEFAULT 1, "
|
||||
"bookmark1 INTEGER DEFAULT -1, "
|
||||
"bookmark2 INTEGER DEFAULT -1, "
|
||||
"bookmark3 INTEGER DEFAULT -1, "
|
||||
"brightness INTEGER DEFAULT -1, "
|
||||
"contrast INTEGER DEFAULT -1, "
|
||||
"gamma INTEGER DEFAULT -1, "
|
||||
//new 7.1 fields
|
||||
"comicVineID TEXT"
|
||||
"hasBeenOpened BOOLEAN DEFAULT 0,"
|
||||
"rating INTEGER DEFAULT 0,"
|
||||
"currentPage INTEGER DEFAULT 1, "
|
||||
"bookmark1 INTEGER DEFAULT -1, "
|
||||
"bookmark2 INTEGER DEFAULT -1, "
|
||||
"bookmark3 INTEGER DEFAULT -1, "
|
||||
"brightness INTEGER DEFAULT -1, "
|
||||
"contrast INTEGER DEFAULT -1, "
|
||||
"gamma INTEGER DEFAULT -1, "
|
||||
//new 7.1 fields
|
||||
"comicVineID TEXT,"
|
||||
//new 8.6 fields
|
||||
"lastTimeOpened INTEGER"
|
||||
|
||||
")");
|
||||
success = success && queryComicInfo.exec();
|
||||
//queryComicInfo.finish();
|
||||
")");
|
||||
success = success && queryComicInfo.exec();
|
||||
//queryComicInfo.finish();
|
||||
|
||||
//COMIC (representa un cómic en disco, contiene el nombre de fichero)
|
||||
QSqlQuery queryComic(database);
|
||||
queryComic.prepare("CREATE TABLE comic (id INTEGER PRIMARY KEY, parentId INTEGER NOT NULL, comicInfoId INTEGER NOT NULL, fileName TEXT NOT NULL, path TEXT, FOREIGN KEY(parentId) REFERENCES folder(id) ON DELETE CASCADE, FOREIGN KEY(comicInfoId) REFERENCES comic_info(id))");
|
||||
success = success && queryComic.exec();
|
||||
//queryComic.finish();
|
||||
//DB INFO
|
||||
QSqlQuery queryDBInfo(database);
|
||||
queryDBInfo.prepare("CREATE TABLE db_info (version TEXT NOT NULL)");
|
||||
success = success && queryDBInfo.exec();
|
||||
//queryDBInfo.finish();
|
||||
//FOLDER (representa una carpeta en disco)
|
||||
QSqlQuery queryFolder(database);
|
||||
queryFolder.prepare("CREATE TABLE folder ("
|
||||
"id INTEGER PRIMARY KEY,"
|
||||
"parentId INTEGER NOT NULL,"
|
||||
"name TEXT NOT NULL,"
|
||||
"path TEXT NOT NULL,"
|
||||
//new 7.1 fields
|
||||
"finished BOOLEAN DEFAULT 0," //reading
|
||||
"completed BOOLEAN DEFAULT 1," //collecting
|
||||
//new 8.6 fields
|
||||
"numChildren INTEGER,"
|
||||
"firstChildHash TEXT,"
|
||||
"customImage TEXT,"
|
||||
"FOREIGN KEY(parentId) REFERENCES folder(id) ON DELETE CASCADE)");
|
||||
success = success && queryFolder.exec();
|
||||
|
||||
QSqlQuery query("INSERT INTO db_info (version) "
|
||||
"VALUES ('" VERSION "')",database);
|
||||
//query.finish();
|
||||
//COMIC (representa un cómic en disco, contiene el nombre de fichero)
|
||||
QSqlQuery queryComic(database);
|
||||
queryComic.prepare("CREATE TABLE comic (id INTEGER PRIMARY KEY, parentId INTEGER NOT NULL, comicInfoId INTEGER NOT NULL, fileName TEXT NOT NULL, path TEXT, FOREIGN KEY(parentId) REFERENCES folder(id) ON DELETE CASCADE, FOREIGN KEY(comicInfoId) REFERENCES comic_info(id))");
|
||||
success = success && queryComic.exec();
|
||||
//queryComic.finish();
|
||||
//DB INFO
|
||||
QSqlQuery queryDBInfo(database);
|
||||
queryDBInfo.prepare("CREATE TABLE db_info (version TEXT NOT NULL)");
|
||||
success = success && queryDBInfo.exec();
|
||||
//queryDBInfo.finish();
|
||||
|
||||
//8.0> tables
|
||||
success = success && DataBaseManagement::createV8Tables(database);
|
||||
QSqlQuery query("INSERT INTO db_info (version) "
|
||||
"VALUES ('" VERSION "')",database);
|
||||
//query.finish();
|
||||
|
||||
//8.0> tables
|
||||
success = success && DataBaseManagement::createV8Tables(database);
|
||||
}
|
||||
|
||||
return success;
|
||||
@ -415,7 +419,9 @@ bool DataBaseManagement::importComicsInfo(QString source, QString dest)
|
||||
|
||||
"edited = :edited,"
|
||||
|
||||
"comicVineID = :comicVineID"
|
||||
"comicVineID = :comicVineID,"
|
||||
|
||||
"lastTimeOpened = :lastTimeOpened"
|
||||
|
||||
" WHERE hash = :hash ");
|
||||
|
||||
@ -449,6 +455,7 @@ bool DataBaseManagement::importComicsInfo(QString source, QString dest)
|
||||
"read,"
|
||||
"edited,"
|
||||
"comicVineID,"
|
||||
"lastTimeOpened,"
|
||||
"hash)"
|
||||
|
||||
"VALUES (:title,"
|
||||
@ -486,6 +493,8 @@ bool DataBaseManagement::importComicsInfo(QString source, QString dest)
|
||||
":edited,"
|
||||
":comicVineID,"
|
||||
|
||||
":lastTimeOpened,"
|
||||
|
||||
":hash )");
|
||||
|
||||
QSqlRecord record = newInfo.record();
|
||||
@ -596,6 +605,8 @@ void DataBaseManagement::bindValuesFromRecord(const QSqlRecord & record, QSqlQue
|
||||
|
||||
bindString("comicVineID",record,query);
|
||||
|
||||
bindString("lastTimeOpened",record,query);
|
||||
|
||||
bindString("hash",record,query);
|
||||
}
|
||||
|
||||
@ -616,6 +627,19 @@ bool DataBaseManagement::addColumns(const QString &tableName, const QStringList
|
||||
return returnValue;
|
||||
}
|
||||
|
||||
bool DataBaseManagement::addConstraint(const QString &tableName, const QString &constraint, const QSqlDatabase &db)
|
||||
{
|
||||
QString sql = "ALTER TABLE %1 ADD %2";
|
||||
bool returnValue = true;
|
||||
|
||||
QSqlQuery alterTable(db);
|
||||
alterTable.prepare(sql.arg(tableName).arg(constraint));
|
||||
alterTable.exec();
|
||||
returnValue = returnValue && (alterTable.numRowsAffected() > 0);
|
||||
|
||||
return returnValue;
|
||||
}
|
||||
|
||||
void DataBaseManagement::bindString(const QString & name, const QSqlRecord & record, QSqlQuery & query)
|
||||
{
|
||||
if(!record.value(name).isNull())
|
||||
@ -686,6 +710,7 @@ bool DataBaseManagement::updateToCurrentVersion(const QString & fullPath)
|
||||
bool pre7 = false;
|
||||
bool pre7_1 = false;
|
||||
bool pre8 = false;
|
||||
bool pre8_6 = false;
|
||||
|
||||
if(compareVersions(DataBaseManagement::checkValidDB(fullPath),"7.0.0")<0)
|
||||
pre7 = true;
|
||||
@ -693,6 +718,8 @@ bool DataBaseManagement::updateToCurrentVersion(const QString & fullPath)
|
||||
pre7_1 = true;
|
||||
if(compareVersions(DataBaseManagement::checkValidDB(fullPath),"8.0.0")<0)
|
||||
pre8 = true;
|
||||
if(compareVersions(DataBaseManagement::checkValidDB(fullPath),"8.6.0")<0)
|
||||
pre8_6 = true;
|
||||
|
||||
QSqlDatabase db = loadDatabaseFromFile(fullPath);
|
||||
bool returnValue = false;
|
||||
@ -745,6 +772,27 @@ bool DataBaseManagement::updateToCurrentVersion(const QString & fullPath)
|
||||
{
|
||||
returnValue = returnValue && createV8Tables(db);
|
||||
}
|
||||
|
||||
if(pre8_6)
|
||||
{
|
||||
{//folder
|
||||
QStringList columnDefs;
|
||||
//a full library update is needed after updating the table
|
||||
columnDefs << "numChildren INTEGER";
|
||||
columnDefs << "firstChildHash TEXT";
|
||||
columnDefs << "customImage TEXT";
|
||||
returnValue = returnValue && addColumns("folder", columnDefs, db);
|
||||
}
|
||||
|
||||
{//comic_info
|
||||
QStringList columnDefs;
|
||||
columnDefs << "lastTimeOpened INTEGER";
|
||||
returnValue = returnValue && addColumns("comic_info", columnDefs, db);
|
||||
|
||||
QSqlQuery queryIndexLastTimeOpened(db);
|
||||
returnValue = returnValue && queryIndexLastTimeOpened.exec("CREATE INDEX last_time_opened_index ON comic_info (lastTimeOpened)");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
db.close();
|
||||
|
@ -38,6 +38,7 @@ private:
|
||||
static void bindValuesFromRecord(const QSqlRecord & record, QSqlQuery & query);
|
||||
|
||||
static bool addColumns(const QString & tableName, const QStringList & columnDefs, const QSqlDatabase & db);
|
||||
static bool addConstraint(const QString &tableName, const QString & constraint, const QSqlDatabase & db);
|
||||
|
||||
public:
|
||||
DataBaseManagement();
|
||||
|
@ -184,6 +184,9 @@ QVariant FolderModel::data(const QModelIndex &index, int role) const
|
||||
if(role == FolderModel::FinishedRole)
|
||||
return item->data(FolderModel::Finished);
|
||||
|
||||
if(role == FolderModel::IdRole)
|
||||
return item->id;
|
||||
|
||||
if (role != Qt::DisplayRole)
|
||||
return QVariant();
|
||||
|
||||
@ -315,7 +318,7 @@ void FolderModel::setupModelData(QSqlQuery &sqlquery, FolderItem *parent)
|
||||
//el diccionario permitir<69> encontrar cualquier nodo del <20>rbol r<>pidamente, de forma que a<>adir un hijo a un padre sea O(1)
|
||||
items.clear();
|
||||
//se a<>ade el nodo 0
|
||||
items.insert(parent->id,parent);
|
||||
items.insert(parent->id,parent);
|
||||
|
||||
QSqlRecord record = sqlquery.record();
|
||||
|
||||
@ -562,6 +565,7 @@ QModelIndex FolderModel::addFolderAtParent(const QString &folderName, const QMod
|
||||
|
||||
QSqlDatabase db = DataBaseManagement::loadDatabase(_databasePath);
|
||||
newFolder.id = DBHelper::insert(&newFolder, db);
|
||||
DBHelper::updateChildrenInfo(parentItem->id, db);
|
||||
QSqlDatabase::removeDatabase(_databasePath);
|
||||
|
||||
int destRow = 0;
|
||||
@ -600,11 +604,18 @@ void FolderModel::deleteFolder(const QModelIndex &mi)
|
||||
|
||||
QSqlDatabase db = DataBaseManagement::loadDatabase(_databasePath);
|
||||
DBHelper::removeFromDB(&f,db);
|
||||
DBHelper::updateChildrenInfo(item->parent()->id, db);
|
||||
QSqlDatabase::removeDatabase(_databasePath);
|
||||
|
||||
endRemoveRows();
|
||||
}
|
||||
|
||||
void FolderModel::updateFolderChildrenInfo(qulonglong folderId)
|
||||
{
|
||||
QSqlDatabase db = DataBaseManagement::loadDatabase(_databasePath);
|
||||
DBHelper::updateChildrenInfo(folderId, db);
|
||||
QSqlDatabase::removeDatabase(_databasePath);
|
||||
}
|
||||
|
||||
//PROXY
|
||||
|
||||
|
@ -127,11 +127,13 @@ public:
|
||||
|
||||
enum Roles {
|
||||
FinishedRole = Qt::UserRole + 1,
|
||||
CompletedRole
|
||||
CompletedRole,
|
||||
IdRole
|
||||
};
|
||||
|
||||
public slots:
|
||||
void deleteFolder(const QModelIndex & mi);
|
||||
void updateFolderChildrenInfo(qulonglong folderId);
|
||||
|
||||
private:
|
||||
void setupModelData( QSqlQuery &sqlquery, FolderItem *parent);
|
||||
|
@ -14,6 +14,7 @@
|
||||
|
||||
#include <limits>
|
||||
|
||||
#include "reading_list_item.h"
|
||||
#include "library_item.h"
|
||||
#include "comic_db.h"
|
||||
#include "data_base_management.h"
|
||||
@ -58,6 +59,38 @@ QList<LibraryItem *> DBHelper::getFolderComicsFromLibrary(qulonglong libraryId,
|
||||
QSqlDatabase::removeDatabase(libraryPath);
|
||||
return list;
|
||||
}
|
||||
|
||||
quint32 DBHelper::getNumChildrenFromFolder(qulonglong libraryId, qulonglong folderId)
|
||||
{
|
||||
QString libraryPath = DBHelper::getLibraries().getPath(libraryId);
|
||||
QSqlDatabase db = DataBaseManagement::loadDatabase(libraryPath+"/.yacreaderlibrary");
|
||||
|
||||
quint32 result = 0;
|
||||
|
||||
{
|
||||
QSqlQuery selectQuery(db);
|
||||
selectQuery.prepare("SELECT count(*) FROM folder WHERE parentId = :parentId and id <> 1");
|
||||
selectQuery.bindValue(":parentId", folderId);
|
||||
selectQuery.exec();
|
||||
|
||||
result += selectQuery.record().value(0).toULongLong();
|
||||
}
|
||||
|
||||
{
|
||||
QSqlQuery selectQuery(db);
|
||||
selectQuery.prepare("SELECT count(*) FROM comic c WHERE c.parentId = :parentId");
|
||||
selectQuery.bindValue(":parentId", folderId);
|
||||
selectQuery.exec();
|
||||
|
||||
result += selectQuery.record().value(0).toULongLong();
|
||||
}
|
||||
|
||||
db.close();
|
||||
QSqlDatabase::removeDatabase(libraryPath);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
qulonglong DBHelper::getParentFromComicFolderId(qulonglong libraryId, qulonglong id)
|
||||
{
|
||||
QString libraryPath = DBHelper::getLibraries().getPath(libraryId);
|
||||
@ -108,7 +141,7 @@ QString DBHelper::getFolderName(qulonglong libraryId, qulonglong id)
|
||||
if(selectQuery.next())
|
||||
{
|
||||
QSqlRecord record = selectQuery.record();
|
||||
name = record.value(0).toString();
|
||||
name = record.value(0).toString();
|
||||
}
|
||||
}
|
||||
|
||||
@ -126,14 +159,141 @@ QString DBHelper::getLibraryName(int id)
|
||||
{
|
||||
return getLibraries().getName(id);
|
||||
}
|
||||
|
||||
QList<ComicDB> DBHelper::getLabelComics(qulonglong libraryId, qulonglong labelId)
|
||||
{
|
||||
QString libraryPath = DBHelper::getLibraries().getPath(libraryId);
|
||||
QSqlDatabase db = DataBaseManagement::loadDatabase(libraryPath+"/.yacreaderlibrary");
|
||||
|
||||
QList<ComicDB> list;
|
||||
|
||||
{
|
||||
QSqlQuery selectQuery(db);
|
||||
selectQuery.prepare("SELECT c.id,c.fileName,ci.title,ci.currentPage,ci.numPages,ci.hash,ci.read "
|
||||
"FROM comic c INNER JOIN comic_info ci ON (c.comicInfoId = ci.id) "
|
||||
"INNER JOIN comic_label cl ON (c.id == cl.comic_id) "
|
||||
"WHERE cl.label_id = :parentLabelId "
|
||||
"ORDER BY cl.ordering");
|
||||
selectQuery.bindValue(":parentLabelId", labelId);
|
||||
selectQuery.exec();
|
||||
|
||||
while (selectQuery.next())
|
||||
{
|
||||
ComicDB comic;
|
||||
|
||||
QList<QVariant> data;
|
||||
QSqlRecord record = selectQuery.record();
|
||||
for(int i=0;i<record.count();i++)
|
||||
data << record.value(i);
|
||||
|
||||
comic.id = record.value(0).toULongLong();
|
||||
comic.parentId = labelId;
|
||||
comic.name = record.value(1).toString();
|
||||
comic.info.title = record.value(2).toString();
|
||||
comic.info.currentPage = record.value(3).toInt();
|
||||
comic.info.numPages = record.value(4).toInt();
|
||||
comic.info.hash = record.value(5).toString();
|
||||
comic.info.read = record.value(6).toBool();
|
||||
|
||||
list.append(comic);
|
||||
}
|
||||
|
||||
db.close();
|
||||
}
|
||||
|
||||
return list;
|
||||
}
|
||||
|
||||
QList<ComicDB> DBHelper::getFavorites(qulonglong libraryId)
|
||||
{
|
||||
QString libraryPath = DBHelper::getLibraries().getPath(libraryId);
|
||||
QSqlDatabase db = DataBaseManagement::loadDatabase(libraryPath+"/.yacreaderlibrary");
|
||||
|
||||
QList<ComicDB> list;
|
||||
|
||||
const int FAV_ID = 1;
|
||||
|
||||
{
|
||||
QSqlQuery selectQuery(db);
|
||||
selectQuery.prepare("SELECT c.id,c.fileName,ci.title,ci.currentPage,ci.numPages,ci.hash,ci.read "
|
||||
"FROM comic c INNER JOIN comic_info ci ON (c.comicInfoId = ci.id) "
|
||||
"INNER JOIN comic_default_reading_list cdrl ON (c.id == cdrl.comic_id) "
|
||||
"WHERE cdrl.default_reading_list_id = :parentDefaultListId "
|
||||
"ORDER BY cdrl.ordering");
|
||||
selectQuery.bindValue(":parentDefaultListId", FAV_ID);
|
||||
selectQuery.exec();
|
||||
|
||||
while (selectQuery.next())
|
||||
{
|
||||
ComicDB comic;
|
||||
|
||||
QSqlRecord record = selectQuery.record();
|
||||
|
||||
comic.id = record.value(0).toULongLong();
|
||||
comic.parentId = FAV_ID;
|
||||
comic.name = record.value(1).toString();
|
||||
comic.info.title = record.value(2).toString();
|
||||
comic.info.currentPage = record.value(3).toInt();
|
||||
comic.info.numPages = record.value(4).toInt();
|
||||
comic.info.hash = record.value(5).toString();
|
||||
comic.info.read = record.value(6).toBool();
|
||||
|
||||
list.append(comic);
|
||||
}
|
||||
|
||||
db.close();
|
||||
}
|
||||
|
||||
return list;
|
||||
}
|
||||
|
||||
QList<ComicDB> DBHelper::getReading(qulonglong libraryId)
|
||||
{
|
||||
QString libraryPath = DBHelper::getLibraries().getPath(libraryId);
|
||||
QSqlDatabase db = DataBaseManagement::loadDatabase(libraryPath+"/.yacreaderlibrary");
|
||||
|
||||
QList<ComicDB> list;
|
||||
|
||||
{
|
||||
QSqlQuery selectQuery(db);
|
||||
selectQuery.prepare("SELECT c.id,c.parentId,c.fileName,ci.title,ci.currentPage,ci.numPages,ci.hash,ci.read "
|
||||
"FROM comic c INNER JOIN comic_info ci ON (c.comicInfoId = ci.id) "
|
||||
"WHERE ci.hasBeenOpened = 1 AND ci.read = 0 AND ci.currentPage != ci.numPages AND ci.currentPage != 1 "
|
||||
"ORDER BY ci.lastTimeOpened DESC");
|
||||
selectQuery.exec();
|
||||
|
||||
while (selectQuery.next())
|
||||
{
|
||||
ComicDB comic;
|
||||
|
||||
QSqlRecord record = selectQuery.record();
|
||||
|
||||
comic.id = record.value(0).toULongLong();
|
||||
comic.parentId = record.value(1).toULongLong();
|
||||
comic.name = record.value(2).toString();
|
||||
comic.info.title = record.value(3).toString();
|
||||
comic.info.currentPage = record.value(4).toInt();
|
||||
comic.info.numPages = record.value(5).toInt();
|
||||
comic.info.hash = record.value(6).toString();
|
||||
comic.info.read = record.value(7).toBool();
|
||||
|
||||
list.append(comic);
|
||||
}
|
||||
|
||||
db.close();
|
||||
}
|
||||
|
||||
return list;
|
||||
}
|
||||
|
||||
//objects management
|
||||
//deletes
|
||||
void DBHelper::removeFromDB(LibraryItem * item, QSqlDatabase & db)
|
||||
{
|
||||
if(item->isDir())
|
||||
DBHelper::removeFromDB(dynamic_cast<Folder *>(item),db);
|
||||
DBHelper::removeFromDB(dynamic_cast<Folder *>(item),db);
|
||||
else
|
||||
DBHelper::removeFromDB(dynamic_cast<ComicDB *>(item),db);
|
||||
DBHelper::removeFromDB(dynamic_cast<ComicDB *>(item),db);
|
||||
}
|
||||
void DBHelper::removeFromDB(Folder * folder, QSqlDatabase & db)
|
||||
{
|
||||
@ -293,7 +453,10 @@ void DBHelper::update(ComicInfo * comicInfo, QSqlDatabase & db)
|
||||
"rating = :rating,"
|
||||
|
||||
//new 7.1 fields
|
||||
"comicVineID = :comicVineID"
|
||||
"comicVineID = :comicVineID,"
|
||||
|
||||
//new 8.6 fields
|
||||
"lastTimeOpened = :lastTimeOpened"
|
||||
//--
|
||||
" WHERE id = :id ");
|
||||
|
||||
@ -348,6 +511,8 @@ void DBHelper::update(ComicInfo * comicInfo, QSqlDatabase & db)
|
||||
|
||||
updateComicInfo.bindValue(":comicVineID", comicInfo->comicVineID);
|
||||
|
||||
updateComicInfo.bindValue(":lastTimeOpened", comicInfo->lastTimeOpened);
|
||||
|
||||
updateComicInfo.exec();
|
||||
}
|
||||
|
||||
@ -376,6 +541,51 @@ void DBHelper::update(const Folder & folder, QSqlDatabase &db)
|
||||
updateFolderInfo.exec();
|
||||
}
|
||||
|
||||
void DBHelper::updateChildrenInfo(const Folder & folder, QSqlDatabase & db)
|
||||
{
|
||||
QSqlQuery updateFolderInfo(db);
|
||||
updateFolderInfo.prepare("UPDATE folder SET "
|
||||
"numChildren = :numChildren, "
|
||||
"firstChildHash = :firstChildHash "
|
||||
"WHERE id = :id ");
|
||||
updateFolderInfo.bindValue(":numChildren", folder.getNumChildren());
|
||||
updateFolderInfo.bindValue(":firstChildHash", folder.getFirstChildHash());
|
||||
updateFolderInfo.bindValue(":id", folder.id);
|
||||
updateFolderInfo.exec();
|
||||
}
|
||||
|
||||
void DBHelper::updateChildrenInfo(qulonglong folderId, QSqlDatabase & db)
|
||||
{
|
||||
QList<LibraryItem *> subfolders = DBHelper::getFoldersFromParent(folderId,db,false);
|
||||
QList<LibraryItem *> comics = DBHelper::getComicsFromParent(folderId,db,true);
|
||||
|
||||
ComicDB * firstComic = NULL;
|
||||
if(comics.count() > 0)
|
||||
firstComic = static_cast<ComicDB *>(comics.first());
|
||||
|
||||
QSqlQuery updateFolderInfo(db);
|
||||
updateFolderInfo.prepare("UPDATE folder SET "
|
||||
"numChildren = :numChildren, "
|
||||
"firstChildHash = :firstChildHash "
|
||||
"WHERE id = :id ");
|
||||
updateFolderInfo.bindValue(":numChildren", subfolders.count() + comics.count());
|
||||
updateFolderInfo.bindValue(":firstChildHash", firstComic != NULL ? firstComic->info.hash : "");
|
||||
updateFolderInfo.bindValue(":id", folderId);
|
||||
updateFolderInfo.exec();
|
||||
}
|
||||
|
||||
void DBHelper::updateChildrenInfo(QSqlDatabase & db)
|
||||
{
|
||||
QSqlQuery selectQuery(db); //TODO check
|
||||
selectQuery.prepare("SELECT id FROM folder");
|
||||
selectQuery.exec();
|
||||
|
||||
while (selectQuery.next())
|
||||
{
|
||||
DBHelper::updateChildrenInfo(selectQuery.record().value(0).toULongLong(), db);
|
||||
}
|
||||
}
|
||||
|
||||
void DBHelper::updateProgress(qulonglong libraryId, const ComicInfo &comicInfo)
|
||||
{
|
||||
QString libraryPath = DBHelper::getLibraries().getPath(libraryId);
|
||||
@ -385,6 +595,8 @@ void DBHelper::updateProgress(qulonglong libraryId, const ComicInfo &comicInfo)
|
||||
comic.info.currentPage = comicInfo.currentPage;
|
||||
comic.info.hasBeenOpened = true;
|
||||
|
||||
comic.info.lastTimeOpened = QDateTime::currentSecsSinceEpoch();
|
||||
|
||||
DBHelper::update(&comic.info,db);
|
||||
|
||||
db.close();
|
||||
@ -398,12 +610,14 @@ void DBHelper::updateReadingRemoteProgress(const ComicInfo &comicInfo, QSqlDatab
|
||||
"read = :read, "
|
||||
"currentPage = :currentPage, "
|
||||
"hasBeenOpened = :hasBeenOpened, "
|
||||
"lastTimeOpened = :lastTimeOpened"
|
||||
"rating = :rating"
|
||||
" WHERE id = :id ");
|
||||
|
||||
updateComicInfo.bindValue(":read", comicInfo.read?1:0);
|
||||
updateComicInfo.bindValue(":currentPage", comicInfo.currentPage);
|
||||
updateComicInfo.bindValue(":hasBeenOpened", comicInfo.hasBeenOpened?1:0);
|
||||
updateComicInfo.bindValue(":lastTimeOpened", QDateTime::currentSecsSinceEpoch());
|
||||
updateComicInfo.bindValue(":id", comicInfo.id);
|
||||
updateComicInfo.bindValue(":rating", comicInfo.rating);
|
||||
updateComicInfo.exec();
|
||||
@ -486,7 +700,7 @@ void DBHelper::reasignOrderToComicsInFavorites(QList<qulonglong> comicIds, QSqlD
|
||||
QSqlQuery updateOrdering(db);
|
||||
updateOrdering.prepare("UPDATE comic_default_reading_list SET "
|
||||
"ordering = :ordering "
|
||||
"WHERE comic_id = :comic_id AND default_reading_list_id = 0");
|
||||
"WHERE comic_id = :comic_id AND default_reading_list_id = 1");
|
||||
db.transaction();
|
||||
int order = 0;
|
||||
foreach(qulonglong id, comicIds)
|
||||
@ -548,6 +762,7 @@ qulonglong DBHelper::insert(Folder * folder, QSqlDatabase & db)
|
||||
query.bindValue(":name", folder->name);
|
||||
query.bindValue(":path", folder->path);
|
||||
query.exec();
|
||||
|
||||
return query.lastInsertId().toULongLong();
|
||||
}
|
||||
|
||||
@ -575,6 +790,7 @@ qulonglong DBHelper::insert(ComicDB * comic, QSqlDatabase & db)
|
||||
query.bindValue(":name", comic->name);
|
||||
query.bindValue(":path", comic->path);
|
||||
query.exec();
|
||||
|
||||
return query.lastInsertId().toULongLong();
|
||||
}
|
||||
|
||||
@ -697,13 +913,22 @@ QList<LibraryItem *> DBHelper::getFoldersFromParent(qulonglong parentId, QSqlDat
|
||||
int name = record.indexOf("name");
|
||||
int path = record.indexOf("path");
|
||||
int id = record.indexOf("id");
|
||||
int numChildren = record.indexOf("numChildren");
|
||||
int firstChildHash = record.indexOf("firstChildHash");
|
||||
int customImage = record.indexOf("customImage");
|
||||
|
||||
Folder * currentItem;
|
||||
while (selectQuery.next())
|
||||
{
|
||||
//TODO sort by sort indicator and name
|
||||
currentItem = new Folder(selectQuery.value(id).toULongLong(),parentId,selectQuery.value(name).toString(),selectQuery.value(path).toString());
|
||||
int lessThan = 0;
|
||||
currentItem = new Folder(selectQuery.value(id).toULongLong(),parentId,selectQuery.value(name).toString(),selectQuery.value(path).toString());
|
||||
|
||||
if(!selectQuery.value(numChildren).isNull() && selectQuery.value(numChildren).isValid())
|
||||
currentItem->setNumChildren(selectQuery.value(numChildren).toInt());
|
||||
currentItem->setFirstChildHash(selectQuery.value(firstChildHash).toString());
|
||||
currentItem->setCustomImage(selectQuery.value(customImage).toString());
|
||||
|
||||
int lessThan = 0;
|
||||
|
||||
if(list.isEmpty() || !sort)
|
||||
list.append(currentItem);
|
||||
@ -923,6 +1148,59 @@ QList<LibraryItem *> DBHelper::getComicsFromParent(qulonglong parentId, QSqlData
|
||||
return list;
|
||||
}
|
||||
|
||||
QList<LabelItem *> DBHelper::getLabelItems(qulonglong libraryId)
|
||||
{
|
||||
QString libraryPath = DBHelper::getLibraries().getPath(libraryId);
|
||||
QSqlDatabase db = DataBaseManagement::loadDatabase(libraryPath+"/.yacreaderlibrary");
|
||||
|
||||
QSqlQuery selectQuery("SELECT * FROM label ORDER BY ordering,name",db); //TODO add some kind of
|
||||
QList<LabelItem *> labels;
|
||||
|
||||
while(selectQuery.next())
|
||||
{
|
||||
QSqlRecord record = selectQuery.record();
|
||||
LabelItem *item = new LabelItem(QList<QVariant>()
|
||||
<< record.value("name")
|
||||
<< record.value("color")
|
||||
<< record.value("id")
|
||||
<< record.value("ordering"));
|
||||
|
||||
if(labels.isEmpty())
|
||||
{
|
||||
labels << item;
|
||||
}
|
||||
else
|
||||
{
|
||||
int i = 0;
|
||||
|
||||
while (i < labels.count() && (labels.at(i)->colorid() < item->colorid()) )
|
||||
i++;
|
||||
|
||||
if(i < labels.count())
|
||||
{
|
||||
if(labels.at(i)->colorid() == item->colorid()) //sort by name
|
||||
{
|
||||
while( i < labels.count() && labels.at(i)->colorid() == item->colorid() && naturalSortLessThanCI(labels.at(i)->name(),item->name()))
|
||||
i++;
|
||||
}
|
||||
}
|
||||
if(i >= labels.count())
|
||||
{
|
||||
labels << item;
|
||||
}
|
||||
else
|
||||
{
|
||||
labels.insert(i,item);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
db.close();
|
||||
QSqlDatabase::removeDatabase(libraryPath);
|
||||
|
||||
return labels;
|
||||
}
|
||||
|
||||
//loads
|
||||
Folder DBHelper::loadFolder(qulonglong id, QSqlDatabase & db)
|
||||
{
|
||||
@ -942,6 +1220,9 @@ Folder DBHelper::loadFolder(qulonglong id, QSqlDatabase & db)
|
||||
int path = record.indexOf("path");
|
||||
int finished = record.indexOf("finished");
|
||||
int completed = record.indexOf("completed");
|
||||
int numChildren = record.indexOf("numChildren");
|
||||
int firstChildHash = record.indexOf("firstChildHash");
|
||||
int customImage = record.indexOf("customImage");
|
||||
|
||||
if(query.next())
|
||||
{
|
||||
@ -950,8 +1231,14 @@ Folder DBHelper::loadFolder(qulonglong id, QSqlDatabase & db)
|
||||
folder.path = query.value(path).toString();
|
||||
folder.knownId = true;
|
||||
//new 7.1
|
||||
|
||||
folder.setFinished(query.value(finished).toBool());
|
||||
folder.setCompleted(query.value(completed).toBool());
|
||||
//new 8.6
|
||||
if(!query.value(numChildren).isNull() && query.value(numChildren).isValid())
|
||||
folder.setNumChildren(query.value(numChildren).toInt());
|
||||
folder.setFirstChildHash(query.value(firstChildHash).toString());
|
||||
folder.setCustomImage(query.value(customImage).toString());
|
||||
}
|
||||
|
||||
return folder;
|
||||
@ -974,6 +1261,9 @@ Folder DBHelper::loadFolder(const QString &folderName, qulonglong parentId, QSql
|
||||
int path = record.indexOf("path");
|
||||
int finished = record.indexOf("finished");
|
||||
int completed = record.indexOf("completed");
|
||||
int numChildren = record.indexOf("numChildren");
|
||||
int firstChildHash = record.indexOf("firstChildHash");
|
||||
int customImage = record.indexOf("customImage");
|
||||
|
||||
folder.parentId = parentId;
|
||||
if(query.next())
|
||||
@ -985,6 +1275,11 @@ Folder DBHelper::loadFolder(const QString &folderName, qulonglong parentId, QSql
|
||||
//new 7.1
|
||||
folder.setFinished(query.value(finished).toBool());
|
||||
folder.setCompleted(query.value(completed).toBool());
|
||||
//new 8.6
|
||||
if(!query.value(numChildren).isNull() && query.value(numChildren).isValid())
|
||||
folder.setNumChildren(query.value(numChildren).toInt());
|
||||
folder.setFirstChildHash(query.value(firstChildHash).toString());
|
||||
folder.setCustomImage(query.value(customImage).toString());
|
||||
}
|
||||
|
||||
return folder;
|
||||
@ -1102,6 +1397,8 @@ ComicInfo DBHelper::loadComicInfo(QString hash, QSqlDatabase & db)
|
||||
|
||||
int comicVineID = record.indexOf("comicVineID");
|
||||
|
||||
int lastTimeOpened = record.indexOf("lastTimeOpened");
|
||||
|
||||
if(findComicInfo.next())
|
||||
{
|
||||
comicInfo.hash = hash;
|
||||
@ -1120,7 +1417,6 @@ ComicInfo DBHelper::loadComicInfo(QString hash, QSqlDatabase & db)
|
||||
comicInfo.gamma = findComicInfo.value(gamma).toInt();
|
||||
comicInfo.rating = findComicInfo.value(rating).toInt();
|
||||
//--
|
||||
|
||||
comicInfo.title = findComicInfo.value(title);
|
||||
comicInfo.numPages = findComicInfo.value(numPages);
|
||||
|
||||
@ -1156,6 +1452,10 @@ ComicInfo DBHelper::loadComicInfo(QString hash, QSqlDatabase & db)
|
||||
|
||||
comicInfo.comicVineID = findComicInfo.value(comicVineID);
|
||||
|
||||
//new 8.6 fields
|
||||
comicInfo.lastTimeOpened = findComicInfo.value(lastTimeOpened);
|
||||
//--
|
||||
|
||||
comicInfo.existOnDb = true;
|
||||
}
|
||||
else
|
||||
|
@ -9,6 +9,7 @@ class QString;
|
||||
class ComicDB;
|
||||
class Folder;
|
||||
class LibraryItem;
|
||||
class LabelItem;
|
||||
class QSqlDatabase;
|
||||
class ComicInfo;
|
||||
class QSqlRecord;
|
||||
@ -23,18 +24,22 @@ public:
|
||||
static QList<LibraryItem *> getFolderSubfoldersFromLibrary(qulonglong libraryId, qulonglong folderId);
|
||||
static QList<LibraryItem *> getFolderComicsFromLibrary(qulonglong libraryId, qulonglong folderId);
|
||||
static QList<LibraryItem *> getFolderComicsFromLibrary(qulonglong libraryId, qulonglong folderId, bool sort);
|
||||
static quint32 getNumChildrenFromFolder(qulonglong libraryId, qulonglong folderId);
|
||||
static qulonglong getParentFromComicFolderId(qulonglong libraryId, qulonglong id);
|
||||
static ComicDB getComicInfo(qulonglong libraryId, qulonglong id);
|
||||
static QList<ComicDB> getSiblings(qulonglong libraryId, qulonglong parentId);
|
||||
static QString getFolderName(qulonglong libraryId, qulonglong id);
|
||||
static QList<QString> getLibrariesNames();
|
||||
static QString getLibraryName(int id);
|
||||
static QList<ComicDB> getLabelComics(qulonglong libraryId, qulonglong labelId);
|
||||
static QList<ComicDB> getFavorites(qulonglong libraryId);
|
||||
static QList<ComicDB> getReading(qulonglong libraryId);
|
||||
|
||||
//objects management
|
||||
//deletes
|
||||
static void removeFromDB(LibraryItem * item, QSqlDatabase & db);
|
||||
static void removeFromDB(Folder * folder, QSqlDatabase & db);
|
||||
static void removeFromDB(ComicDB * comic, QSqlDatabase & db);
|
||||
static void removeFromDB(LibraryItem * item, QSqlDatabase & db);
|
||||
static void removeFromDB(Folder * folder, QSqlDatabase & db);
|
||||
static void removeFromDB(ComicDB * comic, QSqlDatabase & db);
|
||||
static void removeLabelFromDB(qulonglong id, QSqlDatabase & db);
|
||||
static void removeListFromDB(qulonglong id, QSqlDatabase & db);
|
||||
//logic deletes
|
||||
@ -42,8 +47,8 @@ public:
|
||||
static void deleteComicsFromLabel(const QList<ComicDB> & comicsList, qulonglong labelId, QSqlDatabase & db);
|
||||
static void deleteComicsFromReadingList(const QList<ComicDB> & comicsList, qulonglong readingListId, QSqlDatabase & db);
|
||||
//inserts
|
||||
static qulonglong insert(Folder * folder, QSqlDatabase & db);
|
||||
static qulonglong insert(ComicDB * comic, QSqlDatabase & db);
|
||||
static qulonglong insert(Folder * folder, QSqlDatabase & db);
|
||||
static qulonglong insert(ComicDB * comic, QSqlDatabase & db);
|
||||
static qulonglong insertLabel(const QString & name, YACReader::LabelColors color , QSqlDatabase & db);
|
||||
static qulonglong insertReadingList(const QString & name, QSqlDatabase & db);
|
||||
static qulonglong insertReadingSubList(const QString & name, qulonglong parentId, int ordering, QSqlDatabase & db);
|
||||
@ -56,6 +61,9 @@ public:
|
||||
static void update(ComicInfo * comicInfo, QSqlDatabase & db);
|
||||
static void updateRead(ComicInfo * comicInfo, QSqlDatabase & db);
|
||||
static void update(const Folder & folder, QSqlDatabase & db);
|
||||
static void updateChildrenInfo(const Folder & folder, QSqlDatabase & db);
|
||||
static void updateChildrenInfo(qulonglong folderId, QSqlDatabase & db);
|
||||
static void updateChildrenInfo(QSqlDatabase & db);
|
||||
static void updateProgress(qulonglong libraryId,const ComicInfo & comicInfo);
|
||||
static void updateReadingRemoteProgress(const ComicInfo & comicInfo, QSqlDatabase & db);
|
||||
static void updateFromRemoteClient(qulonglong libraryId,const ComicInfo & comicInfo);
|
||||
@ -69,7 +77,9 @@ public:
|
||||
static QList<LibraryItem *> getFoldersFromParent(qulonglong parentId, QSqlDatabase & db, bool sort = true);
|
||||
static QList<ComicDB> getSortedComicsFromParent(qulonglong parentId, QSqlDatabase & db);
|
||||
static QList<LibraryItem *> getComicsFromParent(qulonglong parentId, QSqlDatabase & db, bool sort = true);
|
||||
//load
|
||||
static QList<LabelItem *> getLabelItems(qulonglong libraryId);
|
||||
|
||||
//load
|
||||
static Folder loadFolder(qulonglong id, QSqlDatabase & db);
|
||||
static Folder loadFolder(const QString & folderName, qulonglong parentId, QSqlDatabase & db);
|
||||
static ComicDB loadComic(qulonglong id, QSqlDatabase & db);
|
||||
|
@ -150,6 +150,9 @@ void LibraryCreator::run()
|
||||
_database.transaction();
|
||||
//se crea la librería
|
||||
create(QDir(_source));
|
||||
|
||||
DBHelper::updateChildrenInfo(_database);
|
||||
|
||||
_database.commit();
|
||||
_database.close();
|
||||
QSqlDatabase::removeDatabase(_database.connectionName());
|
||||
@ -187,6 +190,12 @@ void LibraryCreator::run()
|
||||
{
|
||||
update(QDir(_source));
|
||||
}
|
||||
|
||||
if(partialUpdate)
|
||||
DBHelper::updateChildrenInfo(folderDestinationModelIndex.data(FolderModel::IdRole).toULongLong(),_database);
|
||||
else
|
||||
DBHelper::updateChildrenInfo(_database);
|
||||
|
||||
_database.commit();
|
||||
_database.close();
|
||||
QSqlDatabase::removeDatabase(_target);
|
||||
@ -231,7 +240,7 @@ qulonglong LibraryCreator::insertFolders()
|
||||
if(!(i->knownId))
|
||||
{
|
||||
i->setFather(currentId);
|
||||
currentId = DBHelper::insert(&(*i),_database);//insertFolder(currentId,*i);
|
||||
currentId = DBHelper::insert(&(*i),_database);//insertFolder(currentId,*i);
|
||||
i->setId(currentId);
|
||||
}
|
||||
else
|
||||
|
@ -1233,7 +1233,7 @@ void LibraryWindow::loadLibrary(const QString & name)
|
||||
|
||||
if(d.exists(path+"/library.ydb"))
|
||||
{
|
||||
QSqlDatabase db = DataBaseManagement::loadDatabase(path);
|
||||
QSqlDatabase db = DataBaseManagement::loadDatabase(path);
|
||||
manageOpeningLibraryError(db.lastError().databaseText() + "-" + db.lastError().driverText());
|
||||
//será possible renombrar y borrar estas bibliotecas
|
||||
renameLibraryAction->setEnabled(true);
|
||||
@ -2464,7 +2464,7 @@ void LibraryWindow::deleteComicsFromDisk()
|
||||
QLOG_TRACE() << comic.parentId;
|
||||
}
|
||||
|
||||
ComicsRemover * remover = new ComicsRemover(indexList,paths);
|
||||
ComicsRemover * remover = new ComicsRemover(indexList,paths,comics.at(0).parentId);
|
||||
QThread * thread = NULL;
|
||||
|
||||
thread = new QThread(this);
|
||||
@ -2477,6 +2477,8 @@ void LibraryWindow::deleteComicsFromDisk()
|
||||
connect(remover, SIGNAL(remove(int)), comicsModel, SLOT(remove(int)));
|
||||
connect(remover, SIGNAL(removeError()),this,SLOT(setRemoveError()));
|
||||
connect(remover, SIGNAL(finished()), comicsModel, SLOT(finishTransaction()));
|
||||
connect(remover, SIGNAL(finished()), comicsModel, SLOT(finishTransaction()));
|
||||
connect(remover, SIGNAL(removedItemsFromFolder(qulonglong)), foldersModel, SLOT(updateFolderChildrenInfo(qulonglong)));
|
||||
|
||||
connect(remover, SIGNAL(finished()),this,SLOT(checkEmptyFolder()));
|
||||
connect(remover, SIGNAL(finished()),this,SLOT(checkRemoveError()));
|
||||
|
@ -2,6 +2,7 @@
|
||||
|
||||
#include "db_helper.h"
|
||||
#include "yacreader_libraries.h"
|
||||
#include "yacreader_http_session.h"
|
||||
|
||||
#include "template.h"
|
||||
#include "../static.h"
|
||||
@ -18,6 +19,7 @@ ComicController::ComicController() {}
|
||||
void ComicController::service(HttpRequest& request, HttpResponse& response)
|
||||
{
|
||||
HttpSession session=Static::sessionStore->getSession(request,response,false);
|
||||
YACReaderHttpSession *ySession = Static::yacreaderSessionStore->getYACReaderSessionHttpSession(session.getId());
|
||||
|
||||
QString path = QUrl::fromPercentEncoding(request.getPath()).toUtf8();
|
||||
QStringList pathElements = path.split('/');
|
||||
@ -44,7 +46,7 @@ void ComicController::service(HttpRequest& request, HttpResponse& response)
|
||||
ComicDB comic = DBHelper::getComicInfo(libraryId, comicId);
|
||||
|
||||
if(!remoteComic)
|
||||
session.setDownloadedComic(comic.info.hash);
|
||||
ySession->setDownloadedComic(comic.info.hash);
|
||||
|
||||
Comic * comicFile = FactoryComic::newComic(libraries.getPath(libraryId)+comic.path);
|
||||
|
||||
@ -70,19 +72,19 @@ void ComicController::service(HttpRequest& request, HttpResponse& response)
|
||||
if(remoteComic)
|
||||
{
|
||||
QLOG_TRACE() << "remote comic requested";
|
||||
session.setCurrentRemoteComic(comic.id, comicFile);
|
||||
ySession->setCurrentRemoteComic(comic.id, comicFile);
|
||||
|
||||
}
|
||||
else
|
||||
{
|
||||
QLOG_TRACE() << "comic requested";
|
||||
session.setCurrentComic(comic.id, comicFile);
|
||||
ySession->setCurrentComic(comic.id, comicFile);
|
||||
}
|
||||
|
||||
response.setHeader("Content-Type", "plain/text; charset=utf-8");
|
||||
response.setHeader("Content-Type", "text/plain; 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));
|
||||
response.write(QString("library:%1\r\n").arg(libraryName).toUtf8());
|
||||
response.write(QString("libraryId:%1\r\n").arg(libraryId).toUtf8());
|
||||
if(remoteComic) //send previous and next comics id
|
||||
{
|
||||
QList<LibraryItem *> siblings = DBHelper::getFolderComicsFromLibrary(libraryId, comic.parentId, true);
|
||||
@ -99,9 +101,9 @@ void ComicController::service(HttpRequest& request, HttpResponse& response)
|
||||
if(found)
|
||||
{
|
||||
if(i>0)
|
||||
response.writeText(QString("previousComic:%1\r\n").arg(siblings.at(i-1)->id));
|
||||
response.write(QString("previousComic:%1\r\n").arg(siblings.at(i-1)->id).toUtf8());
|
||||
if(i<siblings.length()-1)
|
||||
response.writeText(QString("nextComic:%1\r\n").arg(siblings.at(i+1)->id));
|
||||
response.write(QString("nextComic:%1\r\n").arg(siblings.at(i+1)->id).toUtf8());
|
||||
}
|
||||
else
|
||||
{
|
||||
@ -109,7 +111,7 @@ void ComicController::service(HttpRequest& request, HttpResponse& response)
|
||||
}
|
||||
qDeleteAll(siblings);
|
||||
}
|
||||
response.writeText(comic.toTXT(),true);
|
||||
response.write(comic.toTXT().toUtf8(),true);
|
||||
}
|
||||
else
|
||||
{
|
||||
|
@ -10,7 +10,7 @@ ComicDownloadInfoController::ComicDownloadInfoController() {}
|
||||
|
||||
void ComicDownloadInfoController::service(HttpRequest& request, HttpResponse& response)
|
||||
{
|
||||
response.setHeader("Content-Type", "plain/text; charset=utf-8");
|
||||
response.setHeader("Content-Type", "text/plain; charset=utf-8");
|
||||
|
||||
QString path = QUrl::fromPercentEncoding(request.getPath()).toUtf8();
|
||||
QStringList pathElements = path.split('/');
|
||||
@ -21,6 +21,6 @@ void ComicDownloadInfoController::service(HttpRequest& request, HttpResponse& re
|
||||
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);
|
||||
response.write(QString("fileName:%1\r\n").arg(comic.getFileName()).toUtf8());
|
||||
response.write(QString("fileSize:%1\r\n").arg(comic.getFileSize()).toUtf8(),true);
|
||||
}
|
||||
|
@ -1,6 +1,7 @@
|
||||
#include "covercontroller.h"
|
||||
#include "db_helper.h" //get libraries
|
||||
#include "yacreader_libraries.h"
|
||||
#include "yacreader_http_session.h"
|
||||
|
||||
#include "template.h"
|
||||
#include "../static.h"
|
||||
@ -9,8 +10,8 @@ CoverController::CoverController() {}
|
||||
|
||||
void CoverController::service(HttpRequest& request, HttpResponse& response)
|
||||
{
|
||||
|
||||
HttpSession session=Static::sessionStore->getSession(request,response,false);
|
||||
YACReaderHttpSession *ySession = Static::yacreaderSessionStore->getYACReaderSessionHttpSession(session.getId());
|
||||
|
||||
response.setHeader("Content-Type", "image/jpeg");
|
||||
response.setHeader("Connection","close");
|
||||
@ -47,7 +48,7 @@ void CoverController::service(HttpRequest& request, HttpResponse& response)
|
||||
if (!img.isNull()) {
|
||||
|
||||
int width = 80, height = 120;
|
||||
if(session.getDisplayType()=="@2x")
|
||||
if(ySession->getDisplayType()=="@2x")
|
||||
{
|
||||
width = 160;
|
||||
height = 240;
|
||||
@ -66,7 +67,7 @@ void CoverController::service(HttpRequest& request, HttpResponse& response)
|
||||
|
||||
if(folderCover)
|
||||
{
|
||||
if(session.getDisplayType()=="@2x")
|
||||
if(ySession->getDisplayType()=="@2x")
|
||||
p.drawImage(0,0,QImage(":/images/f_overlayed_retina.png"));
|
||||
else
|
||||
p.drawImage(0,0,QImage(":/images/f_overlayed.png"));
|
||||
|
33
YACReaderLibrary/server/controllers/favoritescontroller.cpp
Normal file
33
YACReaderLibrary/server/controllers/favoritescontroller.cpp
Normal file
@ -0,0 +1,33 @@
|
||||
#include "favoritescontroller.h"
|
||||
|
||||
#include "db_helper.h"
|
||||
#include "comic_db.h"
|
||||
|
||||
#include "yacreader_server_data_helper.h"
|
||||
|
||||
FavoritesController::FavoritesController() {}
|
||||
|
||||
void FavoritesController::service(HttpRequest &request, HttpResponse &response)
|
||||
{
|
||||
response.setHeader("Content-Type", "text/plain; charset=utf-8");
|
||||
|
||||
QString path = QUrl::fromPercentEncoding(request.getPath()).toUtf8();
|
||||
QStringList pathElements = path.split('/');
|
||||
int libraryId = pathElements.at(2).toInt();
|
||||
|
||||
serviceContent(libraryId, response);
|
||||
|
||||
response.write("",true);
|
||||
}
|
||||
|
||||
void FavoritesController::serviceContent(const int library, HttpResponse &response)
|
||||
{
|
||||
QList<ComicDB> tagComics = DBHelper::getFavorites(library);
|
||||
|
||||
for(const ComicDB &comic : tagComics)
|
||||
{
|
||||
response.write(YACReaderServerDataHelper::comicToYSFormat(library, comic).toUtf8());
|
||||
}
|
||||
}
|
||||
|
||||
|
21
YACReaderLibrary/server/controllers/favoritescontroller.h
Normal file
21
YACReaderLibrary/server/controllers/favoritescontroller.h
Normal file
@ -0,0 +1,21 @@
|
||||
#ifndef FAVORITESCONTROLLER_H
|
||||
#define FAVORITESCONTROLLER_H
|
||||
|
||||
#include "httprequest.h"
|
||||
#include "httpresponse.h"
|
||||
#include "httprequesthandler.h"
|
||||
|
||||
class FavoritesController : public HttpRequestHandler {
|
||||
Q_OBJECT
|
||||
Q_DISABLE_COPY(FavoritesController)
|
||||
public:
|
||||
FavoritesController();
|
||||
|
||||
void service(HttpRequest& request, HttpResponse& response);
|
||||
|
||||
private:
|
||||
void serviceContent(const int library, HttpResponse &response);
|
||||
};
|
||||
|
||||
|
||||
#endif // FAVORITESCONTROLLER_H
|
@ -0,0 +1,72 @@
|
||||
#include "foldercontentcontroller.h"
|
||||
|
||||
#include <QUrl>
|
||||
|
||||
#include "db_helper.h"
|
||||
#include "comic_db.h"
|
||||
#include "folder.h"
|
||||
|
||||
#include "yacreader_server_data_helper.h"
|
||||
|
||||
#include "qnaturalsorting.h"
|
||||
|
||||
#include <ctime>
|
||||
using namespace std;
|
||||
|
||||
struct LibraryItemSorter
|
||||
{
|
||||
bool operator()(const LibraryItem * a,const LibraryItem * b) const
|
||||
{
|
||||
return naturalSortLessThanCI(a->name,b->name);
|
||||
}
|
||||
};
|
||||
|
||||
FolderContentController::FolderContentController() {}
|
||||
|
||||
void FolderContentController::service(HttpRequest& request, HttpResponse& response)
|
||||
{
|
||||
response.setHeader("Content-Type", "text/plain; charset=utf-8");
|
||||
|
||||
QString path = QUrl::fromPercentEncoding(request.getPath()).toUtf8();
|
||||
QStringList pathElements = path.split('/');
|
||||
int libraryId = pathElements.at(2).toInt();
|
||||
qulonglong parentId = pathElements.at(4).toULongLong();
|
||||
|
||||
serviceContent(libraryId, parentId, response);
|
||||
|
||||
response.write("",true);
|
||||
}
|
||||
|
||||
void FolderContentController::serviceContent(const int &library, const qulonglong &folderId, HttpResponse &response)
|
||||
{
|
||||
//clock_t begin = clock();
|
||||
|
||||
QList<LibraryItem *> folderContent = DBHelper::getFolderSubfoldersFromLibrary(library,folderId);
|
||||
QList<LibraryItem *> folderComics = DBHelper::getFolderComicsFromLibrary(library,folderId);
|
||||
|
||||
folderContent.append(folderComics);
|
||||
qSort(folderContent.begin(),folderContent.end(),LibraryItemSorter());
|
||||
|
||||
folderComics.clear();
|
||||
|
||||
ComicDB * currentComic;
|
||||
Folder * currentFolder;
|
||||
for(QList<LibraryItem *>::const_iterator itr = folderContent.constBegin();itr!=folderContent.constEnd();itr++)
|
||||
{
|
||||
if((*itr)->isDir())
|
||||
{
|
||||
currentFolder = (Folder *)(*itr);
|
||||
response.write(YACReaderServerDataHelper::folderToYSFormat(library, *currentFolder).toUtf8());
|
||||
}
|
||||
else
|
||||
{
|
||||
currentComic = (ComicDB *)(*itr);
|
||||
response.write(YACReaderServerDataHelper::comicToYSFormat(library, *currentComic).toUtf8());
|
||||
}
|
||||
}
|
||||
|
||||
/*clock_t end = clock();
|
||||
double msecs = double(end - begin);
|
||||
|
||||
response.write(QString("%1ms").arg(msecs).toUtf8());*/
|
||||
}
|
@ -0,0 +1,22 @@
|
||||
#ifndef FOLDERCONTENTCONTROLLER_H
|
||||
#define FOLDERCONTENTCONTROLLER_H
|
||||
|
||||
#include "httprequest.h"
|
||||
#include "httpresponse.h"
|
||||
#include "httprequesthandler.h"
|
||||
|
||||
class FolderContentController : public HttpRequestHandler {
|
||||
Q_OBJECT
|
||||
Q_DISABLE_COPY(FolderContentController);
|
||||
public:
|
||||
/** Constructor */
|
||||
FolderContentController();
|
||||
|
||||
/** Generates the response */
|
||||
void service(HttpRequest& request, HttpResponse& response);
|
||||
|
||||
private:
|
||||
void serviceContent(const int &library, const qulonglong &folderId, HttpResponse &response);
|
||||
};
|
||||
|
||||
#endif // FOLDERCONTENTCONTROLLER_H
|
@ -11,6 +11,8 @@
|
||||
|
||||
#include "qnaturalsorting.h"
|
||||
|
||||
#include "yacreader_http_session.h"
|
||||
|
||||
#include "QsLog.h"
|
||||
|
||||
struct LibraryItemSorter
|
||||
@ -26,6 +28,7 @@ FolderController::FolderController() {}
|
||||
void FolderController::service(HttpRequest& request, HttpResponse& response)
|
||||
{
|
||||
HttpSession session=Static::sessionStore->getSession(request,response,false);
|
||||
YACReaderHttpSession *ySession = Static::yacreaderSessionStore->getYACReaderSessionHttpSession(session.getId());
|
||||
|
||||
response.setHeader("Content-Type", "text/html; charset=utf-8");
|
||||
response.setHeader("Connection","close");
|
||||
@ -33,7 +36,7 @@ void FolderController::service(HttpRequest& request, HttpResponse& response)
|
||||
//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"));
|
||||
Template t = Static::templateLoader->getTemplate("folder_"+ySession->getDeviceType(),request.getHeader("Accept-Language"));
|
||||
t.enableWarnings();
|
||||
QString path = QUrl::fromPercentEncoding(request.getPath()).toUtf8();
|
||||
QStringList pathElements = path.split('/');
|
||||
@ -85,17 +88,17 @@ void FolderController::service(HttpRequest& request, HttpResponse& response)
|
||||
|
||||
if(folderId == 1)
|
||||
{
|
||||
session.clearNavigationPath();
|
||||
session.pushNavigationItem(QPair<qulonglong,quint32>(folderId,page));
|
||||
ySession->clearNavigationPath();
|
||||
ySession->pushNavigationItem(QPair<qulonglong,quint32>(folderId,page));
|
||||
t.setVariable(QString("upurl"),"/");
|
||||
}
|
||||
else
|
||||
{
|
||||
if(fromUp)
|
||||
session.popNavigationItem();
|
||||
ySession->popNavigationItem();
|
||||
else //drill down or direct access
|
||||
{
|
||||
QStack<QPair<qulonglong, quint32> > path = session.getNavigationPath();
|
||||
QStack<QPair<qulonglong, quint32> > path = ySession->getNavigationPath();
|
||||
bool found=false;
|
||||
for(QStack<QPair<qulonglong, quint32> >::const_iterator itr = path.begin(); itr!=path.end(); itr++)
|
||||
if(itr->first == folderId)
|
||||
@ -106,16 +109,16 @@ void FolderController::service(HttpRequest& request, HttpResponse& response)
|
||||
|
||||
if(found)
|
||||
{
|
||||
while(session.topNavigationItem().first != folderId)
|
||||
session.popNavigationItem();
|
||||
while(ySession->topNavigationItem().first != folderId)
|
||||
ySession->popNavigationItem();
|
||||
|
||||
session.updateTopItem(QPair<qulonglong,quint32>(folderId,page));
|
||||
ySession->updateTopItem(QPair<qulonglong,quint32>(folderId,page));
|
||||
}
|
||||
else
|
||||
session.pushNavigationItem(QPair<qulonglong,quint32>(folderId,page));
|
||||
ySession->pushNavigationItem(QPair<qulonglong,quint32>(folderId,page));
|
||||
}
|
||||
|
||||
QStack<QPair<qulonglong, quint32> > path = session.getNavigationPath();
|
||||
QStack<QPair<qulonglong, quint32> > path = ySession->getNavigationPath();
|
||||
if(path.count()>1)
|
||||
{
|
||||
QPair<qulonglong, quint32> parentItem = path.at(path.count()-2);
|
||||
@ -146,7 +149,7 @@ void FolderController::service(HttpRequest& request, HttpResponse& response)
|
||||
int numFoldersAtCurrentPage = qMax(0,qMin(numFolders - indexCurrentPage, elementsPerPage));
|
||||
|
||||
//PATH
|
||||
QStack<QPair<qulonglong,quint32> > foldersPath = session.getNavigationPath();
|
||||
QStack<QPair<qulonglong,quint32> > foldersPath = ySession->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);
|
||||
@ -167,11 +170,11 @@ void FolderController::service(HttpRequest& request, HttpResponse& response)
|
||||
{
|
||||
t.setVariable(QString("element%1.class").arg(i),"folder");
|
||||
|
||||
QList<LibraryItem *> children = DBHelper::getFolderComicsFromLibrary(libraryId, item->id);
|
||||
if(children.length()>0)
|
||||
const Folder * folder = static_cast<Folder*>(item);
|
||||
|
||||
if(folder->getFirstChildHash().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));
|
||||
t.setVariable(QString("element%1.image.url").arg(i),QString("/library/%1/cover/%2.jpg?folderCover=true").arg(libraryId).arg(folder->getFirstChildHash()));
|
||||
}
|
||||
else
|
||||
t.setVariable(QString("element%1.image.url").arg(i),"/images/f.png");
|
||||
@ -195,9 +198,9 @@ void FolderController::service(HttpRequest& request, HttpResponse& response)
|
||||
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))
|
||||
if(!ySession->isComicOnDevice(comic->info.hash) && !ySession->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))
|
||||
else if (ySession->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>"));
|
||||
@ -316,6 +319,5 @@ void FolderController::service(HttpRequest& request, HttpResponse& response)
|
||||
t.setVariable("page",QString("%1").arg(page+1));
|
||||
t.setVariable("pages",QString("%1").arg(numPages));
|
||||
|
||||
response.writeText(t, true);
|
||||
|
||||
response.write(t.toUtf8(), true);
|
||||
}
|
||||
|
@ -12,7 +12,7 @@ FolderInfoController::FolderInfoController() {}
|
||||
|
||||
void FolderInfoController::service(HttpRequest& request, HttpResponse& response)
|
||||
{
|
||||
response.setHeader("Content-Type", "plain/text; charset=utf-8");
|
||||
response.setHeader("Content-Type", "text/plain; charset=utf-8");
|
||||
|
||||
QString path = QUrl::fromPercentEncoding(request.getPath()).toUtf8();
|
||||
QStringList pathElements = path.split('/');
|
||||
@ -22,7 +22,7 @@ void FolderInfoController::service(HttpRequest& request, HttpResponse& response)
|
||||
|
||||
serviceComics(libraryId, parentId, response);
|
||||
|
||||
response.writeText("",true);
|
||||
response.write("",true);
|
||||
}
|
||||
|
||||
void FolderInfoController::serviceComics(const int &library, const qulonglong &folderId, HttpResponse &response)
|
||||
@ -34,7 +34,7 @@ void FolderInfoController::serviceComics(const int &library, const qulonglong &f
|
||||
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()));
|
||||
response.write(QString("/library/%1/comic/%2:%3:%4\r\n").arg(library).arg(currentComic->id).arg(currentComic->getFileName()).arg(currentComic->getFileSize()).toUtf8());
|
||||
delete currentComic;
|
||||
}
|
||||
|
||||
|
@ -1,6 +1,7 @@
|
||||
#include "librariescontroller.h"
|
||||
#include "db_helper.h" //get libraries
|
||||
#include "yacreader_libraries.h"
|
||||
#include "yacreader_http_session.h"
|
||||
|
||||
#include "template.h"
|
||||
#include "../static.h"
|
||||
@ -12,13 +13,14 @@ LibrariesController::LibrariesController() {}
|
||||
void LibrariesController::service(HttpRequest& request, HttpResponse& response)
|
||||
{
|
||||
HttpSession session=Static::sessionStore->getSession(request,response,false);
|
||||
YACReaderHttpSession *ySession = Static::yacreaderSessionStore->getYACReaderSessionHttpSession(session.getId());
|
||||
|
||||
response.setHeader("Content-Type", "text/html; charset=utf-8");
|
||||
response.setHeader("Connection","close");
|
||||
|
||||
session.clearNavigationPath();
|
||||
ySession->clearNavigationPath();
|
||||
|
||||
Template t=Static::templateLoader->getTemplate("libraries_"+session.getDeviceType(),request.getHeader("Accept-Language"));
|
||||
Template t=Static::templateLoader->getTemplate("libraries_"+ySession->getDeviceType(),request.getHeader("Accept-Language"));
|
||||
t.enableWarnings();
|
||||
|
||||
YACReaderLibraries libraries = DBHelper::getLibraries();
|
||||
@ -36,5 +38,5 @@ void LibrariesController::service(HttpRequest& request, HttpResponse& response)
|
||||
}
|
||||
|
||||
response.setStatus(200,"OK");
|
||||
response.writeText(t,true);
|
||||
response.write(t.toUtf8(),true);
|
||||
}
|
||||
|
@ -4,6 +4,8 @@
|
||||
|
||||
#include "comic.h"
|
||||
#include "comiccontroller.h"
|
||||
#include "yacreader_http_session.h"
|
||||
|
||||
#include <QDataStream>
|
||||
#include <QPointer>
|
||||
|
||||
@ -16,6 +18,7 @@ PageController::PageController() {}
|
||||
void PageController::service(HttpRequest& request, HttpResponse& response)
|
||||
{
|
||||
HttpSession session=Static::sessionStore->getSession(request,response,false);
|
||||
YACReaderHttpSession *ySession = Static::yacreaderSessionStore->getYACReaderSessionHttpSession(session.getId());
|
||||
|
||||
QString path = QUrl::fromPercentEncoding(request.getPath()).toUtf8();
|
||||
bool remote = path.endsWith("remote");
|
||||
@ -35,14 +38,14 @@ void PageController::service(HttpRequest& request, HttpResponse& response)
|
||||
if(remote)
|
||||
{
|
||||
QLOG_TRACE() << "se recupera comic remoto para servir páginas";
|
||||
comicFile = session.getCurrentRemoteComic();
|
||||
currentComicId = session.getCurrentRemoteComicId();
|
||||
comicFile = ySession->getCurrentRemoteComic();
|
||||
currentComicId = ySession->getCurrentRemoteComicId();
|
||||
}
|
||||
else
|
||||
{
|
||||
QLOG_TRACE() << "se recupera comic para servir páginas";
|
||||
comicFile = session.getCurrentComic();
|
||||
currentComicId = session.getCurrentComicId();
|
||||
comicFile = ySession->getCurrentComic();
|
||||
currentComicId = ySession->getCurrentComicId();
|
||||
}
|
||||
|
||||
if(currentComicId != 0 && !QPointer<Comic>(comicFile).isNull())
|
||||
@ -77,9 +80,9 @@ void PageController::service(HttpRequest& request, HttpResponse& response)
|
||||
{
|
||||
//delete comicFile;
|
||||
if(remote)
|
||||
session.dismissCurrentRemoteComic();
|
||||
ySession->dismissCurrentRemoteComic();
|
||||
else
|
||||
session.dismissCurrentComic();
|
||||
ySession->dismissCurrentComic();
|
||||
}
|
||||
response.setStatus(404,"not found"); //TODO qué mensaje enviar
|
||||
response.write("404 not found",true);
|
||||
|
@ -0,0 +1,34 @@
|
||||
#include "readingcomicscontroller.h"
|
||||
|
||||
#include "db_helper.h"
|
||||
#include "comic_db.h"
|
||||
|
||||
#include "yacreader_server_data_helper.h"
|
||||
|
||||
ReadingComicsController::ReadingComicsController()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
void ReadingComicsController::service(HttpRequest &request, HttpResponse &response)
|
||||
{
|
||||
response.setHeader("Content-Type", "text/plain; charset=utf-8");
|
||||
|
||||
QString path = QUrl::fromPercentEncoding(request.getPath()).toUtf8();
|
||||
QStringList pathElements = path.split('/');
|
||||
int libraryId = pathElements.at(2).toInt();
|
||||
|
||||
serviceContent(libraryId, response);
|
||||
|
||||
response.write("",true);
|
||||
}
|
||||
|
||||
void ReadingComicsController::serviceContent(const int &library, HttpResponse &response)
|
||||
{
|
||||
QList<ComicDB> readingComics = DBHelper::getReading(library);
|
||||
|
||||
for(const ComicDB &comic : readingComics)
|
||||
{
|
||||
response.write(YACReaderServerDataHelper::comicToYSFormat(library, comic).toUtf8());
|
||||
}
|
||||
}
|
@ -0,0 +1,20 @@
|
||||
#ifndef READINGCOMICSCONTROLLER_H
|
||||
#define READINGCOMICSCONTROLLER_H
|
||||
|
||||
#include "httprequest.h"
|
||||
#include "httpresponse.h"
|
||||
#include "httprequesthandler.h"
|
||||
|
||||
class ReadingComicsController : public HttpRequestHandler {
|
||||
Q_OBJECT
|
||||
Q_DISABLE_COPY(ReadingComicsController)
|
||||
public:
|
||||
ReadingComicsController();
|
||||
|
||||
void service(HttpRequest& request, HttpResponse& response);
|
||||
|
||||
private:
|
||||
void serviceContent(const int &library, HttpResponse &response);
|
||||
};
|
||||
|
||||
#endif // READINGCOMICSCONTROLLER_H
|
@ -0,0 +1,24 @@
|
||||
#include "readinglistscontroller.h"
|
||||
|
||||
ReadingListsController::ReadingListsController()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
void ReadingListsController::service(HttpRequest &request, HttpResponse &response)
|
||||
{
|
||||
response.setHeader("Content-Type", "text/plain; charset=utf-8");
|
||||
|
||||
QString path = QUrl::fromPercentEncoding(request.getPath()).toUtf8();
|
||||
QStringList pathElements = path.split('/');
|
||||
int libraryId = pathElements.at(2).toInt();
|
||||
|
||||
serviceContent(libraryId, response);
|
||||
|
||||
response.write("",true);
|
||||
}
|
||||
|
||||
void ReadingListsController::serviceContent(const int library, HttpResponse &response)
|
||||
{
|
||||
|
||||
}
|
20
YACReaderLibrary/server/controllers/readinglistscontroller.h
Normal file
20
YACReaderLibrary/server/controllers/readinglistscontroller.h
Normal file
@ -0,0 +1,20 @@
|
||||
#ifndef READINGLISTSCONTROLLER_H
|
||||
#define READINGLISTSCONTROLLER_H
|
||||
|
||||
#include "httprequest.h"
|
||||
#include "httpresponse.h"
|
||||
#include "httprequesthandler.h"
|
||||
|
||||
class ReadingListsController : public HttpRequestHandler {
|
||||
Q_OBJECT
|
||||
Q_DISABLE_COPY(ReadingListsController)
|
||||
public:
|
||||
ReadingListsController();
|
||||
|
||||
void service(HttpRequest& request, HttpResponse& response);
|
||||
|
||||
private:
|
||||
void serviceContent(const int library, HttpResponse &response);
|
||||
};
|
||||
|
||||
#endif // READINGLISTSCONTROLLER_H
|
@ -55,7 +55,7 @@ void SyncController::service(HttpRequest &request, HttpResponse &response)
|
||||
else
|
||||
{
|
||||
response.setStatus(412,"No comic info received");
|
||||
response.writeText("",true);
|
||||
response.write("",true);
|
||||
return;
|
||||
}
|
||||
|
||||
|
37
YACReaderLibrary/server/controllers/tagcontentcontroller.cpp
Normal file
37
YACReaderLibrary/server/controllers/tagcontentcontroller.cpp
Normal file
@ -0,0 +1,37 @@
|
||||
#include "tagcontentcontroller.h"
|
||||
|
||||
#include "db_helper.h"
|
||||
#include "comic_db.h"
|
||||
|
||||
#include "yacreader_server_data_helper.h"
|
||||
|
||||
#include <QUrl>
|
||||
|
||||
TagContentController::TagContentController()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
void TagContentController::service(HttpRequest &request, HttpResponse &response)
|
||||
{
|
||||
response.setHeader("Content-Type", "text/plain; charset=utf-8");
|
||||
|
||||
QString path = QUrl::fromPercentEncoding(request.getPath()).toUtf8();
|
||||
QStringList pathElements = path.split('/');
|
||||
int libraryId = pathElements.at(2).toInt();
|
||||
qulonglong tagId = pathElements.at(4).toULongLong();
|
||||
|
||||
serviceContent(libraryId, tagId, response);
|
||||
|
||||
response.write("",true);
|
||||
}
|
||||
|
||||
void TagContentController::serviceContent(const int &library, const qulonglong &tagId, HttpResponse &response)
|
||||
{
|
||||
QList<ComicDB> tagComics = DBHelper::getLabelComics(library, tagId);
|
||||
|
||||
for(const ComicDB &comic : tagComics)
|
||||
{
|
||||
response.write(YACReaderServerDataHelper::comicToYSFormat(library, comic).toUtf8());
|
||||
}
|
||||
}
|
22
YACReaderLibrary/server/controllers/tagcontentcontroller.h
Normal file
22
YACReaderLibrary/server/controllers/tagcontentcontroller.h
Normal file
@ -0,0 +1,22 @@
|
||||
#ifndef TAGCONTENTCONTROLLER_H
|
||||
#define TAGCONTENTCONTROLLER_H
|
||||
|
||||
#include "httprequest.h"
|
||||
#include "httpresponse.h"
|
||||
#include "httprequesthandler.h"
|
||||
|
||||
class TagContentController : public HttpRequestHandler {
|
||||
Q_OBJECT
|
||||
Q_DISABLE_COPY(TagContentController);
|
||||
public:
|
||||
/** Constructor */
|
||||
TagContentController();
|
||||
|
||||
/** Generates the response */
|
||||
void service(HttpRequest& request, HttpResponse& response);
|
||||
|
||||
private:
|
||||
void serviceContent(const int &library, const qulonglong &tagId, HttpResponse &response);
|
||||
};
|
||||
|
||||
#endif // TAGCONTENTCONTROLLER_H
|
30
YACReaderLibrary/server/controllers/tagscontroller.cpp
Normal file
30
YACReaderLibrary/server/controllers/tagscontroller.cpp
Normal file
@ -0,0 +1,30 @@
|
||||
#include "tagscontroller.h"
|
||||
|
||||
#include "db_helper.h"
|
||||
#include "yacreader_libraries.h"
|
||||
|
||||
#include "reading_list_item.h"
|
||||
#include "../static.h"
|
||||
#include "yacreader_global.h"
|
||||
|
||||
#include "QsLog.h"
|
||||
|
||||
TagsController::TagsController() {}
|
||||
|
||||
void TagsController::service(HttpRequest& request, HttpResponse& response)
|
||||
{
|
||||
response.setHeader("Content-Type", "text/plain; charset=utf-8");
|
||||
|
||||
QString path = QUrl::fromPercentEncoding(request.getPath()).toUtf8();
|
||||
QStringList pathElements = path.split('/');
|
||||
int libraryId = pathElements.at(2).toInt();
|
||||
|
||||
QList<LabelItem *> tags = DBHelper::getLabelItems(libraryId);
|
||||
|
||||
foreach(LabelItem * tag, tags)
|
||||
{
|
||||
response.write(QString("%1\t%2\t%3\r\n").arg(tag->getId()).arg(tag->name()).arg(labelColorToRGBString(tag->colorid())).toUtf8());
|
||||
}
|
||||
|
||||
response.write("",true);
|
||||
}
|
22
YACReaderLibrary/server/controllers/tagscontroller.h
Normal file
22
YACReaderLibrary/server/controllers/tagscontroller.h
Normal file
@ -0,0 +1,22 @@
|
||||
#ifndef TAGSCONTROLLER_H
|
||||
#define TAGSCONTROLLER_H
|
||||
|
||||
#include "httprequest.h"
|
||||
#include "httpresponse.h"
|
||||
#include "httprequesthandler.h"
|
||||
|
||||
|
||||
|
||||
class TagsController : public HttpRequestHandler {
|
||||
Q_OBJECT
|
||||
Q_DISABLE_COPY(TagsController)
|
||||
public:
|
||||
|
||||
/** Constructor */
|
||||
TagsController();
|
||||
|
||||
/** Generates the response */
|
||||
void service(HttpRequest& request, HttpResponse& response);
|
||||
};
|
||||
|
||||
#endif // TAGSCONTROLLER_H
|
@ -38,7 +38,7 @@ void UpdateComicController::service(HttpRequest &request, HttpResponse &response
|
||||
else
|
||||
{
|
||||
response.setStatus(412,"No comic info received");
|
||||
response.writeText("",true);
|
||||
response.write("",true);
|
||||
return;
|
||||
}
|
||||
|
||||
|
10
YACReaderLibrary/server/controllers/versioncontroller.cpp
Normal file
10
YACReaderLibrary/server/controllers/versioncontroller.cpp
Normal file
@ -0,0 +1,10 @@
|
||||
#include "versioncontroller.h"
|
||||
|
||||
VersionController::VersionController() {}
|
||||
|
||||
void VersionController::service(HttpRequest& request, HttpResponse& response)
|
||||
{
|
||||
Q_UNUSED(request);
|
||||
|
||||
response.write(SERVER_VERSION_NUMBER,true);
|
||||
}
|
21
YACReaderLibrary/server/controllers/versioncontroller.h
Normal file
21
YACReaderLibrary/server/controllers/versioncontroller.h
Normal file
@ -0,0 +1,21 @@
|
||||
#ifndef VERSIONCONTROLLER_H
|
||||
#define VERSIONCONTROLLER_H
|
||||
|
||||
#include "httprequest.h"
|
||||
#include "httpresponse.h"
|
||||
#include "httprequesthandler.h"
|
||||
|
||||
#include <QThread>
|
||||
|
||||
class VersionController : public HttpRequestHandler {
|
||||
Q_OBJECT
|
||||
Q_DISABLE_COPY(VersionController);
|
||||
public:
|
||||
/** Constructor */
|
||||
VersionController();
|
||||
|
||||
/** Generates the response */
|
||||
void service(HttpRequest& request, HttpResponse& response);
|
||||
};
|
||||
|
||||
#endif // VERSIONCONTROLLER_H
|
@ -1,12 +0,0 @@
|
||||
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
|
@ -1,170 +0,0 @@
|
||||
/**
|
||||
@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);
|
||||
}
|
||||
}
|
||||
}
|
@ -1,64 +0,0 @@
|
||||
#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();
|
||||
}
|
@ -1,132 +0,0 @@
|
||||
/**
|
||||
@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;
|
||||
}
|
@ -1,135 +0,0 @@
|
||||
/**
|
||||
@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
|
@ -1,381 +0,0 @@
|
||||
/**
|
||||
@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ó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> >();
|
||||
}
|
||||
|
@ -1,193 +0,0 @@
|
||||
/**
|
||||
@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án siendo descargados en esta sesión
|
||||
QSet<QString> downloadedComics;
|
||||
//cómic actual que está 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
|
@ -1,235 +0,0 @@
|
||||
/**
|
||||
@file
|
||||
@author Stefan Frings
|
||||
*/
|
||||
|
||||
#include "staticfilecontroller.h"
|
||||
#include <QFileInfo>
|
||||
#include <QDir>
|
||||
#include <QDateTime>
|
||||
#include "httpsession.h"
|
||||
#include "static.h"
|
||||
#include <QCoreApplication>
|
||||
|
||||
|
||||
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(QCoreApplication::applicationDirPath());
|
||||
docroot=QFileInfo(QCoreApplication::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ó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ó alternativa traducida ó fileName-locale.extensión si se encontró
|
||||
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ífico para este dispositivo y locales
|
||||
else
|
||||
return getLocalizedFileName(fileName,locales,path); //no hay archivo especí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;
|
||||
}
|
@ -1,92 +0,0 @@
|
||||
/**
|
||||
@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
|
@ -1,5 +0,0 @@
|
||||
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
|
@ -1,7 +0,0 @@
|
||||
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
|
275
YACReaderLibrary/server/lib/httpserver/httpconnectionhandler.cpp
Normal file
275
YACReaderLibrary/server/lib/httpserver/httpconnectionhandler.cpp
Normal file
@ -0,0 +1,275 @@
|
||||
/**
|
||||
@file
|
||||
@author Stefan Frings
|
||||
*/
|
||||
|
||||
#include "httpconnectionhandler.h"
|
||||
#include "httpresponse.h"
|
||||
|
||||
HttpConnectionHandler::HttpConnectionHandler(QSettings* settings, HttpRequestHandler* requestHandler, QSslConfiguration* sslConfiguration)
|
||||
: QThread()
|
||||
{
|
||||
Q_ASSERT(settings!=0);
|
||||
Q_ASSERT(requestHandler!=0);
|
||||
this->settings=settings;
|
||||
this->requestHandler=requestHandler;
|
||||
this->sslConfiguration=sslConfiguration;
|
||||
currentRequest=0;
|
||||
busy=false;
|
||||
|
||||
// Create TCP or SSL socket
|
||||
createSocket();
|
||||
|
||||
// execute signals in my own thread
|
||||
moveToThread(this);
|
||||
socket->moveToThread(this);
|
||||
readTimer.moveToThread(this);
|
||||
|
||||
// Connect signals
|
||||
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()
|
||||
{
|
||||
quit();
|
||||
wait();
|
||||
qDebug("HttpConnectionHandler (%p): destroyed", this);
|
||||
}
|
||||
|
||||
|
||||
void HttpConnectionHandler::createSocket()
|
||||
{
|
||||
// If SSL is supported and configured, then create an instance of QSslSocket
|
||||
#ifndef QT_NO_OPENSSL
|
||||
if (sslConfiguration)
|
||||
{
|
||||
QSslSocket* sslSocket=new QSslSocket();
|
||||
sslSocket->setSslConfiguration(*sslConfiguration);
|
||||
socket=sslSocket;
|
||||
qDebug("HttpConnectionHandler (%p): SSL is enabled", this);
|
||||
return;
|
||||
}
|
||||
#endif
|
||||
// else create an instance of QTcpSocket
|
||||
socket=new QTcpSocket();
|
||||
}
|
||||
|
||||
|
||||
void HttpConnectionHandler::run()
|
||||
{
|
||||
qDebug("HttpConnectionHandler (%p): thread started", this);
|
||||
try
|
||||
{
|
||||
exec();
|
||||
}
|
||||
catch (...)
|
||||
{
|
||||
qCritical("HttpConnectionHandler (%p): an uncatched exception occured in the thread",this);
|
||||
}
|
||||
socket->close();
|
||||
delete socket;
|
||||
readTimer.stop();
|
||||
qDebug("HttpConnectionHandler (%p): thread stopped", this);
|
||||
}
|
||||
|
||||
|
||||
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
|
||||
|
||||
//UGLY workaround - we need to clear writebuffer before reusing this socket
|
||||
//https://bugreports.qt-project.org/browse/QTBUG-28914
|
||||
socket->connectToHost("",0);
|
||||
socket->abort();
|
||||
|
||||
if (!socket->setSocketDescriptor(socketDescriptor))
|
||||
{
|
||||
qCritical("HttpConnectionHandler (%p): cannot initialize socket: %s", this,qPrintable(socket->errorString()));
|
||||
return;
|
||||
}
|
||||
|
||||
#ifndef QT_NO_OPENSSL
|
||||
// Switch on encryption, if SSL is configured
|
||||
if (sslConfiguration)
|
||||
{
|
||||
qDebug("HttpConnectionHandler (%p): Starting encryption", this);
|
||||
((QSslSocket*)socket)->startServerEncryption();
|
||||
}
|
||||
#endif
|
||||
|
||||
// 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->flush();
|
||||
socket->disconnectFromHost();
|
||||
delete currentRequest;
|
||||
currentRequest=0;
|
||||
}
|
||||
|
||||
|
||||
void HttpConnectionHandler::disconnected()
|
||||
{
|
||||
qDebug("HttpConnectionHandler (%p): disconnected", this);
|
||||
socket->close();
|
||||
readTimer.stop();
|
||||
busy = false;
|
||||
}
|
||||
|
||||
void HttpConnectionHandler::read()
|
||||
{
|
||||
// The loop adds support for HTTP pipelinig
|
||||
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->flush();
|
||||
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);
|
||||
|
||||
// Copy the Connection:close header to the response
|
||||
HttpResponse response(socket);
|
||||
bool closeConnection=QString::compare(currentRequest->getHeader("Connection"),"close",Qt::CaseInsensitive)==0;
|
||||
if (closeConnection)
|
||||
{
|
||||
response.setHeader("Connection","close");
|
||||
}
|
||||
|
||||
// In case of HTTP 1.0 protocol add the Connection:close header.
|
||||
// This ensures that the HttpResponse does not activate chunked mode, which is not spported by HTTP 1.0.
|
||||
else
|
||||
{
|
||||
bool http1_0=QString::compare(currentRequest->getVersion(),"HTTP/1.0",Qt::CaseInsensitive)==0;
|
||||
if (http1_0)
|
||||
{
|
||||
closeConnection=true;
|
||||
response.setHeader("Connection","close");
|
||||
}
|
||||
}
|
||||
|
||||
// Call the request mapper
|
||||
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);
|
||||
}
|
||||
|
||||
qDebug("HttpConnectionHandler (%p): finished request",this);
|
||||
|
||||
// Find out whether the connection must be closed
|
||||
if (!closeConnection)
|
||||
{
|
||||
// Maybe the request handler or mapper added a Connection:close header in the meantime
|
||||
bool closeResponse=QString::compare(response.getHeaders().value("Connection"),"close",Qt::CaseInsensitive)==0;
|
||||
if (closeResponse==true)
|
||||
{
|
||||
closeConnection=true;
|
||||
}
|
||||
else
|
||||
{
|
||||
// If we have no Content-Length header and did not use chunked mode, then we have to close the
|
||||
// connection to tell the HTTP client that the end of the response has been reached.
|
||||
bool hasContentLength=response.getHeaders().contains("Content-Length");
|
||||
if (!hasContentLength)
|
||||
{
|
||||
bool hasChunkedMode=QString::compare(response.getHeaders().value("Transfer-Encoding"),"chunked",Qt::CaseInsensitive)==0;
|
||||
if (!hasChunkedMode)
|
||||
{
|
||||
closeConnection=true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Close the connection or prepare for the next request on the same connection.
|
||||
if (closeConnection)
|
||||
{
|
||||
socket->flush();
|
||||
socket->disconnectFromHost();
|
||||
}
|
||||
else
|
||||
{
|
||||
// Start timer for next request
|
||||
int readTimeout=settings->value("readTimeout",10000).toInt();
|
||||
readTimer.start(readTimeout);
|
||||
}
|
||||
delete currentRequest;
|
||||
currentRequest=0;
|
||||
}
|
||||
}
|
||||
}
|
@ -6,13 +6,29 @@
|
||||
#ifndef HTTPCONNECTIONHANDLER_H
|
||||
#define HTTPCONNECTIONHANDLER_H
|
||||
|
||||
#ifndef QT_NO_OPENSSL
|
||||
#include <QSslConfiguration>
|
||||
#endif
|
||||
#include <QTcpSocket>
|
||||
#include <QSettings>
|
||||
#include <QTimer>
|
||||
#include <QThread>
|
||||
#include "httpglobal.h"
|
||||
#include "httprequest.h"
|
||||
#include "httprequesthandler.h"
|
||||
|
||||
/** Alias type definition, for compatibility to different Qt versions */
|
||||
#if QT_VERSION >= 0x050000
|
||||
typedef qintptr tSocketDescriptor;
|
||||
#else
|
||||
typedef int tSocketDescriptor;
|
||||
#endif
|
||||
|
||||
/** Alias for QSslConfiguration if OpenSSL is not supported */
|
||||
#ifdef QT_NO_OPENSSL
|
||||
#define QSslConfiguration QObject
|
||||
#endif
|
||||
|
||||
/**
|
||||
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,
|
||||
@ -26,26 +42,21 @@
|
||||
</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
|
||||
@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 {
|
||||
class DECLSPEC 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
|
||||
@param requestHandler Handler that will process each incoming HTTP request
|
||||
@param sslConfiguration SSL (HTTPS) will be used if not NULL
|
||||
*/
|
||||
HttpConnectionHandler(QSettings* settings, HttpRequestHandler* requestHandler);
|
||||
HttpConnectionHandler(QSettings* settings, HttpRequestHandler* requestHandler, QSslConfiguration* sslConfiguration=NULL);
|
||||
|
||||
/** Destructor */
|
||||
virtual ~HttpConnectionHandler();
|
||||
@ -61,8 +72,8 @@ private:
|
||||
/** Configuration settings */
|
||||
QSettings* settings;
|
||||
|
||||
/** TCP socket of the current connection */
|
||||
QTcpSocket socket;
|
||||
/** TCP socket of the current connection */
|
||||
QTcpSocket* socket;
|
||||
|
||||
/** Time for read timeout detection */
|
||||
QTimer readTimer;
|
||||
@ -76,9 +87,15 @@ private:
|
||||
/** This shows the busy-state from a very early time */
|
||||
bool busy;
|
||||
|
||||
/** Executes the htreads own event loop */
|
||||
/** Configuration for SSL */
|
||||
QSslConfiguration* sslConfiguration;
|
||||
|
||||
/** Executes the threads own event loop */
|
||||
void run();
|
||||
|
||||
/** Create SSL or TCP socket */
|
||||
void createSocket();
|
||||
|
||||
public slots:
|
||||
|
||||
/**
|
@ -0,0 +1,146 @@
|
||||
#ifndef QT_NO_OPENSSL
|
||||
#include <QSslSocket>
|
||||
#include <QSslKey>
|
||||
#include <QSslCertificate>
|
||||
#include <QSslConfiguration>
|
||||
#endif
|
||||
#include <QDir>
|
||||
#include "httpconnectionhandlerpool.h"
|
||||
|
||||
HttpConnectionHandlerPool::HttpConnectionHandlerPool(QSettings* settings, HttpRequestHandler* requestHandler)
|
||||
: QObject()
|
||||
{
|
||||
Q_ASSERT(settings!=0);
|
||||
this->settings=settings;
|
||||
this->requestHandler=requestHandler;
|
||||
this->sslConfiguration=NULL;
|
||||
loadSslConfig();
|
||||
cleanupTimer.start(settings->value("cleanupInterval",1000).toInt());
|
||||
connect(&cleanupTimer, SIGNAL(timeout()), SLOT(cleanup()));
|
||||
}
|
||||
|
||||
|
||||
HttpConnectionHandlerPool::~HttpConnectionHandlerPool()
|
||||
{
|
||||
// delete all connection handlers and wait until their threads are closed
|
||||
foreach(HttpConnectionHandler* handler, pool)
|
||||
{
|
||||
delete handler;
|
||||
}
|
||||
delete sslConfiguration;
|
||||
qDebug("HttpConnectionHandlerPool (%p): destroyed", this);
|
||||
}
|
||||
|
||||
|
||||
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",100).toInt();
|
||||
if (pool.count()<maxConnectionHandlers)
|
||||
{
|
||||
freeHandler=new HttpConnectionHandler(settings,requestHandler,sslConfiguration);
|
||||
freeHandler->setBusy();
|
||||
pool.append(freeHandler);
|
||||
}
|
||||
}
|
||||
mutex.unlock();
|
||||
return freeHandler;
|
||||
}
|
||||
|
||||
|
||||
void HttpConnectionHandlerPool::cleanup()
|
||||
{
|
||||
int maxIdleHandlers=settings->value("minThreads",1).toInt();
|
||||
int idleCounter=0;
|
||||
mutex.lock();
|
||||
foreach(HttpConnectionHandler* handler, pool)
|
||||
{
|
||||
if (!handler->isBusy())
|
||||
{
|
||||
if (++idleCounter > maxIdleHandlers)
|
||||
{
|
||||
delete handler;
|
||||
pool.removeOne(handler);
|
||||
qDebug("HttpConnectionHandlerPool: Removed connection handler (%p), pool size is now %i",handler,pool.size());
|
||||
break; // remove only one handler in each interval
|
||||
}
|
||||
}
|
||||
}
|
||||
mutex.unlock();
|
||||
}
|
||||
|
||||
|
||||
void HttpConnectionHandlerPool::loadSslConfig()
|
||||
{
|
||||
// If certificate and key files are configured, then load them
|
||||
QString sslKeyFileName=settings->value("sslKeyFile","").toString();
|
||||
QString sslCertFileName=settings->value("sslCertFile","").toString();
|
||||
if (!sslKeyFileName.isEmpty() && !sslCertFileName.isEmpty())
|
||||
{
|
||||
#ifdef QT_NO_OPENSSL
|
||||
qWarning("HttpConnectionHandlerPool: SSL is not supported");
|
||||
#else
|
||||
// Convert relative fileNames to absolute, based on the directory of the config file.
|
||||
QFileInfo configFile(settings->fileName());
|
||||
#ifdef Q_OS_WIN32
|
||||
if (QDir::isRelativePath(sslKeyFileName) && settings->format()!=QSettings::NativeFormat)
|
||||
#else
|
||||
if (QDir::isRelativePath(sslKeyFileName))
|
||||
#endif
|
||||
{
|
||||
sslKeyFileName=QFileInfo(configFile.absolutePath(),sslKeyFileName).absoluteFilePath();
|
||||
}
|
||||
#ifdef Q_OS_WIN32
|
||||
if (QDir::isRelativePath(sslCertFileName) && settings->format()!=QSettings::NativeFormat)
|
||||
#else
|
||||
if (QDir::isRelativePath(sslCertFileName))
|
||||
#endif
|
||||
{
|
||||
sslCertFileName=QFileInfo(configFile.absolutePath(),sslCertFileName).absoluteFilePath();
|
||||
}
|
||||
|
||||
// Load the SSL certificate
|
||||
QFile certFile(sslCertFileName);
|
||||
if (!certFile.open(QIODevice::ReadOnly))
|
||||
{
|
||||
qCritical("HttpConnectionHandlerPool: cannot open sslCertFile %s", qPrintable(sslCertFileName));
|
||||
return;
|
||||
}
|
||||
QSslCertificate certificate(&certFile, QSsl::Pem);
|
||||
certFile.close();
|
||||
|
||||
// Load the key file
|
||||
QFile keyFile(sslKeyFileName);
|
||||
if (!keyFile.open(QIODevice::ReadOnly))
|
||||
{
|
||||
qCritical("HttpConnectionHandlerPool: cannot open sslKeyFile %s", qPrintable(sslKeyFileName));
|
||||
return;
|
||||
}
|
||||
QSslKey sslKey(&keyFile, QSsl::Rsa, QSsl::Pem);
|
||||
keyFile.close();
|
||||
|
||||
// Create the SSL configuration
|
||||
sslConfiguration=new QSslConfiguration();
|
||||
sslConfiguration->setLocalCertificate(certificate);
|
||||
sslConfiguration->setPrivateKey(sslKey);
|
||||
sslConfiguration->setPeerVerifyMode(QSslSocket::VerifyNone);
|
||||
sslConfiguration->setProtocol(QSsl::TlsV1SslV3);
|
||||
|
||||
qDebug("HttpConnectionHandlerPool: SSL settings loaded");
|
||||
#endif
|
||||
}
|
||||
}
|
@ -5,29 +5,45 @@
|
||||
#include <QTimer>
|
||||
#include <QObject>
|
||||
#include <QMutex>
|
||||
#include "httpglobal.h"
|
||||
#include "httpconnectionhandler.h"
|
||||
|
||||
/**
|
||||
Pool of http connection handlers. Connection handlers are created on demand and idle handlers are
|
||||
cleaned up in regular time intervals.
|
||||
Pool of http connection handlers. The size of the pool grows and
|
||||
shrinks on demand.
|
||||
<p>
|
||||
Example for the required configuration settings:
|
||||
<code><pre>
|
||||
minThreads=1
|
||||
minThreads=4
|
||||
maxThreads=100
|
||||
cleanupInterval=1000
|
||||
cleanupInterval=60000
|
||||
readTimeout=60000
|
||||
;sslKeyFile=ssl/my.key
|
||||
;sslCertFile=ssl/my.cert
|
||||
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
|
||||
After server start, the size of the thread pool is always 0. Threads
|
||||
are started on demand when requests come in. The cleanup timer reduces
|
||||
the number of idle threads slowly by closing one thread in each interval.
|
||||
But the configured minimum number of threads are kept running.
|
||||
<p>
|
||||
For SSL support, you need an OpenSSL certificate file and a key file.
|
||||
Both can be created with the command
|
||||
<code><pre>
|
||||
openssl req -x509 -nodes -days 365 -newkey rsa:2048 -keyout my.key -out my.cert
|
||||
</pre></code>
|
||||
<p>
|
||||
Visit http://slproweb.com/products/Win32OpenSSL.html to download the Light version of OpenSSL for Windows.
|
||||
<p>
|
||||
Please note that a listener with SSL settings can only handle HTTPS protocol. To
|
||||
support both HTTP and HTTPS simultaneously, you need to start two listeners on different ports -
|
||||
one with SLL and one without SSL.
|
||||
@see HttpConnectionHandler for description of the readTimeout
|
||||
@see HttpRequest for description of config settings maxRequestSize and maxMultiPartSize
|
||||
*/
|
||||
|
||||
class HttpConnectionHandlerPool : public QObject {
|
||||
class DECLSPEC HttpConnectionHandlerPool : public QObject {
|
||||
Q_OBJECT
|
||||
Q_DISABLE_COPY(HttpConnectionHandlerPool)
|
||||
public:
|
||||
@ -63,6 +79,12 @@ private:
|
||||
/** Used to synchronize threads */
|
||||
QMutex mutex;
|
||||
|
||||
/** The SSL configuration (certificate, key and other settings) */
|
||||
QSslConfiguration* sslConfiguration;
|
||||
|
||||
/** Load SSL configuration */
|
||||
void loadSslConfig();
|
||||
|
||||
private slots:
|
||||
|
||||
/** Received from the clean-up timer. */
|
@ -5,13 +5,15 @@
|
||||
|
||||
#include "httpcookie.h"
|
||||
|
||||
HttpCookie::HttpCookie() {
|
||||
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) {
|
||||
HttpCookie::HttpCookie(const QByteArray name, const QByteArray value, const int maxAge, const QByteArray path, const QByteArray comment, const QByteArray domain, const bool secure, const bool httpOnly)
|
||||
{
|
||||
this->name=name;
|
||||
this->value=value;
|
||||
this->maxAge=maxAge;
|
||||
@ -19,171 +21,230 @@ HttpCookie::HttpCookie(const QByteArray name, const QByteArray value, const int
|
||||
this->comment=comment;
|
||||
this->domain=domain;
|
||||
this->secure=secure;
|
||||
this->httpOnly=httpOnly;
|
||||
this->version=1;
|
||||
}
|
||||
|
||||
HttpCookie::HttpCookie(const QByteArray source) {
|
||||
HttpCookie::HttpCookie(const QByteArray source)
|
||||
{
|
||||
version=1;
|
||||
maxAge=0;
|
||||
secure=false;
|
||||
QList<QByteArray> list=splitCSV(source);
|
||||
foreach(QByteArray part, list) {
|
||||
foreach(QByteArray part, list)
|
||||
{
|
||||
|
||||
// Split the part into name and value
|
||||
QByteArray name;
|
||||
QByteArray value;
|
||||
int posi=part.indexOf('=');
|
||||
if (posi) {
|
||||
if (posi)
|
||||
{
|
||||
name=part.left(posi).trimmed();
|
||||
value=part.mid(posi+1).trimmed();
|
||||
}
|
||||
else {
|
||||
else
|
||||
{
|
||||
name=part.trimmed();
|
||||
value="";
|
||||
}
|
||||
|
||||
// Set fields
|
||||
if (name=="Comment") {
|
||||
if (name=="Comment")
|
||||
{
|
||||
comment=value;
|
||||
}
|
||||
else if (name=="Domain") {
|
||||
else if (name=="Domain")
|
||||
{
|
||||
domain=value;
|
||||
}
|
||||
else if (name=="Max-Age") {
|
||||
else if (name=="Max-Age")
|
||||
{
|
||||
maxAge=value.toInt();
|
||||
}
|
||||
else if (name=="Path") {
|
||||
else if (name=="Path")
|
||||
{
|
||||
path=value;
|
||||
}
|
||||
else if (name=="Secure") {
|
||||
else if (name=="Secure")
|
||||
{
|
||||
secure=true;
|
||||
}
|
||||
else if (name=="Version") {
|
||||
else if (name=="HttpOnly")
|
||||
{
|
||||
httpOnly=true;
|
||||
}
|
||||
else if (name=="Version")
|
||||
{
|
||||
version=value.toInt();
|
||||
}
|
||||
else {
|
||||
if (this->name.isEmpty()) {
|
||||
if (this->name.isEmpty())
|
||||
{
|
||||
this->name=name;
|
||||
this->value=value;
|
||||
}
|
||||
else {
|
||||
else
|
||||
{
|
||||
qWarning("HttpCookie: Ignoring unknown %s=%s",name.data(),value.data());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
QByteArray HttpCookie::toByteArray() const {
|
||||
QByteArray HttpCookie::toByteArray() const
|
||||
{
|
||||
QByteArray buffer(name);
|
||||
buffer.append('=');
|
||||
buffer.append(value);
|
||||
if (!comment.isEmpty()) {
|
||||
if (!comment.isEmpty())
|
||||
{
|
||||
buffer.append("; Comment=");
|
||||
buffer.append(comment);
|
||||
}
|
||||
if (!domain.isEmpty()) {
|
||||
if (!domain.isEmpty())
|
||||
{
|
||||
buffer.append("; Domain=");
|
||||
buffer.append(domain);
|
||||
}
|
||||
if (maxAge!=0) {
|
||||
if (maxAge!=0)
|
||||
{
|
||||
buffer.append("; Max-Age=");
|
||||
buffer.append(QByteArray::number(maxAge));
|
||||
}
|
||||
if (!path.isEmpty()) {
|
||||
if (!path.isEmpty())
|
||||
{
|
||||
buffer.append("; Path=");
|
||||
buffer.append(path);
|
||||
}
|
||||
if (secure) {
|
||||
buffer.append("; Secure");
|
||||
}
|
||||
if (httpOnly) {
|
||||
buffer.append("; HttpOnly");
|
||||
}
|
||||
buffer.append("; Version=");
|
||||
buffer.append(QByteArray::number(version));
|
||||
return buffer;
|
||||
}
|
||||
|
||||
void HttpCookie::setName(const QByteArray name){
|
||||
void HttpCookie::setName(const QByteArray name)
|
||||
{
|
||||
this->name=name;
|
||||
}
|
||||
|
||||
void HttpCookie::setValue(const QByteArray value){
|
||||
void HttpCookie::setValue(const QByteArray value)
|
||||
{
|
||||
this->value=value;
|
||||
}
|
||||
|
||||
void HttpCookie::setComment(const QByteArray comment){
|
||||
void HttpCookie::setComment(const QByteArray comment)
|
||||
{
|
||||
this->comment=comment;
|
||||
}
|
||||
|
||||
void HttpCookie::setDomain(const QByteArray domain){
|
||||
void HttpCookie::setDomain(const QByteArray domain)
|
||||
{
|
||||
this->domain=domain;
|
||||
}
|
||||
|
||||
void HttpCookie::setMaxAge(const int maxAge){
|
||||
void HttpCookie::setMaxAge(const int maxAge)
|
||||
{
|
||||
this->maxAge=maxAge;
|
||||
}
|
||||
|
||||
void HttpCookie::setPath(const QByteArray path){
|
||||
void HttpCookie::setPath(const QByteArray path)
|
||||
{
|
||||
this->path=path;
|
||||
}
|
||||
|
||||
void HttpCookie::setSecure(const bool secure){
|
||||
void HttpCookie::setSecure(const bool secure)
|
||||
{
|
||||
this->secure=secure;
|
||||
}
|
||||
|
||||
QByteArray HttpCookie::getName() const {
|
||||
void HttpCookie::setHttpOnly(const bool httpOnly)
|
||||
{
|
||||
this->httpOnly=httpOnly;
|
||||
}
|
||||
|
||||
QByteArray HttpCookie::getName() const
|
||||
{
|
||||
return name;
|
||||
}
|
||||
|
||||
QByteArray HttpCookie::getValue() const {
|
||||
QByteArray HttpCookie::getValue() const
|
||||
{
|
||||
return value;
|
||||
}
|
||||
|
||||
QByteArray HttpCookie::getComment() const {
|
||||
QByteArray HttpCookie::getComment() const
|
||||
{
|
||||
return comment;
|
||||
}
|
||||
|
||||
QByteArray HttpCookie::getDomain() const {
|
||||
QByteArray HttpCookie::getDomain() const
|
||||
{
|
||||
return domain;
|
||||
}
|
||||
|
||||
int HttpCookie::getMaxAge() const {
|
||||
int HttpCookie::getMaxAge() const
|
||||
{
|
||||
return maxAge;
|
||||
}
|
||||
|
||||
QByteArray HttpCookie::getPath() const {
|
||||
QByteArray HttpCookie::getPath() const
|
||||
{
|
||||
return path;
|
||||
}
|
||||
|
||||
bool HttpCookie::getSecure() const {
|
||||
bool HttpCookie::getSecure() const
|
||||
{
|
||||
return secure;
|
||||
}
|
||||
|
||||
int HttpCookie::getVersion() const {
|
||||
bool HttpCookie::getHttpOnly() const
|
||||
{
|
||||
return httpOnly;
|
||||
}
|
||||
|
||||
int HttpCookie::getVersion() const
|
||||
{
|
||||
return version;
|
||||
}
|
||||
|
||||
QList<QByteArray> HttpCookie::splitCSV(const QByteArray source) {
|
||||
QList<QByteArray> HttpCookie::splitCSV(const QByteArray source)
|
||||
{
|
||||
bool inString=false;
|
||||
QList<QByteArray> list;
|
||||
QByteArray buffer;
|
||||
for (int i=0; i<source.size(); ++i) {
|
||||
for (int i=0; i<source.size(); ++i)
|
||||
{
|
||||
char c=source.at(i);
|
||||
if (inString==false) {
|
||||
if (c=='\"') {
|
||||
if (inString==false)
|
||||
{
|
||||
if (c=='\"')
|
||||
{
|
||||
inString=true;
|
||||
}
|
||||
else if (c==';') {
|
||||
else if (c==';')
|
||||
{
|
||||
QByteArray trimmed=buffer.trimmed();
|
||||
if (!trimmed.isEmpty()) {
|
||||
if (!trimmed.isEmpty())
|
||||
{
|
||||
list.append(trimmed);
|
||||
}
|
||||
buffer.clear();
|
||||
}
|
||||
else {
|
||||
else
|
||||
{
|
||||
buffer.append(c);
|
||||
}
|
||||
}
|
||||
else {
|
||||
if (c=='\"') {
|
||||
else
|
||||
{
|
||||
if (c=='\"')
|
||||
{
|
||||
inString=false;
|
||||
}
|
||||
else {
|
||||
@ -192,7 +253,8 @@ QList<QByteArray> HttpCookie::splitCSV(const QByteArray source) {
|
||||
}
|
||||
}
|
||||
QByteArray trimmed=buffer.trimmed();
|
||||
if (!trimmed.isEmpty()) {
|
||||
if (!trimmed.isEmpty())
|
||||
{
|
||||
list.append(trimmed);
|
||||
}
|
||||
return list;
|
@ -8,6 +8,7 @@
|
||||
|
||||
#include <QList>
|
||||
#include <QByteArray>
|
||||
#include "httpglobal.h"
|
||||
|
||||
/**
|
||||
HTTP cookie as defined in RFC 2109. This class can also parse
|
||||
@ -15,7 +16,7 @@
|
||||
2109.
|
||||
*/
|
||||
|
||||
class HttpCookie
|
||||
class DECLSPEC HttpCookie
|
||||
{
|
||||
public:
|
||||
|
||||
@ -30,9 +31,10 @@ public:
|
||||
@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
|
||||
@param secure If true, the cookie will be sent by the browser to the server only on secure connections
|
||||
@param httpOnly If true, the browser does not allow client-side scripts to access the cookie
|
||||
*/
|
||||
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);
|
||||
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, const bool httpOnly=false);
|
||||
|
||||
/**
|
||||
Create a cookie from a string.
|
||||
@ -40,7 +42,7 @@ public:
|
||||
*/
|
||||
HttpCookie(const QByteArray source);
|
||||
|
||||
/** Convert this cookie to a string that may be used in a Set-Cookie2 header. */
|
||||
/** Convert this cookie to a string that may be used in a Set-Cookie header. */
|
||||
QByteArray toByteArray() const ;
|
||||
|
||||
/**
|
||||
@ -67,9 +69,12 @@ public:
|
||||
/** 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 */
|
||||
/** Set secure mode, so that the cookie will be sent by the browser to the server only on secure connections */
|
||||
void setSecure(const bool secure);
|
||||
|
||||
/** Set secure mode, so that he browser does not allow client-side scripts to access the cookie */
|
||||
void setHttpOnly(const bool httpOnly);
|
||||
|
||||
/** Get the name of this cookie */
|
||||
QByteArray getName() const;
|
||||
|
||||
@ -91,6 +96,9 @@ public:
|
||||
/** Get the secure flag of this cookie */
|
||||
bool getSecure() const;
|
||||
|
||||
/** Get the httpOnly of this cookie */
|
||||
bool getHttpOnly() const;
|
||||
|
||||
/** Returns always 1 */
|
||||
int getVersion() const;
|
||||
|
||||
@ -103,6 +111,7 @@ private:
|
||||
int maxAge;
|
||||
QByteArray path;
|
||||
bool secure;
|
||||
bool httpOnly;
|
||||
int version;
|
||||
|
||||
};
|
7
YACReaderLibrary/server/lib/httpserver/httpglobal.cpp
Normal file
7
YACReaderLibrary/server/lib/httpserver/httpglobal.cpp
Normal file
@ -0,0 +1,7 @@
|
||||
#include "httpglobal.h"
|
||||
|
||||
const char* getQtWebAppLibVersion()
|
||||
{
|
||||
return "1.6.5";
|
||||
}
|
||||
|
27
YACReaderLibrary/server/lib/httpserver/httpglobal.h
Normal file
27
YACReaderLibrary/server/lib/httpserver/httpglobal.h
Normal file
@ -0,0 +1,27 @@
|
||||
/**
|
||||
@file
|
||||
@author Stefan Frings
|
||||
*/
|
||||
|
||||
#ifndef HTTPGLOBAL_H
|
||||
#define HTTPGLOBAL_H
|
||||
|
||||
#include <QtGlobal>
|
||||
|
||||
// This is specific to Windows dll's
|
||||
#if defined(Q_OS_WIN)
|
||||
#if defined(QTWEBAPPLIB_EXPORT)
|
||||
#define DECLSPEC Q_DECL_EXPORT
|
||||
#elif defined(QTWEBAPPLIB_IMPORT)
|
||||
#define DECLSPEC Q_DECL_IMPORT
|
||||
#endif
|
||||
#endif
|
||||
#if !defined(DECLSPEC)
|
||||
#define DECLSPEC
|
||||
#endif
|
||||
|
||||
/** Get the library version number */
|
||||
DECLSPEC const char* getQtWebAppLibVersion();
|
||||
|
||||
#endif // HTTPGLOBAL_H
|
||||
|
@ -12,53 +12,86 @@ HttpListener::HttpListener(QSettings* settings, HttpRequestHandler* requestHandl
|
||||
: QTcpServer(parent)
|
||||
{
|
||||
Q_ASSERT(settings!=0);
|
||||
Q_ASSERT(requestHandler!=0);
|
||||
pool=NULL;
|
||||
this->settings=settings;
|
||||
this->requestHandler=requestHandler;
|
||||
// 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++;
|
||||
listen();
|
||||
}
|
||||
|
||||
|
||||
HttpListener::~HttpListener()
|
||||
{
|
||||
close();
|
||||
qDebug("HttpListener: destroyed");
|
||||
}
|
||||
|
||||
|
||||
void HttpListener::listen()
|
||||
{
|
||||
if (!pool)
|
||||
{
|
||||
pool=new HttpConnectionHandlerPool(settings,requestHandler);
|
||||
}
|
||||
QString host = settings->value("host").toString();
|
||||
int port=settings->value("port").toInt();
|
||||
QTcpServer::listen(host.isEmpty() ? QHostAddress::Any : QHostAddress(host), port);
|
||||
|
||||
//YACReader---------------------------------------------
|
||||
//try to listen even if the default port is no available
|
||||
int i = 0;
|
||||
while (!isListening() && i < 1000) {
|
||||
QTcpServer::listen(QHostAddress::Any, (rand() % 45535)+20000);
|
||||
i++;
|
||||
}
|
||||
//------------------------------------------------------
|
||||
|
||||
if (!isListening())
|
||||
{
|
||||
qDebug("HttpListener: Cannot bind on port %i: %s",port,qPrintable(errorString()));
|
||||
}
|
||||
if(!isListening())
|
||||
{
|
||||
qCritical("HttpListener: Cannot bind on port %i: %s",port,qPrintable(errorString()));
|
||||
}
|
||||
else {
|
||||
qDebug("HttpListener: Listening on port %i",port);
|
||||
}
|
||||
}
|
||||
|
||||
HttpListener::~HttpListener() {
|
||||
close();
|
||||
|
||||
void HttpListener::close() {
|
||||
QTcpServer::close();
|
||||
qDebug("HttpListener: closed");
|
||||
delete pool;
|
||||
qDebug("HttpListener: destroyed");
|
||||
if (pool) {
|
||||
delete pool;
|
||||
pool=NULL;
|
||||
}
|
||||
}
|
||||
|
||||
void HttpListener::incomingConnection(tSocketDescriptor socketDescriptor) {
|
||||
#ifdef SUPERVERBOSE
|
||||
qDebug("HttpListener: New connection");
|
||||
#endif
|
||||
HttpConnectionHandler* freeHandler=pool->getConnectionHandler();
|
||||
|
||||
HttpConnectionHandler* freeHandler=NULL;
|
||||
if (pool)
|
||||
{
|
||||
freeHandler=pool->getConnectionHandler();
|
||||
}
|
||||
|
||||
// Let the handler process the new connection.
|
||||
if (freeHandler) {
|
||||
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.
|
||||
// thread and cannot open the socket when directly 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 {
|
||||
else
|
||||
{
|
||||
// Reject the connection
|
||||
qDebug("HttpListener: Too many connections");
|
||||
qDebug("HttpListener: Too many incoming connections");
|
||||
QTcpSocket* socket=new QTcpSocket(this);
|
||||
socket->setSocketDescriptor(socketDescriptor);
|
||||
connect(socket, SIGNAL(disconnected()), socket, SLOT(deleteLater()));
|
@ -3,12 +3,13 @@
|
||||
@author Stefan Frings
|
||||
*/
|
||||
|
||||
#ifndef LISTENER_H
|
||||
#define LISTENER_H
|
||||
#ifndef HTTPLISTENER_H
|
||||
#define HTTPLISTENER_H
|
||||
|
||||
#include <QTcpServer>
|
||||
#include <QSettings>
|
||||
#include <QBasicTimer>
|
||||
#include "httpglobal.h"
|
||||
#include "httpconnectionhandler.h"
|
||||
#include "httpconnectionhandlerpool.h"
|
||||
#include "httprequesthandler.h"
|
||||
@ -19,36 +20,54 @@
|
||||
<p>
|
||||
Example for the required settings in the config file:
|
||||
<code><pre>
|
||||
;host=192.168.0.100
|
||||
port=8080
|
||||
minThreads=1
|
||||
maxThreads=10
|
||||
cleanupInterval=1000
|
||||
readTimeout=60000
|
||||
;sslKeyFile=ssl/my.key
|
||||
;sslCertFile=ssl/my.cert
|
||||
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
|
||||
The optional host parameter binds the listener to one network interface.
|
||||
The listener handles all network interfaces if no host is configured.
|
||||
The port number specifies the incoming TCP port that this listener listens to.
|
||||
@see HttpConnectionHandlerPool for description of config settings minThreads, maxThreads, cleanupInterval and ssl settings
|
||||
@see HttpConnectionHandler for description of the readTimeout
|
||||
@see HttpRequest for description of config settings maxRequestSize and maxMultiPartSize
|
||||
*/
|
||||
|
||||
class HttpListener : public QTcpServer {
|
||||
class DECLSPEC HttpListener : public QTcpServer {
|
||||
Q_OBJECT
|
||||
Q_DISABLE_COPY(HttpListener)
|
||||
public:
|
||||
|
||||
/**
|
||||
Constructor.
|
||||
Creates a connection pool and starts listening on the configured host and port.
|
||||
@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.
|
||||
@warning Ensure to close or delete the listener before deleting the request handler.
|
||||
*/
|
||||
HttpListener(QSettings* settings, HttpRequestHandler* requestHandler, QObject* parent = 0);
|
||||
HttpListener(QSettings* settings, HttpRequestHandler* requestHandler, QObject* parent = NULL);
|
||||
|
||||
/** Destructor */
|
||||
virtual ~HttpListener();
|
||||
|
||||
/**
|
||||
Restart listeing after close().
|
||||
*/
|
||||
void listen();
|
||||
|
||||
/**
|
||||
Closes the listener, waits until all pending requests are processed,
|
||||
then closes the connection pool.
|
||||
*/
|
||||
void close();
|
||||
|
||||
protected:
|
||||
|
||||
/** Serves new incoming connection requests */
|
||||
@ -59,13 +78,16 @@ private:
|
||||
/** Configuration settings for the HTTP server */
|
||||
QSettings* settings;
|
||||
|
||||
/** Point to the reuqest handler which processes all HTTP requests */
|
||||
HttpRequestHandler* requestHandler;
|
||||
|
||||
/** Pool of connection handlers */
|
||||
HttpConnectionHandlerPool* pool;
|
||||
|
||||
signals:
|
||||
|
||||
/**
|
||||
Emitted when the connection handler shall process a new incoming onnection.
|
||||
Sent to the connection handler to process a new incoming connection.
|
||||
@param socketDescriptor references the accepted connection.
|
||||
*/
|
||||
|
||||
@ -73,4 +95,4 @@ signals:
|
||||
|
||||
};
|
||||
|
||||
#endif // LISTENER_H
|
||||
#endif // HTTPLISTENER_H
|
@ -8,144 +8,189 @@
|
||||
#include <QDir>
|
||||
#include "httpcookie.h"
|
||||
|
||||
HttpRequest::HttpRequest(QSettings* settings) {
|
||||
HttpRequest::HttpRequest(QSettings* settings)
|
||||
{
|
||||
status=waitForRequest;
|
||||
currentSize=0;
|
||||
expectedBodySize=0;
|
||||
maxSize=settings->value("maxRequestSize","32000000").toInt();
|
||||
maxMultiPartSize=settings->value("maxMultiPartSize","32000000").toInt();
|
||||
maxSize=settings->value("maxRequestSize","16000").toInt();
|
||||
maxMultiPartSize=settings->value("maxMultiPartSize","1000000").toInt();
|
||||
}
|
||||
|
||||
void HttpRequest::readRequest(QTcpSocket& socket) {
|
||||
#ifdef SUPERVERBOSE
|
||||
qDebug("HttpRequest: read request");
|
||||
#endif
|
||||
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()) {
|
||||
lineBuffer.append(socket->readLine(toRead));
|
||||
currentSize+=lineBuffer.size();
|
||||
if (!lineBuffer.contains('\r') && !lineBuffer.contains('\n'))
|
||||
{
|
||||
#ifdef SUPERVERBOSE
|
||||
qDebug("HttpRequest: collecting more parts until line break");
|
||||
#endif
|
||||
return;
|
||||
}
|
||||
QByteArray newData=lineBuffer.trimmed();
|
||||
lineBuffer.clear();
|
||||
if (!newData.isEmpty())
|
||||
{
|
||||
QList<QByteArray> list=newData.split(' ');
|
||||
if (list.count()!=3 || !list.at(2).contains("HTTP")) {
|
||||
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);
|
||||
method=list.at(0).trimmed();
|
||||
path=list.at(1);
|
||||
version=list.at(2);
|
||||
peerAddress = socket->peerAddress();
|
||||
status=waitForHeader;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void HttpRequest::readHeader(QTcpSocket& socket) {
|
||||
#ifdef SUPERVERBOSE
|
||||
qDebug("HttpRequest: read header");
|
||||
#endif
|
||||
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();
|
||||
lineBuffer.append(socket->readLine(toRead));
|
||||
currentSize+=lineBuffer.size();
|
||||
if (!lineBuffer.contains('\r') && !lineBuffer.contains('\n'))
|
||||
{
|
||||
#ifdef SUPERVERBOSE
|
||||
qDebug("HttpRequest: collecting more parts until line break");
|
||||
#endif
|
||||
return;
|
||||
}
|
||||
QByteArray newData=lineBuffer.trimmed();
|
||||
lineBuffer.clear();
|
||||
int colon=newData.indexOf(':');
|
||||
if (colon>0) {
|
||||
if (colon>0)
|
||||
{
|
||||
// Received a line with a colon - a header
|
||||
currentHeader=newData.left(colon);
|
||||
currentHeader=newData.left(colon).toLower();
|
||||
QByteArray value=newData.mid(colon+1).trimmed();
|
||||
headers.insert(currentHeader,value);
|
||||
#ifdef SUPERVERBOSE
|
||||
qDebug("HttpRequest: received header %s: %s",currentHeader.data(),value.data());
|
||||
#endif
|
||||
#ifdef SUPERVERBOSE
|
||||
qDebug("HttpRequest: received header %s: %s",currentHeader.data(),value.data());
|
||||
#endif
|
||||
}
|
||||
else if (!newData.isEmpty()) {
|
||||
else if (!newData.isEmpty())
|
||||
{
|
||||
// received another line - belongs to the previous header
|
||||
#ifdef SUPERVERBOSE
|
||||
qDebug("HttpRequest: read additional line of header");
|
||||
#endif
|
||||
#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 {
|
||||
else
|
||||
{
|
||||
// received an empty line - end of headers reached
|
||||
#ifdef SUPERVERBOSE
|
||||
qDebug("HttpRequest: headers completed");
|
||||
#endif
|
||||
#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")) {
|
||||
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);
|
||||
if (boundary.startsWith('"') && boundary.endsWith('"'))
|
||||
{
|
||||
boundary = boundary.mid(1,boundary.length()-2);
|
||||
}
|
||||
}
|
||||
}
|
||||
QByteArray contentLength=getHeader("Content-Length");
|
||||
if (!contentLength.isEmpty()) {
|
||||
QByteArray contentLength=headers.value("content-length");
|
||||
if (!contentLength.isEmpty())
|
||||
{
|
||||
expectedBodySize=contentLength.toInt();
|
||||
}
|
||||
if (expectedBodySize==0) {
|
||||
#ifdef SUPERVERBOSE
|
||||
qDebug("HttpRequest: expect no body");
|
||||
#endif
|
||||
if (expectedBodySize==0)
|
||||
{
|
||||
#ifdef SUPERVERBOSE
|
||||
qDebug("HttpRequest: expect no body");
|
||||
#endif
|
||||
status=complete;
|
||||
}
|
||||
else if (boundary.isEmpty() && expectedBodySize+currentSize>maxSize) {
|
||||
else if (boundary.isEmpty() && expectedBodySize+currentSize>maxSize)
|
||||
{
|
||||
qWarning("HttpRequest: expected body is too large");
|
||||
status=abort;
|
||||
}
|
||||
else if (!boundary.isEmpty() && expectedBodySize>maxMultiPartSize) {
|
||||
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
|
||||
#ifdef SUPERVERBOSE
|
||||
qDebug("HttpRequest: expect %i bytes body",expectedBodySize);
|
||||
#endif
|
||||
status=waitForBody;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void HttpRequest::readBody(QTcpSocket& socket) {
|
||||
void HttpRequest::readBody(QTcpSocket* socket)
|
||||
{
|
||||
Q_ASSERT(expectedBodySize!=0);
|
||||
if (boundary.isEmpty()) {
|
||||
if (boundary.isEmpty())
|
||||
{
|
||||
// normal body, no multipart
|
||||
#ifdef SUPERVERBOSE
|
||||
qDebug("HttpRequest: receive body");
|
||||
#endif
|
||||
#ifdef SUPERVERBOSE
|
||||
qDebug("HttpRequest: receive body");
|
||||
#endif
|
||||
int toRead=expectedBodySize-bodyData.size();
|
||||
QByteArray newData=socket.read(toRead);
|
||||
QByteArray newData=socket->read(toRead);
|
||||
currentSize+=newData.size();
|
||||
bodyData.append(newData);
|
||||
if (bodyData.size()>=expectedBodySize) {
|
||||
if (bodyData.size()>=expectedBodySize)
|
||||
{
|
||||
status=complete;
|
||||
}
|
||||
}
|
||||
else {
|
||||
else
|
||||
{
|
||||
// multipart body, store into temp file
|
||||
#ifdef SUPERVERBOSE
|
||||
qDebug("HttpRequest: receiving multipart body");
|
||||
#endif
|
||||
if (!tempFile.isOpen()) {
|
||||
#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) {
|
||||
if (toRead>65536)
|
||||
{
|
||||
toRead=65536;
|
||||
}
|
||||
fileSize+=tempFile.write(socket.read(toRead));
|
||||
if (fileSize>=maxMultiPartSize) {
|
||||
fileSize+=tempFile.write(socket->read(toRead));
|
||||
if (fileSize>=maxMultiPartSize)
|
||||
{
|
||||
qWarning("HttpRequest: received too many multipart bytes");
|
||||
status=abort;
|
||||
}
|
||||
else if (fileSize>=expectedBodySize) {
|
||||
#ifdef SUPERVERBOSE
|
||||
else if (fileSize>=expectedBodySize)
|
||||
{
|
||||
#ifdef SUPERVERBOSE
|
||||
qDebug("HttpRequest: received whole multipart body");
|
||||
#endif
|
||||
#endif
|
||||
tempFile.flush();
|
||||
if (tempFile.error()) {
|
||||
if (tempFile.error())
|
||||
{
|
||||
qCritical("HttpRequest: Error writing temp file for multipart body");
|
||||
}
|
||||
parseMultiPartFile();
|
||||
@ -155,87 +200,106 @@ void HttpRequest::readBody(QTcpSocket& socket) {
|
||||
}
|
||||
}
|
||||
|
||||
void HttpRequest::decodeRequestParams() {
|
||||
#ifdef SUPERVERBOSE
|
||||
qDebug("HttpRequest: extract and decode request parameters");
|
||||
#endif
|
||||
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) {
|
||||
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()) {
|
||||
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 {
|
||||
else
|
||||
{
|
||||
rawParameters=bodyData;
|
||||
}
|
||||
}
|
||||
// Split the parameters into pairs of value and name
|
||||
QList<QByteArray> list=rawParameters.split('&');
|
||||
foreach (QByteArray part, list) {
|
||||
foreach (QByteArray part, list)
|
||||
{
|
||||
int equalsChar=part.indexOf('=');
|
||||
if (equalsChar>=0) {
|
||||
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()){
|
||||
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")) {
|
||||
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
|
||||
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) {
|
||||
if (posi)
|
||||
{
|
||||
name=part.left(posi).trimmed();
|
||||
value=part.mid(posi+1).trimmed();
|
||||
}
|
||||
else {
|
||||
else
|
||||
{
|
||||
name=part.trimmed();
|
||||
value="";
|
||||
}
|
||||
cookies.insert(name,value);
|
||||
}
|
||||
}
|
||||
headers.remove("Cookie");
|
||||
headers.remove("cookie");
|
||||
}
|
||||
|
||||
void HttpRequest::readFromSocket(QTcpSocket& socket) {
|
||||
void HttpRequest::readFromSocket(QTcpSocket* socket)
|
||||
{
|
||||
Q_ASSERT(status!=complete);
|
||||
if (status==waitForRequest) {
|
||||
if (status==waitForRequest)
|
||||
{
|
||||
readRequest(socket);
|
||||
}
|
||||
else if (status==waitForHeader) {
|
||||
else if (status==waitForHeader)
|
||||
{
|
||||
readHeader(socket);
|
||||
}
|
||||
else if (status==waitForBody) {
|
||||
else if (status==waitForBody)
|
||||
{
|
||||
readBody(socket);
|
||||
}
|
||||
if (currentSize>maxSize) {
|
||||
if ((boundary.isEmpty() && currentSize>maxSize) || (!boundary.isEmpty() && currentSize>maxMultiPartSize))
|
||||
{
|
||||
qWarning("HttpRequest: received too many bytes");
|
||||
status=abort;
|
||||
}
|
||||
if (status==complete) {
|
||||
if (status==complete)
|
||||
{
|
||||
// Extract and decode request parameters from url and body
|
||||
decodeRequestParams();
|
||||
// Extract cookies from headers
|
||||
@ -244,62 +308,82 @@ void HttpRequest::readFromSocket(QTcpSocket& socket) {
|
||||
}
|
||||
|
||||
|
||||
HttpRequest::RequestStatus HttpRequest::getStatus() const {
|
||||
HttpRequest::RequestStatus HttpRequest::getStatus() const
|
||||
{
|
||||
return status;
|
||||
}
|
||||
|
||||
|
||||
QByteArray HttpRequest::getMethod() const {
|
||||
QByteArray HttpRequest::getMethod() const
|
||||
{
|
||||
return method;
|
||||
}
|
||||
|
||||
|
||||
QByteArray HttpRequest::getPath() const {
|
||||
QByteArray HttpRequest::getPath() const
|
||||
{
|
||||
return urlDecode(path);
|
||||
}
|
||||
|
||||
|
||||
QByteArray HttpRequest::getVersion() const {
|
||||
const QByteArray& HttpRequest::getRawPath() const
|
||||
{
|
||||
return path;
|
||||
}
|
||||
|
||||
|
||||
QByteArray HttpRequest::getVersion() const
|
||||
{
|
||||
return version;
|
||||
}
|
||||
|
||||
|
||||
QByteArray HttpRequest::getHeader(const QByteArray& name) const {
|
||||
return headers.value(name);
|
||||
QByteArray HttpRequest::getHeader(const QByteArray& name) const
|
||||
{
|
||||
return headers.value(name.toLower());
|
||||
}
|
||||
|
||||
QList<QByteArray> HttpRequest::getHeaders(const QByteArray& name) const {
|
||||
return headers.values(name);
|
||||
QList<QByteArray> HttpRequest::getHeaders(const QByteArray& name) const
|
||||
{
|
||||
return headers.values(name.toLower());
|
||||
}
|
||||
|
||||
QMultiMap<QByteArray,QByteArray> HttpRequest::getHeaderMap() const {
|
||||
QMultiMap<QByteArray,QByteArray> HttpRequest::getHeaderMap() const
|
||||
{
|
||||
return headers;
|
||||
}
|
||||
|
||||
QByteArray HttpRequest::getParameter(const QByteArray& name) const {
|
||||
QByteArray HttpRequest::getParameter(const QByteArray& name) const
|
||||
{
|
||||
return parameters.value(name);
|
||||
}
|
||||
|
||||
QList<QByteArray> HttpRequest::getParameters(const QByteArray& name) const {
|
||||
QList<QByteArray> HttpRequest::getParameters(const QByteArray& name) const
|
||||
{
|
||||
return parameters.values(name);
|
||||
}
|
||||
|
||||
QMultiMap<QByteArray,QByteArray> HttpRequest::getParameterMap() const {
|
||||
QMultiMap<QByteArray,QByteArray> HttpRequest::getParameterMap() const
|
||||
{
|
||||
return parameters;
|
||||
}
|
||||
|
||||
QByteArray HttpRequest::getBody() const {
|
||||
QByteArray HttpRequest::getBody() const
|
||||
{
|
||||
return bodyData;
|
||||
}
|
||||
|
||||
QByteArray HttpRequest::urlDecode(const QByteArray source) {
|
||||
QByteArray HttpRequest::urlDecode(const QByteArray source)
|
||||
{
|
||||
QByteArray buffer(source);
|
||||
buffer.replace('+',' ');
|
||||
int percentChar=buffer.indexOf('%');
|
||||
while (percentChar>=0) {
|
||||
while (percentChar>=0)
|
||||
{
|
||||
bool ok;
|
||||
char byte=buffer.mid(percentChar+1,2).toInt(&ok,16);
|
||||
if (ok) {
|
||||
if (ok)
|
||||
{
|
||||
buffer.replace(percentChar,3,(char*)&byte,1);
|
||||
}
|
||||
percentChar=buffer.indexOf('%',percentChar+1);
|
||||
@ -308,65 +392,77 @@ QByteArray HttpRequest::urlDecode(const QByteArray source) {
|
||||
}
|
||||
|
||||
|
||||
void HttpRequest::parseMultiPartFile() {
|
||||
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
|
||||
while (!tempFile.atEnd() && !finished && !tempFile.error())
|
||||
{
|
||||
#ifdef SUPERVERBOSE
|
||||
qDebug("HttpRequest: reading multpart headers");
|
||||
#endif
|
||||
QByteArray fieldName;
|
||||
QByteArray fileName;
|
||||
while (!tempFile.atEnd() && !finished && !tempFile.error()) {
|
||||
while (!tempFile.atEnd() && !finished && !tempFile.error())
|
||||
{
|
||||
QByteArray line=tempFile.readLine(65536).trimmed();
|
||||
if (line.startsWith("Content-Disposition:")) {
|
||||
if (line.contains("form-data")) {
|
||||
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) {
|
||||
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) {
|
||||
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
|
||||
#ifdef SUPERVERBOSE
|
||||
qDebug("HttpRequest: multipart field=%s, filename=%s",fieldName.data(),fileName.data());
|
||||
#endif
|
||||
}
|
||||
else {
|
||||
else
|
||||
{
|
||||
qDebug("HttpRequest: ignoring unsupported content part %s",line.data());
|
||||
}
|
||||
}
|
||||
else if (line.isEmpty()) {
|
||||
else if (line.isEmpty())
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
#ifdef SUPERVERBOSE
|
||||
qDebug("HttpRequest: reading multpart data");
|
||||
#endif
|
||||
#ifdef SUPERVERBOSE
|
||||
qDebug("HttpRequest: reading multpart data");
|
||||
#endif
|
||||
QTemporaryFile* uploadedFile=0;
|
||||
QByteArray fieldValue;
|
||||
while (!tempFile.atEnd() && !finished && !tempFile.error()) {
|
||||
while (!tempFile.atEnd() && !finished && !tempFile.error())
|
||||
{
|
||||
QByteArray line=tempFile.readLine(65536);
|
||||
if (line.startsWith("--"+boundary)) {
|
||||
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()) {
|
||||
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()) {
|
||||
else if (!fileName.isEmpty() && !fieldName.isEmpty())
|
||||
{
|
||||
// last field was a file
|
||||
#ifdef SUPERVERBOSE
|
||||
qDebug("HttpRequest: finishing writing to uploaded file");
|
||||
#endif
|
||||
#ifdef SUPERVERBOSE
|
||||
qDebug("HttpRequest: finishing writing to uploaded file");
|
||||
#endif
|
||||
uploadedFile->resize(uploadedFile->size()-2);
|
||||
uploadedFile->flush();
|
||||
uploadedFile->seek(0);
|
||||
@ -375,57 +471,79 @@ void HttpRequest::parseMultiPartFile() {
|
||||
uploadedFiles.insert(fieldName,uploadedFile);
|
||||
qDebug("HttpRequest: uploaded file size is %i",(int) uploadedFile->size());
|
||||
}
|
||||
if (line.contains(boundary+"--")) {
|
||||
if (line.contains(boundary+"--"))
|
||||
{
|
||||
finished=true;
|
||||
}
|
||||
break;
|
||||
}
|
||||
else {
|
||||
if (fileName.isEmpty() && !fieldName.isEmpty()) {
|
||||
else
|
||||
{
|
||||
if (fileName.isEmpty() && !fieldName.isEmpty())
|
||||
{
|
||||
// this is a form field.
|
||||
currentSize+=line.size();
|
||||
fieldValue.append(line);
|
||||
}
|
||||
else if (!fileName.isEmpty() && !fieldName.isEmpty()) {
|
||||
else if (!fileName.isEmpty() && !fieldName.isEmpty())
|
||||
{
|
||||
// this is a file
|
||||
if (!uploadedFile) {
|
||||
if (!uploadedFile)
|
||||
{
|
||||
uploadedFile=new QTemporaryFile();
|
||||
uploadedFile->open();
|
||||
}
|
||||
uploadedFile->write(line);
|
||||
if (uploadedFile->error()) {
|
||||
if (uploadedFile->error())
|
||||
{
|
||||
qCritical("HttpRequest: error writing temp file, %s",qPrintable(uploadedFile->errorString()));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (tempFile.error()) {
|
||||
if (tempFile.error())
|
||||
{
|
||||
qCritical("HttpRequest: cannot read temp file, %s",qPrintable(tempFile.errorString()));
|
||||
}
|
||||
#ifdef SUPERVERBOSE
|
||||
qDebug("HttpRequest: finished parsing multipart temp file");
|
||||
#endif
|
||||
#ifdef SUPERVERBOSE
|
||||
qDebug("HttpRequest: finished parsing multipart temp file");
|
||||
#endif
|
||||
}
|
||||
|
||||
HttpRequest::~HttpRequest() {
|
||||
foreach(QByteArray key, uploadedFiles.keys()) {
|
||||
HttpRequest::~HttpRequest()
|
||||
{
|
||||
foreach(QByteArray key, uploadedFiles.keys())
|
||||
{
|
||||
QTemporaryFile* file=uploadedFiles.value(key);
|
||||
file->close();
|
||||
delete file;
|
||||
}
|
||||
}
|
||||
|
||||
QTemporaryFile* HttpRequest::getUploadedFile(const QByteArray fieldName) {
|
||||
QTemporaryFile* HttpRequest::getUploadedFile(const QByteArray fieldName) const
|
||||
{
|
||||
return uploadedFiles.value(fieldName);
|
||||
}
|
||||
|
||||
QByteArray HttpRequest::getCookie(const QByteArray& name) const {
|
||||
QByteArray HttpRequest::getCookie(const QByteArray& name) const
|
||||
{
|
||||
return cookies.value(name);
|
||||
}
|
||||
|
||||
/** Get the map of cookies */
|
||||
QMap<QByteArray,QByteArray>& HttpRequest::getCookieMap() {
|
||||
QMap<QByteArray,QByteArray>& HttpRequest::getCookieMap()
|
||||
{
|
||||
return cookies;
|
||||
}
|
||||
|
||||
/**
|
||||
Get the address of the connected client.
|
||||
Note that multiple clients may have the same IP address, if they
|
||||
share an internet connection (which is very common).
|
||||
*/
|
||||
QHostAddress HttpRequest::getPeerAddress() const
|
||||
{
|
||||
return peerAddress;
|
||||
}
|
||||
|
@ -7,12 +7,14 @@
|
||||
#define HTTPREQUEST_H
|
||||
|
||||
#include <QByteArray>
|
||||
#include <QHostAddress>
|
||||
#include <QTcpSocket>
|
||||
#include <QMap>
|
||||
#include <QMultiMap>
|
||||
#include <QSettings>
|
||||
#include <QTemporaryFile>
|
||||
#include <QUuid>
|
||||
#include "httpglobal.h"
|
||||
|
||||
/**
|
||||
This object represents a single HTTP request. It reads the request
|
||||
@ -31,9 +33,10 @@
|
||||
The body is always a little larger than the file itself.
|
||||
*/
|
||||
|
||||
class HttpRequest {
|
||||
class DECLSPEC HttpRequest {
|
||||
Q_DISABLE_COPY(HttpRequest)
|
||||
friend class HttpSessionStore;
|
||||
|
||||
public:
|
||||
|
||||
/** Values for getStatus() */
|
||||
@ -51,11 +54,12 @@ public:
|
||||
virtual ~HttpRequest();
|
||||
|
||||
/**
|
||||
Read the request from a socket. This method must be called repeatedly
|
||||
Read the HTTP request from a socket.
|
||||
This method is called by the connection handler repeatedly
|
||||
until the status is RequestStatus::complete or RequestStatus::abort.
|
||||
@param socket Source of the data
|
||||
*/
|
||||
void readFromSocket(QTcpSocket& socket);
|
||||
void readFromSocket(QTcpSocket* socket);
|
||||
|
||||
/**
|
||||
Get the status of this reqeust.
|
||||
@ -69,12 +73,15 @@ public:
|
||||
/** Get the decoded path of the HTPP request (e.g. "/index.html") */
|
||||
QByteArray getPath() const;
|
||||
|
||||
/** Get the raw path of the HTTP request (e.g. "/file%20with%20spaces.html") */
|
||||
const QByteArray& getRawPath() 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
|
||||
@param name Name of the header, not case-senitive.
|
||||
@return If the header occurs multiple times, only the last
|
||||
one is returned.
|
||||
*/
|
||||
@ -82,16 +89,19 @@ public:
|
||||
|
||||
/**
|
||||
Get the values of a HTTP request header.
|
||||
@param name Name of the header
|
||||
@param name Name of the header, not case-senitive.
|
||||
*/
|
||||
QList<QByteArray> getHeaders(const QByteArray& name) const;
|
||||
|
||||
/** Get all HTTP request headers */
|
||||
/**
|
||||
* Get all HTTP request headers. Note that the header names
|
||||
* are returned in lower-case.
|
||||
*/
|
||||
QMultiMap<QByteArray,QByteArray> getHeaderMap() const;
|
||||
|
||||
/**
|
||||
Get the value of a HTTP request parameter.
|
||||
@param name Name of the parameter
|
||||
@param name Name of the parameter, case-sensitive.
|
||||
@return If the parameter occurs multiple times, only the last
|
||||
one is returned.
|
||||
*/
|
||||
@ -99,14 +109,14 @@ public:
|
||||
|
||||
/**
|
||||
Get the values of a HTTP request parameter.
|
||||
@param name Name of the parameter
|
||||
@param name Name of the parameter, case-sensitive.
|
||||
*/
|
||||
QList<QByteArray> getParameters(const QByteArray& name) const;
|
||||
|
||||
/** Get all HTTP request parameters */
|
||||
/** Get all HTTP request parameters. */
|
||||
QMultiMap<QByteArray,QByteArray> getParameterMap() const;
|
||||
|
||||
/** Get the HTTP request body */
|
||||
/** Get the HTTP request body. */
|
||||
QByteArray getBody() const;
|
||||
|
||||
/**
|
||||
@ -125,17 +135,24 @@ public:
|
||||
For uploaded files, the method getParameters() returns
|
||||
the original fileName as provided by the calling web browser.
|
||||
*/
|
||||
QTemporaryFile* getUploadedFile(const QByteArray fieldName);
|
||||
QTemporaryFile* getUploadedFile(const QByteArray fieldName) const;
|
||||
|
||||
/**
|
||||
Get the value of a cookie
|
||||
Get the value of a cookie.
|
||||
@param name Name of the cookie
|
||||
*/
|
||||
QByteArray getCookie(const QByteArray& name) const;
|
||||
|
||||
/** Get the map of cookies */
|
||||
/** Get all cookies. */
|
||||
QMap<QByteArray,QByteArray>& getCookieMap();
|
||||
|
||||
/**
|
||||
Get the address of the connected client.
|
||||
Note that multiple clients may have the same IP address, if they
|
||||
share an internet connection (which is very common).
|
||||
*/
|
||||
QHostAddress getPeerAddress() const;
|
||||
|
||||
private:
|
||||
|
||||
/** Request headers */
|
||||
@ -163,11 +180,14 @@ private:
|
||||
QByteArray version;
|
||||
|
||||
/**
|
||||
Status of this request.
|
||||
Status of this request. For the state engine.
|
||||
@see RequestStatus
|
||||
*/
|
||||
RequestStatus status;
|
||||
|
||||
/** Address of the connected peer. */
|
||||
QHostAddress peerAddress;
|
||||
|
||||
/** Maximum size of requests in bytes. */
|
||||
int maxSize;
|
||||
|
||||
@ -193,13 +213,13 @@ private:
|
||||
void parseMultiPartFile();
|
||||
|
||||
/** Sub-procedure of readFromSocket(), read the first line of a request. */
|
||||
void readRequest(QTcpSocket& socket);
|
||||
void readRequest(QTcpSocket* socket);
|
||||
|
||||
/** Sub-procedure of readFromSocket(), read header lines. */
|
||||
void readHeader(QTcpSocket& socket);
|
||||
void readHeader(QTcpSocket* socket);
|
||||
|
||||
/** Sub-procedure of readFromSocket(), read the request body. */
|
||||
void readBody(QTcpSocket& socket);
|
||||
void readBody(QTcpSocket* socket);
|
||||
|
||||
/** Sub-procedure of readFromSocket(), extract and decode request parameters. */
|
||||
void decodeRequestParams();
|
||||
@ -207,6 +227,9 @@ private:
|
||||
/** Sub-procedure of readFromSocket(), extract cookies from headers */
|
||||
void extractCookies();
|
||||
|
||||
/** Buffer for collecting characters of request and header lines */
|
||||
QByteArray lineBuffer;
|
||||
|
||||
};
|
||||
|
||||
#endif // HTTPREQUEST_H
|
@ -9,10 +9,12 @@ HttpRequestHandler::HttpRequestHandler(QObject* parent)
|
||||
: QObject(parent)
|
||||
{}
|
||||
|
||||
HttpRequestHandler::~HttpRequestHandler() {}
|
||||
HttpRequestHandler::~HttpRequestHandler()
|
||||
{}
|
||||
|
||||
void HttpRequestHandler::service(HttpRequest& request, HttpResponse& response) {
|
||||
qCritical("HttpRequestHandler: you need to override the dispatch() function");
|
||||
void HttpRequestHandler::service(HttpRequest& request, HttpResponse& response)
|
||||
{
|
||||
qCritical("HttpRequestHandler: you need to override the service() 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);
|
@ -6,6 +6,7 @@
|
||||
#ifndef HTTPREQUESTHANDLER_H
|
||||
#define HTTPREQUESTHANDLER_H
|
||||
|
||||
#include "httpglobal.h"
|
||||
#include "httprequest.h"
|
||||
#include "httpresponse.h"
|
||||
|
||||
@ -21,13 +22,16 @@
|
||||
@see StaticFileController which delivers static local files.
|
||||
*/
|
||||
|
||||
class HttpRequestHandler : public QObject {
|
||||
class DECLSPEC HttpRequestHandler : public QObject {
|
||||
Q_OBJECT
|
||||
Q_DISABLE_COPY(HttpRequestHandler)
|
||||
public:
|
||||
|
||||
/** Constructor */
|
||||
HttpRequestHandler(QObject* parent=0);
|
||||
/**
|
||||
* Constructor.
|
||||
* @param parent Parent object.
|
||||
*/
|
||||
HttpRequestHandler(QObject* parent=NULL);
|
||||
|
||||
/** Destructor */
|
||||
virtual ~HttpRequestHandler();
|
198
YACReaderLibrary/server/lib/httpserver/httpresponse.cpp
Normal file
198
YACReaderLibrary/server/lib/httpserver/httpresponse.cpp
Normal file
@ -0,0 +1,198 @@
|
||||
/**
|
||||
@file
|
||||
@author Stefan Frings
|
||||
*/
|
||||
|
||||
#include "httpresponse.h"
|
||||
|
||||
HttpResponse::HttpResponse(QTcpSocket* socket)
|
||||
{
|
||||
this->socket=socket;
|
||||
statusCode=200;
|
||||
statusText="OK";
|
||||
sentHeaders=false;
|
||||
sentLastPart=false;
|
||||
chunkedMode=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;
|
||||
}
|
||||
|
||||
int HttpResponse::getStatusCode() const
|
||||
{
|
||||
return this->statusCode;
|
||||
}
|
||||
|
||||
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)
|
||||
{
|
||||
// If the output buffer has become large, then wait until it has been sent.
|
||||
if (socket->bytesToWrite()>16384)
|
||||
{
|
||||
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);
|
||||
|
||||
// Send HTTP headers, if not already done (that happens only on the first call to write())
|
||||
if (sentHeaders==false)
|
||||
{
|
||||
// If the whole response is generated with a single call to write(), then we know the total
|
||||
// size of the response and therefore can set the Content-Length header automatically.
|
||||
if (lastPart)
|
||||
{
|
||||
// Automatically set the Content-Length header
|
||||
headers.insert("Content-Length",QByteArray::number(data.size()));
|
||||
}
|
||||
|
||||
// else if we will not close the connection at the end, them we must use the chunked mode.
|
||||
else
|
||||
{
|
||||
QByteArray connectionValue=headers.value("Connection",headers.value("connection"));
|
||||
bool connectionClose=QString::compare(connectionValue,"close",Qt::CaseInsensitive)==0;
|
||||
if (!connectionClose)
|
||||
{
|
||||
headers.insert("Transfer-Encoding","chunked");
|
||||
chunkedMode=true;
|
||||
}
|
||||
}
|
||||
|
||||
writeHeaders();
|
||||
}
|
||||
|
||||
// Send data
|
||||
if (data.size()>0)
|
||||
{
|
||||
if (chunkedMode)
|
||||
{
|
||||
if (data.size()>0)
|
||||
{
|
||||
QByteArray size=QByteArray::number(data.size(),16);
|
||||
writeToSocket(size);
|
||||
writeToSocket("\r\n");
|
||||
writeToSocket(data);
|
||||
writeToSocket("\r\n");
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
writeToSocket(data);
|
||||
}
|
||||
}
|
||||
|
||||
// Only for the last chunk, send the terminating marker and flush the buffer.
|
||||
if (lastPart)
|
||||
{
|
||||
if (chunkedMode)
|
||||
{
|
||||
writeToSocket("0\r\n\r\n");
|
||||
}
|
||||
socket->flush();
|
||||
sentLastPart=true;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
|
||||
void HttpResponse::redirect(const QByteArray& url)
|
||||
{
|
||||
setStatus(303,"See Other");
|
||||
setHeader("Location",url);
|
||||
write("Redirect",true);
|
||||
}
|
||||
|
||||
|
||||
void HttpResponse::flush()
|
||||
{
|
||||
socket->flush();
|
||||
}
|
||||
|
||||
|
||||
bool HttpResponse::isConnected() const
|
||||
{
|
||||
return socket->isOpen();
|
||||
}
|
159
YACReaderLibrary/server/lib/httpserver/httpresponse.h
Normal file
159
YACReaderLibrary/server/lib/httpserver/httpresponse.h
Normal file
@ -0,0 +1,159 @@
|
||||
/**
|
||||
@file
|
||||
@author Stefan Frings
|
||||
*/
|
||||
|
||||
#ifndef HTTPRESPONSE_H
|
||||
#define HTTPRESPONSE_H
|
||||
|
||||
#include <QMap>
|
||||
#include <QString>
|
||||
#include <QTcpSocket>
|
||||
#include "httpglobal.h"
|
||||
#include "httpcookie.h"
|
||||
|
||||
/**
|
||||
This object represents a HTTP response, used to return something to the web client.
|
||||
<p>
|
||||
<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>
|
||||
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 DECLSPEC HttpResponse {
|
||||
Q_DISABLE_COPY(HttpResponse)
|
||||
public:
|
||||
|
||||
/**
|
||||
Constructor.
|
||||
@param socket used to write the response
|
||||
*/
|
||||
HttpResponse(QTcpSocket* socket);
|
||||
|
||||
/**
|
||||
Set a HTTP response header.
|
||||
You must call this method before the first write().
|
||||
@param name name of the header
|
||||
@param value value of the header
|
||||
*/
|
||||
void setHeader(QByteArray name, QByteArray value);
|
||||
|
||||
/**
|
||||
Set a HTTP response header.
|
||||
You must call this method before the first write().
|
||||
@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.
|
||||
You must call this method before the first write().
|
||||
*/
|
||||
void setStatus(int statusCode, QByteArray description=QByteArray());
|
||||
|
||||
/** Return the status code. */
|
||||
int getStatusCode() const;
|
||||
|
||||
/**
|
||||
Write body data to the socket.
|
||||
<p>
|
||||
The HTTP status line, headers and cookies are sent automatically before the body.
|
||||
<p>
|
||||
If the response contains only a single chunk (indicated by lastPart=true),
|
||||
then a Content-Length header is automatically set.
|
||||
<p>
|
||||
Chunked mode is automatically selected if there is no Content-Length header
|
||||
and also no Connection:close header.
|
||||
@param data Data bytes of the body
|
||||
@param lastPart Indicates that this is the last chunk of data and flushes the output buffer.
|
||||
*/
|
||||
void write(QByteArray data, bool lastPart=false);
|
||||
|
||||
/**
|
||||
Indicates whether the body has been sent completely (write() has been called with lastPart=true).
|
||||
*/
|
||||
bool hasSentLastPart() const;
|
||||
|
||||
/**
|
||||
Set a cookie.
|
||||
You must call this method before the first write().
|
||||
*/
|
||||
void setCookie(const HttpCookie& cookie);
|
||||
|
||||
/**
|
||||
Send a redirect response to the browser.
|
||||
Cannot be combined with write().
|
||||
@param url Destination URL
|
||||
*/
|
||||
void redirect(const QByteArray& url);
|
||||
|
||||
/**
|
||||
* Flush the output buffer (of the underlying socket).
|
||||
* You normally don't need to call this method because flush is
|
||||
* automatically called after HttpRequestHandler::service() returns.
|
||||
*/
|
||||
void flush();
|
||||
|
||||
/**
|
||||
* May be used to check whether the connection to the web client has been lost.
|
||||
* This might be useful to cancel the generation of large or slow responses.
|
||||
*/
|
||||
bool isConnected() const;
|
||||
|
||||
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;
|
||||
|
||||
/** Whether the response is sent in chunked mode */
|
||||
bool chunkedMode;
|
||||
|
||||
/** 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
|
33
YACReaderLibrary/server/lib/httpserver/httpserver.pri
Normal file
33
YACReaderLibrary/server/lib/httpserver/httpserver.pri
Normal file
@ -0,0 +1,33 @@
|
||||
INCLUDEPATH += $$PWD
|
||||
DEPENDPATH += $$PWD
|
||||
|
||||
QT += network
|
||||
|
||||
# Enable very detailed debug messages when compiling the debug version
|
||||
CONFIG(debug, debug|release) {
|
||||
DEFINES += SUPERVERBOSE
|
||||
}
|
||||
|
||||
HEADERS += $$PWD/httpglobal.h \
|
||||
$$PWD/httplistener.h \
|
||||
$$PWD/httpconnectionhandler.h \
|
||||
$$PWD/httpconnectionhandlerpool.h \
|
||||
$$PWD/httprequest.h \
|
||||
$$PWD/httpresponse.h \
|
||||
$$PWD/httpcookie.h \
|
||||
$$PWD/httprequesthandler.h \
|
||||
$$PWD/httpsession.h \
|
||||
$$PWD/httpsessionstore.h \
|
||||
$$PWD/staticfilecontroller.h
|
||||
|
||||
SOURCES += $$PWD/httpglobal.cpp \
|
||||
$$PWD/httplistener.cpp \
|
||||
$$PWD/httpconnectionhandler.cpp \
|
||||
$$PWD/httpconnectionhandlerpool.cpp \
|
||||
$$PWD/httprequest.cpp \
|
||||
$$PWD/httpresponse.cpp \
|
||||
$$PWD/httpcookie.cpp \
|
||||
$$PWD/httprequesthandler.cpp \
|
||||
$$PWD/httpsession.cpp \
|
||||
$$PWD/httpsessionstore.cpp \
|
||||
$$PWD/staticfilecontroller.cpp
|
186
YACReaderLibrary/server/lib/httpserver/httpsession.cpp
Normal file
186
YACReaderLibrary/server/lib/httpserver/httpsession.cpp
Normal file
@ -0,0 +1,186 @@
|
||||
/**
|
||||
@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().toLocal8Bit();
|
||||
#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();
|
||||
}
|
||||
}
|
118
YACReaderLibrary/server/lib/httpserver/httpsession.h
Normal file
118
YACReaderLibrary/server/lib/httpserver/httpsession.h
Normal file
@ -0,0 +1,118 @@
|
||||
/**
|
||||
@file
|
||||
@author Stefan Frings
|
||||
*/
|
||||
|
||||
#ifndef HTTPSESSION_H
|
||||
#define HTTPSESSION_H
|
||||
|
||||
#include <QByteArray>
|
||||
#include <QVariant>
|
||||
#include <QReadWriteLock>
|
||||
#include "httpglobal.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 DECLSPEC 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();
|
||||
|
||||
private:
|
||||
|
||||
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;
|
||||
|
||||
};
|
||||
|
||||
/** Pointer to the shared data. */
|
||||
HttpSessionData* dataPtr;
|
||||
|
||||
};
|
||||
|
||||
#endif // HTTPSESSION_H
|
@ -11,10 +11,10 @@ HttpSessionStore::HttpSessionStore(QSettings* settings, QObject* parent)
|
||||
:QObject(parent)
|
||||
{
|
||||
this->settings=settings;
|
||||
connect(&cleanupTimer,SIGNAL(timeout()),this,SLOT(timerEvent()));
|
||||
connect(&cleanupTimer,SIGNAL(timeout()),this,SLOT(sessionTimerEvent()));
|
||||
cleanupTimer.start(60000);
|
||||
cookieName=settings->value("cookieName","sessionid").toByteArray();
|
||||
expirationTime=settings->value("expirationTime",864000000).toInt();
|
||||
expirationTime=settings->value("expirationTime",3600000).toInt();
|
||||
qDebug("HttpSessionStore: Sessions expire after %i milliseconds",expirationTime);
|
||||
}
|
||||
|
||||
@ -23,18 +23,22 @@ HttpSessionStore::~HttpSessionStore()
|
||||
cleanupTimer.stop();
|
||||
}
|
||||
|
||||
QByteArray HttpSessionStore::getSessionId(HttpRequest& request, HttpResponse& response) {
|
||||
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()) {
|
||||
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)) {
|
||||
if (!sessionId.isEmpty())
|
||||
{
|
||||
if (!sessions.contains(sessionId))
|
||||
{
|
||||
qDebug("HttpSessionStore: received invalid session cookie with ID %s",sessionId.data());
|
||||
sessionId.clear();
|
||||
}
|
||||
@ -43,21 +47,31 @@ QByteArray HttpSessionStore::getSessionId(HttpRequest& request, HttpResponse& re
|
||||
return sessionId;
|
||||
}
|
||||
|
||||
HttpSession HttpSessionStore::getSession(HttpRequest& request, HttpResponse& response, bool allowCreate) {
|
||||
HttpSession HttpSessionStore::getSession(HttpRequest& request, HttpResponse& response, bool allowCreate)
|
||||
{
|
||||
QByteArray sessionId=getSessionId(request,response);
|
||||
mutex.lock();
|
||||
if (!sessionId.isEmpty()) {
|
||||
if (!sessionId.isEmpty())
|
||||
{
|
||||
HttpSession session=sessions.value(sessionId);
|
||||
if (!session.isNull()) {
|
||||
if (!session.isNull())
|
||||
{
|
||||
mutex.unlock();
|
||||
// Refresh the session cookie
|
||||
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();
|
||||
response.setCookie(HttpCookie(cookieName,session.getId(),expirationTime/1000,cookiePath,cookieComment,cookieDomain));
|
||||
session.setLastAccess();
|
||||
return session;
|
||||
}
|
||||
}
|
||||
// Need to create a new session
|
||||
if (allowCreate) {
|
||||
if (allowCreate)
|
||||
{
|
||||
QByteArray cookieName=settings->value("cookieName","sessionid").toByteArray();
|
||||
QByteArray cookiePath=settings->value("cookiePath","/").toByteArray();
|
||||
QByteArray cookiePath=settings->value("cookiePath").toByteArray();
|
||||
QByteArray cookieComment=settings->value("cookieComment").toByteArray();
|
||||
QByteArray cookieDomain=settings->value("cookieDomain").toByteArray();
|
||||
HttpSession session(true);
|
||||
@ -72,7 +86,8 @@ HttpSession HttpSessionStore::getSession(HttpRequest& request, HttpResponse& res
|
||||
return HttpSession();
|
||||
}
|
||||
|
||||
HttpSession HttpSessionStore::getSession(const QByteArray id) {
|
||||
HttpSession HttpSessionStore::getSession(const QByteArray id)
|
||||
{
|
||||
mutex.lock();
|
||||
HttpSession session=sessions.value(id);
|
||||
mutex.unlock();
|
||||
@ -80,19 +95,19 @@ HttpSession HttpSessionStore::getSession(const QByteArray id) {
|
||||
return session;
|
||||
}
|
||||
|
||||
void HttpSessionStore::timerEvent() {
|
||||
// Todo: find a way to delete sessions only if no controller is accessing them
|
||||
void HttpSessionStore::sessionTimerEvent()
|
||||
{
|
||||
mutex.lock();
|
||||
qint64 now=QDateTime::currentMSecsSinceEpoch();
|
||||
QMap<QByteArray,HttpSession>::iterator i = sessions.begin();
|
||||
while (i != sessions.end()) {
|
||||
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
|
||||
if (now-lastAccess>expirationTime)
|
||||
{
|
||||
qDebug("HttpSessionStore: session %s expired",session.getId().data());
|
||||
sessions.erase(prev);
|
||||
}
|
||||
@ -102,7 +117,8 @@ void HttpSessionStore::timerEvent() {
|
||||
|
||||
|
||||
/** Delete a session */
|
||||
void HttpSessionStore::removeSession(HttpSession session) {
|
||||
void HttpSessionStore::removeSession(HttpSession session)
|
||||
{
|
||||
mutex.lock();
|
||||
sessions.remove(session.getId());
|
||||
mutex.unlock();
|
@ -10,6 +10,7 @@
|
||||
#include <QMap>
|
||||
#include <QTimer>
|
||||
#include <QMutex>
|
||||
#include "httpglobal.h"
|
||||
#include "httpsession.h"
|
||||
#include "httpresponse.h"
|
||||
#include "httprequest.h"
|
||||
@ -25,17 +26,17 @@
|
||||
<code><pre>
|
||||
cookiePath=/
|
||||
cookieComment=Session ID
|
||||
cookieDomain=stefanfrings.de
|
||||
;cookieDomain=stefanfrings.de
|
||||
</pre></code>
|
||||
*/
|
||||
|
||||
class HttpSessionStore : public QObject {
|
||||
class DECLSPEC HttpSessionStore : public QObject {
|
||||
Q_OBJECT
|
||||
Q_DISABLE_COPY(HttpSessionStore)
|
||||
public:
|
||||
|
||||
/** Constructor. */
|
||||
HttpSessionStore(QSettings* settings, QObject* parent);
|
||||
HttpSessionStore(QSettings* settings, QObject* parent=NULL);
|
||||
|
||||
/** Destructor */
|
||||
virtual ~HttpSessionStore();
|
||||
@ -75,14 +76,15 @@ public:
|
||||
/** Delete a session */
|
||||
void removeSession(HttpSession session);
|
||||
|
||||
protected:
|
||||
/** Storage for the sessions */
|
||||
QMap<QByteArray,HttpSession> sessions;
|
||||
|
||||
private:
|
||||
|
||||
/** Configuration settings */
|
||||
QSettings* settings;
|
||||
|
||||
/** Storage for the sessions */
|
||||
QMap<QByteArray,HttpSession> sessions;
|
||||
|
||||
/** Timer to remove expired sessions */
|
||||
QTimer cleanupTimer;
|
||||
|
||||
@ -98,7 +100,7 @@ private:
|
||||
private slots:
|
||||
|
||||
/** Called every minute to cleanup expired sessions. */
|
||||
void timerEvent();
|
||||
void sessionTimerEvent();
|
||||
};
|
||||
|
||||
#endif // HTTPSESSIONSTORE_H
|
288
YACReaderLibrary/server/lib/httpserver/staticfilecontroller.cpp
Normal file
288
YACReaderLibrary/server/lib/httpserver/staticfilecontroller.cpp
Normal file
@ -0,0 +1,288 @@
|
||||
/**
|
||||
@file
|
||||
@author Stefan Frings
|
||||
*/
|
||||
|
||||
#include "staticfilecontroller.h"
|
||||
#include <QFileInfo>
|
||||
#include <QDir>
|
||||
#include <QDateTime>
|
||||
|
||||
//YACReader-----
|
||||
#include "httpsession.h"
|
||||
#include "yacreader_http_session.h"
|
||||
#include "static.h"
|
||||
//--
|
||||
|
||||
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",".").toString();
|
||||
if(!(docroot.startsWith(":/") || docroot.startsWith("qrc://")))
|
||||
{
|
||||
// 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
|
||||
{
|
||||
QFileInfo configFile(settings->fileName());
|
||||
docroot=QFileInfo(configFile.absolutePath(),docroot).absoluteFilePath();
|
||||
}
|
||||
}
|
||||
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();
|
||||
// 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.
|
||||
QByteArray filename=entry->filename;
|
||||
mutex.unlock();
|
||||
qDebug("StaticFileController: Cache hit for %s",path.data());
|
||||
setContentType(filename,response);
|
||||
response.setHeader("Cache-Control","max-age="+QByteArray::number(maxAge/1000));
|
||||
response.write(document);
|
||||
}
|
||||
else
|
||||
{
|
||||
mutex.unlock();
|
||||
|
||||
//TODO(DONE) carga sensible al dispositivo y a la localización
|
||||
QString stringPath = path;
|
||||
QStringList paths = QString(path).split('/');
|
||||
QString fileName = paths.last();
|
||||
stringPath.remove(fileName);
|
||||
HttpSession session=Static::sessionStore->getSession(request,response,false);
|
||||
YACReaderHttpSession *ySession = Static::yacreaderSessionStore->getYACReaderSessionHttpSession(session.getId());
|
||||
QString device = ySession->getDeviceType();
|
||||
QString display = ySession->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
|
||||
|
||||
// The file is not in cache.
|
||||
qDebug("StaticFileController: Cache miss for %s",path.data());
|
||||
// Forbid access to files outside the docroot directory
|
||||
if (path.contains("/.."))
|
||||
{
|
||||
qWarning("StaticFileController: detected forbidden characters in path %s",path.data());
|
||||
response.setStatus(403,"forbidden");
|
||||
response.write("403 forbidden",true);
|
||||
return;
|
||||
}
|
||||
// If the filename is a directory, append index.html.
|
||||
if (QFileInfo(docroot+path).isDir())
|
||||
{
|
||||
path+="/index.html";
|
||||
}
|
||||
// Try to open the file
|
||||
QFile file(docroot+path);
|
||||
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;
|
||||
entry->filename=path;
|
||||
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(65536));
|
||||
}
|
||||
}
|
||||
file.close();
|
||||
}
|
||||
else {
|
||||
if (file.exists())
|
||||
{
|
||||
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");
|
||||
}
|
||||
else if (fileName.endsWith(".svg"))
|
||||
{
|
||||
response.setHeader("Content-Type", "image/svg+xml");
|
||||
}
|
||||
else if (fileName.endsWith(".woff"))
|
||||
{
|
||||
response.setHeader("Content-Type", "font/woff");
|
||||
}
|
||||
else if (fileName.endsWith(".woff2"))
|
||||
{
|
||||
response.setHeader("Content-Type", "font/woff2");
|
||||
}
|
||||
else if (fileName.endsWith(".ttf"))
|
||||
{
|
||||
response.setHeader("Content-Type", "application/x-font-ttf");
|
||||
}
|
||||
else if (fileName.endsWith(".eot"))
|
||||
{
|
||||
response.setHeader("Content-Type", "application/vnd.ms-fontobject");
|
||||
}
|
||||
else if (fileName.endsWith(".otf"))
|
||||
{
|
||||
response.setHeader("Content-Type", "application/font-otf");
|
||||
}
|
||||
// Todo: add all of your content types
|
||||
else
|
||||
{
|
||||
qDebug("StaticFileController: unknown MIME type for filename '%s'", qPrintable(fileName));
|
||||
}
|
||||
}
|
||||
|
||||
//YACReader------------------------------------------------------------------------
|
||||
|
||||
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ó alternativa traducida ó fileName-locale.extensión si se encontró
|
||||
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ífico para este dispositivo y locales
|
||||
else
|
||||
return getLocalizedFileName(fileName,locales,path); //no hay archivo especí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,94 @@
|
||||
/**
|
||||
@file
|
||||
@author Stefan Frings
|
||||
*/
|
||||
|
||||
#ifndef STATICFILECONTROLLER_H
|
||||
#define STATICFILECONTROLLER_H
|
||||
|
||||
#include <QCache>
|
||||
#include <QMutex>
|
||||
#include "httpglobal.h"
|
||||
#include "httprequest.h"
|
||||
#include "httpresponse.h"
|
||||
#include "httprequesthandler.h"
|
||||
|
||||
/**
|
||||
Delivers static files. It is usually called by the applications main request handler when
|
||||
the caller requests 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 DECLSPEC StaticFileController : public HttpRequestHandler {
|
||||
Q_OBJECT
|
||||
Q_DISABLE_COPY(StaticFileController)
|
||||
public:
|
||||
|
||||
/** Constructor */
|
||||
StaticFileController(QSettings* settings, QObject* parent = NULL);
|
||||
|
||||
/** 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;
|
||||
QByteArray filename;
|
||||
};
|
||||
|
||||
/** 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;
|
||||
|
||||
//YACReader------------------------------------------------------------------------
|
||||
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
|
@ -13,8 +13,14 @@ DualFileLogger::DualFileLogger(QSettings* firstSettings, QSettings* secondSettin
|
||||
secondLogger=new FileLogger(secondSettings, refreshInterval, this);
|
||||
}
|
||||
|
||||
|
||||
void DualFileLogger::log(const QtMsgType type, const QString& message) {
|
||||
firstLogger->log(type, message);
|
||||
secondLogger->log(type, message);
|
||||
void DualFileLogger::log(const QtMsgType type, const QString& message, const QString &file, const QString &function, const int line)
|
||||
{
|
||||
firstLogger->log(type,message,file,function,line);
|
||||
secondLogger->log(type,message,file,function,line);
|
||||
}
|
||||
|
||||
void DualFileLogger::clear(const bool buffer, const bool variables)
|
||||
{
|
||||
firstLogger->clear(buffer,variables);
|
||||
secondLogger->clear(buffer,variables);
|
||||
}
|
@ -6,11 +6,12 @@
|
||||
#ifndef DUALFILELOGGER_H
|
||||
#define DUALFILELOGGER_H
|
||||
|
||||
#include "logger.h"
|
||||
#include "filelogger.h"
|
||||
#include <QString>
|
||||
#include <QSettings>
|
||||
#include <QtGlobal>
|
||||
#include "logglobal.h"
|
||||
#include "logger.h"
|
||||
#include "filelogger.h"
|
||||
|
||||
/**
|
||||
Logs messages into two log files simultaneously.
|
||||
@ -18,7 +19,7 @@
|
||||
@see FileLogger for a description of the two underlying loggers.
|
||||
*/
|
||||
|
||||
class DualFileLogger : public Logger {
|
||||
class DECLSPEC DualFileLogger : public Logger {
|
||||
Q_OBJECT
|
||||
Q_DISABLE_COPY(DualFileLogger)
|
||||
public:
|
||||
@ -37,13 +38,24 @@ public:
|
||||
DualFileLogger(QSettings* firstSettings, QSettings* secondSettings, const int refreshInterval=10000, QObject *parent = 0);
|
||||
|
||||
/**
|
||||
Decorate and log a message.
|
||||
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);
|
||||
virtual void log(const QtMsgType type, const QString& message, const QString &file="", const QString &function="", const int line=0);
|
||||
|
||||
/**
|
||||
Clear the thread-local data of the current thread.
|
||||
This method is thread safe.
|
||||
@param buffer Whether to clear the backtrace buffer
|
||||
@param variables Whether to clear the log variables
|
||||
*/
|
||||
virtual void clear(const bool buffer=true, const bool variables=true);
|
||||
|
||||
private:
|
||||
|
@ -13,16 +13,16 @@
|
||||
#include <QDir>
|
||||
#include <QFileInfo>
|
||||
#include <stdio.h>
|
||||
#include "yacreader_global.h"
|
||||
|
||||
void FileLogger::refreshSettings() {
|
||||
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();
|
||||
fileName=settings->value("fileName").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)
|
||||
@ -30,18 +30,19 @@ void FileLogger::refreshSettings() {
|
||||
if (QDir::isRelativePath(fileName))
|
||||
#endif
|
||||
{
|
||||
QFileInfo configFile(YACReader::getSettingsPath());
|
||||
fileName=QFileInfo(YACReader::getSettingsPath(),fileName).absoluteFilePath();
|
||||
QFileInfo configFile(settings->fileName());
|
||||
fileName=QFileInfo(configFile.absolutePath(),fileName).absoluteFilePath();
|
||||
}
|
||||
maxSize=settings->value("maxSize",1048576).toLongLong();
|
||||
maxBackups=settings->value("maxBackups",1).toInt();
|
||||
maxSize=settings->value("maxSize",0).toLongLong();
|
||||
maxBackups=settings->value("maxBackups",0).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",QtCriticalMsg).toInt());
|
||||
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) {
|
||||
if (oldFileName!=fileName)
|
||||
{
|
||||
fprintf(stderr,"Logging to %s\n",qPrintable(fileName));
|
||||
close();
|
||||
open();
|
||||
@ -57,7 +58,8 @@ FileLogger::FileLogger(QSettings* settings, const int refreshInterval, QObject*
|
||||
Q_ASSERT(refreshInterval>=0);
|
||||
this->settings=settings;
|
||||
file=0;
|
||||
if (refreshInterval>0) {
|
||||
if (refreshInterval>0)
|
||||
{
|
||||
refreshTimer.start(refreshInterval,this);
|
||||
}
|
||||
flushTimer.start(1000,this);
|
||||
@ -65,26 +67,31 @@ FileLogger::FileLogger(QSettings* settings, const int refreshInterval, QObject*
|
||||
}
|
||||
|
||||
|
||||
FileLogger::~FileLogger() {
|
||||
FileLogger::~FileLogger()
|
||||
{
|
||||
close();
|
||||
}
|
||||
|
||||
|
||||
void FileLogger::write(const LogMessage* logMessage) {
|
||||
void FileLogger::write(const LogMessage* logMessage)
|
||||
{
|
||||
// Try to write to the file
|
||||
if (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) {
|
||||
if (logMessage->getType()>=QtCriticalMsg)
|
||||
{
|
||||
file->flush();
|
||||
}
|
||||
|
||||
// Check for success
|
||||
if (file->error()) {
|
||||
if (file->error())
|
||||
{
|
||||
close();
|
||||
qWarning("Cannot write to log file %s: %s",qPrintable(fileName),qPrintable(file->errorString()));
|
||||
}
|
||||
@ -92,19 +99,23 @@ void FileLogger::write(const LogMessage* logMessage) {
|
||||
}
|
||||
|
||||
// Fall-back to the super class method, if writing failed
|
||||
if (!file) {
|
||||
if (!file)
|
||||
{
|
||||
Logger::write(logMessage);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
void FileLogger::open() {
|
||||
if (fileName.isEmpty()) {
|
||||
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)) {
|
||||
if (!file->open(QIODevice::WriteOnly | QIODevice::Append | QIODevice::Text))
|
||||
{
|
||||
qWarning("Cannot open log file %s: %s",qPrintable(fileName),qPrintable(file->errorString()));
|
||||
file=0;
|
||||
}
|
||||
@ -112,8 +123,10 @@ void FileLogger::open() {
|
||||
}
|
||||
|
||||
|
||||
void FileLogger::close() {
|
||||
if (file) {
|
||||
void FileLogger::close()
|
||||
{
|
||||
if (file)
|
||||
{
|
||||
file->close();
|
||||
delete file;
|
||||
file=0;
|
||||
@ -123,18 +136,22 @@ void FileLogger::close() {
|
||||
void FileLogger::rotate() {
|
||||
// count current number of existing backup files
|
||||
int count=0;
|
||||
forever {
|
||||
forever
|
||||
{
|
||||
QFile bakFile(QString("%1.%2").arg(fileName).arg(count+1));
|
||||
if (bakFile.exists()) {
|
||||
if (bakFile.exists())
|
||||
{
|
||||
++count;
|
||||
}
|
||||
else {
|
||||
else
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Remove all old backup files that exceed the maximum number
|
||||
while (maxBackups>0 && count>=maxBackups) {
|
||||
while (maxBackups>0 && count>=maxBackups)
|
||||
{
|
||||
QFile::remove(QString("%1.%2").arg(fileName).arg(count));
|
||||
--count;
|
||||
}
|
||||
@ -149,21 +166,26 @@ void FileLogger::rotate() {
|
||||
}
|
||||
|
||||
|
||||
void FileLogger::timerEvent(QTimerEvent* event) {
|
||||
if (!event) {
|
||||
void FileLogger::timerEvent(QTimerEvent* event)
|
||||
{
|
||||
if (!event)
|
||||
{
|
||||
return;
|
||||
}
|
||||
else if (event->timerId()==refreshTimer.timerId()) {
|
||||
else if (event->timerId()==refreshTimer.timerId())
|
||||
{
|
||||
refreshSettings();
|
||||
}
|
||||
else if (event->timerId()==flushTimer.timerId() && file) {
|
||||
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) {
|
||||
if (maxSize>0 && file->size()>=maxSize)
|
||||
{
|
||||
close();
|
||||
rotate();
|
||||
open();
|
@ -11,6 +11,7 @@
|
||||
#include <QFile>
|
||||
#include <QMutex>
|
||||
#include <QBasicTimer>
|
||||
#include "logglobal.h"
|
||||
#include "logger.h"
|
||||
|
||||
/**
|
||||
@ -45,7 +46,7 @@
|
||||
@see Logger for a descrition of the buffer.
|
||||
*/
|
||||
|
||||
class FileLogger : public Logger {
|
||||
class DECLSPEC FileLogger : public Logger {
|
||||
Q_OBJECT
|
||||
Q_DISABLE_COPY(FileLogger)
|
||||
public:
|
@ -9,6 +9,7 @@
|
||||
#include <QMutex>
|
||||
#include <QDateTime>
|
||||
#include <QThread>
|
||||
#include <QObject>
|
||||
|
||||
Logger* Logger::defaultLogger=0;
|
||||
|
||||
@ -16,9 +17,6 @@ Logger* Logger::defaultLogger=0;
|
||||
QThreadStorage<QHash<QString,QString>*> Logger::logVars;
|
||||
|
||||
|
||||
QThreadStorage<QList<LogMessage*>*> Logger::buffers;
|
||||
|
||||
|
||||
QMutex Logger::mutex;
|
||||
|
||||
|
||||
@ -32,7 +30,8 @@ Logger::Logger(QObject* parent)
|
||||
|
||||
|
||||
Logger::Logger(const QString msgFormat, const QString timestampFormat, const QtMsgType minLevel, const int bufferSize, QObject* parent)
|
||||
:QObject(parent) {
|
||||
:QObject(parent)
|
||||
{
|
||||
this->msgFormat=msgFormat;
|
||||
this->timestampFormat=timestampFormat;
|
||||
this->minLevel=minLevel;
|
||||
@ -40,7 +39,8 @@ Logger::Logger(const QString msgFormat, const QString timestampFormat, const QtM
|
||||
}
|
||||
|
||||
|
||||
void Logger::msgHandler(const QtMsgType type, const QString &message, const QString &file, const QString &function, const int line) {
|
||||
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);
|
||||
|
||||
@ -50,18 +50,21 @@ void Logger::msgHandler(const QtMsgType type, const QString &message, const QStr
|
||||
recursiveMutex.lock();
|
||||
|
||||
// Fall back to stderr when this method has been called recursively.
|
||||
if (defaultLogger && nonRecursiveMutex.tryLock()) {
|
||||
if (defaultLogger && nonRecursiveMutex.tryLock())
|
||||
{
|
||||
defaultLogger->log(type, message, file, function, line);
|
||||
nonRecursiveMutex.unlock();
|
||||
}
|
||||
else {
|
||||
else
|
||||
{
|
||||
fputs(qPrintable(message),stderr);
|
||||
fflush(stderr);
|
||||
}
|
||||
|
||||
// Abort the program after logging a fatal message
|
||||
if (type>=QtFatalMsg) {
|
||||
//abort();
|
||||
if (type>=QtFatalMsg)
|
||||
{
|
||||
abort();
|
||||
}
|
||||
|
||||
recursiveMutex.unlock();
|
||||
@ -69,19 +72,23 @@ void Logger::msgHandler(const QtMsgType type, const QString &message, const QStr
|
||||
|
||||
|
||||
#if QT_VERSION >= 0x050000
|
||||
void Logger::msgHandler5(const QtMsgType type, const QMessageLogContext &context, const QString &message) {
|
||||
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) {
|
||||
void Logger::msgHandler4(const QtMsgType type, const char* message)
|
||||
{
|
||||
msgHandler(type,message);
|
||||
}
|
||||
#endif
|
||||
|
||||
|
||||
Logger::~Logger() {
|
||||
if (defaultLogger==this) {
|
||||
Logger::~Logger()
|
||||
{
|
||||
if (defaultLogger==this)
|
||||
{
|
||||
#if QT_VERSION >= 0x050000
|
||||
qInstallMessageHandler(0);
|
||||
#else
|
||||
@ -92,13 +99,15 @@ Logger::~Logger() {
|
||||
}
|
||||
|
||||
|
||||
void Logger::write(const LogMessage* logMessage) {
|
||||
void Logger::write(const LogMessage* logMessage)
|
||||
{
|
||||
fputs(qPrintable(logMessage->toString(msgFormat,timestampFormat)),stderr);
|
||||
fflush(stderr);
|
||||
}
|
||||
|
||||
|
||||
void Logger::installMsgHandler() {
|
||||
void Logger::installMsgHandler()
|
||||
{
|
||||
defaultLogger=this;
|
||||
#if QT_VERSION >= 0x050000
|
||||
qInstallMessageHandler(msgHandler5);
|
||||
@ -108,9 +117,11 @@ void Logger::installMsgHandler() {
|
||||
}
|
||||
|
||||
|
||||
void Logger::set(const QString& name, const QString& value) {
|
||||
void Logger::set(const QString& name, const QString& value)
|
||||
{
|
||||
mutex.lock();
|
||||
if (!logVars.hasLocalData()) {
|
||||
if (!logVars.hasLocalData())
|
||||
{
|
||||
logVars.setLocalData(new QHash<QString,QString>);
|
||||
}
|
||||
logVars.localData()->insert(name,value);
|
||||
@ -118,23 +129,27 @@ void Logger::set(const QString& name, const QString& value) {
|
||||
}
|
||||
|
||||
|
||||
void Logger::clear(const bool buffer, const bool variables) {
|
||||
void Logger::clear(const bool buffer, const bool variables)
|
||||
{
|
||||
mutex.lock();
|
||||
if (buffer && buffers.hasLocalData()) {
|
||||
if (buffer && buffers.hasLocalData())
|
||||
{
|
||||
QList<LogMessage*>* buffer=buffers.localData();
|
||||
while (buffer && !buffer->isEmpty()) {
|
||||
LogMessage* logMessage=buffer->takeLast();
|
||||
delete logMessage;
|
||||
}
|
||||
}
|
||||
if (variables && logVars.hasLocalData()) {
|
||||
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) {
|
||||
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
|
||||
@ -148,12 +163,14 @@ void Logger::log(const QtMsgType type, const QString& message, const QString &fi
|
||||
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) {
|
||||
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()) {
|
||||
while (!buffer->isEmpty())
|
||||
{
|
||||
LogMessage* logMessage=buffer->takeFirst();
|
||||
write(logMessage);
|
||||
delete logMessage;
|
||||
@ -163,7 +180,8 @@ void Logger::log(const QtMsgType type, const QString& message, const QString &fi
|
||||
|
||||
// Buffer is disabled, print the message if the type is high enough
|
||||
else {
|
||||
if (type>=minLevel) {
|
||||
if (type>=minLevel)
|
||||
{
|
||||
LogMessage logMessage(type,message,logVars.localData(),file,function,line);
|
||||
write(&logMessage);
|
||||
}
|
@ -6,13 +6,13 @@
|
||||
#ifndef LOGGER_H
|
||||
#define LOGGER_H
|
||||
|
||||
#include <QObject>
|
||||
|
||||
#include <QtGlobal>
|
||||
#include <QThreadStorage>
|
||||
#include <QHash>
|
||||
#include <QStringList>
|
||||
#include <QMutex>
|
||||
#include <QObject>
|
||||
#include "logglobal.h"
|
||||
#include "logmessage.h"
|
||||
|
||||
/**
|
||||
@ -45,7 +45,7 @@
|
||||
because logging to the console is less useful.
|
||||
*/
|
||||
|
||||
class Logger : public QObject {
|
||||
class DECLSPEC Logger : public QObject {
|
||||
Q_OBJECT
|
||||
Q_DISABLE_COPY(Logger)
|
||||
public:
|
||||
@ -100,10 +100,11 @@ public:
|
||||
|
||||
/**
|
||||
Clear the thread-local data of the current thread.
|
||||
This method is thread safe.
|
||||
@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);
|
||||
virtual void clear(const bool buffer=true, const bool variables=true);
|
||||
|
||||
protected:
|
||||
|
||||
@ -119,7 +120,7 @@ protected:
|
||||
/** Size of backtrace buffer, number of messages per thread. 0=disabled */
|
||||
int bufferSize;
|
||||
|
||||
/** Used to synchronize access to the static members */
|
||||
/** Used to synchronize access of concurrent threads */
|
||||
static QMutex mutex;
|
||||
|
||||
/**
|
||||
@ -176,7 +177,7 @@ private:
|
||||
static QThreadStorage<QHash<QString,QString>*> logVars;
|
||||
|
||||
/** Thread local backtrace buffers */
|
||||
static QThreadStorage<QList<LogMessage*>*> buffers;
|
||||
QThreadStorage<QList<LogMessage*>*> buffers;
|
||||
|
||||
};
|
||||
|
6
YACReaderLibrary/server/lib/logging/logging.pri
Normal file
6
YACReaderLibrary/server/lib/logging/logging.pri
Normal file
@ -0,0 +1,6 @@
|
||||
INCLUDEPATH += $$PWD
|
||||
DEPENDPATH += $$PWD
|
||||
|
||||
HEADERS += $$PWD/logglobal.h $$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
|
24
YACReaderLibrary/server/lib/logging/logglobal.h
Normal file
24
YACReaderLibrary/server/lib/logging/logglobal.h
Normal file
@ -0,0 +1,24 @@
|
||||
/**
|
||||
@file
|
||||
@author Stefan Frings
|
||||
*/
|
||||
|
||||
#ifndef LOGGLOBAL_H
|
||||
#define LOGGLOBAL_H
|
||||
|
||||
#include <QtGlobal>
|
||||
|
||||
// This is specific to Windows dll's
|
||||
#if defined(Q_OS_WIN)
|
||||
#if defined(QTWEBAPPLIB_EXPORT)
|
||||
#define DECLSPEC Q_DECL_EXPORT
|
||||
#elif defined(QTWEBAPPLIB_IMPORT)
|
||||
#define DECLSPEC Q_DECL_IMPORT
|
||||
#endif
|
||||
#endif
|
||||
#if !defined(DECLSPEC)
|
||||
#define DECLSPEC
|
||||
#endif
|
||||
|
||||
#endif // LOGGLOBAL_H
|
||||
|
@ -6,7 +6,8 @@
|
||||
#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) {
|
||||
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;
|
||||
@ -17,16 +18,19 @@ LogMessage::LogMessage(const QtMsgType type, const QString& message, QHash<QStri
|
||||
|
||||
// Copy the logVars if not null,
|
||||
// so that later changes in the original do not affect the copy
|
||||
if (logVars) {
|
||||
if (logVars)
|
||||
{
|
||||
this->logVars=*logVars;
|
||||
}
|
||||
}
|
||||
|
||||
QString LogMessage::toString(const QString& msgFormat, const QString& timestampFormat) const {
|
||||
QString LogMessage::toString(const QString& msgFormat, const QString& timestampFormat) const
|
||||
{
|
||||
QString decorated=msgFormat+"\n";
|
||||
decorated.replace("{msg}",message);
|
||||
|
||||
if (decorated.contains("{timestamp}")) {
|
||||
if (decorated.contains("{timestamp}"))
|
||||
{
|
||||
decorated.replace("{timestamp}",timestamp.toString(timestampFormat));
|
||||
}
|
||||
|
||||
@ -34,21 +38,22 @@ QString LogMessage::toString(const QString& msgFormat, const QString& timestampF
|
||||
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);
|
||||
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);
|
||||
@ -60,9 +65,11 @@ QString LogMessage::toString(const QString& msgFormat, const QString& timestampF
|
||||
decorated.replace("{thread}",threadId);
|
||||
|
||||
// Fill in variables
|
||||
if (decorated.contains("{") && !logVars.isEmpty()) {
|
||||
if (decorated.contains("{") && !logVars.isEmpty())
|
||||
{
|
||||
QList<QString> keys=logVars.keys();
|
||||
foreach (QString key, keys) {
|
||||
foreach (QString key, keys)
|
||||
{
|
||||
decorated.replace("{"+key+"}",logVars.value(key));
|
||||
}
|
||||
}
|
||||
@ -70,6 +77,7 @@ QString LogMessage::toString(const QString& msgFormat, const QString& timestampF
|
||||
return decorated;
|
||||
}
|
||||
|
||||
QtMsgType LogMessage::getType() const {
|
||||
QtMsgType LogMessage::getType() const
|
||||
{
|
||||
return type;
|
||||
}
|
@ -9,6 +9,7 @@
|
||||
#include <QtGlobal>
|
||||
#include <QDateTime>
|
||||
#include <QHash>
|
||||
#include "logglobal.h"
|
||||
|
||||
/**
|
||||
Represents a single log message together with some data
|
||||
@ -20,16 +21,17 @@
|
||||
- {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 #
|
||||
- {msg} Message text
|
||||
- {xxx} For any user-defined logger variable
|
||||
|
||||
# The macros qDebug()...qFatal() dont fill these variable in case of QT versions before 5.0.
|
||||
Plus some new variables since QT 5.0, only filled when compiled in debug mode:
|
||||
|
||||
- {file} Filename where the message was generated
|
||||
- {function} Function where the message was generated
|
||||
- {line} Line number where the message was generated
|
||||
*/
|
||||
|
||||
class LogMessage
|
||||
class DECLSPEC LogMessage
|
||||
{
|
||||
Q_DISABLE_COPY(LogMessage)
|
||||
public:
|
@ -7,131 +7,174 @@
|
||||
#include <QFileInfo>
|
||||
|
||||
Template::Template(QString source, QString sourceName)
|
||||
: QString(source) {
|
||||
: QString(source)
|
||||
{
|
||||
this->sourceName=sourceName;
|
||||
this->warnings=false;
|
||||
}
|
||||
|
||||
Template::Template(QFile& file, QTextCodec* textCodec) {
|
||||
Template::Template(QFile& file, QTextCodec* textCodec)
|
||||
{
|
||||
this->warnings=false;
|
||||
sourceName=QFileInfo(file.fileName()).baseName();
|
||||
if (!file.isOpen()) {
|
||||
if (!file.isOpen())
|
||||
{
|
||||
file.open(QFile::ReadOnly | QFile::Text);
|
||||
}
|
||||
QByteArray data=file.readAll();
|
||||
file.close();
|
||||
if (data.size()==0 || file.error()) {
|
||||
if (data.size()==0 || file.error())
|
||||
{
|
||||
qCritical("Template: cannot read from %s, %s",qPrintable(sourceName),qPrintable(file.errorString()));
|
||||
}
|
||||
else
|
||||
{
|
||||
append(textCodec->toUnicode(data));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
int Template::setVariable(QString name, QString value) {
|
||||
int Template::setVariable(QString name, QString value)
|
||||
{
|
||||
int count=0;
|
||||
QString variable="{"+name+"}";
|
||||
int start=indexOf(variable);
|
||||
while (start>=0) {
|
||||
while (start>=0)
|
||||
{
|
||||
replace(start, variable.length(), value);
|
||||
count++;
|
||||
start=indexOf(variable,start+value.length());
|
||||
}
|
||||
if (count==0 && warnings) {
|
||||
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 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) {
|
||||
while (start>=0)
|
||||
{
|
||||
int end=indexOf(endTag,start+startTag.length());
|
||||
if (end>=0) {
|
||||
if (end>=0)
|
||||
{
|
||||
count++;
|
||||
int ellse=indexOf(elseTag,start+startTag.length());
|
||||
if (ellse>start && ellse<end) { // there is an else part
|
||||
if (value==true) {
|
||||
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
|
||||
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
|
||||
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
|
||||
else
|
||||
{
|
||||
// value==false and no else part
|
||||
replace(start, end-start+endTag.length(), "");
|
||||
}
|
||||
start=indexOf(startTag,start);
|
||||
}
|
||||
else {
|
||||
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) {
|
||||
while (start>=0)
|
||||
{
|
||||
int end=indexOf(endTag,start+startTag2.length());
|
||||
if (end>=0) {
|
||||
if (end>=0)
|
||||
{
|
||||
count++;
|
||||
int ellse=indexOf(elseTag,start+startTag2.length());
|
||||
if (ellse>start && ellse<end) { // there is an else part
|
||||
if (value==false) {
|
||||
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
|
||||
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
|
||||
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
|
||||
else
|
||||
{
|
||||
// value==true and no else part
|
||||
replace(start, end-start+endTag.length(), "");
|
||||
}
|
||||
start=indexOf(startTag2,start);
|
||||
}
|
||||
else {
|
||||
else
|
||||
{
|
||||
qWarning("Template: missing condition end %s in %s",qPrintable(endTag),qPrintable(sourceName));
|
||||
}
|
||||
}
|
||||
if (count==0 && warnings) {
|
||||
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 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) {
|
||||
while (start>=0)
|
||||
{
|
||||
int end=indexOf(endTag,start+startTag.length());
|
||||
if (end>=0) {
|
||||
if (end>=0)
|
||||
{
|
||||
count++;
|
||||
int ellse=indexOf(elseTag,start+startTag.length());
|
||||
if (ellse>start && ellse<end) { // there is an else part
|
||||
if (repetitions>0) {
|
||||
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) {
|
||||
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;
|
||||
@ -145,15 +188,20 @@ int Template::loop(QString name, int repetitions) {
|
||||
}
|
||||
replace(start, end-start+endTag.length(), insertMe);
|
||||
}
|
||||
else { // repetitions==0
|
||||
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
|
||||
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) {
|
||||
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;
|
||||
@ -167,22 +215,27 @@ int Template::loop(QString name, int repetitions) {
|
||||
}
|
||||
replace(start, end-start+endTag.length(), insertMe);
|
||||
}
|
||||
else { // repetitions==0 and no else part
|
||||
else
|
||||
{
|
||||
// repetitions==0 and no else part
|
||||
replace(start, end-start+endTag.length(), "");
|
||||
}
|
||||
start=indexOf(startTag,start);
|
||||
}
|
||||
else {
|
||||
else
|
||||
{
|
||||
qWarning("Template: missing loop end %s in %s",qPrintable(endTag),qPrintable(sourceName));
|
||||
}
|
||||
}
|
||||
if (count==0 && warnings) {
|
||||
if (count==0 && warnings)
|
||||
{
|
||||
qWarning("Template: missing loop %s in %s",qPrintable(startTag),qPrintable(sourceName));
|
||||
}
|
||||
return count;
|
||||
}
|
||||
|
||||
void Template::enableWarnings(bool enable) {
|
||||
void Template::enableWarnings(bool enable)
|
||||
{
|
||||
warnings=enable;
|
||||
}
|
||||
|
@ -12,6 +12,7 @@
|
||||
#include <QTextCodec>
|
||||
#include <QFile>
|
||||
#include <QString>
|
||||
#include "templateglobal.h"
|
||||
|
||||
/**
|
||||
Enhanced version of QString for template processing. Templates
|
||||
@ -37,7 +38,7 @@
|
||||
Example code to fill this template:
|
||||
<p><code><pre>
|
||||
Template t(QFile("test.tpl"),QTextCode::codecForName("UTF-8"));
|
||||
t.setVariable("user", "Stefan");
|
||||
t.setVariable("username", "Stefan");
|
||||
t.setCondition("locked",false);
|
||||
t.loop("user",2);
|
||||
t.setVariable("user0.name,"Markus");
|
||||
@ -86,7 +87,7 @@
|
||||
@see TemplateCache
|
||||
*/
|
||||
|
||||
class Template : public QString {
|
||||
class DECLSPEC Template : public QString {
|
||||
public:
|
||||
|
||||
/**
|
@ -6,17 +6,19 @@
|
||||
TemplateCache::TemplateCache(QSettings* settings, QObject* parent)
|
||||
:TemplateLoader(settings,parent)
|
||||
{
|
||||
cache.setMaxCost(settings->value("cacheSize","160000").toInt());//este tamaño antes era 1000000
|
||||
cache.setMaxCost(settings->value("cacheSize","1000000").toInt());
|
||||
cacheTimeout=settings->value("cacheTime","60000").toInt();
|
||||
qDebug("TemplateCache: timeout=%i, size=%i",cacheTimeout,cache.maxCost());
|
||||
}
|
||||
|
||||
QString TemplateCache::tryFile(QString localizedName) {
|
||||
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)) {
|
||||
if (entry && (cacheTimeout==0 || entry->created>now-cacheTimeout))
|
||||
{
|
||||
return entry->document;
|
||||
}
|
||||
// search on filesystem
|
@ -1,8 +1,9 @@
|
||||
#ifndef TEMPLATECACHE_H
|
||||
#define TEMPLATECACHE_H
|
||||
|
||||
#include "templateloader.h"
|
||||
#include <QCache>
|
||||
#include "templateglobal.h"
|
||||
#include "templateloader.h"
|
||||
|
||||
/**
|
||||
Caching template loader, reduces the amount of I/O and improves performance
|
||||
@ -25,7 +26,7 @@
|
||||
<p>
|
||||
The following settings are required:
|
||||
<code><pre>
|
||||
path=.
|
||||
path=../templates
|
||||
suffix=.tpl
|
||||
encoding=UTF-8
|
||||
cacheSize=1000000
|
||||
@ -38,9 +39,9 @@
|
||||
@see TemplateLoader
|
||||
*/
|
||||
|
||||
class TemplateCache : public TemplateLoader {
|
||||
class DECLSPEC TemplateCache : public TemplateLoader {
|
||||
Q_OBJECT
|
||||
Q_DISABLE_COPY(TemplateCache);
|
||||
Q_DISABLE_COPY(TemplateCache)
|
||||
public:
|
||||
|
||||
/**
|
@ -0,0 +1,11 @@
|
||||
INCLUDEPATH += $$PWD
|
||||
DEPENDPATH += $$PWD
|
||||
|
||||
HEADERS += $$PWD/templateglobal.h
|
||||
HEADERS += $$PWD/template.h
|
||||
HEADERS += $$PWD/templateloader.h
|
||||
HEADERS += $$PWD/templatecache.h
|
||||
|
||||
SOURCES += $$PWD/template.cpp
|
||||
SOURCES += $$PWD/templateloader.cpp
|
||||
SOURCES += $$PWD/templatecache.cpp
|
24
YACReaderLibrary/server/lib/templateengine/templateglobal.h
Normal file
24
YACReaderLibrary/server/lib/templateengine/templateglobal.h
Normal file
@ -0,0 +1,24 @@
|
||||
/**
|
||||
@file
|
||||
@author Stefan Frings
|
||||
*/
|
||||
|
||||
#ifndef TEMPLATEGLOBAL_H
|
||||
#define TEMPLATEGLOBAL_H
|
||||
|
||||
#include <QtGlobal>
|
||||
|
||||
// This is specific to Windows dll's
|
||||
#if defined(Q_OS_WIN)
|
||||
#if defined(QTWEBAPPLIB_EXPORT)
|
||||
#define DECLSPEC Q_DECL_EXPORT
|
||||
#elif defined(QTWEBAPPLIB_IMPORT)
|
||||
#define DECLSPEC Q_DECL_IMPORT
|
||||
#endif
|
||||
#endif
|
||||
#if !defined(DECLSPEC)
|
||||
#define DECLSPEC
|
||||
#endif
|
||||
|
||||
#endif // TEMPLATEGLOBAL_H
|
||||
|
@ -9,12 +9,11 @@
|
||||
#include <QStringList>
|
||||
#include <QDir>
|
||||
#include <QSet>
|
||||
#include <QCoreApplication>
|
||||
|
||||
TemplateLoader::TemplateLoader(QSettings* settings, QObject* parent)
|
||||
: QObject(parent)
|
||||
{
|
||||
templatePath=settings->value("path","./server/templates").toString();
|
||||
templatePath=settings->value("path",".").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)
|
||||
@ -22,28 +21,27 @@ TemplateLoader::TemplateLoader(QSettings* settings, QObject* parent)
|
||||
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(QCoreApplication::applicationDirPath());
|
||||
templatePath=QFileInfo(QCoreApplication::applicationDirPath(),templatePath).absoluteFilePath();
|
||||
#endif
|
||||
QFileInfo configFile(settings->fileName());
|
||||
templatePath=QFileInfo(configFile.absolutePath(),templatePath).absoluteFilePath();
|
||||
}
|
||||
fileNameSuffix=settings->value("suffix",".tpl").toString();
|
||||
QString encoding=settings->value("encoding").toString();
|
||||
if (encoding.isEmpty()) {
|
||||
if (encoding.isEmpty())
|
||||
{
|
||||
textCodec=QTextCodec::codecForLocale();
|
||||
}
|
||||
else {
|
||||
else
|
||||
{
|
||||
textCodec=QTextCodec::codecForName(encoding.toLocal8Bit());
|
||||
}
|
||||
qDebug("TemplateLoader: path=%s, codec=%s",qPrintable(templatePath),textCodec->name().data());
|
||||
}
|
||||
|
||||
TemplateLoader::~TemplateLoader() {}
|
||||
TemplateLoader::~TemplateLoader()
|
||||
{}
|
||||
|
||||
QString TemplateLoader::tryFile(QString localizedName) {
|
||||
QString TemplateLoader::tryFile(QString localizedName)
|
||||
{
|
||||
QString fileName=templatePath+"/"+localizedName+fileNameSuffix;
|
||||
qDebug("TemplateCache: trying file %s",qPrintable(fileName));
|
||||
QFile file(fileName);
|
||||
@ -51,28 +49,33 @@ QString TemplateLoader::tryFile(QString localizedName) {
|
||||
file.open(QIODevice::ReadOnly);
|
||||
QString document=textCodec->toUnicode(file.readAll());
|
||||
file.close();
|
||||
if (file.error()) {
|
||||
if (file.error())
|
||||
{
|
||||
qCritical("TemplateLoader: cannot load file %s, %s",qPrintable(fileName),qPrintable(file.errorString()));
|
||||
return "";
|
||||
}
|
||||
else {
|
||||
else
|
||||
{
|
||||
return document;
|
||||
}
|
||||
}
|
||||
return "";
|
||||
}
|
||||
|
||||
Template TemplateLoader::getTemplate(QString templateName, QString locales) {
|
||||
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) {
|
||||
foreach (QString loc,locs)
|
||||
{
|
||||
loc.replace(QRegExp(";.*"),"");
|
||||
loc.replace('-','_');
|
||||
QString localizedName=templateName+"-"+loc.trimmed();
|
||||
if (!tried.contains(localizedName)) {
|
||||
if (!tried.contains(localizedName))
|
||||
{
|
||||
QString document=tryFile(localizedName);
|
||||
if (!document.isEmpty()) {
|
||||
mutex.unlock();
|
||||
@ -83,12 +86,15 @@ Template TemplateLoader::getTemplate(QString templateName, QString locales) {
|
||||
}
|
||||
|
||||
// Search for correct language but any country
|
||||
foreach (QString loc,locs) {
|
||||
foreach (QString loc,locs)
|
||||
{
|
||||
loc.replace(QRegExp("[;_-].*"),"");
|
||||
QString localizedName=templateName+"-"+loc.trimmed();
|
||||
if (!tried.contains(localizedName)) {
|
||||
if (!tried.contains(localizedName))
|
||||
{
|
||||
QString document=tryFile(localizedName);
|
||||
if (!document.isEmpty()) {
|
||||
if (!document.isEmpty())
|
||||
{
|
||||
mutex.unlock();
|
||||
return Template(document,localizedName);
|
||||
}
|
||||
@ -98,7 +104,8 @@ Template TemplateLoader::getTemplate(QString templateName, QString locales) {
|
||||
|
||||
// Search for default file
|
||||
QString document=tryFile(templateName);
|
||||
if (!document.isEmpty()) {
|
||||
if (!document.isEmpty())
|
||||
{
|
||||
mutex.unlock();
|
||||
return Template(document,templateName);
|
||||
}
|
@ -9,8 +9,9 @@
|
||||
#include <QString>
|
||||
#include <QSettings>
|
||||
#include <QTextCodec>
|
||||
#include "template.h"
|
||||
#include <QMutex>
|
||||
#include "templateglobal.h"
|
||||
#include "template.h"
|
||||
|
||||
/**
|
||||
Loads localized versions of template files. If the caller requests a file with the
|
||||
@ -25,7 +26,7 @@
|
||||
|
||||
The following settings are required:
|
||||
<code><pre>
|
||||
path=.
|
||||
path=../templates
|
||||
suffix=.tpl
|
||||
encoding=UTF-8
|
||||
</pre></code>
|
||||
@ -34,9 +35,9 @@
|
||||
@see TemplateCache
|
||||
*/
|
||||
|
||||
class TemplateLoader : public QObject {
|
||||
class DECLSPEC TemplateLoader : public QObject {
|
||||
Q_OBJECT
|
||||
Q_DISABLE_COPY(TemplateLoader);
|
||||
Q_DISABLE_COPY(TemplateLoader)
|
||||
public:
|
||||
|
||||
/**
|
@ -22,10 +22,18 @@
|
||||
#include "controllers/errorcontroller.h"
|
||||
#include "controllers/comicdownloadinfocontroller.h"
|
||||
#include "controllers/synccontroller.h"
|
||||
#include "controllers/versioncontroller.h"
|
||||
#include "controllers/foldercontentcontroller.h"
|
||||
#include "controllers/tagscontroller.h"
|
||||
#include "controllers/tagcontentcontroller.h"
|
||||
#include "controllers/favoritescontroller.h"
|
||||
#include "controllers/readingcomicscontroller.h"
|
||||
|
||||
#include "db_helper.h"
|
||||
#include "yacreader_libraries.h"
|
||||
|
||||
#include "yacreader_http_session.h"
|
||||
|
||||
#include "QsLog.h"
|
||||
|
||||
RequestMapper::RequestMapper(QObject* parent)
|
||||
@ -36,6 +44,8 @@ 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
|
||||
{
|
||||
YACReaderHttpSession *ySession = Static::yacreaderSessionStore->getYACReaderSessionHttpSession(session.getId());
|
||||
|
||||
QString postData = QString::fromUtf8(request.getBody());
|
||||
|
||||
if(postData.contains("currentPage"))
|
||||
@ -45,26 +55,30 @@ void RequestMapper::loadSession(HttpRequest & request, HttpResponse& response)
|
||||
|
||||
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));
|
||||
ySession->setDeviceType(data.at(0).split(":").at(1));
|
||||
ySession->setDisplayType(data.at(1).split(":").at(1));
|
||||
QList<QString> comics = data.at(2).split(":").at(1).split("\t");
|
||||
session.clearComics();
|
||||
ySession->clearComics();
|
||||
foreach(QString hash,comics) {
|
||||
session.setComicOnDevice(hash);
|
||||
ySession->setComicOnDevice(hash);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if(data.length()>1)
|
||||
{
|
||||
session.setDeviceType(data.at(0).split(":").at(1));
|
||||
session.setDisplayType(data.at(1).split(":").at(1));
|
||||
ySession->setDeviceType(data.at(0).split(":").at(1));
|
||||
ySession->setDisplayType(data.at(1).split(":").at(1));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
YACReaderHttpSession *ySession = new YACReaderHttpSession(this);
|
||||
|
||||
Static::yacreaderSessionStore->addYACReaderHttpSession(session.getId(), ySession);
|
||||
|
||||
session.set("ySession","ok");
|
||||
|
||||
QString postData = QString::fromUtf8(request.getBody());
|
||||
@ -74,18 +88,18 @@ void RequestMapper::loadSession(HttpRequest & request, HttpResponse& response)
|
||||
|
||||
if(data.length() > 2)
|
||||
{
|
||||
session.setDeviceType(data.at(0).split(":").at(1));
|
||||
session.setDisplayType(data.at(1).split(":").at(1));
|
||||
ySession->setDeviceType(data.at(0).split(":").at(1));
|
||||
ySession->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);
|
||||
ySession->setComicOnDevice(hash);
|
||||
}
|
||||
}
|
||||
else //values by default, only for debug purposes.
|
||||
{
|
||||
session.setDeviceType("ipad");
|
||||
session.setDisplayType("@2x");
|
||||
ySession->setDeviceType("ipad");
|
||||
ySession->setDisplayType("@2x");
|
||||
}
|
||||
|
||||
}
|
||||
@ -105,6 +119,14 @@ void RequestMapper::service(HttpRequest& request, HttpResponse& response) {
|
||||
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 serverVersion("/version/?");
|
||||
QRegExp folderContent("/library/.+/folder/[0-9]+/content/?");
|
||||
QRegExp favs("/library/.+/favs/?");
|
||||
QRegExp reading("/library/.+/reading/?");
|
||||
QRegExp tags("/library/.+/tags/?");
|
||||
QRegExp tagContent("/library/.+/tag/[0-9]+/content/?");
|
||||
QRegExp readingLists("/library/.+/reading_lists/?");
|
||||
QRegExp readingListContent("/library/.+/reading_list/[0-9]+/content/?");
|
||||
|
||||
QRegExp sync("/sync");
|
||||
|
||||
@ -122,8 +144,14 @@ void RequestMapper::service(HttpRequest& request, HttpResponse& response) {
|
||||
}
|
||||
else
|
||||
{
|
||||
if(sync.exactMatch(path))
|
||||
if(serverVersion.exactMatch(path))
|
||||
{
|
||||
VersionController().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
|
||||
@ -161,6 +189,34 @@ void RequestMapper::service(HttpRequest& request, HttpResponse& response) {
|
||||
{
|
||||
UpdateComicController().service(request, response);
|
||||
}
|
||||
else if(folderContent.exactMatch(path))
|
||||
{
|
||||
FolderContentController().service(request, response);
|
||||
}
|
||||
else if(tags.exactMatch(path))
|
||||
{
|
||||
TagsController().service(request, response);
|
||||
}
|
||||
else if(tagContent.exactMatch(path))
|
||||
{
|
||||
TagContentController().service(request, response);
|
||||
}
|
||||
else if(favs.exactMatch(path))
|
||||
{
|
||||
FavoritesController().service(request, response);
|
||||
}
|
||||
else if(reading.exactMatch(path))
|
||||
{
|
||||
ReadingComicsController().service(request, response);
|
||||
}
|
||||
else if(readingLists.exactMatch(path))
|
||||
{
|
||||
//ReadingListsController().service(request, response);
|
||||
}
|
||||
else if(readingListContent.exactMatch(path))
|
||||
{
|
||||
//ReadingListContentController().service(request, response);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
|
@ -15,7 +15,18 @@ HEADERS += \
|
||||
$$PWD/controllers/covercontroller.h \
|
||||
$$PWD/controllers/updatecomiccontroller.h \
|
||||
$$PWD/controllers/comicdownloadinfocontroller.h \
|
||||
$$PWD/controllers/synccontroller.h
|
||||
$$PWD/controllers/synccontroller.h \
|
||||
#v2
|
||||
$$PWD/controllers/versioncontroller.h \
|
||||
$$PWD/controllers/foldercontentcontroller.h \
|
||||
$$PWD/controllers/tagscontroller.h \
|
||||
$$PWD/yacreader_http_session.h \
|
||||
$$PWD/yacreader_http_session_store.h \
|
||||
$$PWD/controllers/tagcontentcontroller.h \
|
||||
$$PWD/yacreader_server_data_helper.h \
|
||||
$$PWD/controllers/favoritescontroller.h \
|
||||
$$PWD/controllers/readingcomicscontroller.h \
|
||||
$$PWD/controllers/readinglistscontroller.h
|
||||
|
||||
SOURCES += \
|
||||
$$PWD/static.cpp \
|
||||
@ -31,8 +42,21 @@ SOURCES += \
|
||||
$$PWD/controllers/covercontroller.cpp \
|
||||
$$PWD/controllers/updatecomiccontroller.cpp \
|
||||
$$PWD/controllers/comicdownloadinfocontroller.cpp \
|
||||
$$PWD/controllers/synccontroller.cpp
|
||||
$$PWD/controllers/synccontroller.cpp \
|
||||
#v2
|
||||
$$PWD/controllers/versioncontroller.cpp \
|
||||
$$PWD/controllers/foldercontentcontroller.cpp \
|
||||
$$PWD/controllers/tagscontroller.cpp \
|
||||
$$PWD/yacreader_http_session.cpp \
|
||||
$$PWD/yacreader_http_session_store.cpp \
|
||||
$$PWD/controllers/tagcontentcontroller.cpp \
|
||||
$$PWD/yacreader_server_data_helper.cpp \
|
||||
$$PWD/controllers/favoritescontroller.cpp \
|
||||
$$PWD/controllers/readingcomicscontroller.cpp \
|
||||
$$PWD/controllers/readinglistscontroller.cpp
|
||||
|
||||
include(lib/bfLogging/bfLogging.pri)
|
||||
include(lib/bfHttpServer/bfHttpServer.pri)
|
||||
include(lib/bfTemplateEngine/bfTemplateEngine.pri)
|
||||
include(lib/logging/logging.pri)
|
||||
include(lib/httpserver/httpserver.pri)
|
||||
include(lib/templateengine/templateengine.pri)
|
||||
|
||||
DEFINES += SERVER_VERSION_NUMBER=\\\"2.0\\\"
|
||||
|
@ -35,28 +35,94 @@ void Startup::start() {
|
||||
mainLogSettings->beginGroup("mainLogFile");
|
||||
//QSettings* debugLogSettings=new QSettings(configFileName,QSettings::IniFormat,app);
|
||||
//debugLogSettings->beginGroup("debugLogFile");
|
||||
|
||||
if(mainLogSettings->value("fileName").isNull())
|
||||
mainLogSettings->setValue("fileName", QFileInfo(YACReader::getSettingsPath(), "server_log.log").absoluteFilePath());
|
||||
|
||||
if(mainLogSettings->value("maxSize").isNull())
|
||||
mainLogSettings->setValue("maxSize",1048576);
|
||||
|
||||
if(mainLogSettings->value("maxBackups").isNull())
|
||||
mainLogSettings->setValue("maxBackups",1);
|
||||
|
||||
if(mainLogSettings->value("minLevel").isNull())
|
||||
mainLogSettings->setValue("minLevel",QtCriticalMsg);
|
||||
|
||||
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");
|
||||
|
||||
if(templateSettings->value("cacheSize").isNull())
|
||||
templateSettings->setValue("cacheSize","160000");
|
||||
|
||||
QString baseTemplatePath = QString("./server/templates");
|
||||
QString templatePath;
|
||||
|
||||
#if defined Q_OS_UNIX && !defined Q_OS_MAC
|
||||
templatePath=QFileInfo(QString(DATADIR)+"/yacreader",baseTemplatePath).absoluteFilePath();
|
||||
#else
|
||||
templatePath=QFileInfo(QCoreApplication::applicationDirPath(),baseTemplatePath).absoluteFilePath();
|
||||
#endif
|
||||
|
||||
if(templateSettings->value("path").isNull())
|
||||
templateSettings->setValue("path",templatePath);
|
||||
|
||||
Static::templateLoader=new TemplateCache(templateSettings,app);
|
||||
|
||||
// Configure session store
|
||||
QSettings* sessionSettings=new QSettings(configFileName,QSettings::IniFormat,app);
|
||||
sessionSettings->beginGroup("sessions");
|
||||
|
||||
if(sessionSettings->value("expirationTime").isNull())
|
||||
sessionSettings->setValue("expirationTime",864000000);
|
||||
|
||||
Static::sessionStore=new HttpSessionStore(sessionSettings,app);
|
||||
|
||||
Static::yacreaderSessionStore = new YACReaderHttpSessionStore(Static::sessionStore, app);
|
||||
|
||||
// Configure static file controller
|
||||
QSettings* fileSettings=new QSettings(configFileName,QSettings::IniFormat,app);
|
||||
fileSettings->beginGroup("docroot");
|
||||
|
||||
QString basedocroot = "./server/docroot";
|
||||
QString docroot;
|
||||
|
||||
#if defined Q_OS_UNIX && ! defined Q_OS_MAC
|
||||
QFileInfo configFile(QString(DATADIR)+"/yacreader");
|
||||
docroot=QFileInfo(QString(DATADIR)+"/yacreader",basedocroot).absoluteFilePath();
|
||||
#else
|
||||
QFileInfo configFile(QCoreApplication::applicationDirPath());
|
||||
docroot=QFileInfo(QCoreApplication::applicationDirPath(),basedocroot).absoluteFilePath();
|
||||
#endif
|
||||
|
||||
if(fileSettings->value("path").isNull())
|
||||
fileSettings->setValue("path",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");
|
||||
|
||||
if(listenerSettings->value("maxRequestSize").isNull())
|
||||
listenerSettings->setValue("maxRequestSize","32000000");
|
||||
|
||||
if(listenerSettings->value("maxMultiPartSize").isNull())
|
||||
listenerSettings->setValue("maxMultiPartSize","32000000");
|
||||
|
||||
if(listenerSettings->value("cleanupInterval").isNull())
|
||||
listenerSettings->setValue("cleanupInterval",10000);
|
||||
|
||||
if(listenerSettings->value("minThreads").isNull())
|
||||
listenerSettings->setValue("maxThreads",1000);
|
||||
|
||||
if(listenerSettings->value("minThreads").isNull())
|
||||
listenerSettings->setValue("minThreads",50);
|
||||
|
||||
listener = new HttpListener(listenerSettings,new RequestMapper(app),app);
|
||||
|
||||
qDebug("ServiceHelper: Service has started");
|
||||
|
@ -17,6 +17,8 @@ HttpSessionStore* Static::sessionStore=0;
|
||||
|
||||
StaticFileController* Static::staticFileController=0;
|
||||
|
||||
YACReaderHttpSessionStore* Static::yacreaderSessionStore=0;
|
||||
|
||||
QString Static::getConfigFileName() {
|
||||
return QString("%1/%2.ini").arg(getConfigDir()).arg(QCoreApplication::applicationName());
|
||||
}
|
||||
|
@ -11,6 +11,8 @@
|
||||
#include "httpsessionstore.h"
|
||||
#include "staticfilecontroller.h"
|
||||
|
||||
#include "yacreader_http_session_store.h"
|
||||
|
||||
/**
|
||||
This class contains some static resources that are used by the application.
|
||||
*/
|
||||
@ -51,6 +53,8 @@ public:
|
||||
/** Storage for session cookies */
|
||||
static HttpSessionStore* sessionStore;
|
||||
|
||||
static YACReaderHttpSessionStore* yacreaderSessionStore;
|
||||
|
||||
/** Controller for static files */
|
||||
static StaticFileController* staticFileController;
|
||||
|
||||
|
174
YACReaderLibrary/server/yacreader_http_session.cpp
Normal file
174
YACReaderLibrary/server/yacreader_http_session.cpp
Normal file
@ -0,0 +1,174 @@
|
||||
#include "yacreader_http_session.h"
|
||||
|
||||
YACReaderHttpSession::YACReaderHttpSession(QObject *parent)
|
||||
: QObject(parent), comic(nullptr), remoteComic(nullptr), comicId(0), remoteComicId(0)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
YACReaderHttpSession::~YACReaderHttpSession()
|
||||
{
|
||||
if(comic != nullptr)
|
||||
delete comic;
|
||||
|
||||
if(remoteComic != nullptr)
|
||||
delete remoteComic;
|
||||
}
|
||||
|
||||
bool YACReaderHttpSession::isComicOnDevice(const QString & hash)
|
||||
{
|
||||
return comicsOnDevice.contains(hash);
|
||||
}
|
||||
|
||||
bool YACReaderHttpSession::isComicDownloaded(const QString & hash)
|
||||
{
|
||||
return downloadedComics.contains(hash);
|
||||
}
|
||||
|
||||
void YACReaderHttpSession::setComicOnDevice(const QString & hash)
|
||||
{
|
||||
comicsOnDevice.insert(hash);
|
||||
}
|
||||
|
||||
void YACReaderHttpSession::setComicsOnDevice(const QSet<QString> & set)
|
||||
{
|
||||
comicsOnDevice = set;
|
||||
}
|
||||
|
||||
void YACReaderHttpSession::setDownloadedComic(const QString & hash)
|
||||
{
|
||||
downloadedComics.insert(hash);
|
||||
}
|
||||
|
||||
QSet<QString> YACReaderHttpSession::getComicsOnDevice()
|
||||
{
|
||||
return comicsOnDevice ;
|
||||
}
|
||||
|
||||
QSet<QString> YACReaderHttpSession::getDownloadedComics()
|
||||
{
|
||||
return downloadedComics ;
|
||||
}
|
||||
|
||||
void YACReaderHttpSession::clearComics()
|
||||
{
|
||||
comicsOnDevice.clear();
|
||||
downloadedComics.clear();
|
||||
}
|
||||
//current comic (import)
|
||||
qulonglong YACReaderHttpSession::getCurrentComicId()
|
||||
{
|
||||
return comicId;
|
||||
}
|
||||
|
||||
Comic* YACReaderHttpSession::getCurrentComic()
|
||||
{
|
||||
return comic;
|
||||
}
|
||||
|
||||
void YACReaderHttpSession::dismissCurrentComic()
|
||||
{
|
||||
if(comic != nullptr)
|
||||
{
|
||||
comic->deleteLater();
|
||||
comic = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
void YACReaderHttpSession::setCurrentComic(qulonglong id, Comic * comic)
|
||||
{
|
||||
dismissCurrentComic();
|
||||
comicId = id;
|
||||
this->comic = comic;
|
||||
}
|
||||
|
||||
//current comic (read)
|
||||
qulonglong YACReaderHttpSession::getCurrentRemoteComicId()
|
||||
{
|
||||
return remoteComicId ;
|
||||
}
|
||||
|
||||
Comic* YACReaderHttpSession::getCurrentRemoteComic()
|
||||
{
|
||||
return remoteComic ;
|
||||
}
|
||||
|
||||
void YACReaderHttpSession::dismissCurrentRemoteComic()
|
||||
{
|
||||
if(remoteComic != nullptr)
|
||||
{
|
||||
remoteComic->deleteLater();
|
||||
remoteComic = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
void YACReaderHttpSession::setCurrentRemoteComic(qulonglong id, Comic * comic)
|
||||
{
|
||||
dismissCurrentRemoteComic();
|
||||
remoteComicId = id;
|
||||
remoteComic = comic;
|
||||
}
|
||||
|
||||
QString YACReaderHttpSession::getDeviceType()
|
||||
{
|
||||
return device;
|
||||
}
|
||||
|
||||
QString YACReaderHttpSession::getDisplayType()
|
||||
{
|
||||
return display;
|
||||
}
|
||||
|
||||
void YACReaderHttpSession::setDeviceType(const QString & device)
|
||||
{
|
||||
//comicsOnDevice.clear(); //TODO crear un m<>todo clear que limpie la sesi<73>n completamente
|
||||
//downloadedComics.clear();
|
||||
this->device = device;
|
||||
}
|
||||
|
||||
void YACReaderHttpSession::setDisplayType(const QString & display)
|
||||
{
|
||||
this->display = display;
|
||||
}
|
||||
|
||||
void YACReaderHttpSession::clearNavigationPath()
|
||||
{
|
||||
navigationPath.clear();
|
||||
}
|
||||
|
||||
QPair<qulonglong, quint32> YACReaderHttpSession::popNavigationItem()
|
||||
{
|
||||
if(navigationPath.isEmpty() == false)
|
||||
return navigationPath.pop();
|
||||
return QPair<qulonglong, quint32>();
|
||||
}
|
||||
|
||||
QPair<qulonglong, quint32> YACReaderHttpSession::topNavigationItem()
|
||||
{
|
||||
if(navigationPath.isEmpty() == false)
|
||||
return navigationPath.top();
|
||||
return QPair<qulonglong, quint32>();
|
||||
}
|
||||
|
||||
void YACReaderHttpSession::pushNavigationItem(const QPair<qulonglong, quint32> &item)
|
||||
{
|
||||
navigationPath.push(item);
|
||||
}
|
||||
|
||||
void YACReaderHttpSession::updateTopItem(const QPair<qulonglong, quint32> &item)
|
||||
{
|
||||
if(navigationPath.isEmpty() == false)
|
||||
{
|
||||
navigationPath.pop();
|
||||
navigationPath.push(item);
|
||||
}
|
||||
else
|
||||
{
|
||||
navigationPath.push(item);
|
||||
}
|
||||
}
|
||||
|
||||
QStack<QPair<qulonglong, quint32> > YACReaderHttpSession::getNavigationPath()
|
||||
{
|
||||
return navigationPath;
|
||||
}
|
72
YACReaderLibrary/server/yacreader_http_session.h
Normal file
72
YACReaderLibrary/server/yacreader_http_session.h
Normal file
@ -0,0 +1,72 @@
|
||||
#ifndef YACREADERHTTPSESSION_H
|
||||
#define YACREADERHTTPSESSION_H
|
||||
|
||||
#include <QObject>
|
||||
|
||||
#include "comic.h"
|
||||
|
||||
|
||||
|
||||
class YACReaderHttpSession : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
explicit YACReaderHttpSession(QObject *parent = 0);
|
||||
~YACReaderHttpSession();
|
||||
|
||||
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);
|
||||
|
||||
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();
|
||||
|
||||
signals:
|
||||
|
||||
public slots:
|
||||
|
||||
private:
|
||||
QSet<QString> comicsOnDevice;
|
||||
QSet<QString> downloadedComics;
|
||||
|
||||
QString device;
|
||||
QString display;
|
||||
|
||||
qulonglong comicId;
|
||||
qulonglong remoteComicId;
|
||||
Comic * comic;
|
||||
Comic * remoteComic;
|
||||
|
||||
QStack<QPair<qulonglong, quint32> > navigationPath; /* folder_id, page_number */
|
||||
};
|
||||
|
||||
#endif // YACREADERHTTPSESSION_H
|
44
YACReaderLibrary/server/yacreader_http_session_store.cpp
Normal file
44
YACReaderLibrary/server/yacreader_http_session_store.cpp
Normal file
@ -0,0 +1,44 @@
|
||||
#include "yacreader_http_session_store.h"
|
||||
|
||||
#include "yacreader_http_session.h"
|
||||
|
||||
#include "httpsessionstore.h"
|
||||
|
||||
|
||||
|
||||
YACReaderHttpSessionStore::YACReaderHttpSessionStore(HttpSessionStore *sessionStore, QObject *parent)
|
||||
: QObject(parent), sessionStore(sessionStore)
|
||||
{
|
||||
connect(&cleanupTimer,SIGNAL(timeout()),this,SLOT(sessionTimerEvent()));
|
||||
cleanupTimer.start(60000);
|
||||
}
|
||||
|
||||
void YACReaderHttpSessionStore::addYACReaderHttpSession(const QByteArray &httpSessionId, YACReaderHttpSession *yacreaderHttpSession)
|
||||
{
|
||||
QMutexLocker locker(&mutex);
|
||||
|
||||
sessions.insert(httpSessionId, yacreaderHttpSession);
|
||||
}
|
||||
|
||||
YACReaderHttpSession *YACReaderHttpSessionStore::getYACReaderSessionHttpSession(const QByteArray &httpSessionId)
|
||||
{
|
||||
QMutexLocker locker(&mutex);
|
||||
|
||||
return sessions.value(httpSessionId, nullptr);
|
||||
}
|
||||
|
||||
void YACReaderHttpSessionStore::sessionTimerEvent()
|
||||
{
|
||||
QMutexLocker locker(&mutex);
|
||||
for(const QByteArray &id : sessions.keys())
|
||||
{
|
||||
if(sessionStore->getSession(id).isNull())
|
||||
{
|
||||
YACReaderHttpSession *session = sessions.value(id, nullptr);
|
||||
if(session != nullptr)
|
||||
delete session;
|
||||
|
||||
sessions.remove(id);
|
||||
}
|
||||
}
|
||||
}
|
39
YACReaderLibrary/server/yacreader_http_session_store.h
Normal file
39
YACReaderLibrary/server/yacreader_http_session_store.h
Normal file
@ -0,0 +1,39 @@
|
||||
#ifndef YACREADERHTTPSESSIONSTORE_H
|
||||
#define YACREADERHTTPSESSIONSTORE_H
|
||||
|
||||
#include <QObject>
|
||||
#include <QtCore>
|
||||
|
||||
|
||||
|
||||
class HttpSessionStore;
|
||||
class YACReaderHttpSession;
|
||||
|
||||
|
||||
|
||||
class YACReaderHttpSessionStore : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
explicit YACReaderHttpSessionStore(HttpSessionStore *sessionStore, QObject *parent = 0);
|
||||
|
||||
void addYACReaderHttpSession(const QByteArray & httpSessionId, YACReaderHttpSession *yacreaderHttpSession);
|
||||
YACReaderHttpSession *getYACReaderSessionHttpSession(const QByteArray & httpSessionId);
|
||||
|
||||
signals:
|
||||
|
||||
public slots:
|
||||
|
||||
private:
|
||||
QMap<QByteArray, YACReaderHttpSession*> sessions;
|
||||
HttpSessionStore *sessionStore;
|
||||
QTimer cleanupTimer;
|
||||
|
||||
QMutex mutex;
|
||||
|
||||
private slots:
|
||||
|
||||
void sessionTimerEvent();
|
||||
};
|
||||
|
||||
#endif // YACREADERHTTPSESSIONSTORE_H
|
26
YACReaderLibrary/server/yacreader_server_data_helper.cpp
Normal file
26
YACReaderLibrary/server/yacreader_server_data_helper.cpp
Normal file
@ -0,0 +1,26 @@
|
||||
#include "yacreader_server_data_helper.h"
|
||||
|
||||
QString YACReaderServerDataHelper::folderToYSFormat(const qulonglong libraryId, const Folder & folder)
|
||||
{
|
||||
return QString("f\t%1\t%2\t%3\t%4\t%5\r\n")
|
||||
.arg(libraryId)
|
||||
.arg(folder.id)
|
||||
.arg(folder.name)
|
||||
.arg(folder.getNumChildren())
|
||||
.arg(folder.getFirstChildHash());
|
||||
}
|
||||
|
||||
QString YACReaderServerDataHelper::comicToYSFormat(const qulonglong libraryId,const ComicDB & comic)
|
||||
{
|
||||
return QString("c\t%1\t%2\t%3\t%4\t%5\t%6\t%7\t%8\r\n")
|
||||
.arg(libraryId)
|
||||
.arg(comic.id)
|
||||
.arg(comic.getFileName())
|
||||
.arg(comic.getFileSize())
|
||||
.arg(comic.info.hash)
|
||||
.arg(comic.info.currentPage)
|
||||
.arg(comic.info.numPages.toInt())
|
||||
.arg(comic.info.read?1:0);
|
||||
}
|
||||
|
||||
YACReaderServerDataHelper::YACReaderServerDataHelper() {}
|
19
YACReaderLibrary/server/yacreader_server_data_helper.h
Normal file
19
YACReaderLibrary/server/yacreader_server_data_helper.h
Normal file
@ -0,0 +1,19 @@
|
||||
#ifndef YACREADERSERVERDATAHELPER_H
|
||||
#define YACREADERSERVERDATAHELPER_H
|
||||
|
||||
#include <QtCore>
|
||||
#include "folder.h"
|
||||
#include "comic_db.h"
|
||||
|
||||
class YACReaderServerDataHelper
|
||||
{
|
||||
public:
|
||||
static QString folderToYSFormat(const qulonglong libraryId, const Folder & folder);
|
||||
static QString comicToYSFormat(const qulonglong libraryId, const ComicDB & comic);
|
||||
|
||||
private:
|
||||
YACReaderServerDataHelper();
|
||||
|
||||
};
|
||||
|
||||
#endif // YACREADERSERVERDATAHELPER_H
|
@ -9,6 +9,8 @@
|
||||
|
||||
#include "console_ui_library_creator.h"
|
||||
|
||||
#include <iostream>
|
||||
|
||||
#include "QsLog.h"
|
||||
#include "QsLogDest.h"
|
||||
|
||||
@ -118,7 +120,7 @@ int main( int argc, char ** argv )
|
||||
QCommandLineParser parser;
|
||||
parser.setApplicationDescription(QCoreApplication::tr("\nYACReaderLibraryServer is the headless (no gui) version of YACReaderLibrary"));
|
||||
parser.addHelpOption();
|
||||
parser.addVersionOption();
|
||||
const QCommandLineOption versionOption = parser.addVersionOption();
|
||||
parser.addPositionalArgument("command", "The command to execute. [start, create-library, update-library, add-library, remove-library, list-libraries]");
|
||||
|
||||
parser.parse(QCoreApplication::arguments());
|
||||
@ -126,6 +128,13 @@ int main( int argc, char ** argv )
|
||||
const QStringList args = parser.positionalArguments();
|
||||
const QString command = args.isEmpty() ? QString() : args.first();
|
||||
|
||||
if(parser.isSet(versionOption))
|
||||
{
|
||||
qout << "YACReaderLibraryServer" << " " << VERSION << endl;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
if(command == "start")
|
||||
{
|
||||
QString destLog = YACReader::getSettingsPath()+"/yacreaderlibrary.log";
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user