From: Brion Vibber Date: Fri, 6 Aug 2010 17:56:18 +0000 (-0700) Subject: OStatus/FeedSub: tweaked PuSH feed garbage collection so other plugins can declare... X-Git-Url: https://git.mxchange.org/?a=commitdiff_plain;h=7e55fc00447923b40b2ffc87329fd95347d776f5;p=quix0rs-gnu-social.git OStatus/FeedSub: tweaked PuSH feed garbage collection so other plugins can declare usage of a low-level feed or an OStatus profile besides profile subscriptions & group memberships. SubMirror: redid add-mirror frontend to accept a feed URL, then pass that on to OStatus, instead of pulling from your subscriptions. Profile: tweaked subscriberCount() so it doesn't subtract 1 for foreign profiles who aren't subscribed to themselves; instead excludes the self-subscription in the count query. Memcached_DataObject: tweak to avoid extra error spew in the DB error raising Work in progress: tweaking feedsub garbage collection so we can count other uses --- diff --git a/classes/Memcached_DataObject.php b/classes/Memcached_DataObject.php index 7768fe7571..0f1ed04892 100644 --- a/classes/Memcached_DataObject.php +++ b/classes/Memcached_DataObject.php @@ -574,7 +574,7 @@ class Memcached_DataObject extends Safe_DataObject function raiseError($message, $type = null, $behaviour = null) { $id = get_class($this); - if ($this->id) { + if (!empty($this->id)) { $id .= ':' . $this->id; } if ($message instanceof PEAR_Error) { diff --git a/classes/Profile.php b/classes/Profile.php index 0d0463b730..d7617f0b74 100644 --- a/classes/Profile.php +++ b/classes/Profile.php @@ -464,11 +464,9 @@ class Profile extends Memcached_DataObject $sub = new Subscription(); $sub->subscribed = $this->id; - + $sub->whereAdd('subscriber != subscribed'); $cnt = (int) $sub->count('distinct subscriber'); - $cnt = ($cnt > 0) ? $cnt - 1 : $cnt; - if (!empty($c)) { $c->set(common_cache_key('profile:subscriber_count:'.$this->id), $cnt); } diff --git a/plugins/OStatus/OStatusPlugin.php b/plugins/OStatus/OStatusPlugin.php index 70971c5b34..3b073a5d13 100644 --- a/plugins/OStatus/OStatusPlugin.php +++ b/plugins/OStatus/OStatusPlugin.php @@ -479,6 +479,24 @@ class OStatusPlugin extends Plugin } } + /** + * Tell the FeedSub infrastructure whether we have any active OStatus + * usage for the feed; if not it'll be able to garbage-collect the + * feed subscription. + * + * @param FeedSub $feedsub + * @param integer $count in/out + * @return mixed hook return code + */ + function onFeedSubSubscriberCount($feedsub, &$count) + { + $oprofile = Ostatus_profile::staticGet('feeduri', $feedsub->uri); + if ($oprofile) { + $count += $oprofile->subscriberCount(); + } + return true; + } + /** * When about to subscribe to a remote user, start a server-to-server * PuSH subscription if needed. If we can't establish that, abort. diff --git a/plugins/OStatus/classes/FeedSub.php b/plugins/OStatus/classes/FeedSub.php index b10509dae6..9cd35e29c9 100644 --- a/plugins/OStatus/classes/FeedSub.php +++ b/plugins/OStatus/classes/FeedSub.php @@ -255,6 +255,9 @@ class FeedSub extends Memcached_DataObject /** * Send a PuSH unsubscription request to the hub for this feed. * The hub will later send us a confirmation POST to /main/push/callback. + * Warning: this will cancel the subscription even if someone else in + * the system is using it. Most callers will want garbageCollect() instead, + * which confirms there's no uses left. * * @return bool true on success, false on failure * @throws ServerException if feed state is not valid @@ -275,6 +278,33 @@ class FeedSub extends Memcached_DataObject return $this->doSubscribe('unsubscribe'); } + /** + * Check if there are any active local uses of this feed, and if not then + * make sure it's inactive, unsubscribing if necessary. + * + * @return boolean true if the subscription is now inactive, false if still active. + */ + public function garbageCollect() + { + if ($this->sub_state == '' || $this->sub_state == 'inactive') { + // No active PuSH subscription, we can just leave it be. + return true; + } else { + // PuSH subscription is either active or in an indeterminate state. + // Check if we're out of subscribers, and if so send an unsubscribe. + $count = 0; + Event::handle('FeedSubSubscriberCount', array($this, &$count)); + + if ($count) { + common_log(LOG_INFO, __METHOD__ . ': ok, ' . $count . ' user(s) left for ' . $this->uri); + return false; + } else { + common_log(LOG_INFO, __METHOD__ . ': unsubscribing, no users left for ' . $this->uri); + return $this->unsubscribe(); + } + } + } + protected function doSubscribe($mode) { $orig = clone($this); diff --git a/plugins/OStatus/classes/Ostatus_profile.php b/plugins/OStatus/classes/Ostatus_profile.php index 5d3f37cd07..2d7c632e63 100644 --- a/plugins/OStatus/classes/Ostatus_profile.php +++ b/plugins/OStatus/classes/Ostatus_profile.php @@ -215,22 +215,13 @@ class Ostatus_profile extends Memcached_DataObject } /** - * Send a PuSH unsubscription request to the hub for this feed. - * The hub will later send us a confirmation POST to /main/push/callback. + * Check if this remote profile has any active local subscriptions, and + * if not drop the PuSH subscription feed. * * @return bool true on success, false on failure - * @throws ServerException if feed state is not valid */ public function unsubscribe() { - $feedsub = FeedSub::staticGet('uri', $this->feeduri); - if (!$feedsub || $feedsub->sub_state == '' || $feedsub->sub_state == 'inactive') { - // No active PuSH subscription, we can just leave it be. - return true; - } else { - // PuSH subscription is either active or in an indeterminate state. - // Send an unsubscribe. - return $feedsub->unsubscribe(); - } + $this->garbageCollect(); } /** @@ -240,6 +231,21 @@ class Ostatus_profile extends Memcached_DataObject * @return boolean */ public function garbageCollect() + { + $feedsub = FeedSub::staticGet('uri', $this->feeduri); + return $feedsub->garbageCollect(); + } + + /** + * Check if this remote profile has any active local subscriptions, so the + * PuSH subscription layer can decide if it can drop the feed. + * + * This gets called via the FeedSubSubscriberCount event when running + * FeedSub::garbageCollect(). + * + * @return int + */ + public function subscriberCount() { if ($this->isGroup()) { $members = $this->localGroup()->getMembers(0, 1); @@ -247,13 +253,14 @@ class Ostatus_profile extends Memcached_DataObject } else { $count = $this->localProfile()->subscriberCount(); } - if ($count == 0) { - common_log(LOG_INFO, "Unsubscribing from now-unused remote feed $this->feeduri"); - $this->unsubscribe(); - return true; - } else { - return false; - } + common_log(LOG_INFO, __METHOD__ . " SUB COUNT BEFORE: $count"); + + // Other plugins may be piggybacking on OStatus without having + // an active group or user-to-user subscription we know about. + Event::handle('Ostatus_profileSubscriberCount', array($this, &$count)); + common_log(LOG_INFO, __METHOD__ . " SUB COUNT AFTER: $count"); + + return $count; } /** diff --git a/plugins/SubMirror/SubMirrorPlugin.php b/plugins/SubMirror/SubMirrorPlugin.php index 8678cc3dd0..00c04ad0b6 100644 --- a/plugins/SubMirror/SubMirrorPlugin.php +++ b/plugins/SubMirror/SubMirrorPlugin.php @@ -146,4 +146,27 @@ class SubMirrorPlugin extends Plugin array('href' => common_local_url('mirrorsettings')), _m('Set up mirroring options...')); } + + /** + * Let the OStatus subscription garbage collection know if we're + * making use of a remote feed, so it doesn't get dropped out + * from under us. + * + * @param Ostatus_profile $oprofile + * @param int $count in/out + * @return mixed hook return value + */ + function onOstatus_profileSubscriberCount($oprofile, &$count) + { + if ($oprofile->profile_id) { + $mirror = new SubMirror(); + $mirror->subscribed = $oprofile->profile_id; + if ($mirror->find()) { + while ($mirror->fetch()) { + $count++; + } + } + } + return true; + } } diff --git a/plugins/SubMirror/actions/addmirror.php b/plugins/SubMirror/actions/addmirror.php index df6939491f..5acdf1dfe4 100644 --- a/plugins/SubMirror/actions/addmirror.php +++ b/plugins/SubMirror/actions/addmirror.php @@ -46,9 +46,8 @@ if (!defined('STATUSNET')) { * @link http://status.net/ */ -class AddMirrorAction extends Action +class AddMirrorAction extends BaseMirrorAction { - var $user; var $feedurl; /** @@ -62,94 +61,17 @@ class AddMirrorAction extends Action function prepare($args) { parent::prepare($args); - $ok = $this->sharedBoilerplate(); - if ($ok) { - // and now do something useful! - $this->profile = $this->validateProfile($this->trimmed('profile')); - return true; - } else { - return $ok; - } - } - - function validateProfile($id) - { - $id = intval($id); - $profile = Profile::staticGet('id', $id); - if ($profile && $profile->id != $this->user->id) { - return $profile; - } - // TRANS: Error message returned to user when setting up feed mirroring, but we were unable to resolve the given URL to a working feed. - $this->clientError(_m("Invalid profile for mirroring.")); - } - - /** - * @fixme none of this belongs in end classes - * this stuff belongs in shared code! - */ - function sharedBoilerplate() - { - // Only allow POST requests - - if ($_SERVER['REQUEST_METHOD'] != 'POST') { - $this->clientError(_('This action only accepts POST requests.')); - return false; - } - - // CSRF protection - - $token = $this->trimmed('token'); - - if (!$token || $token != common_session_token()) { - $this->clientError(_('There was a problem with your session token.'. - ' Try again, please.')); - return false; - } - - // Only for logged-in users - - $this->user = common_current_user(); - - if (empty($this->user)) { - $this->clientError(_('Not logged in.')); - return false; - } + $this->feedurl = $this->validateFeedUrl($this->trimmed('feedurl')); + $this->profile = $this->profileForFeed($this->feedurl); return true; } - /** - * Handle request - * - * Does the subscription and returns results. - * - * @param Array $args unused. - * - * @return void - */ - - function handle($args) + function saveMirror() { - // Throws exception on error - $this->saveMirror(); - - if ($this->boolean('ajax')) { - $this->startHTML('text/xml;charset=utf-8'); - $this->elementStart('head'); - $this->element('title', null, _('Subscribed')); - $this->elementEnd('head'); - $this->elementStart('body'); - $unsubscribe = new EditMirrorForm($this, $this->profile); - $unsubscribe->show(); - $this->elementEnd('body'); - $this->elementEnd('html'); + if ($this->oprofile->subscribe()) { + SubMirror::saveMirror($this->user, $this->profile); } else { - $url = common_local_url('mirrorsettings'); - common_redirect($url, 303); + $this->serverError(_m("Could not subscribe to feed.")); } } - - function saveMirror() - { - SubMirror::saveMirror($this->user, $this->profile); - } } diff --git a/plugins/SubMirror/actions/editmirror.php b/plugins/SubMirror/actions/editmirror.php index 7ddd32ef35..c7fdab0d67 100644 --- a/plugins/SubMirror/actions/editmirror.php +++ b/plugins/SubMirror/actions/editmirror.php @@ -46,7 +46,7 @@ if (!defined('STATUSNET')) { * @link http://status.net/ */ -class EditMirrorAction extends AddMirrorAction +class EditMirrorAction extends BaseMirrorAction { /** @@ -60,6 +60,9 @@ class EditMirrorAction extends AddMirrorAction function prepare($args) { parent::prepare($args); + + $this->profile = $this->validateProfile($this->trimmed('profile')); + $this->mirror = SubMirror::pkeyGet(array('subscriber' => $this->user->id, 'subscribed' => $this->profile->id)); @@ -95,6 +98,10 @@ class EditMirrorAction extends AddMirrorAction if ($this->delete) { $mirror->delete(); + $oprofile = Ostatus_profile::staticGet('profile_id', $this->profile->id); + if ($oprofile) { + $oprofile->garbageCollect(); + } } else if ($this->style != $mirror->style) { $orig = clone($mirror); $mirror->style = $this->style; diff --git a/plugins/SubMirror/actions/mirrorsettings.php b/plugins/SubMirror/actions/mirrorsettings.php index edb024183d..5463a8dc0c 100644 --- a/plugins/SubMirror/actions/mirrorsettings.php +++ b/plugins/SubMirror/actions/mirrorsettings.php @@ -50,7 +50,7 @@ class MirrorSettingsAction extends AccountSettingsAction function getInstructions() { - return _m('You can mirror updates from your RSS and Atom feeds ' . + return _m('You can mirror updates from many RSS and Atom feeds ' . 'into your StatusNet timeline!'); } @@ -65,6 +65,8 @@ class MirrorSettingsAction extends AccountSettingsAction function showContent() { $user = common_current_user(); + + $this->showAddFeedForm(); $mirror = new SubMirror(); $mirror->subscriber = $user->id; @@ -73,7 +75,6 @@ class MirrorSettingsAction extends AccountSettingsAction $this->showFeedForm($mirror); } } - $this->showAddFeedForm(); } function showFeedForm($mirror) diff --git a/plugins/SubMirror/lib/addmirrorform.php b/plugins/SubMirror/lib/addmirrorform.php index 9ee59661a5..0a798c9eaf 100644 --- a/plugins/SubMirror/lib/addmirrorform.php +++ b/plugins/SubMirror/lib/addmirrorform.php @@ -57,46 +57,15 @@ class AddMirrorForm extends Form $this->out->elementStart('ul'); $this->li(); - $this->out->element('label', array('for' => $this->id() . '-profile'), - _m("Mirror one of your existing subscriptions:")); - $this->out->elementStart('select', array('name' => 'profile')); - - $user = common_current_user(); - $profile = $user->getSubscriptions(); - while ($profile->fetch()) { - $mirror = SubMirror::pkeyGet(array('subscriber' => $user->id, - 'subscribed' => $profile->id)); - if (!$mirror) { - $this->out->element('option', - array('value' => $profile->id), - $profile->getBestName()); - } - } - $this->out->elementEnd('select'); - $this->out->submit($this->id() . '-save', _m('Save')); + $this->doInput('addmirror-feedurl', + 'feedurl', + _m('Web page or feed URL:'), + $this->out->trimmed('feedurl')); $this->unli(); - $this->li(); - - $this->out->elementStart('fieldset', array('style' => 'width: 360px; margin-left: auto; margin-right: auto')); - $this->out->element('p', false, - _m("Not already subscribed to the feed you want? " . - "Add a new remote subscription and paste in the URL!")); - - $this->out->elementStart('div', 'entity_actions'); - $this->out->elementStart('p', array('id' => 'entity_remote_subscribe', - 'class' => 'entity_subscribe')); - $this->out->element('a', array('href' => common_local_url('ostatussub'), - 'class' => 'entity_remote_subscribe') - , _m('Remote')); - $this->out->elementEnd('p'); - $this->out->elementEnd('div'); - - $this->out->element('div', array('style' => 'clear: both')); - $this->out->elementEnd('fieldset'); + $this->out->submit('addmirror-save', _m('Add feed')); $this->unli(); - $this->out->elementEnd('ul'); $this->out->elementEnd('fieldset'); } @@ -106,7 +75,8 @@ class AddMirrorForm extends Form $this->out->element('label', array('for' => $id), $label); $attrs = array('name' => $name, 'type' => 'text', - 'id' => $id); + 'id' => $id, + 'style' => 'width: 80%'); if ($value) { $attrs['value'] = $value; }