From 8449256817f5a2bd7a7cac6bc04e4cb477d7dc49 Mon Sep 17 00:00:00 2001 From: Brion Vibber Date: Tue, 9 Feb 2010 18:32:52 -0800 Subject: [PATCH] OStatus partial support for group subscriptions: * detection of group feeds is currently a nasty hack based on presence of '/groups/' in URL -- should use some property on the feed? * listing for the remote group is kinda cruddy; needs to be named more cleanly * still need to establish per-author profiles (easier once we have the updated Atom code in) * group delivery probably not right yet * saving of group messages still triggering some weird behavior Added support for since_id and max_id on group timeline feeds as a free extra. Enjoy! --- actions/apitimelinegroup.php | 2 +- actions/showgroup.php | 4 +- classes/Notice.php | 4 +- classes/User_group.php | 4 +- lib/util.php | 2 +- plugins/OStatus/OStatusPlugin.php | 4 +- plugins/OStatus/actions/feedsubsettings.php | 22 ++++-- plugins/OStatus/classes/Feedinfo.php | 55 +++++++++++++-- plugins/OStatus/lib/feedmunger.php | 4 +- .../OStatus/lib/hubdistribqueuehandler.php | 70 ++++++++++++++++--- 10 files changed, 140 insertions(+), 31 deletions(-) diff --git a/actions/apitimelinegroup.php b/actions/apitimelinegroup.php index af414c6804..fd2ed9ff93 100644 --- a/actions/apitimelinegroup.php +++ b/actions/apitimelinegroup.php @@ -130,7 +130,7 @@ class ApiTimelineGroupAction extends ApiPrivateAuthAction case 'atom': $selfuri = common_root_url() . 'api/statusnet/groups/timeline/' . - $this->group->nickname . '.atom'; + $this->group->id . '.atom'; $this->showAtomTimeline( $this->notices, $title, diff --git a/actions/showgroup.php b/actions/showgroup.php index 8042a49513..eb12389029 100644 --- a/actions/showgroup.php +++ b/actions/showgroup.php @@ -330,13 +330,13 @@ class ShowgroupAction extends GroupDesignAction new Feed(Feed::RSS2, common_local_url('ApiTimelineGroup', array('format' => 'rss', - 'id' => $this->group->nickname)), + 'id' => $this->group->id)), sprintf(_('Notice feed for %s group (RSS 2.0)'), $this->group->nickname)), new Feed(Feed::ATOM, common_local_url('ApiTimelineGroup', array('format' => 'atom', - 'id' => $this->group->nickname)), + 'id' => $this->group->id)), sprintf(_('Notice feed for %s group (Atom)'), $this->group->nickname)), new Feed(Feed::FOAF, diff --git a/classes/Notice.php b/classes/Notice.php index fca1c599ce..247440f29c 100644 --- a/classes/Notice.php +++ b/classes/Notice.php @@ -783,7 +783,7 @@ class Notice extends Memcached_DataObject $result = $gi->insert(); - if (!result) { + if (!$result) { common_log_db_error($gi, 'INSERT', __FILE__); throw new ServerException(_('Problem saving group inbox.')); } @@ -917,7 +917,7 @@ class Notice extends Memcached_DataObject /** * Same calculation as saveGroups but without the saving * @fixme merge the functions - * @return array of Group objects + * @return array of Group_inbox objects */ function getGroups() { diff --git a/classes/User_group.php b/classes/User_group.php index c86eadf8fa..1fbb50a6eb 100644 --- a/classes/User_group.php +++ b/classes/User_group.php @@ -49,12 +49,12 @@ class User_group extends Memcached_DataObject array('id' => $this->id)); } - function getNotices($offset, $limit) + function getNotices($offset, $limit, $since_id=null, $max_id=null) { $ids = Notice::stream(array($this, '_streamDirect'), array(), 'user_group:notice_ids:' . $this->id, - $offset, $limit); + $offset, $limit, $since_id, $max_id); return Notice::getStreamByIds($ids); } diff --git a/lib/util.php b/lib/util.php index 00c21aeb21..a07fe49e33 100644 --- a/lib/util.php +++ b/lib/util.php @@ -697,7 +697,7 @@ function common_group_link($sender_id, $nickname) { $sender = Profile::staticGet($sender_id); $group = User_group::getForNickname($nickname); - if ($group && $sender->isMember($group)) { + if ($sender && $group && $sender->isMember($group)) { $attrs = array('href' => $group->permalink(), 'class' => 'url'); if (!empty($group->fullname)) { diff --git a/plugins/OStatus/OStatusPlugin.php b/plugins/OStatus/OStatusPlugin.php index 60a4e38273..89b5c4caaa 100644 --- a/plugins/OStatus/OStatusPlugin.php +++ b/plugins/OStatus/OStatusPlugin.php @@ -107,11 +107,11 @@ class OStatusPlugin extends Plugin /** * Set up a PuSH hub link to our internal link for canonical timeline - * Atom feeds for users. + * Atom feeds for users and groups. */ function onStartApiAtom(Action $action) { - if ($action instanceof ApiTimelineUserAction) { + if ($action instanceof ApiTimelineUserAction || $action instanceof ApiTimelineGroupAction) { $id = $action->arg('id'); if (strval(intval($id)) === strval($id)) { // Canonical form of id in URL? diff --git a/plugins/OStatus/actions/feedsubsettings.php b/plugins/OStatus/actions/feedsubsettings.php index 4d5b7b60f4..6f592bf5b0 100644 --- a/plugins/OStatus/actions/feedsubsettings.php +++ b/plugins/OStatus/actions/feedsubsettings.php @@ -209,7 +209,7 @@ class FeedSubSettingsAction extends ConnectSettingsAction return; } } - + // And subscribe the current user to the local profile $user = common_current_user(); $profile = $this->feedinfo->getProfile(); @@ -217,12 +217,22 @@ class FeedSubSettingsAction extends ConnectSettingsAction throw new ServerException("Feed profile was not saved properly."); } - if ($user->isSubscribed($profile)) { - $this->showForm(_m('Already subscribed!')); - } elseif ($user->subscribeTo($profile)) { - $this->showForm(_m('Feed subscribed!')); + if ($this->feedinfo->isGroup()) { + if ($user->isMember($profile)) { + $this->showForm(_m('Already a member!')); + } elseif (Group_member::join($this->feedinfo->group_id, $user->id)) { + $this->showForm(_m('Joined remote group!')); + } else { + $this->showForm(_m('Remote group join failed!')); + } } else { - $this->showForm(_m('Feed subscription failed!')); + if ($user->isSubscribed($profile)) { + $this->showForm(_m('Already subscribed!')); + } elseif ($user->subscribeTo($profile)) { + $this->showForm(_m('Feed subscribed!')); + } else { + $this->showForm(_m('Feed subscription failed!')); + } } } } diff --git a/plugins/OStatus/classes/Feedinfo.php b/plugins/OStatus/classes/Feedinfo.php index 107faf0125..792ea60345 100644 --- a/plugins/OStatus/classes/Feedinfo.php +++ b/plugins/OStatus/classes/Feedinfo.php @@ -89,7 +89,8 @@ class Feedinfo extends Memcached_DataObject function table() { return array('id' => DB_DATAOBJECT_INT + DB_DATAOBJECT_NOTNULL, - 'profile_id' => DB_DATAOBJECT_INT + DB_DATAOBJECT_NOTNULL, + 'profile_id' => DB_DATAOBJECT_INT, + 'group_id' => DB_DATAOBJECT_INT, 'feeduri' => DB_DATAOBJECT_STR + DB_DATAOBJECT_NOTNULL, 'homeuri' => DB_DATAOBJECT_STR + DB_DATAOBJECT_NOTNULL, 'huburi' => DB_DATAOBJECT_STR + DB_DATAOBJECT_NOTNULL, @@ -111,7 +112,9 @@ class Feedinfo extends Memcached_DataObject /*extra*/ null, /*auto_increment*/ true), new ColumnDef('profile_id', 'integer', - null, false), + null, true), + new ColumnDef('group_id', 'integer', + null, true), new ColumnDef('feeduri', 'varchar', 255, false, 'UNI'), new ColumnDef('homeuri', 'varchar', @@ -176,6 +179,7 @@ class Feedinfo extends Memcached_DataObject /** * @param FeedMunger $munger + * @param boolean $isGroup is this a group record? * @return Feedinfo */ public static function ensureProfile($munger) @@ -217,6 +221,22 @@ class Feedinfo extends Memcached_DataObject } $feedinfo->profile_id = $profile->id; + if ($feedinfo->isGroup()) { + $group = new User_group(); + $group->nickname = $profile->nickname . '@remote'; // @fixme + $group->fullname = $profile->fullname; + $group->homepage = $profile->homepage; + $group->location = $profile->location; + $group->created = $profile->created; + $group->insert(); + + if ($avatar) { + $group->setOriginal($filename); + } + + $feedinfo->group_id = $group->id; + } + $result = $feedinfo->insert(); if (empty($result)) { throw new FeedDBException($feedinfo); @@ -231,6 +251,14 @@ class Feedinfo extends Memcached_DataObject return $feedinfo; } + /** + * Damn dirty hack! + */ + function isGroup() + { + return (strpos($this->feeduri, '/groups/') !== false); + } + /** * Send a subscription request to the hub for this feed. * The hub will later send us a confirmation POST to /feedsub/callback. @@ -325,17 +353,34 @@ class Feedinfo extends Memcached_DataObject $dupe = new Notice(); $dupe->uri = $notice->uri; if ($dupe->find(true)) { + // @fixme we might have to do individual and group delivery separately! common_log(LOG_WARNING, __METHOD__ . ": tried to save dupe notice for entry {$notice->uri} of feed {$this->feeduri}"); continue; } - + if (Event::handle('StartNoticeSave', array(&$notice))) { $id = $notice->insert(); Event::handle('EndNoticeSave', array($notice)); } - $notice->addToInboxes(); - common_log(LOG_INFO, __METHOD__ . ": saved notice {$notice->id} for entry $index of update to \"{$this->feeduri}\""); + + common_log(LOG_DEBUG, "going to check group delivery..."); + if ($this->group_id) { + $group = User_group::staticGet($this->group_id); + if ($group) { + common_log(LOG_INFO, __METHOD__ . ": saving to local shadow group $group->id $group->nickname"); + $groups = array($group); + } else { + common_log(LOG_INFO, __METHOD__ . ": lost the local shadow group?"); + } + } else { + common_log(LOG_INFO, __METHOD__ . ": no local shadow groups"); + $groups = array(); + } + common_log(LOG_DEBUG, "going to add to inboxes..."); + $notice->addToInboxes($groups, array()); + common_log(LOG_DEBUG, "added to inboxes."); + $hits++; } if ($hits == 0) { diff --git a/plugins/OStatus/lib/feedmunger.php b/plugins/OStatus/lib/feedmunger.php index cbaec67750..5dce95342e 100644 --- a/plugins/OStatus/lib/feedmunger.php +++ b/plugins/OStatus/lib/feedmunger.php @@ -203,7 +203,7 @@ class FeedMunger if (!$entry) { return null; } - + if ($preview) { $notice = new FeedSubPreviewNotice($this->profile(true)); $notice->id = -1; @@ -221,7 +221,7 @@ class FeedMunger $notice->uri = $link; $notice->url = $link; $notice->content = $this->noticeFromEntry($entry); - $notice->rendered = common_render_content($notice->content, $notice); + $notice->rendered = common_render_content($notice->content, $notice); // @fixme this is failing on group posts $notice->created = common_sql_date($entry->updated); // @fixme $notice->is_local = Notice::GATEWAY; $notice->source = 'feed'; diff --git a/plugins/OStatus/lib/hubdistribqueuehandler.php b/plugins/OStatus/lib/hubdistribqueuehandler.php index 126f1355f9..189ccbedf9 100644 --- a/plugins/OStatus/lib/hubdistribqueuehandler.php +++ b/plugins/OStatus/lib/hubdistribqueuehandler.php @@ -34,6 +34,14 @@ class HubDistribQueueHandler extends QueueHandler { assert($notice instanceof Notice); + $this->pushUser($notice); + foreach ($notice->getGroups() as $group) { + $this->pushGroup($notice, $group->group_id); + } + } + + function pushUser($notice) + { // See if there's any PuSH subscriptions, including OStatus clients. // @fixme handle group subscriptions as well // http://identi.ca/api/statuses/user_timeline/1.atom @@ -43,20 +51,42 @@ class HubDistribQueueHandler extends QueueHandler $sub = new HubSub(); $sub->topic = $feed; if ($sub->find()) { - common_log(LOG_INFO, "Preparing $sub->N PuSH distribution(s) for $feed"); - $qm = QueueManager::get(); $atom = $this->userFeedForNotice($notice); - while ($sub->fetch()) { - common_log(LOG_INFO, "Prepping PuSH distribution to $sub->callback for $feed"); - $data = array('sub' => clone($sub), - 'atom' => $atom); - $qm->enqueue($data, 'hubout'); - } + $this->pushFeeds($atom, $sub); } else { common_log(LOG_INFO, "No PuSH subscribers for $feed"); } } + function pushGroup($notice, $group_id) + { + $feed = common_local_url('ApiTimelineGroup', + array('id' => $group_id, + 'format' => 'atom')); + $sub = new HubSub(); + $sub->topic = $feed; + if ($sub->find()) { + common_log(LOG_INFO, "Building PuSH feed for $feed"); + $atom = $this->groupFeedForNotice($group_id, $notice); + $this->pushFeeds($atom, $sub); + } else { + common_log(LOG_INFO, "No PuSH subscribers for $feed"); + } + } + + + function pushFeeds($atom, $sub) + { + common_log(LOG_INFO, "Preparing $sub->N PuSH distribution(s) for $sub->topic"); + $qm = QueueManager::get(); + while ($sub->fetch()) { + common_log(LOG_INFO, "Prepping PuSH distribution to $sub->callback for $sub->topic"); + $data = array('sub' => clone($sub), + 'atom' => $atom); + $qm->enqueue($data, 'hubout'); + } + } + /** * Build a single-item version of the sending user's Atom feed. * @param Notice $notice @@ -83,5 +113,29 @@ class HubDistribQueueHandler extends QueueHandler common_log(LOG_DEBUG, $feed); return $feed; } + + function groupFeedForNotice($group_id, $notice) + { + // @fixme this feels VERY hacky... + // should probably be a cleaner way to do it + + ob_start(); + $api = new ApiTimelineGroupAction(); + $args = array('id' => $group_id, + 'format' => 'atom', + 'max_id' => $notice->id, + 'since_id' => $notice->id - 1); + $api->prepare($args); + $api->handle($args); + $feed = ob_get_clean(); + + // ...and override the content-type back to something normal... eww! + // hope there's no other headers that got set while we weren't looking. + header('Content-Type: text/html; charset=utf-8'); + + common_log(LOG_DEBUG, $feed); + return $feed; + } + }