diff --git a/classes/Fave.php b/classes/Fave.php index e8fdbffc71..5067185c0e 100644 --- a/classes/Fave.php +++ b/classes/Fave.php @@ -44,7 +44,7 @@ class Fave extends Memcached_DataObject common_log_db_error($fave, 'INSERT', __FILE__); return false; } - self::blow('fave:by_notice:%d', $fave->notice_id); + self::blow('fave:list:notice_id:%d', $fave->notice_id); Event::handle('EndFavorNotice', array($profile, $notice)); } @@ -62,7 +62,7 @@ class Fave extends Memcached_DataObject if (Event::handle('StartDisfavorNotice', array($profile, $notice, &$result))) { $result = parent::delete(); - self::blow('fave:by_notice:%d', $this->notice_id); + self::blow('fave:list:notice_id:%d', $this->notice_id); if ($result) { Event::handle('EndDisfavorNotice', array($profile, $notice)); @@ -156,31 +156,4 @@ class Fave extends Memcached_DataObject return $fav; } - - /** - * Grab a list of profile who have favored this notice. - * - * @return ArrayWrapper masquerading as a Fave - */ - static function byNotice($noticeId) - { - $c = self::memcache(); - $key = Cache::key('fave:by_notice:' . $noticeId); - - $wrapper = $c->get($key); - if (!$wrapper) { - // @fixme caching & scalability! - $fave = new Fave(); - $fave->notice_id = $noticeId; - $fave->find(); - - $list = array(); - while ($fave->fetch()) { - $list[] = clone($fave); - } - $wrapper = new ArrayWrapper($list); - $c->set($key, $wrapper); - } - return $wrapper; - } } diff --git a/classes/File.php b/classes/File.php index 36ffff585c..767a108087 100644 --- a/classes/File.php +++ b/classes/File.php @@ -55,23 +55,6 @@ class File extends Memcached_DataObject return 'http://www.facebook.com/login.php' === $url; } - /** - * Get the attachments for a particlar notice. - * - * @param int $post_id - * @return array of File objects - */ - static function getAttachments($post_id) { - $file = new File(); - $query = "select file.* from file join file_to_post on (file_id = file.id) where post_id = " . $file->escape($post_id); - $file = Memcached_DataObject::cachedQuery('File', $query); - $att = array(); - while ($file->fetch()) { - $att[] = clone($file); - } - return $att; - } - /** * Save a new file record. * diff --git a/classes/Memcached_DataObject.php b/classes/Memcached_DataObject.php index 9c92003e5c..e1610c56b2 100644 --- a/classes/Memcached_DataObject.php +++ b/classes/Memcached_DataObject.php @@ -156,6 +156,44 @@ class Memcached_DataObject extends Safe_DataObject return $result; } + + function listGet($cls, $keyCol, $keyVals) + { + $result = array_fill_keys($keyVals, array()); + + $toFetch = array(); + + foreach ($keyVals as $keyVal) { + $l = self::cacheGet(sprintf("%s:list:%s:%s", $cls, $keyCol, $keyVal)); + if ($l !== false) { + $result[$keyVal] = $l; + } else { + $toFetch[] = $keyVal; + } + } + + if (count($toFetch) > 0) { + $i = DB_DataObject::factory($cls); + if (empty($i)) { + throw new Exception(_('Cannot instantiate class ' . $cls)); + } + $i->whereAddIn($keyCol, $toFetch, $i->columnType($keyCol)); + if ($i->find()) { + while ($i->fetch()) { + $copy = clone($i); + $copy->encache(); + $result[$i->$keyCol][] = $copy; + } + } + foreach ($toFetch as $keyVal) + { + self::cacheSet(sprintf("%s:list:%s:%s", $cls, $keyCol, $keyVal), + $result[$keyVal]); + } + } + + return $result; + } function columnType($columnName) { diff --git a/classes/Notice.php b/classes/Notice.php index 918190a24c..d39aea6c22 100644 --- a/classes/Notice.php +++ b/classes/Notice.php @@ -755,62 +755,33 @@ class Notice extends Memcached_DataObject return true; } - function getUploadedAttachment() { - $post = clone $this; - $query = 'select file.url as up, file.id as i from file join file_to_post on file.id = file_id where post_id=' . $post->escape($post->id) . ' and url like "%/notice/%/file"'; - $post->query($query); - $post->fetch(); - if (empty($post->up) || empty($post->i)) { - $ret = false; - } else { - $ret = array($post->up, $post->i); - } - $post->free(); - return $ret; - } - - function hasAttachments() { - $post = clone $this; - $query = "select count(file_id) as n_attachments from file join file_to_post on (file_id = file.id) join notice on (post_id = notice.id) where post_id = " . $post->escape($post->id); - $post->query($query); - $post->fetch(); - $n_attachments = intval($post->n_attachments); - $post->free(); - return $n_attachments; - } - + protected $_attachments = -1; + function attachments() { - $keypart = sprintf('notice:file_ids:%d', $this->id); - - $idstr = self::cacheGet($keypart); - - if ($idstr !== false) { - $ids = explode(',', $idstr); - } else { - $ids = array(); - $f2p = new File_to_post; - $f2p->post_id = $this->id; - if ($f2p->find()) { - while ($f2p->fetch()) { - $ids[] = $f2p->file_id; - } - } - self::cacheSet($keypart, implode(',', $ids)); + if ($this->_attachments != -1) { + return $this->_attachments; } - - $att = array(); - - foreach ($ids as $id) { - $f = File::staticGet('id', $id); - if (!empty($f)) { - $att[] = clone($f); - } + + $f2ps = Memcached_DataObject::listGet('File_to_post', 'post_id', array($this->id)); + + $ids = array(); + + foreach ($f2ps[$this->id] as $f2p) { + $ids[] = $f2p->file_id; } + + $files = Memcached_DataObject::multiGet('File', 'id', $ids); - return $att; + $this->_attachments = $files->fetchAll(); + + return $this->_attachments; } + function _setAttachments($attachments) + { + $this->_attachments = $attachments; + } function publicStream($offset=0, $limit=20, $since_id=0, $max_id=0) { @@ -1332,6 +1303,8 @@ class Notice extends Memcached_DataObject return $reply; } + protected $_replies = -1; + /** * Pull the complete list of @-reply targets for this notice. * @@ -1339,31 +1312,28 @@ class Notice extends Memcached_DataObject */ function getReplies() { - $keypart = sprintf('notice:reply_ids:%d', $this->id); - - $idstr = self::cacheGet($keypart); - - if ($idstr !== false) { - $ids = explode(',', $idstr); - } else { - $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; - } - } - self::cacheSet($keypart, implode(',', $ids)); + if ($this->_replies != -1) { + return $this->_replies; } + $replyMap = Memcached_DataObject::listGet('Reply', 'notice_id', array($this->id)); + + $ids = array(); + + foreach ($replyMap[$this->id] as $reply) { + $ids[] = $reply->profile_id; + } + + $this->_replies = $ids; + return $ids; } + function _setReplies($replies) + { + $this->_replies = $replies; + } + /** * Pull the complete list of @-reply targets for this notice. * @@ -1408,6 +1378,9 @@ class Notice extends Memcached_DataObject * * @return array of Group objects */ + + protected $_groups = -1; + function getGroups() { // Don't save groups for repeats @@ -1415,31 +1388,31 @@ class Notice extends Memcached_DataObject if (!empty($this->repeat_of)) { return array(); } + + if ($this->_groups != -1) + { + return $this->_groups; + } + + $gis = Memcached_DataObject::listGet('Group_inbox', 'notice_id', array($this->id)); $ids = array(); - $keypart = sprintf('notice:groups:%d', $this->id); - - $idstr = self::cacheGet($keypart); - - if ($idstr !== false) { - $ids = explode(',', $idstr); - } else { - $gi = new Group_inbox(); - - $gi->selectAdd(); - $gi->selectAdd('group_id'); - - $gi->notice_id = $this->id; - - $ids = $gi->fetchAll('group_id'); - - self::cacheSet($keypart, implode(',', $ids)); - } - + foreach ($gis[$this->id] as $gi) + { + $ids[] = $gi->group_id; + } + $groups = User_group::multiGet('id', $ids); - return $groups->fetchAll(); + $this->_groups = $groups->fetchAll(); + + return $this->_groups; + } + + function _setGroups($groups) + { + $this->_groups = $groups; } /** @@ -2462,7 +2435,7 @@ class Notice extends Memcached_DataObject function __sleep() { $vars = parent::__sleep(); - $skip = array('_original', '_profile'); + $skip = array('_original', '_profile', '_groups', '_attachments', '_faves', '_replies'); return array_diff($vars, $skip); } @@ -2503,4 +2476,120 @@ class Notice extends Memcached_DataObject return Memcached_DataObject::pivotGet('Profile', 'id', $ids); } + + static function fillGroups(&$notices) + { + $ids = self::_idsOf($notices); + + $gis = Memcached_DataObject::listGet('Group_inbox', 'notice_id', $ids); + + $gids = array(); + + foreach ($gis as $id => $gi) + { + foreach ($gi as $g) + { + $gids[] = $g->group_id; + } + } + + $gids = array_unique($gids); + + $group = Memcached_DataObject::pivotGet('User_group', 'id', $gids); + + foreach ($notices as $notice) + { + $grps = array(); + $gi = $gis[$notice->id]; + foreach ($gi as $g) { + $grps[] = $group[$g->group_id]; + } + $notice->_setGroups($grps); + } + } + + static function _idsOf(&$notices) + { + $ids = array(); + foreach ($notices as $notice) { + $ids[] = $notice->id; + } + $ids = array_unique($ids); + return $ids; + } + + static function fillAttachments(&$notices) + { + $ids = self::_idsOf($notices); + + $f2pMap = Memcached_DataObject::listGet('File_to_post', 'post_id', $ids); + + $fileIds = array(); + + foreach ($f2pMap as $noticeId => $f2ps) { + foreach ($f2ps as $f2p) { + $fileIds[] = $f2p->file_id; + } + } + + $fileIds = array_unique($fileIds); + + $fileMap = Memcached_DataObject::pivotGet('File', 'id', $fileIds); + + foreach ($notices as $notice) + { + $files = array(); + $f2ps = $f2pMap[$notice->id]; + foreach ($f2ps as $f2p) { + $files[] = $fileMap[$f2p->file_id]; + } + $notice->_setAttachments($files); + } + } + + protected $_faves = -1; + + /** + * All faves of this notice + * + * @return array Array of Fave objects + */ + + function getFaves() + { + if ($this->_faves != -1) { + return $this->_faves; + } + $faveMap = Memcached_DataObject::listGet('Fave', 'notice_id', array($noticeId)); + $this->_faves = $faveMap[$noticeId]; + return $this->_faves; + } + + function _setFaves($faves) + { + $this->_faves = $faves; + } + + static function fillFaves(&$notices) + { + $ids = self::_idsOf($notices); + $faveMap = Memcached_DataObject::listGet('Fave', 'notice_id', $ids); + foreach ($notices as $notice) { + $notice->_setFaves($faveMap[$notice->id]); + } + } + + static function fillReplies(&$notices) + { + $ids = self::_idsOf($notices); + $replyMap = Memcached_DataObject::listGet('Reply', 'notice_id', $ids); + foreach ($notices as $notice) { + $replies = $replyMap[$notice->id]; + $ids = array(); + foreach ($replies as $reply) { + $ids[] = $reply->profile_id; + } + $notice->_setReplies($ids); + } + } } diff --git a/lib/attachmentlist.php b/lib/attachmentlist.php index cf7c9acc14..a93a6842bb 100644 --- a/lib/attachmentlist.php +++ b/lib/attachmentlist.php @@ -76,7 +76,7 @@ class AttachmentList extends Widget */ function show() { - $att = File::getAttachments($this->notice->id); + $att = $this->notice->attachments(); if (empty($att)) return 0; $this->showListStart(); diff --git a/lib/filteringnoticestream.php b/lib/filteringnoticestream.php index f412211074..0b0fab481e 100644 --- a/lib/filteringnoticestream.php +++ b/lib/filteringnoticestream.php @@ -81,9 +81,16 @@ abstract class FilteringNoticeStream extends NoticeStream break; } - while ($raw->fetch()) { - if ($this->filter($raw)) { - $filtered[] = clone($raw); + $notices = $raw->fetchAll(); + + // XXX: this should probably only be in the scoping one. + + Notice::fillGroups($notices); + Notice::fillReplies($notices); + + foreach ($notices as $notice) { + if ($this->filter($notice)) { + $filtered[] = $notice; if (count($filtered) >= $total) { break; } diff --git a/lib/noticelist.php b/lib/noticelist.php index a4781d9daa..148f428edf 100644 --- a/lib/noticelist.php +++ b/lib/noticelist.php @@ -124,6 +124,10 @@ class NoticeList extends Widget static function prefill(&$notices, $avatarSize=AVATAR_STREAM_SIZE) { + // Prefill attachments + Notice::fillAttachments($notices); + // Prefill attachments + Notice::fillFaves($notices); // Prefill the profiles $profiles = Notice::fillProfiles($notices); // Prefill the avatars diff --git a/lib/threadednoticelist.php b/lib/threadednoticelist.php index 407f7bdde3..cf3c0b8943 100644 --- a/lib/threadednoticelist.php +++ b/lib/threadednoticelist.php @@ -470,9 +470,9 @@ class ThreadedNoticeListFavesItem extends NoticeListActorsItem { function getProfiles() { - $fave = Fave::byNotice($this->notice->id); + $faves = $this->notice->getFaves(); $profiles = array(); - while ($fave->fetch()) { + foreach ($faves as $fave) { $profiles[] = $fave->user_id; } return $profiles; diff --git a/scripts/createsim.php b/scripts/createsim.php index e3677e1564..4912d2a61b 100644 --- a/scripts/createsim.php +++ b/scripts/createsim.php @@ -20,20 +20,31 @@ define('INSTALLDIR', realpath(dirname(__FILE__) . '/..')); -$shortoptions = 'u:n:b:g:j:t:x:z:'; -$longoptions = array('users=', 'notices=', 'subscriptions=', 'groups=', 'joins=', 'tags=', 'prefix='); +$shortoptions = 'b:g:j:n:t:u:w:x:z:'; +$longoptions = array( + 'subscriptions=', + 'groups=', + 'joins=', + 'notices=', + 'tags=', + 'users=', + 'words=', + 'prefix=', + 'groupprefix' +); $helptext = <<