#include #include #include #include #include "global.h" #include "board.h" #include "xmlrpcinterface.h" #include "xmlrpcreply.h" #include "fetchpostsaction.h" FetchPostsAction::FetchPostsAction(int topicId, int start, int end, Board *board) : Action(board), _topicId(topicId), _start(start), _end(end) { } bool FetchPostsAction::isSupersetOf(Action *action) const { FetchPostsAction *other = qobject_cast(action); if (other) { if (other->_topicId == _topicId) { if (_start == FetchUnreadPosts || other->_start == FetchUnreadPosts) { if (_start == other->_start) { // This action supersets if it fetches a larger number of posts return _end >= other->_end; } else { // One fetches unread posts, the other does not. return false; } } else if (_end == FetchAllPosts) { // If this is fetching all posts, then this is a superset // of every other action... except those that also fetch all. return other->_end != FetchAllPosts; } else if (other->_end == FetchAllPosts) { // If the other action fetches all posts, this cannot be a // superset return false; } else if (_start <= other->_start && _end >= other->_end) { return true; } } } return false; } void FetchPostsAction::execute() { if (_start == FetchUnreadPosts) { Q_ASSERT(_end >= 0); qDebug() << "Fetch unread posts" << _end; _call = _board->service()->asyncCall("get_thread_by_unread", QString::number(_topicId), _end); } else { Q_ASSERT(_start >= 0); int end = _end; if (end == FetchAllPosts) { // Fetch posts in blocks of size 50. end = _start + MAX_TOPIC_PAGE_SIZE; // After finishing this action, a new one will be enqueued with the next 50. } qDebug() << "Fetch posts" << _start << end; _call = _board->service()->asyncCall("get_thread", QString::number(_topicId), _start, end); } _call->setParent(this); connect(_call, SIGNAL(finished(XmlRpcPendingCall*)), SLOT(handleFinishedCall())); } void FetchPostsAction::handleFinishedCall() { XmlRpcReply result(_call); if (result.isValid()) { QVariantMap map = result; QVariantList posts = map["posts"].toList(); QSqlDatabase db = _board->database(); db.transaction(); bool ok = false; int topic_id = map["topic_id"].toInt(&ok); if (!ok) { // Not fatal, just assume it's the one we requested topic_id = _topicId; } const QString topic_title = decodePostText(map["topic_title"]); const int unread_position = map["position"].toInt() - 1; const int total_post_num = map["total_post_num"].toInt(); int start; if (_start == FetchUnreadPosts) { // This requires some calculations const int page_size = _end; Q_ASSERT(page_size >= 0); if (unread_position < 0) { qWarning() << "Invalid unread position in reply"; posts.clear(); // Do not add any posts if this happens } const int unread_page = unread_position / page_size; start = unread_page * page_size; } else { Q_ASSERT(_start >= 0); start = _start; } if (start >= total_post_num) { qDebug() << "No more posts"; if (!posts.isEmpty()) { qDebug() << "Yet we got posts from the service!"; posts.clear(); // Sometimes } } int position = start; QSqlQuery query(db); query.prepare("INSERT OR REPLACE INTO posts (topic_id, post_id, post_title, post_content, post_author_id, post_author_name, can_edit, icon_url, post_time, position, last_update_time) " "VALUES (:topic_id, :post_id, :post_title, :post_content, :post_author_id, :post_author_name, :can_edit, :icon_url, :post_time, :position, :last_update_time)"); foreach (const QVariant& post_v, posts) { QVariantMap post = post_v.toMap(); bool ok = false; int post_id = post["post_id"].toInt(&ok); if (!ok) { qWarning() << "No post_id in" << post; continue; } query.bindValue(":topic_id", topic_id); query.bindValue(":post_id", post_id); query.bindValue(":post_title", decodePostTitle(post["post_title"], topic_title)); query.bindValue(":post_content", decodePostContent(post["post_content"])); query.bindValue(":post_author_id", post["post_author_id"].toInt()); query.bindValue(":post_author_name", decodePostText(post["post_author_name"])); query.bindValue(":can_edit", post["can_edit"].toBool() ? 1 : 0); query.bindValue(":icon_url", post["icon_url"].toString()); query.bindValue(":post_time", post["post_time"].toDateTime().toUTC()); query.bindValue(":position", position); query.bindValue(":last_update_time", QDateTime::currentDateTimeUtc()); Q_ASSERT(position < total_post_num); position++; if (!query.exec()) { qWarning() << "Failed to store topic info for:" << topic_id; handleDatabaseError("storing topic info", query); continue; } } db.commit(); if (!posts.isEmpty()) { Q_ASSERT(start >= 0); Q_ASSERT(position - 1 >= start); _board->notifyTopicPostsChanged(_topicId, start, position - 1); } if (_start == FetchUnreadPosts && unread_position >= 0) { _board->notifyTopicPostsUnread(_topicId, unread_position); } if (_end == FetchAllPosts && posts.size() == MAX_TOPIC_PAGE_SIZE) { // Ok, let's prepare to fetch the next block of posts because // there are probably more of them Q_ASSERT(_start >= 0); int next_start = start + MAX_TOPIC_PAGE_SIZE; _board->enqueueAction(new FetchPostsAction(_topicId, next_start, FetchAllPosts, _board)); } } else { qWarning() << "Could not fetch posts"; // TODO emit error ... } emit finished(this); _call->deleteLater(); } QString FetchPostsAction::decodePostText(const QVariant &v) { QByteArray ba = v.toByteArray(); return QString::fromUtf8(ba.constData(), ba.length()); } QString FetchPostsAction::decodePostTitle(const QVariant &v, const QString &topicTitle) { QString title = decodePostText(v); if (QString::compare(title, "Re: " + topicTitle, Qt::CaseInsensitive) == 0) { // Hack to disable the useless "Re: $TOPIC_TITLE" post titles everywhere return QString(); } return title; } QString FetchPostsAction::decodePostContent(const QVariant &v) { QString richText = _board->bbcodeToRichText(decodePostText(v)); return _board->parseSmilies(richText); }