#include #include #include "global.h" #include "board.h" #include "xmlrpcinterface.h" #include "fetchtopicsaction.h" #include "forummodel.h" ForumModel::ForumModel(QObject *parent) : QAbstractListModel(parent), _boardUrl(), _board(0), _forumId(-1) { QHash roles = roleNames(); roles[TitleRole] = QByteArray("title"); roles[IconRole] = QByteArray("icon"); roles[TopicIdRole] = QByteArray("topicId"); setRoleNames(roles); } QString ForumModel::boardUrl() const { return _boardUrl; } void ForumModel::setBoardUrl(const QString &url) { if (_boardUrl != url) { disconnect(this, SLOT(handleForumTopicsChanged(int,int,int))); clearModel(); _board = 0; _boardUrl = url; if (!_boardUrl.isEmpty()) { _board = board_manager->getBoard(_boardUrl); connect(_board, SIGNAL(forumTopicsChanged(int,int,int)), SLOT(handleForumTopicsChanged(int,int,int))); if (_forumId >= 0) { update(); reload(); } } emit boardUrlChanged(); } } int ForumModel::forumId() const { return _forumId; } void ForumModel::setForumId(const int id) { if (_forumId != id) { clearModel(); _forumId = id; if (_forumId >= 0 && _board) { update(); reload(); } emit forumIdChanged(); } } int ForumModel::rowCount(const QModelIndex &parent) const { return parent.isValid() ? 0 : _data.size(); } QVariant ForumModel::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 TopicIdRole: return _data[row].topic_id; break; } return QVariant(); } bool ForumModel::canFetchMore(const QModelIndex &parent) const { if (parent.isValid() || !_board) return false; // Invalid state return !_eof; } void ForumModel::fetchMore(const QModelIndex &parent) { if (parent.isValid()) return; if (!_board) return; if (_eof) return; const int start = _data.size(); QList topics = loadTopics(start, start + FORUM_PAGE_SIZE - 1); const int new_end = start + _data.size() - 1; if (topics.empty()) { // We could not load anything more from DB! _eof = true; } else { beginInsertRows(QModelIndex(), start, new_end); _data.append(topics); _eof = topics.size() < FORUM_PAGE_SIZE; // If short read, we reached EOF. endInsertRows(); } if (_board->service()->isAccessible()) { if (!_data.empty()) { QDateTime last = oldestPostUpdate(topics); // If the topics we got from DB are too old, refresh online. if (last.secsTo(QDateTime::currentDateTime()) > FORUM_TOPICS_TLL) { qDebug() << "Fetching topics because of old"; _board->enqueueAction(new FetchTopicsAction(_forumId, start, new_end, _board)); } } // Try to fetch more topics if board is online and we reached the end of DB if (_eof) { qDebug() << "Fetching topics because of EOF"; _board->enqueueAction(new FetchTopicsAction(_forumId, _data.size(), _data.size() + FORUM_PAGE_SIZE - 1, _board)); } } } QDateTime ForumModel::parseDateTime(const QVariant &v) { QString s = v.toString(); return QDateTime::fromString(s, Qt::ISODate); } QDateTime ForumModel::oldestPostUpdate(const QList &topics) { if (topics.empty()) return QDateTime::currentDateTime(); QDateTime min = topics.first().last_update_time; foreach (const Topic& topic, topics) { if (min < topic.last_update_time) min = topic.last_update_time; } return min; } QDateTime ForumModel::lastTopPostUpdate() { if (!_board) return QDateTime(); QSqlDatabase db = _board->database(); QSqlQuery query(db); query.prepare("SELECT last_update_time FROM topics " "WHERE forum_id = :forum_id " "ORDER BY last_reply_time DESC " "LIMIT 1"); query.bindValue(":forum_id", _forumId); if (query.exec()) { if (query.next()) { return parseDateTime(query.value(0)); } } else { qWarning() << "Could not fetch posts:" << query.lastError().text(); } return QDateTime(); } QList ForumModel::loadTopics(int start, int end) { Q_ASSERT(_board); const int rows = end - start + 1; QList topics; QSqlQuery query(_board->database()); query.prepare("SELECT topic_id, topic_title, last_reply_time, last_update_time FROM topics " "WHERE forum_id = :forum_id " "ORDER by last_reply_time DESC " "LIMIT :start, :limit"); query.bindValue(":forum_id", _forumId); query.bindValue(":start", start); query.bindValue(":limit", rows); if (query.exec()) { topics.reserve(rows); while (query.next()) { Topic topic; topic.topic_id = query.value(0).toInt(); topic.title = query.value(1).toString(); topic.last_reply_time = parseDateTime(query.value(2)); topic.last_update_time = parseDateTime(query.value(3)); topics.append(topic); } } else { qWarning() << "Could not load topics:" << query.lastError().text(); } return topics; } void ForumModel::clearModel() { beginResetModel(); _eof = false; _data.clear(); endResetModel(); } void ForumModel::handleForumTopicsChanged(int forumId, int start, int end) { if (forumId == _forumId) { // Yep, our topics list changed. qDebug() << "My topics changed" << start << end; if (end > _data.size()) { // If for any reason we have more topics now, it means we might // no longer be EOF... _eof = false; } if (start > _data.size() + 1) { // We are still not interested into these topics. qDebug() << "Topics too far"; return; } QList topics = loadTopics(start, end); if (topics.size() < end - start + 1) { _eof = true; // Short read end = start + topics.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] = topics[i - start]; } for (int i = _data.size(); i <= end; i++) { Q_ASSERT(i >= start); _data.append(topics[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] = topics[i - start]; } emit dataChanged(createIndex(start, 0), createIndex(end, 0)); } } } void ForumModel::update() { if (!_board || _forumId < 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()) > FORUM_TOP_TLL) { // Outdated or empty, refresh. qDebug() << "Fetching topics because the top are old"; _board->enqueueAction(new FetchTopicsAction(_forumId, 0, FORUM_PAGE_SIZE - 1, _board)); } else { qDebug() << "Topics not outdated"; } } void ForumModel::reload() { Q_ASSERT(_data.empty()); Q_ASSERT(!_eof); // Fetch an initial bunch of topics fetchMore(); }