summaryrefslogtreecommitdiff
path: root/fetchpostsaction.cpp
blob: d29312907ed6c6e3b069dd6e5efedc911f3bb415 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
#include <QtCore/QDateTime>
#include <QtCore/QDebug>
#include <QtSql/QSqlDatabase>
#include <QtSql/QSqlQuery>

#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<FetchPostsAction*>(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<QVariantMap> 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);
}