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
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) {
$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);
}
}
}
+ /**
+ * 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.
/**
* 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
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);
}
/**
- * 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();
}
/**
* @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);
} 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;
}
/**
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;
+ }
}
* @link http://status.net/
*/
-class AddMirrorAction extends Action
+class AddMirrorAction extends BaseMirrorAction
{
- var $user;
var $feedurl;
/**
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);
- }
}
* @link http://status.net/
*/
-class EditMirrorAction extends AddMirrorAction
+class EditMirrorAction extends BaseMirrorAction
{
/**
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));
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;
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!');
}
function showContent()
{
$user = common_current_user();
+
+ $this->showAddFeedForm();
$mirror = new SubMirror();
$mirror->subscriber = $user->id;
$this->showFeedForm($mirror);
}
}
- $this->showAddFeedForm();
}
function showFeedForm($mirror)
$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');
}
$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;
}