From a6e64fbf9404b201b04fbd1ab4b959a18d8f83a9 Mon Sep 17 00:00:00 2001 From: "Javier S. Pedro" Date: Mon, 1 Apr 2013 20:46:39 +0200 Subject: add support for actually reading topics --- topicmodel.cpp | 285 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 285 insertions(+) create mode 100644 topicmodel.cpp (limited to 'topicmodel.cpp') 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 +#include + +#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 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 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 &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::loadPosts(int start, int end) +{ + Q_ASSERT(_board); + const int rows = end - start + 1; + QList 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 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(); +} -- cgit v1.2.3