+2017-10-01 gregor herrmann <gregor@toastfreeware.priv.at>
+
+ update NEWS before release of version 0.6.3
+
+2017-09-27 Philipp Spitzer <philipp@spitzer.priv.at>
+
+ Now the links in the description are clickable. Fixes #49.
+
+ The ID of an event is checked now when importing the XML file.
+
+ Now using exceptions to report errors in schedulexmlparser.cpp.
+
+ Create dedicated sub-function to parse XML data (to prepare exception error reporting).
+
+ Use TransactionRaii in schedulexmlparser.cpp.
+
+ Implement transaction RAII.
+
+ Implement rollbackTransaction().
+
+2017-09-13 Philipp Spitzer <philipp@spitzer.priv.at>
+
+ If no day_change was given for a conference 4 AM is assumed.
+ Fixes #53.
+
+ Import schedules with dates attached to events correctly.
+
+ Avoid duplicate code when inserting/updating a conference.
+
+2017-09-13 gregor herrmann <gregor@toastfreeware.priv.at>
+
+ URL input dialog: trim URL.
+
+2017-08-30 Philipp Spitzer <philipp@spitzer.priv.at>
+
+ Minor typographic improvement.
+
+ "Open" button is disabled not when no URL was entered.
+
+2017-08-30 gregor herrmann <gregor@toastfreeware.priv.at>
+
+ Mention new location of database file under Qt5 in docs.
+
+ ifdef qt4 and qt5
+
+ Merge branch 'master' into qt5
+
+2017-08-30 Philipp Spitzer <philipp@spitzer.priv.at>
+
+ When no track is present, use the special name "No track" is used. This fixes issue #56.
+
+2017-08-30 Martín Ferrari <tincho@tincho.org>
+
+ Fix possibility for SQL injection attack.
+
+ Demangling exception class name from error message of unknown exceptions.
+
+ Write debug message in case of silently catched exceptions.
+
+ More specific error message for "unknown" exceptions.
+
+2017-08-27 gregor herrmann <gregor@toastfreeware.priv.at>
+
+ TrackInsertException: make error message useful.
+
+ TrackInsertException: correctly derive from OrmSqlException.
+
+ Derive OrmException from std::runtime_error.
+
2017-01-24 gregor herrmann <gregoa@debian.org>
+ bump version after release
+
update NEWS before release.
2017-01-23 Philipp Spitzer <philipp@spitzer.priv.at>
eventdialog: only convertFromPlainText description and abstract if they are not richtext.
+ Merge branch 'master' into qt5
+
2015-01-20 gregor herrmann <gregoa@debian.org>
Bump copyright year.
gitlog-to-changelog sums up the commits, whereas git2cl dumps them
individually.
+2015-01-13 Philipp Spitzer <philipp@spitzer.priv.at>
+
+ Merged changes from trunk. It still compiles successfully. :-)
+
2014-09-11 gregor herrmann <gregoa@debian.org>
Make release target depend on distclean target to ensure we have no compiled objects or Makefiles in the release tarball.
2013-09-24 Philipp Spitzer <philipp@spitzer.priv.at>
+ Now the application compiles for QT5.
+ Note that the location of the database in Linux has changed from
+ ~/.local/share/data/Toastfreeware/ConfClerk
+ to
+ ~/.local/share/Toastfreeware/ConfClerk
+
Fixed a yet unknown bug: The room name was not properly inserted in the room table.
2013-09-10 Philipp Spitzer <philipp@spitzer.priv.at>
This is the NEWS file for ConfClerk. ConfClerk is the successor of
fosdem-schedule; cf. docs/fosdem-schedule for the historic documentation.
+version 0.6.3, 2017-10-01
+* Support Qt5 and Qt4.
+ (Fixes: #57)
+ Mention new location of database file under Qt5 in docs.
+* Fix display of events after midnight.
+ (Fixes: #53)
+* Fix import failures when the conference has no tracks in the XML file.
+ Thanks to Martín Ferrari for the bug report and the help with the fix.
+ (Fixes: #56)
+* Bail out on import when conference XML has empty or ill-formed
+ event IDs.
+ Thanks to Paul Wise for the bug report.
+ (Fixes: #58)
+* Make URLs in Abstract/Description of event detail view clickable.
+ (Fixes: #49)
+* Improve exception handling and error messages.
+ Thanks to Martín Ferrari for his help.
+* Improve import dialog:
+ - enable "Open" button only when a URL is entered
+ - trim URL
+
version 0.6.2, 2017-01-24
* Event dialog: don't unconditionally assume plain text for description and
abstract, can be rich text as well (i.e. contain HTML tags).
QMAKEVERSION = $$[QMAKE_VERSION]
ISQT4 = $$find(QMAKEVERSION, ^[2-9])
isEmpty( ISQT4 ) {
- error("Use the qmake include with Qt4.4 or greater, on Debian that is qmake-qt4");
+ error("Use the qmake include with Qt4.7 or greater, on Debian that is qmake-qt4");
}
TEMPLATE = subdirs
-.\" Automatically generated by Pod::Man 4.07 (Pod::Simple 3.32)
+.\" Automatically generated by Pod::Man 4.09 (Pod::Simple 3.35)
.\"
.\" Standard preamble:
.\" ========================================================================
.\" ========================================================================
.\"
.IX Title "CONFCLERK 1"
-.TH CONFCLERK 1 "2017-01-22" "Version 0.6.2" "Offline conference scheduler"
+.TH CONFCLERK 1 "2017-08-30" "Version 0.6.3" "Offline conference scheduler"
.\" For nroff, turn off justification. Always turn off hyphenation; it makes
.\" way too many mistakes in technical documents.
.if n .ad l
.PP
So the configuration (see \*(L"\s-1CONFIGURATION\*(R"\s0) is stored at
\&\fI~/.config/Toastfreeware/ConfClerk.conf\fR and the database is kept at
-\&\fI~/.local/share/data/Toastfreeware/ConfClerk/ConfClerk.sqlite\fR.
+\&\fI~/.local/share/data/Toastfreeware/ConfClerk/ConfClerk.sqlite\fR (Qt4)
+\&\fI~/.local/share/Toastfreeware/ConfClerk/ConfClerk.sqlite\fR (Qt5).
.SH "COPYRIGHT AND LICENSE"
.IX Header "COPYRIGHT AND LICENSE"
.SS "Main code"
So the configuration (see L</"CONFIGURATION">) is stored at
F<~/.config/Toastfreeware/ConfClerk.conf> and the database is kept at
-F<~/.local/share/data/Toastfreeware/ConfClerk/ConfClerk.sqlite>.
+F<~/.local/share/data/Toastfreeware/ConfClerk/ConfClerk.sqlite> (Qt4)
+F<~/.local/share/Toastfreeware/ConfClerk/ConfClerk.sqlite> (Qt5).
=head1 COPYRIGHT AND LICENSE
TARGET = confclerk
DESTDIR = ../bin
QT += sql xml network
+greaterThan(QT_MAJOR_VERSION, 4): QT += widgets
CONFIG(maemo5) {
QT += maemo5
}
* You should have received a copy of the GNU General Public License along with
* ConfClerk. If not, see <http://www.gnu.org/licenses/>.
*/
+#if defined(__GNUC__) || defined(__llvm__) || defined(__clang__)
+#include <cxxabi.h>
+#endif
+#include <exception>
+
#include "application.h"
#include "errormessage.h"
} catch (OrmException& e) {
error_message("UNCAUGHT OrmException: " + e.text());
return false;
+ } catch (std::exception& e) {
+ error_message("UNCAUGHT exception: " + QString(e.what()));
+ return false;
} catch (...) {
- error_message("UNCAUGHT EXCEPTION: unknown");
+#if defined(__GNUC__) || defined(__llvm__) || defined(__clang__)
+ int status = 0;
+ char *buff = __cxxabiv1::__cxa_demangle(
+ __cxxabiv1::__cxa_current_exception_type()->name(),
+ NULL, NULL, &status);
+ QString exception_name = QString(buff);
+ std::free(buff);
+#else
+ QString exception_name = QString("unknown");
+#endif
+ error_message("UNCAUGHT exception: " + exception_name);
return false;
}
}
# USAGE: include(./global.pri)
# VERSION
-VERSION = 0.6.2
+VERSION = 0.6.3
DEFINES += VERSION=\\\"$$VERSION\\\"
# Define 'MAEMO' specific CONFIG/DEFINE
#include "appsettings.h"
#endif
+QString toHtmlEscaped(const QString& string) {
+#if QT_VERSION >= 0x050000
+ return string.toHtmlEscaped();
+#else
+ return Qt::escape(string);
+#endif
+}
+
EventDialog::EventDialog(int conferenceId, int eventId, QWidget *parent): QDialog(parent), mConferenceId(conferenceId), mEventId(eventId) {
setupUi(this);
QString info;
// title
- info.append(QString("<h1>%1</h1>\n").arg(Qt::escape(event.title())));
+ info.append(QString("<h1>%1</h1>\n").arg(toHtmlEscaped(event.title())));
// persons
info += QString("<h2>%1</h2>\n").arg(tr("Persons"));
QStringList persons = event.persons();
- for (int i = 0; i != persons.size(); ++i) persons[i] = Qt::escape(persons[i]);
+ for (int i = 0; i != persons.size(); ++i) persons[i] = toHtmlEscaped(persons[i]);
info += QString("<p>%1</p>\n").arg(persons.join(", "));
// abstract
if (Qt::mightBeRichText(event.description())) {
info += event.description();
} else {
- info += Qt::convertFromPlainText(event.description(), Qt::WhiteSpaceNormal);
+ QString description = Qt::convertFromPlainText(event.description(), Qt::WhiteSpaceNormal);
+ // make links clickable
+ QRegExp rx("<?(((s?ftp|https?|svn|svn\\+ssh|git|git\\+ssh)://|(file|news):|www\\.)[-a-z0-9_.:%]*[a-z0-9](/[^][{}\\s\"<>()]*[^][{}\\s\"<>().,:!])?/?)>?");
+ info += description.replace(rx, "<a href=\"\\1\">\\1</a>");
}
// links
QString name(i.key());
if (url.isEmpty() || url == "http://") continue;
if (name.isEmpty()) name = url;
- info += QString("<li><a href=\"%1\">%2</a></li>\n").arg(Qt::escape(url), Qt::escape(name));
+ info += QString("<li><a href=\"%1\">%2</a></li>\n").arg(toHtmlEscaped(url), toHtmlEscaped(name));
}
info += QString("</ul>\n");
eventInfoTextBrowser->setHtml(info);
QT += sql \
xml \
network
+greaterThan(QT_MAJOR_VERSION, 4): QT += widgets
QMAKE_CLEAN += ../bin/libgui.a
# module dependencies
QT += maemo5
}
+OTHER_FILES += \
+ test.qml
+
// end of optimization
// initTabs();
- } catch (OrmException& e) {
+ } catch (const OrmException& e) {
+ qDebug() << "OrmException:" << e.text();
// cannon set an active conference
unsetConference(); // TODO: as no active conference is now correctly managed this should be handled as a fatal error
return;
// optimization, see useConference() code
try {
initTabs();
- } catch (OrmException) {
+ } catch (const OrmException& e) {
+ qDebug() << "OrmException:" << e.text();
clearTabs();
}
}
#ifndef MAINWINDOW_H
#define MAINWINDOW_H
+#include "qglobal.h"
+#if QT_VERSION >= 0x050000
+#include <QtWidgets>
+#else
#include <QtGui/QMainWindow>
+#endif
#include <QSslError>
#include "ui_mainwindow.h"
#ifndef SEARCHHEAD_H
#define SEARCHHEAD_H
+#include "qglobal.h"
+#if QT_VERSION >= 0x050000
+#include <QtWidgets>
+#else
#include <QtGui/QWidget>
+#endif
#include <QDebug>
#include "ui_searchhead.h"
{
setupUi(this);
- QPushButton* openFile = buttons->addButton("Open File...", QDialogButtonBox::ActionRole);
+ QPushButton* openFile = buttons->addButton(tr("Open file ..."), QDialogButtonBox::ActionRole);
+ textChanged(urlEntry->text());
connect(openFile, SIGNAL(clicked()), SLOT(openFileClicked()));
connect(buttons, SIGNAL(accepted()), SLOT(acceptClicked()));
connect(buttons, SIGNAL(rejected()), SLOT(rejectClicked()));
+ connect(urlEntry, SIGNAL(textChanged(QString)), SLOT(textChanged(QString)));
}
void UrlInputDialog::openFileClicked()
}
}
+void UrlInputDialog::textChanged(const QString& text)
+{
+ buttons->button(QDialogButtonBox::StandardButton::Open)->setEnabled(!text.isEmpty());
+}
+
void UrlInputDialog::acceptClicked()
{
- saved_result = urlEntry->text();
+ saved_result = urlEntry->text().trimmed();
setResult(HaveUrl);
}
void acceptClicked();
void rejectClicked();
void openFileClicked();
+ void textChanged(const QString& text);
private:
QString saved_result;
};
// reinitialize list from database
void reinit()
{
+ beginResetModel();
conferences = Conference::getAll();
- reset();
+ endResetModel();
}
QList<Conference> conferences;
option.rect.x()+SPACER,
option.rect.y()+option.rect.height()-fmBig.descent());
painter->setFont(fontBig);
- painter->drawText(titlePointF,qVariantValue<QString>(index.data()));
+ painter->drawText(titlePointF,index.data().value<QString>());
}
painter->restore();
#ifndef DELEGATE_H
#define DELEGATE_H
+#include "qglobal.h"
+#if QT_VERSION >= 0x050000
+#include <QtWidgets>
+#else
#include <QtGui>
+#endif
class Delegate : public QItemDelegate
{
// multiple of one hour.
void EventModel::createTimeGroups()
{
+ beginResetModel();
+
mGroups.clear();
mParents.clear();
if (mEvents.empty()) return;
// the last group needs a title as well
mGroups.last().setTitle(mEvents);
- reset();
+ endResetModel();
}
void EventModel::createTrackGroups() {
if (!parent.isValid())
{
- return createIndex(row, column, 0);
+ return createIndex(row, column);
}
else if (parent.internalId() == 0)
{
Event * event = static_cast<Event*>(index.internalPointer());
- return createIndex(mParents[event->id()], 0, 0);
+ return createIndex(mParents[event->id()], 0);
}
return QModelIndex();
void EventModel::clearModel()
{
+ beginResetModel();
mGroups.clear();
mEvents.clear();
mParents.clear();
-
- reset();
+ endResetModel();
}
TARGET = mvc
DESTDIR = ../bin
CONFIG += static
-QT += sql
+QT += sql
+greaterThan(QT_MAJOR_VERSION, 4): QT += widgets
QMAKE_CLEAN += ../bin/libmvc.a
# module dependencies
<< QSqlField(CONFERENCEID, QVariant::Int)
<< QSqlField(NAME, QVariant::String));
-class TrackInsertException : OrmSqlException
+class TrackInsertException : public OrmSqlException
{
public:
TrackInsertException(const QString& text) : OrmSqlException(text) {}
int Track::insert()
{
QSqlQuery query;
- query.prepare("INSERT INTO " + sTableName + " (" + CONFERENCEID + "," + NAME + ")" + " VALUES " + "(\"" + QString::number(conferenceid()) + "\",\"" + name() + "\")");
+ QString trackname = name();
+ query.prepare(
+ QString("INSERT INTO %1 (%2, %3) VALUES (:xid_conference, :name)")
+ .arg(sTableName, CONFERENCEID, NAME));
+ query.bindValue(":xid_conference", conferenceid());
+ query.bindValue(":name", trackname);
if (!query.exec())
{
- throw TrackInsertException("Exec Error");
+ throw TrackInsertException(
+ "Inserting track '" + trackname + "' into database failed: " +
+ query.lastError().text());
}
QVariant variant = query.lastInsertId();
if (variant.isValid())
#include <QStringList>
#include <QDateTime>
#include <QDebug>
+#include <stdexcept>
-class OrmException
+class OrmException : public std::runtime_error
{
public:
- OrmException(const QString& text) : mText(text) {}
+ OrmException(const QString& text) : std::runtime_error(text.toStdString()), mText(text) {}
virtual ~OrmException(){}
virtual const QString& text() const { return mText; }
private:
class OrmNoObjectException : public OrmException
{
public:
- OrmNoObjectException() : OrmException("No object exception"){}
+ OrmNoObjectException() : OrmException("SQL query expects one record but found none."){}
~OrmNoObjectException(){}
};
#include <QDomDocument>
#include <QHash>
+#include <QTime>
#include "schedulexmlparser.h"
#include "sqlengine.h"
}
-void ScheduleXmlParser::parseData(const QByteArray &aData, const QString& url, int conferenceId)
-{
+class ParseException: public std::runtime_error {
+public:
+ ParseException(const QString& message): std::runtime_error(message.toStdString()) {}
+};
+
+
+void checkEvent(QHash<QString,QString>& event) {
+ QString event_id = event["id"];
+ if (event_id.trimmed().isEmpty()) throw ParseException(QObject::tr("The ID of event '%1' is missing.").arg(event["title"]));
+ bool ok;
+ event_id.toInt(&ok);
+ if (!ok) throw ParseException(QObject::tr("The ID '%2' of event '%1' is not numeric.").arg(event["title"]).arg(event_id));
+}
+
+
+void ScheduleXmlParser::parseDataImpl(const QByteArray &aData, const QString& url, int conferenceId) {
QDomDocument document;
QString xml_error;
int xml_error_line;
int xml_error_column;
- if (!document.setContent (aData, false, &xml_error, &xml_error_line, &xml_error_column)) {
- error_message("Could not parse schedule: " + xml_error + " at line " + QString("%1").arg(xml_error_line) + " column " + QString("%1").arg(xml_error_column));
- return;
+ if (!document.setContent(aData, false, &xml_error, &xml_error_line, &xml_error_column)) {
+ throw ParseException("Could not parse schedule: " + xml_error + " at line " + QString("%1").arg(xml_error_line) + " column " + QString("%1").arg(xml_error_column));
}
QDomElement scheduleElement = document.firstChildElement("schedule");
- sqlEngine->beginTransaction();
+ TransactionRaii transaction(*sqlEngine); // begins the transaction
QString conference_title;
if (!scheduleElement.isNull())
{
QDomElement conferenceElement = scheduleElement.firstChildElement("conference");
+ QTime conference_day_change;
if (!conferenceElement.isNull())
{
emit(parsingScheduleBegin());
conference["city"] = conferenceElement.firstChildElement("city").text();
conference["start"] = conferenceElement.firstChildElement("start").text(); // date
conference["end"] = conferenceElement.firstChildElement("end").text(); // date
- conference["day_change"] = conferenceElement.firstChildElement("day_change").text(); // time
+ QString conferenceDayChangeStr = conferenceElement.firstChildElement("day_change").text(); // time, e.g. "04:00:00"
+ if (conferenceDayChangeStr.isEmpty()) conferenceDayChangeStr = "04:00:00";
+ conference["day_change"] = conferenceDayChangeStr;
conference["timeslot_duration"] = conferenceElement.firstChildElement("timeslot_duration").text(); // time
conference["url"] = url;
sqlEngine->addConferenceToDB(conference, conferenceId);
conferenceId = conference["id"].toInt();
conference_title = conference["title"];
+ conference_day_change = QTime(0, 0).addSecs(conference["day_change"].toInt());
}
// we need to get count of all events in order to emit 'progressStatus' signal
// process event's nodes
QHash<QString,QString> event;
- event["id"] = eventElement.attribute("id");;
+ event["id"] = eventElement.attribute("id");
event["conference_id"] = QString::number(conferenceId, 10);
- event["start"] = eventElement.firstChildElement("start").text(); // time eg. 10:00
- event["date"] = dayElement.attribute("date"); // date eg. 2009-02-07
+ QTime event_start = QTime::fromString(eventElement.firstChildElement("start").text(), sqlEngine->TIME_FORMAT);
+ event["start"] = event_start.toString(sqlEngine->TIME_FORMAT); // time eg. 10:00
+ QDate event_date;
+ QDomElement eventDateElement = eventElement.firstChildElement("date");
+ if (!eventDateElement.isNull()) {
+ QString date_str = eventDateElement.text(); // date eg. 2009-02-07T10:00:00+00:00
+ event_date = QDate::fromString(date_str.left(sqlEngine->DATE_FORMAT.size()), sqlEngine->DATE_FORMAT);
+ } else {
+ event_date = QDate::fromString(dayElement.attribute("date"),sqlEngine->DATE_FORMAT); // date eg. 2009-02-07
+ if (event_start < conference_day_change) event_date = event_date.addDays(1);
+ }
+ event["date"] = event_date.toString(sqlEngine->DATE_FORMAT); // date eg. 2009-02-07
event["duration"] = eventElement.firstChildElement("duration").text(); // time eg. 00:30
event["room_name"] = eventElement.firstChildElement("room").text(); // string eg. "Janson"
event["tag"] = eventElement.firstChildElement("tag").text(); // string eg. "welcome"
event["language"] = eventElement.firstChildElement("language").text(); // language eg. "English"
event["abstract"] = eventElement.firstChildElement("abstract").text(); // string
event["description"] = eventElement.firstChildElement("description").text(); // string
+ checkEvent(event);
sqlEngine->addEventToDB(event);
// process persons' nodes
QDomElement personsElement = eventElement.firstChildElement("persons");
} // parsing room elements
} // parsing day elements
} // schedule element
- sqlEngine->commitTransaction();
- if (!conference_title.isNull()) {
- emit parsingScheduleEnd(conferenceId);
- } else {
- error_message("Could not parse schedule");
+ if (conference_title.isNull()) throw ParseException("Could not parse schedule");
+
+ transaction.commit();
+ emit parsingScheduleEnd(conferenceId);
+}
+
+
+void ScheduleXmlParser::parseData(const QByteArray &aData, const QString& url, int conferenceId) {
+ try {
+ parseDataImpl(aData, url, conferenceId);
+ } catch (ParseException& e) {
+ error_message(e.what());
}
}
Q_OBJECT
private:
SqlEngine* sqlEngine;
+ void parseDataImpl(const QByteArray &aData, const QString& url, int conferenceId);
public:
ScheduleXmlParser(SqlEngine* sqlEngine, QObject *aParent = NULL);
#include <QSqlRecord>
#include <QVariant>
#include <QDateTime>
+#include "qglobal.h"
+#if QT_VERSION >= 0x050000
+#include <QStandardPaths>
+#else
+#include <QDesktopServices>
+#endif
#include <QDir>
-#include <QDesktopServices>
#include "sqlengine.h"
#include "track.h"
#include "conference.h"
#include <QDebug>
-const QString DATE_FORMAT ("yyyy-MM-dd");
-const QString TIME_FORMAT ("hh:mm");
-
SqlEngine::SqlEngine(QObject *aParent): QObject(aParent) {
+#if QT_VERSION >= 0x050000
+ QDir dbPath(QStandardPaths::writableLocation(QStandardPaths::DataLocation));
+#else
QDir dbPath(QDesktopServices::storageLocation(QDesktopServices::DataLocation));
+#endif
dbFilename = dbPath.absoluteFilePath("ConfClerk.sqlite");
}
void SqlEngine::addConferenceToDB(QHash<QString,QString> &aConference, int conferenceId) {
QSqlQuery query(db);
- if (conferenceId <= 0) // insert conference
- {
+ bool insert = conferenceId <= 0;
+ if (insert) { // insert conference
query.prepare("INSERT INTO CONFERENCE (title,url,subtitle,venue,city,start,end,"
"day_change,timeslot_duration,active) "
" VALUES (:title,:url,:subtitle,:venue,:city,:start,:end,"
":day_change,:timeslot_duration,:active)");
- foreach (QString prop_name, (QList<QString>() << "title" << "url" << "subtitle" << "venue" << "city")) {
- query.bindValue(QString(":") + prop_name, aConference[prop_name]);
- }
- query.bindValue(":start", QDateTime(QDate::fromString(aConference["start"],DATE_FORMAT),QTime(0,0),Qt::UTC).toTime_t());
- query.bindValue(":end", QDateTime(QDate::fromString(aConference["end"],DATE_FORMAT),QTime(0,0),Qt::UTC).toTime_t());
- query.bindValue(":day_change", -QTime::fromString(aConference["day_change"],TIME_FORMAT).secsTo(QTime(0,0)));
- query.bindValue(":timeslot_duration", -QTime::fromString(aConference["timeslot_duration"],TIME_FORMAT).secsTo(QTime(0,0)));
- query.bindValue(":active", 1);
- query.exec();
- emitSqlQueryError(query);
- aConference["id"] = query.lastInsertId().toString(); // 'id' is assigned automatically
- }
- else // update conference
- {
+ } else { // update conference
query.prepare("UPDATE CONFERENCE set title=:title, url=:url, subtitle=:subtitle, venue=:venue, city=:city, start=:start, end=:end,"
"day_change=:day_change, timeslot_duration=:timeslot_duration, active=:active "
"WHERE id=:id");
- foreach (QString prop_name, (QList<QString>() << "title" << "url" << "subtitle" << "venue" << "city")) {
- query.bindValue(QString(":") + prop_name, aConference[prop_name]);
- }
- query.bindValue(":start", QDateTime(QDate::fromString(aConference["start"],DATE_FORMAT),QTime(0,0),Qt::UTC).toTime_t());
- query.bindValue(":end", QDateTime(QDate::fromString(aConference["end"],DATE_FORMAT),QTime(0,0),Qt::UTC).toTime_t());
- query.bindValue(":day_change", -QTime::fromString(aConference["day_change"],TIME_FORMAT).secsTo(QTime(0,0)));
- query.bindValue(":timeslot_duration", -QTime::fromString(aConference["timeslot_duration"],TIME_FORMAT).secsTo(QTime(0,0)));
- query.bindValue(":active", 1);
- query.bindValue(":id", conferenceId);
- query.exec();
- emitSqlQueryError(query);
+ }
+ foreach (QString prop_name, (QList<QString>() << "title" << "url" << "subtitle" << "venue" << "city")) {
+ query.bindValue(QString(":") + prop_name, aConference[prop_name]);
+ }
+ query.bindValue(":start", QDateTime(QDate::fromString(aConference["start"],DATE_FORMAT),QTime(0,0),Qt::UTC).toTime_t());
+ query.bindValue(":end", QDateTime(QDate::fromString(aConference["end"],DATE_FORMAT),QTime(0,0),Qt::UTC).toTime_t());
+ QTime dayChange = QTime::fromString(aConference["day_change"].left(TIME_FORMAT.size()), TIME_FORMAT);
+ query.bindValue(":day_change", QTime(0, 0).secsTo(dayChange));
+ query.bindValue(":timeslot_duration", -QTime::fromString(aConference["timeslot_duration"],TIME_FORMAT).secsTo(QTime(0,0)));
+ query.bindValue(":active", 1);
+ if (!insert) query.bindValue(":id", conferenceId);
+ query.exec();
+ emitSqlQueryError(query);
+ if (insert) {
+ aConference["id"] = query.lastInsertId().toString(); // 'id' is assigned automatically
+ } else {
aConference["id"] = QVariant(conferenceId).toString();
}
}
Track track;
int trackId;
QString trackName = aEvent["track"];
+ if (trackName.isEmpty()) trackName = tr("No track");
try
{
track = Track::retrieveByName(conferenceId, trackName);
}
QDate startDate = QDate::fromString(aEvent["date"], DATE_FORMAT);
QTime startTime = QTime::fromString(aEvent["start"], TIME_FORMAT);
- // consider day_change (note that if day_change is e.g. at 04:00 AM, an event starting at 02:00 AM has the previous date in the XML file)
- if (startTime < conference.dayChangeTime()) startDate = startDate.addDays(1);
QDateTime startDateTime;
startDateTime.setTimeSpec(Qt::UTC);
startDateTime = QDateTime(startDate, startTime, Qt::UTC);
}
+bool SqlEngine::rollbackTransaction() {
+ QSqlQuery query(db);
+ bool success = query.exec("ROLLBACK");
+ emitSqlQueryError(query);
+ return success;
+}
+
+
bool SqlEngine::deleteConference(int id) {
QSqlQuery query(db);
bool success = query.exec("BEGIN IMMEDIATE TRANSACTION");
if (error.type() == QSqlError::NoError) return;
emit dbError(error.text());
}
-
class SqlEngine : public QObject {
Q_OBJECT
public:
+ const QString DATE_FORMAT = "yyyy-MM-dd";
+ const QString TIME_FORMAT = "hh:mm";
+
QString dbFilename; ///< database filename including path
QSqlDatabase db; ///< this may be private one day...
bool beginTransaction();
bool commitTransaction();
+ bool rollbackTransaction();
/// search Events for .... returns true if success
bool searchEvent(int conferenceId, const QHash<QString,QString> &columns, const QString &keyword);
void dbError(const QString& message);
};
+
+class TransactionRaii {
+ SqlEngine& sqlEngine;
+ bool committed = false;
+public:
+ TransactionRaii(SqlEngine& sqlEngine): sqlEngine(sqlEngine) {
+ sqlEngine.beginTransaction();
+ }
+
+ void commit() {
+ sqlEngine.commitTransaction();
+ committed = true;
+ }
+
+ ~TransactionRaii() {
+ if (!committed) sqlEngine.rollbackTransaction();
+ }
+};
+
#endif /* SQLENGINE_H */