summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--board.cpp132
-rw-r--r--board.h8
-rw-r--r--boardmodel.cpp10
-rw-r--r--boardmodel.h1
-rw-r--r--fetchforumsaction.cpp8
-rw-r--r--fetchpostsaction.cpp89
-rw-r--r--fetchpostsaction.h9
-rw-r--r--fetchtopicsaction.cpp23
-rw-r--r--forummodel.cpp34
-rw-r--r--forummodel.h1
-rw-r--r--global.h6
-rw-r--r--qml/TopicPage.qml9
-rw-r--r--topicmodel.cpp79
-rw-r--r--topicmodel.h4
14 files changed, 341 insertions, 72 deletions
diff --git a/board.cpp b/board.cpp
index b3c5bcb..9a3d6cc 100644
--- a/board.cpp
+++ b/board.cpp
@@ -1,4 +1,5 @@
#include <QtCore/QDir>
+#include <QtCore/QFileInfo>
#include <QtCore/QDebug>
#include <QtSql/QSqlQuery>
#include <QtSql/QSqlError>
@@ -55,6 +56,8 @@ Board::~Board()
{
disconnect(this, SLOT(handleActionFinished(Action*)));
if (!_slug.isEmpty()) {
+ qDebug() << "Cleaning cache database";
+ cleanDb();
QSqlDatabase::removeDatabase(_slug);
}
}
@@ -257,12 +260,23 @@ void Board::notifyForumsChanged()
emit forumsChanged();
}
+void Board::notifyForumChanged(int forumId)
+{
+ emit forumChanged(forumId);
+}
+
void Board::notifyForumTopicsChanged(int forumId, int start, int end)
{
qDebug() << "ForumTopics changed" << forumId << start << end;
emit forumTopicsChanged(forumId, start, end);
}
+void Board::notifyForumTopicChanged(int forumId, int topicId)
+{
+ qDebug() << "ForumTopic changed" << forumId << topicId;
+ emit forumTopicChanged(forumId, topicId);
+}
+
void Board::notifyTopicPostsChanged(int topicId, int start, int end)
{
qDebug() << "TopicPosts changed" << topicId << start << end;
@@ -303,11 +317,29 @@ void Board::notifyLogout()
}
}
-void Board::markPostAsRead(int postId)
+void Board::markTopicAsRead(int topicId)
{
- _postsToMarkRead.insert(postId);
- if (!_markReadDelay->isActive()) {
- _markReadDelay->start();
+ QSqlQuery q(_db);
+ q.prepare("UPDATE topics SET new_post = 0 WHERE topic_id = ? AND new_post = 1");
+ q.bindValue(0, topicId);
+ if (q.exec()) {
+ if (q.numRowsAffected() > 0) {
+ q.prepare("SELECT forum_id FROM topics WHERE topic_id = ?");
+ q.bindValue(0, topicId);
+ if (q.exec()) {
+ if (q.next()) {
+ int forum_id = q.value(0).toInt();
+ notifyForumTopicChanged(forum_id, topicId);
+ updateForumReadState(forum_id);
+ } else {
+ qWarning() << "Could not get forum of topic";
+ }
+ } else {
+ qWarning() << "Could not get forum of topic:" << q.lastError().text();
+ }
+ }
+ } else {
+ qWarning() << "Could not mark topic as read:" << q.lastError().text();
}
}
@@ -337,6 +369,12 @@ QString Board::getTempDbPathFor(const QString& slug)
return QDir::tempPath() + "/" + slug + ".sqlite";
}
+int Board::dbSize() const
+{
+ QFileInfo info(getDbPathFor(_slug));
+ return info.size();
+}
+
bool Board::checkCompatibleDb()
{
QString version = getConfig("tapasboard_db_version");
@@ -357,7 +395,7 @@ bool Board::initializeDb()
return false;
}
- if (!q.exec("CREATE TABLE IF NOT EXISTS forums (forum_id INTEGER PRIMARY KEY, forum_name TEXT, description TEXT, parent_id INT, logo_url TEXT, new_post BOOL, is_protected BOOL, is_subscribed BOOL, can_subscribe BOOL, url TEXT, sub_only BOOL, sort_index INT)")) {
+ if (!q.exec("CREATE TABLE IF NOT EXISTS forums (forum_id INTEGER PRIMARY KEY, forum_name TEXT, description TEXT, parent_id INT, logo_url TEXT, new_post BOOL, is_protected BOOL, is_subscribed BOOL, can_subscribe BOOL, url TEXT, sub_only BOOL, position INT)")) {
qWarning() << "Could not create forums table:" << q.lastError().text();
return false;
}
@@ -365,12 +403,12 @@ bool Board::initializeDb()
qWarning() << "Could not create forums_parent index:" << q.lastError().text();
return false;
}
- if (!q.exec("CREATE UNIQUE INDEX IF NOT EXISTS forums_order ON forums (sort_index ASC)")) {
+ if (!q.exec("CREATE UNIQUE INDEX IF NOT EXISTS forums_order ON forums (position ASC)")) {
qWarning() << "Could not create forums_order index:" << q.lastError().text();
return false;
}
- if (!q.exec("CREATE TABLE IF NOT EXISTS topics (forum_id INTEGER, topic_id INTEGER PRIMARY KEY, topic_title TEXT, topic_author_id INTEGER, topic_author_name TEXT, is_subscribed BOOL, is_closed BOOL, icon_url TEXT, last_reply_time TEXT, reply_number INT, new_post BOOL, last_update_time TEXT)")) {
+ if (!q.exec("CREATE TABLE IF NOT EXISTS topics (forum_id INTEGER, topic_id INTEGER PRIMARY KEY, topic_title TEXT, topic_author_id INTEGER, topic_author_name TEXT, is_subscribed BOOL, is_closed BOOL, icon_url TEXT, last_reply_time TEXT, reply_number INT, new_post BOOL, position INT, last_update_time TEXT)")) {
qWarning() << "Could not create topics table:" << q.lastError().text();
return false;
}
@@ -382,8 +420,12 @@ bool Board::initializeDb()
qWarning() << "Could not create topics_time index:" << q.lastError().text();
return false;
}
+ if (!q.exec("CREATE INDEX IF NOT EXISTS topics_order ON topics (forum_id, position ASC)")) {
+ qWarning() << "Could not create topics_order index:" << q.lastError().text();
+ return false;
+ }
- if (!q.exec("CREATE TABLE IF NOT EXISTS posts (forum_id INTEGER, topic_id INTEGER, post_id INTEGER PRIMARY KEY, post_title TEXT, post_content TEXT, post_author_id INTEGER, post_author_name TEXT, can_edit BOOL, icon_url TEXT, post_time TEXT, last_update_time TEXT)")) {
+ if (!q.exec("CREATE TABLE IF NOT EXISTS posts (forum_id INTEGER, topic_id INTEGER, post_id INTEGER PRIMARY KEY, post_title TEXT, post_content TEXT, post_author_id INTEGER, post_author_name TEXT, can_edit BOOL, icon_url TEXT, post_time TEXT, position INT, last_update_time TEXT)")) {
qWarning() << "Could not create posts table:" << q.lastError().text();
return false;
}
@@ -395,6 +437,10 @@ bool Board::initializeDb()
qWarning() << "Could not create posts_time index:" << q.lastError().text();
return false;
}
+ if (!q.exec("CREATE INDEX IF NOT EXISTS posts_order ON posts (topic_id, position ASC)")) {
+ qWarning() << "Could not create posts_order index:" << q.lastError().text();
+ return false;
+ }
return true;
}
@@ -430,11 +476,44 @@ bool Board::eraseDb()
bool Board::cleanDb()
{
QSqlQuery q(_db);
- // TODO: Delete old posts from cache
- if (!q.exec("VACUUM")) {
- qWarning() << "Could not vacuum database:" << q.lastError().text();
- return false;
+
+ int total_rows = 0;
+
+ q.prepare("DELETE FROM topics WHERE last_update_time < ?");
+ q.bindValue(0, QDateTime::currentDateTime().addDays(-FORUM_TOPICS_CACHE));
+ if (q.exec()) {
+ total_rows += q.numRowsAffected();
+ } else {
+ qWarning() << "Could not clean old topics:" << q.lastError().text();
}
+
+
+ q.prepare("DELETE FROM posts WHERE last_update_time < ?");
+ q.bindValue(0, QDateTime::currentDateTime().addDays(-TOPIC_POSTS_CACHE));
+ if (q.exec()) {
+ total_rows += q.numRowsAffected();
+ } else {
+ qWarning() << "Could not clean old posts:" << q.lastError().text();
+ }
+
+ if (q.exec("DELETE FROM topics WHERE forum_id NOT IN (SELECT forum_id FROM forums)")) {
+ total_rows += q.numRowsAffected();
+ } else {
+ qWarning() << "Could not clean unreferenced topics:" << q.lastError().text();
+ }
+ if (q.exec("DELETE FROM posts WHERE topic_id NOT IN (SELECT topic_id FROM topics)")) {
+ total_rows += q.numRowsAffected();
+ } else {
+ qWarning() << "Could not clean unreferenced posts:" << q.lastError().text();
+ }
+
+ if (total_rows > 100) {
+ qDebug() << "Vacuuming database";
+ if (q.exec("VACUUM")) {
+ qWarning() << "Could not vacuum database:" << q.lastError().text();
+ }
+ }
+
return true;
}
@@ -445,6 +524,7 @@ bool Board::removeFromActionQueue(Action *action)
if (_queue.removeOne(action)) {
if (!_queue.isEmpty() && head != _queue.head()) {
// The head action was removed; advance the queue.
+ qDebug() << "Now running" << _queue.head();
executeActionFromQueue();
}
action->deleteLater();
@@ -552,6 +632,34 @@ void Board::fetchForumsIfOutdated()
}
}
+void Board::updateForumReadState(int forumId)
+{
+ QSqlQuery q(_db);
+
+ q.prepare("SELECT COUNT() FROM topics WHERE forum_id = ? AND new_post = 1");
+ q.bindValue(0, forumId);
+ if (!q.exec()) {
+ qWarning() << "Could not select unread topics from forum:" << q.lastError().text();
+ return;
+ }
+ int unread_topics = q.value(0).toInt();
+ int new_post = (unread_topics > 0) ? 1 : 0;
+
+ q.prepare("UPDATE forums SET new_post = :new_post WHERE forum_id = :forum_id AND new_post != :cur_new_post");
+ q.bindValue(":new_post", new_post);
+ q.bindValue(":forum_id", forumId);
+ q.bindValue(":cur_new_post", new_post);
+
+ if (!q.exec()) {
+ qWarning() << "Could not update forum read status:" << q.lastError().text();
+ return;
+ }
+
+ if (q.numRowsAffected() > 0) {
+ notifyForumChanged(forumId);
+ }
+}
+
void Board::handleActionFinished(Action *action)
{
qDebug() << action << "finished";
diff --git a/board.h b/board.h
index 698b4b6..bfb0d6e 100644
--- a/board.h
+++ b/board.h
@@ -63,21 +63,25 @@ public slots:
// These functions wrap emitting the signals below
void notifyConfigChanged(const QString& key = QString());
void notifyForumsChanged();
+ void notifyForumChanged(int forumId);
void notifyForumTopicsChanged(int forumId, int start, int end);
+ void notifyForumTopicChanged(int forumId, int topicId);
void notifyTopicPostsChanged(int topicId, int start, int end);
void notifyTopicPostsUnread(int topicId, int position);
void notifyLogin(const QMap<QString, QVariant>& info);
void notifyLogout();
// Functions for marking posts as read
- void markPostAsRead(int postId);
+ void markTopicAsRead(int topicId);
signals:
void busyChanged();
void loggedInChanged();
void configChanged(const QString& key);
void forumsChanged();
+ void forumChanged(int forumId);
void forumTopicsChanged(int forumId, int start, int end);
+ void forumTopicChanged(int forumId, int topicId);
void topicPostsChanged(int topicId, int start, int end);
void topicPostsUnread(int topicId, int position);
@@ -85,6 +89,7 @@ private:
static QString createSlug(const QUrl& url);
static QString getDbPathFor(const QString& slug);
static QString getTempDbPathFor(const QString& slug);
+ int dbSize() const;
bool checkCompatibleDb();
bool initializeDb();
bool eraseDb();
@@ -96,6 +101,7 @@ private:
void initializeMarkRead();
void fetchConfigIfOutdated();
void fetchForumsIfOutdated();
+ void updateForumReadState(int forumId);
private slots:
void handleActionFinished(Action *action);
diff --git a/boardmodel.cpp b/boardmodel.cpp
index 3e24769..8d37fa5 100644
--- a/boardmodel.cpp
+++ b/boardmodel.cpp
@@ -29,9 +29,11 @@ void BoardModel::setBoard(Board *board)
{
if (_board != board) {
disconnect(this, SLOT(reload()));
+ disconnect(this, SLOT(handleForumChanged(int)));
_board = board;
if (_board) {
connect(board, SIGNAL(forumsChanged()), SLOT(reload()));
+ connect(board, SIGNAL(forumChanged(int)), SLOT(handleForumChanged(int)));
}
reload();
emit boardChanged();
@@ -141,7 +143,7 @@ void BoardModel::reload()
"LEFT JOIN forums f2 ON f2.forum_id = f1.parent_id "
"WHERE (f1.parent_id=:parent_id_1 AND f1.sub_only = 0) OR f1.parent_id IN "
"(SELECT forum_id from forums WHERE parent_id=:parent_id_2 AND sub_only=1) "
- "ORDER by f1.sort_index ASC;");
+ "ORDER by f1.position ASC;");
_query.bindValue(0, _forumId);
_query.bindValue(1, _forumId);
if (!_query.exec()) {
@@ -152,3 +154,9 @@ void BoardModel::reload()
endResetModel();
fetchMore(); // So that at least a few rows are sent
}
+
+void BoardModel::handleForumChanged(int forumId)
+{
+ Q_UNUSED(forumId);
+ reload(); // TODO improve
+}
diff --git a/boardmodel.h b/boardmodel.h
index 35ed447..ba4cda9 100644
--- a/boardmodel.h
+++ b/boardmodel.h
@@ -47,6 +47,7 @@ signals:
private slots:
void reload();
+ void handleForumChanged(int forumId);
private:
Board *_board;
diff --git a/fetchforumsaction.cpp b/fetchforumsaction.cpp
index c3b77cd..8bbe23b 100644
--- a/fetchforumsaction.cpp
+++ b/fetchforumsaction.cpp
@@ -39,8 +39,8 @@ void FetchForumsAction::handleFinishedCall()
handleDatabaseError("truncating forums table", db.lastError());
}
QSqlQuery query(db);
- query.prepare("INSERT INTO forums (forum_id, forum_name, description, parent_id, logo_url, new_post, is_protected, is_subscribed, can_subscribe, url, sub_only, sort_index)"
- "VALUES (:forum_id, :forum_name, :description, :parent_id, :logo_url, :new_post, :is_protected, :is_subscribed, :can_subscribe, :url, :sub_only, :sort_index)");
+ query.prepare("INSERT INTO forums (forum_id, forum_name, description, parent_id, logo_url, new_post, is_protected, is_subscribed, can_subscribe, url, sub_only, position)"
+ "VALUES (:forum_id, :forum_name, :description, :parent_id, :logo_url, :new_post, :is_protected, :is_subscribed, :can_subscribe, :url, :sub_only, :position)");
foreach (const QVariant& list_element, list) {
QVariantMap map = list_element.toMap();
@@ -67,7 +67,7 @@ void FetchForumsAction::handleFinishedCall()
query.bindValue(":can_subscribe", map["can_subscribed"].toBool() ? 1 : 0);
query.bindValue(":url", map["url"].toString());
query.bindValue(":sub_only", map["sub_only"].toBool() ? 1 : 0);
- query.bindValue(":sort_index", map["sort_index"]);
+ query.bindValue(":position", map["position"]);
if (!query.exec()) {
qWarning() << "Failed to store forum info for:" << forum_id;
@@ -95,7 +95,7 @@ QList<QVariantMap> FetchForumsAction::flattenForumList(const QVariantList &list,
QList<QVariantMap> flattened;
foreach (const QVariant& list_element, list) {
QVariantMap map = list_element.toMap();
- map["sort_index"] = (*order)++;
+ map["position"] = (*order)++;
QVariantMap::iterator child_key = map.find("child");
if (child_key != map.end()) {
// There are children, so flatten them too.
diff --git a/fetchpostsaction.cpp b/fetchpostsaction.cpp
index e38e423..1330f44 100644
--- a/fetchpostsaction.cpp
+++ b/fetchpostsaction.cpp
@@ -19,7 +19,15 @@ bool FetchPostsAction::isSupersetOf(Action *action) const
FetchPostsAction *other = qobject_cast<FetchPostsAction*>(action);
if (other) {
if (other->_topicId == _topicId) {
- if (_end == FetchAllPosts) {
+ 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;
@@ -37,15 +45,25 @@ bool FetchPostsAction::isSupersetOf(Action *action) const
void FetchPostsAction::execute()
{
- 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.
+ 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 = _board->service()->asyncCall("get_thread",
- QString::number(_topicId), _start, end);
_call->setParent(this);
connect(_call, SIGNAL(finished(XmlRpcPendingCall*)), SLOT(handleFinishedCall()));
}
@@ -65,11 +83,37 @@ void FetchPostsAction::handleFinishedCall()
// Not fatal, just assume it's the one we requested
topic_id = _topicId;
}
- QString topic_title = unencodePostContent(map["topic_title"]);
+ const QString topic_title = unencodePostText(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, last_update_time) "
- "VALUES (:topic_id, :post_id, :post_title, :post_content, :post_author_id, :post_author_name, :can_edit, :icon_url, :post_time, :last_update_time)");
+ 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();
@@ -89,8 +133,13 @@ void FetchPostsAction::handleFinishedCall()
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());
+ query.bindValue(":position", position);
query.bindValue(":last_update_time", QDateTime::currentDateTime());
+ Q_ASSERT(position < total_post_num);
+
+ position++;
+
if (!query.exec()) {
qWarning() << "Failed to store topic info for:" << topic_id;
handleDatabaseError("storing topic info", query);
@@ -99,15 +148,25 @@ void FetchPostsAction::handleFinishedCall()
}
db.commit();
- if (posts.size() > 0) {
+
+ if (!posts.isEmpty()) {
+ Q_ASSERT(start >= 0);
+ Q_ASSERT(position - 1 >= start);
_board->notifyTopicPostsChanged(_topicId,
- _start, _start + posts.size() - 1);
+ 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
- int start = _start + MAX_TOPIC_PAGE_SIZE;
- _board->enqueueAction(new FetchPostsAction(_topicId, start, FetchAllPosts, _board));
+ 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";
diff --git a/fetchpostsaction.h b/fetchpostsaction.h
index e122622..66e6c73 100644
--- a/fetchpostsaction.h
+++ b/fetchpostsaction.h
@@ -11,12 +11,15 @@ class FetchPostsAction : public Action
{
Q_OBJECT
public:
- explicit FetchPostsAction(int topicId, int start, int end, Board *board);
-
enum {
- FetchAllPosts = -1
+ /** Use as 'end' parameter; 'start' then indicates starting post. */
+ FetchAllPosts = -1,
+ /** Use as 'start' parameter; 'end' then indicates number of posts. */
+ FetchUnreadPosts = -2
};
+ explicit FetchPostsAction(int topicId, int start, int end, Board *board);
+
bool isSupersetOf(Action *action) const;
void execute();
diff --git a/fetchtopicsaction.cpp b/fetchtopicsaction.cpp
index bc94965..13aebdd 100644
--- a/fetchtopicsaction.cpp
+++ b/fetchtopicsaction.cpp
@@ -63,8 +63,11 @@ void FetchTopicsAction::handleFinishedCall()
db.transaction();
QSqlQuery query(db);
- query.prepare("INSERT OR REPLACE INTO topics (forum_id, topic_id, topic_title, topic_author_id, topic_author_name, is_subscribed, is_closed, icon_url, last_reply_time, reply_number, new_post, last_update_time) "
- "VALUES (:forum_id, :topic_id, :topic_title, :topic_author_id, :topic_author_name, :is_subscribed, :is_closed, :icon_url, :last_reply_time, :reply_number, :new_post, :last_update_time)");
+ query.prepare("INSERT OR REPLACE INTO topics (forum_id, topic_id, topic_title, topic_author_id, topic_author_name, is_subscribed, is_closed, icon_url, last_reply_time, reply_number, new_post, position, last_update_time) "
+ "VALUES (:forum_id, :topic_id, :topic_title, :topic_author_id, :topic_author_name, :is_subscribed, :is_closed, :icon_url, :last_reply_time, :reply_number, :new_post, :position, :last_update_time)");
+
+ Q_ASSERT(_start >= 0);
+ int position = _start;
foreach (const QVariant& topic_v, topics) {
QVariantMap topic = topic_v.toMap();
@@ -91,8 +94,11 @@ void FetchTopicsAction::handleFinishedCall()
query.bindValue(":last_reply_time", topic["last_reply_time"].toDateTime());
query.bindValue(":reply_number", topic["reply_number"].toInt());
query.bindValue(":new_post", topic["new_post"].toBool() ? 1 : 0);
+ query.bindValue(":position", position);
query.bindValue(":last_update_time", QDateTime::currentDateTime());
+ position++;
+
if (!query.exec()) {
qWarning() << "Failed to store topic info for:" << topic_id;
handleDatabaseError("storing topic info", query);
@@ -101,15 +107,20 @@ void FetchTopicsAction::handleFinishedCall()
}
db.commit();
- if (topics.size() > 0) {
+ if (!topics.isEmpty()) {
+ Q_ASSERT(_start >= 0);
+ Q_ASSERT(position - 1 >= _start);
_board->notifyForumTopicsChanged(_forumId,
- _start, _start + topics.size() - 1);
+ _start, position - 1);
}
if (_end == FetchAllTopics && topics.size() == MAX_FORUM_PAGE_SIZE) {
// Ok, let's prepare to fetch the next block of topics because
// there are probably more of them
- int start = _start + MAX_FORUM_PAGE_SIZE;
- _board->enqueueAction(new FetchTopicsAction(_forumId, start, FetchAllTopics, _board));
+ int next_start = _start + MAX_FORUM_PAGE_SIZE;
+ _board->enqueueAction(new FetchTopicsAction(_forumId,
+ next_start,
+ FetchAllTopics,
+ _board));
}
} else {
qWarning() << "Could not fetch topics";
diff --git a/forummodel.cpp b/forummodel.cpp
index faf0b75..7606f93 100644
--- a/forummodel.cpp
+++ b/forummodel.cpp
@@ -28,6 +28,7 @@ void ForumModel::setBoard(Board *board)
{
if (_board != board) {
disconnect(this, SLOT(handleForumTopicsChanged(int,int,int)));
+ disconnect(this, SLOT(handleForumTopicChanged(int,int)));
clearModel();
_board = board;
@@ -35,6 +36,8 @@ void ForumModel::setBoard(Board *board)
if (_board) {
connect(_board, SIGNAL(forumTopicsChanged(int,int,int)),
SLOT(handleForumTopicsChanged(int,int,int)));
+ connect(_board, SIGNAL(forumTopicChanged(int,int)),
+ SLOT(handleForumTopicChanged(int,int)));
if (_forumId >= 0) {
update();
reload();
@@ -175,7 +178,7 @@ QDateTime ForumModel::lastTopPostUpdate()
QSqlQuery query(db);
query.prepare("SELECT last_update_time FROM topics "
"WHERE forum_id = :forum_id "
- "ORDER BY last_reply_time DESC "
+ "ORDER BY position ASC "
"LIMIT 1");
query.bindValue(":forum_id", _forumId);
if (query.exec()) {
@@ -191,18 +194,16 @@ QDateTime ForumModel::lastTopPostUpdate()
QList<ForumModel::Topic> ForumModel::loadTopics(int start, int end)
{
Q_ASSERT(_board);
- const int rows = end - start + 1;
QList<Topic> topics;
QSqlQuery query(_board->database());
query.prepare("SELECT topic_id, topic_title, reply_number, new_post, last_reply_time, last_update_time FROM topics "
- "WHERE forum_id = :forum_id "
- "ORDER by last_reply_time DESC "
- "LIMIT :start, :limit");
+ "WHERE forum_id = :forum_id AND position BETWEEN :start AND :end "
+ "ORDER by position ASC ");
query.bindValue(":forum_id", _forumId);
query.bindValue(":start", start);
- query.bindValue(":limit", rows);
+ query.bindValue(":end", end);
if (query.exec()) {
- topics.reserve(rows);
+ topics.reserve(end - start + 1);
while (query.next()) {
Topic topic;
topic.topic_id = query.value(0).toInt();
@@ -272,6 +273,25 @@ void ForumModel::handleForumTopicsChanged(int forumId, int start, int end)
}
}
+void ForumModel::handleForumTopicChanged(int forumId, int topicId)
+{
+ if (forumId == _forumId) {qDebug() << "Me topic cha";
+ for (int i = 0; i < _data.size(); i++) {
+ Topic& topic = _data[i];
+ if (topic.topic_id == topicId) {qDebug() << "Me topic cha cha";
+ // Need to refresh this topic
+ QList<Topic> topics = loadTopics(i, i);
+ if (topics.size() == 1) {
+ _data[i] = topics[0];
+ emit dataChanged(createIndex(i, 0), createIndex(i, 0));
+ } else {
+ qWarning() << "Topic changed yet not in DB";
+ }
+ }
+ }
+ }
+}
+
void ForumModel::update()
{
if (!_board || _forumId < 0) return;
diff --git a/forummodel.h b/forummodel.h
index d0dd980..33a718e 100644
--- a/forummodel.h
+++ b/forummodel.h
@@ -64,6 +64,7 @@ private:
private slots:
void handleForumTopicsChanged(int forumId, int start, int end);
+ void handleForumTopicChanged(int forumId, int topicId);
void update();
void reload();
diff --git a/global.h b/global.h
index 694dce0..8c64a4c 100644
--- a/global.h
+++ b/global.h
@@ -18,6 +18,9 @@
/** Time we should consider other topics in a forum up to date, in seconds. */
#define FORUM_TOPICS_TLL 15 * 60
+/** Time we should keep topics in the cache, in days. */
+#define FORUM_TOPICS_CACHE 10
+
/** Number of topics per "block" in subforum view */
#define FORUM_PAGE_SIZE 20
@@ -30,6 +33,9 @@
/** Time we should consider other posts in a topic up to date, in seconds. */
#define TOPIC_POSTS_TLL 15 * 60
+/** Time we should keep topics in the cache, in days. */
+#define TOPIC_POSTS_CACHE 10
+
/** Number of posts per "block" in topic view */
#define TOPIC_PAGE_SIZE 20
diff --git a/qml/TopicPage.qml b/qml/TopicPage.qml
index 689dce7..9835a06 100644
--- a/qml/TopicPage.qml
+++ b/qml/TopicPage.qml
@@ -36,6 +36,11 @@ Page {
id: topicModel
board: topicPage.board
topicId: topicPage.topicId
+
+ onFirstUnreadPostChanged: {
+ postsView.positionViewAtIndex(firstUnreadPost, ListView.Beginning);
+ topicModel.markAsRead();
+ }
}
section.property: "humanDate"
section.criteria: ViewSection.FullString
@@ -56,9 +61,13 @@ Page {
height: postItemColumn.height + UiConstants.DefaultMargin
anchors.centerIn: parent
+ visible: model.postId >= 0
color: "white"
radius: 20
+ border.width: model.unread ? 2 : 0
+ border.color: "black"
+
Column {
id: postItemColumn
anchors.left: parent.left
diff --git a/topicmodel.cpp b/topicmodel.cpp
index 6b4991d..d3032be 100644
--- a/topicmodel.cpp
+++ b/topicmodel.cpp
@@ -20,6 +20,7 @@ TopicModel::TopicModel(QObject *parent) :
roles[DateTimeRole] = QByteArray("dateTime");
roles[HumanDateRole] = QByteArray("humanDate");
roles[HumanTimeRole] = QByteArray("humanTime");
+ roles[UnreadRole] = QByteArray("unread");
setRoleNames(roles);
}
@@ -94,8 +95,23 @@ QVariant TopicModel::data(const QModelIndex &index, int role) const
if (_data[row].post_id < 0) {
// This post is a unfetched stub
- this->fetchPost(row);
- return QVariant(); // Will update the model once the post arrives
+ switch (role) {
+ case PostIdRole:
+ this->fetchPost(row);
+ return QVariant::fromValue(-1);
+ case TitleRole:
+ case ContentRole:
+ case UserNameRole:
+ case HumanDateRole:
+ case HumanTimeRole:
+ return QVariant::fromValue(QString());
+ case DateTimeRole:
+ return QVariant::fromValue(QDateTime());
+ case UnreadRole:
+ return QVariant::fromValue(_firstUnread >= 0 && row >= _firstUnread);
+ default:
+ return QVariant();
+ }
}
switch (role) { // Mind the lack of break statements
@@ -115,6 +131,8 @@ QVariant TopicModel::data(const QModelIndex &index, int role) const
return _board->renderHumanDate(_data[row].time);
case HumanTimeRole:
return _board->renderHumanTime(_data[row].time);
+ case UnreadRole:
+ return _firstUnread >= 0 && row >= _firstUnread;
}
return QVariant();
@@ -180,6 +198,11 @@ void TopicModel::refresh()
_board));
}
+void TopicModel::markAsRead()
+{
+ _board->markTopicAsRead(_topicId);
+}
+
QDateTime TopicModel::parseDbDateTime(const QVariant &v)
{
QString s = v.toString();
@@ -203,7 +226,7 @@ QDateTime TopicModel::lastTopPostUpdate()
QSqlQuery query(db);
query.prepare("SELECT last_update_time FROM posts "
"WHERE topic_id = :topic_id "
- "ORDER BY post_time ASC "
+ "ORDER BY position ASC "
"LIMIT 1");
query.bindValue(":topic_id", _topicId);
if (query.exec()) {
@@ -219,19 +242,17 @@ QDateTime TopicModel::lastTopPostUpdate()
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_author_id, post_author_name, post_time, last_update_time FROM posts "
- "WHERE topic_id = :topic_id "
- "ORDER by post_time ASC "
- "LIMIT :start, :limit");
+ "WHERE topic_id = :topic_id AND position BETWEEN :start AND :end "
+ "ORDER by position ASC ");
query.bindValue(":topic_id", _topicId);
query.bindValue(":start", start);
- query.bindValue(":limit", rows);
+ query.bindValue(":end", end);
if (query.exec()) {
int loaded = 0;
- posts.reserve(rows);
+ posts.reserve(end - start + 1);
while (query.next()) {
Post post;
post.post_id = query.value(0).toInt();
@@ -254,13 +275,12 @@ void TopicModel::fetchPost(int position) const
{
if (_board->service()->isAccessible()) {
// There are lest posts on the DB than we wanted
- qDebug() << "Fetching posts because of unfetched";
+ qDebug() << "Fetching post" << position << "because of unfetched";
// Always fetch one page at least.
- int fetch_start = position % FORUM_PAGE_SIZE;
- int fetch_end = fetch_start + FORUM_PAGE_SIZE;
- _board->enqueueAction(new FetchPostsAction(_topicId,
- fetch_start, fetch_end,
- _board));
+ int start = (position / FORUM_PAGE_SIZE) * FORUM_PAGE_SIZE;
+ int end = (start + FORUM_PAGE_SIZE) - 1;
+ qDebug() << "From" << start << "to" << end;
+ _board->enqueueAction(new FetchPostsAction(_topicId, start, end, _board));
}
}
@@ -277,6 +297,7 @@ void TopicModel::enlargeModel(int end)
_data.append(post);
}
endInsertRows();
+ Q_ASSERT(_data.size() == end + 1);
}
}
@@ -298,17 +319,19 @@ void TopicModel::handleTopicPostsChanged(int topicId, int start, int end)
_eof = false;
}
if (start > _data.size() + 1) {
- // We are still not interested into these posts.
- qDebug() << "Posts too far";
- return;
+ enlargeModel(start);
}
QList<Post> posts = loadPosts(start, end);
if (posts.size() < end - start + 1) {
- _eof = true; // Short read
- end = start + posts.size() - 1;
+ // Short read
+ qWarning() << "Short read while handling changed posts";
+ return; // This should not happen, really.
}
+ Q_ASSERT(start <= _data.size());
+ Q_ASSERT(end >= 0);
+
if (end >= _data.size()) {
qDebug() << "Call insert rows (changed):" << _data.size() << end;
beginInsertRows(QModelIndex(), _data.size(), end);
@@ -324,7 +347,7 @@ void TopicModel::handleTopicPostsChanged(int topicId, int start, int end)
emit dataChanged(createIndex(start, 0), createIndex(_data.size() - 1, 0));
} else {
qDebug() << "Just refresh the data";
- for (int i = start; i < end; i++) {
+ for (int i = start; i <= end; i++) {
_data[i] = posts[i - start];
}
emit dataChanged(createIndex(start, 0), createIndex(end, 0));
@@ -338,6 +361,7 @@ void TopicModel::handleTopicPostsUnread(int topicId, int position)
if (position != _firstUnread) {
enlargeModel(position);
_firstUnread = position;
+ qDebug() << "Changing unread post to" << position;
emit firstUnreadPostChanged();
}
}
@@ -351,8 +375,19 @@ void TopicModel::update()
QDateTime last = lastTopPostUpdate();
if (!last.isValid() ||
last.secsTo(QDateTime::currentDateTime()) > TOPIC_TOP_TLL) {
+ qDebug() << "Fetching posts because the top are old";
// Outdated or empty, refresh.
- _board->enqueueAction(new FetchPostsAction(_topicId, 0, TOPIC_PAGE_SIZE - 1, _board));
+ if (_board->loggedIn() && _board->getConfig("goto_unread") == "1") {
+ _board->enqueueAction(new FetchPostsAction(_topicId,
+ FetchPostsAction::FetchUnreadPosts,
+ TOPIC_PAGE_SIZE,
+ _board));
+ } else {
+ _board->enqueueAction(new FetchPostsAction(_topicId,
+ 0,
+ TOPIC_PAGE_SIZE - 1,
+ _board));
+ }
} else {
qDebug() << "Topics not outdated";
}
diff --git a/topicmodel.h b/topicmodel.h
index 67e48c4..d0c60bb 100644
--- a/topicmodel.h
+++ b/topicmodel.h
@@ -27,7 +27,8 @@ public:
UserNameRole,
DateTimeRole,
HumanDateRole,
- HumanTimeRole
+ HumanTimeRole,
+ UnreadRole
};
Board * board() const;
@@ -46,6 +47,7 @@ public:
public slots:
void refresh();
+ void markAsRead();
signals:
void boardChanged();