From 5bf01430ff0e0330b4a85e2c08e890a4c5e105a4 Mon Sep 17 00:00:00 2001
From: Sascha Grossenbacher <saschagros@gmail.com>
Date: Fri, 18 Mar 2011 01:01:13 +0100
Subject: [PATCH 1/3] Initial creaton of pm_thread working.

---
 privatemsg.install |  136 ++++++++++++++++++++++++++++++++++++++++++++++++++++
 privatemsg.module  |   23 ++++++++-
 2 files changed, 157 insertions(+), 2 deletions(-)

diff --git a/privatemsg.install b/privatemsg.install
index 12cbc71..5da796e 100644
--- a/privatemsg.install
+++ b/privatemsg.install
@@ -61,6 +61,69 @@ function privatemsg_schema() {
     ),
   );
 
+  $schema['pm_thread'] = array(
+    'description'       => '{pm_thread} holds global information about a thread.',
+    'fields'        => array(
+      'thread_id'    => array(
+        'description'   => 'Private message thread ID.',
+        'type'          => 'int',
+        'not null'      => TRUE,
+        'unsigned'      => TRUE,
+      ),
+      'subject'    => array(
+        'description'   => 'Subject of the thread.',
+        'type'          => 'varchar',
+        'length'        => 255,
+        'not null'      => TRUE,
+      ),
+    ),
+    'primary key' => array('thread_id'),
+  );
+
+  $schema['pm_thread_user'] = array(
+    'description'       => '{pm_thread_user} holds information about a thread for a single recipient.',
+    'fields'        => array(
+      'thread_id'    => array(
+        'description'   => 'Private message thread ID.',
+        'type'          => 'int',
+        'not null'      => TRUE,
+        'unsigned'      => TRUE,
+      ),
+      'uid'    => array(
+        'description'   => 'User ID of the recipient',
+        'type'          => 'int',
+        'not null'      => TRUE,
+        'unsigned'      => TRUE,
+      ),
+      'started'    => array(
+        'description'   => 'When the first message of the thread was received.',
+        'type'          => 'int',
+        'not null'      => TRUE,
+        'unsigned'      => TRUE,
+      ),
+      'last_updated'    => array(
+        'description'   => 'When the last message of that thread was received.',
+        'type'          => 'int',
+        'not null'      => TRUE,
+        'unsigned'      => TRUE,
+      ),
+      'count'    => array(
+        'description'   => 'Number of messages in a thread.',
+        'type'          => 'int',
+        'not null'      => TRUE,
+        'unsigned'      => TRUE,
+      ),
+      'unread_count'    => array(
+        'description'   => 'Number of unread messages in a thread.',
+        'type'          => 'int',
+        'not null'      => TRUE,
+        'unsigned'      => TRUE,
+      ),
+    ),
+    'primary key' => array('thread_id', 'uid'),
+  );
+
+  
   $schema['pm_message'] = array(
     'description'       => '{pm_messages} holds the message information',
     'fields'        => array(
@@ -686,3 +749,76 @@ function privatemsg_db_index_exists($table, $name) {
     return (bool)db_fetch_array(db_query('SHOW INDEX FROM {' . $table . "} WHERE key_name = '$name'"));
   }
 }
+
+/**
+ * Add {pm_thread} and {pm_thread_recipient} table.
+ */
+function privatemsg_update_6205() {
+  $schema = array();
+  $schema['pm_thread'] = array(
+    'description'       => '{pm_thread} holds global information about a thread.',
+    'fields'        => array(
+      'thread_id'    => array(
+        'description'   => 'Private message thread ID.',
+        'type'          => 'int',
+        'not null'      => TRUE,
+        'unsigned'      => TRUE,
+      ),
+      'subject'    => array(
+        'description'   => 'Subject of the thread.',
+        'type'          => 'varchar',
+        'length'        => 255,
+        'not null'      => TRUE,
+      ),
+    ),
+    'primary key' => array('thread_id'),
+  );
+
+  $schema['pm_thread_user'] = array(
+    'description'       => '{pm_thread_user} holds information about a thread for a single recipient.',
+    'fields'        => array(
+      'thread_id'    => array(
+        'description'   => 'Private message thread ID.',
+        'type'          => 'int',
+        'not null'      => TRUE,
+        'unsigned'      => TRUE,
+      ),
+      'uid'    => array(
+        'description'   => 'User ID of the recipient',
+        'type'          => 'int',
+        'not null'      => TRUE,
+        'unsigned'      => TRUE,
+      ),
+      'started'    => array(
+        'description'   => 'When the first message of the thread was received.',
+        'type'          => 'int',
+        'not null'      => TRUE,
+        'unsigned'      => TRUE,
+      ),
+      'last_updated'    => array(
+        'description'   => 'When the last message of that thread was received.',
+        'type'          => 'int',
+        'not null'      => TRUE,
+        'unsigned'      => TRUE,
+      ),
+      'count'    => array(
+        'description'   => 'Number of messages in a thread.',
+        'type'          => 'int',
+        'not null'      => TRUE,
+        'unsigned'      => TRUE,
+      ),
+      'unread_count'    => array(
+        'description'   => 'Number of unread messages in a thread.',
+        'type'          => 'int',
+        'not null'      => TRUE,
+        'unsigned'      => TRUE,
+      ),
+    ),
+    'primary key' => array('thread_id', 'uid'),
+  );
+
+  $ret = array();
+  db_create_table($ret, 'pm_thread', $schema['pm_thread']);
+  db_create_table($ret, 'pm_thread_user', $schema['pm_thread_user']);
+  return $ret;
+}
\ No newline at end of file
diff --git a/privatemsg.module b/privatemsg.module
index 71a5dce..38b93e4 100644
--- a/privatemsg.module
+++ b/privatemsg.module
@@ -1762,7 +1762,7 @@ function _privatemsg_send($message) {
     }
   }
 
-  // 1) Save the message body first.
+  // Save the message body and subject.
   $args = array();
   $args[] = $message['subject'];
   $args[] = $message['author']->uid;
@@ -1777,9 +1777,12 @@ function _privatemsg_send($message) {
   // Thread ID is the same as the mid if it's the first message in the thread.
   if (!isset($message['thread_id'])) {
     $message['thread_id'] = $mid;
+
+    // Create an entry for this thread in {pm_thread}
+    db_query("INSERT INTO {pm_thread} (thread_id, subject) VALUES(%d, '%s')", $message['thread_id'], $message['subject']);
   }
 
-  // 2) Save message to recipients.
+  // Save message to recipients.
   // Each recipient gets a record in the pm_index table.
   foreach ($message['recipients'] as $recipient) {
     if (!db_query($index_sql, $mid, $message['thread_id'], $recipient->recipient, $recipient->type, 1) ) {
@@ -1797,6 +1800,9 @@ function _privatemsg_send($message) {
     }
   }
 
+  // Update {pm_thread_recipients} table.
+  _privatemsg_thread_update($message['thread_id']);
+
   module_invoke_all('privatemsg_message_insert', $message);
 
   // If we reached here that means we were successful at writing all messages to db.
@@ -1804,6 +1810,19 @@ function _privatemsg_send($message) {
 }
 
 /**
+ * Updates the {pm_thread_recipient} table for all recipients.
+ */
+function _privatemsg_thread_update($thread_id) {
+  // First, delete all existing information.
+  db_query('DELETE FROM {pm_thread_user} WHERE thread_id = %d', $thread_id);
+
+  $insert_sql = 'INSERT INTO {pm_thread_user} (thread_id, uid, started, last_updated, count, unread_count)';
+  $select_sql = "SELECT pmi.thread_id, pmi.recipient, MIN(pm.timestamp), MAX(pm.timestamp), COUNT(pmi.thread_id), SUM(pmi.is_new) FROM {pm_index} pmi INNER JOIN {pm_message} pm ON pm.mid = pmi.mid WHERE pmi.thread_id = %d AND pmi.type IN ('user') GROUP BY pmi.thread_id, pmi.recipient";
+
+  db_query($insert_sql . ' ' . $select_sql, $thread_id);
+}
+
+/**
  * Returns a link to send message form for a specific users.
  *
  * Contains permission checks of author/recipient, blocking and
-- 
1.7.4.1


From 76b208b22d1720c68db0cb5625f46ac9105b2b10 Mon Sep 17 00:00:00 2001
From: Sascha Grossenbacher <saschagros@gmail.com>
Date: Fri, 18 Mar 2011 02:28:56 +0100
Subject: [PATCH 2/3] Converted list and unread query to pm_thread tables.

---
 privatemsg.module                          |   36 +++++++++++++--------------
 privatemsg_filter/privatemsg_filter.module |    4 +-
 2 files changed, 19 insertions(+), 21 deletions(-)

diff --git a/privatemsg.module b/privatemsg.module
index 38b93e4..94ea3c5 100644
--- a/privatemsg.module
+++ b/privatemsg.module
@@ -997,25 +997,27 @@ function privatemsg_privatemsg_name_lookup($string) {
  */
 
 function privatemsg_sql_list(&$fragments, $account, $argument = 'list') {
-  $fragments['primary_table'] = '{pm_message} pm';
+  $fragments['primary_table'] = '{pm_thread_user} ptu';
+
+  $fragments['inner_join'][]  = 'INNER JOIN {pm_thread} pt ON pt.thread_id = ptu.thread_id';
 
   // Load enabled columns.
   $fields = privatemsg_get_enabled_headers();
 
   // Required columns.
-  $fragments['select'][]      = 'pmi.thread_id';
+  $fragments['select'][]      = 'ptu.thread_id';
   // We have to use MIN as the subject might not be the same in some threads.
   // MIN() does not have a useful meaning except that it helps to correctly
   // aggregate the thread on PostgreSQL.
-  $fragments['select'][]      = 'MIN(pm.subject) as subject';
-  $fragments['select'][]      = 'MAX(pm.timestamp) as last_updated';
+  $fragments['select'][]      = 'pt.subject';
+  $fragments['select'][]      = 'ptu.last_updated';
   // We use SUM so that we can count the number of unread messages.
-  $fragments['select'][]      = 'SUM(pmi.is_new) as is_new';
+  $fragments['select'][]      = 'ptu.unread_count as is_new';
 
   // Select number of messages in the thread if the count is
   // set to be displayed.
   if (in_array('count', $fields)) {
-    $fragments['select'][]      = 'COUNT(distinct pmi.mid) as count';
+    $fragments['select'][]      = 'ptu.count';
   }
   if (in_array('participants', $fields)) {
     // Query for a string with uid's, for example "1,6,7".
@@ -1024,26 +1026,22 @@ function privatemsg_sql_list(&$fragments, $account, $argument = 'list') {
       // PostgreSQL does not know GROUP_CONCAT, so a subquery is required.
       $fragments['select'][]      = "array_to_string(array(SELECT DISTINCT pmia.type || '_' || textin(int4out(pmia.recipient))
                                                             FROM {pm_index} pmia
-                                                            WHERE pmia.type <> 'hidden' AND pmia.thread_id = pmi.thread_id AND pmia.recipient <> %d), ',') AS participants";
+                                                            WHERE pmia.type <> 'hidden' AND pmia.thread_id = ptu.thread_id AND pmia.recipient <> %d), ',') AS participants";
     }
     else {
       $fragments['select'][]      = "(SELECT GROUP_CONCAT(DISTINCT CONCAT(pmia.type, '_', pmia.recipient) SEPARATOR ',')
                                                             FROM {pm_index} pmia
-                                                            WHERE pmia.type = 'user' AND pmia.thread_id = pmi.thread_id AND pmia.recipient <> %d) AS participants";
+                                                            WHERE pmia.type = 'user' AND pmia.thread_id = ptu.thread_id AND pmia.recipient <> %d) AS participants";
     }
     $fragments['query_args']['select'][] = $account->uid;
   }
   if (in_array('thread_started', $fields)) {
-    $fragments['select'][]      = 'MIN(pm.timestamp) as thread_started';
+    $fragments['select'][]      = 'ptu.started AS thread_started';
   }
 
-  $fragments['inner_join'][]  = 'INNER JOIN {pm_index} pmi ON pm.mid = pmi.mid';
-
   // Only load undeleted messages of the current user and group by thread.
-  $fragments['where'][]      = "pmi.recipient = %d AND pmi.type IN ('user', 'hidden')";
+  $fragments['where'][]      = "ptu.uid = %d";
   $fragments['query_args']['where'][] = $account->uid;
-  $fragments['where'][]       = 'pmi.deleted = 0';
-  $fragments['group_by'][]    = 'pmi.thread_id';
 
   // tablesort_sql() generates a ORDER BY string. However, the "ORDER BY " part
   // is not needed and added by the query builder. Discard the first 9
@@ -1174,14 +1172,13 @@ function privatemsg_sql_participants(&$fragments, $thread_id, $account = NULL) {
  *   User object for which the messages are being counted.
  */
 function privatemsg_sql_unread_count(&$fragments, $account) {
-  $fragments['primary_table'] = '{pm_index} pmi';
+  $fragments['primary_table'] = '{pm_thread_user} ptu';
 
-  $fragments['select'][]      = 'COUNT(DISTINCT thread_id) as unread_count';
+  $fragments['select'][]      = 'COUNT(thread_id) as unread_count';
 
   // Only count new messages that have not been deleted.
-  $fragments['where'][]       = 'pmi.deleted = 0';
-  $fragments['where'][]       = 'pmi.is_new = 1';
-  $fragments['where'][]       = "pmi.recipient = %d AND pmi.type IN ('user', 'hidden')";
+  $fragments['where'][]       = 'ptu.uid = %d';
+  $fragments['where'][]       = 'ptu.unread_count > 0';
   $fragments['query_args']['where'][]  = $account->uid;
 }
 
@@ -2109,6 +2106,7 @@ function _privatemsg_assemble_query($query) {
       _db_query_callback($query_args_count, TRUE);
       $count = preg_replace_callback(DB_QUERY_REGEXP, '_db_query_callback', $count);
     }
+    drupal_set_message(str_replace(array('{', '}'), '', $query));
     return array('query' => $query, 'count' => $count);
   }
   return FALSE;
diff --git a/privatemsg_filter/privatemsg_filter.module b/privatemsg_filter/privatemsg_filter.module
index 652acf2..8f08711 100644
--- a/privatemsg_filter/privatemsg_filter.module
+++ b/privatemsg_filter/privatemsg_filter.module
@@ -643,7 +643,7 @@ function privatemsg_filter_privatemsg_sql_list_alter(&$fragments, $account, $arg
     $count = 0;
     if (!empty($filter['tags'])) {
       foreach ($filter['tags'] as $tag) {
-        $fragments['inner_join'][]  = "INNER JOIN {pm_tags_index} pmti$count ON (pmti$count.thread_id = pmi.thread_id AND pmti$count.uid = pmi.recipient AND pmi.type IN ('user', 'hidden'))";
+        $fragments['inner_join'][]  = "INNER JOIN {pm_tags_index} pmti$count ON (pmti$count.thread_id = ptu.thread_id AND pmti$count.uid = ptu.uid)";
         $fragments['where'][]       = "pmti$count.tag_id = %d";
         $fragments['query_args']['where'][]   = $tag;
         $count++;
@@ -652,7 +652,7 @@ function privatemsg_filter_privatemsg_sql_list_alter(&$fragments, $account, $arg
 
     if (!empty($filter['author'])) {
       foreach ($filter['author'] as $author) {
-        $fragments['inner_join'][]  = "INNER JOIN {pm_index} pmi$count ON (pmi$count.mid = pm.mid)";
+        $fragments['inner_join'][]  = "INNER JOIN {pm_index} pmi$count ON (pmi$count.thread_id = ptu.thread_id)";
         $fragments['where'][]       = "pmi$count.recipient = %d AND pmi$count.type = 'user'";
         $fragments['query_args']['where'][]   = $author->uid;
         $count++;
-- 
1.7.4.1


From 20c68c7a11a8be62d9e826b39bc2cc4f283c3ffb Mon Sep 17 00:00:00 2001
From: Sascha Grossenbacher <saschagros@gmail.com>
Date: Fri, 18 Mar 2011 18:15:44 +0100
Subject: [PATCH 3/3] Working on thread update API.

---
 privatemsg.install                           |   33 ++++-
 privatemsg.module                            |  210 +++++++++++++++++++++++---
 privatemsg_filter/privatemsg_filter.module   |    3 +-
 privatemsg_forward/privatemsg_forward.module |    1 +
 4 files changed, 225 insertions(+), 22 deletions(-)

diff --git a/privatemsg.install b/privatemsg.install
index 5da796e..65cb000 100644
--- a/privatemsg.install
+++ b/privatemsg.install
@@ -119,11 +119,17 @@ function privatemsg_schema() {
         'not null'      => TRUE,
         'unsigned'      => TRUE,
       ),
+      'last_sent'       => array(
+        'description'   => 'Indicates when this recipient sent the last message.',
+        'type'          => 'int',
+        'not null'      => FALSE,
+        'unsigned'      => TRUE,
+      ),
     ),
     'primary key' => array('thread_id', 'uid'),
   );
 
-  
+
   $schema['pm_message'] = array(
     'description'       => '{pm_messages} holds the message information',
     'fields'        => array(
@@ -813,6 +819,12 @@ function privatemsg_update_6205() {
         'not null'      => TRUE,
         'unsigned'      => TRUE,
       ),
+      'last_sent'       => array(
+        'description'   => 'Indicates when this recipient sent the last message.',
+        'type'          => 'int',
+        'not null'      => FALSE,
+        'unsigned'      => TRUE,
+      ),
     ),
     'primary key' => array('thread_id', 'uid'),
   );
@@ -821,4 +833,23 @@ function privatemsg_update_6205() {
   db_create_table($ret, 'pm_thread', $schema['pm_thread']);
   db_create_table($ret, 'pm_thread_user', $schema['pm_thread_user']);
   return $ret;
+}
+
+/**
+ * Populate {pm_thread} and {pm_thread_user}.
+ */
+function privatemsg_update_6206() {
+  // Make it easy to re-run the update.
+  db_query('DELETE FROM {pm_thread}');
+  db_query('DELETE FROM {pm_thread_user}');
+
+  // Populate {pm_thread}
+  $insert_sql = 'INSERT INTO {pm_thread} (thread_id, subject)';
+  $select_sql = 'SELECT pmi.thread_id, pm.subject FROM {pm_index} pmi INNER JOIN {pm_message} pm ON pmi.thread_id = pm.mid GROUP BY pmi.thread_id, pm.subject';
+  db_query($insert_sql . ' ' . $select_sql);
+
+  // Populate {pm_thread_user}.
+  $insert_sql_user = 'INSERT INTO {pm_thread_user} (thread_id, uid, started, last_updated, count, unread_count)';
+  $select_sql_user = "SELECT pmi.thread_id, pmi.recipient, MIN(pm.timestamp), MAX(pm.timestamp), COUNT(pmi.thread_id), SUM(pmi.is_new) FROM {pm_index} pmi INNER JOIN {pm_message} pm ON pm.mid = pmi.mid WHERE pmi.type IN ('user', 'hidden') GROUP BY pmi.thread_id, pmi.recipient";
+  db_query($insert_sql_user . ' ' . $select_sql_user);
 }
\ No newline at end of file
diff --git a/privatemsg.module b/privatemsg.module
index 94ea3c5..b1b61c1 100644
--- a/privatemsg.module
+++ b/privatemsg.module
@@ -778,6 +778,17 @@ function privatemsg_message_change_status($pmid, $status, $account = NULL) {
   $query = "UPDATE {pm_index} SET is_new = %d WHERE mid = %d AND recipient = %d AND type IN ('user', 'hidden')";
   db_query($query, $status, $pmid, $account->uid);
 
+  // Update pm_thread_user table.
+  _privatemsg_thread_update(array(
+    'condition' => array(
+      'uid' => $account->uid,
+      'mid' => $pmid,
+    ),
+    'update' => array(
+      'unread_count' => $status == PRIVATEMSG_READ ? '-' : '+',
+    ),
+  ));
+
   // Allows modules to respond to the status change.
   module_invoke_all('privatemsg_message_status_changed', $pmid, $status, $account);
 }
@@ -1043,6 +1054,9 @@ function privatemsg_sql_list(&$fragments, $account, $argument = 'list') {
   $fragments['where'][]      = "ptu.uid = %d";
   $fragments['query_args']['where'][] = $account->uid;
 
+  // Only show threads with messages.
+  $fragments['where'][]      = "ptu.count > 0";
+
   // tablesort_sql() generates a ORDER BY string. However, the "ORDER BY " part
   // is not needed and added by the query builder. Discard the first 9
   // characters of the string.
@@ -1469,14 +1483,29 @@ function privatemsg_message_change_delete($pmid, $delete, $account = NULL) {
     $delete_value = time();
   }
 
+  $command = array(
+    'condition' => array(
+      'mid' => $pmid,
+    ),
+    'update' => array(
+      'count' => $delete ? '-' : '+',
+    ),
+  );
+
   if ($account) {
     db_query("UPDATE {pm_index} SET deleted = %d WHERE mid = %d AND recipient = %d AND type IN ('user', 'hidden')", $delete_value, $pmid, $account->uid);
+
+    // Add specific uid to thread update command.
+    $command['condition']['uid'] = $account->uid;
   }
   else {
     // Mark deleted for all users.
     db_query('UPDATE {pm_index} SET deleted = %d WHERE mid = %d', $delete_value, $pmid);
   }
 
+    // Update pm_thread_user table.
+  _privatemsg_thread_update($command);
+
   // Allow modules to respond to the deleted changes.
   module_invoke_all('privatemsg_message_status_delete', $pmid, $delete, $account);
 }
@@ -1791,14 +1820,50 @@ function _privatemsg_send($message) {
 
   // We only want to add the author to the pm_index table, if the message has
   // not been sent directly to him.
+  $author_is_recipient = TRUE;
   if (!isset($message['recipients']['user_' . $message['author']->uid])) {
+    $author_is_recipient = FALSE;
     if (!db_query($index_sql, $mid, $message['thread_id'], $message['author']->uid, 'user', 0)) {
       return FALSE;
     }
   }
 
-  // Update {pm_thread_recipients} table.
-  _privatemsg_thread_update($message['thread_id']);
+  // If this is a new thread, initalize {pm_thread_user}
+  if ($message['thread_id'] == $message['mid']) {
+    _privatemsg_thread_refresh($message['thread_id']);
+  }
+  else {
+    // If this is a reply, update count, count_unread, last_updated for all
+    // recipients.
+    $command = array(
+      'condition' => array(
+        'mid' => $message['mid'],
+        'thread_id' => $message['thread_id'],
+      ),
+      'update' => array(
+        'count' => '+',
+        'unread_count' => '+',
+        'last_updated' => $message['timestamp'],
+      ),
+    );
+    _privatemsg_thread_update($command);
+  }
+
+  // Update last_sent for author for new threads and replies.
+  $command = array(
+    'condition' => array(
+      'thread_id' => $message['thread_id'],
+      'uid' => $message['author']->uid,
+    ),
+    'update' => array(
+      'last_sent' => $message['timestamp'],
+    ),
+  );
+  // If the author is not a recipient, set unread_count to 0 too.
+  if (!$author_is_recipient) {
+    $command['update']['unread_count'] = 0;
+  }
+  _privatemsg_thread_update($command);
 
   module_invoke_all('privatemsg_message_insert', $message);
 
@@ -1809,14 +1874,96 @@ function _privatemsg_send($message) {
 /**
  * Updates the {pm_thread_recipient} table for all recipients.
  */
-function _privatemsg_thread_update($thread_id) {
-  // First, delete all existing information.
-  db_query('DELETE FROM {pm_thread_user} WHERE thread_id = %d', $thread_id);
+function _privatemsg_thread_refresh($thread_id, $uid = NULL) {
+  if ($uid) {
+    db_query('DELETE FROM {pm_thread_user} WHERE thread_id = %d AND uid = %d', $thread_id, $uid);
+  }
+  else {
+    db_query('DELETE FROM {pm_thread_user} WHERE thread_id = %d', $thread_id);
+  }
 
+  $condition = '';
+  if ($uid) {
+    $condition = 'AND pmi.recipient = %d ';
+  }
   $insert_sql = 'INSERT INTO {pm_thread_user} (thread_id, uid, started, last_updated, count, unread_count)';
-  $select_sql = "SELECT pmi.thread_id, pmi.recipient, MIN(pm.timestamp), MAX(pm.timestamp), COUNT(pmi.thread_id), SUM(pmi.is_new) FROM {pm_index} pmi INNER JOIN {pm_message} pm ON pm.mid = pmi.mid WHERE pmi.thread_id = %d AND pmi.type IN ('user') GROUP BY pmi.thread_id, pmi.recipient";
+  $select_sql = "SELECT pmi.thread_id, pmi.recipient, MIN(pm.timestamp), MAX(pm.timestamp), COUNT(pmi.thread_id), SUM(pmi.is_new) FROM {pm_index} pmi INNER JOIN {pm_message} pm ON pm.mid = pmi.mid WHERE pmi.thread_id = %d AND pmi.type IN ('user', 'hidden')$condition GROUP BY pmi.thread_id, pmi.recipient";
 
-  db_query($insert_sql . ' ' . $select_sql, $thread_id);
+  if ($uid) {
+    db_query($insert_sql . ' ' . $select_sql, $thread_id, $uid);
+  }
+  else {
+    db_query($insert_sql . ' ' . $select_sql, $thread_id);
+  }
+}
+
+
+function _privatemsg_thread_update($command) {
+  $conditions = array();
+  $condition_args = array();
+  $updates = array();
+  $update_args = array();
+  foreach ($command['condition'] as $key => $value) {
+    switch ($key) {
+      case 'thread_id':
+      case 'uid':
+        $conditions[] = $key . ' = %d';
+        $condition_args[] = $value;
+
+        break;
+      case 'mid':
+        // mid is not an actual existing field. Instead, first select the
+        // thread_id if not already given.
+        if (empty($command['condition']['thread_id'])) {
+          $thread_id = db_result(db_query_range('SELECT thread_id FROM {pm_index} WHERE mid = %d', $value, 0, 1));
+          $conditions[] = 'thread_id = %d';
+          $condition_args[] = $thread_id;
+        }
+        // If there is no explicit uid definition, apply for all recipients of this message.
+        if (empty($command['condition']['uid'])) {
+          $conditions[] = "uid IN (SELECT pmi.recipient FROM {pm_index} pmi WHERE pmi.mid = %d AND pmi.type IN ('user', 'hidden'))";
+          $condition_args[] = $value;
+        }
+        break;
+    }
+  }
+  foreach ($command['update'] as $key => $value) {
+    switch ($key) {
+      case 'unread_count':
+      case 'count';
+      case 'last_sent':
+      case 'last_updated':
+        _privatemsg_thread_update_build_update($updates, $update_args, $key, $value);
+        break;
+
+      default:
+        break;
+    }
+  }
+
+  $sql = 'UPDATE {pm_thread_user} SET ' . implode(', ', $updates) . ' WHERE (' . implode(') AND (', $conditions) . ')';
+  $args = array_merge($update_args, $condition_args);
+  db_query($sql, $args);
+}
+
+/**
+ *
+ * @param type $updates
+ * @param type $update_args
+ * @param type $key
+ * @param type $value
+ */
+function _privatemsg_thread_update_build_update(&$updates, &$update_args, $key, $value) {
+  if ($value === '+') {
+    $updates[] = $key . ' = ' . $key . ' + 1';
+  }
+  elseif ($value === '-') {
+    $updates[] = $key . ' = ' . $key . ' - 1';
+  }
+  else {
+    $updates[] = $key . ' = %d';
+    $update_args[] = $value;
+  }
 }
 
 /**
@@ -2106,7 +2253,7 @@ function _privatemsg_assemble_query($query) {
       _db_query_callback($query_args_count, TRUE);
       $count = preg_replace_callback(DB_QUERY_REGEXP, '_db_query_callback', $count);
     }
-    drupal_set_message(str_replace(array('{', '}'), '', $query));
+    //drupal_set_message(str_replace(array('{', '}'), '', $query));
     return array('query' => $query, 'count' => $count);
   }
   return FALSE;
@@ -2136,17 +2283,28 @@ function privatemsg_thread_change_status($threads, $status, $account = NULL) {
 
   // Record which messages will change status.
   $changed = array();
-  $result = db_query("SELECT mid FROM {pm_index} WHERE is_new <> %d AND recipient = %d and type IN ('user', 'hidden') AND thread_id IN (" . db_placeholders($threads) . ')', $params);
+  $result = db_query("SELECT mid, thread_id FROM {pm_index} WHERE is_new <> %d AND recipient = %d and type IN ('user', 'hidden') AND thread_id IN (" . db_placeholders($threads) . ')', $params);
   while($row = db_fetch_object($result)) {
-    $changed[] = $row->mid;
+    $changed[$row->thread_id][] = $row->mid;
   }
-
   // Update the status of the threads.
   db_query("UPDATE {pm_index} SET is_new = %d WHERE recipient = %d and type IN ('user', 'hidden') AND thread_id IN (" . db_placeholders($threads) . ')', $params);
 
-  // Allow modules to respond to the status changes.
-  foreach ($changed as $mid) {
-    module_invoke_all('privatemsg_message_status_changed', $mid, $status, $account);
+  foreach ($changed as $thread_id => $mids) {
+    // Allow modules to respond to the status changes.
+    foreach ($mids as $mid) {
+      module_invoke_all('privatemsg_message_status_changed', $mid, $status, $account);
+    }
+    // Update pm_thread_user table.
+    _privatemsg_thread_update(array(
+      'condition' => array(
+        'thread_id' => $thread_id,
+        'uid' => $account->uid,
+      ),
+      'update' => array(
+        'unread_count' => $status == PRIVATEMSG_READ ? 0 : count($mids),
+      ),
+    ));
   }
 
   if ($status == PRIVATEMSG_UNREAD) {
@@ -2248,9 +2406,9 @@ function privatemsg_thread_change_delete($threads, $delete, $account = NULL) {
   // Record which messages will be deleted.
   $changed = array();
   $cond = $delete ? '=' : '>';
-  $result = db_query("SELECT mid FROM {pm_index} WHERE deleted $cond 0 AND recipient = %d and type IN ('user', 'hidden') AND thread_id IN (" . db_placeholders($threads) . ')', array_merge(array($account->uid), $threads));
+  $result = db_query("SELECT mid, thread_id FROM {pm_index} WHERE deleted $cond 0 AND recipient = %d and type IN ('user', 'hidden') AND thread_id IN (" . db_placeholders($threads) . ')', array_merge(array($account->uid), $threads));
   while($row = db_fetch_object($result)) {
-    $changed[] = $row->mid;
+    $changed[$row->thread_id][] = $row->mid;
   }
 
   // Merge status and uid with the threads list. array_merge() will not overwrite/ignore thread_id 1.
@@ -2262,9 +2420,21 @@ function privatemsg_thread_change_delete($threads, $delete, $account = NULL) {
     // Update the status of the threads.
   db_query("UPDATE {pm_index} SET deleted = %d WHERE recipient = %d and type IN ('user', 'hidden') AND thread_id IN (" . db_placeholders($threads) . ')', $params);
 
-  // Allow modules to respond to the deleted changes.
-  foreach ($changed as $mid) {
-    module_invoke_all('privatemsg_message_status_delete', $mid, $delete, $account);
+  foreach ($changed as $thread_id => $mids) {
+    // Allow modules to respond to the delete changes.
+    foreach ($mids as $mid) {
+      module_invoke_all('privatemsg_message_delete changed', $mid, $delete, $account);
+    }
+    // Update pm_thread_user table.
+    _privatemsg_thread_update(array(
+      'condition' => array(
+        'thread_id' => $thread_id,
+        'uid' => $account->uid,
+      ),
+      'update' => array(
+        'count' => $delete ? 0 : count($mids),
+      ),
+    ));
   }
 
   if ($delete) {
@@ -2584,6 +2754,8 @@ function privatemsg_message_change_recipient($mid, $uid, $type = 'user', $add =
     }
     db_query($delete_sql, $mid, $thread_id, $uid, $type);
   }
+  // Refresh the thread for that user only.
+  _privatemsg_thread_refresh($thread_id, $uid);
   module_invoke_all('privatemsg_message_recipient_changed', $mid, $thread_id, $uid, $type, $add);
 }
 
diff --git a/privatemsg_filter/privatemsg_filter.module b/privatemsg_filter/privatemsg_filter.module
index 8f08711..720bc1f 100644
--- a/privatemsg_filter/privatemsg_filter.module
+++ b/privatemsg_filter/privatemsg_filter.module
@@ -630,8 +630,7 @@ function privatemsg_filter_privatemsg_sql_list_alter(&$fragments, $account, $arg
 
   // Check if its a filtered view.
   if ($argument == 'sent') {
-    $fragments['where'][]       = "pm.author = %d";
-    $fragments['query_args']['where'][]   = $account->uid;
+    $fragments['where'][]       = "ptu.last_sent > 0";
   }
   $filter = privatemsg_filter_get_filter($account);
   if ($argument == 'inbox') {
diff --git a/privatemsg_forward/privatemsg_forward.module b/privatemsg_forward/privatemsg_forward.module
index 45dc077..900a118 100644
--- a/privatemsg_forward/privatemsg_forward.module
+++ b/privatemsg_forward/privatemsg_forward.module
@@ -134,6 +134,7 @@ function privatemsg_forward_form_delete_submit($form, &$form_state) {
   global $user;
   if ($form_state['values']['remove']) {
     db_query("DELETE FROM {pm_index} WHERE thread_id = %d AND recipient = %d AND type IN ('hidden', 'user')", $form_state['values']['thread_id'], $user->uid);
+    _privatemsg_thread_refresh($form_state['values']['thread_id'], $user->uid);
 
     // Display message and redirect to messages if user has removed himself.
     $form_state['redirect'] = 'messages';
-- 
1.7.4.1

