X-Git-Url: https://git.mxchange.org/?a=blobdiff_plain;f=classes%2FNotice.php;h=fca1c599ce39830993f8304397a11cefb8470799;hb=384387c9b05aefb438f5dbe7e272b1f234ede172;hp=3e55bd6fa5b1c6e0294730883ed551b77ad8142f;hpb=90a2563a90e1bf2239d177a564bbe3e838d93dc4;p=quix0rs-gnu-social.git diff --git a/classes/Notice.php b/classes/Notice.php index 3e55bd6fa5..fca1c599ce 100644 --- a/classes/Notice.php +++ b/classes/Notice.php @@ -63,7 +63,7 @@ class Notice extends Memcached_DataObject public $created; // datetime multiple_key not_null default_0000-00-00%2000%3A00%3A00 public $modified; // timestamp not_null default_CURRENT_TIMESTAMP public $reply_to; // int(4) - public $is_local; // tinyint(1) + public $is_local; // int(4) public $source; // varchar(32) public $conversation; // int(4) public $lat; // decimal(10,7) @@ -94,10 +94,6 @@ class Notice extends Memcached_DataObject function delete() { - $this->blowCaches(true); - $this->blowFavesCache(true); - $this->blowSubsCache(true); - // For auditing purposes, save a record that the notice // was deleted. @@ -109,32 +105,20 @@ class Notice extends Memcached_DataObject $deleted->created = $this->created; $deleted->deleted = common_sql_now(); - $this->query('BEGIN'); - $deleted->insert(); - //Null any notices that are replies to this notice - $this->query(sprintf("UPDATE notice set reply_to = null WHERE reply_to = %d", $this->id)); - - //Null any notices that are repeats of this notice - //XXX: probably need to uncache these, too + // Clear related records - $this->query(sprintf("UPDATE notice set repeat_of = null WHERE repeat_of = %d", $this->id)); + $this->clearReplies(); + $this->clearRepeats(); + $this->clearFaves(); + $this->clearTags(); + $this->clearGroupInboxes(); - $related = array('Reply', - 'Fave', - 'Notice_tag', - 'Group_inbox', - 'Queue_item', - 'Notice_inbox'); + // NOTE: we don't clear inboxes + // NOTE: we don't clear queue items - foreach ($related as $cls) { - $inst = new $cls(); - $inst->notice_id = $this->id; - $inst->delete(); - } $result = parent::delete(); - $this->query('COMMIT'); } function saveTags() @@ -156,6 +140,7 @@ class Notice extends Memcached_DataObject foreach(array_unique($hashtags) as $hashtag) { /* elide characters we don't want in the tag */ $this->saveTag($hashtag); + self::blow('profile:notice_ids_tagged:%d:%s', $this->profile_id, $hashtag); } return true; } @@ -173,6 +158,9 @@ class Notice extends Memcached_DataObject $last_error->message)); return; } + + // if it's saved, blow its cache + $tag->blowCache(false); } /** @@ -276,7 +264,6 @@ class Notice extends Memcached_DataObject if (isset($repeat_of)) { $notice->repeat_of = $repeat_of; - $notice->reply_to = $repeat_of; } else { $notice->reply_to = self::getReplyTo($reply_to, $profile_id, $source, $final); } @@ -300,8 +287,6 @@ class Notice extends Memcached_DataObject // XXX: some of these functions write to the DB - $notice->query('BEGIN'); - $id = $notice->insert(); if (!$id) { @@ -335,25 +320,41 @@ class Notice extends Memcached_DataObject } } - // XXX: do we need to change this for remote users? + } - $notice->saveTags(); + # Clear the cache for subscribed users, so they'll update at next request + # XXX: someone clever could prepend instead of clearing the cache + $notice->blowOnInsert(); - $notice->addToInboxes(); + $notice->distribute(); - $notice->saveUrls(); + return $notice; + } - $notice->query('COMMIT'); + function blowOnInsert() + { + self::blow('profile:notice_ids:%d', $this->profile_id); + self::blow('public'); - Event::handle('EndNoticeSave', array($notice)); + if ($this->conversation != $this->id) { + self::blow('notice:conversation_ids:%d', $this->conversation); } - # Clear the cache for subscribed users, so they'll update at next request - # XXX: someone clever could prepend instead of clearing the cache + if (!empty($this->repeat_of)) { + self::blow('notice:repeats:%d', $this->repeat_of); + } - $notice->blowCaches(); + $original = Notice::staticGet('id', $this->repeat_of); - return $notice; + if (!empty($original)) { + $originalUser = User::staticGet('id', $original->profile_id); + if (!empty($originalUser)) { + self::blow('user:repeats_of_me:%d', $originalUser->id); + } + } + + $profile = Profile::staticGet($this->profile_id); + $profile->blowNoticeCount(); } /** save all urls in the notice to the db @@ -458,241 +459,6 @@ class Notice extends Memcached_DataObject return $att; } - function blowCaches($blowLast=false) - { - $this->blowSubsCache($blowLast); - $this->blowNoticeCache($blowLast); - $this->blowRepliesCache($blowLast); - $this->blowPublicCache($blowLast); - $this->blowTagCache($blowLast); - $this->blowGroupCache($blowLast); - $this->blowConversationCache($blowLast); - $this->blowRepeatCache(); - $profile = Profile::staticGet($this->profile_id); - $profile->blowNoticeCount(); - } - - function blowRepeatCache() - { - if (!empty($this->repeat_of)) { - $cache = common_memcache(); - if (!empty($cache)) { - // XXX: only blow if <100 in cache - $ck = common_cache_key('notice:repeats:'.$this->repeat_of); - $result = $cache->delete($ck); - - $user = User::staticGet('id', $this->profile_id); - - if (!empty($user)) { - $uk = common_cache_key('user:repeated_by_me:'.$user->id); - $cache->delete($uk); - $user->free(); - unset($user); - } - - $original = Notice::staticGet('id', $this->repeat_of); - - if (!empty($original)) { - $originalUser = User::staticGet('id', $original->profile_id); - if (!empty($originalUser)) { - $ouk = common_cache_key('user:repeats_of_me:'.$originalUser->id); - $cache->delete($ouk); - $originalUser->free(); - unset($originalUser); - } - $original->free(); - unset($original); - } - - $ni = new Notice_inbox(); - - $ni->notice_id = $this->id; - - if ($ni->find()) { - while ($ni->fetch()) { - $tmk = common_cache_key('user:repeated_to_me:'.$ni->user_id); - $cache->delete($tmk); - } - } - - $ni->free(); - unset($ni); - } - } - } - - function blowConversationCache($blowLast=false) - { - $cache = common_memcache(); - if ($cache) { - $ck = common_cache_key('notice:conversation_ids:'.$this->conversation); - $cache->delete($ck); - if ($blowLast) { - $cache->delete($ck.';last'); - } - } - } - - function blowGroupCache($blowLast=false) - { - $cache = common_memcache(); - if ($cache) { - $group_inbox = new Group_inbox(); - $group_inbox->notice_id = $this->id; - if ($group_inbox->find()) { - while ($group_inbox->fetch()) { - $cache->delete(common_cache_key('user_group:notice_ids:' . $group_inbox->group_id)); - if ($blowLast) { - $cache->delete(common_cache_key('user_group:notice_ids:' . $group_inbox->group_id.';last')); - } - $member = new Group_member(); - $member->group_id = $group_inbox->group_id; - if ($member->find()) { - while ($member->fetch()) { - $cache->delete(common_cache_key('notice_inbox:by_user:' . $member->profile_id)); - $cache->delete(common_cache_key('notice_inbox:by_user_own:' . $member->profile_id)); - if (empty($this->repeat_of)) { - $cache->delete(common_cache_key('user:friends_timeline:' . $member->profile_id)); - $cache->delete(common_cache_key('user:friends_timeline_own:' . $member->profile_id)); - } - if ($blowLast) { - $cache->delete(common_cache_key('notice_inbox:by_user:' . $member->profile_id . ';last')); - $cache->delete(common_cache_key('notice_inbox:by_user_own:' . $member->profile_id . ';last')); - if (empty($this->repeat_of)) { - $cache->delete(common_cache_key('user:friends_timeline:' . $member->profile_id . ';last')); - $cache->delete(common_cache_key('user:friends_timeline_own:' . $member->profile_id . ';last')); - } - } - } - } - } - } - $group_inbox->free(); - unset($group_inbox); - } - } - - function blowTagCache($blowLast=false) - { - $cache = common_memcache(); - if ($cache) { - $tag = new Notice_tag(); - $tag->notice_id = $this->id; - if ($tag->find()) { - while ($tag->fetch()) { - $tag->blowCache($blowLast); - $ck = 'profile:notice_ids_tagged:' . $this->profile_id . ':' . $tag->tag; - - $cache->delete($ck); - if ($blowLast) { - $cache->delete($ck . ';last'); - } - } - } - $tag->free(); - unset($tag); - } - } - - function blowSubsCache($blowLast=false) - { - $cache = common_memcache(); - if ($cache) { - $user = new User(); - - $UT = common_config('db','type')=='pgsql'?'"user"':'user'; - $user->query('SELECT id ' . - - "FROM $UT JOIN subscription ON $UT.id = subscription.subscriber " . - 'WHERE subscription.subscribed = ' . $this->profile_id); - - while ($user->fetch()) { - $cache->delete(common_cache_key('notice_inbox:by_user:'.$user->id)); - $cache->delete(common_cache_key('notice_inbox:by_user_own:'.$user->id)); - if (empty($this->repeat_of)) { - $cache->delete(common_cache_key('user:friends_timeline:'.$user->id)); - $cache->delete(common_cache_key('user:friends_timeline_own:'.$user->id)); - } - if ($blowLast) { - $cache->delete(common_cache_key('notice_inbox:by_user:'.$user->id.';last')); - $cache->delete(common_cache_key('notice_inbox:by_user_own:'.$user->id.';last')); - if (empty($this->repeat_of)) { - $cache->delete(common_cache_key('user:friends_timeline:'.$user->id.';last')); - $cache->delete(common_cache_key('user:friends_timeline_own:'.$user->id.';last')); - } - } - } - $user->free(); - unset($user); - } - } - - function blowNoticeCache($blowLast=false) - { - if ($this->is_local) { - $cache = common_memcache(); - if (!empty($cache)) { - $cache->delete(common_cache_key('profile:notice_ids:'.$this->profile_id)); - if ($blowLast) { - $cache->delete(common_cache_key('profile:notice_ids:'.$this->profile_id.';last')); - } - } - } - } - - function blowRepliesCache($blowLast=false) - { - $cache = common_memcache(); - if ($cache) { - $reply = new Reply(); - $reply->notice_id = $this->id; - if ($reply->find()) { - while ($reply->fetch()) { - $cache->delete(common_cache_key('reply:stream:'.$reply->profile_id)); - if ($blowLast) { - $cache->delete(common_cache_key('reply:stream:'.$reply->profile_id.';last')); - } - } - } - $reply->free(); - unset($reply); - } - } - - function blowPublicCache($blowLast=false) - { - if ($this->is_local == Notice::LOCAL_PUBLIC) { - $cache = common_memcache(); - if ($cache) { - $cache->delete(common_cache_key('public')); - if ($blowLast) { - $cache->delete(common_cache_key('public').';last'); - } - } - } - } - - function blowFavesCache($blowLast=false) - { - $cache = common_memcache(); - if ($cache) { - $fave = new Fave(); - $fave->notice_id = $this->id; - if ($fave->find()) { - while ($fave->fetch()) { - $cache->delete(common_cache_key('fave:ids_by_user:'.$fave->user_id)); - $cache->delete(common_cache_key('fave:by_user_own:'.$fave->user_id)); - if ($blowLast) { - $cache->delete(common_cache_key('fave:ids_by_user:'.$fave->user_id.';last')); - $cache->delete(common_cache_key('fave:by_user_own:'.$fave->user_id.';last')); - } - } - } - $fave->free(); - unset($fave); - } - } - function getStreamByIds($ids) { $cache = common_memcache(); @@ -842,11 +608,28 @@ class Notice extends Memcached_DataObject return $ids; } - function addToInboxes() + /** + * @param $groups array of Group *objects* + * @param $recipients array of profile *ids* + */ + function whoGets($groups=null, $recipients=null) { - // XXX: loads constants + $c = self::memcache(); - $inbox = new Notice_inbox(); + if (!empty($c)) { + $ni = $c->get(common_cache_key('notice:who_gets:'.$this->id)); + if ($ni !== false) { + return $ni; + } + } + + if (is_null($groups)) { + $groups = $this->getGroups(); + } + + if (is_null($recipients)) { + $recipients = $this->getReplies(); + } $users = $this->getSubscribedUsers(); @@ -860,7 +643,6 @@ class Notice extends Memcached_DataObject $ni[$id] = NOTICE_INBOX_SOURCE_SUB; } - $groups = $this->saveGroups(); $profile = $this->getProfile(); foreach ($groups as $group) { @@ -875,8 +657,6 @@ class Notice extends Memcached_DataObject } } - $recipients = $this->saveReplies(); - foreach ($recipients as $recipient) { if (!array_key_exists($recipient, $ni)) { @@ -887,7 +667,19 @@ class Notice extends Memcached_DataObject } } - Notice_inbox::bulkInsert($this->id, $this->created, $ni); + if (!empty($c)) { + // XXX: pack this data better + $c->set(common_cache_key('notice:who_gets:'.$this->id), $ni); + } + + return $ni; + } + + function addToInboxes($groups, $recipients) + { + $ni = $this->whoGets($groups, $recipients); + + Inbox::bulkInsert($this->id, array_keys($ni)); return; } @@ -919,8 +711,17 @@ class Notice extends Memcached_DataObject return $ids; } + /** + * @return array of Group objects + */ function saveGroups() { + // Don't save groups for repeats + + if (!empty($this->repeat_of)) { + return array(); + } + $groups = array(); /* extract all !group */ @@ -980,7 +781,14 @@ class Notice extends Memcached_DataObject $gi->notice_id = $this->id; $gi->created = $this->created; - return $gi->insert(); + $result = $gi->insert(); + + if (!result) { + common_log_db_error($gi, 'INSERT', __FILE__); + throw new ServerException(_('Problem saving group inbox.')); + } + + self::blow('user_group:notice_ids:%d', $gi->group_id); } return true; @@ -991,6 +799,12 @@ class Notice extends Memcached_DataObject */ function saveReplies() { + // Don't save reply data for repeats + + if (!empty($this->repeat_of)) { + return array(); + } + // Alternative reply format $tname = false; if (preg_match('/^T ([A-Z0-9]{1,64}) /', $this->content, $match)) { @@ -1069,7 +883,8 @@ class Notice extends Memcached_DataObject foreach ($recipientIds as $recipientId) { $user = User::staticGet('id', $recipientId); - if ($user) { + if (!empty($user)) { + self::blow('reply:stream:%d', $reply->profile_id); mail_notify_attn($user, $this); } } @@ -1077,6 +892,63 @@ class Notice extends Memcached_DataObject return $recipientIds; } + function getReplies() + { + // XXX: cache me + + $ids = array(); + + $reply = new Reply(); + $reply->selectAdd(); + $reply->selectAdd('profile_id'); + $reply->notice_id = $this->id; + + if ($reply->find()) { + while($reply->fetch()) { + $ids[] = $reply->profile_id; + } + } + + $reply->free(); + + return $ids; + } + + /** + * Same calculation as saveGroups but without the saving + * @fixme merge the functions + * @return array of Group objects + */ + function getGroups() + { + // Don't save groups for repeats + + if (!empty($this->repeat_of)) { + return array(); + } + + // XXX: cache me + + $groups = array(); + + $gi = new Group_inbox(); + + $gi->selectAdd(); + $gi->selectAdd('group_id'); + + $gi->notice_id = $this->id; + + if ($gi->find()) { + while ($gi->fetch()) { + $groups[] = clone($gi); + } + } + + $gi->free(); + + return $groups; + } + function asAtomEntry($namespace=false, $source=false) { $profile = $this->getProfile(); @@ -1207,7 +1079,7 @@ class Notice extends Memcached_DataObject $idstr = $cache->get($idkey); - if (!empty($idstr)) { + if ($idstr !== false) { // Cache hit! Woohoo! $window = explode(',', $idstr); $ids = array_slice($window, $offset, $limit); @@ -1216,7 +1088,7 @@ class Notice extends Memcached_DataObject $laststr = $cache->get($idkey.';last'); - if (!empty($laststr)) { + if ($laststr !== false) { $window = explode(',', $laststr); $last_id = $window[0]; $new_ids = call_user_func_array($fn, array_merge($args, array(0, NOTICE_CACHE_WINDOW, @@ -1304,6 +1176,10 @@ class Notice extends Memcached_DataObject // Figure out who that is. $sender = Profile::staticGet('id', $profile_id); + if (empty($sender)) { + return null; + } + $recipient = common_relative_profile($sender, $nickname, common_sql_now()); if (empty($recipient)) { @@ -1356,12 +1232,21 @@ class Notice extends Memcached_DataObject { $author = Profile::staticGet('id', $this->profile_id); - // FIXME: truncate on long repeats...? - $content = sprintf(_('RT @%1$s %2$s'), $author->nickname, $this->content); + $maxlen = common_config('site', 'textlimit'); + if ($maxlen > 0 && mb_strlen($content) > $maxlen) { + // Web interface and current Twitter API clients will + // pull the original notice's text, but some older + // clients and RSS/Atom feeds will see this trimmed text. + // + // Unfortunately this is likely to lose tags or URLs + // at the end of long notices. + $content = mb_substr($content, 0, $maxlen - 4) . ' ...'; + } + return self::saveNew($repeater_id, $content, $source, array('repeat_of' => $this->id)); } @@ -1376,7 +1261,7 @@ class Notice extends Memcached_DataObject $ids = $this->_repeatStreamDirect($limit); } else { $idstr = $cache->get(common_cache_key('notice:repeats:'.$this->id)); - if (!empty($idstr)) { + if ($idstr !== false) { $ids = explode(',', $idstr); } else { $ids = $this->_repeatStreamDirect(100); @@ -1462,4 +1347,150 @@ class Notice extends Memcached_DataObject return $options; } + + function clearReplies() + { + $replyNotice = new Notice(); + $replyNotice->reply_to = $this->id; + + //Null any notices that are replies to this notice + + if ($replyNotice->find()) { + while ($replyNotice->fetch()) { + $orig = clone($replyNotice); + $replyNotice->reply_to = null; + $replyNotice->update($orig); + } + } + + // Reply records + + $reply = new Reply(); + $reply->notice_id = $this->id; + + if ($reply->find()) { + while($reply->fetch()) { + self::blow('reply:stream:%d', $reply->profile_id); + $reply->delete(); + } + } + + $reply->free(); + } + + function clearRepeats() + { + $repeatNotice = new Notice(); + $repeatNotice->repeat_of = $this->id; + + //Null any notices that are repeats of this notice + + if ($repeatNotice->find()) { + while ($repeatNotice->fetch()) { + $orig = clone($repeatNotice); + $repeatNotice->repeat_of = null; + $repeatNotice->update($orig); + } + } + } + + function clearFaves() + { + $fave = new Fave(); + $fave->notice_id = $this->id; + + if ($fave->find()) { + while ($fave->fetch()) { + self::blow('fave:ids_by_user_own:%d', $fave->user_id); + self::blow('fave:ids_by_user_own:%d;last', $fave->user_id); + self::blow('fave:ids_by_user:%d', $fave->user_id); + self::blow('fave:ids_by_user:%d;last', $fave->user_id); + $fave->delete(); + } + } + + $fave->free(); + } + + function clearTags() + { + $tag = new Notice_tag(); + $tag->notice_id = $this->id; + + if ($tag->find()) { + while ($tag->fetch()) { + self::blow('profile:notice_ids_tagged:%d:%s', $this->profile_id, common_keyize($tag->tag)); + self::blow('profile:notice_ids_tagged:%d:%s;last', $this->profile_id, common_keyize($tag->tag)); + self::blow('notice_tag:notice_ids:%s', common_keyize($tag->tag)); + self::blow('notice_tag:notice_ids:%s;last', common_keyize($tag->tag)); + $tag->delete(); + } + } + + $tag->free(); + } + + function clearGroupInboxes() + { + $gi = new Group_inbox(); + + $gi->notice_id = $this->id; + + if ($gi->find()) { + while ($gi->fetch()) { + self::blow('user_group:notice_ids:%d', $gi->group_id); + $gi->delete(); + } + } + + $gi->free(); + } + + function distribute() + { + if (common_config('queue', 'inboxes')) { + // If there's a failure, we want to _force_ + // distribution at this point. + try { + $qm = QueueManager::get(); + $qm->enqueue($this, 'distrib'); + } catch (Exception $e) { + // If the exception isn't transient, this + // may throw more exceptions as DQH does + // its own enqueueing. So, we ignore them! + try { + $handler = new DistribQueueHandler(); + $handler->handle($this); + } catch (Exception $e) { + common_log(LOG_ERR, "emergency redistribution resulted in " . $e->getMessage()); + } + // Re-throw so somebody smarter can handle it. + throw $e; + } + } else { + $handler = new DistribQueueHandler(); + $handler->handle($this); + } + } + + function insert() + { + $result = parent::insert(); + + if ($result) { + // Profile::hasRepeated() abuses pkeyGet(), so we + // have to clear manually + if (!empty($this->repeat_of)) { + $c = self::memcache(); + if (!empty($c)) { + $ck = self::multicacheKey('Notice', + array('profile_id' => $this->profile_id, + 'repeat_of' => $this->repeat_of)); + $c->delete($ck); + } + } + } + + return $result; + } }