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
This commit is contained in:
parent
ebd2fc2f7c
commit
7e55fc0044
@ -574,7 +574,7 @@ class Memcached_DataObject extends Safe_DataObject
|
|||||||
function raiseError($message, $type = null, $behaviour = null)
|
function raiseError($message, $type = null, $behaviour = null)
|
||||||
{
|
{
|
||||||
$id = get_class($this);
|
$id = get_class($this);
|
||||||
if ($this->id) {
|
if (!empty($this->id)) {
|
||||||
$id .= ':' . $this->id;
|
$id .= ':' . $this->id;
|
||||||
}
|
}
|
||||||
if ($message instanceof PEAR_Error) {
|
if ($message instanceof PEAR_Error) {
|
||||||
|
@ -464,11 +464,9 @@ class Profile extends Memcached_DataObject
|
|||||||
|
|
||||||
$sub = new Subscription();
|
$sub = new Subscription();
|
||||||
$sub->subscribed = $this->id;
|
$sub->subscribed = $this->id;
|
||||||
|
$sub->whereAdd('subscriber != subscribed');
|
||||||
$cnt = (int) $sub->count('distinct subscriber');
|
$cnt = (int) $sub->count('distinct subscriber');
|
||||||
|
|
||||||
$cnt = ($cnt > 0) ? $cnt - 1 : $cnt;
|
|
||||||
|
|
||||||
if (!empty($c)) {
|
if (!empty($c)) {
|
||||||
$c->set(common_cache_key('profile:subscriber_count:'.$this->id), $cnt);
|
$c->set(common_cache_key('profile:subscriber_count:'.$this->id), $cnt);
|
||||||
}
|
}
|
||||||
|
@ -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
|
* When about to subscribe to a remote user, start a server-to-server
|
||||||
* PuSH subscription if needed. If we can't establish that, abort.
|
* PuSH subscription if needed. If we can't establish that, abort.
|
||||||
|
@ -255,6 +255,9 @@ class FeedSub extends Memcached_DataObject
|
|||||||
/**
|
/**
|
||||||
* Send a PuSH unsubscription request to the hub for this feed.
|
* Send a PuSH unsubscription request to the hub for this feed.
|
||||||
* The hub will later send us a confirmation POST to /main/push/callback.
|
* 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
|
* @return bool true on success, false on failure
|
||||||
* @throws ServerException if feed state is not valid
|
* @throws ServerException if feed state is not valid
|
||||||
@ -275,6 +278,33 @@ class FeedSub extends Memcached_DataObject
|
|||||||
return $this->doSubscribe('unsubscribe');
|
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)
|
protected function doSubscribe($mode)
|
||||||
{
|
{
|
||||||
$orig = clone($this);
|
$orig = clone($this);
|
||||||
|
@ -215,22 +215,13 @@ class Ostatus_profile extends Memcached_DataObject
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Send a PuSH unsubscription request to the hub for this feed.
|
* Check if this remote profile has any active local subscriptions, and
|
||||||
* The hub will later send us a confirmation POST to /main/push/callback.
|
* if not drop the PuSH subscription feed.
|
||||||
*
|
*
|
||||||
* @return bool true on success, false on failure
|
* @return bool true on success, false on failure
|
||||||
* @throws ServerException if feed state is not valid
|
|
||||||
*/
|
*/
|
||||||
public function unsubscribe() {
|
public function unsubscribe() {
|
||||||
$feedsub = FeedSub::staticGet('uri', $this->feeduri);
|
$this->garbageCollect();
|
||||||
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();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -240,6 +231,21 @@ class Ostatus_profile extends Memcached_DataObject
|
|||||||
* @return boolean
|
* @return boolean
|
||||||
*/
|
*/
|
||||||
public function garbageCollect()
|
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()) {
|
if ($this->isGroup()) {
|
||||||
$members = $this->localGroup()->getMembers(0, 1);
|
$members = $this->localGroup()->getMembers(0, 1);
|
||||||
@ -247,13 +253,14 @@ class Ostatus_profile extends Memcached_DataObject
|
|||||||
} else {
|
} else {
|
||||||
$count = $this->localProfile()->subscriberCount();
|
$count = $this->localProfile()->subscriberCount();
|
||||||
}
|
}
|
||||||
if ($count == 0) {
|
common_log(LOG_INFO, __METHOD__ . " SUB COUNT BEFORE: $count");
|
||||||
common_log(LOG_INFO, "Unsubscribing from now-unused remote feed $this->feeduri");
|
|
||||||
$this->unsubscribe();
|
// Other plugins may be piggybacking on OStatus without having
|
||||||
return true;
|
// an active group or user-to-user subscription we know about.
|
||||||
} else {
|
Event::handle('Ostatus_profileSubscriberCount', array($this, &$count));
|
||||||
return false;
|
common_log(LOG_INFO, __METHOD__ . " SUB COUNT AFTER: $count");
|
||||||
}
|
|
||||||
|
return $count;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -146,4 +146,27 @@ class SubMirrorPlugin extends Plugin
|
|||||||
array('href' => common_local_url('mirrorsettings')),
|
array('href' => common_local_url('mirrorsettings')),
|
||||||
_m('Set up mirroring options...'));
|
_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;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -46,9 +46,8 @@ if (!defined('STATUSNET')) {
|
|||||||
* @link http://status.net/
|
* @link http://status.net/
|
||||||
*/
|
*/
|
||||||
|
|
||||||
class AddMirrorAction extends Action
|
class AddMirrorAction extends BaseMirrorAction
|
||||||
{
|
{
|
||||||
var $user;
|
|
||||||
var $feedurl;
|
var $feedurl;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -62,94 +61,17 @@ class AddMirrorAction extends Action
|
|||||||
function prepare($args)
|
function prepare($args)
|
||||||
{
|
{
|
||||||
parent::prepare($args);
|
parent::prepare($args);
|
||||||
$ok = $this->sharedBoilerplate();
|
$this->feedurl = $this->validateFeedUrl($this->trimmed('feedurl'));
|
||||||
if ($ok) {
|
$this->profile = $this->profileForFeed($this->feedurl);
|
||||||
// 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;
|
|
||||||
}
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Handle request
|
|
||||||
*
|
|
||||||
* Does the subscription and returns results.
|
|
||||||
*
|
|
||||||
* @param Array $args unused.
|
|
||||||
*
|
|
||||||
* @return void
|
|
||||||
*/
|
|
||||||
|
|
||||||
function handle($args)
|
|
||||||
{
|
|
||||||
// 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');
|
|
||||||
} else {
|
|
||||||
$url = common_local_url('mirrorsettings');
|
|
||||||
common_redirect($url, 303);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function saveMirror()
|
function saveMirror()
|
||||||
{
|
{
|
||||||
SubMirror::saveMirror($this->user, $this->profile);
|
if ($this->oprofile->subscribe()) {
|
||||||
|
SubMirror::saveMirror($this->user, $this->profile);
|
||||||
|
} else {
|
||||||
|
$this->serverError(_m("Could not subscribe to feed."));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -46,7 +46,7 @@ if (!defined('STATUSNET')) {
|
|||||||
* @link http://status.net/
|
* @link http://status.net/
|
||||||
*/
|
*/
|
||||||
|
|
||||||
class EditMirrorAction extends AddMirrorAction
|
class EditMirrorAction extends BaseMirrorAction
|
||||||
{
|
{
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -60,6 +60,9 @@ class EditMirrorAction extends AddMirrorAction
|
|||||||
function prepare($args)
|
function prepare($args)
|
||||||
{
|
{
|
||||||
parent::prepare($args);
|
parent::prepare($args);
|
||||||
|
|
||||||
|
$this->profile = $this->validateProfile($this->trimmed('profile'));
|
||||||
|
|
||||||
$this->mirror = SubMirror::pkeyGet(array('subscriber' => $this->user->id,
|
$this->mirror = SubMirror::pkeyGet(array('subscriber' => $this->user->id,
|
||||||
'subscribed' => $this->profile->id));
|
'subscribed' => $this->profile->id));
|
||||||
|
|
||||||
@ -95,6 +98,10 @@ class EditMirrorAction extends AddMirrorAction
|
|||||||
|
|
||||||
if ($this->delete) {
|
if ($this->delete) {
|
||||||
$mirror->delete();
|
$mirror->delete();
|
||||||
|
$oprofile = Ostatus_profile::staticGet('profile_id', $this->profile->id);
|
||||||
|
if ($oprofile) {
|
||||||
|
$oprofile->garbageCollect();
|
||||||
|
}
|
||||||
} else if ($this->style != $mirror->style) {
|
} else if ($this->style != $mirror->style) {
|
||||||
$orig = clone($mirror);
|
$orig = clone($mirror);
|
||||||
$mirror->style = $this->style;
|
$mirror->style = $this->style;
|
||||||
|
@ -50,7 +50,7 @@ class MirrorSettingsAction extends AccountSettingsAction
|
|||||||
|
|
||||||
function getInstructions()
|
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!');
|
'into your StatusNet timeline!');
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -65,6 +65,8 @@ class MirrorSettingsAction extends AccountSettingsAction
|
|||||||
function showContent()
|
function showContent()
|
||||||
{
|
{
|
||||||
$user = common_current_user();
|
$user = common_current_user();
|
||||||
|
|
||||||
|
$this->showAddFeedForm();
|
||||||
|
|
||||||
$mirror = new SubMirror();
|
$mirror = new SubMirror();
|
||||||
$mirror->subscriber = $user->id;
|
$mirror->subscriber = $user->id;
|
||||||
@ -73,7 +75,6 @@ class MirrorSettingsAction extends AccountSettingsAction
|
|||||||
$this->showFeedForm($mirror);
|
$this->showFeedForm($mirror);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
$this->showAddFeedForm();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function showFeedForm($mirror)
|
function showFeedForm($mirror)
|
||||||
|
@ -57,46 +57,15 @@ class AddMirrorForm extends Form
|
|||||||
$this->out->elementStart('ul');
|
$this->out->elementStart('ul');
|
||||||
|
|
||||||
$this->li();
|
$this->li();
|
||||||
$this->out->element('label', array('for' => $this->id() . '-profile'),
|
$this->doInput('addmirror-feedurl',
|
||||||
_m("Mirror one of your existing subscriptions:"));
|
'feedurl',
|
||||||
$this->out->elementStart('select', array('name' => 'profile'));
|
_m('Web page or feed URL:'),
|
||||||
|
$this->out->trimmed('feedurl'));
|
||||||
$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->unli();
|
$this->unli();
|
||||||
|
|
||||||
|
|
||||||
$this->li();
|
$this->li();
|
||||||
|
$this->out->submit('addmirror-save', _m('Add feed'));
|
||||||
$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->unli();
|
$this->unli();
|
||||||
|
|
||||||
$this->out->elementEnd('ul');
|
$this->out->elementEnd('ul');
|
||||||
$this->out->elementEnd('fieldset');
|
$this->out->elementEnd('fieldset');
|
||||||
}
|
}
|
||||||
@ -106,7 +75,8 @@ class AddMirrorForm extends Form
|
|||||||
$this->out->element('label', array('for' => $id), $label);
|
$this->out->element('label', array('for' => $id), $label);
|
||||||
$attrs = array('name' => $name,
|
$attrs = array('name' => $name,
|
||||||
'type' => 'text',
|
'type' => 'text',
|
||||||
'id' => $id);
|
'id' => $id,
|
||||||
|
'style' => 'width: 80%');
|
||||||
if ($value) {
|
if ($value) {
|
||||||
$attrs['value'] = $value;
|
$attrs['value'] = $value;
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user