mirror of
https://github.com/YACReader/yacreader
synced 2025-07-19 05:24:57 -04:00
cambiado casting de void * a uint por qint64 (se evita el error ce compilaci?n
al compilar en 64 bit)
This commit is contained in:
82
YACReaderLibrary/YACReaderLibrary.pro
Normal file
82
YACReaderLibrary/YACReaderLibrary.pro
Normal file
@ -0,0 +1,82 @@
|
||||
######################################################################
|
||||
# Automatically generated by qmake (2.01a) dom 12. oct 20:47:48 2008
|
||||
######################################################################
|
||||
|
||||
TEMPLATE = app
|
||||
TARGET =
|
||||
DEPENDPATH += .
|
||||
INCLUDEPATH += .
|
||||
INCLUDEPATH += ../common \
|
||||
./server \
|
||||
./db
|
||||
CONFIG += release
|
||||
CONFIG -= flat
|
||||
QT += sql network
|
||||
|
||||
# Input
|
||||
HEADERS += comic_flow.h \
|
||||
create_library_dialog.h \
|
||||
library_creator.h \
|
||||
library_window.h \
|
||||
../common/pictureflow.h \
|
||||
add_library_dialog.h \
|
||||
../common/custom_widgets.h \
|
||||
rename_library_dialog.h \
|
||||
properties_dialog.h \
|
||||
options_dialog.h \
|
||||
export_library_dialog.h \
|
||||
import_library_dialog.h \
|
||||
package_manager.h \
|
||||
../common/qnaturalsorting.h \
|
||||
bundle_creator.h \
|
||||
./db/data_base_management.h \
|
||||
./db/treeitem.h \
|
||||
./db/treemodel.h \
|
||||
./db/tablemodel.h \
|
||||
./db/tableitem.h \
|
||||
./db/comic.h \
|
||||
./db/folder.h \
|
||||
./db/library_item.h \
|
||||
export_comics_info_dialog.h \
|
||||
import_comics_info_dialog.h \
|
||||
../common/check_new_version.h
|
||||
|
||||
SOURCES += comic_flow.cpp \
|
||||
create_library_dialog.cpp \
|
||||
library_creator.cpp \
|
||||
library_window.cpp \
|
||||
main.cpp \
|
||||
../common/pictureflow.cpp \
|
||||
add_library_dialog.cpp \
|
||||
../common/custom_widgets.cpp \
|
||||
rename_library_dialog.cpp \
|
||||
properties_dialog.cpp \
|
||||
options_dialog.cpp \
|
||||
export_library_dialog.cpp \
|
||||
import_library_dialog.cpp \
|
||||
package_manager.cpp \
|
||||
../common/qnaturalsorting.cpp \
|
||||
bundle_creator.cpp \
|
||||
./db/data_base_management.cpp \
|
||||
./db/treeitem.cpp \
|
||||
./db/treemodel.cpp \
|
||||
./db/tablemodel.cpp \
|
||||
./db/tableitem.cpp \
|
||||
./db/comic.cpp \
|
||||
./db/folder.cpp \
|
||||
./db/library_item.cpp \
|
||||
export_comics_info_dialog.cpp \
|
||||
import_comics_info_dialog.cpp \
|
||||
../common/check_new_version.cpp
|
||||
|
||||
include(./server/server.pri)
|
||||
|
||||
|
||||
RESOURCES += images.qrc files.qrc
|
||||
RC_FILE = icon.rc
|
||||
|
||||
TRANSLATIONS = yacreaderlibrary_es.ts
|
||||
|
||||
Release:DESTDIR = ../release
|
||||
Debug:DESTDIR = ../debug
|
||||
|
91
YACReaderLibrary/add_library_dialog.cpp
Normal file
91
YACReaderLibrary/add_library_dialog.cpp
Normal file
@ -0,0 +1,91 @@
|
||||
#include "add_library_dialog.h"
|
||||
|
||||
#include <QHBoxLayout>
|
||||
#include <QVBoxLayout>
|
||||
#include <QFileDialog>
|
||||
#include <QGridLayout>
|
||||
|
||||
|
||||
AddLibraryDialog::AddLibraryDialog(QWidget * parent)
|
||||
:QDialog(parent)
|
||||
{
|
||||
setupUI();
|
||||
}
|
||||
|
||||
void AddLibraryDialog::setupUI()
|
||||
{
|
||||
textLabel = new QLabel(tr("Comics folder : "));
|
||||
path = new QLineEdit;
|
||||
textLabel->setBuddy(path);
|
||||
|
||||
nameLabel = new QLabel(tr("Library Name : "));
|
||||
nameEdit = new QLineEdit;
|
||||
nameLabel->setBuddy(nameEdit);
|
||||
|
||||
accept = new QPushButton(tr("Add"));
|
||||
accept->setDisabled(true);
|
||||
connect(accept,SIGNAL(clicked()),this,SLOT(add()));
|
||||
|
||||
cancel = new QPushButton(tr("Cancel"));
|
||||
connect(cancel,SIGNAL(clicked()),this,SLOT(close()));
|
||||
|
||||
find = new QPushButton(QIcon(":/images/comicFolder.png"),"");
|
||||
connect(find,SIGNAL(clicked()),this,SLOT(findPath()));
|
||||
|
||||
QGridLayout * content = new QGridLayout;
|
||||
|
||||
content->addWidget(nameLabel,0,0);
|
||||
content->addWidget(nameEdit,0,1);
|
||||
|
||||
content->addWidget(textLabel,1,0);
|
||||
content->addWidget(path,1,1);
|
||||
content->addWidget(find,1,2);
|
||||
content->setColumnStretch(2,0);
|
||||
|
||||
QHBoxLayout *bottomLayout = new QHBoxLayout;
|
||||
bottomLayout->addStretch();
|
||||
bottomLayout->addWidget(accept);
|
||||
bottomLayout->addWidget(cancel);
|
||||
|
||||
QVBoxLayout *mainLayout = new QVBoxLayout;
|
||||
mainLayout->addLayout(content);
|
||||
mainLayout->addStretch();
|
||||
mainLayout->addLayout(bottomLayout);
|
||||
|
||||
QHBoxLayout * imgMainLayout = new QHBoxLayout;
|
||||
QLabel * imgLabel = new QLabel(this);
|
||||
QPixmap p(":/images/openLibrary.png");
|
||||
imgLabel->setPixmap(p);
|
||||
imgMainLayout->addWidget(imgLabel);//,0,Qt::AlignTop);
|
||||
imgMainLayout->addLayout(mainLayout);
|
||||
|
||||
setLayout(imgMainLayout);
|
||||
|
||||
setModal(true);
|
||||
setWindowTitle(tr("Add an existing library"));
|
||||
}
|
||||
|
||||
void AddLibraryDialog::add()
|
||||
{
|
||||
//accept->setEnabled(false);
|
||||
emit(addLibrary(QDir::cleanPath(path->text()),nameEdit->text()));
|
||||
close();
|
||||
}
|
||||
|
||||
void AddLibraryDialog::findPath()
|
||||
{
|
||||
QString s = QFileDialog::getExistingDirectory(0,"Comics directory",".");
|
||||
if(!s.isEmpty())
|
||||
{
|
||||
path->setText(s);
|
||||
accept->setEnabled(true);
|
||||
}
|
||||
}
|
||||
|
||||
void AddLibraryDialog::close()
|
||||
{
|
||||
path->clear();
|
||||
nameEdit->clear();
|
||||
accept->setEnabled(false);
|
||||
QDialog::close();
|
||||
}
|
33
YACReaderLibrary/add_library_dialog.h
Normal file
33
YACReaderLibrary/add_library_dialog.h
Normal file
@ -0,0 +1,33 @@
|
||||
#ifndef __ADD_LIBRARY_DIALOG_H
|
||||
#define __ADD_LIBRARY_DIALOG_H
|
||||
|
||||
#include <QDialog>
|
||||
#include <QLabel>
|
||||
#include <QLineEdit>
|
||||
#include <QPushButton>
|
||||
#include <QThread>
|
||||
|
||||
class AddLibraryDialog : public QDialog
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
AddLibraryDialog(QWidget * parent = 0);
|
||||
private:
|
||||
QLabel * nameLabel;
|
||||
QLabel * textLabel;
|
||||
QLineEdit * path;
|
||||
QLineEdit * nameEdit;
|
||||
QPushButton * find;
|
||||
QPushButton * accept;
|
||||
QPushButton * cancel;
|
||||
void setupUI();
|
||||
public slots:
|
||||
void add();
|
||||
void findPath();
|
||||
void close();
|
||||
signals:
|
||||
void addLibrary(QString target, QString name);
|
||||
};
|
||||
|
||||
#endif
|
||||
|
13
YACReaderLibrary/bundle_creator.cpp
Normal file
13
YACReaderLibrary/bundle_creator.cpp
Normal file
@ -0,0 +1,13 @@
|
||||
#include "bundle_creator.h"
|
||||
|
||||
|
||||
BundleCreator::BundleCreator(void)
|
||||
:QObject()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
|
||||
BundleCreator::~BundleCreator(void)
|
||||
{
|
||||
}
|
14
YACReaderLibrary/bundle_creator.h
Normal file
14
YACReaderLibrary/bundle_creator.h
Normal file
@ -0,0 +1,14 @@
|
||||
#ifndef __BUNDLE_CREATOR_H
|
||||
#define __BUNDLE_CREATOR_H
|
||||
|
||||
#include <QtCore>
|
||||
|
||||
class BundleCreator : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
BundleCreator(void);
|
||||
~BundleCreator(void);
|
||||
};
|
||||
|
||||
#endif
|
205
YACReaderLibrary/comic_flow.cpp
Normal file
205
YACReaderLibrary/comic_flow.cpp
Normal file
@ -0,0 +1,205 @@
|
||||
#include "comic_flow.h"
|
||||
#include "qnaturalsorting.h"
|
||||
|
||||
#include <QMutex>
|
||||
#include <QImageReader>
|
||||
#include <algorithm>
|
||||
|
||||
ComicFlow::ComicFlow(QWidget* parent,FlowType flowType)
|
||||
:YACReaderFlow(parent,flowType)
|
||||
{
|
||||
updateTimer = new QTimer;
|
||||
connect(updateTimer, SIGNAL(timeout()), this, SLOT(updateImageData()));
|
||||
|
||||
worker = new ImageLoader;
|
||||
connect(this, SIGNAL(centerIndexChanged(int)), this, SLOT(preload()));
|
||||
|
||||
setReflectionEffect(PlainReflection);
|
||||
}
|
||||
|
||||
ComicFlow::~ComicFlow()
|
||||
{
|
||||
delete worker;
|
||||
delete updateTimer;
|
||||
}
|
||||
|
||||
void ComicFlow::setImagePaths(const QStringList& paths)
|
||||
{
|
||||
clear();
|
||||
|
||||
//imagePath = path;
|
||||
imageFiles = paths;
|
||||
imagesLoaded.clear();
|
||||
imagesLoaded.fill(false,imageFiles.size());
|
||||
numImagesLoaded = 0;
|
||||
|
||||
imagesSetted.clear();
|
||||
imagesSetted.fill(false,imageFiles.size());
|
||||
|
||||
// populate with empty images
|
||||
QImage img; //TODO remove
|
||||
QString s;
|
||||
for(int i = 0; i < (int)imageFiles.size(); i++)
|
||||
{
|
||||
addSlide(img);
|
||||
s = imageFiles.at(i);
|
||||
s.remove(s.size()-4,4);
|
||||
if(QFileInfo(s+".r").exists())
|
||||
markSlide(i);
|
||||
}
|
||||
|
||||
setCenterIndex(0);
|
||||
worker->reset();
|
||||
preload();
|
||||
}
|
||||
|
||||
void ComicFlow::preload()
|
||||
{
|
||||
if(numImagesLoaded < imagesLoaded.size())
|
||||
updateTimer->start(30); //TODO comprobar rendimiento, originalmente era 70
|
||||
}
|
||||
|
||||
void ComicFlow::updateImageData()
|
||||
{
|
||||
// can't do anything, wait for the next possibility
|
||||
if(worker->busy())
|
||||
return;
|
||||
|
||||
// set image of last one
|
||||
int idx = worker->index();
|
||||
if( idx >= 0 && !worker->result().isNull())
|
||||
{
|
||||
if(!imagesSetted[idx])
|
||||
{
|
||||
setSlide(idx, worker->result());
|
||||
imagesSetted[idx] = true;
|
||||
numImagesLoaded++;
|
||||
}
|
||||
}
|
||||
|
||||
// try to load only few images on the left and right side
|
||||
// i.e. all visible ones plus some extra
|
||||
#define COUNT 8
|
||||
int indexes[2*COUNT+1];
|
||||
int center = centerIndex();
|
||||
indexes[0] = center;
|
||||
for(int j = 0; j < COUNT; j++)
|
||||
{
|
||||
indexes[j*2+1] = center+j+1;
|
||||
indexes[j*2+2] = center-j-1;
|
||||
}
|
||||
for(int c = 0; c < 2*COUNT+1; c++)
|
||||
{
|
||||
int i = indexes[c];
|
||||
if((i >= 0) && (i < slideCount()))
|
||||
if(!imagesLoaded[i])//slide(i).isNull())
|
||||
{
|
||||
// schedule thumbnail generation
|
||||
QString fname = imageFiles[i];
|
||||
imagesLoaded[i]=true;
|
||||
|
||||
worker->generate(i, fname, slideSize());
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// no need to generate anything? stop polling...
|
||||
updateTimer->stop();
|
||||
}
|
||||
|
||||
void ComicFlow::keyPressEvent(QKeyEvent* event)
|
||||
{
|
||||
PictureFlow::keyPressEvent(event);
|
||||
}
|
||||
|
||||
void ComicFlow::wheelEvent(QWheelEvent * event)
|
||||
{
|
||||
if(event->delta()<0)
|
||||
showNext();
|
||||
else
|
||||
showPrevious();
|
||||
event->accept();
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
//ImageLoader
|
||||
//-----------------------------------------------------------------------------
|
||||
static QImage loadImage(const QString& fileName)
|
||||
{
|
||||
QImage image;
|
||||
bool result = image.load(fileName);
|
||||
|
||||
if(!result)
|
||||
return QImage();
|
||||
|
||||
return image;
|
||||
}
|
||||
|
||||
ImageLoader::ImageLoader():
|
||||
QThread(), restart(false), working(false), idx(-1)
|
||||
{
|
||||
}
|
||||
|
||||
ImageLoader::~ImageLoader()
|
||||
{
|
||||
mutex.lock();
|
||||
condition.wakeOne();
|
||||
mutex.unlock();
|
||||
wait();
|
||||
}
|
||||
|
||||
bool ImageLoader::busy() const
|
||||
{
|
||||
return isRunning() ? working : false;
|
||||
}
|
||||
|
||||
void ImageLoader::generate(int index, const QString& fileName, QSize size)
|
||||
{
|
||||
mutex.lock();
|
||||
this->idx = index;
|
||||
this->fileName = fileName;
|
||||
this->size = size;
|
||||
this->img = QImage();
|
||||
mutex.unlock();
|
||||
|
||||
if (!isRunning())
|
||||
start();
|
||||
else
|
||||
{
|
||||
// already running, wake up whenever ready
|
||||
restart = true;
|
||||
condition.wakeOne();
|
||||
}
|
||||
}
|
||||
|
||||
void ImageLoader::run()
|
||||
{
|
||||
for(;;)
|
||||
{
|
||||
// copy necessary data
|
||||
mutex.lock();
|
||||
this->working = true;
|
||||
QString fileName = this->fileName;
|
||||
mutex.unlock();
|
||||
|
||||
QImage image = loadImage(fileName);
|
||||
|
||||
// let everyone knows it is ready
|
||||
mutex.lock();
|
||||
this->working = false;
|
||||
this->img = image;
|
||||
mutex.unlock();
|
||||
|
||||
// put to sleep
|
||||
mutex.lock();
|
||||
if (!this->restart)
|
||||
condition.wait(&mutex);
|
||||
restart = false;
|
||||
mutex.unlock();
|
||||
}
|
||||
}
|
||||
|
||||
QImage ImageLoader::result()
|
||||
{
|
||||
return img;
|
||||
}
|
73
YACReaderLibrary/comic_flow.h
Normal file
73
YACReaderLibrary/comic_flow.h
Normal file
@ -0,0 +1,73 @@
|
||||
#ifndef __COMICFLOW_H
|
||||
#define __COMICFLOW_H
|
||||
|
||||
#include "custom_widgets.h"
|
||||
#include <QtCore>
|
||||
#include <QObject>
|
||||
#include <QThread>
|
||||
#include <QImage>
|
||||
#include <QMutex>
|
||||
#include <QWaitCondition>
|
||||
#include <QString>
|
||||
#include <QWheelEvent>
|
||||
|
||||
class ImageLoader;
|
||||
class ComicFlow : public YACReaderFlow
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
ComicFlow(QWidget* parent = 0,FlowType flowType = CoverFlowLike);
|
||||
virtual ~ComicFlow();
|
||||
|
||||
void setImagePaths(const QStringList& paths);
|
||||
//bool eventFilter(QObject *target, QEvent *event);
|
||||
void keyPressEvent(QKeyEvent* event);
|
||||
|
||||
private slots:
|
||||
void preload();
|
||||
void updateImageData();
|
||||
|
||||
private:
|
||||
//QString imagePath;
|
||||
QStringList imageFiles;
|
||||
QVector<bool> imagesLoaded;
|
||||
QVector<bool> imagesSetted;
|
||||
uint numImagesLoaded;
|
||||
QTimer* updateTimer;
|
||||
ImageLoader* worker;
|
||||
virtual void wheelEvent(QWheelEvent * event);
|
||||
};
|
||||
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Source code of ImageLoader class was modified from http://code.google.com/p/photoflow/
|
||||
//------------------------------------------------------------------------------
|
||||
class ImageLoader : public QThread
|
||||
{
|
||||
public:
|
||||
ImageLoader();
|
||||
~ImageLoader();
|
||||
// returns FALSE if worker is still busy and can't take the task
|
||||
bool busy() const;
|
||||
void generate(int index, const QString& fileName, QSize size);
|
||||
void reset(){idx = -1;};
|
||||
int index() const { return idx; };
|
||||
QImage result();
|
||||
|
||||
protected:
|
||||
void run();
|
||||
|
||||
private:
|
||||
QMutex mutex;
|
||||
QWaitCondition condition;
|
||||
|
||||
bool restart;
|
||||
bool working;
|
||||
int idx;
|
||||
QString fileName;
|
||||
QSize size;
|
||||
QImage img;
|
||||
};
|
||||
|
||||
|
||||
#endif
|
192
YACReaderLibrary/create_library_dialog.cpp
Normal file
192
YACReaderLibrary/create_library_dialog.cpp
Normal file
@ -0,0 +1,192 @@
|
||||
#include "create_library_dialog.h"
|
||||
|
||||
#include <QHBoxLayout>
|
||||
#include <QVBoxLayout>
|
||||
#include <QFileDialog>
|
||||
#include <QSizePolicy>
|
||||
|
||||
|
||||
CreateLibraryDialog::CreateLibraryDialog(QWidget * parent)
|
||||
:QDialog(parent)
|
||||
{
|
||||
setupUI();
|
||||
}
|
||||
|
||||
void CreateLibraryDialog::setupUI()
|
||||
{
|
||||
textLabel = new QLabel(tr("Comics folder : "));
|
||||
path = new QLineEdit;
|
||||
textLabel->setBuddy(path);
|
||||
|
||||
nameLabel = new QLabel(tr("Library Name : "));
|
||||
nameEdit = new QLineEdit;
|
||||
nameLabel->setBuddy(nameEdit);
|
||||
|
||||
accept = new QPushButton(tr("Create"));
|
||||
accept->setDisabled(true);
|
||||
connect(accept,SIGNAL(clicked()),this,SLOT(create()));
|
||||
|
||||
cancel = new QPushButton(tr("Cancel"));
|
||||
connect(cancel,SIGNAL(clicked()),this,SIGNAL(cancelCreate()));
|
||||
connect(cancel,SIGNAL(clicked()),this,SLOT(close()));
|
||||
|
||||
find = new QPushButton(QIcon(":/images/comicFolder.png"),"");
|
||||
connect(find,SIGNAL(clicked()),this,SLOT(findPath()));
|
||||
|
||||
QGridLayout * content = new QGridLayout;
|
||||
|
||||
//QHBoxLayout *nameLayout = new QHBoxLayout;
|
||||
|
||||
content->addWidget(nameLabel,0,0);
|
||||
content->addWidget(nameEdit,0,1);
|
||||
|
||||
//QHBoxLayout *libraryLayout = new QHBoxLayout;
|
||||
|
||||
content->addWidget(textLabel,1,0);
|
||||
content->addWidget(path,1,1);
|
||||
content->addWidget(find,1,2);
|
||||
content->setColumnMinimumWidth(2,0); //TODO
|
||||
|
||||
QHBoxLayout *middleLayout = new QHBoxLayout;
|
||||
|
||||
progressBar = new QProgressBar(this);
|
||||
progressBar->setMinimum(0);
|
||||
progressBar->setMaximum(0);
|
||||
progressBar->setTextVisible(false);
|
||||
progressBar->hide();
|
||||
|
||||
currentFileLabel = new QLabel("");
|
||||
currentFileLabel->setWordWrap(true);
|
||||
middleLayout->addWidget(currentFileLabel);
|
||||
middleLayout->addStretch();
|
||||
middleLayout->setSizeConstraint(QLayout::SetMaximumSize);
|
||||
|
||||
|
||||
QHBoxLayout *bottomLayout = new QHBoxLayout;
|
||||
bottomLayout->addWidget(message = new QLabel(tr("Create a library could take several minutes. You can stop the process and update the library later for completing the task.")));
|
||||
message->setWordWrap(true);
|
||||
//message->hide();
|
||||
bottomLayout->addStretch();
|
||||
bottomLayout->addWidget(accept);
|
||||
bottomLayout->addWidget(cancel);
|
||||
|
||||
QVBoxLayout *mainLayout = new QVBoxLayout;
|
||||
mainLayout->addLayout(content);
|
||||
|
||||
mainLayout->addLayout(middleLayout);
|
||||
mainLayout->addStretch();
|
||||
mainLayout->addWidget(progressBar);
|
||||
mainLayout->addLayout(bottomLayout);
|
||||
|
||||
QHBoxLayout * imgMainLayout = new QHBoxLayout;
|
||||
QLabel * imgLabel = new QLabel(this);
|
||||
QPixmap p(":/images/new.png");
|
||||
imgLabel->setPixmap(p);
|
||||
imgMainLayout->addWidget(imgLabel);
|
||||
imgMainLayout->addLayout(mainLayout);
|
||||
|
||||
setLayout(imgMainLayout);
|
||||
|
||||
setModal(true);
|
||||
setWindowTitle(tr("Create new library"));
|
||||
}
|
||||
|
||||
void CreateLibraryDialog::create()
|
||||
{
|
||||
progressBar->show();
|
||||
message->show();
|
||||
currentFileLabel->setText("Importing : \n\n\n\n\n");
|
||||
this->adjustSize();
|
||||
accept->setEnabled(false);
|
||||
emit(createLibrary(QDir::cleanPath(path->text()),QDir::cleanPath(path->text())+"/.yacreaderlibrary",nameEdit->text()));
|
||||
}
|
||||
|
||||
void CreateLibraryDialog::findPath()
|
||||
{
|
||||
QString s = QFileDialog::getExistingDirectory(0,"Comics directory",".");
|
||||
if(!s.isEmpty())
|
||||
{
|
||||
path->setText(s);
|
||||
accept->setEnabled(true);
|
||||
}
|
||||
}
|
||||
|
||||
void CreateLibraryDialog::showCurrentFile(QString file)
|
||||
{
|
||||
currentFileLabel->setText(tr("Importing : \n") + file);
|
||||
currentFileLabel->update();
|
||||
//this->adjustSize();
|
||||
//is->update();
|
||||
}
|
||||
void CreateLibraryDialog::close()
|
||||
{
|
||||
progressBar->hide();
|
||||
path->clear();
|
||||
nameEdit->clear();
|
||||
currentFileLabel->setText("");
|
||||
this->adjustSize();
|
||||
accept->setEnabled(true);
|
||||
QDialog::close();
|
||||
}
|
||||
|
||||
void CreateLibraryDialog::setDataAndStart(QString name, QString path)
|
||||
{
|
||||
this->path->setText(path);
|
||||
this->nameEdit->setText(name);
|
||||
show();
|
||||
create();
|
||||
}
|
||||
//-----------------------------------------------------------------------------
|
||||
// UpdateLibraryDialog
|
||||
//-----------------------------------------------------------------------------
|
||||
UpdateLibraryDialog::UpdateLibraryDialog(QWidget * parent)
|
||||
:QDialog(parent)
|
||||
{
|
||||
QVBoxLayout * mainLayout = new QVBoxLayout;
|
||||
mainLayout->addWidget(message = new QLabel(tr("Updating....")));
|
||||
mainLayout->addWidget(currentFileLabel = new QLabel("\n\n\n\n"));
|
||||
currentFileLabel->setWordWrap(true);
|
||||
|
||||
QHBoxLayout * bottom = new QHBoxLayout;
|
||||
bottom->addStretch();
|
||||
bottom->addWidget(cancel = new QPushButton(tr("Cancel")));
|
||||
|
||||
connect(cancel,SIGNAL(clicked()),this,SIGNAL(cancelUpdate()));
|
||||
connect(cancel,SIGNAL(clicked()),this,SLOT(close()));
|
||||
|
||||
mainLayout->addStretch();
|
||||
|
||||
progressBar = new QProgressBar(this);
|
||||
progressBar->setMinimum(0);
|
||||
progressBar->setMaximum(0);
|
||||
progressBar->setTextVisible(false);
|
||||
|
||||
mainLayout->addWidget(progressBar);
|
||||
mainLayout->addLayout(bottom);
|
||||
|
||||
QHBoxLayout * imgMainLayout = new QHBoxLayout;
|
||||
QLabel * imgLabel = new QLabel(this);
|
||||
QPixmap p(":/images/updateLibrary.png");
|
||||
imgLabel->setPixmap(p);
|
||||
imgMainLayout->addWidget(imgLabel);
|
||||
imgMainLayout->addLayout(mainLayout);
|
||||
|
||||
setLayout(imgMainLayout);
|
||||
|
||||
setModal(true);
|
||||
setWindowTitle(tr("Update library"));
|
||||
}
|
||||
|
||||
void UpdateLibraryDialog::showCurrentFile(QString file)
|
||||
{
|
||||
currentFileLabel->setText(file);
|
||||
currentFileLabel->update();
|
||||
this->update();
|
||||
}
|
||||
|
||||
void UpdateLibraryDialog::close()
|
||||
{
|
||||
currentFileLabel->setText("");
|
||||
this->adjustSize();
|
||||
QDialog::close();
|
||||
}
|
56
YACReaderLibrary/create_library_dialog.h
Normal file
56
YACReaderLibrary/create_library_dialog.h
Normal file
@ -0,0 +1,56 @@
|
||||
#ifndef __CREATE_LIBRARY_DIALOG_H
|
||||
#define __CREATE_LIBRARY_DIALOG_H
|
||||
|
||||
#include <QDialog>
|
||||
#include <QLabel>
|
||||
#include <QLineEdit>
|
||||
#include <QPushButton>
|
||||
#include <QThread>
|
||||
#include <QProgressBar>
|
||||
|
||||
class CreateLibraryDialog : public QDialog
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
CreateLibraryDialog(QWidget * parent = 0);
|
||||
private:
|
||||
QLabel * nameLabel;
|
||||
QLabel * textLabel;
|
||||
QLabel * message;
|
||||
QProgressBar *progressBar;
|
||||
QLabel * currentFileLabel;
|
||||
QLineEdit * path;
|
||||
QLineEdit * nameEdit;
|
||||
QPushButton * find;
|
||||
QPushButton * accept;
|
||||
QPushButton * cancel;
|
||||
void setupUI();
|
||||
public slots:
|
||||
void create();
|
||||
void findPath();
|
||||
void showCurrentFile(QString file);
|
||||
void close();
|
||||
void setDataAndStart(QString name, QString paht);
|
||||
signals:
|
||||
void createLibrary(QString source, QString target, QString name);
|
||||
void cancelCreate();
|
||||
};
|
||||
|
||||
class UpdateLibraryDialog : public QDialog
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
UpdateLibraryDialog(QWidget * parent = 0);
|
||||
private:
|
||||
QLabel * message;
|
||||
QLabel * currentFileLabel;
|
||||
QProgressBar *progressBar;
|
||||
QPushButton * cancel;
|
||||
public slots:
|
||||
void showCurrentFile(QString file);
|
||||
void close();
|
||||
signals:
|
||||
void cancelUpdate();
|
||||
};
|
||||
|
||||
#endif
|
665
YACReaderLibrary/db/comic.cpp
Normal file
665
YACReaderLibrary/db/comic.cpp
Normal file
@ -0,0 +1,665 @@
|
||||
#include "comic.h"
|
||||
|
||||
#include <QSqlQuery>
|
||||
#include <QSqlRecord>
|
||||
#include <QVariant>
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
//COMIC------------------------------------------------------------------------
|
||||
//-----------------------------------------------------------------------------
|
||||
Comic::Comic()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
Comic::Comic(qulonglong cparentId, QString cname, QString cpath, QString chash, QSqlDatabase & database)
|
||||
{
|
||||
parentId = cparentId;
|
||||
name = cname;
|
||||
path = cpath;
|
||||
|
||||
if(!info.load(chash,database))
|
||||
{
|
||||
info.hash = chash;
|
||||
info.coverPage = new int(1);
|
||||
_hasCover = false;
|
||||
}
|
||||
else
|
||||
_hasCover = true;
|
||||
}
|
||||
|
||||
QList<LibraryItem *> Comic::getComicsFromParent(qulonglong parentId, QSqlDatabase & db)
|
||||
{
|
||||
QList<LibraryItem *> list;
|
||||
|
||||
QSqlQuery selectQuery(db); //TODO check
|
||||
selectQuery.prepare("select c.id,c.parentId,c.fileName,c.path,ci.hash from comic c inner join comic_info ci on (c.comicInfoId = ci.id) where c.parentId = :parentId");
|
||||
selectQuery.bindValue(":parentId", parentId);
|
||||
selectQuery.exec();
|
||||
|
||||
Comic * currentItem;
|
||||
while (selectQuery.next())
|
||||
{
|
||||
QList<QVariant> data;
|
||||
QSqlRecord record = selectQuery.record();
|
||||
for(int i=0;i<record.count();i++)
|
||||
data << record.value(i);
|
||||
//TODO sort by sort indicator and name
|
||||
currentItem = new Comic();
|
||||
currentItem->id = record.value("id").toULongLong();
|
||||
currentItem->parentId = record.value(1).toULongLong();
|
||||
currentItem->name = record.value(2).toString();
|
||||
currentItem->path = record.value(3).toString();
|
||||
currentItem->info.load(record.value(4).toString(),db);
|
||||
int lessThan = 0;
|
||||
if(list.isEmpty())
|
||||
list.append(currentItem);
|
||||
else
|
||||
{
|
||||
Comic * last = static_cast<Comic *>(list.back());
|
||||
QString nameLast = last->name;
|
||||
QString nameCurrent = currentItem->name;
|
||||
QList<LibraryItem *>::iterator i;
|
||||
i = list.end();
|
||||
i--;
|
||||
while ((0 > (lessThan = nameCurrent.localeAwareCompare(nameLast))) && i != list.begin()) //se usa la misma ordenaci<63>n que en QDir
|
||||
{
|
||||
i--;
|
||||
nameLast = (*i)->name;
|
||||
}
|
||||
if(lessThan>0) //si se ha encontrado un elemento menor que current, se inserta justo despu<70>s
|
||||
list.insert(++i,currentItem);
|
||||
else
|
||||
list.insert(i,currentItem);
|
||||
|
||||
}
|
||||
}
|
||||
//selectQuery.finish();
|
||||
return list;
|
||||
}
|
||||
|
||||
bool Comic::load(qulonglong idc, QSqlDatabase & db)
|
||||
{
|
||||
|
||||
QSqlQuery selectQuery(db); //TODO check
|
||||
selectQuery.prepare("select c.id,c.parentId,c.fileName,c.path,ci.hash from comic c inner join comic_info ci on (c.comicInfoId = ci.id) where c.id = :id");
|
||||
selectQuery.bindValue(":id", idc);
|
||||
selectQuery.exec();
|
||||
this->id = idc;
|
||||
if(selectQuery.next())
|
||||
{
|
||||
QSqlRecord record = selectQuery.record();
|
||||
//id = record.value("id").toULongLong();
|
||||
parentId = record.value("parentId").toULongLong();
|
||||
name = record.value("name").toString();
|
||||
path = record.value("path").toString();
|
||||
info.load(record.value("hash").toString(),db);
|
||||
|
||||
return true;
|
||||
}
|
||||
//selectQuery.finish();
|
||||
return false;
|
||||
|
||||
}
|
||||
|
||||
qulonglong Comic::insert(QSqlDatabase & db)
|
||||
{
|
||||
//TODO comprobar si ya hay comic info con ese hash
|
||||
//TODO cambiar por info.insert(db)
|
||||
|
||||
if(!info.existOnDb)
|
||||
{
|
||||
QSqlQuery comicInfoInsert(db);
|
||||
comicInfoInsert.prepare("INSERT INTO comic_info (hash,numPages) "
|
||||
"VALUES (:hash,:numPages)");
|
||||
comicInfoInsert.bindValue(":hash", info.hash);
|
||||
comicInfoInsert.bindValue(":numPages", *info.numPages);
|
||||
comicInfoInsert.exec();
|
||||
info.id =comicInfoInsert.lastInsertId().toULongLong();
|
||||
_hasCover = false;
|
||||
}
|
||||
else
|
||||
_hasCover = true; //TODO check on disk...
|
||||
|
||||
QSqlQuery query(db);
|
||||
query.prepare("INSERT INTO comic (parentId, comicInfoId, fileName, path) "
|
||||
"VALUES (:parentId,:comicInfoId,:name, :path)");
|
||||
query.bindValue(":parentId", parentId);
|
||||
query.bindValue(":comicInfoId", info.id);
|
||||
query.bindValue(":name", name);
|
||||
query.bindValue(":path", path);
|
||||
query.exec();
|
||||
return query.lastInsertId().toULongLong();
|
||||
}
|
||||
|
||||
void Comic::update(QSqlDatabase & db)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
void Comic::removeFromDB(QSqlDatabase & db)
|
||||
{
|
||||
QSqlQuery query(db);
|
||||
query.prepare("DELETE FROM comic WHERE id = :id");
|
||||
query.bindValue(":id", id);
|
||||
query.exec();
|
||||
//query.finish();
|
||||
}
|
||||
|
||||
bool Comic::isDir()
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
//COMIC_INFO-------------------------------------------------------------------
|
||||
//-----------------------------------------------------------------------------
|
||||
ComicInfo::ComicInfo()
|
||||
:existOnDb(false),
|
||||
title(NULL),
|
||||
coverPage(NULL),
|
||||
numPages(NULL),
|
||||
number(NULL),
|
||||
isBis(NULL),
|
||||
count(NULL),
|
||||
volume(NULL),
|
||||
storyArc(NULL),
|
||||
arcNumber(NULL),
|
||||
arcCount(NULL),
|
||||
genere(NULL),
|
||||
writer(NULL),
|
||||
penciller(NULL),
|
||||
inker(NULL),
|
||||
colorist(NULL),
|
||||
letterer(NULL),
|
||||
coverArtist(NULL),
|
||||
date(NULL),
|
||||
publisher(NULL),
|
||||
format(NULL),
|
||||
color(NULL),
|
||||
ageRating(NULL),
|
||||
synopsis(NULL),
|
||||
characters(NULL),
|
||||
notes(NULL)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
ComicInfo::ComicInfo(const ComicInfo & comicInfo)
|
||||
: title(NULL),
|
||||
coverPage(NULL),
|
||||
numPages(NULL),
|
||||
number(NULL),
|
||||
isBis(NULL),
|
||||
count(NULL),
|
||||
volume(NULL),
|
||||
storyArc(NULL),
|
||||
arcNumber(NULL),
|
||||
arcCount(NULL),
|
||||
genere(NULL),
|
||||
writer(NULL),
|
||||
penciller(NULL),
|
||||
inker(NULL),
|
||||
colorist(NULL),
|
||||
letterer(NULL),
|
||||
coverArtist(NULL),
|
||||
date(NULL),
|
||||
publisher(NULL),
|
||||
format(NULL),
|
||||
color(NULL),
|
||||
ageRating(NULL),
|
||||
synopsis(NULL),
|
||||
characters(NULL),
|
||||
notes(NULL)
|
||||
{
|
||||
copyField(title,comicInfo.title);
|
||||
copyField(coverPage,comicInfo.coverPage);
|
||||
copyField(numPages,comicInfo.numPages);
|
||||
copyField(number,comicInfo.number);
|
||||
copyField(isBis,comicInfo.isBis);
|
||||
copyField(count,comicInfo.count);
|
||||
copyField(volume,comicInfo.volume);
|
||||
copyField(storyArc,comicInfo.storyArc);
|
||||
copyField(arcNumber,comicInfo.arcNumber);
|
||||
copyField(arcCount,comicInfo.arcCount);
|
||||
copyField(genere,comicInfo.genere);
|
||||
copyField(writer,comicInfo.writer);
|
||||
copyField(penciller,comicInfo.penciller);
|
||||
copyField(inker,comicInfo.inker);
|
||||
copyField(colorist,comicInfo.colorist);
|
||||
copyField(letterer,comicInfo.letterer);
|
||||
copyField(coverArtist,comicInfo.coverArtist);
|
||||
copyField(date,comicInfo.date);
|
||||
copyField(publisher,comicInfo.publisher);
|
||||
copyField(format,comicInfo.format);
|
||||
copyField(color,comicInfo.color);
|
||||
copyField(ageRating,comicInfo.ageRating);
|
||||
copyField(synopsis,comicInfo.synopsis);
|
||||
copyField(characters,comicInfo.characters);
|
||||
copyField(notes,comicInfo.notes);
|
||||
|
||||
hash = comicInfo.hash;
|
||||
id = comicInfo.id;
|
||||
existOnDb = comicInfo.existOnDb;
|
||||
read = comicInfo.read;
|
||||
edited = comicInfo.edited;
|
||||
|
||||
}
|
||||
|
||||
ComicInfo::~ComicInfo()
|
||||
{
|
||||
delete title;
|
||||
delete coverPage;
|
||||
delete numPages;
|
||||
delete number;
|
||||
delete isBis;
|
||||
delete count;
|
||||
delete volume;
|
||||
delete storyArc;
|
||||
delete arcNumber;
|
||||
delete arcCount;
|
||||
delete genere;
|
||||
delete writer;
|
||||
delete penciller;
|
||||
delete inker;
|
||||
delete colorist;
|
||||
delete letterer;
|
||||
delete coverArtist;
|
||||
delete date;
|
||||
delete publisher;
|
||||
delete format;
|
||||
delete color;
|
||||
delete ageRating;
|
||||
delete synopsis;
|
||||
delete characters;
|
||||
delete notes;
|
||||
}
|
||||
|
||||
void ComicInfo::setField(const QString & name, QString * & field, QSqlRecord & record)
|
||||
{
|
||||
if(!record.value(name).isNull())
|
||||
{
|
||||
field = new QString();
|
||||
*field = record.value(name).toString();
|
||||
}
|
||||
}
|
||||
|
||||
void ComicInfo::setField(const QString & name, int * & field, QSqlRecord & record)
|
||||
{
|
||||
if(!record.value(name).isNull())
|
||||
{
|
||||
field = new int;
|
||||
*field = record.value(name).toInt();
|
||||
}
|
||||
}
|
||||
|
||||
void ComicInfo::setField(const QString & name, bool * & field, QSqlRecord & record)
|
||||
{
|
||||
if(!record.value(name).isNull())
|
||||
{
|
||||
field = new bool;
|
||||
*field = record.value(name).toBool();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void ComicInfo::bindField(const QString & name, QString * field, QSqlQuery & query)
|
||||
{
|
||||
if(field != NULL)
|
||||
{
|
||||
query.bindValue(name,*field);
|
||||
}
|
||||
}
|
||||
|
||||
void ComicInfo::bindField(const QString & name, int * field, QSqlQuery & query)
|
||||
{
|
||||
if(field != NULL)
|
||||
{
|
||||
query.bindValue(name,*field);
|
||||
}
|
||||
}
|
||||
|
||||
void ComicInfo::bindField(const QString & name, bool * field, QSqlQuery & query)
|
||||
{
|
||||
if(field != NULL)
|
||||
{
|
||||
query.bindValue(name,*field);
|
||||
}
|
||||
}
|
||||
|
||||
void ComicInfo::setValue(QString * & field, const QString & value)
|
||||
{
|
||||
if(field == NULL)
|
||||
field = new QString;
|
||||
*field = value;
|
||||
}
|
||||
|
||||
void ComicInfo::setValue(int * & field, int value)
|
||||
{
|
||||
if(field == NULL)
|
||||
field = new int;
|
||||
*field = value;
|
||||
}
|
||||
|
||||
void ComicInfo::setValue(bool * & field, bool value)
|
||||
{
|
||||
if(field == NULL)
|
||||
field = new bool;
|
||||
*field = value;
|
||||
}
|
||||
|
||||
void ComicInfo::copyField(QString * & field, const QString * value)
|
||||
{
|
||||
if(value != NULL)
|
||||
field = new QString(*value);
|
||||
}
|
||||
|
||||
void ComicInfo::copyField(int * & field, int * value)
|
||||
{
|
||||
if(value != NULL)
|
||||
field = new int(*value);
|
||||
}
|
||||
|
||||
void ComicInfo::copyField(bool * & field, bool * value)
|
||||
{
|
||||
if(value != NULL)
|
||||
field = new bool(*value);
|
||||
}
|
||||
|
||||
bool ComicInfo::load(QString hash, QSqlDatabase & db)
|
||||
{
|
||||
QSqlQuery findComicInfo(db);
|
||||
findComicInfo.prepare("SELECT * FROM comic_info WHERE hash = :hash");
|
||||
findComicInfo.bindValue(":hash", hash);
|
||||
findComicInfo.exec();
|
||||
|
||||
|
||||
if(findComicInfo.next())
|
||||
{
|
||||
this->hash = hash;
|
||||
QSqlRecord record = findComicInfo.record();
|
||||
|
||||
hash = hash;
|
||||
id = record.value("id").toULongLong();
|
||||
read = record.value("read").toBool();
|
||||
edited = record.value("edited").toBool();
|
||||
|
||||
setField("title",title,record);
|
||||
setField("numPages",numPages,record);
|
||||
|
||||
setField("coverPage",coverPage,record);
|
||||
|
||||
setField("number",number,record);
|
||||
setField("isBis",isBis,record);
|
||||
setField("count",count,record);
|
||||
|
||||
setField("volume",volume,record);
|
||||
setField("storyArc",storyArc,record);
|
||||
setField("arcNumber",arcNumber,record);
|
||||
setField("arcCount",arcCount,record);
|
||||
|
||||
setField("genere",genere,record);
|
||||
|
||||
setField("writer",writer,record);
|
||||
setField("penciller",penciller,record);
|
||||
setField("inker",inker,record);
|
||||
setField("colorist",colorist,record);
|
||||
setField("letterer",letterer,record);
|
||||
setField("coverArtist",coverArtist,record);
|
||||
|
||||
setField("date",date,record);
|
||||
setField("publisher",publisher,record);
|
||||
setField("format",format,record);
|
||||
setField("color",color,record);
|
||||
setField("ageRating",ageRating,record);
|
||||
|
||||
setField("synopsis",synopsis,record);
|
||||
setField("characters",characters,record);
|
||||
setField("notes",notes,record);
|
||||
|
||||
return existOnDb = true;
|
||||
}
|
||||
|
||||
return existOnDb = false;
|
||||
}
|
||||
|
||||
qulonglong ComicInfo::insert(QSqlDatabase & db)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
void ComicInfo::removeFromDB(QSqlDatabase & db)
|
||||
{
|
||||
|
||||
}
|
||||
void ComicInfo::update(QSqlDatabase & db)
|
||||
{
|
||||
//db.open();
|
||||
QSqlQuery updateComicInfo(db);
|
||||
updateComicInfo.prepare("UPDATE comic_info SET "
|
||||
"title = :title,"
|
||||
|
||||
"coverPage = :coverPage,"
|
||||
"numPages = :numPages,"
|
||||
|
||||
"number = :number,"
|
||||
"isBis = :isBis,"
|
||||
"count = :count,"
|
||||
|
||||
"volume = :volume,"
|
||||
"storyArc = :storyArc,"
|
||||
"arcNumber = :arcNumber,"
|
||||
"arcCount = :arcCount,"
|
||||
|
||||
"genere = :genere,"
|
||||
|
||||
"writer = :writer,"
|
||||
"penciller = :penciller,"
|
||||
"inker = :inker,"
|
||||
"colorist = :colorist,"
|
||||
"letterer = :letterer,"
|
||||
"coverArtist = :coverArtist,"
|
||||
|
||||
"date = :date,"
|
||||
"publisher = :publisher,"
|
||||
"format = :format,"
|
||||
"color = :color,"
|
||||
"ageRating = :ageRating,"
|
||||
|
||||
"synopsis = :synopsis,"
|
||||
"characters = :characters,"
|
||||
"notes = :notes,"
|
||||
|
||||
"read = :read,"
|
||||
"edited = :edited"
|
||||
|
||||
" WHERE id = :id ");
|
||||
bindField(":title",title,updateComicInfo);
|
||||
|
||||
bindField(":coverPage",coverPage,updateComicInfo);
|
||||
bindField(":numPages",numPages,updateComicInfo);
|
||||
|
||||
bindField(":number",number,updateComicInfo);
|
||||
bindField(":isBis",isBis,updateComicInfo);
|
||||
bindField(":count",count,updateComicInfo);
|
||||
|
||||
bindField(":volume",volume,updateComicInfo);
|
||||
bindField(":storyArc",storyArc,updateComicInfo);
|
||||
bindField(":arcNumber",arcNumber,updateComicInfo);
|
||||
bindField(":arcCount",arcCount,updateComicInfo);
|
||||
|
||||
bindField(":genere",genere,updateComicInfo);
|
||||
|
||||
bindField(":writer",writer,updateComicInfo);
|
||||
bindField(":penciller",penciller,updateComicInfo);
|
||||
bindField(":inker",inker,updateComicInfo);
|
||||
bindField(":colorist",colorist,updateComicInfo);
|
||||
bindField(":letterer",letterer,updateComicInfo);
|
||||
bindField(":coverArtist",coverArtist,updateComicInfo);
|
||||
|
||||
bindField(":date",date,updateComicInfo);
|
||||
bindField(":publisher",publisher,updateComicInfo);
|
||||
bindField(":format",format,updateComicInfo);
|
||||
bindField(":color",color,updateComicInfo);
|
||||
bindField(":ageRating",ageRating,updateComicInfo);
|
||||
|
||||
bindField(":synopsis",synopsis,updateComicInfo);
|
||||
bindField(":characters",characters,updateComicInfo);
|
||||
bindField(":notes",notes,updateComicInfo);
|
||||
|
||||
updateComicInfo.bindValue(":read", read?1:0);
|
||||
updateComicInfo.bindValue(":id", id);
|
||||
updateComicInfo.bindValue(":edited", edited?1:0);
|
||||
updateComicInfo.exec();
|
||||
//updateComicInfo.finish();
|
||||
//db.close();
|
||||
}
|
||||
|
||||
void ComicInfo::updateRead(QSqlDatabase & db)
|
||||
{
|
||||
QSqlQuery findComicInfo(db);
|
||||
findComicInfo.prepare("UPDATE comic_info SET "
|
||||
"read = :read"
|
||||
" WHERE id = :id ");
|
||||
|
||||
findComicInfo.bindValue(":read", read?1:0);
|
||||
findComicInfo.bindValue(":id", id);
|
||||
findComicInfo.exec();
|
||||
//findComicInfo.finish();
|
||||
}
|
||||
|
||||
|
||||
//set fields
|
||||
void ComicInfo::setTitle(QString value)
|
||||
{
|
||||
setValue(title,value);
|
||||
}
|
||||
|
||||
void ComicInfo::setCoverPage(int value)
|
||||
{
|
||||
setValue(coverPage,value);
|
||||
}
|
||||
void ComicInfo::setNumPages(int value)
|
||||
{
|
||||
setValue(numPages,value);
|
||||
}
|
||||
|
||||
void ComicInfo::setNumber(int value)
|
||||
{
|
||||
setValue(number,value);
|
||||
}
|
||||
|
||||
void ComicInfo::setIsBis(bool value)
|
||||
{
|
||||
setValue(isBis,value);
|
||||
}
|
||||
|
||||
void ComicInfo::setCount(int value)
|
||||
{
|
||||
setValue(count,value);
|
||||
}
|
||||
|
||||
void ComicInfo::setVolume(QString value)
|
||||
{
|
||||
setValue(volume,value);
|
||||
}
|
||||
|
||||
void ComicInfo::setStoryArc(QString value)
|
||||
{
|
||||
setValue(storyArc,value);
|
||||
}
|
||||
|
||||
void ComicInfo::setArcNumber(int value)
|
||||
{
|
||||
setValue(arcNumber,value);
|
||||
}
|
||||
|
||||
void ComicInfo::setArcCount(int value)
|
||||
{
|
||||
setValue(arcCount,value);
|
||||
}
|
||||
|
||||
void ComicInfo::setGenere(QString value)
|
||||
{
|
||||
setValue(genere,value);
|
||||
}
|
||||
|
||||
void ComicInfo::setWriter(QString value)
|
||||
{
|
||||
setValue(writer,value);
|
||||
}
|
||||
|
||||
void ComicInfo::setPenciller(QString value)
|
||||
{
|
||||
setValue(penciller,value);
|
||||
}
|
||||
|
||||
void ComicInfo::setInker(QString value)
|
||||
{
|
||||
setValue(inker,value);
|
||||
}
|
||||
|
||||
void ComicInfo::setColorist(QString value)
|
||||
{
|
||||
setValue(colorist,value);
|
||||
}
|
||||
|
||||
void ComicInfo::setLetterer(QString value)
|
||||
{
|
||||
setValue(letterer,value);
|
||||
}
|
||||
|
||||
void ComicInfo::setCoverArtist(QString value)
|
||||
{
|
||||
setValue(coverArtist,value);
|
||||
}
|
||||
|
||||
void ComicInfo::setDate(QString value)
|
||||
{
|
||||
setValue(date,value);
|
||||
}
|
||||
|
||||
void ComicInfo::setPublisher(QString value)
|
||||
{
|
||||
setValue(publisher,value);
|
||||
}
|
||||
|
||||
void ComicInfo::setFormat(QString value)
|
||||
{
|
||||
setValue(format,value);
|
||||
}
|
||||
|
||||
void ComicInfo::setColor(bool value)
|
||||
{
|
||||
setValue(color,value);
|
||||
}
|
||||
|
||||
void ComicInfo::setAgeRating(QString value)
|
||||
{
|
||||
setValue(ageRating,value);
|
||||
}
|
||||
|
||||
void ComicInfo::setSynopsis(QString value)
|
||||
{
|
||||
setValue(synopsis,value);
|
||||
}
|
||||
|
||||
void ComicInfo::setCharacters(QString value)
|
||||
{
|
||||
setValue(characters,value);
|
||||
}
|
||||
|
||||
void ComicInfo::setNotes(QString value)
|
||||
{
|
||||
setValue(notes,value);
|
||||
}
|
||||
|
||||
QPixmap ComicInfo::getCover(const QString & basePath)
|
||||
{
|
||||
if(cover.isNull())
|
||||
{
|
||||
cover.load(basePath + "/.yacreaderlibrary/covers/" + hash + ".jpg");
|
||||
}
|
||||
QPixmap c;
|
||||
c.convertFromImage(cover);
|
||||
return c;
|
||||
}
|
139
YACReaderLibrary/db/comic.h
Normal file
139
YACReaderLibrary/db/comic.h
Normal file
@ -0,0 +1,139 @@
|
||||
#ifndef __COMIC_H
|
||||
#define __COMIC_H
|
||||
|
||||
#include "library_item.h"
|
||||
#include <QSqlDatabase>
|
||||
#include <QList>
|
||||
#include <QPixmap>
|
||||
#include <QImage>
|
||||
|
||||
class ComicInfo
|
||||
{
|
||||
public:
|
||||
ComicInfo();
|
||||
ComicInfo(const ComicInfo & comicInfo);
|
||||
~ComicInfo();
|
||||
|
||||
bool load(QString hash, QSqlDatabase & db);
|
||||
qulonglong insert(QSqlDatabase & db);
|
||||
void removeFromDB(QSqlDatabase & db);
|
||||
void update(QSqlDatabase & db);
|
||||
void updateRead(QSqlDatabase & db);
|
||||
|
||||
qulonglong id;
|
||||
bool read;
|
||||
bool edited;
|
||||
QString hash;
|
||||
bool existOnDb;
|
||||
|
||||
QString * title;
|
||||
|
||||
int * coverPage;
|
||||
int * numPages;
|
||||
|
||||
int * number;
|
||||
bool * isBis;
|
||||
int * count;
|
||||
|
||||
QString * volume;
|
||||
QString * storyArc;
|
||||
int * arcNumber;
|
||||
int * arcCount;
|
||||
|
||||
QString * genere;
|
||||
|
||||
QString * writer;
|
||||
QString * penciller;
|
||||
QString * inker;
|
||||
QString * colorist;
|
||||
QString * letterer;
|
||||
QString * coverArtist;
|
||||
|
||||
QString * date;
|
||||
QString * publisher;
|
||||
QString * format;
|
||||
bool * color;
|
||||
QString * ageRating;
|
||||
|
||||
QString * synopsis;
|
||||
QString * characters;
|
||||
QString * notes;
|
||||
|
||||
QImage cover;
|
||||
|
||||
void setTitle(QString value);
|
||||
|
||||
void setCoverPage(int value);
|
||||
void setNumPages(int value);
|
||||
|
||||
void setNumber(int value);
|
||||
void setIsBis(bool value);
|
||||
void setCount(int value);
|
||||
|
||||
void setVolume(QString value);
|
||||
void setStoryArc(QString value);
|
||||
void setArcNumber(int value);
|
||||
void setArcCount(int value);
|
||||
|
||||
void setGenere(QString value);
|
||||
|
||||
void setWriter(QString value);
|
||||
void setPenciller(QString value);
|
||||
void setInker(QString value);
|
||||
void setColorist(QString value);
|
||||
void setLetterer(QString value);
|
||||
void setCoverArtist(QString value);
|
||||
|
||||
void setDate(QString value);
|
||||
void setPublisher(QString value);
|
||||
void setFormat(QString value);
|
||||
void setColor(bool value);
|
||||
void setAgeRating(QString value);
|
||||
|
||||
void setSynopsis(QString value);
|
||||
void setCharacters(QString value);
|
||||
void setNotes(QString value);
|
||||
|
||||
QPixmap getCover(const QString & basePath);
|
||||
|
||||
private:
|
||||
void setField(const QString & name, QString * & field, QSqlRecord & record);
|
||||
void setField(const QString & name, int * & field, QSqlRecord & record);
|
||||
void setField(const QString & name, bool * & field, QSqlRecord & record);
|
||||
|
||||
void bindField(const QString & name, QString * field, QSqlQuery & query);
|
||||
void bindField(const QString & name, int * field, QSqlQuery & query);
|
||||
void bindField(const QString & name, bool * field, QSqlQuery & query);
|
||||
|
||||
void setValue(QString * & field, const QString & value);
|
||||
void setValue(int * & field, int value);
|
||||
void setValue(bool * & field, bool value);
|
||||
|
||||
void copyField(QString * & field, const QString * value);
|
||||
void copyField(int * & field, int * value);
|
||||
void copyField(bool * & field, bool * value);
|
||||
};
|
||||
|
||||
class Comic : public LibraryItem
|
||||
{
|
||||
private:
|
||||
bool _hasCover;
|
||||
public:
|
||||
Comic();
|
||||
Comic(qulonglong cparentId, QString cname, QString cpath, QString chash, QSqlDatabase & database);
|
||||
//Comic(QString fn, QString fp):name(fn),path(fp),knownParent(false), knownId(false){};
|
||||
|
||||
static QList<LibraryItem *> getComicsFromParent(qulonglong parentId, QSqlDatabase & db);
|
||||
bool isDir();
|
||||
|
||||
bool load(qulonglong id, QSqlDatabase & db);
|
||||
qulonglong insert(QSqlDatabase & db);
|
||||
void removeFromDB(QSqlDatabase & db);
|
||||
void update(QSqlDatabase & db);
|
||||
bool hasCover() {return _hasCover;};
|
||||
|
||||
ComicInfo info;
|
||||
};
|
||||
|
||||
|
||||
#endif
|
579
YACReaderLibrary/db/data_base_management.cpp
Normal file
579
YACReaderLibrary/db/data_base_management.cpp
Normal file
@ -0,0 +1,579 @@
|
||||
#include "data_base_management.h"
|
||||
|
||||
#include <QtCore>
|
||||
#include "library_creator.h"
|
||||
#include "check_new_version.h"
|
||||
|
||||
static QString fields = "title ,"
|
||||
|
||||
"coverPage,"
|
||||
"numPages,"
|
||||
|
||||
"number,"
|
||||
"isBis,"
|
||||
"count,"
|
||||
|
||||
"volume,"
|
||||
"storyArc,"
|
||||
"arcNumber,"
|
||||
"arcCount,"
|
||||
|
||||
"genere,"
|
||||
|
||||
"writer,"
|
||||
"penciller,"
|
||||
"inker,"
|
||||
"colorist,"
|
||||
"letterer,"
|
||||
"coverArtist,"
|
||||
|
||||
"date,"
|
||||
"publisher,"
|
||||
"format,"
|
||||
"color,"
|
||||
"ageRating,"
|
||||
|
||||
"synopsis,"
|
||||
"characters,"
|
||||
"notes,"
|
||||
|
||||
"hash";
|
||||
|
||||
DataBaseManagement::DataBaseManagement()
|
||||
:QObject(),dataBasesList()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
/*TreeModel * DataBaseManagement::newTreeModel(QString path)
|
||||
{
|
||||
//la consulta se ejecuta...
|
||||
QSqlQuery selectQuery(loadDatabase(path));
|
||||
selectQuery.setForwardOnly(true);
|
||||
selectQuery.exec("select * from folder order by parentId,name");
|
||||
//selectQuery.finish();
|
||||
return new TreeModel(selectQuery);
|
||||
}*/
|
||||
|
||||
QSqlDatabase DataBaseManagement::createDatabase(QString name, QString path)
|
||||
{
|
||||
return createDatabase(QDir::cleanPath(path) + "/" + name + ".ydb");
|
||||
}
|
||||
|
||||
QSqlDatabase DataBaseManagement::createDatabase(QString dest)
|
||||
{
|
||||
QSqlDatabase db = QSqlDatabase::addDatabase("QSQLITE",dest);
|
||||
db.setDatabaseName(dest);
|
||||
if (!db.open())
|
||||
qDebug() << db.lastError();
|
||||
else {
|
||||
qDebug() << db.tables();
|
||||
}
|
||||
|
||||
{
|
||||
QSqlQuery pragma("PRAGMA foreign_keys = ON",db);
|
||||
//pragma.finish();
|
||||
DataBaseManagement::createTables(db);
|
||||
|
||||
QSqlQuery query("INSERT INTO folder (parentId, name, path) "
|
||||
"VALUES (1,'root', '/')",db);
|
||||
}
|
||||
//query.finish();
|
||||
//db.close();
|
||||
|
||||
return db;
|
||||
}
|
||||
|
||||
QSqlDatabase DataBaseManagement::loadDatabase(QString path)
|
||||
{
|
||||
//TODO check path
|
||||
QSqlDatabase db = QSqlDatabase::addDatabase("QSQLITE",path);
|
||||
db.setDatabaseName(path+"/library.ydb");
|
||||
if (!db.open()) {
|
||||
//se devuelve una base de datos vac<61>a e inv<6E>lida
|
||||
|
||||
return QSqlDatabase();
|
||||
}
|
||||
QSqlQuery pragma("PRAGMA foreign_keys = ON",db);
|
||||
//pragma.finish();
|
||||
//devuelve la base de datos
|
||||
return db;
|
||||
}
|
||||
|
||||
QSqlDatabase DataBaseManagement::loadDatabaseFromFile(QString filePath)
|
||||
{
|
||||
//TODO check path
|
||||
QSqlDatabase db = QSqlDatabase::addDatabase("QSQLITE",filePath);
|
||||
db.setDatabaseName(filePath);
|
||||
if (!db.open()) {
|
||||
//se devuelve una base de datos vac<61>a e inv<6E>lida
|
||||
|
||||
return QSqlDatabase();
|
||||
}
|
||||
{
|
||||
QSqlQuery pragma("PRAGMA foreign_keys = ON",db);
|
||||
}
|
||||
//pragma.finish();
|
||||
//devuelve la base de datos
|
||||
return db;
|
||||
}
|
||||
|
||||
bool DataBaseManagement::createTables(QSqlDatabase & database)
|
||||
{
|
||||
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, FOREIGN KEY(parentId) REFERENCES folder(id) ON DELETE CASCADE)");
|
||||
success = success && queryFolder.exec();
|
||||
//queryFolder.finish();
|
||||
//COMIC INFO (representa la informaci<63>n de un c<>mic, cada c<>mic tendr<64> un id<69>ntificador <20>nico formado por un hash sha1'de los primeros 512kb' + su tama<6D>o en bytes)
|
||||
QSqlQuery queryComicInfo(database);
|
||||
queryComicInfo.prepare("CREATE TABLE comic_info ("
|
||||
"id INTEGER PRIMARY KEY,"
|
||||
"title TEXT,"
|
||||
|
||||
"coverPage INTEGER DEFAULT 1,"
|
||||
"numPages INTEGER,"
|
||||
|
||||
"number INTEGER,"
|
||||
"isBis BOOLEAN,"
|
||||
"count INTEGER,"
|
||||
|
||||
"volume TEXT,"
|
||||
"storyArc TEXT,"
|
||||
"arcNumber INTEGER,"
|
||||
"arcCount INTEGER,"
|
||||
|
||||
"genere TEXT,"
|
||||
|
||||
"writer TEXT,"
|
||||
"penciller TEXT,"
|
||||
"inker TEXT,"
|
||||
"colorist TEXT,"
|
||||
"letterer TEXT,"
|
||||
"coverArtist TEXT,"
|
||||
|
||||
"date TEXT," //dd/mm/yyyy --> se mostrar<61> en 3 campos diferentes
|
||||
"publisher TEXT,"
|
||||
"format TEXT,"
|
||||
"color BOOLEAN,"
|
||||
"ageRating BOOLEAN,"
|
||||
|
||||
"synopsis TEXT,"
|
||||
"characters TEXT,"
|
||||
"notes TEXT,"
|
||||
|
||||
"hash TEXT UNIQUE NOT NULL,"
|
||||
"edited BOOLEAN DEFAULT 0,"
|
||||
"read BOOLEAN DEFAULT 0)");
|
||||
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();
|
||||
|
||||
QSqlQuery query("INSERT INTO db_info (version) "
|
||||
"VALUES ('"VERSION"')",database);
|
||||
//query.finish();
|
||||
}
|
||||
|
||||
return success;
|
||||
}
|
||||
#include <qmessagebox.h>
|
||||
void DataBaseManagement::exportComicsInfo(QString source, QString dest)
|
||||
{
|
||||
//QSqlDatabase sourceDB = loadDatabase(source);
|
||||
QSqlDatabase destDB = loadDatabaseFromFile(dest);
|
||||
//sourceDB.open();
|
||||
{
|
||||
QSqlQuery attach(destDB);
|
||||
attach.prepare("ATTACH DATABASE '"+QDir().toNativeSeparators(dest) +"' AS dest;");
|
||||
//attach.bindValue(":dest",QDir().toNativeSeparators(dest));
|
||||
attach.exec();
|
||||
//attach.finish();
|
||||
|
||||
QSqlQuery attach2(destDB);
|
||||
attach2.prepare("ATTACH DATABASE '"+QDir().toNativeSeparators(source) +"' AS source;");
|
||||
attach2.exec();
|
||||
//attach2.finish();
|
||||
|
||||
//sourceDB.close();
|
||||
QSqlQuery queryDBInfo(destDB);
|
||||
queryDBInfo.prepare("CREATE TABLE dest.db_info (version TEXT NOT NULL)");
|
||||
queryDBInfo.exec();
|
||||
//queryDBInfo.finish();
|
||||
|
||||
/*QSqlQuery queryComicsInfo(sourceDB);
|
||||
queryComicsInfo.prepare("CREATE TABLE dest.comic_info (id INTEGER PRIMARY KEY, hash TEXT NOT NULL, edited BOOLEAN DEFAULT FALSE, title TEXT, read BOOLEAN)");
|
||||
queryComicsInfo.exec();*/
|
||||
|
||||
QSqlQuery query("INSERT INTO dest.db_info (version) "
|
||||
"VALUES ('5.0.0')",destDB);
|
||||
//query.finish();
|
||||
|
||||
QSqlQuery exportData(destDB);
|
||||
exportData.prepare("create table dest.comic_info as select " + fields +
|
||||
" from source.comic_info where source.comic_info.edited = 1");
|
||||
exportData.exec();
|
||||
//exportData.finish();
|
||||
}
|
||||
|
||||
//sourceDB.close();
|
||||
destDB.close();
|
||||
QSqlDatabase::removeDatabase(dest);
|
||||
|
||||
}
|
||||
|
||||
bool DataBaseManagement::importComicsInfo(QString source, QString dest)
|
||||
{
|
||||
QString error;
|
||||
QString driver;
|
||||
QStringList hashes;
|
||||
|
||||
bool b = false;
|
||||
|
||||
QSqlDatabase sourceDB = loadDatabaseFromFile(source);
|
||||
QSqlDatabase destDB = loadDatabaseFromFile(dest);
|
||||
|
||||
{
|
||||
QSqlQuery pragma("PRAGMA synchronous=OFF",destDB);
|
||||
|
||||
QSqlQuery update(destDB);
|
||||
update.prepare("UPDATE comic_info SET "
|
||||
"title = :title,"
|
||||
|
||||
"coverPage = :coverPage,"
|
||||
"numPages = :numPages,"
|
||||
|
||||
"number = :number,"
|
||||
"isBis = :isBis,"
|
||||
"count = :count,"
|
||||
|
||||
"volume = :volume,"
|
||||
"storyArc = :storyArc,"
|
||||
"arcNumber = :arcNumber,"
|
||||
"arcCount = :arcCount,"
|
||||
|
||||
"genere = :genere,"
|
||||
|
||||
"writer = :writer,"
|
||||
"penciller = :penciller,"
|
||||
"inker = :inker,"
|
||||
"colorist = :colorist,"
|
||||
"letterer = :letterer,"
|
||||
"coverArtist = :coverArtist,"
|
||||
|
||||
"date = :date,"
|
||||
"publisher = :publisher,"
|
||||
"format = :format,"
|
||||
"color = :color,"
|
||||
"ageRating = :ageRating,"
|
||||
|
||||
"synopsis = :synopsis,"
|
||||
"characters = :characters,"
|
||||
"notes = :notes,"
|
||||
|
||||
"edited = :edited"
|
||||
|
||||
" WHERE hash = :hash ");
|
||||
|
||||
QSqlQuery insert(destDB);
|
||||
insert.prepare("INSERT INTO comic_info "
|
||||
"(title,"
|
||||
"coverPage,"
|
||||
"numPages,"
|
||||
"number,"
|
||||
"isBis,"
|
||||
"count,"
|
||||
"volume,"
|
||||
"storyArc,"
|
||||
"arcNumber,"
|
||||
"arcCount,"
|
||||
"genere,"
|
||||
"writer,"
|
||||
"penciller,"
|
||||
"inker,"
|
||||
"colorist,"
|
||||
"letterer,"
|
||||
"coverArtist,"
|
||||
"date,"
|
||||
"publisher,"
|
||||
"format,"
|
||||
"color,"
|
||||
"ageRating,"
|
||||
"synopsis,"
|
||||
"characters,"
|
||||
"notes,"
|
||||
"read,"
|
||||
"edited,"
|
||||
"hash)"
|
||||
|
||||
"VALUES (:title,"
|
||||
":coverPage,"
|
||||
":numPages,"
|
||||
":number,"
|
||||
":isBis,"
|
||||
":count,"
|
||||
|
||||
":volume,"
|
||||
":storyArc,"
|
||||
":arcNumber,"
|
||||
":arcCount,"
|
||||
|
||||
":genere,"
|
||||
|
||||
":writer,"
|
||||
":penciller,"
|
||||
":inker,"
|
||||
":colorist,"
|
||||
":letterer,"
|
||||
":coverArtist,"
|
||||
|
||||
":date,"
|
||||
":publisher,"
|
||||
":format,"
|
||||
":color,"
|
||||
":ageRating,"
|
||||
|
||||
":synopsis,"
|
||||
":characters,"
|
||||
":notes,"
|
||||
|
||||
":read,"
|
||||
":edited,"
|
||||
|
||||
":hash )");
|
||||
QSqlQuery newInfo(sourceDB);
|
||||
newInfo.prepare("SELECT * FROM comic_info");
|
||||
newInfo.exec();
|
||||
destDB.transaction();
|
||||
int cp;
|
||||
while (newInfo.next()) //cada tupla deber<65> ser insertada o actualizada
|
||||
{
|
||||
QSqlRecord record = newInfo.record();
|
||||
cp = record.value("coverPage").toInt();
|
||||
if(cp>1)
|
||||
{
|
||||
QSqlQuery checkCoverPage(destDB);
|
||||
checkCoverPage.prepare("SELECT coverPage FROM comic_info where hash = :hash");
|
||||
checkCoverPage.bindValue(":hash",record.value("hash").toString());
|
||||
checkCoverPage.exec();
|
||||
bool extract = false;
|
||||
if(checkCoverPage.next())
|
||||
{
|
||||
extract = checkCoverPage.record().value("coverPage").toInt() != cp;
|
||||
}
|
||||
if(extract)
|
||||
hashes.append(record.value("hash").toString());
|
||||
}
|
||||
|
||||
bindValuesFromRecord(record,update);
|
||||
|
||||
update.bindValue(":edited",1);
|
||||
|
||||
|
||||
update.exec();
|
||||
|
||||
if(update.numRowsAffected() == 0)
|
||||
{
|
||||
|
||||
bindValuesFromRecord(record,insert);
|
||||
insert.bindValue(":edited",1);
|
||||
insert.bindValue(":read",0);
|
||||
|
||||
insert.exec();
|
||||
|
||||
QString error1 = insert.lastError().databaseText();
|
||||
QString error2 = insert.lastError().driverText();
|
||||
|
||||
QMessageBox::critical(NULL,"db",error1);
|
||||
QMessageBox::critical(NULL,"driver",error2);
|
||||
}
|
||||
update.finish();
|
||||
insert.finish();
|
||||
}
|
||||
}
|
||||
|
||||
destDB.commit();
|
||||
QString hash;
|
||||
foreach(hash, hashes)
|
||||
{
|
||||
QSqlQuery getComic(destDB);
|
||||
getComic.prepare("SELECT c.path,ci.coverPage FROM comic c INNER JOIN comic_info ci ON (c.comicInfoId = ci.id) where ci.hash = :hash");
|
||||
getComic.bindValue(":hash",hash);
|
||||
getComic.exec();
|
||||
if(getComic.next())
|
||||
{
|
||||
QString basePath = QString(dest).remove("/.yacreaderlibrary/library.ydb");
|
||||
QString path = basePath + getComic.record().value("path").toString();
|
||||
int coverPage = getComic.record().value("coverPage").toInt();
|
||||
ThumbnailCreator tc(path,basePath+"/.yacreaderlibrary/covers/"+hash+".jpg",coverPage);
|
||||
tc.create();
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
destDB.close();
|
||||
sourceDB.close();
|
||||
QSqlDatabase::removeDatabase(source);
|
||||
QSqlDatabase::removeDatabase(dest);
|
||||
return b;
|
||||
|
||||
}
|
||||
|
||||
void DataBaseManagement::bindValuesFromRecord(const QSqlRecord & record, QSqlQuery & query)
|
||||
{
|
||||
bindString("title",record,query);
|
||||
|
||||
bindInt("coverPage",record,query);
|
||||
bindInt("numPages",record,query);
|
||||
|
||||
bindInt("number",record,query);
|
||||
bindInt("isBis",record,query);
|
||||
bindInt("count",record,query);
|
||||
|
||||
bindString("volume",record,query);
|
||||
bindString("storyArc",record,query);
|
||||
bindInt("arcNumber",record,query);
|
||||
bindInt("arcCount",record,query);
|
||||
|
||||
bindString("genere",record,query);
|
||||
|
||||
bindString("writer",record,query);
|
||||
bindString("penciller",record,query);
|
||||
bindString("inker",record,query);
|
||||
bindString("colorist",record,query);
|
||||
bindString("letterer",record,query);
|
||||
bindString("coverArtist",record,query);
|
||||
|
||||
bindString("date",record,query);
|
||||
bindString("publisher",record,query);
|
||||
bindString("format",record,query);
|
||||
bindInt("color",record,query);
|
||||
bindString("ageRating",record,query);
|
||||
|
||||
bindString("synopsis",record,query);
|
||||
bindString("characters",record,query);
|
||||
bindString("notes",record,query);
|
||||
|
||||
bindString("hash",record,query);
|
||||
}
|
||||
|
||||
void DataBaseManagement::bindString(const QString & name, const QSqlRecord & record, QSqlQuery & query)
|
||||
{
|
||||
if(!record.value(name).isNull())
|
||||
{
|
||||
query.bindValue(":"+name,record.value(name).toString());
|
||||
}
|
||||
}
|
||||
void DataBaseManagement::bindInt(const QString & name, const QSqlRecord & record, QSqlQuery & query)
|
||||
{
|
||||
if(!record.value(name).isNull())
|
||||
{
|
||||
query.bindValue(":"+name,record.value(name).toInt());
|
||||
}
|
||||
}
|
||||
|
||||
QString DataBaseManagement::checkValidDB(const QString & fullPath)
|
||||
{
|
||||
QSqlDatabase db = loadDatabaseFromFile(fullPath);
|
||||
if(db.isValid() && db.isOpen())
|
||||
{
|
||||
QSqlQuery version(db);
|
||||
version.prepare("SELECT * FROM db_info");
|
||||
version.exec();
|
||||
if(version.next())
|
||||
{
|
||||
db.close();
|
||||
QSqlDatabase::removeDatabase(fullPath);
|
||||
return version.record().value("version").toString();
|
||||
}
|
||||
else
|
||||
{
|
||||
db.close();
|
||||
QSqlDatabase::removeDatabase(fullPath);
|
||||
return "";
|
||||
}
|
||||
|
||||
}
|
||||
else
|
||||
{
|
||||
db.close();
|
||||
QSqlDatabase::removeDatabase(fullPath);
|
||||
return "";
|
||||
}
|
||||
}
|
||||
|
||||
int DataBaseManagement::compareVersions(const QString & v1, const QString v2)
|
||||
{
|
||||
QStringList v1l = v1.split('.');
|
||||
QStringList v2l = v2.split('.');
|
||||
QList<int> v1il;
|
||||
QList<int> v2il;
|
||||
|
||||
foreach(QString s, v1l)
|
||||
v1il.append(s.toInt());
|
||||
|
||||
foreach(QString s,v2l)
|
||||
v2il.append(s.toInt());
|
||||
|
||||
for(int i=0;i<qMin(v1il.length(),v2il.length());i++)
|
||||
{
|
||||
if(v1il[i]<v2il[i])
|
||||
return -1;
|
||||
if(v1il[i]>v2il[i])
|
||||
return 1;
|
||||
}
|
||||
|
||||
if(v1il.length() < v2il.length())
|
||||
return -1;
|
||||
if(v1il.length() == v2il.length())
|
||||
return 0;
|
||||
if(v1il.length() > v2il.length())
|
||||
return 1;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
//COMICS_INFO_EXPORTER
|
||||
ComicsInfoExporter::ComicsInfoExporter()
|
||||
:QThread()
|
||||
{
|
||||
}
|
||||
|
||||
void ComicsInfoExporter::exportComicsInfo(QSqlDatabase & source, QSqlDatabase & dest)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
void ComicsInfoExporter::run()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
|
||||
//COMICS_INFO_IMPORTER
|
||||
ComicsInfoImporter::ComicsInfoImporter()
|
||||
:QThread()
|
||||
{
|
||||
}
|
||||
|
||||
void ComicsInfoImporter::importComicsInfo(QSqlDatabase & source, QSqlDatabase & dest)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
void ComicsInfoImporter::run()
|
||||
{
|
||||
|
||||
}
|
58
YACReaderLibrary/db/data_base_management.h
Normal file
58
YACReaderLibrary/db/data_base_management.h
Normal file
@ -0,0 +1,58 @@
|
||||
#ifndef __DATA_BASE_MANAGEMENT_H
|
||||
#define __DATA_BASE_MANAGEMENT_H
|
||||
|
||||
#include <QtCore>
|
||||
#include <QtSql>
|
||||
#include <QSqlDatabase>
|
||||
|
||||
#include "treemodel.h"
|
||||
|
||||
class ComicsInfoExporter : public QThread
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
ComicsInfoExporter();
|
||||
void exportComicsInfo(QSqlDatabase & source, QSqlDatabase & dest);
|
||||
private:
|
||||
void run();
|
||||
};
|
||||
|
||||
class ComicsInfoImporter : public QThread
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
ComicsInfoImporter();
|
||||
void importComicsInfo(QSqlDatabase & source, QSqlDatabase & dest);
|
||||
private:
|
||||
void run();
|
||||
|
||||
};
|
||||
|
||||
class DataBaseManagement : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
private:
|
||||
QList<QString> dataBasesList;
|
||||
static void bindString(const QString & name, const QSqlRecord & record, QSqlQuery & query);
|
||||
static void bindInt(const QString & name, const QSqlRecord & record, QSqlQuery & query);
|
||||
static void bindValuesFromRecord(const QSqlRecord & record, QSqlQuery & query);
|
||||
|
||||
public:
|
||||
DataBaseManagement();
|
||||
//TreeModel * newTreeModel(QString path);
|
||||
//crea una base de datos y todas sus tablas
|
||||
static QSqlDatabase createDatabase(QString name, QString path);
|
||||
static QSqlDatabase createDatabase(QString dest);
|
||||
//carga una base de datos desde la ruta path
|
||||
static QSqlDatabase loadDatabase(QString path);
|
||||
static QSqlDatabase loadDatabaseFromFile(QString path);
|
||||
static bool createTables(QSqlDatabase & database);
|
||||
|
||||
static void exportComicsInfo(QString source, QString dest);
|
||||
static bool importComicsInfo(QString source, QString dest);
|
||||
|
||||
static QString checkValidDB(const QString & fullPath); //retorna "" si la DB es inv<6E>lida <20> la versi<73>n si es v<>lida.
|
||||
static int compareVersions(const QString & v1, const QString v2); //retorna <0 si v1 < v2, 0 si v1 = v2 y >0 si v1 > v2
|
||||
};
|
||||
|
||||
#endif
|
71
YACReaderLibrary/db/folder.cpp
Normal file
71
YACReaderLibrary/db/folder.cpp
Normal file
@ -0,0 +1,71 @@
|
||||
|
||||
#include "folder.h"
|
||||
#include <QSqlQuery>
|
||||
#include <QSqlRecord>
|
||||
#include <QVariant>
|
||||
|
||||
qulonglong Folder::insert(QSqlDatabase & db)
|
||||
{
|
||||
QSqlQuery query(db);
|
||||
query.prepare("INSERT INTO folder (parentId, name, path) "
|
||||
"VALUES (:parentId, :name, :path)");
|
||||
query.bindValue(":parentId", parentId);
|
||||
query.bindValue(":name", name);
|
||||
query.bindValue(":path", path);
|
||||
query.exec();
|
||||
return query.lastInsertId().toULongLong();
|
||||
}
|
||||
|
||||
QList<LibraryItem *> Folder::getFoldersFromParent(qulonglong parentId, QSqlDatabase & db)
|
||||
{
|
||||
QList<LibraryItem *> list;
|
||||
|
||||
QSqlQuery selectQuery(db); //TODO check
|
||||
selectQuery.prepare("SELECT * FROM folder WHERE parentId = :parentId and id <> 1");
|
||||
selectQuery.bindValue(":parentId", parentId);
|
||||
selectQuery.exec();
|
||||
|
||||
Folder * currentItem;
|
||||
while (selectQuery.next())
|
||||
{
|
||||
QList<QVariant> data;
|
||||
QSqlRecord record = selectQuery.record();
|
||||
for(int i=0;i<record.count();i++)
|
||||
data << record.value(i);
|
||||
//TODO sort by sort indicator and name
|
||||
currentItem = new Folder(record.value("id").toULongLong(),record.value("parentId").toULongLong(),record.value("name").toString(),record.value("path").toString());
|
||||
int lessThan = 0;
|
||||
if(list.isEmpty())
|
||||
list.append(currentItem);
|
||||
else
|
||||
{
|
||||
Folder * last = static_cast<Folder *>(list.back());
|
||||
QString nameLast = last->name; //TODO usar info name si est<73> disponible, sino el nombre del fichero.....
|
||||
QString nameCurrent = currentItem->name;
|
||||
QList<LibraryItem *>::iterator i;
|
||||
i = list.end();
|
||||
i--;
|
||||
while ((0 > (lessThan = nameCurrent.localeAwareCompare(nameLast))) && i != list.begin())
|
||||
{
|
||||
i--;
|
||||
nameLast = (*i)->name;
|
||||
}
|
||||
if(lessThan>0) //si se ha encontrado un elemento menor que current, se inserta justo despu<70>s
|
||||
list.insert(++i,currentItem);
|
||||
else
|
||||
list.insert(i,currentItem);
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
return list;
|
||||
}
|
||||
|
||||
void Folder::removeFromDB(QSqlDatabase & db)
|
||||
{
|
||||
QSqlQuery query(db);
|
||||
query.prepare("DELETE FROM folder WHERE id = :id");
|
||||
query.bindValue(":id", id);
|
||||
query.exec();
|
||||
}
|
||||
|
26
YACReaderLibrary/db/folder.h
Normal file
26
YACReaderLibrary/db/folder.h
Normal file
@ -0,0 +1,26 @@
|
||||
#ifndef __FOLDER_H
|
||||
#define __FOLDER_H
|
||||
|
||||
#include "library_item.h"
|
||||
|
||||
#include <QSqlDatabase>
|
||||
#include <QList>
|
||||
|
||||
class Folder : public LibraryItem
|
||||
{
|
||||
public:
|
||||
bool knownParent;
|
||||
bool knownId;
|
||||
|
||||
Folder():knownParent(false), knownId(false){};
|
||||
Folder(qulonglong sid, qulonglong pid,QString fn, QString fp):knownParent(true), knownId(true){id = sid; parentId = pid;name = fn; path = fp;};
|
||||
Folder(QString fn, QString fp):knownParent(false), knownId(false){name = fn; path = fp;};
|
||||
void setId(qulonglong sid){id = sid;knownId = true;};
|
||||
void setFather(qulonglong pid){parentId = pid;knownParent = true;};
|
||||
static QList<LibraryItem *> getFoldersFromParent(qulonglong parentId, QSqlDatabase & db);
|
||||
qulonglong insert(QSqlDatabase & db);
|
||||
bool isDir(){return true;};
|
||||
void removeFromDB(QSqlDatabase & db);
|
||||
};
|
||||
|
||||
#endif
|
0
YACReaderLibrary/db/library_item.cpp
Normal file
0
YACReaderLibrary/db/library_item.cpp
Normal file
17
YACReaderLibrary/db/library_item.h
Normal file
17
YACReaderLibrary/db/library_item.h
Normal file
@ -0,0 +1,17 @@
|
||||
#ifndef __LIBRARY_ITEM_H
|
||||
#define __LIBRARY_ITEM_H
|
||||
|
||||
#include <QSqlDatabase>
|
||||
|
||||
class LibraryItem
|
||||
{
|
||||
public:
|
||||
virtual bool isDir() = 0;
|
||||
virtual void removeFromDB(QSqlDatabase & db) = 0;
|
||||
QString name;
|
||||
QString path;
|
||||
qulonglong parentId;
|
||||
qulonglong id;
|
||||
};
|
||||
|
||||
#endif
|
43
YACReaderLibrary/db/tableitem.cpp
Normal file
43
YACReaderLibrary/db/tableitem.cpp
Normal file
@ -0,0 +1,43 @@
|
||||
|
||||
#include <QStringList>
|
||||
|
||||
#include "tableitem.h"
|
||||
|
||||
//! [0]
|
||||
TableItem::TableItem(const QList<QVariant> &data)
|
||||
|
||||
{
|
||||
itemData = data;
|
||||
}
|
||||
//! [0]
|
||||
|
||||
//! [1]
|
||||
TableItem::~TableItem()
|
||||
{
|
||||
|
||||
}
|
||||
//! [1]
|
||||
|
||||
|
||||
//! [5]
|
||||
int TableItem::columnCount() const
|
||||
{
|
||||
return itemData.count();
|
||||
}
|
||||
//! [5]
|
||||
|
||||
//! [6]
|
||||
QVariant TableItem::data(int column) const
|
||||
{
|
||||
return itemData.value(column);
|
||||
}
|
||||
//! [6]
|
||||
|
||||
|
||||
//! [8]
|
||||
int TableItem::row() const
|
||||
{
|
||||
|
||||
return 0;
|
||||
}
|
||||
//! [8]
|
26
YACReaderLibrary/db/tableitem.h
Normal file
26
YACReaderLibrary/db/tableitem.h
Normal file
@ -0,0 +1,26 @@
|
||||
#ifndef TABLEITEM_H
|
||||
#define TABLEITEM_H
|
||||
|
||||
#include <QList>
|
||||
#include <QVariant>
|
||||
#include "comic.h"
|
||||
|
||||
//! [0]
|
||||
class TableItem
|
||||
{
|
||||
public:
|
||||
TableItem(const QList<QVariant> &data);
|
||||
~TableItem();
|
||||
int columnCount() const;
|
||||
QVariant data(int column) const;
|
||||
int row() const;
|
||||
unsigned long long int id; //TODO sustituir por una clase adecuada
|
||||
//Comic comic;
|
||||
private:
|
||||
QList<QVariant> itemData;
|
||||
|
||||
|
||||
};
|
||||
//! [0]
|
||||
|
||||
#endif
|
366
YACReaderLibrary/db/tablemodel.cpp
Normal file
366
YACReaderLibrary/db/tablemodel.cpp
Normal file
@ -0,0 +1,366 @@
|
||||
|
||||
#include <QtGui>
|
||||
#include <QtDebug>
|
||||
|
||||
#include "tableitem.h"
|
||||
#include "tablemodel.h"
|
||||
#include "data_base_management.h"
|
||||
#include "qnaturalsorting.h"
|
||||
|
||||
//ci.number,ci.title,c.fileName,ci.numPages,c.id,c.parentId,c.path,ci.hash,ci.read
|
||||
#define NUMBER 0
|
||||
#define TITLE 1
|
||||
#define FILE_NAME 2
|
||||
#define NUM_PAGES 3
|
||||
#define ID 4
|
||||
#define PARENT_ID 5
|
||||
#define PATH 6
|
||||
#define HASH 7
|
||||
#define READ 8
|
||||
#define IS_BIS 9
|
||||
|
||||
TableModel::TableModel(QObject *parent)
|
||||
: QAbstractItemModel(parent)
|
||||
{
|
||||
connect(this,SIGNAL(beforeReset()),this,SIGNAL(modelAboutToBeReset()));
|
||||
connect(this,SIGNAL(reset()),this,SIGNAL(modelReset()));
|
||||
}
|
||||
|
||||
//! [0]
|
||||
TableModel::TableModel( QSqlQuery &sqlquery, QObject *parent)
|
||||
: QAbstractItemModel(parent)
|
||||
{
|
||||
setupModelData(sqlquery);
|
||||
}
|
||||
//! [0]
|
||||
|
||||
//! [1]
|
||||
TableModel::~TableModel()
|
||||
{
|
||||
qDeleteAll(_data);
|
||||
}
|
||||
//! [1]
|
||||
|
||||
//! [2]
|
||||
int TableModel::columnCount(const QModelIndex &parent) const
|
||||
{
|
||||
if(_data.isEmpty())
|
||||
return 0;
|
||||
return _data.first()->columnCount();
|
||||
}
|
||||
//! [2]
|
||||
|
||||
//! [3]
|
||||
QVariant TableModel::data(const QModelIndex &index, int role) const
|
||||
{
|
||||
if (!index.isValid())
|
||||
return QVariant();
|
||||
|
||||
if (role == Qt::DecorationRole)
|
||||
{
|
||||
return QVariant();
|
||||
}
|
||||
|
||||
if (role != Qt::DisplayRole)
|
||||
return QVariant();
|
||||
|
||||
TableItem *item = static_cast<TableItem*>(index.internalPointer());
|
||||
|
||||
return item->data(index.column());
|
||||
}
|
||||
//! [3]
|
||||
|
||||
//! [4]
|
||||
Qt::ItemFlags TableModel::flags(const QModelIndex &index) const
|
||||
{
|
||||
if (!index.isValid())
|
||||
return 0;
|
||||
|
||||
return Qt::ItemIsEnabled | Qt::ItemIsSelectable;
|
||||
}
|
||||
//! [4]
|
||||
|
||||
//! [5]
|
||||
QVariant TableModel::headerData(int section, Qt::Orientation orientation,
|
||||
int role) const
|
||||
{
|
||||
if (orientation == Qt::Horizontal && role == Qt::DisplayRole)
|
||||
{
|
||||
switch(section)//TODO obtener esto de la query
|
||||
{
|
||||
case 0:
|
||||
return QVariant(QString("#"));
|
||||
case 1:
|
||||
return QVariant(QString(tr("Title")));
|
||||
case 2:
|
||||
return QVariant(QString(tr("File Name")));
|
||||
case 3:
|
||||
return QVariant(QString(tr("Pages")));
|
||||
}
|
||||
}
|
||||
|
||||
if(orientation == Qt::Vertical && role == Qt::DecorationRole)
|
||||
return QVariant(QIcon(":/images/icon.png"));
|
||||
|
||||
return QVariant();
|
||||
}
|
||||
//! [5]
|
||||
|
||||
//! [6]
|
||||
QModelIndex TableModel::index(int row, int column, const QModelIndex &parent)
|
||||
const
|
||||
{
|
||||
if (!hasIndex(row, column, parent))
|
||||
return QModelIndex();
|
||||
|
||||
return createIndex(row, column, _data.at(row));
|
||||
}
|
||||
//! [6]
|
||||
|
||||
//! [7]
|
||||
QModelIndex TableModel::parent(const QModelIndex &index) const
|
||||
{
|
||||
return QModelIndex();
|
||||
}
|
||||
//! [7]
|
||||
|
||||
//! [8]
|
||||
int TableModel::rowCount(const QModelIndex &parent) const
|
||||
{
|
||||
TreeItem *parentItem;
|
||||
if (parent.column() > 0)
|
||||
return 0;
|
||||
|
||||
if (!parent.isValid())
|
||||
return _data.count();
|
||||
|
||||
return 0;
|
||||
}
|
||||
//! [8]
|
||||
|
||||
QStringList TableModel::getPaths(const QString & _source)
|
||||
{
|
||||
QStringList paths;
|
||||
QString source = _source + "/.yacreaderlibrary/covers/";
|
||||
QList<TableItem *>::ConstIterator itr;
|
||||
for(itr = _data.constBegin();itr != _data.constEnd();itr++)
|
||||
{
|
||||
QString hash = (*itr)->data(HASH).toString();
|
||||
paths << source+ hash +".jpg";
|
||||
}
|
||||
|
||||
return paths;
|
||||
}
|
||||
|
||||
void TableModel::setupModelData(unsigned long long int folderId,const QString & databasePath)
|
||||
{
|
||||
//QFile f(QCoreApplication::applicationDirPath()+"/performance.txt");
|
||||
//f.open(QIODevice::Append);
|
||||
beginResetModel();
|
||||
//QElapsedTimer timer;
|
||||
//timer.start();
|
||||
qDeleteAll(_data);
|
||||
_data.clear();
|
||||
|
||||
//QTextStream txtS(&f);
|
||||
//txtS << "TABLEMODEL: Tiempo de borrado: " << timer.elapsed() << "ms\r\n";
|
||||
_databasePath = databasePath;
|
||||
QSqlDatabase db = DataBaseManagement::loadDatabase(databasePath);
|
||||
{
|
||||
//crear la consulta
|
||||
//timer.restart();
|
||||
QSqlQuery selectQuery(db); //TODO check
|
||||
selectQuery.prepare("select ci.number,ci.title,c.fileName,ci.numPages,c.id,c.parentId,c.path,ci.hash,ci.read,ci.isBis from comic c inner join comic_info ci on (c.comicInfoId = ci.id) where c.parentId = :parentId");
|
||||
selectQuery.bindValue(":parentId", folderId);
|
||||
selectQuery.exec();
|
||||
//txtS << "TABLEMODEL: Tiempo de consulta: " << timer.elapsed() << "ms\r\n";
|
||||
//timer.restart();
|
||||
setupModelData(selectQuery);
|
||||
//txtS << "TABLEMODEL: Tiempo de creaci<63>n del modelo: " << timer.elapsed() << "ms\r\n";
|
||||
//selectQuery.finish();
|
||||
}
|
||||
db.close();
|
||||
QSqlDatabase::removeDatabase(_databasePath);
|
||||
endResetModel();
|
||||
//f.close();
|
||||
}
|
||||
|
||||
QString TableModel::getComicPath(QModelIndex mi)
|
||||
{
|
||||
if(mi.isValid())
|
||||
return _data.at(mi.row())->data(PATH).toString();
|
||||
return "";
|
||||
}
|
||||
#define NUMBER_MAX 99999999
|
||||
void TableModel::setupModelData(QSqlQuery &sqlquery)
|
||||
{
|
||||
TableItem * currentItem;
|
||||
while (sqlquery.next())
|
||||
{
|
||||
QList<QVariant> data;
|
||||
QSqlRecord record = sqlquery.record();
|
||||
for(int i=0;i<record.count();i++)
|
||||
data << record.value(i);
|
||||
//TODO sort by sort indicator and name
|
||||
currentItem = new TableItem(data);
|
||||
bool lessThan = false;
|
||||
if(_data.isEmpty())
|
||||
_data.append(currentItem);
|
||||
else
|
||||
{
|
||||
TableItem * last = _data.back();
|
||||
QString nameLast = last->data(FILE_NAME).toString(); //TODO usar info name si est<73> disponible, sino el nombre del fichero.....
|
||||
QString nameCurrent = currentItem->data(FILE_NAME).toString();
|
||||
int numberLast,numberCurrent;
|
||||
numberLast = numberCurrent = NUMBER_MAX; //TODO change by std limit
|
||||
|
||||
if(!last->data(NUMBER).isNull())
|
||||
numberLast = last->data(NUMBER).toInt();
|
||||
|
||||
if(!currentItem->data(NUMBER).isNull())
|
||||
numberCurrent = currentItem->data(NUMBER).toInt();
|
||||
|
||||
QList<TableItem *>::iterator i;
|
||||
i = _data.end();
|
||||
i--;
|
||||
|
||||
if(numberCurrent != NUMBER_MAX)
|
||||
{
|
||||
while ((lessThan =numberCurrent < numberLast) && i != _data.begin())
|
||||
{
|
||||
i--;
|
||||
numberLast = NUMBER_MAX; //TODO change by std limit
|
||||
|
||||
if(!(*i)->data(NUMBER).isNull())
|
||||
numberLast = (*i)->data(NUMBER).toInt();
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
while ((lessThan = naturalSortLessThanCI(nameCurrent,nameLast)) && i != _data.begin() && numberLast == 99999999)
|
||||
{
|
||||
i--;
|
||||
nameLast = (*i)->data(FILE_NAME).toString();
|
||||
numberLast = NUMBER_MAX; //TODO change by std limit
|
||||
|
||||
if(!(*i)->data(NUMBER).isNull())
|
||||
numberLast = (*i)->data(NUMBER).toInt();
|
||||
}
|
||||
|
||||
}
|
||||
if(!lessThan) //si se ha encontrado un elemento menor que current, se inserta justo despu<70>s
|
||||
{
|
||||
if(numberCurrent != NUMBER_MAX)
|
||||
{
|
||||
if(numberCurrent == numberLast)
|
||||
if(currentItem->data(IS_BIS).toBool())
|
||||
{
|
||||
_data.insert(++i,currentItem);
|
||||
}
|
||||
else
|
||||
_data.insert(i,currentItem);
|
||||
else
|
||||
_data.insert(++i,currentItem);
|
||||
}
|
||||
else
|
||||
_data.insert(++i,currentItem);
|
||||
}
|
||||
else
|
||||
{
|
||||
_data.insert(i,currentItem);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Comic TableModel::getComic(const QModelIndex & mi)
|
||||
{
|
||||
Comic c;
|
||||
|
||||
QSqlDatabase db = DataBaseManagement::loadDatabase(_databasePath);
|
||||
c.load(_data.at(mi.row())->data(ID).toULongLong(),db);
|
||||
db.close();
|
||||
QSqlDatabase::removeDatabase(_databasePath);
|
||||
|
||||
return c;
|
||||
}
|
||||
|
||||
Comic TableModel::_getComic(const QModelIndex & mi)
|
||||
{
|
||||
Comic c;
|
||||
|
||||
QSqlDatabase db = DataBaseManagement::loadDatabase(_databasePath);
|
||||
c.load(_data.at(mi.row())->data(ID).toULongLong(),db);
|
||||
db.close();
|
||||
QSqlDatabase::removeDatabase(_databasePath);
|
||||
|
||||
return c;
|
||||
}
|
||||
|
||||
|
||||
QVector<bool> TableModel::getReadList()
|
||||
{
|
||||
int numComics = _data.count();
|
||||
QVector<bool> readList(numComics);
|
||||
for(int i=0;i<numComics;i++)
|
||||
{
|
||||
//TODO reemplazar el acceso a las columnas con enteros por defines
|
||||
readList[i] = _data.value(i)->data(READ).toBool();
|
||||
}
|
||||
return readList;
|
||||
}
|
||||
|
||||
QVector<bool> TableModel::setAllComicsRead(bool read)
|
||||
{
|
||||
QSqlDatabase db = DataBaseManagement::loadDatabase(_databasePath);
|
||||
db.transaction();
|
||||
int numComics = _data.count();
|
||||
QVector<bool> readList(numComics);
|
||||
for(int i=0;i<numComics;i++)
|
||||
{
|
||||
//TODO reemplazar el acceso a las columnas con enteros por defines
|
||||
readList[i] = read;
|
||||
_data.value(i)->data(READ) = QVariant(true);
|
||||
Comic c;
|
||||
c.load(_data.value(i)->data(ID).toULongLong(),db);
|
||||
c.info.read = read;
|
||||
c.info.update(db);
|
||||
}
|
||||
db.commit();
|
||||
db.close();
|
||||
QSqlDatabase::removeDatabase(_databasePath);
|
||||
|
||||
return readList;
|
||||
}
|
||||
|
||||
QList<Comic> TableModel::getComics(QList<QModelIndex> list)
|
||||
{
|
||||
QList<Comic> comics;
|
||||
|
||||
QSqlDatabase db = DataBaseManagement::loadDatabase(_databasePath);
|
||||
db.transaction();
|
||||
QList<QModelIndex>::const_iterator itr;
|
||||
for(itr = list.constBegin(); itr!= list.constEnd();itr++)
|
||||
{
|
||||
comics.append(_getComic(*itr));
|
||||
}
|
||||
db.commit();
|
||||
db.close();
|
||||
QSqlDatabase::removeDatabase(_databasePath);
|
||||
return comics;
|
||||
}
|
||||
|
||||
QModelIndex TableModel::getIndexFromId(quint64 id)
|
||||
{
|
||||
QList<TableItem *>::ConstIterator itr;
|
||||
int i=0;
|
||||
for(itr = _data.constBegin();itr != _data.constEnd();itr++)
|
||||
{
|
||||
if((*itr)->data(ID).toULongLong() == id)
|
||||
break;
|
||||
i++;
|
||||
}
|
||||
|
||||
return index(i,0);
|
||||
}
|
59
YACReaderLibrary/db/tablemodel.h
Normal file
59
YACReaderLibrary/db/tablemodel.h
Normal file
@ -0,0 +1,59 @@
|
||||
#ifndef TABLEMODEL_H
|
||||
#define TABLEMODEL_H
|
||||
|
||||
#include <QAbstractItemModel>
|
||||
#include <QModelIndex>
|
||||
#include <QVariant>
|
||||
#include <QSqlQuery>
|
||||
#include <QSqlDatabase>
|
||||
|
||||
#include "comic.h"
|
||||
|
||||
class TableItem;
|
||||
|
||||
//! [0]
|
||||
class TableModel : public QAbstractItemModel
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
TableModel(QObject *parent = 0);
|
||||
TableModel( QSqlQuery &sqlquery, QObject *parent = 0);
|
||||
~TableModel();
|
||||
|
||||
QVariant data(const QModelIndex &index, int role) const;
|
||||
Qt::ItemFlags flags(const QModelIndex &index) const;
|
||||
QVariant headerData(int section, Qt::Orientation orientation,
|
||||
int role = Qt::DisplayRole) const;
|
||||
QModelIndex index(int row, int column,
|
||||
const QModelIndex &parent = QModelIndex()) const;
|
||||
QModelIndex parent(const QModelIndex &index) const;
|
||||
int rowCount(const QModelIndex &parent = QModelIndex()) const;
|
||||
int columnCount(const QModelIndex &parent = QModelIndex()) const;
|
||||
void setupModelData(unsigned long long int parentFolder,const QString & databasePath);
|
||||
|
||||
//M<>todos de conveniencia
|
||||
QStringList getPaths(const QString & _source);
|
||||
QString getComicPath(QModelIndex mi);
|
||||
Comic getComic(const QModelIndex & mi); //--> para la edici<63>n
|
||||
QVector<bool> getReadList();
|
||||
QVector<bool> setAllComicsRead(bool read);
|
||||
QList<Comic> getComics(QList<QModelIndex> list); //--> recupera la informaci<63>n com<6F>n a los comics seleccionados
|
||||
QModelIndex getIndexFromId(quint64 id);
|
||||
//setcomicInfo(QModelIndex & mi); --> inserta en la base datos
|
||||
//setComicInfoForAllComics(); --> inserta la informaci<63>n com<6F>n a todos los c<>mics de una sola vez.
|
||||
//setComicInfoForSelectedComis(QList<QModelIndex> list); -->inserta la informaci<63>n com<6F>n para los comics seleccionados
|
||||
private:
|
||||
void setupModelData( QSqlQuery &sqlquery);
|
||||
Comic _getComic(const QModelIndex & mi);
|
||||
QList<TableItem *> _data;
|
||||
|
||||
QString _databasePath;
|
||||
|
||||
signals:
|
||||
void beforeReset();
|
||||
void reset();
|
||||
};
|
||||
//! [0]
|
||||
|
||||
#endif
|
147
YACReaderLibrary/db/treeitem.cpp
Normal file
147
YACReaderLibrary/db/treeitem.cpp
Normal file
@ -0,0 +1,147 @@
|
||||
/****************************************************************************
|
||||
**
|
||||
** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies).
|
||||
** All rights reserved.
|
||||
** Contact: Nokia Corporation (qt-info@nokia.com)
|
||||
**
|
||||
** This file is part of the examples of the Qt Toolkit.
|
||||
**
|
||||
** $QT_BEGIN_LICENSE:BSD$
|
||||
** You may use this file under the terms of the BSD license as follows:
|
||||
**
|
||||
** "Redistribution and use in source and binary forms, with or without
|
||||
** modification, are permitted provided that the following conditions are
|
||||
** met:
|
||||
** * Redistributions of source code must retain the above copyright
|
||||
** notice, this list of conditions and the following disclaimer.
|
||||
** * Redistributions in binary form must reproduce the above copyright
|
||||
** notice, this list of conditions and the following disclaimer in
|
||||
** the documentation and/or other materials provided with the
|
||||
** distribution.
|
||||
** * Neither the name of Nokia Corporation and its Subsidiary(-ies) nor
|
||||
** the names of its contributors may be used to endorse or promote
|
||||
** products derived from this software without specific prior written
|
||||
** permission.
|
||||
**
|
||||
** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||
** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||
** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||
** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||
** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||
** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE."
|
||||
** $QT_END_LICENSE$
|
||||
**
|
||||
****************************************************************************/
|
||||
|
||||
/*
|
||||
treeitem.cpp
|
||||
|
||||
A container for items of data supplied by the simple tree model.
|
||||
*/
|
||||
|
||||
#include <QStringList>
|
||||
|
||||
#include "treeitem.h"
|
||||
#include "qnaturalsorting.h"
|
||||
|
||||
//! [0]
|
||||
TreeItem::TreeItem(const QList<QVariant> &data, TreeItem *parent)
|
||||
{
|
||||
parentItem = parent;
|
||||
itemData = data;
|
||||
}
|
||||
//! [0]
|
||||
|
||||
//! [1]
|
||||
TreeItem::~TreeItem()
|
||||
{
|
||||
qDeleteAll(childItems);
|
||||
}
|
||||
//! [1]
|
||||
|
||||
//! [2]
|
||||
void TreeItem::appendChild(TreeItem *item)
|
||||
{
|
||||
item->parentItem = this;
|
||||
//TODO insertar odernado
|
||||
if(childItems.isEmpty())
|
||||
childItems.append(item);
|
||||
else
|
||||
{
|
||||
TreeItem * last = childItems.back();
|
||||
QString nameLast = last->data(1).toString(); //TODO usar info name si est<73> disponible, sino el nombre del fichero.....
|
||||
QString nameCurrent = item->data(1).toString();
|
||||
QList<TreeItem *>::iterator i;
|
||||
i = childItems.end();
|
||||
i--;
|
||||
while (naturalSortLessThanCI(nameCurrent,nameLast) && i != childItems.begin())
|
||||
{
|
||||
i--;
|
||||
nameLast = (*i)->data(1).toString();
|
||||
}
|
||||
if(!naturalSortLessThanCI(nameCurrent,nameLast)) //si se ha encontrado un elemento menor que current, se inserta justo despu<70>s
|
||||
childItems.insert(++i,item);
|
||||
else
|
||||
childItems.insert(i,item);
|
||||
|
||||
}
|
||||
|
||||
//childItems.append(item);
|
||||
}
|
||||
//! [2]
|
||||
|
||||
//! [3]
|
||||
TreeItem *TreeItem::child(int row)
|
||||
{
|
||||
return childItems.value(row);
|
||||
}
|
||||
//! [3]
|
||||
|
||||
//! [4]
|
||||
int TreeItem::childCount() const
|
||||
{
|
||||
return childItems.count();
|
||||
}
|
||||
//! [4]
|
||||
|
||||
//! [5]
|
||||
int TreeItem::columnCount() const
|
||||
{
|
||||
return itemData.count();
|
||||
}
|
||||
//! [5]
|
||||
|
||||
//! [6]
|
||||
QVariant TreeItem::data(int column) const
|
||||
{
|
||||
return itemData.value(column);
|
||||
}
|
||||
//! [6]
|
||||
|
||||
//! [7]
|
||||
TreeItem *TreeItem::parent()
|
||||
{
|
||||
return parentItem;
|
||||
}
|
||||
//! [7]
|
||||
|
||||
//! [8]
|
||||
int TreeItem::row() const
|
||||
{
|
||||
if (parentItem)
|
||||
return parentItem->childItems.indexOf(const_cast<TreeItem*>(this));
|
||||
|
||||
return 0;
|
||||
}
|
||||
//! [8]
|
||||
|
||||
|
||||
QList<QVariant> TreeItem::getData() const
|
||||
{
|
||||
return itemData;
|
||||
}
|
78
YACReaderLibrary/db/treeitem.h
Normal file
78
YACReaderLibrary/db/treeitem.h
Normal file
@ -0,0 +1,78 @@
|
||||
/****************************************************************************
|
||||
**
|
||||
** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies).
|
||||
** All rights reserved.
|
||||
** Contact: Nokia Corporation (qt-info@nokia.com)
|
||||
**
|
||||
** This file is part of the examples of the Qt Toolkit.
|
||||
**
|
||||
** $QT_BEGIN_LICENSE:BSD$
|
||||
** You may use this file under the terms of the BSD license as follows:
|
||||
**
|
||||
** "Redistribution and use in source and binary forms, with or without
|
||||
** modification, are permitted provided that the following conditions are
|
||||
** met:
|
||||
** * Redistributions of source code must retain the above copyright
|
||||
** notice, this list of conditions and the following disclaimer.
|
||||
** * Redistributions in binary form must reproduce the above copyright
|
||||
** notice, this list of conditions and the following disclaimer in
|
||||
** the documentation and/or other materials provided with the
|
||||
** distribution.
|
||||
** * Neither the name of Nokia Corporation and its Subsidiary(-ies) nor
|
||||
** the names of its contributors may be used to endorse or promote
|
||||
** products derived from this software without specific prior written
|
||||
** permission.
|
||||
**
|
||||
** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||
** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||
** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||
** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||
** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||
** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE."
|
||||
** $QT_END_LICENSE$
|
||||
**
|
||||
****************************************************************************/
|
||||
|
||||
#ifndef TREEITEM_H
|
||||
#define TREEITEM_H
|
||||
|
||||
#include <QList>
|
||||
#include <QVariant>
|
||||
#include <QModelIndex>
|
||||
|
||||
//! [0]
|
||||
class TreeItem
|
||||
{
|
||||
public:
|
||||
TreeItem(const QList<QVariant> &data, TreeItem *parent = 0);
|
||||
~TreeItem();
|
||||
|
||||
void appendChild(TreeItem *child);
|
||||
|
||||
TreeItem *child(int row);
|
||||
int childCount() const;
|
||||
int columnCount() const;
|
||||
QVariant data(int column) const;
|
||||
QList<QVariant> getData() const;
|
||||
int row() const;
|
||||
TreeItem *parent();
|
||||
TreeItem *parentItem;
|
||||
unsigned long long int id;
|
||||
QList<QString> comicNames;
|
||||
TreeItem * originalItem;
|
||||
private:
|
||||
QList<TreeItem*> childItems;
|
||||
QList<QVariant> itemData;
|
||||
|
||||
|
||||
|
||||
|
||||
};
|
||||
//! [0]
|
||||
|
||||
#endif
|
413
YACReaderLibrary/db/treemodel.cpp
Normal file
413
YACReaderLibrary/db/treemodel.cpp
Normal file
@ -0,0 +1,413 @@
|
||||
/****************************************************************************
|
||||
**
|
||||
** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies).
|
||||
** All rights reserved.
|
||||
** Contact: Nokia Corporation (qt-info@nokia.com)
|
||||
**
|
||||
** This file is part of the examples of the Qt Toolkit.
|
||||
**
|
||||
** $QT_BEGIN_LICENSE:BSD$
|
||||
** You may use this file under the terms of the BSD license as follows:
|
||||
**
|
||||
** "Redistribution and use in source and binary forms, with or without
|
||||
** modification, are permitted provided that the following conditions are
|
||||
** met:
|
||||
** * Redistributions of source code must retain the above copyright
|
||||
** notice, this list of conditions and the following disclaimer.
|
||||
** * Redistributions in binary form must reproduce the above copyright
|
||||
** notice, this list of conditions and the following disclaimer in
|
||||
** the documentation and/or other materials provided with the
|
||||
** distribution.
|
||||
** * Neither the name of Nokia Corporation and its Subsidiary(-ies) nor
|
||||
** the names of its contributors may be used to endorse or promote
|
||||
** products derived from this software without specific prior written
|
||||
** permission.
|
||||
**
|
||||
** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||
** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||
** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||
** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||
** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||
** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE."
|
||||
** $QT_END_LICENSE$
|
||||
**
|
||||
****************************************************************************/
|
||||
|
||||
/*
|
||||
treemodel.cpp
|
||||
|
||||
Provides a simple tree model to show how to create and use hierarchical
|
||||
models.
|
||||
*/
|
||||
|
||||
#include <QtGui>
|
||||
|
||||
|
||||
#include "treeitem.h"
|
||||
#include "treemodel.h"
|
||||
#include "data_base_management.h"
|
||||
|
||||
#define ROOT 1
|
||||
|
||||
TreeModel::TreeModel(QObject *parent)
|
||||
: QAbstractItemModel(parent),rootItem(0),rootBeforeFilter(0),filterEnabled(false),includeComics(false)
|
||||
{
|
||||
connect(this,SIGNAL(beforeReset()),this,SIGNAL(modelAboutToBeReset()));
|
||||
connect(this,SIGNAL(reset()),this,SIGNAL(modelReset()));
|
||||
}
|
||||
|
||||
//! [0]
|
||||
TreeModel::TreeModel( QSqlQuery &sqlquery, QObject *parent)
|
||||
: QAbstractItemModel(parent),rootItem(0),rootBeforeFilter(0),filterEnabled(false),includeComics(false)
|
||||
{
|
||||
//lo m<>s probable es que el nodo ra<72>z no necesite tener informaci<63>n
|
||||
QList<QVariant> rootData;
|
||||
rootData << "root"; //id 0, padre 0, title "root" (el id, y el id del padre van a ir en la clase TreeItem)
|
||||
rootItem = new TreeItem(rootData);
|
||||
rootItem->id = ROOT;
|
||||
rootItem->parentItem = 0;
|
||||
setupModelData(sqlquery, rootItem);
|
||||
//sqlquery.finish();
|
||||
}
|
||||
//! [0]
|
||||
|
||||
//! [1]
|
||||
TreeModel::~TreeModel()
|
||||
{
|
||||
if(rootItem != 0)
|
||||
delete rootItem;
|
||||
}
|
||||
//! [1]
|
||||
|
||||
//! [2]
|
||||
int TreeModel::columnCount(const QModelIndex &parent) const
|
||||
{
|
||||
if (parent.isValid())
|
||||
return static_cast<TreeItem*>(parent.internalPointer())->columnCount();
|
||||
else
|
||||
return rootItem->columnCount();
|
||||
}
|
||||
//! [2]
|
||||
|
||||
//! [3]
|
||||
QVariant TreeModel::data(const QModelIndex &index, int role) const
|
||||
{
|
||||
if (!index.isValid())
|
||||
return QVariant();
|
||||
|
||||
if (role == Qt::DecorationRole)
|
||||
return QVariant(QIcon(":/images/folder.png"));
|
||||
|
||||
if (role != Qt::DisplayRole)
|
||||
return QVariant();
|
||||
|
||||
TreeItem *item = static_cast<TreeItem*>(index.internalPointer());
|
||||
|
||||
return item->data(index.column());
|
||||
}
|
||||
//! [3]
|
||||
|
||||
//! [4]
|
||||
Qt::ItemFlags TreeModel::flags(const QModelIndex &index) const
|
||||
{
|
||||
if (!index.isValid())
|
||||
return 0;
|
||||
|
||||
return Qt::ItemIsEnabled | Qt::ItemIsSelectable;
|
||||
}
|
||||
//! [4]
|
||||
|
||||
//! [5]
|
||||
QVariant TreeModel::headerData(int section, Qt::Orientation orientation,
|
||||
int role) const
|
||||
{
|
||||
if (orientation == Qt::Horizontal && role == Qt::DisplayRole)
|
||||
return rootItem->data(section);
|
||||
|
||||
return QVariant();
|
||||
}
|
||||
//! [5]
|
||||
|
||||
//! [6]
|
||||
QModelIndex TreeModel::index(int row, int column, const QModelIndex &parent)
|
||||
const
|
||||
{
|
||||
if (!hasIndex(row, column, parent))
|
||||
return QModelIndex();
|
||||
|
||||
TreeItem *parentItem;
|
||||
|
||||
if (!parent.isValid())
|
||||
parentItem = rootItem;
|
||||
else
|
||||
parentItem = static_cast<TreeItem*>(parent.internalPointer());
|
||||
|
||||
TreeItem *childItem = parentItem->child(row);
|
||||
if (childItem)
|
||||
return createIndex(row, column, childItem);
|
||||
else
|
||||
return QModelIndex();
|
||||
}
|
||||
//! [6]
|
||||
|
||||
//! [7]
|
||||
QModelIndex TreeModel::parent(const QModelIndex &index) const
|
||||
{
|
||||
if (!index.isValid())
|
||||
return QModelIndex();
|
||||
|
||||
TreeItem *childItem = static_cast<TreeItem*>(index.internalPointer());
|
||||
TreeItem *parentItem = childItem->parent();
|
||||
|
||||
if (parentItem == rootItem)
|
||||
return QModelIndex();
|
||||
|
||||
return createIndex(parentItem->row(), 0, parentItem);
|
||||
}
|
||||
//! [7]
|
||||
|
||||
QModelIndex TreeModel::indexFromItem(TreeItem * item,int column)
|
||||
{
|
||||
//if(item->parent() != 0)
|
||||
// return index(item->row(),column,parent(indexFromItem(item->parent(),column-1)));
|
||||
//else
|
||||
// return index(item->row(),0,QModelIndex());
|
||||
return createIndex(item->row(), column, item);
|
||||
}
|
||||
|
||||
|
||||
//! [8]
|
||||
int TreeModel::rowCount(const QModelIndex &parent) const
|
||||
{
|
||||
TreeItem *parentItem;
|
||||
if (parent.column() > 0)
|
||||
return 0;
|
||||
|
||||
if (!parent.isValid())
|
||||
parentItem = rootItem;
|
||||
else
|
||||
parentItem = static_cast<TreeItem*>(parent.internalPointer());
|
||||
|
||||
return parentItem->childCount();
|
||||
}
|
||||
//! [8]
|
||||
|
||||
void TreeModel::setupModelData(QString path)
|
||||
{
|
||||
beginResetModel();
|
||||
if(rootItem != 0)
|
||||
delete rootItem; //TODO comprobar que se libera bien la memoria
|
||||
filterEnabled = false;
|
||||
rootItem = 0;
|
||||
rootBeforeFilter = 0;
|
||||
//inicializar el nodo ra<72>z
|
||||
QList<QVariant> rootData;
|
||||
rootData << "root"; //id 0, padre 0, title "root" (el id, y el id del padre van a ir en la clase TreeItem)
|
||||
rootItem = new TreeItem(rootData);
|
||||
rootItem->id = ROOT;
|
||||
rootItem->parentItem = 0;
|
||||
|
||||
//cargar la base de datos
|
||||
_databasePath = path;
|
||||
QSqlDatabase db = DataBaseManagement::loadDatabase(path);
|
||||
//crear la consulta
|
||||
{
|
||||
QSqlQuery selectQuery("select * from folder where id <> 1 order by parentId,name",db);
|
||||
|
||||
setupModelData(selectQuery,rootItem);
|
||||
}
|
||||
//selectQuery.finish();
|
||||
db.close();
|
||||
QSqlDatabase::removeDatabase(path);
|
||||
endResetModel();
|
||||
|
||||
}
|
||||
|
||||
|
||||
void TreeModel::setupModelData(QSqlQuery &sqlquery, TreeItem *parent)
|
||||
{
|
||||
//64 bits para la primary key, es decir la misma precisi<73>n que soporta sqlit 2^64
|
||||
//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);
|
||||
|
||||
while (sqlquery.next()) {
|
||||
QList<QVariant> data;
|
||||
QSqlRecord record = sqlquery.record();
|
||||
|
||||
data << record.value("name").toString();
|
||||
data << record.value("path").toString();
|
||||
TreeItem * item = new TreeItem(data);
|
||||
|
||||
item->id = record.value("id").toULongLong();
|
||||
//la inserci<63>n de hijos se hace de forma ordenada
|
||||
items.value(record.value("parentId").toULongLong())->appendChild(item);
|
||||
//se a<>ade el item al map, de forma que se pueda encontrar como padre en siguientes iteraciones
|
||||
items.insert(item->id,item);
|
||||
}
|
||||
}
|
||||
|
||||
void TreeModel::setupFilteredModelData()
|
||||
{
|
||||
beginResetModel();
|
||||
|
||||
//TODO hay que liberar memoria de anteriores filtrados
|
||||
|
||||
//inicializar el nodo ra<72>z
|
||||
|
||||
if(rootBeforeFilter == 0)
|
||||
rootBeforeFilter = rootItem;
|
||||
else
|
||||
delete rootItem;//los resultados de la b<>squeda anterior deben ser borrados
|
||||
|
||||
QList<QVariant> rootData;
|
||||
rootData << "root"; //id 1, padre 1, title "root" (el id, y el id del padre van a ir en la clase TreeItem)
|
||||
rootItem = new TreeItem(rootData);
|
||||
rootItem->id = ROOT;
|
||||
rootItem->parentItem = 0;
|
||||
|
||||
//cargar la base de datos
|
||||
QSqlDatabase db = DataBaseManagement::loadDatabase(_databasePath);
|
||||
//crear la consulta
|
||||
{
|
||||
QSqlQuery selectQuery(db); //TODO check
|
||||
if(!includeComics)
|
||||
{
|
||||
selectQuery.prepare("select * from folder where id <> 1 and upper(name) like upper(:filter) order by parentId,name ");
|
||||
selectQuery.bindValue(":filter", "%%"+filter+"%%");
|
||||
}
|
||||
else
|
||||
{
|
||||
selectQuery.prepare("SELECT DISTINCT f.id, f.parentId, f.name, f.path FROM folder f INNER JOIN comic c ON (f.id = c.parentId) WHERE f.id <> 1 AND ((UPPER(c.fileName) like UPPER(:filter)) OR (UPPER(f.name) like UPPER(:filter2))) ORDER BY f.parentId,f.name");
|
||||
selectQuery.bindValue(":filter", "%%"+filter+"%%");
|
||||
selectQuery.bindValue(":filter2", "%%"+filter+"%%");
|
||||
}
|
||||
selectQuery.exec();
|
||||
|
||||
setupFilteredModelData(selectQuery,rootItem);
|
||||
}
|
||||
//selectQuery.finish();
|
||||
db.close();
|
||||
QSqlDatabase::removeDatabase(_databasePath);
|
||||
|
||||
endResetModel();
|
||||
}
|
||||
|
||||
void TreeModel::setupFilteredModelData(QSqlQuery &sqlquery, TreeItem *parent)
|
||||
{
|
||||
//64 bits para la primary key, es decir la misma precisi<73>n que soporta sqlit 2^64
|
||||
filteredItems.clear();
|
||||
|
||||
//se a<>ade el nodo 0 al modelo que representa el arbol de elementos que cumplen con el filtro
|
||||
filteredItems.insert(parent->id,parent);
|
||||
|
||||
while (sqlquery.next()) { //se procesan todos los folders que cumplen con el filtro
|
||||
//datos de la base de datos
|
||||
QList<QVariant> data;
|
||||
QSqlRecord record = sqlquery.record();
|
||||
|
||||
data << record.value("name").toString();
|
||||
data << record.value("path").toString();
|
||||
TreeItem * item = new TreeItem(data);
|
||||
item->id = sqlquery.value(0).toULongLong();
|
||||
|
||||
//id del padre
|
||||
quint64 parentId = record.value("parentId").toULongLong();
|
||||
|
||||
//se a<>ade el item al map, de forma que se pueda encontrar como padre en siguientes iteraciones
|
||||
if(!filteredItems.contains(item->id))
|
||||
filteredItems.insert(item->id,item);
|
||||
|
||||
//es necesario conocer las coordenadas de origen para poder realizar scroll autom<6F>tico en la vista
|
||||
item->originalItem = items.value(item->id);
|
||||
|
||||
//si el padre ya existe en el modelo, el item se a<>ade como hijo
|
||||
if(filteredItems.contains(parentId))
|
||||
filteredItems.value(parentId)->appendChild(item);
|
||||
else//si el padre a<>n no se ha a<>adido, hay que a<>adirlo a <20>l y todos los padres hasta el nodo ra<72>z
|
||||
{
|
||||
//comprobamos con esta variable si el <20>ltimo de los padres (antes del nodo ra<72>z) ya exist<73>a en el modelo
|
||||
bool parentPreviousInserted = false;
|
||||
|
||||
//mientras no se alcance el nodo ra<72>z se procesan todos los padres (de abajo a arriba)
|
||||
while(parentId != ROOT )
|
||||
{
|
||||
//el padre no estaba en el modelo filtrado, as<61> que se rescata del modelo original
|
||||
TreeItem * parentItem = items.value(parentId);
|
||||
//se debe crear un nuevo nodo (para no compartir los hijos con el nodo original)
|
||||
TreeItem * newparentItem = new TreeItem(parentItem->getData()); //padre que se a<>adir<69> a la estructura de directorios filtrados
|
||||
newparentItem->id = parentId;
|
||||
|
||||
newparentItem->originalItem = parentItem;
|
||||
|
||||
//si el modelo contiene al padre, se a<>ade el item actual como hijo
|
||||
if(filteredItems.contains(parentId))
|
||||
{
|
||||
filteredItems.value(parentId)->appendChild(item);
|
||||
parentPreviousInserted = true;
|
||||
}
|
||||
//sino se registra el nodo para poder encontrarlo con posterioridad y se a<>ade el item actual como hijo
|
||||
else
|
||||
{
|
||||
newparentItem->appendChild(item);
|
||||
filteredItems.insert(newparentItem->id,newparentItem);
|
||||
parentPreviousInserted = false;
|
||||
}
|
||||
|
||||
//variables de control del bucle, se avanza hacia el nodo padre
|
||||
item = newparentItem;
|
||||
parentId = parentItem->parentItem->id;
|
||||
}
|
||||
|
||||
//si el nodo es hijo de 1 y no hab<61>a sido previamente insertado como hijo, se a<>ade como tal
|
||||
if(!parentPreviousInserted)
|
||||
filteredItems.value(ROOT)->appendChild(item);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
QString TreeModel::getDatabase()
|
||||
{
|
||||
return _databasePath;
|
||||
}
|
||||
|
||||
QString TreeModel::getFolderPath(const QModelIndex &folder)
|
||||
{
|
||||
return static_cast<TreeItem*>(folder.internalPointer())->data(1).toString();
|
||||
}
|
||||
|
||||
void TreeModel::setFilter(QString filter, bool includeComics)
|
||||
{
|
||||
this->filter = filter;
|
||||
this->includeComics = includeComics;
|
||||
filterEnabled = true;
|
||||
setupFilteredModelData();
|
||||
}
|
||||
|
||||
void TreeModel::resetFilter()
|
||||
{
|
||||
beginResetModel();
|
||||
filter = "";
|
||||
includeComics = false;
|
||||
//TODO hay que liberar la memoria reservada para el filtrado
|
||||
//items.clear();
|
||||
filteredItems.clear();
|
||||
TreeItem * root = rootItem;
|
||||
rootItem = rootBeforeFilter; //TODO si no se aplica el filtro previamente, esto invalidar<61>a en modelo
|
||||
if(root !=0)
|
||||
delete root;
|
||||
|
||||
rootBeforeFilter = 0;
|
||||
filterEnabled = false;
|
||||
endResetModel();
|
||||
|
||||
|
||||
}
|
106
YACReaderLibrary/db/treemodel.h
Normal file
106
YACReaderLibrary/db/treemodel.h
Normal file
@ -0,0 +1,106 @@
|
||||
/****************************************************************************
|
||||
**
|
||||
** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies).
|
||||
** All rights reserved.
|
||||
** Contact: Nokia Corporation (qt-info@nokia.com)
|
||||
**
|
||||
** This file is part of the examples of the Qt Toolkit.
|
||||
**
|
||||
** $QT_BEGIN_LICENSE:BSD$
|
||||
** You may use this file under the terms of the BSD license as follows:
|
||||
**
|
||||
** "Redistribution and use in source and binary forms, with or without
|
||||
** modification, are permitted provided that the following conditions are
|
||||
** met:
|
||||
** * Redistributions of source code must retain the above copyright
|
||||
** notice, this list of conditions and the following disclaimer.
|
||||
** * Redistributions in binary form must reproduce the above copyright
|
||||
** notice, this list of conditions and the following disclaimer in
|
||||
** the documentation and/or other materials provided with the
|
||||
** distribution.
|
||||
** * Neither the name of Nokia Corporation and its Subsidiary(-ies) nor
|
||||
** the names of its contributors may be used to endorse or promote
|
||||
** products derived from this software without specific prior written
|
||||
** permission.
|
||||
**
|
||||
** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||
** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||
** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||
** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||
** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||
** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE."
|
||||
** $QT_END_LICENSE$
|
||||
**
|
||||
****************************************************************************/
|
||||
|
||||
#ifndef TREEMODEL_H
|
||||
#define TREEMODEL_H
|
||||
|
||||
#include <QAbstractItemModel>
|
||||
#include <QModelIndex>
|
||||
#include <QVariant>
|
||||
#include <QSqlQuery>
|
||||
#include <QSqlDatabase>
|
||||
|
||||
class TreeItem;
|
||||
|
||||
//! [0]
|
||||
class TreeModel : public QAbstractItemModel
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
TreeModel(QObject *parent = 0);
|
||||
TreeModel( QSqlQuery &sqlquery, QObject *parent = 0);
|
||||
~TreeModel();
|
||||
|
||||
QVariant data(const QModelIndex &index, int role) const;
|
||||
Qt::ItemFlags flags(const QModelIndex &index) const;
|
||||
QVariant headerData(int section, Qt::Orientation orientation,
|
||||
int role = Qt::DisplayRole) const;
|
||||
QModelIndex index(int row, int column,
|
||||
const QModelIndex &parent = QModelIndex()) const;
|
||||
QModelIndex parent(const QModelIndex &index) const;
|
||||
QModelIndex indexFromItem(TreeItem * item, int column);
|
||||
/*QModelIndex _indexFromItem(TreeItem * item, int column);
|
||||
int column;*/
|
||||
|
||||
int rowCount(const QModelIndex &parent = QModelIndex()) const;
|
||||
int columnCount(const QModelIndex &parent = QModelIndex()) const;
|
||||
void setupModelData(QString path);
|
||||
QString getDatabase();
|
||||
|
||||
//M<>todos de conveniencia
|
||||
QString getFolderPath(const QModelIndex &folder);
|
||||
|
||||
void setFilter(QString filter, bool includeComics);
|
||||
void resetFilter();
|
||||
bool isFilterEnabled(){return filterEnabled;};
|
||||
private:
|
||||
void setupModelData( QSqlQuery &sqlquery, TreeItem *parent);
|
||||
void setupFilteredModelData( QSqlQuery &sqlquery, TreeItem *parent);
|
||||
void setupFilteredModelData();
|
||||
|
||||
TreeItem *rootItem; //el <20>rbol
|
||||
QMap<unsigned long long int, TreeItem *> items; //relaci<63>n entre folders
|
||||
|
||||
TreeItem *rootBeforeFilter;
|
||||
QMap<unsigned long long int, TreeItem *> filteredItems; //relaci<63>n entre folders
|
||||
|
||||
QString _databasePath;
|
||||
|
||||
bool includeComics;
|
||||
QString filter;
|
||||
bool filterEnabled;
|
||||
signals:
|
||||
void beforeReset();
|
||||
void reset();
|
||||
};
|
||||
//! [0]
|
||||
|
||||
#endif
|
84
YACReaderLibrary/export_comics_info_dialog.cpp
Normal file
84
YACReaderLibrary/export_comics_info_dialog.cpp
Normal file
@ -0,0 +1,84 @@
|
||||
#include "export_comics_info_dialog.h"
|
||||
|
||||
#include <QHBoxLayout>
|
||||
#include <QVBoxLayout>
|
||||
#include <QFileDialog>
|
||||
#include <QDir>
|
||||
|
||||
#include "data_base_management.h"
|
||||
|
||||
ExportComicsInfoDialog::ExportComicsInfoDialog(QWidget *parent)
|
||||
: QDialog(parent)
|
||||
{
|
||||
textLabel = new QLabel(tr("Output file : "));
|
||||
path = new QLineEdit;
|
||||
textLabel->setBuddy(path);
|
||||
|
||||
accept = new QPushButton(tr("Create"));
|
||||
accept->setDisabled(true);
|
||||
connect(accept,SIGNAL(clicked()),this,SLOT(exportComicsInfo()));
|
||||
|
||||
cancel = new QPushButton(tr("Cancel"));
|
||||
connect(cancel,SIGNAL(clicked()),this,SLOT(close()));
|
||||
connect(cancel,SIGNAL(clicked()),this,SIGNAL(rejected()));
|
||||
|
||||
find = new QPushButton(QIcon(":/images/db.png"),"");
|
||||
connect(find,SIGNAL(clicked()),this,SLOT(findPath()));
|
||||
|
||||
QHBoxLayout *libraryLayout = new QHBoxLayout;
|
||||
|
||||
libraryLayout->addWidget(textLabel);
|
||||
libraryLayout->addWidget(path);
|
||||
libraryLayout->addWidget(find);
|
||||
libraryLayout->setStretchFactor(find,0); //TODO
|
||||
|
||||
QHBoxLayout *bottomLayout = new QHBoxLayout;
|
||||
bottomLayout->addStretch();
|
||||
bottomLayout->addWidget(accept);
|
||||
bottomLayout->addWidget(cancel);
|
||||
|
||||
QVBoxLayout *mainLayout = new QVBoxLayout;
|
||||
mainLayout->addLayout(libraryLayout);
|
||||
mainLayout->addWidget(progress=new QLabel());
|
||||
mainLayout->addStretch();
|
||||
mainLayout->addLayout(bottomLayout);
|
||||
|
||||
QHBoxLayout * imgMainLayout = new QHBoxLayout;
|
||||
QLabel * imgLabel = new QLabel(this);
|
||||
QPixmap p(":/images/exportComicsInfo.png");
|
||||
imgLabel->setPixmap(p);
|
||||
imgMainLayout->addWidget(imgLabel);
|
||||
imgMainLayout->addLayout(mainLayout);
|
||||
|
||||
setLayout(imgMainLayout);
|
||||
|
||||
setModal(true);
|
||||
setWindowTitle(tr("Export comics info"));
|
||||
}
|
||||
|
||||
ExportComicsInfoDialog::~ExportComicsInfoDialog()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
void ExportComicsInfoDialog::findPath()
|
||||
{
|
||||
QString s = QFileDialog::getSaveFileName(this,tr("Destination database name"),".","*.ydb");
|
||||
if(!s.isEmpty())
|
||||
{
|
||||
path->setText(s);
|
||||
accept->setEnabled(true);
|
||||
}
|
||||
}
|
||||
|
||||
void ExportComicsInfoDialog::exportComicsInfo()
|
||||
{
|
||||
DataBaseManagement::exportComicsInfo(source,path->text());
|
||||
close();
|
||||
}
|
||||
|
||||
void ExportComicsInfoDialog::close()
|
||||
{
|
||||
path->clear();
|
||||
QDialog::close();
|
||||
}
|
35
YACReaderLibrary/export_comics_info_dialog.h
Normal file
35
YACReaderLibrary/export_comics_info_dialog.h
Normal file
@ -0,0 +1,35 @@
|
||||
#ifndef EXPORT_COMICS_INFO_DIALOG_H
|
||||
#define EXPORT_COMICS_INFO_DIALOG_H
|
||||
|
||||
#include <QDialog>
|
||||
#include <QLabel>
|
||||
#include <QLineEdit>
|
||||
#include <QPushButton>
|
||||
|
||||
|
||||
class ExportComicsInfoDialog : public QDialog
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
ExportComicsInfoDialog(QWidget *parent = 0);
|
||||
~ExportComicsInfoDialog();
|
||||
QString source;
|
||||
|
||||
public slots:
|
||||
void findPath();
|
||||
void exportComicsInfo();
|
||||
void close();
|
||||
|
||||
private:
|
||||
QLabel * progress;
|
||||
QLabel * textLabel;
|
||||
QLineEdit * path;
|
||||
QPushButton * find;
|
||||
QPushButton * accept;
|
||||
QPushButton * cancel;
|
||||
|
||||
|
||||
};
|
||||
|
||||
#endif // EXPORT_COMICS_INFO_DIALOG_H
|
96
YACReaderLibrary/export_library_dialog.cpp
Normal file
96
YACReaderLibrary/export_library_dialog.cpp
Normal file
@ -0,0 +1,96 @@
|
||||
#include "export_library_dialog.h"
|
||||
#include <QHBoxLayout>
|
||||
#include <QVBoxLayout>
|
||||
#include <QFileDialog>
|
||||
#include <QDir>
|
||||
|
||||
ExportLibraryDialog::ExportLibraryDialog(QWidget * parent)
|
||||
:QDialog(parent),progressCount(0)
|
||||
{
|
||||
textLabel = new QLabel(tr("Output folder : "));
|
||||
path = new QLineEdit;
|
||||
textLabel->setBuddy(path);
|
||||
|
||||
accept = new QPushButton(tr("Create"));
|
||||
accept->setDisabled(true);
|
||||
connect(accept,SIGNAL(clicked()),this,SLOT(exportLibrary()));
|
||||
|
||||
cancel = new QPushButton(tr("Cancel"));
|
||||
connect(cancel,SIGNAL(clicked()),this,SLOT(close()));
|
||||
connect(cancel,SIGNAL(clicked()),this,SIGNAL(rejected()));
|
||||
|
||||
find = new QPushButton(QIcon(":/images/comicFolder.png"),"");
|
||||
connect(find,SIGNAL(clicked()),this,SLOT(findPath()));
|
||||
|
||||
QHBoxLayout *libraryLayout = new QHBoxLayout;
|
||||
|
||||
libraryLayout->addWidget(textLabel);
|
||||
libraryLayout->addWidget(path);
|
||||
libraryLayout->addWidget(find);
|
||||
libraryLayout->setStretchFactor(find,0); //TODO
|
||||
|
||||
QHBoxLayout *bottomLayout = new QHBoxLayout;
|
||||
bottomLayout->addStretch();
|
||||
bottomLayout->addWidget(accept);
|
||||
bottomLayout->addWidget(cancel);
|
||||
|
||||
progressBar = new QProgressBar(this);
|
||||
progressBar->setMinimum(0);
|
||||
progressBar->setMaximum(0);
|
||||
progressBar->setTextVisible(false);
|
||||
progressBar->hide();
|
||||
|
||||
QVBoxLayout *mainLayout = new QVBoxLayout;
|
||||
mainLayout->addLayout(libraryLayout);
|
||||
mainLayout->addStretch();
|
||||
mainLayout->addWidget(progressBar);
|
||||
mainLayout->addLayout(bottomLayout);
|
||||
|
||||
QHBoxLayout * imgMainLayout = new QHBoxLayout;
|
||||
QLabel * imgLabel = new QLabel(this);
|
||||
QPixmap p(":/images/exportLibrary.png");
|
||||
imgLabel->setPixmap(p);
|
||||
imgMainLayout->addWidget(imgLabel);
|
||||
imgMainLayout->addLayout(mainLayout);
|
||||
|
||||
setLayout(imgMainLayout);
|
||||
|
||||
setModal(true);
|
||||
setWindowTitle(tr("Create covers package"));
|
||||
t.setInterval(500);
|
||||
connect(&t,SIGNAL(timeout()),this,SLOT(updateProgress()));
|
||||
}
|
||||
|
||||
void ExportLibraryDialog::exportLibrary()
|
||||
{
|
||||
progressBar->show();
|
||||
accept->setEnabled(false);
|
||||
emit exportPath(QDir::cleanPath(path->text()));
|
||||
t.start();
|
||||
}
|
||||
|
||||
void ExportLibraryDialog::findPath()
|
||||
{
|
||||
QString s = QFileDialog::getExistingDirectory(0,tr("Destination directory"),".");
|
||||
if(!s.isEmpty())
|
||||
{
|
||||
path->setText(s);
|
||||
accept->setEnabled(true);
|
||||
}
|
||||
}
|
||||
|
||||
void ExportLibraryDialog::close()
|
||||
{
|
||||
path->clear();
|
||||
progressBar->hide();
|
||||
accept->setEnabled(false);
|
||||
t.stop();
|
||||
progressCount=0;
|
||||
QDialog::close();
|
||||
}
|
||||
|
||||
void ExportLibraryDialog::run()
|
||||
{
|
||||
|
||||
}
|
||||
|
36
YACReaderLibrary/export_library_dialog.h
Normal file
36
YACReaderLibrary/export_library_dialog.h
Normal file
@ -0,0 +1,36 @@
|
||||
#ifndef EXPORT_LIBRARY_DIALOG_H
|
||||
#define EXPORT_LIBRARY_DIALOG_H
|
||||
|
||||
#include <QDialog>
|
||||
#include <QLabel>
|
||||
#include <QLineEdit>
|
||||
#include <QPushButton>
|
||||
#include <QString>
|
||||
#include <QThread>
|
||||
#include <QTimer>
|
||||
#include <QProgressBar>
|
||||
|
||||
class ExportLibraryDialog : public QDialog
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
ExportLibraryDialog(QWidget * parent = 0);
|
||||
public slots:
|
||||
void exportLibrary();
|
||||
void findPath();
|
||||
void close();
|
||||
private:
|
||||
int progressCount;
|
||||
QProgressBar *progressBar;
|
||||
QLabel * textLabel;
|
||||
QLineEdit * path;
|
||||
QPushButton * find;
|
||||
QPushButton * accept;
|
||||
QPushButton * cancel;
|
||||
void run();
|
||||
QTimer t;
|
||||
signals:
|
||||
void exportPath(QString);
|
||||
};
|
||||
|
||||
#endif
|
13
YACReaderLibrary/files.qrc
Normal file
13
YACReaderLibrary/files.qrc
Normal file
@ -0,0 +1,13 @@
|
||||
<RCC>
|
||||
<qresource>
|
||||
<file>../files/about.html</file>
|
||||
<file>../files/helpYACReaderLibrary.html</file>
|
||||
<file>./yacreaderlibrary_es.qm</file>
|
||||
</qresource>
|
||||
|
||||
<qresource lang="es_ES">
|
||||
<file alias="/files/about.html">../files/about_es_ES.html</file>
|
||||
<file alias="/files/helpYACReaderLibrary.html">../files/helpYACReaderLibrary_es_ES.html</file>
|
||||
</qresource>
|
||||
|
||||
</RCC>
|
BIN
YACReaderLibrary/icon.ico
Normal file
BIN
YACReaderLibrary/icon.ico
Normal file
Binary file not shown.
After Width: | Height: | Size: 97 KiB |
2
YACReaderLibrary/icon.rc
Normal file
2
YACReaderLibrary/icon.rc
Normal file
@ -0,0 +1,2 @@
|
||||
IDI_ICON1 ICON DISCARDABLE "icon.ico"
|
||||
IDI_ICON2 ICON DISCARDABLE "icon2.ico"
|
BIN
YACReaderLibrary/icon2.ico
Normal file
BIN
YACReaderLibrary/icon2.ico
Normal file
Binary file not shown.
After Width: | Height: | Size: 97 KiB |
41
YACReaderLibrary/images.qrc
Normal file
41
YACReaderLibrary/images.qrc
Normal file
@ -0,0 +1,41 @@
|
||||
<RCC>
|
||||
<qresource prefix="/" >
|
||||
<file>../images/deleteLibrary.png</file>
|
||||
<file>../images/folder.png</file>
|
||||
<file>../images/help.png</file>
|
||||
<file>../images/icon.png</file>
|
||||
<file>../images/new.png</file>
|
||||
<file>../images/openLibrary.png</file>
|
||||
<file>../images/removeLibrary.png</file>
|
||||
<file>../images/updateLibrary.png</file>
|
||||
<file>../images/setRoot.png</file>
|
||||
<file>../images/expand.png</file>
|
||||
<file>../images/colapse.png</file>
|
||||
<file>../images/comicFolder.png</file>
|
||||
<file>../images/notCover.png</file>
|
||||
<file>../images/edit.png</file>
|
||||
<file>../images/fit.png</file>
|
||||
<file>../images/properties.png</file>
|
||||
<file>../images/options.png</file>
|
||||
<file>../images/flow1.png</file>
|
||||
<file>../images/flow2.png</file>
|
||||
<file>../images/flow3.png</file>
|
||||
<file>../images/importLibrary.png</file>
|
||||
<file>../images/exportLibrary.png</file>
|
||||
<file>../images/open.png</file>
|
||||
<file>../images/coversPackage.png</file>
|
||||
<file>../images/setRead.png</file>
|
||||
<file>../images/setAllRead.png</file>
|
||||
<file>../images/setUnread.png</file>
|
||||
<file>../images/setAllUnread.png</file>
|
||||
<file>../images/showMarks.png</file>
|
||||
<file>../images/importCover.png</file>
|
||||
<file>../images/editComic.png</file>
|
||||
<file>../images/selectAll.png</file>
|
||||
<file>../images/hideComicFlow.png</file>
|
||||
<file>../images/exportComicsInfo.png</file>
|
||||
<file>../images/importComicsInfo.png</file>
|
||||
<file>../images/db.png</file>
|
||||
<file>../images/asignNumber.png</file>
|
||||
</qresource>
|
||||
</RCC>
|
111
YACReaderLibrary/import_comics_info_dialog.cpp
Normal file
111
YACReaderLibrary/import_comics_info_dialog.cpp
Normal file
@ -0,0 +1,111 @@
|
||||
#include "import_comics_info_dialog.h"
|
||||
|
||||
#include <QHBoxLayout>
|
||||
#include <QVBoxLayout>
|
||||
#include <QFileDialog>
|
||||
#include <QProgressBar>
|
||||
|
||||
#include "data_base_management.h"
|
||||
|
||||
ImportComicsInfoDialog::ImportComicsInfoDialog(QWidget *parent)
|
||||
: QDialog(parent)
|
||||
{
|
||||
setModal(true);
|
||||
setWindowTitle(tr("Import comics info"));
|
||||
|
||||
|
||||
textLabel = new QLabel(tr("Info database location : "));
|
||||
path = new QLineEdit;
|
||||
textLabel->setBuddy(path);
|
||||
|
||||
accept = new QPushButton(tr("Import"));
|
||||
accept->setDisabled(true);
|
||||
connect(accept,SIGNAL(clicked()),this,SLOT(import()));
|
||||
|
||||
cancel = new QPushButton(tr("Cancel"));
|
||||
connect(cancel,SIGNAL(clicked()),this,SLOT(close()));
|
||||
//connect(cancel,SIGNAL(clicked()),this,SIGNAL(rejected()));
|
||||
|
||||
find = new QPushButton(QIcon(":/images/coversPackage.png"),"");
|
||||
connect(find,SIGNAL(clicked()),this,SLOT(findPath()));
|
||||
|
||||
QHBoxLayout *libraryLayout = new QHBoxLayout;
|
||||
|
||||
libraryLayout->addWidget(textLabel);
|
||||
libraryLayout->addWidget(path);
|
||||
libraryLayout->addWidget(find);
|
||||
libraryLayout->setStretchFactor(find,0); //TODO
|
||||
|
||||
progressBar = new QProgressBar(this);
|
||||
progressBar->setMinimum(0);
|
||||
progressBar->setMaximum(0);
|
||||
progressBar->setTextVisible(false);
|
||||
progressBar->hide();
|
||||
connect(accept,SIGNAL(progressBar()),this,SLOT(show()));
|
||||
|
||||
QHBoxLayout *bottomLayout = new QHBoxLayout;
|
||||
bottomLayout->addStretch();
|
||||
bottomLayout->addWidget(accept);
|
||||
bottomLayout->addWidget(cancel);
|
||||
|
||||
QVBoxLayout *mainLayout = new QVBoxLayout;
|
||||
mainLayout->addLayout(libraryLayout);
|
||||
mainLayout->addStretch();
|
||||
mainLayout->addWidget(progressBar);
|
||||
mainLayout->addLayout(bottomLayout);
|
||||
|
||||
QHBoxLayout * imgMainLayout = new QHBoxLayout;
|
||||
QLabel * imgLabel = new QLabel(this);
|
||||
QPixmap p(":/images/importComicsInfo.png");
|
||||
imgLabel->setPixmap(p);
|
||||
imgMainLayout->addWidget(imgLabel);
|
||||
imgMainLayout->addLayout(mainLayout);
|
||||
|
||||
setLayout(imgMainLayout);
|
||||
|
||||
setModal(true);
|
||||
}
|
||||
|
||||
ImportComicsInfoDialog::~ImportComicsInfoDialog()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
|
||||
void ImportComicsInfoDialog::findPath()
|
||||
{
|
||||
QString s = QFileDialog::getOpenFileName(0,"Comics Info",".",tr("Comics info file (*.ydb)"));
|
||||
if(!s.isEmpty())
|
||||
{
|
||||
path->setText(s);
|
||||
accept->setEnabled(true);
|
||||
}
|
||||
}
|
||||
|
||||
void ImportComicsInfoDialog::import()
|
||||
{
|
||||
progressBar->show();
|
||||
|
||||
Importer * importer = new Importer();
|
||||
importer->source = path->text();
|
||||
importer->dest = dest;
|
||||
connect(importer,SIGNAL(finished()),this,SLOT(close()));
|
||||
connect(importer,SIGNAL(finished()),this,SLOT(hide()));
|
||||
importer->start();
|
||||
}
|
||||
|
||||
void ImportComicsInfoDialog::close()
|
||||
{
|
||||
path->clear();
|
||||
progressBar->hide();
|
||||
accept->setDisabled(true);
|
||||
QDialog::close();
|
||||
emit(finished(0));
|
||||
}
|
||||
|
||||
void Importer::run()
|
||||
{
|
||||
DataBaseManagement::importComicsInfo(source,dest);
|
||||
}
|
||||
|
||||
|
52
YACReaderLibrary/import_comics_info_dialog.h
Normal file
52
YACReaderLibrary/import_comics_info_dialog.h
Normal file
@ -0,0 +1,52 @@
|
||||
#ifndef IMPORT_COMICS_INFO_DIALOG_H
|
||||
#define IMPORT_COMICS_INFO_DIALOG_H
|
||||
|
||||
#include <QDialog>
|
||||
#include <QDialog>
|
||||
#include <QLabel>
|
||||
#include <QLineEdit>
|
||||
#include <QPushButton>
|
||||
#include <QProgressBar>
|
||||
#include <QThread>
|
||||
|
||||
class Importer : public QThread
|
||||
{
|
||||
public:
|
||||
QString source;
|
||||
QString dest;
|
||||
private:
|
||||
void run();
|
||||
};
|
||||
|
||||
class ImportComicsInfoDialog : public QDialog
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
ImportComicsInfoDialog(QWidget *parent = 0);
|
||||
~ImportComicsInfoDialog();
|
||||
QString dest;
|
||||
|
||||
private:
|
||||
QLabel * nameLabel;
|
||||
QLabel * textLabel;
|
||||
QLabel * destLabel;
|
||||
QLineEdit * path;
|
||||
QLineEdit * destPath;
|
||||
QLineEdit * nameEdit;
|
||||
QPushButton * find;
|
||||
QPushButton * findDest;
|
||||
QPushButton * accept;
|
||||
QPushButton * cancel;
|
||||
QLabel * progress;
|
||||
void setupUI();
|
||||
int progressCount;
|
||||
QProgressBar *progressBar;
|
||||
|
||||
public slots:
|
||||
void findPath();
|
||||
void import();
|
||||
void close();
|
||||
};
|
||||
|
||||
#endif // IMPORT_COMICS_INFO_DIALOG_H
|
144
YACReaderLibrary/import_library_dialog.cpp
Normal file
144
YACReaderLibrary/import_library_dialog.cpp
Normal file
@ -0,0 +1,144 @@
|
||||
#include "import_library_dialog.h"
|
||||
|
||||
#include <QHBoxLayout>
|
||||
#include <QVBoxLayout>
|
||||
#include <QFileDialog>
|
||||
#include <QGridLayout>
|
||||
|
||||
|
||||
ImportLibraryDialog::ImportLibraryDialog(QWidget * parent)
|
||||
:QDialog(parent),progressCount(0)
|
||||
{
|
||||
setupUI();
|
||||
}
|
||||
|
||||
void ImportLibraryDialog::setupUI()
|
||||
{
|
||||
nameLabel = new QLabel(tr("Library Name : "));
|
||||
nameEdit = new QLineEdit;
|
||||
nameLabel->setBuddy(nameEdit);
|
||||
connect(nameEdit,SIGNAL(textChanged(QString)),this,SLOT(nameEntered()));
|
||||
|
||||
textLabel = new QLabel(tr("Package location : "));
|
||||
path = new QLineEdit;
|
||||
textLabel->setBuddy(path);
|
||||
|
||||
destLabel = new QLabel(tr("Destination folder : "));
|
||||
destPath = new QLineEdit;
|
||||
textLabel->setBuddy(destPath);
|
||||
|
||||
accept = new QPushButton(tr("Unpack"));
|
||||
accept->setDisabled(true);
|
||||
connect(accept,SIGNAL(clicked()),this,SLOT(add()));
|
||||
|
||||
cancel = new QPushButton(tr("Cancel"));
|
||||
connect(cancel,SIGNAL(clicked()),this,SLOT(close()));
|
||||
//connect(cancel,SIGNAL(clicked()),this,SIGNAL(rejected()));
|
||||
|
||||
find = new QPushButton(QIcon(":/images/coversPackage.png"),"");
|
||||
connect(find,SIGNAL(clicked()),this,SLOT(findPath()));
|
||||
|
||||
findDest = new QPushButton(QIcon(":/images/open.png"),"");
|
||||
connect(findDest,SIGNAL(clicked()),this,SLOT(findDestination()));
|
||||
|
||||
QGridLayout * content = new QGridLayout;
|
||||
|
||||
content->addWidget(nameLabel,0,0);
|
||||
content->addWidget(nameEdit,0,1);
|
||||
|
||||
content->addWidget(textLabel,1,0);
|
||||
content->addWidget(path,1,1);
|
||||
content->addWidget(find,1,2);
|
||||
content->setColumnStretch(2,0); //TODO
|
||||
|
||||
content->addWidget(destLabel,2,0);
|
||||
content->addWidget(destPath,2,1);
|
||||
content->addWidget(findDest,2,2);
|
||||
//destLayout->setStretchFactor(findDest,0); //TODO
|
||||
|
||||
QHBoxLayout *bottomLayout = new QHBoxLayout;
|
||||
bottomLayout->addStretch();
|
||||
bottomLayout->addWidget(accept);
|
||||
bottomLayout->addWidget(cancel);
|
||||
|
||||
progressBar = new QProgressBar(this);
|
||||
progressBar->setMinimum(0);
|
||||
progressBar->setMaximum(0);
|
||||
progressBar->setTextVisible(false);
|
||||
progressBar->hide();
|
||||
|
||||
QVBoxLayout *mainLayout = new QVBoxLayout;
|
||||
mainLayout->addLayout(content);
|
||||
//mainLayout->addWidget(progress = new QLabel());
|
||||
mainLayout->addStretch();
|
||||
mainLayout->addWidget(progressBar);
|
||||
mainLayout->addLayout(bottomLayout);
|
||||
|
||||
QHBoxLayout * imgMainLayout = new QHBoxLayout;
|
||||
QLabel * imgLabel = new QLabel(this);
|
||||
QPixmap p(":/images/importLibrary.png");
|
||||
imgLabel->setPixmap(p);
|
||||
imgMainLayout->addWidget(imgLabel);
|
||||
imgMainLayout->addLayout(mainLayout);
|
||||
|
||||
setLayout(imgMainLayout);
|
||||
|
||||
setModal(true);
|
||||
setWindowTitle(tr("Extract a catalog"));
|
||||
}
|
||||
|
||||
void ImportLibraryDialog::add()
|
||||
{
|
||||
accept->setEnabled(false);
|
||||
progressBar->show();
|
||||
emit(unpackCLC(QDir::cleanPath(path->text()),QDir::cleanPath(destPath->text()),nameEdit->text()));
|
||||
}
|
||||
|
||||
void ImportLibraryDialog::findPath()
|
||||
{
|
||||
QString s = QFileDialog::getOpenFileName(0,"Covers Package",".",tr("Compresed library covers (*.clc)"));
|
||||
if(!s.isEmpty())
|
||||
{
|
||||
path->setText(s);
|
||||
if(!destPath->text().isEmpty() && !nameEdit->text().isEmpty())
|
||||
accept->setEnabled(true);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void ImportLibraryDialog::findDestination()
|
||||
{
|
||||
QString s = QFileDialog::getExistingDirectory(0,"Folder",".",QFileDialog::ShowDirsOnly);
|
||||
if(!s.isEmpty())
|
||||
{
|
||||
destPath->setText(s);
|
||||
if(!path->text().isEmpty() && !nameEdit->text().isEmpty())
|
||||
accept->setEnabled(true);
|
||||
}
|
||||
}
|
||||
|
||||
void ImportLibraryDialog::nameEntered()
|
||||
{
|
||||
if(!nameEdit->text().isEmpty())
|
||||
{
|
||||
if(!path->text().isEmpty() && !destPath->text().isEmpty())
|
||||
accept->setEnabled(true);
|
||||
}
|
||||
else
|
||||
accept->setEnabled(false);
|
||||
}
|
||||
|
||||
void ImportLibraryDialog::close()
|
||||
{
|
||||
path->clear();
|
||||
destPath->clear();
|
||||
nameEdit->clear();
|
||||
accept->setEnabled(false);
|
||||
progressBar->hide();
|
||||
QDialog::hide();
|
||||
}
|
||||
|
||||
void ImportLibraryDialog::closeEvent ( QCloseEvent * e )
|
||||
{
|
||||
close();
|
||||
}
|
43
YACReaderLibrary/import_library_dialog.h
Normal file
43
YACReaderLibrary/import_library_dialog.h
Normal file
@ -0,0 +1,43 @@
|
||||
#ifndef IMPORT_LIBRARY_DIALOG_H
|
||||
#define IMPORT_LIBRARY_DIALOG_H
|
||||
|
||||
|
||||
#include <QDialog>
|
||||
#include <QLabel>
|
||||
#include <QLineEdit>
|
||||
#include <QPushButton>
|
||||
#include <QThread>
|
||||
#include <QProgressBar>
|
||||
|
||||
class ImportLibraryDialog : public QDialog
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
ImportLibraryDialog(QWidget * parent = 0);
|
||||
private:
|
||||
QLabel * nameLabel;
|
||||
QLabel * textLabel;
|
||||
QLabel * destLabel;
|
||||
QLineEdit * path;
|
||||
QLineEdit * destPath;
|
||||
QLineEdit * nameEdit;
|
||||
QPushButton * find;
|
||||
QPushButton * findDest;
|
||||
QPushButton * accept;
|
||||
QPushButton * cancel;
|
||||
QProgressBar *progressBar;
|
||||
void setupUI();
|
||||
int progressCount;
|
||||
void closeEvent ( QCloseEvent * e );
|
||||
public slots:
|
||||
void add();
|
||||
void findPath();
|
||||
void findDestination();
|
||||
void close();
|
||||
void nameEntered();
|
||||
|
||||
signals:
|
||||
void unpackCLC(QString clc,QString targetFolder, QString name);
|
||||
};
|
||||
|
||||
#endif
|
487
YACReaderLibrary/library_creator.cpp
Normal file
487
YACReaderLibrary/library_creator.cpp
Normal file
@ -0,0 +1,487 @@
|
||||
#include "library_creator.h"
|
||||
#include "custom_widgets.h"
|
||||
|
||||
#include <QMutex>
|
||||
#include <QDebug>
|
||||
#include <QSqlQuery>
|
||||
#include <QSqlRecord>
|
||||
#include "data_base_management.h"
|
||||
#include "qnaturalsorting.h"
|
||||
|
||||
#include <algorithm>
|
||||
using namespace std;
|
||||
|
||||
//QMutex mutex;
|
||||
|
||||
|
||||
|
||||
/*int numThreads = 0;
|
||||
QWaitCondition waitCondition;
|
||||
QMutex mutex;
|
||||
*/
|
||||
|
||||
|
||||
//--------------------------------------------------------------------------------
|
||||
LibraryCreator::LibraryCreator()
|
||||
{
|
||||
_nameFilter << "*.cbr" << "*.cbz" << "*.rar" << "*.zip" << "*.tar";
|
||||
}
|
||||
|
||||
void LibraryCreator::createLibrary(const QString &source, const QString &target)
|
||||
{
|
||||
processLibrary(source,target);
|
||||
}
|
||||
|
||||
void LibraryCreator::updateLibrary(const QString &source, const QString &target)
|
||||
{
|
||||
processLibrary(source,target);
|
||||
}
|
||||
|
||||
void LibraryCreator::processLibrary(const QString & source, const QString & target)
|
||||
{
|
||||
_source = source;
|
||||
_target = target;
|
||||
//if(!(QFile(target+"/library.ydb").exists())) //TODO, no sirve s<>lo con realizar el update, hay que validar la base de datos y determinar si existe "/covers"
|
||||
if(DataBaseManagement::checkValidDB(target+"/library.ydb")=="")
|
||||
{
|
||||
//se limpia el directorio ./yacreaderlibrary
|
||||
delTree(target);
|
||||
_mode = CREATOR;
|
||||
}
|
||||
else
|
||||
_mode = UPDATER;
|
||||
}
|
||||
|
||||
|
||||
//
|
||||
void LibraryCreator::run()
|
||||
{
|
||||
stopRunning = false;
|
||||
|
||||
if(_mode == CREATOR)
|
||||
{
|
||||
_currentPathFolders.clear();
|
||||
_currentPathFolders.append(Folder(1,1,"root","/"));
|
||||
//se crean los directorios .yacreaderlibrary y .yacreaderlibrary/covers
|
||||
QDir dir;
|
||||
dir.mkpath(_target+"/covers");
|
||||
|
||||
//se crea la base de datos .yacreaderlibrary/library.ydb
|
||||
_database = DataBaseManagement::createDatabase("library",_target);//
|
||||
/*if(!_database.open())
|
||||
return; //TODO avisar del problema
|
||||
|
||||
QSqlQuery pragma("PRAGMA foreign_keys = ON",_database);*/
|
||||
_database.transaction();
|
||||
//se crea la librer<65>a
|
||||
create(QDir(_source));
|
||||
_database.commit();
|
||||
_database.close();
|
||||
QSqlDatabase::removeDatabase(_database.connectionName());
|
||||
emit(created());
|
||||
}
|
||||
else
|
||||
{
|
||||
_currentPathFolders.clear();
|
||||
_currentPathFolders.append(Folder(1,1,"root","/"));
|
||||
_database = DataBaseManagement::loadDatabase(_target);
|
||||
//_database.setDatabaseName(_target+"/library.ydb");
|
||||
/*if(!_database.open())
|
||||
return; //TODO avisar del problema*/
|
||||
//QSqlQuery pragma("PRAGMA foreign_keys = ON",_database);
|
||||
_database.transaction();
|
||||
update(QDir(_source));
|
||||
_database.commit();
|
||||
_database.close();
|
||||
QSqlDatabase::removeDatabase(_target);
|
||||
emit(updated());
|
||||
}
|
||||
msleep(100);//TODO try to solve the problem with the udpate dialog
|
||||
emit(finished());
|
||||
}
|
||||
|
||||
void LibraryCreator::stop()
|
||||
{
|
||||
_database.commit(); //TODO check
|
||||
stopRunning = true;
|
||||
}
|
||||
|
||||
//retorna el id del ultimo de los folders
|
||||
qulonglong LibraryCreator::insertFolders()
|
||||
{
|
||||
QList<Folder>::iterator i;
|
||||
int currentId = 0;
|
||||
for (i = _currentPathFolders.begin(); i != _currentPathFolders.end(); ++i)
|
||||
{
|
||||
if(!(i->knownId))
|
||||
{
|
||||
i->setFather(currentId);
|
||||
currentId = i->insert(_database);//insertFolder(currentId,*i);
|
||||
i->setId(currentId);
|
||||
}
|
||||
else
|
||||
{
|
||||
currentId = i->id;
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*qulonglong LibraryCreator::insertFolder(qulonglong parentId,const Folder & folder)
|
||||
{
|
||||
QSqlQuery query(_database);
|
||||
query.prepare("INSERT INTO folder (parentId, name, path) "
|
||||
"VALUES (:parentId, :name, :path)");
|
||||
query.bindValue(":parentId", parentId);
|
||||
query.bindValue(":name", folder.name);
|
||||
query.bindValue(":path", folder.path);
|
||||
query.exec();
|
||||
return query.lastInsertId().toULongLong();
|
||||
}*/
|
||||
|
||||
/*qulonglong LibraryCreator::insertComic(const Comic & comic)
|
||||
{
|
||||
//TODO comprobar si ya hay comic info con ese hash
|
||||
QSqlQuery comicInfoInsert(_database);
|
||||
comicInfoInsert.prepare("INSERT INTO comic_info (hash) "
|
||||
"VALUES (:hash)");
|
||||
comicInfoInsert.bindValue(":hash", comic.hash);
|
||||
comicInfoInsert.exec();
|
||||
qulonglong comicInfoId =comicInfoInsert.lastInsertId().toULongLong();
|
||||
|
||||
QSqlQuery query(_database);
|
||||
query.prepare("INSERT INTO comic (parentId, comicInfoId, fileName, path) "
|
||||
"VALUES (:parentId,:comicInfoId,:name, :path)");
|
||||
query.bindValue(":parentId", comic.parentId);
|
||||
query.bindValue(":comicInfoId", comicInfoId);
|
||||
query.bindValue(":name", comic.name);
|
||||
query.bindValue(":path", comic.path);
|
||||
query.exec();
|
||||
return query.lastInsertId().toULongLong();
|
||||
}*/
|
||||
|
||||
void LibraryCreator::create(QDir dir)
|
||||
{
|
||||
dir.setNameFilters(_nameFilter);
|
||||
dir.setFilter(QDir::AllDirs|QDir::Files|QDir::NoDotAndDotDot);
|
||||
QFileInfoList list = dir.entryInfoList();
|
||||
for (int i = 0; i < list.size(); ++i)
|
||||
{
|
||||
if(stopRunning)
|
||||
return;
|
||||
QFileInfo fileInfo = list.at(i);
|
||||
QString fileName = fileInfo.fileName();
|
||||
QString relativePath = QDir::cleanPath(fileInfo.absoluteFilePath()).remove(_source);
|
||||
if(fileInfo.isDir())
|
||||
{
|
||||
//se a<>ade al path actual el folder, a<>n no se sabe si habr<62> que a<>adirlo a la base de datos
|
||||
_currentPathFolders.append(Folder(fileInfo.fileName(),relativePath));
|
||||
create(QDir(fileInfo.absoluteFilePath()));
|
||||
//una vez importada la informaci<63>n del folder, se retira del path actual ya que no volver<65> a ser visitado
|
||||
_currentPathFolders.pop_back();
|
||||
}
|
||||
else
|
||||
{
|
||||
insertComic(relativePath,fileInfo);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool LibraryCreator::checkCover(const QString & hash)
|
||||
{
|
||||
return QFile::exists(_target+"/covers/"+hash+".jpg");
|
||||
}
|
||||
|
||||
void LibraryCreator::insertComic(const QString & relativePath,const QFileInfo & fileInfo)
|
||||
{
|
||||
//en este punto sabemos que todos los folders que hay en _currentPath, deber<65>an estar a<>adidos a la base de datos
|
||||
insertFolders();
|
||||
emit(coverExtracted(relativePath));
|
||||
|
||||
//Se calcula el hash del c<>mic
|
||||
|
||||
QCryptographicHash crypto(QCryptographicHash::Sha1);
|
||||
QFile file(fileInfo.absoluteFilePath());
|
||||
file.open(QFile::ReadOnly);
|
||||
crypto.addData(file.read(524288));
|
||||
file.close();
|
||||
//hash Sha1 del primer 0.5MB + filesize
|
||||
QString hash = QString(crypto.result().toHex().constData()) + QString::number(fileInfo.size());
|
||||
Comic comic(_currentPathFolders.last().id,fileInfo.fileName(),relativePath,hash,_database);
|
||||
int numPages;
|
||||
|
||||
if(! ( comic.hasCover() && checkCover(hash)))
|
||||
{
|
||||
ThumbnailCreator tc(QDir::cleanPath(fileInfo.absoluteFilePath()),_target+"/covers/"+hash+".jpg",*comic.info.coverPage);
|
||||
//ThumbnailCreator tc(QDir::cleanPath(fileInfo.absoluteFilePath()),_target+"/covers/"+fileInfo.fileName()+".jpg");
|
||||
tc.create();
|
||||
numPages = tc.getNumPages();
|
||||
}
|
||||
comic.info.setNumPages(numPages);
|
||||
comic.insert(_database);
|
||||
}
|
||||
|
||||
void LibraryCreator::update(QDir dirS)
|
||||
{
|
||||
dirS.setNameFilters(_nameFilter);
|
||||
dirS.setFilter(QDir::AllDirs|QDir::Files|QDir::NoDotAndDotDot);
|
||||
dirS.setSorting(QDir::Name|QDir::IgnoreCase|QDir::LocaleAware|QDir::DirsFirst); //TODO la ordenaci<63>n debe ser igual que en la base de datos
|
||||
//TODO obtener primero los directorios, despu<70>s los ficheros, ordenar por separado y concatenar
|
||||
QFileInfoList listS = dirS.entryInfoList();
|
||||
|
||||
QList<LibraryItem *> folders = Folder::getFoldersFromParent(_currentPathFolders.last().id,_database);
|
||||
QList<LibraryItem *> comics = Comic::getComicsFromParent(_currentPathFolders.last().id,_database);
|
||||
|
||||
QList <LibraryItem *> listD;
|
||||
listD.append(folders);
|
||||
listD.append(comics);
|
||||
|
||||
int lenghtS = listS.size();
|
||||
int lenghtD = listD.size();
|
||||
int maxLenght = qMax(lenghtS,lenghtD);
|
||||
|
||||
bool updated;
|
||||
int i,j;
|
||||
for (i=0,j=0; (i < lenghtS)||(j < lenghtD);)
|
||||
{
|
||||
if(stopRunning)
|
||||
return;
|
||||
updated = false;
|
||||
if(i>=lenghtS) //finished source files/dirs
|
||||
{
|
||||
//delete listD //from j
|
||||
for(;j<lenghtD;j++)
|
||||
{
|
||||
if(stopRunning)
|
||||
return;
|
||||
listD.at(j)->removeFromDB(_database);
|
||||
}
|
||||
updated = true;
|
||||
}
|
||||
if(j>=lenghtD) //finished library files/dirs
|
||||
{
|
||||
//create listS //from i
|
||||
for(;i<lenghtS;i++)
|
||||
{
|
||||
if(stopRunning)
|
||||
return;
|
||||
QFileInfo fileInfoS = listS.at(i);
|
||||
if(fileInfoS.isDir()) //create folder
|
||||
{
|
||||
_currentPathFolders.append(Folder(fileInfoS.fileName(),QDir::cleanPath(fileInfoS.absoluteFilePath()).remove(_source))); //folder actual no est<73> en la BD
|
||||
create(QDir(fileInfoS.absoluteFilePath()));
|
||||
_currentPathFolders.pop_back();
|
||||
}
|
||||
else //create comic
|
||||
{
|
||||
insertComic(QDir::cleanPath(fileInfoS.absoluteFilePath()).remove(_source),fileInfoS);
|
||||
}
|
||||
}
|
||||
updated = true;
|
||||
}
|
||||
if(!updated)
|
||||
{
|
||||
QFileInfo fileInfoS = listS.at(i);
|
||||
LibraryItem * fileInfoD = listD.at(j);
|
||||
QString nameS = QDir::cleanPath(fileInfoS.absoluteFilePath()).remove(QDir::cleanPath(fileInfoS.absolutePath())); //remove source
|
||||
QString nameD = "/"+fileInfoD->name;
|
||||
|
||||
int comparation = QString::localeAwareCompare(nameS,nameD);
|
||||
if(fileInfoS.isDir()&&fileInfoD->isDir())
|
||||
if(comparation == 0)//same folder, update
|
||||
{
|
||||
_currentPathFolders.append(*static_cast<Folder *>(fileInfoD));//fileInfoD conoce su padre y su id
|
||||
update(QDir(fileInfoS.absoluteFilePath()));
|
||||
_currentPathFolders.pop_back();
|
||||
i++;
|
||||
j++;
|
||||
}
|
||||
else
|
||||
if(comparation < 0) //nameS doesn't exist on DB
|
||||
{
|
||||
if(nameS!="/.yacreaderlibrary")
|
||||
{
|
||||
_currentPathFolders.append(Folder(fileInfoS.fileName(),QDir::cleanPath(fileInfoS.absoluteFilePath()).remove(_source)));
|
||||
create(QDir(fileInfoS.absoluteFilePath()));
|
||||
_currentPathFolders.pop_back();
|
||||
}
|
||||
i++;
|
||||
}
|
||||
else //nameD no longer avaliable on Source folder...
|
||||
{
|
||||
if(nameS!="/.yacreaderlibrary")
|
||||
{
|
||||
fileInfoD->removeFromDB(_database);
|
||||
j++;
|
||||
}
|
||||
else
|
||||
i++; //skip library directory
|
||||
}
|
||||
else // one of them(or both) is a file
|
||||
if(fileInfoS.isDir()) //this folder doesn't exist on library
|
||||
{
|
||||
if(nameS!="/.yacreaderlibrary") //skip .yacreaderlibrary folder
|
||||
{
|
||||
_currentPathFolders.append(Folder(fileInfoS.fileName(),QDir::cleanPath(fileInfoS.absoluteFilePath()).remove(_source)));
|
||||
create(QDir(fileInfoS.absoluteFilePath()));
|
||||
_currentPathFolders.pop_back();
|
||||
}
|
||||
i++;
|
||||
}
|
||||
else
|
||||
if(fileInfoD->isDir()) //delete this folder from library
|
||||
{
|
||||
fileInfoD->removeFromDB(_database);
|
||||
j++;
|
||||
}
|
||||
else //both are files //BUG on windows (no case sensitive)
|
||||
{
|
||||
//nameD.remove(nameD.size()-4,4);
|
||||
int comparation = QString::localeAwareCompare(nameS,nameD);
|
||||
if(comparation < 0) //create new thumbnail
|
||||
{
|
||||
insertComic(QDir::cleanPath(fileInfoS.absoluteFilePath()).remove(_source),fileInfoS);
|
||||
i++;
|
||||
}
|
||||
else
|
||||
{
|
||||
if(comparation > 0) //delete thumbnail
|
||||
{
|
||||
fileInfoD->removeFromDB(_database);
|
||||
j++;
|
||||
}
|
||||
else //same file
|
||||
{
|
||||
if(fileInfoS.isFile() && !fileInfoD->isDir())
|
||||
{
|
||||
//TODO comprobar fechas + tama<6D>o
|
||||
//if(fileInfoS.lastModified()>fileInfoD.lastModified())
|
||||
//{
|
||||
// dirD.mkpath(_target+(QDir::cleanPath(fileInfoS.absolutePath()).remove(_source)));
|
||||
// emit(coverExtracted(QDir::cleanPath(fileInfoS.absoluteFilePath()).remove(_source)));
|
||||
// ThumbnailCreator tc(QDir::cleanPath(fileInfoS.absoluteFilePath()),_target+(QDir::cleanPath(fileInfoS.absoluteFilePath()).remove(_source))+".jpg");
|
||||
// tc.create();
|
||||
//}
|
||||
}
|
||||
i++;j++;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
ThumbnailCreator::ThumbnailCreator(QString fileSource, QString target="", int coverPage)
|
||||
:_fileSource(fileSource),_target(target),_numPages(0),_coverPage(coverPage)
|
||||
{
|
||||
}
|
||||
|
||||
void ThumbnailCreator::create()
|
||||
{
|
||||
_7z = new QProcess();
|
||||
QStringList attributes;
|
||||
attributes << "l" << "-ssc-" << "-r" << _fileSource << "*.jpg" << "*.jpeg" << "*.png" << "*.gif" << "*.tiff" << "*.tif" << "*.bmp";
|
||||
//connect(_7z,SIGNAL(finished(int,QProcess::ExitStatus)),this,SLOT(loadFirstImage(void)));
|
||||
connect(_7z,SIGNAL(error(QProcess::ProcessError)),this,SIGNAL(openingError(QProcess::ProcessError)));
|
||||
_7z->start(QCoreApplication::applicationDirPath()+"/utils/7z",attributes);
|
||||
_7z->waitForFinished(60000);
|
||||
|
||||
QRegExp rx("[0-9]{4}-[0-9]{2}-[0-9]{2}[ ]+[0-9]{2}:[0-9]{2}:[0-9]{2}[ ]+.{5}[ ]+([0-9]+)[ ]+([0-9]+)[ ]+(.+)");
|
||||
|
||||
QString ba = QString::fromUtf8(_7z->readAllStandardOutput());
|
||||
QList<QString> lines = ba.split('\n');
|
||||
QString line;
|
||||
_currentName = "ZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZ"; //TODO
|
||||
QString name;
|
||||
if(_coverPage == 1)
|
||||
{
|
||||
foreach(line,lines)
|
||||
{
|
||||
if(rx.indexIn(line)!=-1)
|
||||
{
|
||||
name = rx.cap(3).trimmed();
|
||||
if(naturalSortLessThanCI(name,_currentName))
|
||||
_currentName = name;
|
||||
_numPages++;
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
QList<QString> names;
|
||||
foreach(line,lines)
|
||||
{
|
||||
if(rx.indexIn(line)!=-1)
|
||||
{
|
||||
name = rx. cap(3).trimmed();
|
||||
names.append(name);
|
||||
}
|
||||
}
|
||||
std::sort(names.begin(),names.end(),naturalSortLessThanCI);
|
||||
_currentName = names[_coverPage-1];
|
||||
}
|
||||
delete _7z;
|
||||
attributes.clear();
|
||||
_currentName = QDir::fromNativeSeparators(_currentName).split('/').last(); //separator fixed.
|
||||
#ifdef Q_WS_WIN
|
||||
attributes << "e" << "-so" << "-r" << _fileSource << QString(_currentName.toLocal8Bit().constData()); //TODO platform dependency?? OEM 437
|
||||
#else
|
||||
attributes << "e" << "-so" << "-r" << _fileSource << _currentName; //TODO platform dependency?? OEM 437
|
||||
#endif
|
||||
_7z = new QProcess();
|
||||
connect(_7z,SIGNAL(error(QProcess::ProcessError)),this,SIGNAL(openingError(QProcess::ProcessError)));
|
||||
//connect(_7z,SIGNAL(finished(int,QProcess::ExitStatus)),this,SLOT(writeThumbnail(void)));
|
||||
_7z->start(QCoreApplication::applicationDirPath()+"/utils/7z",attributes);
|
||||
_7z->waitForFinished(60000);
|
||||
|
||||
QByteArray image = _7z->readAllStandardOutput();
|
||||
QString error = _7z->readAllStandardError();
|
||||
QImage p;
|
||||
if(_target=="")
|
||||
{
|
||||
if(!_cover.loadFromData(image))
|
||||
_cover.load(":/images/notCover.png");
|
||||
}
|
||||
else
|
||||
{
|
||||
if(p.loadFromData(image))
|
||||
{
|
||||
//TODO calculate aspect ratio
|
||||
QImage scaled;
|
||||
if(p.width()>p.height()) //landscape??
|
||||
scaled = p.scaledToWidth(640,Qt::SmoothTransformation);
|
||||
else
|
||||
scaled = p.scaledToWidth(480,Qt::SmoothTransformation);
|
||||
scaled.save(_target,0,75);
|
||||
}
|
||||
else
|
||||
{
|
||||
p.load(":/images/notCover.png");
|
||||
p.save(_target);
|
||||
//TODO save a default image.
|
||||
}
|
||||
}
|
||||
delete _7z;
|
||||
}
|
||||
|
||||
/*void ThumbnailCreator::openingError(QProcess::ProcessError error)
|
||||
{
|
||||
//TODO : move to the gui thread
|
||||
switch(error)
|
||||
{
|
||||
case QProcess::FailedToStart:
|
||||
QMessageBox::critical(NULL,tr("7z not found"),tr("7z wasn't found in your PATH."));
|
||||
break;
|
||||
case QProcess::Crashed:
|
||||
QMessageBox::critical(NULL,tr("7z crashed"),tr("7z crashed."));
|
||||
break;
|
||||
case QProcess::ReadError:
|
||||
QMessageBox::critical(NULL,tr("7z reading"),tr("problem reading from 7z"));
|
||||
break;
|
||||
case QProcess::UnknownError:
|
||||
QMessageBox::critical(NULL,tr("7z problem"),tr("Unknown error 7z"));
|
||||
break;
|
||||
default:
|
||||
//TODO
|
||||
break;
|
||||
}
|
||||
}*/
|
81
YACReaderLibrary/library_creator.h
Normal file
81
YACReaderLibrary/library_creator.h
Normal file
@ -0,0 +1,81 @@
|
||||
#ifndef __LIBRARY_CREATOR_H
|
||||
#define __LIBRARY_CREATOR_H
|
||||
|
||||
#include <QObject>
|
||||
#include <QString>
|
||||
#include <QDir>
|
||||
#include <QFile>
|
||||
#include <QByteArray>
|
||||
#include <QRegExp>
|
||||
#include <QProcess>
|
||||
#include <QtCore>
|
||||
#include <QtGui>
|
||||
#include <QMutex>
|
||||
#include <QThread>
|
||||
#include <QSqlDatabase>
|
||||
|
||||
#include "folder.h"
|
||||
#include "comic.h"
|
||||
|
||||
|
||||
class LibraryCreator : public QThread
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
LibraryCreator();
|
||||
void createLibrary(const QString & source, const QString & target);
|
||||
void updateLibrary(const QString & source, const QString & target);
|
||||
void stop();
|
||||
private:
|
||||
void processLibrary(const QString & source, const QString & target);
|
||||
enum Mode {CREATOR,UPDATER};
|
||||
//atributos "globales" durante el proceso de creaci<63>n y actualizaci<63>n
|
||||
enum Mode _mode;
|
||||
QString _source;
|
||||
QString _target;
|
||||
QStringList _nameFilter;
|
||||
QSqlDatabase _database;
|
||||
QList<Folder> _currentPathFolders; //lista de folders en el orden en el que est<73>n siendo explorados, el <20>ltimo es el folder actual
|
||||
//recursive method
|
||||
void create(QDir currentDirectory);
|
||||
void update(QDir currentDirectory);
|
||||
void run();
|
||||
qulonglong insertFolders();//devuelve el id del <20>ltimo folder a<>adido (<28>ltimo en la ruta)
|
||||
bool checkCover(const QString & hash);
|
||||
void insertComic(const QString & relativePath,const QFileInfo & fileInfo);
|
||||
//qulonglong insertFolder(qulonglong parentId,const Folder & folder);
|
||||
//qulonglong insertComic(const Comic & comic);
|
||||
bool stopRunning;
|
||||
signals:
|
||||
void finished();
|
||||
void coverExtracted(QString);
|
||||
void folderUpdated(QString);
|
||||
void updated();
|
||||
void created();
|
||||
};
|
||||
|
||||
class ThumbnailCreator : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
ThumbnailCreator(QString fileSource, QString target, int coverPage = 1);
|
||||
private:
|
||||
QProcess * _7z;
|
||||
QString _fileSource;
|
||||
QString _target;
|
||||
QString _currentName;
|
||||
int _numPages;
|
||||
QPixmap _cover;
|
||||
int _coverPage;
|
||||
|
||||
public slots:
|
||||
void create();
|
||||
int getNumPages(){return _numPages;};
|
||||
QPixmap getCover(){return _cover;};
|
||||
signals:
|
||||
void openingError(QProcess::ProcessError error);
|
||||
|
||||
};
|
||||
|
||||
#endif
|
1328
YACReaderLibrary/library_window.cpp
Normal file
1328
YACReaderLibrary/library_window.cpp
Normal file
File diff suppressed because it is too large
Load Diff
207
YACReaderLibrary/library_window.h
Normal file
207
YACReaderLibrary/library_window.h
Normal file
@ -0,0 +1,207 @@
|
||||
#ifndef __LIBRARYWINDOW_H
|
||||
#define __LIBRARYWINDOW_H
|
||||
|
||||
#include <QMainWindow>
|
||||
#include <QTableView>
|
||||
#include <QTreeView>
|
||||
#include <QModelIndex>
|
||||
#include <QDirModel>
|
||||
#include <QAction>
|
||||
#include <QToolBar>
|
||||
#include <QComboBox>
|
||||
#include <QThread>
|
||||
#include <QFileInfoList>
|
||||
|
||||
#include "create_library_dialog.h"
|
||||
#include "add_library_dialog.h"
|
||||
#include "library_creator.h"
|
||||
#include "comic_flow.h"
|
||||
#include "custom_widgets.h"
|
||||
#include "rename_library_dialog.h"
|
||||
#include "properties_dialog.h"
|
||||
#include "options_dialog.h"
|
||||
#include "export_comics_info_dialog.h"
|
||||
#include "import_comics_info_dialog.h"
|
||||
#include "export_library_dialog.h"
|
||||
#include "import_library_dialog.h"
|
||||
#include "package_manager.h"
|
||||
#include "treemodel.h"
|
||||
#include "tablemodel.h"
|
||||
#include "treeitem.h"
|
||||
|
||||
class LibraryWindow : public QMainWindow
|
||||
{
|
||||
Q_OBJECT
|
||||
private:
|
||||
QWidget * left;
|
||||
QSplitter * sVertical;
|
||||
CreateLibraryDialog * createLibraryDialog;
|
||||
UpdateLibraryDialog * updateLibraryDialog;
|
||||
ExportLibraryDialog * exportLibraryDialog;
|
||||
ImportLibraryDialog * importLibraryDialog;
|
||||
ExportComicsInfoDialog * exportComicsInfoDialog;
|
||||
ImportComicsInfoDialog * importComicsInfoDialog;
|
||||
AddLibraryDialog * addLibraryDialog;
|
||||
LibraryCreator * libraryCreator;
|
||||
HelpAboutDialog * had;
|
||||
RenameLibraryDialog * renameLibraryDialog;
|
||||
PropertiesDialog * propertiesDialog;
|
||||
bool fullscreen;
|
||||
bool importedCovers; //if true, the library is read only (not updates,open comic or properties)
|
||||
YACReaderTreeSearch * proxyFilter;
|
||||
YACReaderSortComics * proxySort;
|
||||
PackageManager * packageManager;
|
||||
|
||||
ComicFlow * comicFlow;
|
||||
QSize slideSizeW;
|
||||
QSize slideSizeF;
|
||||
//search filter
|
||||
QLineEdit * foldersFilter;
|
||||
TreeItem * index; //index al que hay que hacer scroll despu<70>s de pulsar sobre un folder filtrado
|
||||
int column;
|
||||
QString previousFilter;
|
||||
QPushButton * clearFoldersFilter;
|
||||
QCheckBox * includeComicsCheckBox;
|
||||
//-------------
|
||||
QWidget *comics;
|
||||
QTableView * comicView;
|
||||
QTreeView * foldersView;
|
||||
QComboBox * selectedLibrary;
|
||||
TreeModel * dm;
|
||||
QItemSelectionModel * sm;
|
||||
TableModel * dmCV;
|
||||
//QStringList paths;
|
||||
QMap<QString,QString> libraries;
|
||||
QLabel * fullScreenToolTip;
|
||||
YACReaderIconProvider fip;
|
||||
|
||||
bool fetching;
|
||||
|
||||
int i;
|
||||
unsigned int skip;
|
||||
|
||||
QAction * openComicAction;
|
||||
QAction * showPropertiesAction;
|
||||
QAction * createLibraryAction;
|
||||
QAction * openLibraryAction;
|
||||
|
||||
QAction * exportComicsInfo;
|
||||
QAction * importComicsInfo;
|
||||
|
||||
QAction * exportLibraryAction;
|
||||
QAction * importLibraryAction;
|
||||
|
||||
QAction * updateLibraryAction;
|
||||
QAction * deleteLibraryAction;
|
||||
QAction * removeLibraryAction;
|
||||
QAction * helpAboutAction;
|
||||
QAction * renameLibraryAction;
|
||||
QAction * toggleFullScreenAction;
|
||||
QAction * optionsAction;
|
||||
|
||||
//tree actions
|
||||
QAction * setRootIndexAction;
|
||||
QAction * expandAllNodesAction;
|
||||
QAction * colapseAllNodesAction;
|
||||
|
||||
QAction * openContainingFolderAction;
|
||||
QAction * openContainingFolderComicAction;
|
||||
QAction * setAsReadAction;
|
||||
QAction * setAsNonReadAction;
|
||||
QAction * setAllAsReadAction;
|
||||
QAction * setAllAsNonReadAction;
|
||||
QAction * showHideMarksAction;
|
||||
|
||||
//edit info actions
|
||||
QAction * selectAllComicsAction;
|
||||
QAction * editSelectedComicsAction;
|
||||
QAction * asignOrderActions;
|
||||
QAction * forceConverExtractedAction;
|
||||
QAction * hideComicViewAction;
|
||||
|
||||
|
||||
QToolBar * libraryToolBar;
|
||||
QToolBar * treeActions;
|
||||
QToolBar * comicsToolBar;
|
||||
QToolBar * editInfoToolBar;
|
||||
|
||||
OptionsDialog * optionsDialog;
|
||||
|
||||
QString libraryPath;
|
||||
QString comicsPath;
|
||||
|
||||
QString _lastAdded;
|
||||
QString _sourceLastAdded;
|
||||
|
||||
QModelIndex _rootIndex;
|
||||
QModelIndex _rootIndexCV;
|
||||
|
||||
quint64 _comicIdEdited;
|
||||
|
||||
void setupUI();
|
||||
void createActions();
|
||||
void createToolBars();
|
||||
void createMenus();
|
||||
void createConnections();
|
||||
void doLayout();
|
||||
void doDialogs();
|
||||
void doModels();
|
||||
|
||||
void disableAllActions();
|
||||
void disableActions();
|
||||
void enableActions();
|
||||
void enableLibraryActions();
|
||||
|
||||
QString currentPath();
|
||||
public:
|
||||
LibraryWindow();
|
||||
public slots:
|
||||
void loadLibrary(const QString & path);
|
||||
void loadCovers(const QModelIndex & mi);
|
||||
void reloadCovers();
|
||||
void centerComicFlow(const QModelIndex & mi);
|
||||
void updateComicView(int i);
|
||||
void openComic();
|
||||
void createLibrary();
|
||||
void create(QString source,QString dest, QString name);
|
||||
void showAddLibrary();
|
||||
void openLibrary(QString path, QString name);
|
||||
void loadLibraries();
|
||||
void saveLibraries();
|
||||
void reloadCurrentLibrary();
|
||||
void openLastCreated();
|
||||
void updateLibrary();
|
||||
void deleteLibrary();
|
||||
void openContainingFolder();
|
||||
void openContainingFolderComic();
|
||||
void deleteCurrentLibrary();
|
||||
void removeLibrary();
|
||||
void renameLibrary();
|
||||
void rename(QString newName);
|
||||
void cancelCreating();
|
||||
void stopLibraryCreator();
|
||||
void setRootIndex();
|
||||
void toggleFullScreen();
|
||||
void toNormal();
|
||||
void toFullScreen();
|
||||
void setFoldersFilter(QString filter);
|
||||
void showProperties();
|
||||
void exportLibrary(QString destPath);
|
||||
void importLibrary(QString clc,QString destPath,QString name);
|
||||
void reloadOptions();
|
||||
void updateFoldersView(QString);
|
||||
void setCurrentComicReaded();
|
||||
void setCurrentComicUnreaded();
|
||||
void setComicsReaded();
|
||||
void setComicsUnreaded();
|
||||
void searchInFiles(int);
|
||||
void hideComicFlow(bool hide);
|
||||
void showExportComicsInfo();
|
||||
void showImportComicsInfo();
|
||||
void asignNumbers();
|
||||
};
|
||||
|
||||
#endif
|
||||
|
||||
|
||||
|
25
YACReaderLibrary/main.cpp
Normal file
25
YACReaderLibrary/main.cpp
Normal file
@ -0,0 +1,25 @@
|
||||
#include "library_window.h"
|
||||
|
||||
#include <QApplication>
|
||||
//#include "startup.h"
|
||||
#define PICTUREFLOW_QT4 1
|
||||
|
||||
int main( int argc, char ** argv )
|
||||
{
|
||||
QApplication app( argc, argv );
|
||||
|
||||
QTranslator translator;
|
||||
QString sufix = QLocale::system().name();
|
||||
translator.load(":/yacreaderlibrary_"+sufix);
|
||||
app.installTranslator(&translator);
|
||||
app.setApplicationName("YACReaderLibrary");
|
||||
|
||||
QMainWindow * mw = new LibraryWindow();
|
||||
mw->resize(800,480);
|
||||
mw->showMaximized();
|
||||
|
||||
//Startup * s = new Startup();
|
||||
//s->start();
|
||||
|
||||
return app.exec();
|
||||
}
|
148
YACReaderLibrary/options_dialog.cpp
Normal file
148
YACReaderLibrary/options_dialog.cpp
Normal file
@ -0,0 +1,148 @@
|
||||
#include "options_dialog.h"
|
||||
#include <QVBoxLayout>
|
||||
#include <QHBoxLayout>
|
||||
#include <QFileDialog>
|
||||
#include <QGroupBox>
|
||||
#include <QRadioButton>
|
||||
#include <QTextStream>
|
||||
#include <QCoreApplication>
|
||||
#include <QFile>
|
||||
#include <QMessageBox>
|
||||
|
||||
PictureFlow::FlowType flowType = PictureFlow::Strip;
|
||||
|
||||
OptionsDialog::OptionsDialog(QWidget * parent)
|
||||
:QDialog()
|
||||
{
|
||||
QVBoxLayout * layout = new QVBoxLayout(this);
|
||||
|
||||
accept = new QPushButton(tr("Save"));
|
||||
cancel = new QPushButton(tr("Cancel"));
|
||||
connect(accept,SIGNAL(clicked()),this,SLOT(saveOptions()));
|
||||
connect(cancel,SIGNAL(clicked()),this,SLOT(restoreOptions()));
|
||||
connect(cancel,SIGNAL(clicked()),this,SLOT(close()));
|
||||
|
||||
QGroupBox *groupBox = new QGroupBox(tr("How to show covers:"));
|
||||
|
||||
radio1 = new QRadioButton(tr("CoverFlow look"));
|
||||
radio2 = new QRadioButton(tr("Stripe look"));
|
||||
radio3 = new QRadioButton(tr("Overlapped Stripe look"));
|
||||
|
||||
|
||||
QVBoxLayout *vbox = new QVBoxLayout;
|
||||
QHBoxLayout * opt1 = new QHBoxLayout;
|
||||
opt1->addWidget(radio1);
|
||||
QLabel * lOpt1 = new QLabel();
|
||||
lOpt1->setPixmap(QPixmap(":/images/flow1.png"));
|
||||
opt1->addStretch();
|
||||
opt1->addWidget(lOpt1);
|
||||
vbox->addLayout(opt1);
|
||||
|
||||
QHBoxLayout * opt2 = new QHBoxLayout;
|
||||
opt2->addWidget(radio2);
|
||||
QLabel * lOpt2 = new QLabel();
|
||||
lOpt2->setPixmap(QPixmap(":/images/flow2.png"));
|
||||
opt2->addStretch();
|
||||
opt2->addWidget(lOpt2);
|
||||
vbox->addLayout(opt2);
|
||||
|
||||
QHBoxLayout * opt3 = new QHBoxLayout;
|
||||
opt3->addWidget(radio3);
|
||||
QLabel * lOpt3 = new QLabel();
|
||||
lOpt3->setPixmap(QPixmap(":/images/flow3.png"));
|
||||
opt3->addStretch();
|
||||
opt3->addWidget(lOpt3);
|
||||
vbox->addLayout(opt3);
|
||||
|
||||
|
||||
//vbox->addStretch(1);
|
||||
groupBox->setLayout(vbox);
|
||||
|
||||
QHBoxLayout * buttons = new QHBoxLayout();
|
||||
buttons->addStretch();
|
||||
buttons->addWidget(accept);
|
||||
buttons->addWidget(cancel);
|
||||
|
||||
layout->addWidget(groupBox);
|
||||
layout->addLayout(buttons);
|
||||
|
||||
setLayout(layout);
|
||||
|
||||
restoreOptions(); //load options
|
||||
resize(200,0);
|
||||
setModal (true);
|
||||
setWindowTitle("Options");
|
||||
}
|
||||
|
||||
void OptionsDialog::findFolder()
|
||||
{
|
||||
QString s = QFileDialog::getExistingDirectory(0,tr("Comics directory"),".");
|
||||
if(!s.isEmpty())
|
||||
{
|
||||
pathEdit->setText(s);
|
||||
}
|
||||
}
|
||||
|
||||
void OptionsDialog::saveOptions()
|
||||
{
|
||||
QFile f(QCoreApplication::applicationDirPath()+"/YACReaderLibrary.conf");
|
||||
if(!f.open(QIODevice::WriteOnly))
|
||||
{
|
||||
QMessageBox::critical(NULL,tr("Saving config file...."),tr("There was a problem saving YACReaderLibrary configuration. Please, check if you have enough permissions in the YACReader root folder."));
|
||||
}
|
||||
else
|
||||
{
|
||||
QTextStream txtS(&f);
|
||||
if(radio1->isChecked())
|
||||
{
|
||||
txtS << "FLOW_TYPE" << "\n" << (int)PictureFlow::CoverFlowLike << "\n";
|
||||
flowType = PictureFlow::CoverFlowLike;
|
||||
}
|
||||
if(radio2->isChecked())
|
||||
{
|
||||
txtS << "FLOW_TYPE" << "\n" << (int)PictureFlow::Strip << "\n";
|
||||
flowType = PictureFlow::Strip;
|
||||
}
|
||||
if(radio3->isChecked())
|
||||
{
|
||||
txtS << "FLOW_TYPE" << "\n" << (int)PictureFlow::StripOverlapped << "\n";
|
||||
flowType = PictureFlow::StripOverlapped;
|
||||
}
|
||||
f.close();
|
||||
close();
|
||||
emit(optionsChanged());
|
||||
}
|
||||
}
|
||||
|
||||
void OptionsDialog::restoreOptions()
|
||||
{
|
||||
QFile f(QCoreApplication::applicationDirPath()+"/YACReaderLibrary.conf");
|
||||
if(f.exists())
|
||||
{
|
||||
f.open(QIODevice::ReadOnly);
|
||||
QTextStream txtS(&f);
|
||||
QString content = txtS.readAll();
|
||||
|
||||
QStringList lines = content.split('\n');
|
||||
if(lines.count()>0){
|
||||
QString name = lines.at(1);
|
||||
|
||||
switch(flowType=(PictureFlow::FlowType)name.toInt()){
|
||||
case PictureFlow::CoverFlowLike:
|
||||
radio1->setChecked(true);
|
||||
break;
|
||||
case PictureFlow::Strip:
|
||||
radio2->setChecked(true);
|
||||
break;
|
||||
case PictureFlow::StripOverlapped:
|
||||
radio3->setChecked(true);
|
||||
break;
|
||||
}
|
||||
}
|
||||
else
|
||||
flowType=PictureFlow::Strip;
|
||||
}
|
||||
else
|
||||
flowType=PictureFlow::Strip;
|
||||
|
||||
}
|
50
YACReaderLibrary/options_dialog.h
Normal file
50
YACReaderLibrary/options_dialog.h
Normal file
@ -0,0 +1,50 @@
|
||||
#ifndef __OPTIONS_DIALOG_H
|
||||
#define __OPTIONS_DIALOG_H
|
||||
|
||||
#include <QDialog>
|
||||
#include <QLabel>
|
||||
#include <QLineEdit>
|
||||
#include <QPushButton>
|
||||
#include <QSlider>
|
||||
#include <QPushButton>
|
||||
#include <QRadioButton>
|
||||
#include "pictureflow.h"
|
||||
|
||||
extern PictureFlow::FlowType flowType;
|
||||
|
||||
class OptionsDialog : public QDialog
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
OptionsDialog(QWidget * parent = 0);
|
||||
private:
|
||||
QLabel * pathLabel;
|
||||
QLineEdit * pathEdit;
|
||||
QPushButton * pathFindButton;
|
||||
|
||||
QLabel * magGlassSizeLabel;
|
||||
|
||||
QLabel * zoomLevel;
|
||||
|
||||
QLabel * slideSizeLabel;
|
||||
QSlider * slideSize;
|
||||
|
||||
QPushButton * accept;
|
||||
QPushButton * cancel;
|
||||
|
||||
QRadioButton *radio1;
|
||||
QRadioButton *radio2;
|
||||
QRadioButton *radio3;
|
||||
|
||||
public slots:
|
||||
void saveOptions();
|
||||
void restoreOptions();
|
||||
void findFolder();
|
||||
|
||||
signals:
|
||||
void optionsChanged();
|
||||
|
||||
};
|
||||
|
||||
|
||||
#endif
|
47
YACReaderLibrary/package_manager.cpp
Normal file
47
YACReaderLibrary/package_manager.cpp
Normal file
@ -0,0 +1,47 @@
|
||||
#include "package_manager.h"
|
||||
#include <QCoreApplication>
|
||||
|
||||
PackageManager::PackageManager()
|
||||
:_7z(0)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
void PackageManager::createPackage(const QString & libraryPath,const QString & dest)
|
||||
{
|
||||
QStringList attributes;
|
||||
attributes << "a" << "-y" << "-ttar" << dest+".clc" << libraryPath ;
|
||||
_7z = new QProcess();
|
||||
connect(_7z,SIGNAL(error(QProcess::ProcessError)),this,SLOT(openingError(QProcess::ProcessError)));
|
||||
connect(_7z,SIGNAL(finished(int,QProcess::ExitStatus)),this,SIGNAL(exported()));
|
||||
_7z->start(QCoreApplication::applicationDirPath()+"/utils/7z",attributes);
|
||||
}
|
||||
|
||||
void PackageManager::extractPackage(const QString & packagePath,const QString & destDir)
|
||||
{
|
||||
QStringList attributes;
|
||||
QString output = "-o";
|
||||
output += destDir;
|
||||
attributes << "x" << "-y" << output << packagePath;
|
||||
_7z = new QProcess();
|
||||
connect(_7z,SIGNAL(error(QProcess::ProcessError)),this,SLOT(openingError(QProcess::ProcessError)));
|
||||
connect(_7z,SIGNAL(finished(int,QProcess::ExitStatus)),this,SIGNAL(imported()));
|
||||
_7z->start(QCoreApplication::applicationDirPath()+"/utils/7z",attributes);
|
||||
}
|
||||
|
||||
void PackageManager::cancel()
|
||||
{
|
||||
if(_7z!=0)
|
||||
{
|
||||
_7z->disconnect();
|
||||
_7z->kill();
|
||||
if(creating)
|
||||
{
|
||||
//TODO remove dest+".clc"
|
||||
}
|
||||
else
|
||||
{
|
||||
//TODO fixed: is done by libraryWindow
|
||||
}
|
||||
}
|
||||
}
|
24
YACReaderLibrary/package_manager.h
Normal file
24
YACReaderLibrary/package_manager.h
Normal file
@ -0,0 +1,24 @@
|
||||
#ifndef PACKAGE_MANAGER_H
|
||||
#define PACKAGE_MANAGER_H
|
||||
|
||||
#include <QProcess>
|
||||
|
||||
class PackageManager : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
PackageManager();
|
||||
void createPackage(const QString & libraryPath,const QString & dest);
|
||||
void extractPackage(const QString & packagePath,const QString & destDir);
|
||||
public slots:
|
||||
void cancel();
|
||||
private:
|
||||
bool creating;
|
||||
QProcess * _7z;
|
||||
|
||||
signals:
|
||||
void exported();
|
||||
void imported();
|
||||
};
|
||||
|
||||
#endif
|
691
YACReaderLibrary/properties_dialog.cpp
Normal file
691
YACReaderLibrary/properties_dialog.cpp
Normal file
@ -0,0 +1,691 @@
|
||||
#include "properties_dialog.h"
|
||||
#include <QHBoxLayout>
|
||||
#include <QApplication>
|
||||
#include <QDesktopWidget>
|
||||
#include <QSizePolicy>
|
||||
#include <QFormLayout>
|
||||
#include <QCheckBox>
|
||||
#include <QTabWidget>
|
||||
#include <QIntValidator>
|
||||
|
||||
#include "data_base_management.h"
|
||||
#include "library_creator.h"
|
||||
|
||||
PropertiesDialog::PropertiesDialog(QWidget * parent)
|
||||
:QDialog(parent)
|
||||
{
|
||||
|
||||
createCoverBox();
|
||||
createGeneralInfoBox();
|
||||
createAuthorsBox();
|
||||
createPublishingBox();
|
||||
createButtonBox();
|
||||
createPlotBox();
|
||||
|
||||
createTabBar();
|
||||
|
||||
mainLayout = new QGridLayout;
|
||||
mainLayout->addWidget(coverBox,0,0);
|
||||
mainLayout->addWidget(tabBar,0,1);
|
||||
mainLayout->setColumnStretch(1,1);
|
||||
/*mainLayout->addWidget(authorsBox,1,1);
|
||||
mainLayout->addWidget(publishingBox,2,1);*/
|
||||
mainLayout->addWidget(buttonBox,1,1,Qt::AlignBottom);
|
||||
|
||||
this->setLayout(mainLayout);
|
||||
mainLayout->setSizeConstraint(QLayout::SetMinimumSize);
|
||||
|
||||
int heightDesktopResolution = QApplication::desktop()->screenGeometry().height();
|
||||
int widthDesktopResolution = QApplication::desktop()->screenGeometry().width();
|
||||
int sHeight,sWidth;
|
||||
sHeight = static_cast<int>(heightDesktopResolution*0.65);
|
||||
sWidth = static_cast<int>(sHeight*1.4);
|
||||
setCover(QPixmap(":/images/notCover.png"));
|
||||
this->resize(sWidth,this->height());
|
||||
this->move(QPoint((widthDesktopResolution-sWidth)/2,((heightDesktopResolution-sHeight)-40)/2));
|
||||
setModal(true);
|
||||
repaint();
|
||||
|
||||
}
|
||||
|
||||
void PropertiesDialog::createTabBar()
|
||||
{
|
||||
tabBar = new QTabWidget;
|
||||
tabBar->addTab(generalInfoBox,tr("General info"));
|
||||
tabBar->addTab(authorsBox,tr("Authors"));
|
||||
tabBar->addTab(publishingBox,tr("Publishing"));
|
||||
tabBar->addTab(plotBox,tr("Plot"));
|
||||
}
|
||||
|
||||
void PropertiesDialog::createCoverBox()
|
||||
{
|
||||
coverBox = new QGroupBox(tr("Cover"));
|
||||
|
||||
sa = new QScrollArea();
|
||||
cover = new QLabel();
|
||||
cover->setSizePolicy(QSizePolicy::Ignored, QSizePolicy::Ignored);
|
||||
cover->setScaledContents(false);
|
||||
cover->setAlignment(Qt::AlignTop|Qt::AlignHCenter);
|
||||
|
||||
|
||||
/*sa->setWidget(cover);
|
||||
sa->setBackgroundRole(QPalette::Dark);
|
||||
sa->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
|
||||
sa->setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
|
||||
sa->setFrameStyle(QFrame::NoFrame);
|
||||
sa->setAlignment(Qt::AlignCenter);*/
|
||||
|
||||
QVBoxLayout * coverLayout = new QVBoxLayout();
|
||||
coverLayout->addWidget(cover);
|
||||
|
||||
QHBoxLayout * coverPageLayout = new QHBoxLayout;
|
||||
coverPageLayout->addWidget(new QLabel(tr("Cover page : ")));
|
||||
coverPageLayout->addWidget(coverPageEdit = new YACReaderFieldEdit());
|
||||
coverPageLayout->setStretch(1,0);
|
||||
|
||||
coverLayout->addLayout(coverPageLayout);
|
||||
|
||||
coverBox->setLayout(coverLayout);
|
||||
}
|
||||
|
||||
QFrame * createLine()
|
||||
{
|
||||
QFrame * line = new QFrame();
|
||||
line->setObjectName(QString::fromUtf8("line"));
|
||||
//line->setGeometry(QRect(320, 150, 118, 3));
|
||||
line->setFrameShape(QFrame::HLine);
|
||||
line->setFrameShadow(QFrame::Sunken);
|
||||
|
||||
return line;
|
||||
}
|
||||
|
||||
void PropertiesDialog::createGeneralInfoBox()
|
||||
{
|
||||
generalInfoBox = new QWidget;
|
||||
|
||||
QFormLayout *generalInfoLayout = new QFormLayout;
|
||||
|
||||
//generalInfoLayout->setRowWrapPolicy(QFormLayout::WrapAllRows);
|
||||
generalInfoLayout->addRow(tr("Title:"), title = new YACReaderFieldEdit());
|
||||
|
||||
|
||||
QHBoxLayout * number = new QHBoxLayout;
|
||||
number->addWidget(numberEdit = new YACReaderFieldEdit());
|
||||
numberValidator.setBottom(0);
|
||||
numberEdit->setValidator(&numberValidator);
|
||||
number->addWidget(new QLabel("Bis:"));
|
||||
number->addWidget(isBisCheck = new QCheckBox());
|
||||
number->addWidget(new QLabel("of:"));
|
||||
number->addWidget(countEdit = new YACReaderFieldEdit());
|
||||
countValidator.setBottom(0);
|
||||
countEdit->setValidator(&countValidator);
|
||||
number->addStretch(1);
|
||||
/*generalInfoLayout->addRow(tr("&Issue number:"), );
|
||||
generalInfoLayout->addRow(tr("&Bis:"), );*/
|
||||
generalInfoLayout->addRow(tr("Issue number:"), number);
|
||||
|
||||
generalInfoLayout->addRow(tr("Volume:"), volumeEdit = new YACReaderFieldEdit());
|
||||
|
||||
QHBoxLayout * arc = new QHBoxLayout;
|
||||
arc->addWidget(storyArcEdit = new YACReaderFieldEdit());
|
||||
arc->addWidget(new QLabel("Arc number:"));
|
||||
arc->addWidget(arcNumberEdit = new YACReaderFieldEdit());
|
||||
arcNumberValidator.setBottom(0);
|
||||
arcNumberEdit->setValidator(&arcNumberValidator);
|
||||
arc->addWidget(new QLabel("of:"));
|
||||
arc->addWidget(arcCountEdit = new YACReaderFieldEdit());
|
||||
arcCountValidator.setBottom(0);
|
||||
arcCountEdit->setValidator(&arcCountValidator);
|
||||
arc->addStretch(1);
|
||||
generalInfoLayout->addRow(tr("Story arc:"), arc);
|
||||
|
||||
generalInfoLayout->addRow(tr("Genere:"), genereEdit = new YACReaderFieldEdit());
|
||||
|
||||
generalInfoLayout->addRow(tr("Size:"), size = new QLabel("size"));
|
||||
|
||||
generalInfoBox->setLayout(generalInfoLayout);
|
||||
}
|
||||
|
||||
void PropertiesDialog::createAuthorsBox()
|
||||
{
|
||||
authorsBox = new QWidget;
|
||||
|
||||
QVBoxLayout *authorsLayout = new QVBoxLayout;
|
||||
|
||||
//authorsLayout->setRowWrapPolicy(QFormLayout::WrapAllRows);
|
||||
QHBoxLayout * h1 = new QHBoxLayout;
|
||||
QVBoxLayout * vl1 = new QVBoxLayout;
|
||||
QVBoxLayout * vr1 = new QVBoxLayout;
|
||||
vl1->addWidget(new QLabel(tr("Writer(s):")));
|
||||
vl1->addWidget(writer = new YACReaderFieldPlainTextEdit());
|
||||
h1->addLayout(vl1);
|
||||
vr1->addWidget(new QLabel(tr("Penciller(s):")));
|
||||
vr1->addWidget(penciller = new YACReaderFieldPlainTextEdit());
|
||||
h1->addLayout(vr1);
|
||||
//authorsLayout->addRow(tr("Writer(s):"), new YACReaderFieldPlainTextEdit());
|
||||
//authorsLayout->addRow(tr("Penciller(s):"), new YACReaderFieldPlainTextEdit());
|
||||
QHBoxLayout * h2 = new QHBoxLayout;
|
||||
QVBoxLayout * vl2 = new QVBoxLayout;
|
||||
QVBoxLayout * vr2 = new QVBoxLayout;
|
||||
vl2->addWidget(new QLabel(tr("Inker(s):")));
|
||||
vl2->addWidget(inker = new YACReaderFieldPlainTextEdit());
|
||||
h2->addLayout(vl2);
|
||||
vr2->addWidget(new QLabel(tr("Colorist(s):")));
|
||||
vr2->addWidget(colorist = new YACReaderFieldPlainTextEdit());
|
||||
h2->addLayout(vr2);
|
||||
|
||||
//authorsLayout->addRow(tr("Inker(s):"), new YACReaderFieldPlainTextEdit());
|
||||
//authorsLayout->addRow(tr("Colorist(s):"), new YACReaderFieldPlainTextEdit());
|
||||
|
||||
QHBoxLayout * h3 = new QHBoxLayout;
|
||||
QVBoxLayout * vl3 = new QVBoxLayout;
|
||||
QVBoxLayout * vr3 = new QVBoxLayout;
|
||||
vl3->addWidget(new QLabel(tr("Letterer(es):")));
|
||||
vl3->addWidget(letterer = new YACReaderFieldPlainTextEdit());
|
||||
h3->addLayout(vl3);
|
||||
vr3->addWidget(new QLabel(tr("Cover Artist(s):")));
|
||||
vr3->addWidget(coverArtist = new YACReaderFieldPlainTextEdit());
|
||||
h3->addLayout(vr3);
|
||||
//authorsLayout->addRow(tr("Letterer(es):"), new YACReaderFieldPlainTextEdit());
|
||||
//authorsLayout->addRow(tr("Cover Artist(s):"), new YACReaderFieldPlainTextEdit());
|
||||
|
||||
authorsLayout->addLayout(h1);
|
||||
authorsLayout->addLayout(h2);
|
||||
authorsLayout->addLayout(h3);
|
||||
authorsLayout->addStretch(1);
|
||||
authorsBox->setLayout(authorsLayout);
|
||||
|
||||
}
|
||||
|
||||
void PropertiesDialog::createPublishingBox()
|
||||
{
|
||||
publishingBox = new QWidget;
|
||||
|
||||
QFormLayout *publishingLayout = new QFormLayout;
|
||||
|
||||
QHBoxLayout * date = new QHBoxLayout;
|
||||
date->addWidget(new QLabel(tr("Day:")));
|
||||
date->addWidget(dayEdit = new YACReaderFieldEdit());
|
||||
dayValidator.setRange(1,31);
|
||||
dayEdit->setValidator(&dayValidator);
|
||||
date->addWidget(new QLabel(tr("Month:")));
|
||||
date->addWidget(monthEdit = new YACReaderFieldEdit());
|
||||
monthValidator.setRange(1,12);
|
||||
monthEdit->setValidator(&monthValidator);
|
||||
date->addWidget(new QLabel(tr("Year:")));
|
||||
date->addWidget(yearEdit = new YACReaderFieldEdit());
|
||||
yearValidator.setRange(1,9999);
|
||||
yearEdit->setValidator(&yearValidator);
|
||||
date->addStretch(1);
|
||||
|
||||
publishingLayout->setRowWrapPolicy(QFormLayout::WrapAllRows);
|
||||
publishingLayout->addRow(date);
|
||||
publishingLayout->addRow(tr("Publisher:"), publisherEdit = new YACReaderFieldEdit());
|
||||
publishingLayout->addRow(tr("Format:"), formatEdit = new YACReaderFieldEdit());
|
||||
publishingLayout->addRow(tr("Color/BW:"), colorCheck = new QCheckBox());
|
||||
publishingLayout->addRow(tr("Age rating:"), ageRatingEdit = new YACReaderFieldEdit());
|
||||
|
||||
publishingBox->setLayout(publishingLayout);
|
||||
}
|
||||
|
||||
void PropertiesDialog::createPlotBox()
|
||||
{
|
||||
plotBox = new QWidget;
|
||||
|
||||
QFormLayout *plotLayout = new QFormLayout;
|
||||
|
||||
plotLayout->setRowWrapPolicy(QFormLayout::WrapAllRows);
|
||||
plotLayout->addRow(tr("Synopsis:"), synopsis = new YACReaderFieldPlainTextEdit());
|
||||
plotLayout->addRow(tr("Characters:"), characters = new YACReaderFieldPlainTextEdit());
|
||||
plotLayout->addRow(tr("Notes:"), notes = new YACReaderFieldPlainTextEdit());
|
||||
|
||||
plotBox->setLayout(plotLayout);
|
||||
|
||||
}
|
||||
|
||||
void PropertiesDialog::createButtonBox()
|
||||
{
|
||||
buttonBox = new QDialogButtonBox;
|
||||
|
||||
closeButton = buttonBox->addButton(QDialogButtonBox::Close);
|
||||
saveButton = buttonBox->addButton(QDialogButtonBox::Save);
|
||||
//rotateWidgetsButton = buttonBox->addButton(tr("Rotate &Widgets"),QDialogButtonBox::ActionRole);
|
||||
|
||||
//connect(rotateWidgetsButton, SIGNAL(clicked()), this, SLOT(rotateWidgets()));
|
||||
connect(closeButton, SIGNAL(clicked()), this, SLOT(close()));
|
||||
connect(saveButton, SIGNAL(clicked()), this, SLOT(save()));
|
||||
}
|
||||
|
||||
|
||||
|
||||
void PropertiesDialog::setComics(QList<Comic> comics)
|
||||
{
|
||||
this->comics = comics;
|
||||
|
||||
Comic comic = comics.at(0);
|
||||
|
||||
if(comic.info.title != NULL)
|
||||
title->setText(*comic.info.title);
|
||||
|
||||
if(comic.info.coverPage != NULL)
|
||||
{
|
||||
coverPageEdit->setText(QString::number(*comic.info.coverPage));
|
||||
coverPageValidator.setRange(1,*comic.info.numPages);
|
||||
coverPageEdit->setValidator(&coverPageValidator);
|
||||
}
|
||||
/*if(comic.info.numPages != NULL)
|
||||
numPagesEdit->setText(QString::number(*comic.info.numPages));*/
|
||||
|
||||
|
||||
if(comic.info.number != NULL)
|
||||
numberEdit->setText(QString::number(*comic.info.number));
|
||||
if(comic.info.isBis != NULL)
|
||||
isBisCheck->setChecked(*comic.info.isBis);
|
||||
if(comic.info.count != NULL)
|
||||
countEdit->setText(QString::number(*comic.info.count));
|
||||
|
||||
if(comic.info.volume != NULL)
|
||||
volumeEdit->setText(*comic.info.volume);
|
||||
if(comic.info.storyArc != NULL)
|
||||
storyArcEdit->setText(*comic.info.storyArc);
|
||||
if(comic.info.arcNumber != NULL)
|
||||
arcNumberEdit->setText(QString::number(*comic.info.arcNumber));
|
||||
if(comic.info.arcCount != NULL)
|
||||
arcCountEdit->setText(QString::number(*comic.info.arcCount));
|
||||
|
||||
if(comic.info.genere != NULL)
|
||||
genereEdit->setText(*comic.info.genere);
|
||||
|
||||
if(comic.info.writer != NULL)
|
||||
writer->setPlainText(*comic.info.writer);
|
||||
if(comic.info.penciller != NULL)
|
||||
penciller->setPlainText(*comic.info.penciller);
|
||||
if(comic.info.inker != NULL)
|
||||
inker->setPlainText(*comic.info.inker);
|
||||
if(comic.info.colorist != NULL)
|
||||
colorist->setPlainText(*comic.info.colorist);
|
||||
if(comic.info.letterer != NULL)
|
||||
letterer->setPlainText(*comic.info.letterer);
|
||||
if(comic.info.coverArtist != NULL)
|
||||
coverArtist->setPlainText(*comic.info.coverArtist);
|
||||
|
||||
size->setText(QString::number(comic.info.hash.right(comic.info.hash.length()-40).toInt()/1024.0/1024.0,'f',2)+"Mb");
|
||||
|
||||
if(comic.info.date != NULL)
|
||||
{
|
||||
QStringList date = (*comic.info.date).split("/");
|
||||
dayEdit->setText(date[0]);
|
||||
monthEdit->setText(date[1]);
|
||||
yearEdit->setText(date[2]);
|
||||
}
|
||||
if(comic.info.publisher != NULL)
|
||||
publisherEdit->setText(*comic.info.publisher);
|
||||
if(comic.info.format != NULL)
|
||||
formatEdit->setText(*comic.info.format);
|
||||
if(comic.info.color != NULL)
|
||||
colorCheck->setChecked(*comic.info.color);
|
||||
else
|
||||
colorCheck->setCheckState(Qt::PartiallyChecked);
|
||||
|
||||
if(comic.info.ageRating != NULL)
|
||||
ageRatingEdit->setText(*comic.info.ageRating);
|
||||
|
||||
if(comic.info.synopsis != NULL)
|
||||
synopsis->setPlainText(*comic.info.synopsis);
|
||||
if(comic.info.characters != NULL)
|
||||
characters->setPlainText(*comic.info.characters);
|
||||
if(comic.info.notes != NULL)
|
||||
notes->setPlainText(*comic.info.notes);
|
||||
|
||||
|
||||
if(comics.length() > 1)
|
||||
{
|
||||
setDisableUniqueValues(true);
|
||||
this->setWindowTitle(tr("Edit selected comics information"));
|
||||
setCover(QPixmap(":/images/editComic.png"));
|
||||
|
||||
QList<Comic>::iterator itr;
|
||||
for(itr = ++comics.begin();itr!=comics.end();itr++)
|
||||
{
|
||||
if(itr->info.title == NULL || *(itr->info.title) != title->text())
|
||||
title->clear();
|
||||
|
||||
if(itr->info.count == NULL || *(itr->info.count) != countEdit->text().toInt()) //TODO esto est<73> mal
|
||||
countEdit->clear();
|
||||
|
||||
if(itr->info.volume == NULL || *(itr->info.volume) != volumeEdit->text())
|
||||
volumeEdit->clear();
|
||||
if(itr->info.storyArc == NULL || *(itr->info.storyArc) != storyArcEdit->text())
|
||||
storyArcEdit->clear();
|
||||
if(itr->info.arcCount == NULL || *(itr->info.arcCount) != storyArcEdit->text().toInt())
|
||||
arcCountEdit->clear();
|
||||
|
||||
if(itr->info.genere == NULL || *(itr->info.genere) != genereEdit->text())
|
||||
genereEdit->clear();
|
||||
|
||||
if(itr->info.writer == NULL || *(itr->info.writer) != writer->toPlainText())
|
||||
writer->clear();
|
||||
if(itr->info.penciller == NULL || *(itr->info.penciller) != penciller->toPlainText())
|
||||
penciller->clear();
|
||||
if(itr->info.inker == NULL || *(itr->info.inker) != inker->toPlainText())
|
||||
inker->clear();
|
||||
if(itr->info.colorist == NULL || *(itr->info.colorist) != colorist->toPlainText())
|
||||
colorist->clear();
|
||||
if(itr->info.letterer == NULL || *(itr->info.letterer) != letterer->toPlainText())
|
||||
letterer->clear();
|
||||
if(itr->info.coverArtist == NULL || *(itr->info.coverArtist) != coverArtist->toPlainText())
|
||||
coverArtist->clear();
|
||||
|
||||
if(itr->info.date == NULL)
|
||||
{
|
||||
dayEdit->clear();
|
||||
monthEdit->clear();
|
||||
yearEdit->clear();
|
||||
}
|
||||
else
|
||||
{
|
||||
QStringList date = itr->info.date->split("/");
|
||||
if(dayEdit->text() != date[0])
|
||||
dayEdit->clear();
|
||||
if(monthEdit->text() != date[1])
|
||||
monthEdit->clear();
|
||||
if(yearEdit->text() != date[2])
|
||||
yearEdit->clear();
|
||||
}
|
||||
|
||||
if(itr->info.publisher == NULL || *(itr->info.publisher) != publisherEdit->text())
|
||||
publisherEdit->clear();
|
||||
if(itr->info.format == NULL || *(itr->info.format) != formatEdit->text())
|
||||
formatEdit->clear();
|
||||
if(itr->info.color == NULL || *(itr->info.color) != colorCheck->isChecked())
|
||||
colorCheck->setCheckState(Qt::PartiallyChecked);
|
||||
if(itr->info.ageRating == NULL || *(itr->info.ageRating) != ageRatingEdit->text())
|
||||
ageRatingEdit->clear();
|
||||
|
||||
if(itr->info.synopsis == NULL || *(itr->info.synopsis) != synopsis->toPlainText())
|
||||
synopsis->clear();
|
||||
if(itr->info.characters == NULL || *(itr->info.characters) != characters->toPlainText())
|
||||
characters->clear();
|
||||
if(itr->info.notes == NULL || *(itr->info.notes) != notes->toPlainText())
|
||||
notes->clear();
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
this->setWindowTitle(tr("Edit comic information"));
|
||||
setCover(comic.info.getCover(basePath));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
void PropertiesDialog::updateComics()
|
||||
{
|
||||
QSqlDatabase db = DataBaseManagement::loadDatabase(databasePath);
|
||||
db.open();
|
||||
db.transaction();
|
||||
QList<Comic>::iterator itr;
|
||||
for(itr = comics.begin();itr!=comics.end();itr++)
|
||||
{
|
||||
if(itr->info.edited)
|
||||
itr->info.update(db);
|
||||
}
|
||||
db.commit();
|
||||
db.close();
|
||||
QSqlDatabase::removeDatabase(databasePath);
|
||||
}
|
||||
//Deprecated
|
||||
void PropertiesDialog::setCover(const QPixmap & coverImage)
|
||||
{
|
||||
cover->setPixmap(coverImage.scaledToWidth(125,Qt::SmoothTransformation));
|
||||
//cover->repaint();
|
||||
|
||||
//float aspectRatio = (float)coverImage.width()/coverImage.height();
|
||||
//int heightDesktopResolution = QApplication::desktop()->screenGeometry().height();
|
||||
//int widthDesktopResolution = QApplication::desktop()->screenGeometry().width();
|
||||
//int sHeight,sWidth;
|
||||
//sHeight = static_cast<int>(heightDesktopResolution*0.65);
|
||||
|
||||
//if(aspectRatio<1)
|
||||
//{
|
||||
// sWidth = static_cast<int>(sHeight*1.4);
|
||||
// //this->resize(sWidth,sHeight);
|
||||
// this->move(QPoint((widthDesktopResolution-sWidth)/2,((heightDesktopResolution-sHeight)-40)/2));
|
||||
// //sa->resize(sa->width(),sa->width()*1.333);
|
||||
// /*cover->resize(static_cast<int>((sa->height())*aspectRatio),
|
||||
// (sa->height()));*/
|
||||
|
||||
//}
|
||||
//else
|
||||
//{
|
||||
// sWidth = static_cast<int>(sHeight/1.16);
|
||||
// //this->resize(sWidth,sHeight);
|
||||
// this->move(QPoint((widthDesktopResolution-sWidth)/2,((heightDesktopResolution-sHeight)-40)/2));
|
||||
// cover->resize((width()-25),
|
||||
// static_cast<int>((width()-25)/aspectRatio));
|
||||
//}
|
||||
|
||||
}
|
||||
void PropertiesDialog::setFilename(const QString & nameString)
|
||||
{
|
||||
title->setText(nameString);
|
||||
}
|
||||
void PropertiesDialog::setNumpages(int pagesNum)
|
||||
{
|
||||
numPagesEdit->setText(QString::number(pagesNum));
|
||||
}
|
||||
void PropertiesDialog::setSize(float sizeFloat)
|
||||
{
|
||||
|
||||
size->setText(QString::number(sizeFloat,'f',2) + " MB");
|
||||
}
|
||||
|
||||
void PropertiesDialog::save()
|
||||
{
|
||||
QList<Comic>::iterator itr;
|
||||
for(itr = comics.begin();itr!=comics.end();itr++)
|
||||
{
|
||||
//Comic & comic = comics[0];
|
||||
bool edited = false;
|
||||
|
||||
if(title->isModified())
|
||||
{
|
||||
itr->info.setTitle(title->text());
|
||||
edited = true;
|
||||
}
|
||||
|
||||
if(comics.size()==1)
|
||||
if(coverPageEdit->isModified() && !coverPageEdit->text().isEmpty() && coverPageEdit->text().toInt() != 0)
|
||||
{
|
||||
itr->info.setCoverPage(coverPageEdit->text().toInt());
|
||||
edited = true;
|
||||
}
|
||||
|
||||
/*if(comic.info.numPages != NULL)
|
||||
numPagesEdit->setText(QString::number(*comic.info.numPages));*/
|
||||
if(comics.size()==1)
|
||||
if(numberEdit->isModified() && !numberEdit->text().isEmpty())
|
||||
{
|
||||
itr->info.setNumber(numberEdit->text().toInt());
|
||||
edited = true;
|
||||
}
|
||||
if(comics.size()==1)
|
||||
if(itr->info.isBis != NULL || isBisCheck->isChecked())
|
||||
{
|
||||
itr->info.setIsBis(isBisCheck->isChecked());
|
||||
edited = true;
|
||||
}
|
||||
|
||||
if(countEdit->isModified())
|
||||
{
|
||||
itr->info.setCount(countEdit->text().toInt());
|
||||
edited = true;
|
||||
}
|
||||
|
||||
if(volumeEdit->isModified())
|
||||
{
|
||||
itr->info.setVolume(volumeEdit->text());
|
||||
edited = true;
|
||||
}
|
||||
if(storyArcEdit->isModified())
|
||||
{
|
||||
itr->info.setStoryArc(storyArcEdit->text());
|
||||
edited = true;
|
||||
}
|
||||
if(comics.size()==1)
|
||||
if(arcNumberEdit->isModified() && !arcNumberEdit->text().isEmpty())
|
||||
{
|
||||
itr->info.setArcNumber(arcNumberEdit->text().toInt());
|
||||
edited = true;
|
||||
}
|
||||
if(arcCountEdit->isModified())
|
||||
{
|
||||
itr->info.setArcCount(arcCountEdit->text().toInt());
|
||||
edited = true;
|
||||
}
|
||||
|
||||
if(genereEdit->isModified())
|
||||
{
|
||||
itr->info.setGenere(genereEdit->text());
|
||||
edited = true;
|
||||
}
|
||||
|
||||
if(writer->document()->isModified())
|
||||
{
|
||||
itr->info.setWriter(writer->toPlainText());
|
||||
edited = true;
|
||||
}
|
||||
if(penciller->document()->isModified())
|
||||
{
|
||||
itr->info.setPenciller(penciller->toPlainText());
|
||||
edited = true;
|
||||
}
|
||||
if(inker->document()->isModified())
|
||||
{
|
||||
itr->info.setInker(inker->toPlainText());
|
||||
edited = true;
|
||||
}
|
||||
if(colorist->document()->isModified())
|
||||
{
|
||||
itr->info.setColorist(colorist->toPlainText());
|
||||
edited = true;
|
||||
}
|
||||
if(letterer->document()->isModified())
|
||||
{
|
||||
itr->info.setLetterer(letterer->toPlainText());
|
||||
edited = true;
|
||||
}
|
||||
if(coverArtist->document()->isModified())
|
||||
{
|
||||
itr->info.setCoverArtist(coverArtist->toPlainText());
|
||||
edited = true;
|
||||
}
|
||||
|
||||
if(dayEdit->isModified() || monthEdit->isModified() || yearEdit->isModified() )
|
||||
{
|
||||
itr->info.setDate(dayEdit->text()+"/"+monthEdit->text()+"/"+yearEdit->text());
|
||||
edited = true;
|
||||
}
|
||||
if(publisherEdit->isModified())
|
||||
{
|
||||
itr->info.setPublisher(publisherEdit->text());
|
||||
edited = true;
|
||||
}
|
||||
if(formatEdit->isModified())
|
||||
{
|
||||
itr->info.setFormat(formatEdit->text());
|
||||
edited = true;
|
||||
}
|
||||
if(colorCheck->checkState() != Qt::PartiallyChecked)
|
||||
{
|
||||
itr->info.setColor(colorCheck->isChecked());
|
||||
edited = true;
|
||||
}
|
||||
if(ageRatingEdit->isModified())
|
||||
{
|
||||
itr->info.setAgeRating(ageRatingEdit->text());
|
||||
edited = true;
|
||||
}
|
||||
|
||||
if(synopsis->document()->isModified())
|
||||
{
|
||||
itr->info.setSynopsis(synopsis->toPlainText());
|
||||
edited = true;
|
||||
}
|
||||
if(characters->document()->isModified())
|
||||
{
|
||||
itr->info.setCharacters(characters->toPlainText());
|
||||
edited = true;
|
||||
}
|
||||
if(notes->document()->isModified())
|
||||
{
|
||||
itr->info.setNotes(notes->toPlainText());
|
||||
edited = true;
|
||||
}
|
||||
|
||||
itr->info.edited = edited;
|
||||
}
|
||||
updateComics();
|
||||
if(comics.count() == 1)
|
||||
{
|
||||
if(coverPageEdit->isModified())// && coverPageEdit->text().toInt() != *comics[0].info.coverPage)
|
||||
{
|
||||
ThumbnailCreator tc(basePath+comics[0].path,basePath+"/.yacreaderlibrary/covers/"+comics[0].info.hash+".jpg",*comics[0].info.coverPage);
|
||||
tc.create();
|
||||
}
|
||||
}
|
||||
close();
|
||||
emit(accepted());
|
||||
}
|
||||
|
||||
void PropertiesDialog::setDisableUniqueValues(bool disabled)
|
||||
{
|
||||
coverPageEdit->setDisabled(disabled);
|
||||
coverPageEdit->clear();
|
||||
numberEdit->setDisabled(disabled);
|
||||
numberEdit->clear();
|
||||
isBisCheck->setDisabled(disabled);
|
||||
isBisCheck->setChecked(false);
|
||||
arcNumberEdit->setDisabled(disabled);
|
||||
arcNumberEdit->clear();
|
||||
}
|
||||
|
||||
void PropertiesDialog::closeEvent ( QCloseEvent * e )
|
||||
{
|
||||
|
||||
title->clear();
|
||||
title->setModified(false);
|
||||
coverPageEdit->clear();
|
||||
// numPagesEdit->setText(QString::number(*comic.info.numPages));
|
||||
numberEdit->clear();
|
||||
isBisCheck->setChecked(false);
|
||||
countEdit->clear();
|
||||
volumeEdit->clear();
|
||||
storyArcEdit->clear();
|
||||
arcNumberEdit->clear();
|
||||
arcCountEdit->clear();
|
||||
genereEdit->clear();
|
||||
writer->clear();
|
||||
penciller->clear();
|
||||
inker->clear();
|
||||
colorist->clear();
|
||||
letterer->clear();
|
||||
coverArtist->clear();
|
||||
dayEdit->clear();
|
||||
monthEdit->clear();
|
||||
yearEdit->clear();
|
||||
publisherEdit->clear();
|
||||
formatEdit->clear();
|
||||
colorCheck->setCheckState(Qt::PartiallyChecked);
|
||||
ageRatingEdit->clear();
|
||||
synopsis->clear();
|
||||
characters->clear();
|
||||
notes->clear();
|
||||
|
||||
setDisableUniqueValues(false);
|
||||
|
||||
tabBar->setCurrentIndex(0);
|
||||
|
||||
QDialog::closeEvent(e);
|
||||
}
|
116
YACReaderLibrary/properties_dialog.h
Normal file
116
YACReaderLibrary/properties_dialog.h
Normal file
@ -0,0 +1,116 @@
|
||||
#ifndef __PROPERTIES_DIALOG_H
|
||||
#define __PROPERTIES_DIALOG_H
|
||||
|
||||
#include <QDialog>
|
||||
#include <QLabel>
|
||||
#include <QPushButton>
|
||||
#include <QScrollArea>
|
||||
#include <QGroupBox>
|
||||
#include <QDialogButtonBox>
|
||||
#include <QGridLayout>
|
||||
#include <QTabWidget>
|
||||
#include <QCheckBox>
|
||||
#include <QIntValidator>
|
||||
|
||||
#include "comic.h"
|
||||
#include "custom_widgets.h"
|
||||
|
||||
class PropertiesDialog : public QDialog
|
||||
{
|
||||
Q_OBJECT
|
||||
private:
|
||||
QGridLayout * mainLayout;
|
||||
|
||||
QTabWidget * tabBar;
|
||||
|
||||
QGroupBox * coverBox;
|
||||
QLabel * cover;
|
||||
QScrollArea * sa;
|
||||
|
||||
QWidget * generalInfoBox;
|
||||
YACReaderFieldEdit * title;
|
||||
YACReaderFieldEdit * numPagesEdit;
|
||||
QLabel * size;
|
||||
|
||||
YACReaderFieldEdit * coverPageEdit;
|
||||
QIntValidator coverPageValidator;
|
||||
|
||||
YACReaderFieldEdit * numberEdit;
|
||||
QIntValidator numberValidator;
|
||||
QCheckBox * isBisCheck;
|
||||
YACReaderFieldEdit * countEdit;
|
||||
QIntValidator countValidator;
|
||||
|
||||
YACReaderFieldEdit * volumeEdit;
|
||||
YACReaderFieldEdit * storyArcEdit;
|
||||
YACReaderFieldEdit * arcNumberEdit;
|
||||
QIntValidator arcNumberValidator;
|
||||
YACReaderFieldEdit * arcCountEdit;
|
||||
QIntValidator arcCountValidator;
|
||||
|
||||
YACReaderFieldEdit * genereEdit;
|
||||
|
||||
YACReaderFieldPlainTextEdit * writer;
|
||||
YACReaderFieldPlainTextEdit * penciller;
|
||||
YACReaderFieldPlainTextEdit * inker;
|
||||
YACReaderFieldPlainTextEdit * colorist;
|
||||
YACReaderFieldPlainTextEdit * letterer;
|
||||
YACReaderFieldPlainTextEdit * coverArtist;
|
||||
|
||||
YACReaderFieldEdit * dayEdit;
|
||||
QIntValidator dayValidator;
|
||||
YACReaderFieldEdit * monthEdit;
|
||||
QIntValidator monthValidator;
|
||||
YACReaderFieldEdit * yearEdit;
|
||||
QIntValidator yearValidator;
|
||||
YACReaderFieldEdit * publisherEdit;
|
||||
YACReaderFieldEdit * formatEdit;
|
||||
QCheckBox * colorCheck;
|
||||
YACReaderFieldEdit * ageRatingEdit;
|
||||
|
||||
YACReaderFieldPlainTextEdit * synopsis;
|
||||
YACReaderFieldPlainTextEdit * characters;
|
||||
YACReaderFieldPlainTextEdit * notes;
|
||||
|
||||
QWidget * authorsBox;
|
||||
|
||||
QWidget * publishingBox;
|
||||
|
||||
QWidget * plotBox;
|
||||
|
||||
QDialogButtonBox *buttonBox;
|
||||
QPushButton *closeButton;
|
||||
QPushButton *saveButton;
|
||||
QPushButton *restoreButton; //??
|
||||
|
||||
void createTabBar();
|
||||
void createCoverBox();
|
||||
void createGeneralInfoBox();
|
||||
void createAuthorsBox();
|
||||
void createPublishingBox();
|
||||
void createPlotBox();
|
||||
|
||||
void createButtonBox();
|
||||
|
||||
void setDisableUniqueValues(bool disabled);
|
||||
|
||||
QList<Comic> comics;
|
||||
void closeEvent ( QCloseEvent * e );
|
||||
public:
|
||||
PropertiesDialog(QWidget * parent = 0);
|
||||
QString databasePath;
|
||||
QString basePath;
|
||||
|
||||
public slots:
|
||||
void setComics(QList<Comic> comics);
|
||||
void updateComics();
|
||||
void save();
|
||||
//Deprecated
|
||||
void setCover(const QPixmap & cover);
|
||||
void setFilename(const QString & name);
|
||||
void setNumpages(int pages);
|
||||
void setSize(float size);
|
||||
|
||||
};
|
||||
#endif
|
||||
|
68
YACReaderLibrary/rename_library_dialog.cpp
Normal file
68
YACReaderLibrary/rename_library_dialog.cpp
Normal file
@ -0,0 +1,68 @@
|
||||
#include "rename_library_dialog.h"
|
||||
|
||||
#include <QHBoxLayout>
|
||||
#include <QVBoxLayout>
|
||||
#include <QFileDialog>
|
||||
|
||||
|
||||
|
||||
RenameLibraryDialog::RenameLibraryDialog(QWidget * parent)
|
||||
:QDialog(parent)
|
||||
{
|
||||
setupUI();
|
||||
}
|
||||
|
||||
void RenameLibraryDialog::setupUI()
|
||||
{
|
||||
newNameLabel = new QLabel(tr("New Library Name : "));
|
||||
newNameEdit = new QLineEdit;
|
||||
newNameLabel->setBuddy(newNameEdit);
|
||||
|
||||
accept = new QPushButton(tr("Rename"));
|
||||
//accept->setDisabled(true);
|
||||
connect(accept,SIGNAL(clicked()),this,SLOT(rename()));
|
||||
|
||||
cancel = new QPushButton(tr("Cancel"));
|
||||
connect(cancel,SIGNAL(clicked()),this,SLOT(close()));
|
||||
|
||||
QHBoxLayout *nameLayout = new QHBoxLayout;
|
||||
|
||||
nameLayout->addWidget(newNameLabel);
|
||||
nameLayout->addWidget(newNameEdit);
|
||||
|
||||
QHBoxLayout *bottomLayout = new QHBoxLayout;
|
||||
bottomLayout->addStretch();
|
||||
bottomLayout->addWidget(accept);
|
||||
bottomLayout->addWidget(cancel);
|
||||
|
||||
QVBoxLayout *mainLayout = new QVBoxLayout;
|
||||
mainLayout->addLayout(nameLayout);
|
||||
mainLayout->addStretch();
|
||||
mainLayout->addLayout(bottomLayout);
|
||||
|
||||
QHBoxLayout * imgMainLayout = new QHBoxLayout;
|
||||
QLabel * imgLabel = new QLabel(this);
|
||||
QPixmap p(":/images/edit.png");
|
||||
imgLabel->setPixmap(p);
|
||||
imgMainLayout->addWidget(imgLabel);
|
||||
imgMainLayout->addLayout(mainLayout);
|
||||
|
||||
setLayout(imgMainLayout);
|
||||
|
||||
setModal(true);
|
||||
setWindowTitle(tr("Rename current library"));
|
||||
}
|
||||
|
||||
void RenameLibraryDialog::rename()
|
||||
{
|
||||
//accept->setEnabled(false);
|
||||
emit(renameLibrary(newNameEdit->text()));
|
||||
close();
|
||||
}
|
||||
|
||||
void RenameLibraryDialog::close()
|
||||
{
|
||||
newNameEdit->clear();
|
||||
//accept->setEnabled(false);
|
||||
QDialog::close();
|
||||
}
|
30
YACReaderLibrary/rename_library_dialog.h
Normal file
30
YACReaderLibrary/rename_library_dialog.h
Normal file
@ -0,0 +1,30 @@
|
||||
#ifndef __RENAME_LIBRARY_DIALOG_H
|
||||
#define __RENAME_LIBRARY_DIALOG_H
|
||||
|
||||
#include <QDialog>
|
||||
#include <QLabel>
|
||||
#include <QLineEdit>
|
||||
#include <QPushButton>
|
||||
|
||||
|
||||
class RenameLibraryDialog : public QDialog
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
RenameLibraryDialog(QWidget * parent = 0);
|
||||
private:
|
||||
QLabel * newNameLabel;
|
||||
QLineEdit * newNameEdit;
|
||||
QPushButton * accept;
|
||||
QPushButton * cancel;
|
||||
void setupUI();
|
||||
public slots:
|
||||
void rename();
|
||||
void close();
|
||||
signals:
|
||||
void renameLibrary(QString newName);
|
||||
};
|
||||
|
||||
|
||||
#endif
|
||||
|
62
YACReaderLibrary/server/controllers/dumpcontroller.cpp
Normal file
62
YACReaderLibrary/server/controllers/dumpcontroller.cpp
Normal file
@ -0,0 +1,62 @@
|
||||
/**
|
||||
@file
|
||||
@author Stefan Frings
|
||||
*/
|
||||
|
||||
#include "dumpcontroller.h"
|
||||
#include <QVariant>
|
||||
#include <QDateTime>
|
||||
|
||||
DumpController::DumpController(){}
|
||||
|
||||
void DumpController::service(HttpRequest& request, HttpResponse& response) {
|
||||
|
||||
response.setHeader("Content-Type", "text/html; charset=ISO-8859-1");
|
||||
response.setCookie(HttpCookie("firstCookie","hello",600));
|
||||
response.setCookie(HttpCookie("secondCookie","world",600));
|
||||
|
||||
QByteArray body("<html><body>");
|
||||
body.append("<b>Request:</b>");
|
||||
body.append("<br>Method: ");
|
||||
body.append(request.getMethod());
|
||||
body.append("<br>Path: ");
|
||||
body.append(request.getPath());
|
||||
body.append("<br>Version: ");
|
||||
body.append(request.getVersion());
|
||||
|
||||
body.append("<p><b>Headers:</b>");
|
||||
QMapIterator<QByteArray,QByteArray> i(request.getHeaderMap());
|
||||
while (i.hasNext()) {
|
||||
i.next();
|
||||
body.append("<br>");
|
||||
body.append(i.key());
|
||||
body.append("=");
|
||||
body.append(i.value());
|
||||
}
|
||||
|
||||
body.append("<p><b>Parameters:</b>");
|
||||
i=QMapIterator<QByteArray,QByteArray>(request.getParameterMap());
|
||||
while (i.hasNext()) {
|
||||
i.next();
|
||||
body.append("<br>");
|
||||
body.append(i.key());
|
||||
body.append("=");
|
||||
body.append(i.value());
|
||||
}
|
||||
|
||||
body.append("<p><b>Cookies:</b>");
|
||||
i=QMapIterator<QByteArray,QByteArray>(request.getCookieMap());
|
||||
while (i.hasNext()) {
|
||||
i.next();
|
||||
body.append("<br>");
|
||||
body.append(i.key());
|
||||
body.append("=");
|
||||
body.append(i.value());
|
||||
}
|
||||
|
||||
body.append("<p><b>Body:</b><br>");
|
||||
body.append(request.getBody());
|
||||
|
||||
body.append("</body></html>");
|
||||
response.write(body,true);
|
||||
}
|
29
YACReaderLibrary/server/controllers/dumpcontroller.h
Normal file
29
YACReaderLibrary/server/controllers/dumpcontroller.h
Normal file
@ -0,0 +1,29 @@
|
||||
/**
|
||||
@file
|
||||
@author Stefan Frings
|
||||
*/
|
||||
|
||||
#ifndef DUMPCONTROLLER_H
|
||||
#define DUMPCONTROLLER_H
|
||||
|
||||
#include "httprequest.h"
|
||||
#include "httpresponse.h"
|
||||
#include "httprequesthandler.h"
|
||||
|
||||
/**
|
||||
This controller dumps the received HTTP request in the response.
|
||||
*/
|
||||
|
||||
class DumpController : public HttpRequestHandler {
|
||||
Q_OBJECT
|
||||
Q_DISABLE_COPY(DumpController);
|
||||
public:
|
||||
|
||||
/** Constructor */
|
||||
DumpController();
|
||||
|
||||
/** Generates the response */
|
||||
void service(HttpRequest& request, HttpResponse& response);
|
||||
};
|
||||
|
||||
#endif // DUMPCONTROLLER_H
|
38
YACReaderLibrary/server/controllers/fileuploadcontroller.cpp
Normal file
38
YACReaderLibrary/server/controllers/fileuploadcontroller.cpp
Normal file
@ -0,0 +1,38 @@
|
||||
/**
|
||||
@file
|
||||
@author Stefan Frings
|
||||
*/
|
||||
|
||||
#include "fileuploadcontroller.h"
|
||||
|
||||
FileUploadController::FileUploadController() {}
|
||||
|
||||
void FileUploadController::service(HttpRequest& request, HttpResponse& response) {
|
||||
|
||||
if (request.getParameter("action")=="show") {
|
||||
response.setHeader("Content-Type", "image/jpeg");
|
||||
QTemporaryFile* file=request.getUploadedFile("file1");
|
||||
if (file) {
|
||||
while (!file->atEnd() && !file->error()) {
|
||||
QByteArray buffer=file->read(65536);
|
||||
response.write(buffer);
|
||||
}
|
||||
}
|
||||
else {
|
||||
response.write("upload failed");
|
||||
}
|
||||
}
|
||||
|
||||
else {
|
||||
response.setHeader("Content-Type", "text/html; charset=ISO-8859-1");
|
||||
response.write("<html><body>");
|
||||
response.write("Upload a JPEG image file<p>");
|
||||
response.write("<form method=\"post\" enctype=\"multipart/form-data\">");
|
||||
response.write(" <input type=\"hidden\" name=\"action\" value=\"show\">");
|
||||
response.write(" File: <input type=\"file\" name=\"file1\"><br>");
|
||||
response.write(" <input type=\"submit\">");
|
||||
response.write("</form>");
|
||||
response.write("</body></html>",true);
|
||||
}
|
||||
}
|
||||
|
30
YACReaderLibrary/server/controllers/fileuploadcontroller.h
Normal file
30
YACReaderLibrary/server/controllers/fileuploadcontroller.h
Normal file
@ -0,0 +1,30 @@
|
||||
/**
|
||||
@file
|
||||
@author Stefan Frings
|
||||
*/
|
||||
|
||||
#ifndef FILEUPLOADCONTROLLER_H
|
||||
#define FILEUPLOADCONTROLLER_H
|
||||
|
||||
#include "httprequest.h"
|
||||
#include "httpresponse.h"
|
||||
#include "httprequesthandler.h"
|
||||
|
||||
/**
|
||||
This controller displays a HTML form for file upload and recieved the file.
|
||||
*/
|
||||
|
||||
|
||||
class FileUploadController : public HttpRequestHandler {
|
||||
Q_OBJECT
|
||||
Q_DISABLE_COPY(FileUploadController);
|
||||
public:
|
||||
|
||||
/** Constructor */
|
||||
FileUploadController();
|
||||
|
||||
/** Generates the response */
|
||||
void service(HttpRequest& request, HttpResponse& response);
|
||||
};
|
||||
|
||||
#endif // FILEUPLOADCONTROLLER_H
|
33
YACReaderLibrary/server/controllers/formcontroller.cpp
Normal file
33
YACReaderLibrary/server/controllers/formcontroller.cpp
Normal file
@ -0,0 +1,33 @@
|
||||
/**
|
||||
@file
|
||||
@author Stefan Frings
|
||||
*/
|
||||
|
||||
#include "formcontroller.h"
|
||||
|
||||
FormController::FormController() {}
|
||||
|
||||
void FormController::service(HttpRequest& request, HttpResponse& response) {
|
||||
|
||||
response.setHeader("Content-Type", "text/html; charset=ISO-8859-1");
|
||||
|
||||
if (request.getParameter("action")=="show") {
|
||||
response.write("<html><body>");
|
||||
response.write("Name = ");
|
||||
response.write(request.getParameter("name"));
|
||||
response.write("<br>City = ");
|
||||
response.write(request.getParameter("city"));
|
||||
response.write("</body></html>",true);
|
||||
}
|
||||
else {
|
||||
response.write("<html><body>");
|
||||
response.write("<form method=\"post\">");
|
||||
response.write(" <input type=\"hidden\" name=\"action\" value=\"show\">");
|
||||
response.write(" Name: <input type=\"text\" name=\"name\"><br>");
|
||||
response.write(" City: <input type=\"text\" name=\"city\"><br>");
|
||||
response.write(" <input type=\"submit\">");
|
||||
response.write("</form>");
|
||||
response.write("</body></html>",true);
|
||||
}
|
||||
}
|
||||
|
30
YACReaderLibrary/server/controllers/formcontroller.h
Normal file
30
YACReaderLibrary/server/controllers/formcontroller.h
Normal file
@ -0,0 +1,30 @@
|
||||
/**
|
||||
@file
|
||||
@author Stefan Frings
|
||||
*/
|
||||
|
||||
#ifndef FORMCONTROLLER_H
|
||||
#define FORMCONTROLLER_H
|
||||
|
||||
#include "httprequest.h"
|
||||
#include "httpresponse.h"
|
||||
#include "httprequesthandler.h"
|
||||
|
||||
/**
|
||||
This controller displays a HTML form and dumps the submitted input.
|
||||
*/
|
||||
|
||||
|
||||
class FormController : public HttpRequestHandler {
|
||||
Q_OBJECT
|
||||
Q_DISABLE_COPY(FormController);
|
||||
public:
|
||||
|
||||
/** Constructor */
|
||||
FormController();
|
||||
|
||||
/** Generates the response */
|
||||
void service(HttpRequest& request, HttpResponse& response);
|
||||
};
|
||||
|
||||
#endif // FORMCONTROLLER_H
|
31
YACReaderLibrary/server/controllers/sessioncontroller.cpp
Normal file
31
YACReaderLibrary/server/controllers/sessioncontroller.cpp
Normal file
@ -0,0 +1,31 @@
|
||||
/**
|
||||
@file
|
||||
@author Stefan Frings
|
||||
*/
|
||||
|
||||
#include "sessioncontroller.h"
|
||||
#include "../static.h"
|
||||
#include <QVariant>
|
||||
#include <QDateTime>
|
||||
|
||||
SessionController::SessionController(){}
|
||||
|
||||
void SessionController::service(HttpRequest& request, HttpResponse& response) {
|
||||
|
||||
response.setHeader("Content-Type", "text/html; charset=ISO-8859-1");
|
||||
|
||||
// Get current session, or create a new one
|
||||
HttpSession session=Static::sessionStore->getSession(request,response);
|
||||
if (!session.contains("startTime")) {
|
||||
response.write("<html><body>New session started. Reload this page now.</body></html>");
|
||||
session.set("startTime",QDateTime::currentDateTime());
|
||||
}
|
||||
|
||||
else {
|
||||
QDateTime startTime=session.get("startTime").toDateTime();
|
||||
response.write("<html><body>Your session started ");
|
||||
response.write(startTime.toString().toLatin1());
|
||||
response.write("</body></html>");
|
||||
}
|
||||
|
||||
}
|
29
YACReaderLibrary/server/controllers/sessioncontroller.h
Normal file
29
YACReaderLibrary/server/controllers/sessioncontroller.h
Normal file
@ -0,0 +1,29 @@
|
||||
/**
|
||||
@file
|
||||
@author Stefan Frings
|
||||
*/
|
||||
|
||||
#ifndef SESSIONCONTROLLER_H
|
||||
#define SESSIONCONTROLLER_H
|
||||
|
||||
#include "httprequest.h"
|
||||
#include "httpresponse.h"
|
||||
#include "httprequesthandler.h"
|
||||
|
||||
/**
|
||||
This controller demonstrates how to use sessions.
|
||||
*/
|
||||
|
||||
class SessionController : public HttpRequestHandler {
|
||||
Q_OBJECT
|
||||
Q_DISABLE_COPY(SessionController);
|
||||
public:
|
||||
|
||||
/** Constructor */
|
||||
SessionController();
|
||||
|
||||
/** Generates the response */
|
||||
void service(HttpRequest& request, HttpResponse& response);
|
||||
};
|
||||
|
||||
#endif // SESSIONCONTROLLER_H
|
31
YACReaderLibrary/server/controllers/templatecontroller.cpp
Normal file
31
YACReaderLibrary/server/controllers/templatecontroller.cpp
Normal file
@ -0,0 +1,31 @@
|
||||
/**
|
||||
@file
|
||||
@author Stefan Frings
|
||||
*/
|
||||
|
||||
#include "templatecontroller.h"
|
||||
#include "template.h"
|
||||
#include "../static.h"
|
||||
|
||||
TemplateController::TemplateController(){}
|
||||
|
||||
void TemplateController::service(HttpRequest& request, HttpResponse& response) {
|
||||
|
||||
response.setHeader("Content-Type", "text/html; charset=ISO-8859-1");
|
||||
|
||||
Template t=Static::templateLoader->getTemplate("demo",request.getHeader("Accept-Language"));
|
||||
t.enableWarnings();
|
||||
t.setVariable("path",request.getPath());
|
||||
QMap<QByteArray,QByteArray> headers=request.getHeaderMap();
|
||||
QMapIterator<QByteArray,QByteArray> iterator(headers);
|
||||
t.loop("header",headers.size());
|
||||
int i=0;
|
||||
while (iterator.hasNext()) {
|
||||
iterator.next();
|
||||
t.setVariable(QString("header%1.name").arg(i),QString(iterator.key()));
|
||||
t.setVariable(QString("header%1.value").arg(i),QString(iterator.value()));
|
||||
++i;
|
||||
}
|
||||
|
||||
response.write(t.toLatin1(),true);
|
||||
}
|
30
YACReaderLibrary/server/controllers/templatecontroller.h
Normal file
30
YACReaderLibrary/server/controllers/templatecontroller.h
Normal file
@ -0,0 +1,30 @@
|
||||
/**
|
||||
@file
|
||||
@author Stefan Frings
|
||||
*/
|
||||
|
||||
#ifndef TEMPLATECONTROLLER_H
|
||||
#define TEMPLATECONTROLLER_H
|
||||
|
||||
#include "httprequest.h"
|
||||
#include "httpresponse.h"
|
||||
#include "httprequesthandler.h"
|
||||
|
||||
/**
|
||||
This controller generates a website using the template engine.
|
||||
It generates a Latin1 (ISO-8859-1) encoded website from a UTF-8 encoded template file.
|
||||
*/
|
||||
|
||||
class TemplateController : public HttpRequestHandler {
|
||||
Q_OBJECT
|
||||
Q_DISABLE_COPY(TemplateController);
|
||||
public:
|
||||
|
||||
/** Constructor */
|
||||
TemplateController();
|
||||
|
||||
/** Generates the response */
|
||||
void service(HttpRequest& request, HttpResponse& response);
|
||||
};
|
||||
|
||||
#endif // TEMPLATECONTROLLER_H
|
4
YACReaderLibrary/server/documentcache.h
Normal file
4
YACReaderLibrary/server/documentcache.h
Normal file
@ -0,0 +1,4 @@
|
||||
#ifndef DOCUMENTCACHE_H
|
||||
#define DOCUMENTCACHE_H
|
||||
|
||||
#endif // DOCUMENTCACHE_H
|
12
YACReaderLibrary/server/lib/bfHttpServer/bfHttpServer.pri
Normal file
12
YACReaderLibrary/server/lib/bfHttpServer/bfHttpServer.pri
Normal file
@ -0,0 +1,12 @@
|
||||
INCLUDEPATH += $$PWD
|
||||
DEPENDPATH += $$PWD
|
||||
|
||||
HEADERS += $$PWD/httplistener.h $$PWD/httpconnectionhandler.h $$PWD/httpconnectionhandlerpool.h $$PWD/httprequest.h $$PWD/httpresponse.h $$PWD/httpcookie.h $$PWD/httprequesthandler.h
|
||||
HEADERS += $$PWD/httpsession.h $$PWD/httpsessionstore.h
|
||||
HEADERS += $$PWD/staticfilecontroller.h
|
||||
|
||||
SOURCES += $$PWD/httplistener.cpp $$PWD/httpconnectionhandler.cpp $$PWD/httpconnectionhandlerpool.cpp $$PWD/httprequest.cpp $$PWD/httpresponse.cpp $$PWD/httpcookie.cpp $$PWD/httprequesthandler.cpp
|
||||
SOURCES += $$PWD/httpsession.cpp $$PWD/httpsessionstore.cpp
|
||||
SOURCES += $$PWD/staticfilecontroller.cpp
|
||||
|
||||
OTHER_FILES += $$PWD/../doc/readme.txt
|
@ -0,0 +1,154 @@
|
||||
/**
|
||||
@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; // pdiener: it is not busy if it is new
|
||||
// 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() {
|
||||
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(int 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);
|
||||
currentRequest=0;
|
||||
}
|
||||
|
||||
|
||||
bool HttpConnectionHandler::isBusy() {
|
||||
//return socket.isOpen();
|
||||
return busy; // pdiener: changed this from socket readout to bool variable
|
||||
}
|
||||
|
||||
void HttpConnectionHandler::setBusy() {
|
||||
this->busy = true;
|
||||
}
|
||||
|
||||
|
||||
void HttpConnectionHandler::readTimeout() {
|
||||
qDebug("HttpConnectionHandler (%p): read timeout occured",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();
|
||||
delete currentRequest;
|
||||
currentRequest=0;
|
||||
readTimer.stop();
|
||||
busy = false; // pdiener: now we have finished
|
||||
}
|
||||
|
||||
void HttpConnectionHandler::read() {
|
||||
#ifdef SUPERVERBOSE
|
||||
qDebug("HttpConnectionHandler (%x): read input",(unsigned int) 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);
|
||||
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);
|
||||
}
|
||||
// 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;
|
||||
}
|
||||
}
|
@ -0,0 +1,97 @@
|
||||
/**
|
||||
@file
|
||||
@author Stefan Frings
|
||||
*/
|
||||
|
||||
#ifndef HTTPCONNECTIONHANDLER_H
|
||||
#define HTTPCONNECTIONHANDLER_H
|
||||
|
||||
#include <QTcpSocket>
|
||||
#include <QSettings>
|
||||
#include <QTimer>
|
||||
#include <QThread>
|
||||
#include "httprequest.h"
|
||||
#include "httprequesthandler.h"
|
||||
|
||||
/**
|
||||
The connection handler accepts incoming connections and dispatches incoming requests to to a
|
||||
request mapper. Since HTTP clients can send multiple requests before waiting for the response,
|
||||
the incoming requests are queued and processed one after the other.
|
||||
<p>
|
||||
Example for the required configuration settings:
|
||||
<code><pre>
|
||||
readTimeout=60000
|
||||
maxRequestSize=16000
|
||||
maxMultiPartSize=1000000
|
||||
</pre></code>
|
||||
<p>
|
||||
The readTimeout value defines the maximum time to wait for a complete HTTP request.
|
||||
@see HttpRequest for description of config settings maxRequestSize and maxMultiPartSize
|
||||
*/
|
||||
|
||||
class HttpConnectionHandler : public QThread {
|
||||
Q_OBJECT
|
||||
Q_DISABLE_COPY(HttpConnectionHandler)
|
||||
public:
|
||||
|
||||
/**
|
||||
Constructor.
|
||||
@param settings Configuration settings of the HTTP webserver
|
||||
@param requestHandler handler that will process each incomin HTTP request
|
||||
*/
|
||||
HttpConnectionHandler(QSettings* settings, HttpRequestHandler* requestHandler);
|
||||
|
||||
/** Destructor */
|
||||
virtual ~HttpConnectionHandler();
|
||||
|
||||
/** Returns true, if this handler is busy */
|
||||
bool isBusy();
|
||||
|
||||
/** Mark this handler as busy */
|
||||
void setBusy();
|
||||
|
||||
/** This shows the busy-state from a very early time */
|
||||
bool busy;
|
||||
|
||||
private:
|
||||
|
||||
/** Configuration settings */
|
||||
QSettings* settings;
|
||||
|
||||
/** TCP socket of the current connection */
|
||||
QTcpSocket socket;
|
||||
|
||||
/** Time for read timeout detection */
|
||||
QTimer readTimer;
|
||||
|
||||
/** Storage for the current incoming HTTP request */
|
||||
HttpRequest* currentRequest;
|
||||
|
||||
/** Dispatches received requests to services */
|
||||
HttpRequestHandler* requestHandler;
|
||||
|
||||
/** Executes the htreads own event loop */
|
||||
void run();
|
||||
|
||||
public slots:
|
||||
|
||||
/**
|
||||
Received from from the listener, when the handler shall start processing a new connection.
|
||||
@param socketDescriptor references the accepted connection.
|
||||
*/
|
||||
void handleConnection(int socketDescriptor);
|
||||
|
||||
private slots:
|
||||
|
||||
/** Received from the socket when a read-timeout occured */
|
||||
void readTimeout();
|
||||
|
||||
/** Received from the socket when incoming data can be read */
|
||||
void read();
|
||||
|
||||
/** Received from the socket when a connection has been closed */
|
||||
void disconnected();
|
||||
|
||||
};
|
||||
|
||||
#endif // HTTPCONNECTIONHANDLER_H
|
@ -0,0 +1,55 @@
|
||||
#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",1000).toInt());
|
||||
connect(&cleanupTimer, SIGNAL(timeout()), SLOT(cleanup()));
|
||||
}
|
||||
|
||||
|
||||
HttpConnectionHandlerPool::~HttpConnectionHandlerPool() {
|
||||
foreach(HttpConnectionHandler* handler, pool) {
|
||||
delete handler;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
HttpConnectionHandler* HttpConnectionHandlerPool::getConnectionHandler() {
|
||||
HttpConnectionHandler* freeHandler=0;
|
||||
foreach(HttpConnectionHandler* handler, pool) {
|
||||
if (!handler->isBusy()) {
|
||||
freeHandler=handler;
|
||||
}
|
||||
}
|
||||
if (!freeHandler) {
|
||||
int maxConnectionHandlers=settings->value("maxThreads",10).toInt();
|
||||
if (pool.count()<maxConnectionHandlers) {
|
||||
freeHandler=new HttpConnectionHandler(settings,requestHandler);
|
||||
pool.append(freeHandler);
|
||||
}
|
||||
}
|
||||
if (freeHandler) freeHandler->busy = true; // pdiener: set it to busy-state immediately
|
||||
return freeHandler;
|
||||
}
|
||||
|
||||
|
||||
|
||||
void HttpConnectionHandlerPool::cleanup() {
|
||||
int maxIdleHandlers=settings->value("minThreads",1).toInt();
|
||||
int idleCounter=0;
|
||||
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
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,69 @@
|
||||
#ifndef HTTPCONNECTIONHANDLERPOOL_H
|
||||
#define HTTPCONNECTIONHANDLERPOOL_H
|
||||
|
||||
#include <QList>
|
||||
#include <QTimer>
|
||||
#include <QObject>
|
||||
#include "httpconnectionhandler.h"
|
||||
|
||||
/**
|
||||
Pool of http connection handlers. Connection handlers are created on demand and idle handlers are
|
||||
cleaned up in regular time intervals.
|
||||
<p>
|
||||
Example for the required configuration settings:
|
||||
<code><pre>
|
||||
minThreads=1
|
||||
maxThreads=10
|
||||
cleanupInterval=1000
|
||||
maxRequestSize=16000
|
||||
maxMultiPartSize=1000000
|
||||
</pre></code>
|
||||
The pool is empty initially and grows with the number of concurrent
|
||||
connections. A timer removes one idle connection handler at each
|
||||
interval, but a it leaves some spare handlers in memory to improve
|
||||
performance.
|
||||
@see HttpConnectionHandler for description of config settings readTimeout
|
||||
@see HttpRequest for description of config settings maxRequestSize and maxMultiPartSize
|
||||
*/
|
||||
|
||||
class HttpConnectionHandlerPool : public QObject {
|
||||
Q_OBJECT
|
||||
Q_DISABLE_COPY(HttpConnectionHandlerPool)
|
||||
public:
|
||||
|
||||
/**
|
||||
Constructor.
|
||||
@param settings Configuration settings for the HTTP server. Must not be 0.
|
||||
@param requestHandler The handler that will process each received HTTP request.
|
||||
@warning The requestMapper gets deleted by the destructor of this pool
|
||||
*/
|
||||
HttpConnectionHandlerPool(QSettings* settings, HttpRequestHandler* requestHandler);
|
||||
|
||||
/** Destructor */
|
||||
virtual ~HttpConnectionHandlerPool();
|
||||
|
||||
/** Get a free connection handler, or 0 if not available. */
|
||||
HttpConnectionHandler* getConnectionHandler();
|
||||
|
||||
private:
|
||||
|
||||
/** Settings for this pool */
|
||||
QSettings* settings;
|
||||
|
||||
/** Will be assigned to each Connectionhandler during their creation */
|
||||
HttpRequestHandler* requestHandler;
|
||||
|
||||
/** Pool of connection handlers */
|
||||
QList<HttpConnectionHandler*> pool;
|
||||
|
||||
/** Timer to clean-up unused connection handler */
|
||||
QTimer cleanupTimer;
|
||||
|
||||
private slots:
|
||||
|
||||
/** Received from the clean-up timer. */
|
||||
void cleanup();
|
||||
|
||||
};
|
||||
|
||||
#endif // HTTPCONNECTIONHANDLERPOOL_H
|
199
YACReaderLibrary/server/lib/bfHttpServer/httpcookie.cpp
Normal file
199
YACReaderLibrary/server/lib/bfHttpServer/httpcookie.cpp
Normal file
@ -0,0 +1,199 @@
|
||||
/**
|
||||
@file
|
||||
@author Stefan Frings
|
||||
*/
|
||||
|
||||
#include "httpcookie.h"
|
||||
|
||||
HttpCookie::HttpCookie() {
|
||||
version=1;
|
||||
maxAge=0;
|
||||
secure=false;
|
||||
}
|
||||
|
||||
HttpCookie::HttpCookie(const QByteArray name, const QByteArray value, const int maxAge, const QByteArray path, const QByteArray comment, const QByteArray domain, const bool secure) {
|
||||
this->name=name;
|
||||
this->value=value;
|
||||
this->maxAge=maxAge;
|
||||
this->path=path;
|
||||
this->comment=comment;
|
||||
this->domain=domain;
|
||||
this->secure=secure;
|
||||
this->version=1;
|
||||
}
|
||||
|
||||
HttpCookie::HttpCookie(const QByteArray source) {
|
||||
version=1;
|
||||
maxAge=0;
|
||||
secure=false;
|
||||
QList<QByteArray> list=splitCSV(source);
|
||||
foreach(QByteArray part, list) {
|
||||
|
||||
// Split the part into name and value
|
||||
QByteArray name;
|
||||
QByteArray value;
|
||||
int posi=part.indexOf('=');
|
||||
if (posi) {
|
||||
name=part.left(posi).trimmed();
|
||||
value=part.mid(posi+1).trimmed();
|
||||
}
|
||||
else {
|
||||
name=part.trimmed();
|
||||
value="";
|
||||
}
|
||||
|
||||
// Set fields
|
||||
if (name=="Comment") {
|
||||
comment=value;
|
||||
}
|
||||
else if (name=="Domain") {
|
||||
domain=value;
|
||||
}
|
||||
else if (name=="Max-Age") {
|
||||
maxAge=value.toInt();
|
||||
}
|
||||
else if (name=="Path") {
|
||||
path=value;
|
||||
}
|
||||
else if (name=="Secure") {
|
||||
secure=true;
|
||||
}
|
||||
else if (name=="Version") {
|
||||
version=value.toInt();
|
||||
}
|
||||
else {
|
||||
if (this->name.isEmpty()) {
|
||||
this->name=name;
|
||||
this->value=value;
|
||||
}
|
||||
else {
|
||||
qWarning("HttpCookie: Ignoring unknown %s=%s",name.data(),value.data());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
QByteArray HttpCookie::toByteArray() const {
|
||||
QByteArray buffer(name);
|
||||
buffer.append('=');
|
||||
buffer.append(value);
|
||||
if (!comment.isEmpty()) {
|
||||
buffer.append("; Comment=");
|
||||
buffer.append(comment);
|
||||
}
|
||||
if (!domain.isEmpty()) {
|
||||
buffer.append("; Domain=");
|
||||
buffer.append(domain);
|
||||
}
|
||||
if (maxAge!=0) {
|
||||
buffer.append("; Max-Age=");
|
||||
buffer.append(QByteArray::number(maxAge));
|
||||
}
|
||||
if (!path.isEmpty()) {
|
||||
buffer.append("; Path=");
|
||||
buffer.append(path);
|
||||
}
|
||||
if (secure) {
|
||||
buffer.append("; Secure");
|
||||
}
|
||||
buffer.append("; Version=");
|
||||
buffer.append(QByteArray::number(version));
|
||||
return buffer;
|
||||
}
|
||||
|
||||
void HttpCookie::setName(const QByteArray name){
|
||||
this->name=name;
|
||||
}
|
||||
|
||||
void HttpCookie::setValue(const QByteArray value){
|
||||
this->value=value;
|
||||
}
|
||||
|
||||
void HttpCookie::setComment(const QByteArray comment){
|
||||
this->comment=comment;
|
||||
}
|
||||
|
||||
void HttpCookie::setDomain(const QByteArray domain){
|
||||
this->domain=domain;
|
||||
}
|
||||
|
||||
void HttpCookie::setMaxAge(const int maxAge){
|
||||
this->maxAge=maxAge;
|
||||
}
|
||||
|
||||
void HttpCookie::setPath(const QByteArray path){
|
||||
this->path=path;
|
||||
}
|
||||
|
||||
void HttpCookie::setSecure(const bool secure){
|
||||
this->secure=secure;
|
||||
}
|
||||
|
||||
QByteArray HttpCookie::getName() const {
|
||||
return name;
|
||||
}
|
||||
|
||||
QByteArray HttpCookie::getValue() const {
|
||||
return value;
|
||||
}
|
||||
|
||||
QByteArray HttpCookie::getComment() const {
|
||||
return comment;
|
||||
}
|
||||
|
||||
QByteArray HttpCookie::getDomain() const {
|
||||
return domain;
|
||||
}
|
||||
|
||||
int HttpCookie::getMaxAge() const {
|
||||
return maxAge;
|
||||
}
|
||||
|
||||
QByteArray HttpCookie::getPath() const {
|
||||
return path;
|
||||
}
|
||||
|
||||
bool HttpCookie::getSecure() const {
|
||||
return secure;
|
||||
}
|
||||
|
||||
int HttpCookie::getVersion() const {
|
||||
return version;
|
||||
}
|
||||
|
||||
QList<QByteArray> HttpCookie::splitCSV(const QByteArray source) {
|
||||
bool inString=false;
|
||||
QList<QByteArray> list;
|
||||
QByteArray buffer;
|
||||
for (int i=0; i<source.size(); ++i) {
|
||||
char c=source.at(i);
|
||||
if (inString==false) {
|
||||
if (c=='\"') {
|
||||
inString=true;
|
||||
}
|
||||
else if (c==';') {
|
||||
QByteArray trimmed=buffer.trimmed();
|
||||
if (!trimmed.isEmpty()) {
|
||||
list.append(trimmed);
|
||||
}
|
||||
buffer.clear();
|
||||
}
|
||||
else {
|
||||
buffer.append(c);
|
||||
}
|
||||
}
|
||||
else {
|
||||
if (c=='\"') {
|
||||
inString=false;
|
||||
}
|
||||
else {
|
||||
buffer.append(c);
|
||||
}
|
||||
}
|
||||
}
|
||||
QByteArray trimmed=buffer.trimmed();
|
||||
if (!trimmed.isEmpty()) {
|
||||
list.append(trimmed);
|
||||
}
|
||||
return list;
|
||||
}
|
110
YACReaderLibrary/server/lib/bfHttpServer/httpcookie.h
Normal file
110
YACReaderLibrary/server/lib/bfHttpServer/httpcookie.h
Normal file
@ -0,0 +1,110 @@
|
||||
/**
|
||||
@file
|
||||
@author Stefan Frings
|
||||
*/
|
||||
|
||||
#ifndef HTTPCOOKIE_H
|
||||
#define HTTPCOOKIE_H
|
||||
|
||||
#include <QList>
|
||||
#include <QByteArray>
|
||||
|
||||
/**
|
||||
HTTP cookie as defined in RFC 2109. This class can also parse
|
||||
RFC 2965 cookies, but skips fields that are not defined in RFC
|
||||
2109.
|
||||
*/
|
||||
|
||||
class HttpCookie
|
||||
{
|
||||
public:
|
||||
|
||||
/** Creates an empty cookie */
|
||||
HttpCookie();
|
||||
|
||||
/**
|
||||
Create a cookie and set name/value pair.
|
||||
@param name name of the cookie
|
||||
@param value value of the cookie
|
||||
@param maxAge maximum age of the cookie in seconds. 0=discard immediately
|
||||
@param path Path for that the cookie will be sent, default="/" which means the whole domain
|
||||
@param comment Optional comment, may be displayed by the web browser somewhere
|
||||
@param domain Optional domain for that the cookie will be sent. Defaults to the current domain
|
||||
@param secure If true, the cookie will only be sent on secure connections
|
||||
*/
|
||||
HttpCookie(const QByteArray name, const QByteArray value, const int maxAge, const QByteArray path="/", const QByteArray comment=QByteArray(), const QByteArray domain=QByteArray(), const bool secure=false);
|
||||
|
||||
/**
|
||||
Create a cookie from a string.
|
||||
@param source String as received in a HTTP Cookie2 header.
|
||||
*/
|
||||
HttpCookie(const QByteArray source);
|
||||
|
||||
/** Convert this cookie to a string that may be used in a Set-Cookie2 header. */
|
||||
QByteArray toByteArray() const ;
|
||||
|
||||
/**
|
||||
Split a string list into parts, where each part is delimited by semicolon.
|
||||
Semicolons within double quotes are skipped. Double quotes are removed.
|
||||
*/
|
||||
static QList<QByteArray> splitCSV(const QByteArray source);
|
||||
|
||||
/** Set the name of this cookie */
|
||||
void setName(const QByteArray name);
|
||||
|
||||
/** Set the value of this cookie */
|
||||
void setValue(const QByteArray value);
|
||||
|
||||
/** Set the comment of this cookie */
|
||||
void setComment(const QByteArray comment);
|
||||
|
||||
/** Set the domain of this cookie */
|
||||
void setDomain(const QByteArray domain);
|
||||
|
||||
/** Set the maximum age of this cookie in seconds. 0=discard immediately */
|
||||
void setMaxAge(const int maxAge);
|
||||
|
||||
/** Set the path for that the cookie will be sent, default="/" which means the whole domain */
|
||||
void setPath(const QByteArray path);
|
||||
|
||||
/** Set secure mode, so that the cokkie will only be sent on secure connections */
|
||||
void setSecure(const bool secure);
|
||||
|
||||
/** Get the name of this cookie */
|
||||
QByteArray getName() const;
|
||||
|
||||
/** Get the value of this cookie */
|
||||
QByteArray getValue() const;
|
||||
|
||||
/** Get the comment of this cookie */
|
||||
QByteArray getComment() const;
|
||||
|
||||
/** Get the domain of this cookie */
|
||||
QByteArray getDomain() const;
|
||||
|
||||
/** Set the maximum age of this cookie in seconds. */
|
||||
int getMaxAge() const;
|
||||
|
||||
/** Set the path of this cookie */
|
||||
QByteArray getPath() const;
|
||||
|
||||
/** Get the secure flag of this cookie */
|
||||
bool getSecure() const;
|
||||
|
||||
/** Returns always 1 */
|
||||
int getVersion() const;
|
||||
|
||||
private:
|
||||
|
||||
QByteArray name;
|
||||
QByteArray value;
|
||||
QByteArray comment;
|
||||
QByteArray domain;
|
||||
int maxAge;
|
||||
QByteArray path;
|
||||
bool secure;
|
||||
int version;
|
||||
|
||||
};
|
||||
|
||||
#endif // HTTPCOOKIE_H
|
56
YACReaderLibrary/server/lib/bfHttpServer/httplistener.cpp
Normal file
56
YACReaderLibrary/server/lib/bfHttpServer/httplistener.cpp
Normal file
@ -0,0 +1,56 @@
|
||||
/**
|
||||
@file
|
||||
@author Stefan Frings
|
||||
*/
|
||||
|
||||
#include "httplistener.h"
|
||||
#include "httpconnectionhandler.h"
|
||||
#include "httpconnectionhandlerpool.h"
|
||||
#include <QCoreApplication>
|
||||
|
||||
HttpListener::HttpListener(QSettings* settings, HttpRequestHandler* requestHandler, QObject *parent)
|
||||
: QTcpServer(parent), pool(settings,requestHandler)
|
||||
{
|
||||
Q_ASSERT(settings!=0);
|
||||
this->settings=settings;
|
||||
// Start listening
|
||||
int port=settings->value("port").toInt();
|
||||
listen(QHostAddress::Any, port);
|
||||
if (!isListening()) {
|
||||
qCritical("HttpListener: Cannot bind on port %i: %s",port,qPrintable(errorString()));
|
||||
}
|
||||
else {
|
||||
qDebug("HttpListener: Listening on port %i",port);
|
||||
}
|
||||
}
|
||||
|
||||
HttpListener::~HttpListener() {
|
||||
close();
|
||||
qDebug("HttpListener: closed");
|
||||
}
|
||||
|
||||
|
||||
void HttpListener::incomingConnection(int socketDescriptor) {
|
||||
#ifdef SUPERVERBOSE
|
||||
qDebug("HttpListener: New connection");
|
||||
#endif
|
||||
HttpConnectionHandler* freeHandler=pool.getConnectionHandler();
|
||||
|
||||
// Let the handler process the new connection.
|
||||
if (freeHandler) {
|
||||
// The descriptor is passed via signal/slot because the handler lives in another
|
||||
// thread and cannot open the socket when called by another thread.
|
||||
connect(this,SIGNAL(handleConnection(int)),freeHandler,SLOT(handleConnection(int)));
|
||||
emit handleConnection(socketDescriptor);
|
||||
disconnect(this,SIGNAL(handleConnection(int)),freeHandler,SLOT(handleConnection(int)));
|
||||
}
|
||||
else {
|
||||
// Reject the connection
|
||||
qDebug("HttpListener: Too many connections");
|
||||
QTcpSocket* socket=new QTcpSocket(this);
|
||||
socket->setSocketDescriptor(socketDescriptor);
|
||||
connect(socket, SIGNAL(disconnected()), socket, SLOT(deleteLater()));
|
||||
socket->write("HTTP/1.1 503 too many connections\r\nConnection: close\r\n\r\nToo many connections\r\n");
|
||||
socket->disconnectFromHost();
|
||||
}
|
||||
}
|
76
YACReaderLibrary/server/lib/bfHttpServer/httplistener.h
Normal file
76
YACReaderLibrary/server/lib/bfHttpServer/httplistener.h
Normal file
@ -0,0 +1,76 @@
|
||||
/**
|
||||
@file
|
||||
@author Stefan Frings
|
||||
*/
|
||||
|
||||
#ifndef LISTENER_H
|
||||
#define LISTENER_H
|
||||
|
||||
#include <QTcpServer>
|
||||
#include <QSettings>
|
||||
#include <QBasicTimer>
|
||||
#include "httpconnectionhandler.h"
|
||||
#include "httpconnectionhandlerpool.h"
|
||||
#include "httprequesthandler.h"
|
||||
|
||||
/**
|
||||
Listens for incoming TCP connections and passes control to
|
||||
one of the pooled connection handlers. This class is also
|
||||
responsible for managing the pool.
|
||||
<p>
|
||||
Example for the required settings in the config file:
|
||||
<code><pre>
|
||||
port=8080
|
||||
minThreads=1
|
||||
maxThreads=10
|
||||
cleanupInterval=1000
|
||||
readTimeout=60000
|
||||
maxRequestSize=16000
|
||||
maxMultiPartSize=1000000
|
||||
</pre></code>
|
||||
The port number is the incoming TCP port that this listener listens to.
|
||||
@see HttpConnectionHandlerPool for description of config settings minThreads, maxThreads and cleanupInterval
|
||||
@see HttpConnectionHandler for description of config settings readTimeout
|
||||
@see HttpRequest for description of config settings maxRequestSize and maxMultiPartSize
|
||||
*/
|
||||
|
||||
class HttpListener : public QTcpServer {
|
||||
Q_OBJECT
|
||||
Q_DISABLE_COPY(HttpListener)
|
||||
public:
|
||||
|
||||
/**
|
||||
Constructor.
|
||||
@param settings Configuration settings for the HTTP server. Must not be 0.
|
||||
@param requestHandler Processes each received HTTP request, usually by dispatching to controller classes.
|
||||
@param parent Parent object
|
||||
*/
|
||||
HttpListener(QSettings* settings, HttpRequestHandler* requestHandler, QObject* parent = 0);
|
||||
|
||||
/** Destructor */
|
||||
virtual ~HttpListener();
|
||||
|
||||
protected:
|
||||
|
||||
/** Serves new incoming connection requests */
|
||||
void incomingConnection(int socketDescriptor);
|
||||
|
||||
private:
|
||||
|
||||
/** Configuration settings for the HTTP server */
|
||||
QSettings* settings;
|
||||
|
||||
/** Pool of connection handlers */
|
||||
HttpConnectionHandlerPool pool;
|
||||
|
||||
signals:
|
||||
|
||||
/**
|
||||
Emitted when the connection handler shall process a new incoming onnection.
|
||||
@param socketDescriptor references the accepted connection.
|
||||
*/
|
||||
void handleConnection(int socketDescriptor);
|
||||
|
||||
};
|
||||
|
||||
#endif // LISTENER_H
|
435
YACReaderLibrary/server/lib/bfHttpServer/httprequest.cpp
Normal file
435
YACReaderLibrary/server/lib/bfHttpServer/httprequest.cpp
Normal file
@ -0,0 +1,435 @@
|
||||
/**
|
||||
@file
|
||||
@author Stefan Frings
|
||||
*/
|
||||
|
||||
#include "httprequest.h"
|
||||
#include <QList>
|
||||
#include <QDir>
|
||||
#include "httpcookie.h"
|
||||
|
||||
HttpRequest::HttpRequest(QSettings* settings) {
|
||||
status=waitForRequest;
|
||||
currentSize=0;
|
||||
expectedBodySize=0;
|
||||
maxSize=settings->value("maxRequestSize","16000").toInt();
|
||||
maxMultiPartSize=settings->value("maxMultiPartSize","1000000").toInt();
|
||||
|
||||
// Convert relative path to absolute, based on the directory of the config file.
|
||||
#ifdef Q_OS_WIN32
|
||||
if (QDir::isRelativePath(tempDir) && settings->format()!=QSettings::NativeFormat)
|
||||
#else
|
||||
if (QDir::isRelativePath(tempDir))
|
||||
#endif
|
||||
{
|
||||
QFileInfo configFile(settings->fileName());
|
||||
tempDir=QFileInfo(configFile.absolutePath(),tempDir).absoluteFilePath();
|
||||
}
|
||||
}
|
||||
|
||||
void HttpRequest::readRequest(QTcpSocket& socket) {
|
||||
#ifdef SUPERVERBOSE
|
||||
qDebug("HttpRequest: read request");
|
||||
#endif
|
||||
int toRead=maxSize-currentSize+1; // allow one byte more to be able to detect overflow
|
||||
QByteArray newData=socket.readLine(toRead).trimmed();
|
||||
currentSize+=newData.size();
|
||||
if (!newData.isEmpty()) {
|
||||
QList<QByteArray> list=newData.split(' ');
|
||||
if (list.count()!=3 || !list.at(2).contains("HTTP")) {
|
||||
qWarning("HttpRequest: received broken HTTP request, invalid first line");
|
||||
status=abort;
|
||||
}
|
||||
else {
|
||||
method=list.at(0);
|
||||
path=list.at(1);
|
||||
version=list.at(2);
|
||||
status=waitForHeader;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void HttpRequest::readHeader(QTcpSocket& socket) {
|
||||
#ifdef SUPERVERBOSE
|
||||
qDebug("HttpRequest: read header");
|
||||
#endif
|
||||
int toRead=maxSize-currentSize+1; // allow one byte more to be able to detect overflow
|
||||
QByteArray newData=socket.readLine(toRead).trimmed();
|
||||
currentSize+=newData.size();
|
||||
int colon=newData.indexOf(':');
|
||||
if (colon>0) {
|
||||
// Received a line with a colon - a header
|
||||
currentHeader=newData.left(colon);
|
||||
QByteArray value=newData.mid(colon+1).trimmed();
|
||||
headers.insert(currentHeader,value);
|
||||
#ifdef SUPERVERBOSE
|
||||
qDebug("HttpRequest: received header %s: %s",currentHeader.data(),value.data());
|
||||
#endif
|
||||
}
|
||||
else if (!newData.isEmpty()) {
|
||||
// received another line - belongs to the previous header
|
||||
#ifdef SUPERVERBOSE
|
||||
qDebug("HttpRequest: read additional line of header");
|
||||
#endif
|
||||
// Received additional line of previous header
|
||||
if (headers.contains(currentHeader)) {
|
||||
headers.insert(currentHeader,headers.value(currentHeader)+" "+newData);
|
||||
}
|
||||
}
|
||||
else {
|
||||
// received an empty line - end of headers reached
|
||||
#ifdef SUPERVERBOSE
|
||||
qDebug("HttpRequest: headers completed");
|
||||
#endif
|
||||
// Empty line received, that means all headers have been received
|
||||
// Check for multipart/form-data
|
||||
QByteArray contentType=headers.value("Content-Type");
|
||||
if (contentType.startsWith("multipart/form-data")) {
|
||||
int posi=contentType.indexOf("boundary=");
|
||||
if (posi>=0) {
|
||||
boundary=contentType.mid(posi+9);
|
||||
}
|
||||
}
|
||||
QByteArray contentLength=getHeader("Content-Length");
|
||||
if (!contentLength.isEmpty()) {
|
||||
expectedBodySize=contentLength.toInt();
|
||||
}
|
||||
if (expectedBodySize==0) {
|
||||
#ifdef SUPERVERBOSE
|
||||
qDebug("HttpRequest: expect no body");
|
||||
#endif
|
||||
status=complete;
|
||||
}
|
||||
else if (boundary.isEmpty() && expectedBodySize+currentSize>maxSize) {
|
||||
qWarning("HttpRequest: expected body is too large");
|
||||
status=abort;
|
||||
}
|
||||
else if (!boundary.isEmpty() && expectedBodySize>maxMultiPartSize) {
|
||||
qWarning("HttpRequest: expected multipart body is too large");
|
||||
status=abort;
|
||||
}
|
||||
else {
|
||||
#ifdef SUPERVERBOSE
|
||||
qDebug("HttpRequest: expect %i bytes body",expectedBodySize);
|
||||
#endif
|
||||
status=waitForBody;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void HttpRequest::readBody(QTcpSocket& socket) {
|
||||
Q_ASSERT(expectedBodySize!=0);
|
||||
if (boundary.isEmpty()) {
|
||||
// normal body, no multipart
|
||||
#ifdef SUPERVERBOSE
|
||||
qDebug("HttpRequest: receive body");
|
||||
#endif
|
||||
int toRead=expectedBodySize-bodyData.size();
|
||||
QByteArray newData=socket.read(toRead);
|
||||
currentSize+=newData.size();
|
||||
bodyData.append(newData);
|
||||
if (bodyData.size()>=expectedBodySize) {
|
||||
status=complete;
|
||||
}
|
||||
}
|
||||
else {
|
||||
// multipart body, store into temp file
|
||||
#ifdef SUPERVERBOSE
|
||||
qDebug("HttpRequest: receiving multipart body");
|
||||
#endif
|
||||
if (!tempFile.isOpen()) {
|
||||
tempFile.open();
|
||||
}
|
||||
// Transfer data in 64kb blocks
|
||||
int fileSize=tempFile.size();
|
||||
int toRead=expectedBodySize-fileSize;
|
||||
if (toRead>65536) {
|
||||
toRead=65536;
|
||||
}
|
||||
fileSize+=tempFile.write(socket.read(toRead));
|
||||
if (fileSize>=maxMultiPartSize) {
|
||||
qWarning("HttpRequest: received too many multipart bytes");
|
||||
status=abort;
|
||||
}
|
||||
else if (fileSize>=expectedBodySize) {
|
||||
#ifdef SUPERVERBOSE
|
||||
qDebug("HttpRequest: received whole multipart body");
|
||||
#endif
|
||||
tempFile.flush();
|
||||
if (tempFile.error()) {
|
||||
qCritical("HttpRequest: Error writing temp file for multipart body");
|
||||
}
|
||||
parseMultiPartFile();
|
||||
tempFile.close();
|
||||
status=complete;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void HttpRequest::decodeRequestParams() {
|
||||
#ifdef SUPERVERBOSE
|
||||
qDebug("HttpRequest: extract and decode request parameters");
|
||||
#endif
|
||||
QByteArray rawParameters;
|
||||
if (headers.value("Content-Type")=="application/x-www-form-urlencoded") {
|
||||
rawParameters=bodyData;
|
||||
}
|
||||
else {
|
||||
int questionMark=path.indexOf('?');
|
||||
if (questionMark>=0) {
|
||||
rawParameters=path.mid(questionMark+1);
|
||||
path=path.left(questionMark);
|
||||
}
|
||||
}
|
||||
// Split the parameters into pairs of value and name
|
||||
QList<QByteArray> list=rawParameters.split('&');
|
||||
foreach (QByteArray part, list) {
|
||||
int equalsChar=part.indexOf('=');
|
||||
if (equalsChar>=0) {
|
||||
QByteArray name=part.left(equalsChar).trimmed();
|
||||
QByteArray value=part.mid(equalsChar+1).trimmed();
|
||||
parameters.insert(urlDecode(name),urlDecode(value));
|
||||
}
|
||||
else if (!part.isEmpty()){
|
||||
// Name without value
|
||||
parameters.insert(urlDecode(part),"");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void HttpRequest::extractCookies() {
|
||||
#ifdef SUPERVERBOSE
|
||||
qDebug("HttpRequest: extract cookies");
|
||||
#endif
|
||||
foreach(QByteArray cookieStr, headers.values("Cookie")) {
|
||||
QList<QByteArray> list=HttpCookie::splitCSV(cookieStr);
|
||||
foreach(QByteArray part, list) {
|
||||
#ifdef SUPERVERBOSE
|
||||
qDebug("HttpRequest: found cookie %s",part.data());
|
||||
#endif // Split the part into name and value
|
||||
QByteArray name;
|
||||
QByteArray value;
|
||||
int posi=part.indexOf('=');
|
||||
if (posi) {
|
||||
name=part.left(posi).trimmed();
|
||||
value=part.mid(posi+1).trimmed();
|
||||
}
|
||||
else {
|
||||
name=part.trimmed();
|
||||
value="";
|
||||
}
|
||||
cookies.insert(name,value);
|
||||
}
|
||||
}
|
||||
headers.remove("Cookie");
|
||||
}
|
||||
|
||||
void HttpRequest::readFromSocket(QTcpSocket& socket) {
|
||||
Q_ASSERT(status!=complete);
|
||||
if (status==waitForRequest) {
|
||||
readRequest(socket);
|
||||
}
|
||||
else if (status==waitForHeader) {
|
||||
readHeader(socket);
|
||||
}
|
||||
else if (status==waitForBody) {
|
||||
readBody(socket);
|
||||
}
|
||||
if (currentSize>maxSize) {
|
||||
qWarning("HttpRequest: received too many bytes");
|
||||
status=abort;
|
||||
}
|
||||
if (status==complete) {
|
||||
// Extract and decode request parameters from url and body
|
||||
decodeRequestParams();
|
||||
// Extract cookies from headers
|
||||
extractCookies();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
HttpRequest::RequestStatus HttpRequest::getStatus() const {
|
||||
return status;
|
||||
}
|
||||
|
||||
|
||||
QByteArray HttpRequest::getMethod() const {
|
||||
return method;
|
||||
}
|
||||
|
||||
|
||||
QByteArray HttpRequest::getPath() const {
|
||||
return urlDecode(path);
|
||||
}
|
||||
|
||||
|
||||
QByteArray HttpRequest::getVersion() const {
|
||||
return version;
|
||||
}
|
||||
|
||||
|
||||
QByteArray HttpRequest::getHeader(const QByteArray& name) const {
|
||||
return headers.value(name);
|
||||
}
|
||||
|
||||
QList<QByteArray> HttpRequest::getHeaders(const QByteArray& name) const {
|
||||
return headers.values(name);
|
||||
}
|
||||
|
||||
QMultiMap<QByteArray,QByteArray> HttpRequest::getHeaderMap() const {
|
||||
return headers;
|
||||
}
|
||||
|
||||
QByteArray HttpRequest::getParameter(const QByteArray& name) const {
|
||||
return parameters.value(name);
|
||||
}
|
||||
|
||||
QList<QByteArray> HttpRequest::getParameters(const QByteArray& name) const {
|
||||
return parameters.values(name);
|
||||
}
|
||||
|
||||
QMultiMap<QByteArray,QByteArray> HttpRequest::getParameterMap() const {
|
||||
return parameters;
|
||||
}
|
||||
|
||||
QByteArray HttpRequest::getBody() const {
|
||||
return bodyData;
|
||||
}
|
||||
|
||||
QByteArray HttpRequest::urlDecode(const QByteArray source) {
|
||||
QByteArray buffer(source);
|
||||
buffer.replace('+',' ');
|
||||
int percentChar=buffer.indexOf('%');
|
||||
while (percentChar>=0) {
|
||||
bool ok;
|
||||
char byte=buffer.mid(percentChar+1,2).toInt(&ok,16);
|
||||
if (ok) {
|
||||
buffer.replace(percentChar,3,(char*)&byte,1);
|
||||
}
|
||||
percentChar=buffer.indexOf('%',percentChar+1);
|
||||
}
|
||||
return buffer;
|
||||
}
|
||||
|
||||
|
||||
void HttpRequest::parseMultiPartFile() {
|
||||
qDebug("HttpRequest: parsing multipart temp file");
|
||||
tempFile.seek(0);
|
||||
bool finished=false;
|
||||
while (!tempFile.atEnd() && !finished && !tempFile.error()) {
|
||||
|
||||
#ifdef SUPERVERBOSE
|
||||
qDebug("HttpRequest: reading multpart headers");
|
||||
#endif
|
||||
QByteArray fieldName;
|
||||
QByteArray fileName;
|
||||
while (!tempFile.atEnd() && !finished && !tempFile.error()) {
|
||||
QByteArray line=tempFile.readLine(65536).trimmed();
|
||||
if (line.startsWith("Content-Disposition:")) {
|
||||
if (line.contains("form-data")) {
|
||||
int start=line.indexOf(" name=\"");
|
||||
int end=line.indexOf("\"",start+7);
|
||||
if (start>=0 && end>=start) {
|
||||
fieldName=line.mid(start+7,end-start-7);
|
||||
}
|
||||
start=line.indexOf(" filename=\"");
|
||||
end=line.indexOf("\"",start+11);
|
||||
if (start>=0 && end>=start) {
|
||||
fileName=line.mid(start+11,end-start-11);
|
||||
}
|
||||
#ifdef SUPERVERBOSE
|
||||
qDebug("HttpRequest: multipart field=%s, filename=%s",fieldName.data(),fileName.data());
|
||||
#endif
|
||||
}
|
||||
else {
|
||||
qDebug("HttpRequest: ignoring unsupported content part %s",line.data());
|
||||
}
|
||||
}
|
||||
else if (line.isEmpty()) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
#ifdef SUPERVERBOSE
|
||||
qDebug("HttpRequest: reading multpart data");
|
||||
#endif
|
||||
QTemporaryFile* uploadedFile=0;
|
||||
QByteArray fieldValue;
|
||||
while (!tempFile.atEnd() && !finished && !tempFile.error()) {
|
||||
QByteArray line=tempFile.readLine(65536);
|
||||
if (line.startsWith("--"+boundary)) {
|
||||
// Boundary found. Until now we have collected 2 bytes too much,
|
||||
// so remove them from the last result
|
||||
if (fileName.isEmpty() && !fieldName.isEmpty()) {
|
||||
// last field was a form field
|
||||
fieldValue.remove(fieldValue.size()-2,2);
|
||||
parameters.insert(fieldName,fieldValue);
|
||||
qDebug("HttpRequest: set parameter %s=%s",fieldName.data(),fieldValue.data());
|
||||
}
|
||||
else if (!fileName.isEmpty() && !fieldName.isEmpty()) {
|
||||
// last field was a file
|
||||
#ifdef SUPERVERBOSE
|
||||
qDebug("HttpRequest: finishing writing to uploaded file");
|
||||
#endif
|
||||
uploadedFile->resize(uploadedFile->size()-2);
|
||||
uploadedFile->flush();
|
||||
uploadedFile->seek(0);
|
||||
parameters.insert(fieldName,fileName);
|
||||
qDebug("HttpRequest: set parameter %s=%s",fieldName.data(),fileName.data());
|
||||
uploadedFiles.insert(fieldName,uploadedFile);
|
||||
qDebug("HttpRequest: uploaded file size is %i",(int) uploadedFile->size());
|
||||
}
|
||||
if (line.contains(boundary+"--")) {
|
||||
finished=true;
|
||||
}
|
||||
break;
|
||||
}
|
||||
else {
|
||||
if (fileName.isEmpty() && !fieldName.isEmpty()) {
|
||||
// this is a form field.
|
||||
currentSize+=line.size();
|
||||
fieldValue.append(line);
|
||||
}
|
||||
else if (!fileName.isEmpty() && !fieldName.isEmpty()) {
|
||||
// this is a file
|
||||
if (!uploadedFile) {
|
||||
uploadedFile=new QTemporaryFile();
|
||||
uploadedFile->open();
|
||||
}
|
||||
uploadedFile->write(line);
|
||||
if (uploadedFile->error()) {
|
||||
qCritical("HttpRequest: error writing temp file, %s",qPrintable(uploadedFile->errorString()));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (tempFile.error()) {
|
||||
qCritical("HttpRequest: cannot read temp file, %s",qPrintable(tempFile.errorString()));
|
||||
}
|
||||
#ifdef SUPERVERBOSE
|
||||
qDebug("HttpRequest: finished parsing multipart temp file");
|
||||
#endif
|
||||
}
|
||||
|
||||
HttpRequest::~HttpRequest() {
|
||||
foreach(QByteArray key, uploadedFiles.keys()) {
|
||||
QTemporaryFile* file=uploadedFiles.value(key);
|
||||
file->close();
|
||||
delete file;
|
||||
}
|
||||
}
|
||||
|
||||
QTemporaryFile* HttpRequest::getUploadedFile(const QByteArray fieldName) {
|
||||
return uploadedFiles.value(fieldName);
|
||||
}
|
||||
|
||||
QByteArray HttpRequest::getCookie(const QByteArray& name) const {
|
||||
return cookies.value(name);
|
||||
}
|
||||
|
||||
/** Get the map of cookies */
|
||||
QMap<QByteArray,QByteArray>& HttpRequest::getCookieMap() {
|
||||
return cookies;
|
||||
}
|
||||
|
214
YACReaderLibrary/server/lib/bfHttpServer/httprequest.h
Normal file
214
YACReaderLibrary/server/lib/bfHttpServer/httprequest.h
Normal file
@ -0,0 +1,214 @@
|
||||
/**
|
||||
@file
|
||||
@author Stefan Frings
|
||||
*/
|
||||
|
||||
#ifndef HTTPREQUEST_H
|
||||
#define HTTPREQUEST_H
|
||||
|
||||
#include <QByteArray>
|
||||
#include <QTcpSocket>
|
||||
#include <QMap>
|
||||
#include <QMultiMap>
|
||||
#include <QSettings>
|
||||
#include <QTemporaryFile>
|
||||
#include <QUuid>
|
||||
|
||||
/**
|
||||
This object represents a single HTTP request. It reads the request
|
||||
from a TCP socket and provides getters for the individual parts
|
||||
of the request.
|
||||
<p>
|
||||
The follwing config settings are required:
|
||||
<code><pre>
|
||||
maxRequestSize=16000
|
||||
maxMultiPartSize=1000000
|
||||
</pre></code>
|
||||
<p>
|
||||
MaxRequestSize is the maximum size of a HTTP request. In case of
|
||||
multipart/form-data requests (also known as file-upload), the maximum
|
||||
size of the body must not exceed maxMultiPartSize.
|
||||
The body is always a little larger than the file itself.
|
||||
*/
|
||||
|
||||
class HttpRequest {
|
||||
Q_DISABLE_COPY(HttpRequest)
|
||||
friend class HttpSessionStore;
|
||||
public:
|
||||
|
||||
/** Values for getStatus() */
|
||||
enum RequestStatus {waitForRequest, waitForHeader, waitForBody, complete, abort};
|
||||
|
||||
/**
|
||||
Constructor.
|
||||
@param settings Configuration settings
|
||||
*/
|
||||
HttpRequest(QSettings* settings);
|
||||
|
||||
/**
|
||||
Destructor.
|
||||
*/
|
||||
virtual ~HttpRequest();
|
||||
|
||||
/**
|
||||
Read the request from a socket. This method must be called repeatedly
|
||||
until the status is RequestStatus::complete or RequestStatus::abort.
|
||||
@param socket Source of the data
|
||||
*/
|
||||
void readFromSocket(QTcpSocket& socket);
|
||||
|
||||
/**
|
||||
Get the status of this reqeust.
|
||||
@see RequestStatus
|
||||
*/
|
||||
RequestStatus getStatus() const;
|
||||
|
||||
/** Get the method of the HTTP request (e.g. "GET") */
|
||||
QByteArray getMethod() const;
|
||||
|
||||
/** Get the decoded path of the HTPP request (e.g. "/index.html") */
|
||||
QByteArray getPath() const;
|
||||
|
||||
/** Get the version of the HTPP request (e.g. "HTTP/1.1") */
|
||||
QByteArray getVersion() const;
|
||||
|
||||
/**
|
||||
Get the value of a HTTP request header.
|
||||
@param name Name of the header
|
||||
@return If the header occurs multiple times, only the last
|
||||
one is returned.
|
||||
*/
|
||||
QByteArray getHeader(const QByteArray& name) const;
|
||||
|
||||
/**
|
||||
Get the values of a HTTP request header.
|
||||
@param name Name of the header
|
||||
*/
|
||||
QList<QByteArray> getHeaders(const QByteArray& name) const;
|
||||
|
||||
/** Get all HTTP request headers */
|
||||
QMultiMap<QByteArray,QByteArray> getHeaderMap() const;
|
||||
|
||||
/**
|
||||
Get the value of a HTTP request parameter.
|
||||
@param name Name of the parameter
|
||||
@return If the parameter occurs multiple times, only the last
|
||||
one is returned.
|
||||
*/
|
||||
QByteArray getParameter(const QByteArray& name) const;
|
||||
|
||||
/**
|
||||
Get the values of a HTTP request parameter.
|
||||
@param name Name of the parameter
|
||||
*/
|
||||
QList<QByteArray> getParameters(const QByteArray& name) const;
|
||||
|
||||
/** Get all HTTP request parameters */
|
||||
QMultiMap<QByteArray,QByteArray> getParameterMap() const;
|
||||
|
||||
/** Get the HTTP request body */
|
||||
QByteArray getBody() const;
|
||||
|
||||
/**
|
||||
Decode an URL parameter.
|
||||
E.g. replace "%23" by '#' and replace '+' by ' '
|
||||
@param source The url encoded string
|
||||
*/
|
||||
static QByteArray urlDecode(const QByteArray source);
|
||||
|
||||
/**
|
||||
Get an uploaded file. The file is already open. It will
|
||||
be closed and deleted by the destructor of this HttpRequest
|
||||
object (after processing the request).
|
||||
<p>
|
||||
For uploaded files, the method getParameters() returns
|
||||
the original fileName as provided by the calling web browser.
|
||||
*/
|
||||
QTemporaryFile* getUploadedFile(const QByteArray fieldName);
|
||||
|
||||
/**
|
||||
Get the value of a cookie
|
||||
@param name Name of the cookie
|
||||
*/
|
||||
QByteArray getCookie(const QByteArray& name) const;
|
||||
|
||||
/** Get the map of cookies */
|
||||
QMap<QByteArray,QByteArray>& getCookieMap();
|
||||
|
||||
private:
|
||||
|
||||
/** Request headers */
|
||||
QMultiMap<QByteArray,QByteArray> headers;
|
||||
|
||||
/** Parameters of the request */
|
||||
QMultiMap<QByteArray,QByteArray> parameters;
|
||||
|
||||
/** Uploaded files of the request, key is the field name. */
|
||||
QMap<QByteArray,QTemporaryFile*> uploadedFiles;
|
||||
|
||||
/** Received cookies */
|
||||
QMap<QByteArray,QByteArray> cookies;
|
||||
|
||||
/** Storage for raw body data */
|
||||
QByteArray bodyData;
|
||||
|
||||
/** Request method */
|
||||
QByteArray method;
|
||||
|
||||
/** Request path (in raw encoded format) */
|
||||
QByteArray path;
|
||||
|
||||
/** Request protocol version */
|
||||
QByteArray version;
|
||||
|
||||
/**
|
||||
Status of this request.
|
||||
@see RequestStatus
|
||||
*/
|
||||
RequestStatus status;
|
||||
|
||||
/** Maximum size of requests in bytes. */
|
||||
int maxSize;
|
||||
|
||||
/** Maximum allowed size of multipart forms in bytes. */
|
||||
int maxMultiPartSize;
|
||||
|
||||
/** Directory for temp files */
|
||||
QString tempDir;
|
||||
|
||||
/** Current size */
|
||||
int currentSize;
|
||||
|
||||
/** Expected size of body */
|
||||
int expectedBodySize;
|
||||
|
||||
/** Name of the current header, or empty if no header is being processed */
|
||||
QByteArray currentHeader;
|
||||
|
||||
/** Boundary of multipart/form-data body. Empty if there is no such header */
|
||||
QByteArray boundary;
|
||||
|
||||
/** Temp file, that is used to store the multipart/form-data body */
|
||||
QTemporaryFile tempFile;
|
||||
|
||||
/** Parset he multipart body, that has been stored in the temp file. */
|
||||
void parseMultiPartFile();
|
||||
|
||||
/** Sub-procedure of readFromSocket(), read the first line of a request. */
|
||||
void readRequest(QTcpSocket& socket);
|
||||
|
||||
/** Sub-procedure of readFromSocket(), read header lines. */
|
||||
void readHeader(QTcpSocket& socket);
|
||||
|
||||
/** Sub-procedure of readFromSocket(), read the request body. */
|
||||
void readBody(QTcpSocket& socket);
|
||||
|
||||
/** Sub-procedure of readFromSocket(), extract and decode request parameters. */
|
||||
void decodeRequestParams();
|
||||
|
||||
/** Sub-procedure of readFromSocket(), extract cookies from headers */
|
||||
void extractCookies();
|
||||
|
||||
};
|
||||
|
||||
#endif // HTTPREQUEST_H
|
@ -0,0 +1,19 @@
|
||||
/**
|
||||
@file
|
||||
@author Stefan Frings
|
||||
*/
|
||||
|
||||
#include "httprequesthandler.h"
|
||||
|
||||
HttpRequestHandler::HttpRequestHandler(QObject* parent)
|
||||
: QObject(parent)
|
||||
{}
|
||||
|
||||
HttpRequestHandler::~HttpRequestHandler() {}
|
||||
|
||||
void HttpRequestHandler::service(HttpRequest& request, HttpResponse& response) {
|
||||
qCritical("HttpRequestHandler: you need to override the dispatch() function");
|
||||
qDebug("HttpRequestHandler: request=%s %s %s",request.getMethod().data(),request.getPath().data(),request.getVersion().data());
|
||||
response.setStatus(501,"not implemented");
|
||||
response.write("501 not implemented",true);
|
||||
}
|
@ -0,0 +1,45 @@
|
||||
/**
|
||||
@file
|
||||
@author Stefan Frings
|
||||
*/
|
||||
|
||||
#ifndef HTTPREQUESTHANDLER_H
|
||||
#define HTTPREQUESTHANDLER_H
|
||||
|
||||
#include "httprequest.h"
|
||||
#include "httpresponse.h"
|
||||
|
||||
/**
|
||||
The request handler generates a response for each HTTP request. Web Applications
|
||||
usually have one central request handler that maps incoming requests to several
|
||||
controllers (servlets) based on the requested path.
|
||||
<p>
|
||||
You need to override the service() method or you will always get an HTTP error 501.
|
||||
<p>
|
||||
@warning Be aware that the main request handler instance must be created on the heap and
|
||||
that it is used by multiple threads simultaneously.
|
||||
@see StaticFileController which delivers static local files.
|
||||
*/
|
||||
|
||||
class HttpRequestHandler : public QObject {
|
||||
Q_OBJECT
|
||||
Q_DISABLE_COPY(HttpRequestHandler)
|
||||
public:
|
||||
|
||||
/** Constructor */
|
||||
HttpRequestHandler(QObject* parent=0);
|
||||
|
||||
/** Destructor */
|
||||
virtual ~HttpRequestHandler();
|
||||
|
||||
/**
|
||||
Generate a response for an incoming HTTP request.
|
||||
@param request The received HTTP request
|
||||
@param response Must be used to return the response
|
||||
@warning This method must be thread safe
|
||||
*/
|
||||
virtual void service(HttpRequest& request, HttpResponse& response);
|
||||
|
||||
};
|
||||
|
||||
#endif // HTTPREQUESTHANDLER_H
|
122
YACReaderLibrary/server/lib/bfHttpServer/httpresponse.cpp
Normal file
122
YACReaderLibrary/server/lib/bfHttpServer/httpresponse.cpp
Normal file
@ -0,0 +1,122 @@
|
||||
/**
|
||||
@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;
|
||||
}
|
||||
|
||||
void HttpResponse::writeToSocket(QByteArray data) {
|
||||
int remaining=data.size();
|
||||
char* ptr=data.data();
|
||||
while (socket->isOpen() && remaining>0) {
|
||||
int written=socket->write(data);
|
||||
ptr+=written;
|
||||
remaining-=written;
|
||||
}
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
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;
|
||||
}
|
134
YACReaderLibrary/server/lib/bfHttpServer/httpresponse.h
Normal file
134
YACReaderLibrary/server/lib/bfHttpServer/httpresponse.h
Normal file
@ -0,0 +1,134 @@
|
||||
/**
|
||||
@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);
|
||||
|
||||
/**
|
||||
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 */
|
||||
void 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
|
158
YACReaderLibrary/server/lib/bfHttpServer/httpsession.cpp
Normal file
158
YACReaderLibrary/server/lib/bfHttpServer/httpsession.cpp
Normal file
@ -0,0 +1,158 @@
|
||||
/**
|
||||
@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().toAscii();
|
||||
#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/bfHttpServer/httpsession.h
Normal file
118
YACReaderLibrary/server/lib/bfHttpServer/httpsession.h
Normal file
@ -0,0 +1,118 @@
|
||||
/**
|
||||
@file
|
||||
@author Stefan Frings
|
||||
*/
|
||||
|
||||
#ifndef HTTPSESSION_H
|
||||
#define HTTPSESSION_H
|
||||
|
||||
#include <QByteArray>
|
||||
#include <QVariant>
|
||||
#include <QReadWriteLock>
|
||||
|
||||
|
||||
/**
|
||||
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();
|
||||
|
||||
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
|
107
YACReaderLibrary/server/lib/bfHttpServer/httpsessionstore.cpp
Normal file
107
YACReaderLibrary/server/lib/bfHttpServer/httpsessionstore.cpp
Normal file
@ -0,0 +1,107 @@
|
||||
/**
|
||||
@file
|
||||
@author Stefan Frings
|
||||
*/
|
||||
|
||||
#include "httpsessionstore.h"
|
||||
#include <QDateTime>
|
||||
#include <QUuid>
|
||||
|
||||
HttpSessionStore::HttpSessionStore(QSettings* settings, QObject* parent)
|
||||
:QObject(parent)
|
||||
{
|
||||
this->settings=settings;
|
||||
connect(&cleanupTimer,SIGNAL(timeout()),this,SLOT(timerEvent()));
|
||||
cleanupTimer.start(60000);
|
||||
cookieName=settings->value("cookieName","sessionid").toByteArray();
|
||||
expirationTime=settings->value("expirationTime",3600000).toInt();
|
||||
qDebug("HttpSessionStore: Sessions expire after %i milliseconds",expirationTime);
|
||||
}
|
||||
|
||||
HttpSessionStore::~HttpSessionStore()
|
||||
{
|
||||
cleanupTimer.stop();
|
||||
}
|
||||
|
||||
QByteArray HttpSessionStore::getSessionId(HttpRequest& request, HttpResponse& response) {
|
||||
// The session ID in the response has priority because this one will be used in the next request.
|
||||
mutex.lock();
|
||||
// Get the session ID from the response cookie
|
||||
QByteArray sessionId=response.getCookies().value(cookieName).getValue();
|
||||
if (sessionId.isEmpty()) {
|
||||
// Get the session ID from the request cookie
|
||||
sessionId=request.getCookie(cookieName);
|
||||
}
|
||||
// Clear the session ID if there is no such session in the storage.
|
||||
if (!sessionId.isEmpty()) {
|
||||
if (!sessions.contains(sessionId)) {
|
||||
qDebug("HttpSessionStore: received invalid session cookie with ID %s",sessionId.data());
|
||||
sessionId.clear();
|
||||
}
|
||||
}
|
||||
mutex.unlock();
|
||||
return sessionId;
|
||||
}
|
||||
|
||||
HttpSession HttpSessionStore::getSession(HttpRequest& request, HttpResponse& response, bool allowCreate) {
|
||||
QByteArray sessionId=getSessionId(request,response);
|
||||
mutex.lock();
|
||||
if (!sessionId.isEmpty()) {
|
||||
HttpSession session=sessions.value(sessionId);
|
||||
if (!session.isNull()) {
|
||||
mutex.unlock();
|
||||
session.setLastAccess();
|
||||
return session;
|
||||
}
|
||||
}
|
||||
// Need to create a new session
|
||||
if (allowCreate) {
|
||||
QByteArray cookieName=settings->value("cookieName","sessionid").toByteArray();
|
||||
QByteArray cookiePath=settings->value("cookiePath").toByteArray();
|
||||
QByteArray cookieComment=settings->value("cookieComment").toByteArray();
|
||||
QByteArray cookieDomain=settings->value("cookieDomain").toByteArray();
|
||||
HttpSession session(true);
|
||||
qDebug("HttpSessionStore: create new session with ID %s",session.getId().data());
|
||||
sessions.insert(session.getId(),session);
|
||||
response.setCookie(HttpCookie(cookieName,session.getId(),expirationTime/1000,cookiePath,cookieComment,cookieDomain));
|
||||
mutex.unlock();
|
||||
return session;
|
||||
}
|
||||
// Return a null session
|
||||
mutex.unlock();
|
||||
return HttpSession();
|
||||
}
|
||||
|
||||
HttpSession HttpSessionStore::getSession(const QByteArray id) {
|
||||
mutex.lock();
|
||||
HttpSession session=sessions.value(id);
|
||||
mutex.unlock();
|
||||
session.setLastAccess();
|
||||
return session;
|
||||
}
|
||||
|
||||
void HttpSessionStore::timerEvent() {
|
||||
// Todo: find a way to delete sessions only if no controller is accessing them
|
||||
mutex.lock();
|
||||
qint64 now=QDateTime::currentMSecsSinceEpoch();
|
||||
QMap<QByteArray,HttpSession>::iterator i = sessions.begin();
|
||||
while (i != sessions.end()) {
|
||||
QMap<QByteArray,HttpSession>::iterator prev = i;
|
||||
++i;
|
||||
HttpSession session=prev.value();
|
||||
qint64 lastAccess=session.getLastAccess();
|
||||
if (now-lastAccess>expirationTime) {
|
||||
qDebug("HttpSessionStore: session %s expired",session.getId().data());
|
||||
sessions.erase(prev);
|
||||
}
|
||||
}
|
||||
mutex.unlock();
|
||||
}
|
||||
|
||||
|
||||
/** Delete a session */
|
||||
void HttpSessionStore::removeSession(HttpSession session) {
|
||||
mutex.lock();
|
||||
sessions.remove(session.getId());
|
||||
mutex.unlock();
|
||||
}
|
104
YACReaderLibrary/server/lib/bfHttpServer/httpsessionstore.h
Normal file
104
YACReaderLibrary/server/lib/bfHttpServer/httpsessionstore.h
Normal file
@ -0,0 +1,104 @@
|
||||
/**
|
||||
@file
|
||||
@author Stefan Frings
|
||||
*/
|
||||
|
||||
#ifndef HTTPSESSIONSTORE_H
|
||||
#define HTTPSESSIONSTORE_H
|
||||
|
||||
#include <QObject>
|
||||
#include <QMap>
|
||||
#include <QTimer>
|
||||
#include <QMutex>
|
||||
#include "httpsession.h"
|
||||
#include "httpresponse.h"
|
||||
#include "httprequest.h"
|
||||
|
||||
/**
|
||||
Stores HTTP sessions and deletes them when they have expired.
|
||||
The following configuration settings are required in the config file:
|
||||
<code><pre>
|
||||
expirationTime=3600000
|
||||
cookieName=sessionid
|
||||
</pre></code>
|
||||
The following additional configurations settings are optionally:
|
||||
<code><pre>
|
||||
cookiePath=/
|
||||
cookieComment=Session ID
|
||||
cookieDomain=stefanfrings.de
|
||||
</pre></code>
|
||||
*/
|
||||
|
||||
class HttpSessionStore : public QObject {
|
||||
Q_OBJECT
|
||||
Q_DISABLE_COPY(HttpSessionStore);
|
||||
public:
|
||||
|
||||
/** Constructor. */
|
||||
HttpSessionStore(QSettings* settings, QObject* parent);
|
||||
|
||||
/** Destructor */
|
||||
virtual ~HttpSessionStore();
|
||||
|
||||
/**
|
||||
Get the ID of the current HTTP session, if it is valid.
|
||||
This method is thread safe.
|
||||
@warning Sessions may expire at any time, so subsequent calls of
|
||||
getSession() might return a new session with a different ID.
|
||||
@param request Used to get the session cookie
|
||||
@param response Used to get and set the new session cookie
|
||||
@return Empty string, if there is no valid session.
|
||||
*/
|
||||
QByteArray getSessionId(HttpRequest& request, HttpResponse& response);
|
||||
|
||||
/**
|
||||
Get the session of a HTTP request, eventually create a new one.
|
||||
This method is thread safe. New sessions can only be created before
|
||||
the first byte has been written to the HTTP response.
|
||||
@param request Used to get the session cookie
|
||||
@param response Used to get and set the new session cookie
|
||||
@param allowCreate can be set to false, to disable the automatic creation of a new session.
|
||||
@return If autoCreate is disabled, the function returns a null session if there is no session.
|
||||
@see HttpSession::isNull()
|
||||
*/
|
||||
HttpSession getSession(HttpRequest& request, HttpResponse& response, bool allowCreate=true);
|
||||
|
||||
/**
|
||||
Get a HTTP session by it's ID number.
|
||||
This method is thread safe.
|
||||
@return If there is no such session, the function returns a null session.
|
||||
@param id ID number of the session
|
||||
@see HttpSession::isNull()
|
||||
*/
|
||||
HttpSession getSession(const QByteArray id);
|
||||
|
||||
/** Delete a session */
|
||||
void removeSession(HttpSession session);
|
||||
|
||||
private:
|
||||
|
||||
/** Configuration settings */
|
||||
QSettings* settings;
|
||||
|
||||
/** Storage for the sessions */
|
||||
QMap<QByteArray,HttpSession> sessions;
|
||||
|
||||
/** Timer to remove expired sessions */
|
||||
QTimer cleanupTimer;
|
||||
|
||||
/** Name of the session cookie */
|
||||
QByteArray cookieName;
|
||||
|
||||
/** Time when sessions expire (in ms)*/
|
||||
int expirationTime;
|
||||
|
||||
/** Used to synchronize threads */
|
||||
QMutex mutex;
|
||||
|
||||
private slots:
|
||||
|
||||
/** Called every minute to cleanup expired sessions. */
|
||||
void timerEvent();
|
||||
};
|
||||
|
||||
#endif // HTTPSESSIONSTORE_H
|
@ -0,0 +1,165 @@
|
||||
/**
|
||||
@file
|
||||
@author Stefan Frings
|
||||
*/
|
||||
|
||||
#include "staticfilecontroller.h"
|
||||
#include <QFileInfo>
|
||||
#include <QDir>
|
||||
#include <QDateTime>
|
||||
|
||||
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();
|
||||
// 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();
|
||||
// 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);
|
||||
}
|
||||
// Check if we have the file in cache
|
||||
qint64 now=QDateTime::currentMSecsSinceEpoch();
|
||||
CacheEntry* entry=cache.object(path);
|
||||
if (entry && (cacheTimeout==0 || entry->created>now-cacheTimeout)) {
|
||||
qDebug("StaticFileController: Cache hit for %s",path.data());
|
||||
setContentType(path,response);
|
||||
response.setHeader("Cache-Control","max-age="+QByteArray::number(maxAge/1000));
|
||||
response.write(entry->document);
|
||||
}
|
||||
else {
|
||||
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";
|
||||
}
|
||||
|
||||
//TODO(DONE) carga sensible a la localizaci<63>n
|
||||
QString stringPath = path;
|
||||
QStringList paths = QString(path).split('/');
|
||||
QString fileName = paths.last();
|
||||
stringPath.remove(fileName);
|
||||
fileName = getLocalizedFileName(fileName, request.getHeader("Accept-Language"), stringPath);
|
||||
QString newPath = stringPath.append(fileName);
|
||||
//END_TODO
|
||||
QFile file(docroot+newPath);
|
||||
if (file.exists()) {
|
||||
qDebug("StaticFileController: Open file %s",qPrintable(file.fileName()));
|
||||
if (file.open(QIODevice::ReadOnly)) {
|
||||
setContentType(newPath,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;
|
||||
cache.insert(request.getPath(),entry,entry->document.size());
|
||||
}
|
||||
else {
|
||||
// Return the file content, do not store in cache
|
||||
while (!file.atEnd() && !file.error()) {
|
||||
response.write(file.read(65536));
|
||||
}
|
||||
}
|
||||
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(".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=charset="+encoding));
|
||||
}
|
||||
// Todo: add all of your content types
|
||||
}
|
||||
|
||||
bool StaticFileController::exists(QString localizedName, QString path) const
|
||||
{
|
||||
QString fileName=docroot+"/"+path + localizedName;
|
||||
QFile file(fileName);
|
||||
return file.exists();
|
||||
}
|
||||
|
||||
//retorna fileName si no se encontr<74> alternativa traducida <20> fileName-locale.extensi<73>n si se encontr<74>
|
||||
QString StaticFileController::getLocalizedFileName(QString fileName, QString locales, QString path) const
|
||||
{
|
||||
QSet<QString> tried; // used to suppress duplicate attempts
|
||||
QStringList locs=locales.split(',',QString::SkipEmptyParts);
|
||||
QStringList fileNameParts = fileName.split('.');
|
||||
QString file = fileNameParts.first();
|
||||
QString extension = fileNameParts.last();
|
||||
// Search for exact match
|
||||
foreach (QString loc,locs) {
|
||||
loc.replace(QRegExp(";.*"),"");
|
||||
loc.replace('-','_');
|
||||
QString localizedName=file+"-"+loc.trimmed()+"."+extension;
|
||||
if (!tried.contains(localizedName)) {
|
||||
if(exists(localizedName, path))
|
||||
return localizedName;
|
||||
tried.insert(localizedName);
|
||||
}
|
||||
}
|
||||
|
||||
// Search for correct language but any country
|
||||
foreach (QString loc,locs) {
|
||||
loc.replace(QRegExp("[;_-].*"),"");
|
||||
QString localizedName=file+"-"+loc.trimmed()+"."+extension;
|
||||
if (!tried.contains(localizedName)) {
|
||||
if(exists(localizedName, path))
|
||||
return localizedName;
|
||||
tried.insert(localizedName);
|
||||
}
|
||||
}
|
||||
|
||||
return fileName;
|
||||
}
|
||||
|
@ -0,0 +1,85 @@
|
||||
/**
|
||||
@file
|
||||
@author Stefan Frings
|
||||
*/
|
||||
|
||||
#ifndef STATICFILECONTROLLER_H
|
||||
#define STATICFILECONTROLLER_H
|
||||
|
||||
#include "httprequest.h"
|
||||
#include "httpresponse.h"
|
||||
#include "httprequesthandler.h"
|
||||
#include <QCache>
|
||||
|
||||
/**
|
||||
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;
|
||||
|
||||
/** 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;
|
||||
|
||||
bool exists(QString localizedName, QString path) const;
|
||||
};
|
||||
|
||||
#endif // STATICFILECONTROLLER_H
|
7
YACReaderLibrary/server/lib/bfLogging/bfLogging.pri
Normal file
7
YACReaderLibrary/server/lib/bfLogging/bfLogging.pri
Normal file
@ -0,0 +1,7 @@
|
||||
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
|
||||
|
||||
OTHER_FILES += $$PWD/../doc/readme.txt
|
20
YACReaderLibrary/server/lib/bfLogging/dualfilelogger.cpp
Normal file
20
YACReaderLibrary/server/lib/bfLogging/dualfilelogger.cpp
Normal file
@ -0,0 +1,20 @@
|
||||
/**
|
||||
@file
|
||||
@author Stefan Frings
|
||||
*/
|
||||
|
||||
#include "dualfilelogger.h"
|
||||
|
||||
|
||||
DualFileLogger::DualFileLogger(QSettings* firstSettings, QSettings* secondSettings, const int refreshInterval, QObject* parent)
|
||||
:Logger(parent)
|
||||
{
|
||||
firstLogger=new FileLogger(firstSettings, refreshInterval, this);
|
||||
secondLogger=new FileLogger(secondSettings, refreshInterval, this);
|
||||
}
|
||||
|
||||
|
||||
void DualFileLogger::log(const QtMsgType type, const QString& message) {
|
||||
firstLogger->log(type, message);
|
||||
secondLogger->log(type, message);
|
||||
}
|
60
YACReaderLibrary/server/lib/bfLogging/dualfilelogger.h
Normal file
60
YACReaderLibrary/server/lib/bfLogging/dualfilelogger.h
Normal file
@ -0,0 +1,60 @@
|
||||
/**
|
||||
@file
|
||||
@author Stefan Frings
|
||||
*/
|
||||
|
||||
#ifndef DUALFILELOGGER_H
|
||||
#define DUALFILELOGGER_H
|
||||
|
||||
#include "logger.h"
|
||||
#include "filelogger.h"
|
||||
#include <QString>
|
||||
#include <QSettings>
|
||||
#include <QtGlobal>
|
||||
|
||||
/**
|
||||
Logs messages into two log files. This is specially useful to get one less detailed
|
||||
logfile for normal operation plus one more detailed file for debugging.
|
||||
@see FileLogger for a description of the required config settings.
|
||||
@see set() describes how to set logger variables
|
||||
@see LogMessage for a description of the message decoration.
|
||||
*/
|
||||
|
||||
class DualFileLogger : public Logger {
|
||||
Q_OBJECT
|
||||
Q_DISABLE_COPY(DualFileLogger)
|
||||
public:
|
||||
|
||||
/**
|
||||
Constructor.
|
||||
@param firstSettings Configuration settings for the first log file, usually stored in an INI file.
|
||||
Must not be 0.
|
||||
Settings are read from the current group, so the caller must have called settings->beginGroup().
|
||||
Because the group must not change during runtime, it is recommended to provide a
|
||||
separate QSettings instance to the logger that is not used by other parts of the program.
|
||||
@param secondSettings Same as firstSettings, but for the second log file.
|
||||
@param refreshInterval Interval of checking the config settings in msec, or 0=disabled
|
||||
@param parent Parent object
|
||||
*/
|
||||
DualFileLogger(QSettings* firstSettings, QSettings* secondSettings, const int refreshInterval=10000, QObject *parent = 0);
|
||||
|
||||
/**
|
||||
Decorate and log a message.
|
||||
This method is thread safe.
|
||||
@param type Message type (level)
|
||||
@param message Message text
|
||||
@see LogMessage for a description of the message decoration.
|
||||
*/
|
||||
virtual void log(const QtMsgType type, const QString& message);
|
||||
|
||||
private:
|
||||
|
||||
/** First logger */
|
||||
FileLogger* firstLogger;
|
||||
|
||||
/** Second logger */
|
||||
FileLogger* secondLogger;
|
||||
|
||||
};
|
||||
|
||||
#endif // DUALFILELOGGER_H
|
176
YACReaderLibrary/server/lib/bfLogging/filelogger.cpp
Normal file
176
YACReaderLibrary/server/lib/bfLogging/filelogger.cpp
Normal file
@ -0,0 +1,176 @@
|
||||
/**
|
||||
@file
|
||||
@author Stefan Frings
|
||||
*/
|
||||
|
||||
#include "filelogger.h"
|
||||
#include <QTime>
|
||||
#include <QStringList>
|
||||
#include <QThread>
|
||||
#include <QtGlobal>
|
||||
#include <QFile>
|
||||
#include <QTimerEvent>
|
||||
#include <QDir>
|
||||
#include <QFileInfo>
|
||||
#include <stdio.h>
|
||||
|
||||
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").toString();
|
||||
// Convert relative fileName to absolute, based on the directory of the config file.
|
||||
#ifdef Q_OS_WIN32
|
||||
if (QDir::isRelativePath(fileName) && settings->format()!=QSettings::NativeFormat)
|
||||
#else
|
||||
if (QDir::isRelativePath(fileName))
|
||||
#endif
|
||||
{
|
||||
QFileInfo configFile(settings->fileName());
|
||||
fileName=QFileInfo(configFile.absolutePath(),fileName).absoluteFilePath();
|
||||
}
|
||||
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",0).toInt());
|
||||
bufferSize=settings->value("bufferSize",0).toInt();
|
||||
disabled=settings->value("disabled",false).toBool();
|
||||
|
||||
// Create new file if the filename has been changed
|
||||
if (oldFileName!=fileName) {
|
||||
fprintf(stderr,"Logging to %s\n",qPrintable(fileName));
|
||||
close();
|
||||
open();
|
||||
}
|
||||
mutex.unlock();
|
||||
}
|
||||
|
||||
|
||||
FileLogger::FileLogger(QSettings* settings, const int refreshInterval, QObject* parent)
|
||||
: Logger(parent)
|
||||
{
|
||||
Q_ASSERT(settings!=0);
|
||||
Q_ASSERT(refreshInterval>=0);
|
||||
this->settings=settings;
|
||||
file=0;
|
||||
if (refreshInterval>0)
|
||||
refreshTimer.start(refreshInterval,this);
|
||||
flushTimer.start(1000,this);
|
||||
refreshSettings();
|
||||
}
|
||||
|
||||
|
||||
FileLogger::~FileLogger() {
|
||||
close();
|
||||
}
|
||||
|
||||
|
||||
void FileLogger::write(const LogMessage* logMessage) {
|
||||
// Write to the file
|
||||
if (!disabled) {
|
||||
// Try to write to the file
|
||||
if (file) {
|
||||
|
||||
// Write the message
|
||||
file->write(qPrintable(logMessage->toString(msgFormat,timestampFormat)));
|
||||
|
||||
// Flush error messages immediately, to ensure that no important message
|
||||
// gets lost when the program terinates abnormally.
|
||||
if (logMessage->getType()>=QtCriticalMsg) {
|
||||
file->flush();
|
||||
}
|
||||
|
||||
// Check for success
|
||||
if (file->error()) {
|
||||
close();
|
||||
qWarning("Cannot write to log file %s: %s",qPrintable(fileName),qPrintable(file->errorString()));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// Fall-back to the super class method, if writing failed
|
||||
if (!file) {
|
||||
Logger::write(logMessage);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
void FileLogger::open() {
|
||||
if (fileName.isEmpty()) {
|
||||
qWarning("Name of logFile is empty");
|
||||
}
|
||||
else {
|
||||
file=new QFile(fileName);
|
||||
if (!file->open(QIODevice::WriteOnly | QIODevice::Append | QIODevice::Text)) {
|
||||
qWarning("Cannot open log file %s: %s",qPrintable(fileName),qPrintable(file->errorString()));
|
||||
file=0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void FileLogger::close() {
|
||||
if (file) {
|
||||
file->close();
|
||||
delete file;
|
||||
file=0;
|
||||
}
|
||||
}
|
||||
|
||||
void FileLogger::rotate() {
|
||||
// count current number of existing backup files
|
||||
int count=0;
|
||||
forever {
|
||||
QFile bakFile(QString("%1.%2").arg(fileName).arg(count+1));
|
||||
if (bakFile.exists()) {
|
||||
++count;
|
||||
}
|
||||
else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Remove all old backup files that exceed the maximum number
|
||||
while (maxBackups>0 && count>=maxBackups) {
|
||||
QFile::remove(QString("%1.%2").arg(fileName).arg(count));
|
||||
--count;
|
||||
}
|
||||
|
||||
// Rotate backup files
|
||||
for (int i=count; i>0; --i) {
|
||||
QFile::rename(QString("%1.%2").arg(fileName).arg(i),QString("%1.%2").arg(fileName).arg(i+1));
|
||||
}
|
||||
|
||||
// Backup the current logfile
|
||||
QFile::rename(fileName,fileName+".1");
|
||||
}
|
||||
|
||||
|
||||
void FileLogger::timerEvent(QTimerEvent* event) {
|
||||
if (!event) {
|
||||
return;
|
||||
}
|
||||
else if (event->timerId()==refreshTimer.timerId()) {
|
||||
refreshSettings();
|
||||
}
|
||||
else if (event->timerId()==flushTimer.timerId() && file) {
|
||||
mutex.lock();
|
||||
|
||||
// Flush the I/O buffer
|
||||
file->flush();
|
||||
|
||||
// Rotate the file if it is too large
|
||||
if (maxSize>0 && file->size()>=maxSize) {
|
||||
close();
|
||||
rotate();
|
||||
open();
|
||||
}
|
||||
|
||||
mutex.unlock();
|
||||
}
|
||||
}
|
131
YACReaderLibrary/server/lib/bfLogging/filelogger.h
Normal file
131
YACReaderLibrary/server/lib/bfLogging/filelogger.h
Normal file
@ -0,0 +1,131 @@
|
||||
/**
|
||||
@file
|
||||
@author Stefan Frings
|
||||
*/
|
||||
|
||||
#ifndef FILELOGGER_H
|
||||
#define FILELOGGER_H
|
||||
|
||||
#include <QtGlobal>
|
||||
#include <QSettings>
|
||||
#include <QFile>
|
||||
#include <QMutex>
|
||||
#include <QBasicTimer>
|
||||
#include "logger.h"
|
||||
|
||||
/**
|
||||
Logger that uses a text file for output. Settings are read from a
|
||||
config file using a QSettings object. Config settings can be changed at runtime.
|
||||
They are copied to private fields in regular intervals for permance reason.
|
||||
<p>
|
||||
Example for the required configuration settings:
|
||||
<code><pre>
|
||||
disabled=false
|
||||
fileName=logs/QtWebApp.log
|
||||
maxSize=1000000
|
||||
maxBackups=2
|
||||
minLevel=0
|
||||
msgformat={timestamp} {typeNr} {type} thread={thread}: {msg}
|
||||
timestampFormat=dd.MM.yyyy hh:mm:ss.zzz
|
||||
bufferSize=0
|
||||
</pre></code>
|
||||
|
||||
- fileName is the name of the log file, relative to the directory of the settings file.
|
||||
In case of windows, if the settings are in the registry, the path is relative to the current
|
||||
working directory.
|
||||
- maxSize is the maximum size of that file in bytes. The file will be backed up and
|
||||
replaced by a new file if it becomes larger than this limit. Please note that
|
||||
the actual file size may become a little bit larger than this limit. 0=unlimited.
|
||||
- maxBackups defines the number of backup files to keep. 0=unlimited.
|
||||
- minLevel defines the minimum level of message types to be written into the file.
|
||||
- msgFormat defines the decoration of log messages.
|
||||
- timestampFormat defines the format of timestamps.
|
||||
- buffersize defines the size of the backtrace buffer. 0=disabled.
|
||||
The buffer stores log messages of any level from the time before an error occurs.
|
||||
It can be used to provide detailed debug information when an error occurs, while keeping
|
||||
the logfile clean as long no error occurs. Using this buffer may reduce performance
|
||||
significantly.
|
||||
|
||||
@see set() describes how to set logger variables
|
||||
@see LogMessage for a description of the message decoration.
|
||||
@see Logger for a descrition of the backtrace buffer
|
||||
*/
|
||||
|
||||
class FileLogger : public Logger {
|
||||
Q_OBJECT
|
||||
Q_DISABLE_COPY(FileLogger)
|
||||
public:
|
||||
|
||||
/**
|
||||
Constructor.
|
||||
@param settings Configuration settings, usually stored in an INI file. Must not be 0.
|
||||
Settings are read from the current group, so the caller must have called settings->beginGroup().
|
||||
Because the group must not change during runtime, it is recommended to provide a
|
||||
separate QSettings instance to the logger that is not used by other parts of the program.
|
||||
@param refreshInterval Interval of checking the config settings in msec, or 0=disabled
|
||||
@param parent Parent object
|
||||
*/
|
||||
FileLogger(QSettings* settings, const int refreshInterval=10000, QObject* parent = 0);
|
||||
|
||||
/**
|
||||
Destructor. Closes the file.
|
||||
*/
|
||||
virtual ~FileLogger();
|
||||
|
||||
/** Write a message to the log file */
|
||||
virtual void write(const LogMessage* logMessage);
|
||||
|
||||
protected:
|
||||
|
||||
/**
|
||||
Handler for timer events.
|
||||
Refreshes config settings or synchronizes I/O buffer, depending on the event.
|
||||
This method is thread-safe.
|
||||
@param event used to distinguish between the two timers.
|
||||
*/
|
||||
void timerEvent(QTimerEvent* event);
|
||||
|
||||
private:
|
||||
|
||||
/** Configured name of the log file */
|
||||
QString fileName;
|
||||
|
||||
/** Configured maximum size of the file in bytes, or 0=unlimited */
|
||||
long maxSize;
|
||||
|
||||
/** Configured maximum number of backup files, or 0=unlimited */
|
||||
int maxBackups;
|
||||
|
||||
/** Whether this logger is disabled */
|
||||
bool disabled;
|
||||
|
||||
/** Pointer to the configuration settings */
|
||||
QSettings* settings;
|
||||
|
||||
/** Output file, or 0=disabled */
|
||||
QFile* file;
|
||||
|
||||
/** Timer for refreshing configuration settings */
|
||||
QBasicTimer refreshTimer;
|
||||
|
||||
/** Timer for flushing the file I/O buffer */
|
||||
QBasicTimer flushTimer;
|
||||
|
||||
/** Open the output file */
|
||||
void open();
|
||||
|
||||
/** Close the output file */
|
||||
void close();
|
||||
|
||||
/** Rotate files and delete some backups if there are too many */
|
||||
void rotate();
|
||||
|
||||
/**
|
||||
Refreshes the configuration settings.
|
||||
This method is thread-safe.
|
||||
*/
|
||||
void refreshSettings();
|
||||
|
||||
};
|
||||
|
||||
#endif // FILELOGGER_H
|
154
YACReaderLibrary/server/lib/bfLogging/logger.cpp
Normal file
154
YACReaderLibrary/server/lib/bfLogging/logger.cpp
Normal file
@ -0,0 +1,154 @@
|
||||
/**
|
||||
@file
|
||||
@author Stefan Frings
|
||||
*/
|
||||
|
||||
#include "logger.h"
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <QMutex>
|
||||
#include <QDateTime>
|
||||
#include <QThread>
|
||||
|
||||
Logger* Logger::defaultLogger=0;
|
||||
|
||||
|
||||
QThreadStorage<QHash<QString,QString>*> Logger::logVars;
|
||||
|
||||
|
||||
QThreadStorage<QList<LogMessage*>*> Logger::buffers;
|
||||
|
||||
|
||||
QMutex Logger::mutex;
|
||||
|
||||
|
||||
Logger::Logger(QObject* parent)
|
||||
: QObject(parent),
|
||||
msgFormat("{timestamp} {type} {msg}"),
|
||||
timestampFormat("dd.MM.yyyy hh:mm:ss.zzz"),
|
||||
minLevel(QtDebugMsg),
|
||||
bufferSize(0)
|
||||
{}
|
||||
|
||||
|
||||
Logger::Logger(const QString msgFormat, const QString timestampFormat, const QtMsgType minLevel, const int bufferSize, QObject* parent)
|
||||
:QObject(parent) {
|
||||
this->msgFormat=msgFormat;
|
||||
this->timestampFormat=timestampFormat;
|
||||
this->minLevel=minLevel;
|
||||
this->bufferSize=bufferSize;
|
||||
}
|
||||
|
||||
|
||||
void Logger::msgHandler(const QtMsgType type, const char* message) {
|
||||
static QMutex recursiveMutex(QMutex::Recursive);
|
||||
static QMutex nonRecursiveMutex(QMutex::NonRecursive);
|
||||
|
||||
// Prevent multiple threads from calling this method simultaneoulsy.
|
||||
// But allow recursive calls, which is required to prevent a deadlock
|
||||
// if the logger itself produces an error message.
|
||||
recursiveMutex.lock();
|
||||
|
||||
// Fall back to stderr when this method has been called recursively.
|
||||
if (defaultLogger && nonRecursiveMutex.tryLock()) {
|
||||
defaultLogger->log(type,message);
|
||||
nonRecursiveMutex.unlock();
|
||||
}
|
||||
else {
|
||||
fputs(message,stderr);
|
||||
fflush(stderr);
|
||||
}
|
||||
|
||||
// Abort the program after logging a fatal message
|
||||
if (type>=QtFatalMsg) {
|
||||
abort();
|
||||
}
|
||||
|
||||
recursiveMutex.unlock();
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
Logger::~Logger() {
|
||||
if (defaultLogger==this) {
|
||||
qInstallMsgHandler(0);
|
||||
defaultLogger=0;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void Logger::write(const LogMessage* logMessage) {
|
||||
fputs(qPrintable(logMessage->toString(msgFormat,timestampFormat)),stderr);
|
||||
fflush(stderr);
|
||||
}
|
||||
|
||||
|
||||
void Logger::installMsgHandler() {
|
||||
defaultLogger=this;
|
||||
qInstallMsgHandler(msgHandler);
|
||||
}
|
||||
|
||||
|
||||
void Logger::set(const QString& name, const QString& value) {
|
||||
mutex.lock();
|
||||
if (!logVars.hasLocalData()) {
|
||||
logVars.setLocalData(new QHash<QString,QString>);
|
||||
}
|
||||
logVars.localData()->insert(name,value);
|
||||
mutex.unlock();
|
||||
}
|
||||
|
||||
|
||||
void Logger::clear(const bool buffer, const bool variables) {
|
||||
mutex.lock();
|
||||
if (buffer && buffers.hasLocalData()) {
|
||||
QList<LogMessage*>* buffer=buffers.localData();
|
||||
while (buffer && !buffer->isEmpty()) {
|
||||
LogMessage* logMessage=buffer->takeLast();
|
||||
delete logMessage;
|
||||
}
|
||||
}
|
||||
if (variables && logVars.hasLocalData()) {
|
||||
logVars.localData()->clear();
|
||||
}
|
||||
mutex.unlock();
|
||||
}
|
||||
|
||||
|
||||
void Logger::log(const QtMsgType type, const QString& message) {
|
||||
mutex.lock();
|
||||
|
||||
// If the buffer is enabled, write the message into it
|
||||
if (bufferSize>0) {
|
||||
// Create new thread local buffer, if necessary
|
||||
if (!buffers.hasLocalData()) {
|
||||
buffers.setLocalData(new QList<LogMessage*>());
|
||||
}
|
||||
QList<LogMessage*>* buffer=buffers.localData();
|
||||
// Append the decorated log message
|
||||
LogMessage* logMessage=new LogMessage(type,message,logVars.localData());
|
||||
buffer->append(logMessage);
|
||||
// Delete oldest message if the buffer became too large
|
||||
if (buffer->size()>bufferSize) {
|
||||
delete buffer->takeFirst();
|
||||
}
|
||||
// If the type of the message is high enough, print the whole buffer
|
||||
if (type>=minLevel) {
|
||||
while (!buffer->isEmpty()) {
|
||||
LogMessage* logMessage=buffer->takeFirst();
|
||||
write(logMessage);
|
||||
delete logMessage;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Buffer is disabled, print the message if the type is high enough
|
||||
else {
|
||||
if (type>=minLevel) {
|
||||
LogMessage logMessage(type,message,logVars.localData());
|
||||
write(&logMessage);
|
||||
}
|
||||
}
|
||||
mutex.unlock();
|
||||
}
|
144
YACReaderLibrary/server/lib/bfLogging/logger.h
Normal file
144
YACReaderLibrary/server/lib/bfLogging/logger.h
Normal file
@ -0,0 +1,144 @@
|
||||
/**
|
||||
@file
|
||||
@author Stefan Frings
|
||||
*/
|
||||
|
||||
#ifndef LOGGER_H
|
||||
#define LOGGER_H
|
||||
|
||||
#include <QtGlobal>
|
||||
#include <QThreadStorage>
|
||||
#include <QHash>
|
||||
#include <QStringList>
|
||||
#include <QMutex>
|
||||
#include "logmessage.h"
|
||||
|
||||
/**
|
||||
Decorates and writes log messages to the console, stderr.
|
||||
<p>
|
||||
The decorator uses a predefined msgFormat string to enrich log messages
|
||||
with additional information (e.g. timestamp).
|
||||
<p>
|
||||
The msgFormat string and also the message text may contain additional
|
||||
variable names in the form <i>{name}</i> that are filled by values
|
||||
taken from a static thread local dictionary.
|
||||
<p>
|
||||
The buffer stores log messages of any level from the time before an error occurs.
|
||||
It can be used to provide detailed debug information when an error occurs, while
|
||||
keeping the logfile clean as long no error occurs. Using this buffer may
|
||||
reduce performance significantly.
|
||||
<p>
|
||||
The logger can be registered to handle messages from
|
||||
the static global functions qDebug(), qWarning(), qCritical() and qFatal().
|
||||
@see set() describes how to set logger variables
|
||||
@see LogMessage for a description of the message decoration.
|
||||
@warning You should prefer a derived class, for example FileLogger,
|
||||
because logging to the console is less useful.
|
||||
*/
|
||||
|
||||
class Logger : public QObject {
|
||||
Q_OBJECT
|
||||
Q_DISABLE_COPY(Logger)
|
||||
public:
|
||||
|
||||
/**
|
||||
Constructor.
|
||||
Uses the same defaults as the other constructor.
|
||||
@param parent Parent object
|
||||
*/
|
||||
Logger(QObject* parent);
|
||||
|
||||
|
||||
/**
|
||||
Constructor.
|
||||
@param msgFormat Format of the decoration, e.g. "{timestamp} {type} thread={thread}: {msg}"
|
||||
@param timestampFormat Format of timestamp, e.g. "dd.MM.yyyy hh:mm:ss.zzz"
|
||||
@param minLevel Minimum type of messages that are written out.
|
||||
@param bufferSize Size of the backtrace buffer, number of messages per thread. 0=disabled.
|
||||
@param parent Parent object
|
||||
@see LogMessage for a description of the message decoration.
|
||||
*/
|
||||
Logger(const QString msgFormat="{timestamp} {type} {msg}", const QString timestampFormat="dd.MM.yyyy hh:mm:ss.zzz", const QtMsgType minLevel=QtDebugMsg, const int bufferSize=0, QObject* parent = 0);
|
||||
|
||||
/** Destructor */
|
||||
virtual ~Logger();
|
||||
|
||||
/**
|
||||
Decorate and log the message, if type>=minLevel.
|
||||
This method is thread safe.
|
||||
@param type Message type (level)
|
||||
@param message Message text
|
||||
@see LogMessage for a description of the message decoration.
|
||||
*/
|
||||
virtual void log(const QtMsgType type, const QString& message);
|
||||
|
||||
/**
|
||||
Installs this logger as the default message handler, so it
|
||||
can be used through the global static logging functions (e.g. qDebug()).
|
||||
*/
|
||||
void installMsgHandler();
|
||||
|
||||
/**
|
||||
Sets a thread-local variable that may be used to decorate log messages.
|
||||
This method is thread safe.
|
||||
@param name Name of the variable
|
||||
@param value Value of the variable
|
||||
*/
|
||||
static void set(const QString& name, const QString& value);
|
||||
|
||||
/**
|
||||
Clear the thread-local data of the current thread.
|
||||
@param buffer Whether to clear the backtrace buffer
|
||||
@param variables Whether to clear the log variables
|
||||
*/
|
||||
static void clear(const bool buffer=true, const bool variables=true);
|
||||
|
||||
protected:
|
||||
|
||||
/** Format string for message decoration */
|
||||
QString msgFormat;
|
||||
|
||||
/** Format string of timestamps */
|
||||
QString timestampFormat;
|
||||
|
||||
/** Minimum level of message types that are written out */
|
||||
QtMsgType minLevel;
|
||||
|
||||
/** Size of backtrace buffer, number of messages per thread. 0=disabled */
|
||||
int bufferSize;
|
||||
|
||||
/** Used to synchronize access to the static members */
|
||||
static QMutex mutex;
|
||||
|
||||
/**
|
||||
Decorate and write a log message to stderr. Override this method
|
||||
to provide a different output medium.
|
||||
*/
|
||||
virtual void write(const LogMessage* logMessage);
|
||||
|
||||
private:
|
||||
|
||||
/** Pointer to the default logger, used by msgHandler() */
|
||||
static Logger* defaultLogger;
|
||||
|
||||
/**
|
||||
Message Handler for the global static logging functions (e.g. qDebug()).
|
||||
Forward calls to the default logger.
|
||||
<p>
|
||||
In case of a fatal message, the program will abort.
|
||||
Variables in the in the message are replaced by their values.
|
||||
This method is thread safe.
|
||||
@param type Message type (level)
|
||||
@param message Message text
|
||||
*/
|
||||
static void msgHandler(const QtMsgType type, const char* message);
|
||||
|
||||
/** Thread local variables to be used in log messages */
|
||||
static QThreadStorage<QHash<QString,QString>*> logVars;
|
||||
|
||||
/** Thread local backtrace buffers */
|
||||
static QThreadStorage<QList<LogMessage*>*> buffers;
|
||||
|
||||
};
|
||||
|
||||
#endif // LOGGER_H
|
68
YACReaderLibrary/server/lib/bfLogging/logmessage.cpp
Normal file
68
YACReaderLibrary/server/lib/bfLogging/logmessage.cpp
Normal file
@ -0,0 +1,68 @@
|
||||
/**
|
||||
@file
|
||||
@author Stefan Frings
|
||||
*/
|
||||
|
||||
#include "logmessage.h"
|
||||
#include <QThread>
|
||||
|
||||
LogMessage::LogMessage(const QtMsgType type, const QString& message, QHash<QString,QString>* logVars) {
|
||||
this->type=type;
|
||||
this->message=message;
|
||||
timestamp=QDateTime::currentDateTime();
|
||||
threadId=QThread::currentThreadId();
|
||||
|
||||
// Copy the logVars if not null,
|
||||
// so that later changes in the original do not affect the copy
|
||||
if (logVars) {
|
||||
this->logVars=*logVars;
|
||||
}
|
||||
}
|
||||
|
||||
QString LogMessage::toString(const QString& msgFormat, const QString& timestampFormat) const {
|
||||
QString decorated=msgFormat+"\n";
|
||||
decorated.replace("{msg}",message);
|
||||
|
||||
if (decorated.contains("{timestamp}")) {
|
||||
decorated.replace("{timestamp}",QDateTime::currentDateTime().toString(timestampFormat));
|
||||
}
|
||||
|
||||
QString typeNr;
|
||||
typeNr.setNum(type);
|
||||
decorated.replace("{typeNr}",typeNr);
|
||||
|
||||
switch (type) {
|
||||
case QtDebugMsg:
|
||||
decorated.replace("{type}","DEBUG");
|
||||
break;
|
||||
case QtWarningMsg:
|
||||
decorated.replace("{type}","WARNING");
|
||||
break;
|
||||
case QtCriticalMsg:
|
||||
decorated.replace("{type}","CRITICAL");
|
||||
break;
|
||||
case QtFatalMsg:
|
||||
decorated.replace("{type}","FATAL");
|
||||
break;
|
||||
default:
|
||||
decorated.replace("{type}",typeNr);
|
||||
}
|
||||
|
||||
QString threadId;
|
||||
threadId.setNum((qint64)QThread::currentThreadId());
|
||||
decorated.replace("{thread}",threadId);
|
||||
|
||||
// Fill in variables
|
||||
if (decorated.contains("{") && !logVars.isEmpty()) {
|
||||
QList<QString> keys=logVars.keys();
|
||||
foreach (QString key, keys) {
|
||||
decorated.replace("{"+key+"}",logVars.value(key));
|
||||
}
|
||||
}
|
||||
|
||||
return decorated;
|
||||
}
|
||||
|
||||
QtMsgType LogMessage::getType() const {
|
||||
return type;
|
||||
}
|
74
YACReaderLibrary/server/lib/bfLogging/logmessage.h
Normal file
74
YACReaderLibrary/server/lib/bfLogging/logmessage.h
Normal file
@ -0,0 +1,74 @@
|
||||
/**
|
||||
@file
|
||||
@author Stefan Frings
|
||||
*/
|
||||
|
||||
#ifndef LOGMESSAGE_H
|
||||
#define LOGMESSAGE_H
|
||||
|
||||
#include <QtGlobal>
|
||||
#include <QDateTime>
|
||||
#include <QHash>
|
||||
|
||||
/**
|
||||
Represents a single log message together with some data
|
||||
that are used to decorate the log message.
|
||||
|
||||
The following variables may be used in the message and in msgFormat:
|
||||
|
||||
- {timestamp} Date and time of creation
|
||||
- {typeNr} Type of the message in numeric format (0-3)
|
||||
- {type} Type of the message in string format (DEBUG, WARNING, CRITICAL, FATAL)
|
||||
- {thread} ID number of the thread
|
||||
- {msg} Message text (only useable in msgFormat)
|
||||
- {xxx} For any user-defined logger variable
|
||||
*/
|
||||
|
||||
class LogMessage
|
||||
{
|
||||
Q_DISABLE_COPY(LogMessage)
|
||||
public:
|
||||
|
||||
/**
|
||||
Constructor. All parameters are copied, so that later changes to them do not
|
||||
affect this object.
|
||||
@param type Type of the message
|
||||
@param message Message text
|
||||
@param logVars Logger variables, 0 is allowed
|
||||
*/
|
||||
LogMessage(const QtMsgType type, const QString& message, QHash<QString,QString>* logVars);
|
||||
|
||||
/**
|
||||
Returns the log message as decorated string.
|
||||
@param msgFormat Format of the decoration. May contain variables and static text,
|
||||
e.g. "{timestamp} {type} thread={thread}: {msg}"
|
||||
@param timestampFormat Format of timestamp, e.g. "dd.MM.yyyy hh:mm:ss.zzz"
|
||||
@see QDatetime for a description of the timestamp format pattern
|
||||
*/
|
||||
QString toString(const QString& msgFormat, const QString& timestampFormat) const;
|
||||
|
||||
/**
|
||||
Get the message type.
|
||||
*/
|
||||
QtMsgType getType() const;
|
||||
|
||||
private:
|
||||
|
||||
/** Logger variables */
|
||||
QHash<QString,QString> logVars;
|
||||
|
||||
/** Date and time of creation */
|
||||
QDateTime timestamp;
|
||||
|
||||
/** Type of the message */
|
||||
QtMsgType type;
|
||||
|
||||
/** ID number of the thread */
|
||||
Qt::HANDLE threadId;
|
||||
|
||||
/** Message text */
|
||||
QString message;
|
||||
|
||||
};
|
||||
|
||||
#endif // LOGMESSAGE_H
|
@ -0,0 +1,7 @@
|
||||
INCLUDEPATH += $$PWD
|
||||
DEPENDPATH += $$PWD
|
||||
|
||||
HEADERS += $$PWD/template.h $$PWD/templateloader.h $$PWD/templatecache.h
|
||||
SOURCES += $$PWD/template.cpp $$PWD/templateloader.cpp $$PWD/templatecache.cpp
|
||||
|
||||
OTHER_FILES += $$PWD/../doc/readme.txt
|
187
YACReaderLibrary/server/lib/bfTemplateEngine/template.cpp
Normal file
187
YACReaderLibrary/server/lib/bfTemplateEngine/template.cpp
Normal file
@ -0,0 +1,187 @@
|
||||
/**
|
||||
@file
|
||||
@author Stefan Frings
|
||||
*/
|
||||
|
||||
#include "template.h"
|
||||
#include <QFileInfo>
|
||||
|
||||
Template::Template(QString source, QString sourceName)
|
||||
: QString(source) {
|
||||
this->sourceName=sourceName;
|
||||
this->warnings=false;
|
||||
}
|
||||
|
||||
Template::Template(QFile& file, QTextCodec* textCodec) {
|
||||
sourceName=QFileInfo(file.fileName()).baseName();
|
||||
if (!file.isOpen()) {
|
||||
file.open(QFile::ReadOnly | QFile::Text);
|
||||
}
|
||||
QByteArray data=file.readAll();
|
||||
file.close();
|
||||
if (data.size()==0 || file.error()) {
|
||||
qCritical("Template: cannot read from %s, %s",qPrintable(sourceName),qPrintable(file.errorString()));
|
||||
append(textCodec->toUnicode(data));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
int Template::setVariable(QString name, QString value) {
|
||||
int count=0;
|
||||
QString variable="{"+name+"}";
|
||||
int start=indexOf(variable);
|
||||
while (start>=0) {
|
||||
replace(start, variable.length(), value);
|
||||
count++;
|
||||
start=indexOf(variable,start+variable.length());
|
||||
}
|
||||
if (count==0 && warnings) {
|
||||
qWarning("Template: missing variable %s in %s",qPrintable(variable),qPrintable(sourceName));
|
||||
}
|
||||
return count;
|
||||
}
|
||||
|
||||
int Template::setCondition(QString name, bool value) {
|
||||
int count=0;
|
||||
QString startTag=QString("{if %1}").arg(name);
|
||||
QString elseTag=QString("{else %1}").arg(name);
|
||||
QString endTag=QString("{end %1}").arg(name);
|
||||
// search for if-else-end
|
||||
int start=indexOf(startTag);
|
||||
while (start>=0) {
|
||||
int end=indexOf(endTag,start+startTag.length());
|
||||
if (end>=0) {
|
||||
count++;
|
||||
int ellse=indexOf(elseTag,start+startTag.length());
|
||||
if (ellse>start && ellse<end) { // there is an else part
|
||||
if (value==true) {
|
||||
QString truePart=mid(start+startTag.length(), ellse-start-startTag.length());
|
||||
replace(start, end-start+endTag.length(), truePart);
|
||||
}
|
||||
else { // value==false
|
||||
QString falsePart=mid(ellse+elseTag.length(), end-ellse-elseTag.length());
|
||||
replace(start, end-start+endTag.length(), falsePart);
|
||||
}
|
||||
}
|
||||
else if (value==true) { // and no else part
|
||||
QString truePart=mid(start+startTag.length(), end-start-startTag.length());
|
||||
replace(start, end-start+endTag.length(), truePart);
|
||||
}
|
||||
else { // value==false and no else part
|
||||
replace(start, end-start+endTag.length(), "");
|
||||
}
|
||||
start=indexOf(startTag,start);
|
||||
}
|
||||
else {
|
||||
qWarning("Template: missing condition end %s in %s",qPrintable(endTag),qPrintable(sourceName));
|
||||
}
|
||||
}
|
||||
// search for ifnot-else-end
|
||||
QString startTag2="{ifnot "+name+"}";
|
||||
start=indexOf(startTag2);
|
||||
while (start>=0) {
|
||||
int end=indexOf(endTag,start+startTag2.length());
|
||||
if (end>=0) {
|
||||
count++;
|
||||
int ellse=indexOf(elseTag,start+startTag2.length());
|
||||
if (ellse>start && ellse<end) { // there is an else part
|
||||
if (value==false) {
|
||||
QString falsePart=mid(start+startTag2.length(), ellse-start-startTag2.length());
|
||||
replace(start, end-start+endTag.length(), falsePart);
|
||||
}
|
||||
else { // value==true
|
||||
QString truePart=mid(ellse+elseTag.length(), end-ellse-elseTag.length());
|
||||
replace(start, end-start+endTag.length(), truePart);
|
||||
}
|
||||
}
|
||||
else if (value==false) { // and no else part
|
||||
QString falsePart=mid(start+startTag2.length(), end-start-startTag2.length());
|
||||
replace(start, end-start+endTag.length(), falsePart);
|
||||
}
|
||||
else { // value==true and no else part
|
||||
replace(start, end-start+endTag.length(), "");
|
||||
}
|
||||
start=indexOf(startTag2,start);
|
||||
}
|
||||
else {
|
||||
qWarning("Template: missing condition end %s in %s",qPrintable(endTag),qPrintable(sourceName));
|
||||
}
|
||||
}
|
||||
if (count==0 && warnings) {
|
||||
qWarning("Template: missing condition %s or %s in %s",qPrintable(startTag),qPrintable(startTag2),qPrintable(sourceName));
|
||||
}
|
||||
return count;
|
||||
}
|
||||
|
||||
int Template::loop(QString name, int repetitions) {
|
||||
Q_ASSERT(repetitions>=0);
|
||||
int count=0;
|
||||
QString startTag="{loop "+name+"}";
|
||||
QString elseTag="{else "+name+"}";
|
||||
QString endTag="{end "+name+"}";
|
||||
// search for loop-else-end
|
||||
int start=indexOf(startTag);
|
||||
while (start>=0) {
|
||||
int end=indexOf(endTag,start+startTag.length());
|
||||
if (end>=0) {
|
||||
count++;
|
||||
int ellse=indexOf(elseTag,start+startTag.length());
|
||||
if (ellse>start && ellse<end) { // there is an else part
|
||||
if (repetitions>0) {
|
||||
QString loopPart=mid(start+startTag.length(), ellse-start-startTag.length());
|
||||
QString insertMe;
|
||||
for (int i=0; i<repetitions; ++i) {
|
||||
// number variables, conditions and sub-loop within the loop
|
||||
QString nameNum=name+QString::number(i);
|
||||
QString s=loopPart;
|
||||
s.replace(QString("{%1.").arg(name), QString("{%1.").arg(nameNum));
|
||||
s.replace(QString("{if %1.").arg(name), QString("{if %1.").arg(nameNum));
|
||||
s.replace(QString("{ifnot %1.").arg(name), QString("{ifnot %1.").arg(nameNum));
|
||||
s.replace(QString("{else %1.").arg(name), QString("{else %1.").arg(nameNum));
|
||||
s.replace(QString("{end %1.").arg(name), QString("{end %1.").arg(nameNum));
|
||||
s.replace(QString("{loop %1.").arg(name), QString("{loop %1.").arg(nameNum));
|
||||
insertMe.append(s);
|
||||
}
|
||||
replace(start, end-start+endTag.length(), insertMe);
|
||||
}
|
||||
else { // repetitions==0
|
||||
QString elsePart=mid(ellse+elseTag.length(), end-ellse-elseTag.length());
|
||||
replace(start, end-start+endTag.length(), elsePart);
|
||||
}
|
||||
}
|
||||
else if (repetitions>0) { // and no else part
|
||||
QString loopPart=mid(start+startTag.length(), end-start-startTag.length());
|
||||
QString insertMe;
|
||||
for (int i=0; i<repetitions; ++i) {
|
||||
// number variables, conditions and sub-loop within the loop
|
||||
QString nameNum=name+QString::number(i);
|
||||
QString s=loopPart;
|
||||
s.replace(QString("{%1.").arg(name), QString("{%1.").arg(nameNum));
|
||||
s.replace(QString("{if %1.").arg(name), QString("{if %1.").arg(nameNum));
|
||||
s.replace(QString("{ifnot %1.").arg(name), QString("{ifnot %1.").arg(nameNum));
|
||||
s.replace(QString("{else %1.").arg(name), QString("{else %1.").arg(nameNum));
|
||||
s.replace(QString("{end %1.").arg(name), QString("{end %1.").arg(nameNum));
|
||||
s.replace(QString("{loop %1.").arg(name), QString("{loop %1.").arg(nameNum));
|
||||
insertMe.append(s);
|
||||
}
|
||||
replace(start, end-start+endTag.length(), insertMe);
|
||||
}
|
||||
else { // repetitions==0 and no else part
|
||||
replace(start, end-start+endTag.length(), "");
|
||||
}
|
||||
start=indexOf(startTag,start);
|
||||
}
|
||||
else {
|
||||
qWarning("Template: missing loop end %s in %s",qPrintable(endTag),qPrintable(sourceName));
|
||||
}
|
||||
}
|
||||
if (count==0 && warnings) {
|
||||
qWarning("Template: missing loop %s in %s",qPrintable(startTag),qPrintable(sourceName));
|
||||
}
|
||||
return count;
|
||||
}
|
||||
|
||||
void Template::enableWarnings(bool enable) {
|
||||
warnings=enable;
|
||||
}
|
||||
|
167
YACReaderLibrary/server/lib/bfTemplateEngine/template.h
Normal file
167
YACReaderLibrary/server/lib/bfTemplateEngine/template.h
Normal file
@ -0,0 +1,167 @@
|
||||
/**
|
||||
@file
|
||||
@author Stefan Frings
|
||||
*/
|
||||
|
||||
#ifndef TEMPLATE_H
|
||||
#define TEMPLATE_H
|
||||
|
||||
#include <QString>
|
||||
#include <QRegExp>
|
||||
#include <QIODevice>
|
||||
#include <QTextCodec>
|
||||
#include <QFile>
|
||||
#include <QString>
|
||||
|
||||
/**
|
||||
Enhanced version of QString for template processing. Templates
|
||||
are usually loaded from files, but may also be loaded from
|
||||
prepared Strings.
|
||||
Example template file:
|
||||
<p><code><pre>
|
||||
Hello {username}, how are you?
|
||||
|
||||
{if locked}
|
||||
Your account is locked.
|
||||
{else locked}
|
||||
Welcome on our system.
|
||||
{end locked}
|
||||
|
||||
The following users are on-line:
|
||||
Username Time
|
||||
{loop user}
|
||||
{user.name} {user.time}
|
||||
{end user}
|
||||
</pre></code></p>
|
||||
<p>
|
||||
Example code to fill this template:
|
||||
<p><code><pre>
|
||||
Template t(QFile("test.tpl"),QTextCode::codecForName("UTF-8"));
|
||||
t.setVariable("user", "Stefan");
|
||||
t.setCondition("locked",false);
|
||||
t.loop("user",2);
|
||||
t.setVariable("user0.name,"Markus");
|
||||
t.setVariable("user0.time,"8:30");
|
||||
t.setVariable("user1.name,"Roland");
|
||||
t.setVariable("user1.time,"8:45");
|
||||
</pre></code></p>
|
||||
<p>
|
||||
The code example above shows how variable within loops are numbered.
|
||||
Counting starts with 0. Loops can be nested, for example:
|
||||
<p><code><pre>
|
||||
<table>
|
||||
{loop row}
|
||||
<tr>
|
||||
{loop row.column}
|
||||
<td>{row.column.value}</td>
|
||||
{end row.column}
|
||||
</tr>
|
||||
{end row}
|
||||
</table>
|
||||
</pre></code></p>
|
||||
<p>
|
||||
Example code to fill this nested loop with 3 rows and 4 columns:
|
||||
<p><code><pre>
|
||||
t.loop("row",3);
|
||||
|
||||
t.loop("row0.column",4);
|
||||
t.setVariable("row0.column0.value","a");
|
||||
t.setVariable("row0.column1.value","b");
|
||||
t.setVariable("row0.column2.value","c");
|
||||
t.setVariable("row0.column3.value","d");
|
||||
|
||||
t.loop("row1.column",4);
|
||||
t.setVariable("row1.column0.value","e");
|
||||
t.setVariable("row1.column1.value","f");
|
||||
t.setVariable("row1.column2.value","g");
|
||||
t.setVariable("row1.column3.value","h");
|
||||
|
||||
t.loop("row2.column",4);
|
||||
t.setVariable("row2.column0.value","i");
|
||||
t.setVariable("row2.column1.value","j");
|
||||
t.setVariable("row2.column2.value","k");
|
||||
t.setVariable("row2.column3.value","l");
|
||||
</pre></code></p>
|
||||
@see TemplateLoader
|
||||
@see TemplateCache
|
||||
*/
|
||||
|
||||
class Template : public QString {
|
||||
public:
|
||||
|
||||
/**
|
||||
Constructor that reads the template from a string.
|
||||
@param source The template source text
|
||||
@param sourceName Name of the source file, used for logging
|
||||
*/
|
||||
Template(QString source, QString sourceName);
|
||||
|
||||
/**
|
||||
Constructor that reads the template from a file. Note that this class does not
|
||||
cache template files by itself, so using this constructor is only recommended
|
||||
to be used on local filesystem.
|
||||
@param file File that provides the source text
|
||||
@param textCodec Encoding of the source
|
||||
@see TemplateLoader
|
||||
@see TemplateCache
|
||||
*/
|
||||
Template(QFile& file, QTextCodec* textCodec);
|
||||
|
||||
/**
|
||||
Replace a variable by the given value.
|
||||
Affects tags with the syntax
|
||||
|
||||
- {name}
|
||||
|
||||
After settings the
|
||||
value of a variable, the variable does not exist anymore,
|
||||
it it cannot be changed multiple times.
|
||||
@param name name of the variable
|
||||
@param value new value
|
||||
@return The count of variables that have been processed
|
||||
*/
|
||||
int setVariable(QString name, QString value);
|
||||
|
||||
/**
|
||||
Set a condition. This affects tags with the syntax
|
||||
|
||||
- {if name}...{end name}
|
||||
- {if name}...{else name}...{end name}
|
||||
- {ifnot name}...{end name}
|
||||
- {ifnot name}...{else name}...{end name}
|
||||
|
||||
@param name Name of the condition
|
||||
@param value Value of the condition
|
||||
@return The count of conditions that have been processed
|
||||
*/
|
||||
int setCondition(QString name, bool value);
|
||||
|
||||
/**
|
||||
Set number of repetitions of a loop.
|
||||
This affects tags with the syntax
|
||||
|
||||
- {loop name}...{end name}
|
||||
- {loop name}...{else name}...{end name}
|
||||
|
||||
@param name Name of the loop
|
||||
@param repetitions The number of repetitions
|
||||
@return The number of loops that have been processed
|
||||
*/
|
||||
int loop(QString name, int repetitions);
|
||||
|
||||
/**
|
||||
Enable warnings for missing tags
|
||||
@param enable Warnings are enabled, if true
|
||||
*/
|
||||
void enableWarnings(bool enable=true);
|
||||
|
||||
private:
|
||||
|
||||
/** Name of the source file */
|
||||
QString sourceName;
|
||||
|
||||
/** Enables warnings, if true */
|
||||
bool warnings;
|
||||
};
|
||||
|
||||
#endif // TEMPLATE_H
|
@ -0,0 +1,30 @@
|
||||
#include "templatecache.h"
|
||||
#include <QDateTime>
|
||||
#include <QStringList>
|
||||
#include <QSet>
|
||||
|
||||
TemplateCache::TemplateCache(QSettings* settings, QObject* parent)
|
||||
:TemplateLoader(settings,parent)
|
||||
{
|
||||
cache.setMaxCost(settings->value("cacheSize","1000000").toInt());
|
||||
cacheTimeout=settings->value("cacheTime","60000").toInt();
|
||||
qDebug("TemplateCache: timeout=%i, size=%i",cacheTimeout,cache.maxCost());
|
||||
}
|
||||
|
||||
QString TemplateCache::tryFile(QString localizedName) {
|
||||
qint64 now=QDateTime::currentMSecsSinceEpoch();
|
||||
// search in cache
|
||||
qDebug("TemplateCache: trying cached %s",qPrintable(localizedName));
|
||||
CacheEntry* entry=cache.object(localizedName);
|
||||
if (entry && (cacheTimeout==0 || entry->created>now-cacheTimeout)) {
|
||||
return entry->document;
|
||||
}
|
||||
// search on filesystem
|
||||
entry=new CacheEntry();
|
||||
entry->created=now;
|
||||
entry->document=TemplateLoader::tryFile(localizedName);
|
||||
// Store in cache even when the file did not exist, to remember that there is no such file
|
||||
cache.insert(localizedName,entry,entry->document.size());
|
||||
return entry->document;
|
||||
}
|
||||
|
77
YACReaderLibrary/server/lib/bfTemplateEngine/templatecache.h
Normal file
77
YACReaderLibrary/server/lib/bfTemplateEngine/templatecache.h
Normal file
@ -0,0 +1,77 @@
|
||||
#ifndef TEMPLATECACHE_H
|
||||
#define TEMPLATECACHE_H
|
||||
|
||||
#include "templateloader.h"
|
||||
#include <QCache>
|
||||
|
||||
/**
|
||||
Caching template loader, reduces the amount of I/O and improves performance
|
||||
on remote file systems. The cache has a limited size, it prefers to keep
|
||||
the last recently used files. Optionally, the maximum time of cached entries
|
||||
can be defined to enforce a reload of the template file after a while.
|
||||
<p>
|
||||
In case of local file system, the use of this cache is optionally, since
|
||||
the operating system caches files already.
|
||||
<p>
|
||||
Loads localized versions of template files. If the caller requests a file with the
|
||||
name "index" and the suffix is ".tpl" and the requested locale is "de_DE, de, en-US",
|
||||
then files are searched in the following order:
|
||||
|
||||
- index-de_DE.tpl
|
||||
- index-de.tpl
|
||||
- index-en_US.tpl
|
||||
- index-en.tpl
|
||||
- index.tpl
|
||||
<p>
|
||||
The following settings are required:
|
||||
<code><pre>
|
||||
path=.
|
||||
suffix=.tpl
|
||||
encoding=UTF-8
|
||||
cacheSize=1000000
|
||||
cacheTime=60000
|
||||
</pre></code>
|
||||
The path is relative to the directory of the config file. In case of windows, if the
|
||||
settings are in the registry, the path is relative to the current working directory.
|
||||
<p>
|
||||
Files are cached as long as possible, when cacheTime=0.
|
||||
@see TemplateLoader
|
||||
*/
|
||||
|
||||
class TemplateCache : public TemplateLoader {
|
||||
Q_OBJECT
|
||||
Q_DISABLE_COPY(TemplateCache);
|
||||
public:
|
||||
|
||||
/**
|
||||
Constructor.
|
||||
@param settings configurations settings
|
||||
@param parent Parent object
|
||||
*/
|
||||
TemplateCache(QSettings* settings, QObject* parent=0);
|
||||
|
||||
protected:
|
||||
|
||||
/**
|
||||
Try to get a file from cache or filesystem.
|
||||
@param localizedName Name of the template with locale to find
|
||||
@return The template document, or empty string if not found
|
||||
*/
|
||||
virtual QString tryFile(QString localizedName);
|
||||
|
||||
private:
|
||||
|
||||
struct CacheEntry {
|
||||
QString document;
|
||||
qint64 created;
|
||||
};
|
||||
|
||||
/** Timeout for each cached file */
|
||||
int cacheTimeout;
|
||||
|
||||
/** Cache storage */
|
||||
QCache<QString,CacheEntry> cache;
|
||||
|
||||
};
|
||||
|
||||
#endif // TEMPLATECACHE_H
|
103
YACReaderLibrary/server/lib/bfTemplateEngine/templateloader.cpp
Normal file
103
YACReaderLibrary/server/lib/bfTemplateEngine/templateloader.cpp
Normal file
@ -0,0 +1,103 @@
|
||||
/**
|
||||
@file
|
||||
@author Stefan Frings
|
||||
*/
|
||||
|
||||
#include "templateloader.h"
|
||||
#include <QFile>
|
||||
#include <QFileInfo>
|
||||
#include <QStringList>
|
||||
#include <QDir>
|
||||
#include <QSet>
|
||||
|
||||
TemplateLoader::TemplateLoader(QSettings* settings, QObject* parent)
|
||||
: QObject(parent)
|
||||
{
|
||||
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)
|
||||
#else
|
||||
if (QDir::isRelativePath(templatePath))
|
||||
#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()) {
|
||||
textCodec=QTextCodec::codecForLocale();
|
||||
}
|
||||
else {
|
||||
textCodec=QTextCodec::codecForName(encoding.toAscii());
|
||||
}
|
||||
qDebug("TemplateLoader: path=%s, codec=%s",qPrintable(templatePath),textCodec->name().data());
|
||||
}
|
||||
|
||||
TemplateLoader::~TemplateLoader() {}
|
||||
|
||||
QString TemplateLoader::tryFile(QString localizedName) {
|
||||
QString fileName=templatePath+"/"+localizedName+fileNameSuffix;
|
||||
qDebug("TemplateCache: trying file %s",qPrintable(fileName));
|
||||
QFile file(fileName);
|
||||
if (file.exists()) {
|
||||
file.open(QIODevice::ReadOnly);
|
||||
QString document=textCodec->toUnicode(file.readAll());
|
||||
file.close();
|
||||
if (file.error()) {
|
||||
qCritical("TemplateLoader: cannot load file %s, %s",qPrintable(fileName),qPrintable(file.errorString()));
|
||||
return "";
|
||||
}
|
||||
else {
|
||||
return document;
|
||||
}
|
||||
}
|
||||
return "";
|
||||
}
|
||||
|
||||
Template TemplateLoader::getTemplate(QString templateName, QString locales) {
|
||||
mutex.lock();
|
||||
QSet<QString> tried; // used to suppress duplicate attempts
|
||||
QStringList locs=locales.split(',',QString::SkipEmptyParts);
|
||||
|
||||
// Search for exact match
|
||||
foreach (QString loc,locs) {
|
||||
loc.replace(QRegExp(";.*"),"");
|
||||
loc.replace('-','_');
|
||||
QString localizedName=templateName+"-"+loc.trimmed();
|
||||
if (!tried.contains(localizedName)) {
|
||||
QString document=tryFile(localizedName);
|
||||
if (!document.isEmpty()) {
|
||||
mutex.unlock();
|
||||
return Template(document,localizedName);
|
||||
}
|
||||
tried.insert(localizedName);
|
||||
}
|
||||
}
|
||||
|
||||
// Search for correct language but any country
|
||||
foreach (QString loc,locs) {
|
||||
loc.replace(QRegExp("[;_-].*"),"");
|
||||
QString localizedName=templateName+"-"+loc.trimmed();
|
||||
if (!tried.contains(localizedName)) {
|
||||
QString document=tryFile(localizedName);
|
||||
if (!document.isEmpty()) {
|
||||
mutex.unlock();
|
||||
return Template(document,localizedName);
|
||||
}
|
||||
tried.insert(localizedName);
|
||||
}
|
||||
}
|
||||
|
||||
// Search for default file
|
||||
QString document=tryFile(templateName);
|
||||
if (!document.isEmpty()) {
|
||||
mutex.unlock();
|
||||
return Template(document,templateName);
|
||||
}
|
||||
|
||||
qCritical("TemplateCache: cannot find template %s",qPrintable(templateName));
|
||||
mutex.unlock();
|
||||
return Template("",templateName);
|
||||
}
|
@ -0,0 +1,85 @@
|
||||
/**
|
||||
@file
|
||||
@author Stefan Frings
|
||||
*/
|
||||
|
||||
#ifndef TEMPLATELOADER_H
|
||||
#define TEMPLATELOADER_H
|
||||
|
||||
#include <QString>
|
||||
#include <QSettings>
|
||||
#include <QTextCodec>
|
||||
#include "template.h"
|
||||
#include <QMutex>
|
||||
|
||||
/**
|
||||
Loads localized versions of template files. If the caller requests a file with the
|
||||
name "index" and the suffix is ".tpl" and the requested locale is "de_DE, de, en-US",
|
||||
then files are searched in the following order:
|
||||
|
||||
- index-de_DE.tpl
|
||||
- index-de.tpl
|
||||
- index-en_US.tpl
|
||||
- index-en.tpl
|
||||
- index.tpl
|
||||
|
||||
The following settings are required:
|
||||
<code><pre>
|
||||
path=.
|
||||
suffix=.tpl
|
||||
encoding=UTF-8
|
||||
</pre></code>
|
||||
The path is relative to the directory of the config file. In case of windows, if the
|
||||
settings are in the registry, the path is relative to the current working directory.
|
||||
@see TemplateCache
|
||||
*/
|
||||
|
||||
class TemplateLoader : public QObject {
|
||||
Q_OBJECT
|
||||
Q_DISABLE_COPY(TemplateLoader);
|
||||
public:
|
||||
|
||||
/**
|
||||
Constructor.
|
||||
@param settings configurations settings
|
||||
@param parent parent object
|
||||
*/
|
||||
TemplateLoader(QSettings* settings, QObject* parent=0);
|
||||
|
||||
/** Destructor */
|
||||
virtual ~TemplateLoader();
|
||||
|
||||
/**
|
||||
Get a template for a given locale.
|
||||
This method is thread safe.
|
||||
@param templateName base name of the template file, without suffix and without locale
|
||||
@param locales Requested locale(s), e.g. "de_DE, en_EN". Strings in the format of
|
||||
the HTTP header Accept-Locale may be used. Badly formatted parts in the string are silently
|
||||
ignored.
|
||||
@return If the template cannot be loaded, an error message is logged and an empty template is returned.
|
||||
*/
|
||||
Template getTemplate(QString templateName, QString locales=QString());
|
||||
|
||||
protected:
|
||||
|
||||
/**
|
||||
Try to get a file from cache or filesystem.
|
||||
@param localizedName Name of the template with locale to find
|
||||
@return The template document, or empty string if not found
|
||||
*/
|
||||
virtual QString tryFile(QString localizedName);
|
||||
|
||||
/** Directory where the templates are searched */
|
||||
QString templatePath;
|
||||
|
||||
/** Suffix to the filenames */
|
||||
QString fileNameSuffix;
|
||||
|
||||
/** Codec for decoding the files */
|
||||
QTextCodec* textCodec;
|
||||
|
||||
/** Used to synchronize threads */
|
||||
QMutex mutex;
|
||||
};
|
||||
|
||||
#endif // TEMPLATELOADER_H
|
58
YACReaderLibrary/server/main.cpp
Normal file
58
YACReaderLibrary/server/main.cpp
Normal file
@ -0,0 +1,58 @@
|
||||
/**
|
||||
@file
|
||||
@author Stefan Frings
|
||||
*/
|
||||
|
||||
/**
|
||||
@mainpage
|
||||
This project provies libraries for writing server-side web application in C++
|
||||
based on the Qt toolkit. It is a light-weight implementation that works like
|
||||
Java Servlets from the programmers point of view.
|
||||
<p>
|
||||
Features:
|
||||
|
||||
- HTTP 1.1 web server
|
||||
- persistent connections
|
||||
- chunked and non-chunked transfer
|
||||
- file uploads (multipart encoded forms)
|
||||
- cookies
|
||||
- sessions
|
||||
- uses dynamic thread pool
|
||||
- optional file cache
|
||||
- Template engine
|
||||
- multi-language
|
||||
- optional file cache
|
||||
- Logger
|
||||
- configurable without program restart
|
||||
- automatic backup and file rotation
|
||||
- configurable message format
|
||||
- messages may contain thread-local variables
|
||||
- optional buffer for writing history details in case of an error
|
||||
- Example application
|
||||
- Install and run as Windows service, unix daemon or at the command-line
|
||||
- Search config file in multiple common directories
|
||||
- Demonstrates how to write servlets for common use-cases
|
||||
|
||||
If you write a real application based on this source, take a look into startup.cpp,
|
||||
which contains startup and shutdown procedures. The example source sets
|
||||
up a single listener on port 8080, however multiple listeners with individual
|
||||
configuration could be set up.
|
||||
<p>
|
||||
Incoming requests are mapped to controller classes in requestmapper.cpp, based on the
|
||||
requested path. If you want to learn form the example, then focus on these classes.
|
||||
<p>
|
||||
High-availability and HTTPS encryption can be easily added by putting an Apache HTTP server
|
||||
in front of the self-written web application using the mod-proxy module with sticky sessions.
|
||||
*/
|
||||
|
||||
#include "startup.h"
|
||||
|
||||
/**
|
||||
Entry point of the program.
|
||||
Passes control to the service helper.
|
||||
*/
|
||||
int main(int argc, char *argv[]) {
|
||||
// Use a qtservice wrapper to start the application as a Windows service or Unix daemon
|
||||
Startup startup(argc, argv);
|
||||
return startup.exec();
|
||||
}
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user