summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--board.cpp96
-rw-r--r--board.h18
-rw-r--r--fetchboardconfigaction.cpp10
-rw-r--r--fetchpostsaction.cpp7
-rw-r--r--fetchpostsaction.h1
-rw-r--r--fetchtopicsaction.cpp5
-rw-r--r--main.cpp18
-rw-r--r--qml/tapasboard/TopicPage.qml31
-rw-r--r--topicmodel.cpp38
-rw-r--r--topicmodel.h11
10 files changed, 190 insertions, 45 deletions
diff --git a/board.cpp b/board.cpp
index 4883ebc..3acf03b 100644
--- a/board.cpp
+++ b/board.cpp
@@ -1,5 +1,4 @@
#include <QtCore/QRegExp>
-#include <QtCore/QDateTime>
#include <QtCore/QDir>
#include <QtCore/QDebug>
#include <QtSql/QSqlQuery>
@@ -12,6 +11,8 @@
#include "xmlrpcinterface.h"
#include "board.h"
+const QLatin1String Board::CURRENT_DB_VERSION("testing2");
+
Board::Board(const QString& forumUrl, QObject *parent) :
QObject(parent), _url(forumUrl), _slug(createSlug(forumUrl)),
_db(QSqlDatabase::addDatabase("QSQLITE", _slug)),
@@ -22,10 +23,21 @@ Board::Board(const QString& forumUrl, QObject *parent) :
if (!_db.open()) {
qWarning() << "Could not open database file" << _db.databaseName() << ":"
<< _db.lastError().text();
+ _db.setDatabaseName(QDir::toNativeSeparators(getTempDbPathFor(_slug)));
+ if (!_db.open()) {
+ qWarning() << "Could not open temp database file"
+ << _db.databaseName() << ":" << _db.lastError().text();
+ return; // Give up
+ }
+ }
+ if (!checkCompatibleDb()) {
+ qDebug() << "Database version incompatible, reinitializing";
+ eraseDb();
+ initializeDb();
}
- initializeDb();
fetchConfigIfOutdated();
fetchForumsIfOutdated();
+ initializeBbCode(); // TODO This might depend on board config
}
Board::~Board()
@@ -85,17 +97,50 @@ void Board::setConfig(const QString &key, const QString &value)
notifyConfigChanged();
}
-int Board::rootForumId() const
+QString Board::removeHtml(QString text) const
{
- QSqlQuery query(_db);
- query.exec("SELECT forum_id FROM forums WHERE parent_id = -1");
- if (query.next()) {
- return query.value(0).toInt();
+ static const QRegExp regexp("<[a-zA-Z\\/][^>]*>");
+ text.replace(regexp, "");
+ return text;
+}
+
+QString Board::removeBbcode(QString text) const
+{
+ static const QRegExp regexp("\\[[a-zA-Z\\/][^]]*\\]");
+ text.replace(regexp, "");
+ return text;
+}
+
+QString Board::bbcodeToRichText(QString text) const
+{
+ typedef QPair<QRegExp, QString> Pair; // Workaround for ',' in Q_FOREACH
+ foreach (const Pair& pair, _bbcodes) {
+ text.replace(pair.first, pair.second);
+ }
+
+ return text;
+}
+
+QString Board::renderHumanDate(const QDateTime &dateTime)
+{
+ QDate date = dateTime.toLocalTime().date();
+ QDate today = QDate::currentDate();
+ if (date == today) {
+ return tr("Today");
+ } else if (date.daysTo(today) == 1) {
+ return tr("Yesterday");
+ } else if (date.daysTo(today) < 5) {
+ return QDate::longDayName(date.dayOfWeek(), QDate::StandaloneFormat);
} else {
- return -1;
+ return date.toString(Qt::DefaultLocaleShortDate);
}
}
+QString Board::renderHumanTime(const QDateTime &dateTime)
+{
+ return dateTime.toLocalTime().time().toString(Qt::DefaultLocaleShortDate);
+}
+
void Board::notifyConfigChanged()
{
emit configChanged();
@@ -150,6 +195,17 @@ QString Board::getDbPathFor(const QString &slug)
return getDbDir() + "/" + slug + ".sqlite";
}
+QString Board::getTempDbPathFor(const QString& slug)
+{
+ return QDir::tempPath() + "/" + slug + ".sqlite";
+}
+
+bool Board::checkCompatibleDb()
+{
+ QString version = getConfig("tapasboard_db_version");
+ return version == CURRENT_DB_VERSION;
+}
+
bool Board::initializeDb()
{
QSqlQuery q(_db);
@@ -158,16 +214,20 @@ bool Board::initializeDb()
return false;
}
- if (!q.exec("CREATE TABLE IF NOT EXISTS forums (forum_id INTEGER PRIMARY KEY, forum_name TEXT, description TEXT, parent_id INT, logo_url TEXT, new_post BOOL, is_protected BOOL, is_subscribed BOOL, can_subscribe BOOL, url TEXT, sub_only BOOL, sort_index INT UNIQUE)")) {
+ if (!q.exec("CREATE TABLE IF NOT EXISTS forums (forum_id INTEGER PRIMARY KEY, forum_name TEXT, description TEXT, parent_id INT, logo_url TEXT, new_post BOOL, is_protected BOOL, is_subscribed BOOL, can_subscribe BOOL, url TEXT, sub_only BOOL, sort_index INT)")) {
qWarning() << "Could not create forums table:" << q.lastError().text();
return false;
}
if (!q.exec("CREATE INDEX IF NOT EXISTS forums_parent ON forums (parent_id)")) {
- qWarning() << "Could not create forums table:" << q.lastError().text();
+ qWarning() << "Could not create forums_parent index:" << q.lastError().text();
+ return false;
+ }
+ if (!q.exec("CREATE UNIQUE INDEX IF NOT EXISTS forums_order ON forums (sort_index ASC)")) {
+ qWarning() << "Could not create forums_order index:" << q.lastError().text();
return false;
}
- if (!q.exec("CREATE TABLE IF NOT EXISTS topics (forum_id INTEGER, topic_id INTEGER PRIMARY KEY, topic_title TEXT, topic_author_id INTEGER, topic_author_name TEXT, is_subscribed BOOL, is_closed BOOL, icon_url TEXT, last_reply_time TEXT, new_post BOOL, last_update_time TEXT)")) {
+ if (!q.exec("CREATE TABLE IF NOT EXISTS topics (forum_id INTEGER, topic_id INTEGER PRIMARY KEY, topic_title TEXT, topic_author_id INTEGER, topic_author_name TEXT, is_subscribed BOOL, is_closed BOOL, icon_url TEXT, last_reply_time TEXT, reply_number INT, new_post BOOL, last_update_time TEXT)")) {
qWarning() << "Could not create topics table:" << q.lastError().text();
return false;
}
@@ -258,6 +318,20 @@ void Board::executeActionFromQueue()
}
}
+void Board::initializeBbCode()
+{
+ _bbcodes.clear();
+ _bbcodes << qMakePair(QRegExp("\\[(/?[bius])\\]", Qt::CaseInsensitive), QString("<\\1>"));
+
+ _bbcodes << qMakePair(QRegExp("\\[(/?)quote\\]", Qt::CaseInsensitive), QString("<\\1blockquote>"));
+
+ _bbcodes << qMakePair(QRegExp("\\[url\\]([^[]*)\\[/url\\]", Qt::CaseInsensitive), QString("<a href=\"\\1\">\\1</a>"));
+ _bbcodes << qMakePair(QRegExp("\\[url=([^]]*)\\]", Qt::CaseInsensitive), QString("<a href=\"\\1\">"));
+ _bbcodes << qMakePair(QRegExp("\\[/url\\]", Qt::CaseInsensitive), QString("</a>"));
+
+ _bbcodes << qMakePair(QRegExp("\n"), QString("<br>"));
+}
+
void Board::fetchConfigIfOutdated()
{
if (_iface->isAccessible()) {
diff --git a/board.h b/board.h
index fb28ea0..e8aecaa 100644
--- a/board.h
+++ b/board.h
@@ -1,7 +1,9 @@
#ifndef BOARD_H
#define BOARD_H
+#include <QtCore/QDateTime>
#include <QtCore/QObject>
+#include <QtCore/QPair>
#include <QtCore/QQueue>
#include <QtSql/QSqlDatabase>
@@ -15,17 +17,27 @@ public:
explicit Board(const QString& boardUrl, QObject *parent = 0);
~Board();
+ static const QLatin1String CURRENT_DB_VERSION;
+
bool busy() const;
void enqueueAction(Action* action);
QSqlDatabase database();
XmlRpcInterface *service();
+ // Configuration table
QString getConfig(const QString& key) const;
void setConfig(const QString& key, const QString &value);
- int rootForumId() const;
+ // Some helper functions
+ QString removeHtml(QString text) const;
+ QString removeBbcode(QString text) const;
+ QString bbcodeToRichText(QString text) const;
+
+ QString renderHumanDate(const QDateTime& dateTime);
+ QString renderHumanTime(const QDateTime& dateTime);
+ // These functions wrap emitting the signals below
void notifyConfigChanged();
void notifyForumsChanged();
void notifyForumTopicsChanged(int forumId, int start, int end);
@@ -42,11 +54,14 @@ private:
static QString createSlug(const QString& forumUrl);
static QString getDbDir();
static QString getDbPathFor(const QString& slug);
+ static QString getTempDbPathFor(const QString& slug);
+ bool checkCompatibleDb();
bool initializeDb();
bool eraseDb();
bool cleanDb();
bool removeFromActionQueue(Action *action);
void executeActionFromQueue();
+ void initializeBbCode();
void fetchConfigIfOutdated();
void fetchForumsIfOutdated();
@@ -60,6 +75,7 @@ private:
QSqlDatabase _db;
XmlRpcInterface *_iface;
QQueue<Action*> _queue;
+ QList< QPair<QRegExp, QString> > _bbcodes;
};
inline bool Board::busy() const
diff --git a/fetchboardconfigaction.cpp b/fetchboardconfigaction.cpp
index 0dd5e4c..2364dd1 100644
--- a/fetchboardconfigaction.cpp
+++ b/fetchboardconfigaction.cpp
@@ -32,6 +32,11 @@ void FetchBoardConfigAction::handleFinishedCall()
QSqlDatabase db = _board->database();
db.transaction();
QSqlQuery query(db);
+
+ // Let's add some of our config settings
+ map["last_config_fetch"] = QDateTime::currentDateTimeUtc().toString(Qt::ISODate);
+ map["tapasboard_db_version"] = Board::CURRENT_DB_VERSION;
+
query.prepare("INSERT OR REPLACE INTO config (key, value) VALUES (:key, :value)");
for (QVariantMap::iterator i = map.begin(); i != map.end(); i++) {
query.bindValue(":key", i.key());
@@ -40,11 +45,6 @@ void FetchBoardConfigAction::handleFinishedCall()
qWarning() << "Failed to set config key:" << i.key();
}
}
- query.bindValue(":key", "last_config_fetch");
- query.bindValue(":value", QDateTime::currentDateTimeUtc().toString(Qt::ISODate));
- if (!query.exec()) {
- qWarning() << "Failed to set last config fetch date";
- }
db.commit();
_board->notifyConfigChanged();
} else {
diff --git a/fetchpostsaction.cpp b/fetchpostsaction.cpp
index 16802fa..426f766 100644
--- a/fetchpostsaction.cpp
+++ b/fetchpostsaction.cpp
@@ -63,7 +63,7 @@ void FetchPostsAction::handleFinishedCall()
query.bindValue(":topic_id", topic_id);
query.bindValue(":post_id", post_id);
query.bindValue(":post_title", unencodePostText(post["post_title"]));
- query.bindValue(":post_content", unencodePostText(post["post_content"]));
+ query.bindValue(":post_content", unencodePostContent(post["post_content"]));
query.bindValue(":post_author_id", post["post_author_id"].toInt());
query.bindValue(":post_author_name", unencodePostText(post["post_author_name"]));
query.bindValue(":can_edit", post["can_edit"].toBool() ? 1 : 0);
@@ -96,3 +96,8 @@ QString FetchPostsAction::unencodePostText(const QVariant &v)
QByteArray ba = v.toByteArray();
return QString::fromUtf8(ba.constData(), ba.length());
}
+
+QString FetchPostsAction::unencodePostContent(const QVariant &v)
+{
+ return _board->bbcodeToRichText(unencodePostText(v));
+}
diff --git a/fetchpostsaction.h b/fetchpostsaction.h
index 63cb5f8..9951989 100644
--- a/fetchpostsaction.h
+++ b/fetchpostsaction.h
@@ -22,6 +22,7 @@ private slots:
private:
static QString unencodePostText(const QVariant& v);
+ QString unencodePostContent(const QVariant& v);
private:
XmlRpcPendingCall *_call;
diff --git a/fetchtopicsaction.cpp b/fetchtopicsaction.cpp
index dd59b4d..31a507a 100644
--- a/fetchtopicsaction.cpp
+++ b/fetchtopicsaction.cpp
@@ -43,8 +43,8 @@ void FetchTopicsAction::handleFinishedCall()
db.transaction();
QSqlQuery query(db);
- query.prepare("INSERT OR REPLACE INTO topics (forum_id, topic_id, topic_title, topic_author_id, topic_author_name, is_subscribed, is_closed, icon_url, last_reply_time, new_post, last_update_time) "
- "VALUES (:forum_id, :topic_id, :topic_title, :topic_author_id, :topic_author_name, :is_subscribed, :is_closed, :icon_url, :last_reply_time, :new_post, :last_update_time)");
+ query.prepare("INSERT OR REPLACE INTO topics (forum_id, topic_id, topic_title, topic_author_id, topic_author_name, is_subscribed, is_closed, icon_url, last_reply_time, reply_number, new_post, last_update_time) "
+ "VALUES (:forum_id, :topic_id, :topic_title, :topic_author_id, :topic_author_name, :is_subscribed, :is_closed, :icon_url, :last_reply_time, :reply_number, :new_post, :last_update_time)");
foreach (const QVariant& topic_v, topics) {
QVariantMap topic = topic_v.toMap();
@@ -69,6 +69,7 @@ void FetchTopicsAction::handleFinishedCall()
query.bindValue(":is_closed", topic["is_closed"].toBool() ? 1 : 0);
query.bindValue(":icon_url", topic["icon_url"].toString());
query.bindValue(":last_reply_time", topic["last_reply_time"].toDateTime());
+ query.bindValue(":reply_number", topic["reply_number"].toInt());
query.bindValue(":new_post", topic["new_post"].toBool() ? 1 : 0);
query.bindValue(":last_update_time", QDateTime::currentDateTime());
diff --git a/main.cpp b/main.cpp
index 5fd69ee..a02f000 100644
--- a/main.cpp
+++ b/main.cpp
@@ -8,29 +8,23 @@
#include "forummodel.h"
#include "topicmodel.h"
-#include "xmlrpcinterface.h"
-#include "xmlrpcreply.h"
-
BoardManager *board_manager;
Q_DECL_EXPORT int main(int argc, char *argv[])
{
QScopedPointer<QApplication> app(createApplication(argc, argv));
- QScopedPointer<BoardManager> manager(new BoardManager);
- QmlApplicationViewer viewer;
+ QApplication::setOrganizationDomain("com.javispedro.tapasboard");
+ QApplication::setOrganizationName("tapasboard");
+ QApplication::setApplicationName("tapasboard");
- board_manager = manager.data();
-
- //Board *test = manager->getBoard("http://support.tapatalk.com/mobiquo/mobiquo.php");
- //XmlRpcInterface *iface = test->service();
- //XmlRpcPendingCall *call = iface->asyncCall("get_topic", "41");
- //call->waitForFinished();
- //Board *test = manager->getBoard("http://localhost:4444/point.php");
+ QScopedPointer<BoardManager> manager(new BoardManager);
+ board_manager = manager.data(); // Set the global pointer to this singleton
qmlRegisterType<BoardModel>("com.javispedro.tapasboard", 1, 0, "BoardModel");
qmlRegisterType<ForumModel>("com.javispedro.tapasboard", 1, 0, "ForumModel");
qmlRegisterType<TopicModel>("com.javispedro.tapasboard", 1, 0, "TopicModel");
+ QmlApplicationViewer viewer;
viewer.setOrientation(QmlApplicationViewer::ScreenOrientationAuto);
viewer.setMainQmlFile(QLatin1String("qml/tapasboard/main.qml"));
viewer.showExpanded();
diff --git a/qml/tapasboard/TopicPage.qml b/qml/tapasboard/TopicPage.qml
index 9a305f8..d3b2a35 100644
--- a/qml/tapasboard/TopicPage.qml
+++ b/qml/tapasboard/TopicPage.qml
@@ -28,6 +28,13 @@ Page {
boardUrl: topicPage.boardUrl
topicId: topicPage.topicId
}
+ section.property: "humanDate"
+ section.criteria: ViewSection.FullString
+ section.delegate: GroupHeader {
+ width: parent.width
+ text: section
+ }
+
delegate: Item {
id: postItem
@@ -49,19 +56,43 @@ Page {
anchors.right: parent.right
anchors.margins: UiConstants.DefaultMargin
anchors.verticalCenter: parent.verticalCenter
+ spacing: 2
+
+ Item {
+ width: parent.width
+ height: childrenRect.height
+
+ Text {
+ anchors.top: parent.top
+ anchors.left: parent.left
+ text: model.userName
+ font: UiConstants.SmallTitleFont
+ textFormat: Text.PlainText
+ }
+ Text {
+ anchors.top: parent.top
+ anchors.right: parent.right
+ text: model.humanTime
+ font: UiConstants.SubtitleFont
+ textFormat: Text.PlainText
+ }
+ }
Text {
text: model.title
width: parent.width
font: UiConstants.TitleFont
visible: text != ""
+ textFormat: Text.PlainText
}
Text {
text: model.content
width: parent.width
font: UiConstants.SubtitleFont
+ textFormat: Text.RichText
wrapMode: Text.Wrap
+ onLinkActivated: Qt.openUrlExternally(link)
}
}
}
diff --git a/topicmodel.cpp b/topicmodel.cpp
index bbc2314..0c7541e 100644
--- a/topicmodel.cpp
+++ b/topicmodel.cpp
@@ -14,7 +14,12 @@ TopicModel::TopicModel(QObject *parent) :
roles[TitleRole] = QByteArray("title");
roles[ContentRole] = QByteArray("content");
roles[IconRole] = QByteArray("icon");
- roles[PostIdRole] = QByteArray("postcId");
+ roles[PostIdRole] = QByteArray("postId");
+ roles[UserIdRole] = QByteArray("userId");
+ roles[UserNameRole] = QByteArray("userName");
+ roles[DateTimeRole] = QByteArray("dateTime");
+ roles[HumanDateRole] = QByteArray("humanDate");
+ roles[HumanTimeRole] = QByteArray("humanTime");
setRoleNames(roles);
}
@@ -72,22 +77,31 @@ int TopicModel::rowCount(const QModelIndex &parent) const
QVariant TopicModel::data(const QModelIndex &index, int role) const
{
if (!index.isValid()) return QVariant();
+ if (!_board) return QVariant();
+
const int row = index.row();
if (row >= _data.size()) {
qWarning() << "Could not seek to" << row;
return QVariant();
}
- switch (role) {
+ switch (role) { // Mind the lack of break statements
case TitleRole:
return _data[row].title;
- break;
case ContentRole:
return _data[row].content;
- break;
case PostIdRole:
return _data[row].post_id;
- break;
+ case UserIdRole:
+ return _data[row].user_id;
+ case UserNameRole:
+ return _data[row].user_name;
+ case DateTimeRole:
+ return _data[row].time;
+ case HumanDateRole:
+ return _board->renderHumanDate(_data[row].time);
+ case HumanTimeRole:
+ return _board->renderHumanTime(_data[row].time);
}
return QVariant();
@@ -143,7 +157,7 @@ void TopicModel::fetchMore(const QModelIndex &parent)
}
}
-QDateTime TopicModel::parseDateTime(const QVariant &v)
+QDateTime TopicModel::parseDbDateTime(const QVariant &v)
{
QString s = v.toString();
return QDateTime::fromString(s, Qt::ISODate);
@@ -171,10 +185,10 @@ QDateTime TopicModel::lastTopPostUpdate()
query.bindValue(":topic_id", _topicId);
if (query.exec()) {
if (query.next()) {
- return parseDateTime(query.value(0));
+ return parseDbDateTime(query.value(0));
}
} else {
- qWarning() << "Could not fetch posts:" << query.lastError().text();
+ qWarning() << "Could not load top posts:" << query.lastError().text();
}
return QDateTime();
}
@@ -185,7 +199,7 @@ QList<TopicModel::Post> TopicModel::loadPosts(int start, int end)
const int rows = end - start + 1;
QList<Post> posts;
QSqlQuery query(_board->database());
- query.prepare("SELECT post_id, post_title, post_content, post_time, last_update_time FROM posts "
+ query.prepare("SELECT post_id, post_title, post_content, post_author_id, post_author_name, post_time, last_update_time FROM posts "
"WHERE topic_id = :topic_id "
"ORDER by post_time ASC "
"LIMIT :start, :limit");
@@ -199,8 +213,10 @@ QList<TopicModel::Post> TopicModel::loadPosts(int start, int end)
post.post_id = query.value(0).toInt();
post.title = query.value(1).toString();
post.content = query.value(2).toString();
- post.time = parseDateTime(query.value(3));
- post.last_update_time = parseDateTime(query.value(4));
+ post.user_id = query.value(3).toInt();
+ post.user_name = query.value(4).toString();
+ post.time = parseDbDateTime(query.value(5));
+ post.last_update_time = parseDbDateTime(query.value(6));
posts.append(post);
}
} else {
diff --git a/topicmodel.h b/topicmodel.h
index 95a3f78..39dcdee 100644
--- a/topicmodel.h
+++ b/topicmodel.h
@@ -21,7 +21,12 @@ public:
IconRole = Qt::DecorationRole,
ContentRole = Qt::ToolTipRole,
- PostIdRole = Qt::UserRole
+ PostIdRole = Qt::UserRole,
+ UserIdRole,
+ UserNameRole,
+ DateTimeRole,
+ HumanDateRole,
+ HumanTimeRole
};
QString boardUrl() const;
@@ -45,12 +50,14 @@ protected:
int post_id;
QString title;
QString content;
+ int user_id;
+ QString user_name;
QDateTime time;
QDateTime last_update_time;
};
private:
- static QDateTime parseDateTime(const QVariant& v);
+ static QDateTime parseDbDateTime(const QVariant& v);
static QDateTime oldestPostUpdate(const QList<Post>& posts);
QDateTime lastTopPostUpdate();
QList<Post> loadPosts(int start, int end);