summaryrefslogtreecommitdiff
path: root/topicmodel.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'topicmodel.cpp')
-rw-r--r--topicmodel.cpp285
1 files changed, 285 insertions, 0 deletions
diff --git a/topicmodel.cpp b/topicmodel.cpp
new file mode 100644
index 0000000..bbc2314
--- /dev/null
+++ b/topicmodel.cpp
@@ -0,0 +1,285 @@
+#include <QtCore/QDebug>
+#include <QtSql/QSqlError>
+
+#include "global.h"
+#include "board.h"
+#include "xmlrpcinterface.h"
+#include "fetchpostsaction.h"
+#include "topicmodel.h"
+
+TopicModel::TopicModel(QObject *parent) :
+ QAbstractListModel(parent), _boardUrl(), _board(0), _topicId(-1)
+{
+ QHash<int, QByteArray> roles = roleNames();
+ roles[TitleRole] = QByteArray("title");
+ roles[ContentRole] = QByteArray("content");
+ roles[IconRole] = QByteArray("icon");
+ roles[PostIdRole] = QByteArray("postcId");
+ setRoleNames(roles);
+}
+
+QString TopicModel::boardUrl() const
+{
+ return _boardUrl;
+}
+
+void TopicModel::setBoardUrl(const QString &url)
+{
+ if (_boardUrl != url) {
+ disconnect(this, SLOT(handleTopicPostsChanged(int,int,int)));
+ clearModel();
+ _board = 0;
+
+ _boardUrl = url;
+ if (!_boardUrl.isEmpty()) {
+ _board = board_manager->getBoard(_boardUrl);
+ connect(_board, SIGNAL(topicPostsChanged(int,int,int)),
+ SLOT(handleTopicPostsChanged(int,int,int)));
+ if (_topicId >= 0) {
+ update();
+ reload();
+ }
+ }
+ emit boardUrlChanged();
+ }
+}
+
+int TopicModel::topicId() const
+{
+ return _topicId;
+}
+
+void TopicModel::setTopicId(const int id)
+{
+ if (_topicId != id) {
+ clearModel();
+
+ _topicId = id;
+
+ if (_topicId >= 0 && _board) {
+ update();
+ reload();
+ }
+ emit topicIdChanged();
+ }
+}
+
+int TopicModel::rowCount(const QModelIndex &parent) const
+{
+ return parent.isValid() ? 0 : _data.size();
+}
+
+QVariant TopicModel::data(const QModelIndex &index, int role) const
+{
+ if (!index.isValid()) return QVariant();
+ const int row = index.row();
+ if (row >= _data.size()) {
+ qWarning() << "Could not seek to" << row;
+ return QVariant();
+ }
+
+ switch (role) {
+ case TitleRole:
+ return _data[row].title;
+ break;
+ case ContentRole:
+ return _data[row].content;
+ break;
+ case PostIdRole:
+ return _data[row].post_id;
+ break;
+ }
+
+ return QVariant();
+}
+
+bool TopicModel::canFetchMore(const QModelIndex &parent) const
+{
+ if (parent.isValid() || !_board) return false; // Invalid state
+ return !_eof;
+}
+
+void TopicModel::fetchMore(const QModelIndex &parent)
+{
+ if (parent.isValid()) return;
+ if (!_board) return;
+ if (_eof) return;
+
+ const int start = _data.size();
+ QList<Post> posts = loadPosts(start, start + TOPIC_PAGE_SIZE - 1);
+ const int new_end = start + _data.size() - 1;
+
+ if (posts.empty()) {
+ // We could not load anything more from DB!
+ _eof = true;
+ } else {
+ beginInsertRows(QModelIndex(), start, new_end);
+ _data.append(posts);
+ _eof = posts.size() < TOPIC_PAGE_SIZE; // If short read, we reached EOF.
+ endInsertRows();
+ }
+
+ if (_board->service()->isAccessible()) {
+ if (!_data.empty()) {
+ QDateTime last = oldestPostUpdate(posts);
+ // If the posts we got from DB are too old, refresh online.
+ if (last.secsTo(QDateTime::currentDateTime()) > TOPIC_POSTS_TLL) {
+ qDebug() << "Fetching posts because of old";
+ _board->enqueueAction(new FetchPostsAction(_topicId,
+ start,
+ new_end,
+ _board));
+ }
+ }
+
+ // Try to fetch more posts if board is online and we reached the end of DB
+ if (_eof) {
+ qDebug() << "Fetching posts because of EOF";
+ _board->enqueueAction(new FetchPostsAction(_topicId,
+ _data.size(),
+ _data.size() + FORUM_PAGE_SIZE - 1,
+ _board));
+ }
+ }
+}
+
+QDateTime TopicModel::parseDateTime(const QVariant &v)
+{
+ QString s = v.toString();
+ return QDateTime::fromString(s, Qt::ISODate);
+}
+
+QDateTime TopicModel::oldestPostUpdate(const QList<Post> &posts)
+{
+ if (posts.empty()) return QDateTime::currentDateTime();
+ QDateTime min = posts.first().last_update_time;
+ foreach (const Post& post, posts) {
+ if (min < post.last_update_time) min = post.last_update_time;
+ }
+ return min;
+}
+
+QDateTime TopicModel::lastTopPostUpdate()
+{
+ if (!_board) return QDateTime();
+ QSqlDatabase db = _board->database();
+ QSqlQuery query(db);
+ query.prepare("SELECT last_update_time FROM posts "
+ "WHERE topic_id = :topic_id "
+ "ORDER BY post_time ASC "
+ "LIMIT 1");
+ query.bindValue(":topic_id", _topicId);
+ if (query.exec()) {
+ if (query.next()) {
+ return parseDateTime(query.value(0));
+ }
+ } else {
+ qWarning() << "Could not fetch posts:" << query.lastError().text();
+ }
+ return QDateTime();
+}
+
+QList<TopicModel::Post> TopicModel::loadPosts(int start, int end)
+{
+ Q_ASSERT(_board);
+ 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 "
+ "WHERE topic_id = :topic_id "
+ "ORDER by post_time ASC "
+ "LIMIT :start, :limit");
+ query.bindValue(":topic_id", _topicId);
+ query.bindValue(":start", start);
+ query.bindValue(":limit", rows);
+ if (query.exec()) {
+ posts.reserve(rows);
+ while (query.next()) {
+ Post post;
+ 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));
+ posts.append(post);
+ }
+ } else {
+ qWarning() << "Could not load posts:" << query.lastError().text();
+ }
+ return posts;
+}
+
+void TopicModel::clearModel()
+{
+ beginResetModel();
+ _eof = false;
+ _data.clear();
+ endResetModel();
+}
+
+void TopicModel::handleTopicPostsChanged(int topicId, int start, int end)
+{
+ if (topicId == _topicId) {
+ // Yep, our posts list changed.
+ qDebug() << "My posts changed" << start << end;
+ if (end > _data.size()) {
+ // If for any reason we have more posts now, it means we might
+ // no longer be EOF...
+ _eof = false;
+ }
+ if (start > _data.size() + 1) {
+ // We are still not interested into these posts.
+ qDebug() << "Posts too far";
+ return;
+ }
+
+ QList<Post> posts = loadPosts(start, end);
+ if (posts.size() < end - start + 1) {
+ _eof = true; // Short read
+ end = start + posts.size() - 1;
+ }
+
+ if (end >= _data.size()) {
+ qDebug() << "Call insert rows" << _data.size() << end;
+ beginInsertRows(QModelIndex(), _data.size(), end);
+ _data.reserve(end + 1);
+ for (int i = start; i < _data.size(); i++) {
+ _data[i] = posts[i - start];
+ }
+ for (int i = _data.size(); i <= end; i++) {
+ Q_ASSERT(i >= start);
+ _data.append(posts[i - start]);
+ }
+ endInsertRows();
+ emit dataChanged(createIndex(start, 0), createIndex(_data.size() - 1, 0));
+ } else {
+ qDebug() << "Just refresh the data";
+ for (int i = start; i < end; i++) {
+ _data[i] = posts[i - start];
+ }
+ emit dataChanged(createIndex(start, 0), createIndex(end, 0));
+ }
+ }
+}
+
+void TopicModel::update()
+{
+ if (!_board || _topicId < 0) return;
+ // Start by requesting an update of the first 20 topics
+ if (!_board->service()->isAccessible()) return;
+ QDateTime last = lastTopPostUpdate();
+ if (!last.isValid() ||
+ last.secsTo(QDateTime::currentDateTime()) > TOPIC_TOP_TLL) {
+ // Outdated or empty, refresh.
+ _board->enqueueAction(new FetchPostsAction(_topicId, 0, TOPIC_PAGE_SIZE - 1, _board));
+ } else {
+ qDebug() << "Topics not outdated";
+ }
+}
+
+void TopicModel::reload()
+{
+ Q_ASSERT(_data.empty());
+ Q_ASSERT(!_eof);
+ fetchMore();
+}