From d44e5ac935ec50ec48dd3f1599e5fbba39b3278a Mon Sep 17 00:00:00 2001 From: Brion Vibber Date: Mon, 29 Mar 2010 12:57:16 -0700 Subject: Add $config['db']['annotate_queries'] option to include caller ID comments into queries. Comment can then be seen in process list, slow query logs on the server, aiding in tracking down unexpected slow queries. SELECT /* queuedaemon.php Ostatus_profile->processPost */ * FROM notice WHERE ( notice.uri = 'http://stormcloud.local/mublog2/notice/479' ) INSERT /* POST Notice::saveNew */ INTO notice (profile_id , content .... --- classes/Memcached_DataObject.php | 68 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 68 insertions(+) (limited to 'classes/Memcached_DataObject.php') diff --git a/classes/Memcached_DataObject.php b/classes/Memcached_DataObject.php index bc4c3a000..8d54e1f0f 100644 --- a/classes/Memcached_DataObject.php +++ b/classes/Memcached_DataObject.php @@ -330,6 +330,10 @@ class Memcached_DataObject extends Safe_DataObject */ function _query($string) { + if (common_config('db', 'annotate_queries')) { + $string = $this->annotateQuery($string); + } + $start = microtime(true); $result = parent::_query($string); $delta = microtime(true) - $start; @@ -342,6 +346,70 @@ class Memcached_DataObject extends Safe_DataObject return $result; } + /** + * Find the first caller in the stack trace that's not a + * low-level database function and add a comment to the + * query string. This should then be visible in process lists + * and slow query logs, to help identify problem areas. + * + * Also marks whether this was a web GET/POST or which daemon + * was running it. + * + * @param string $string SQL query string + * @return string SQL query string, with a comment in it + */ + function annotateQuery($string) + { + $ignore = array('annotateQuery', + '_query', + 'query', + 'get', + 'insert', + 'delete', + 'update', + 'find'); + $ignoreStatic = array('staticGet', + 'pkeyGet', + 'cachedQuery'); + $here = get_class($this); // if we get confused + $bt = debug_backtrace(); + + // Find the first caller that's not us? + foreach ($bt as $frame) { + $func = $frame['function']; + if (isset($frame['type']) && $frame['type'] == '::') { + if (in_array($func, $ignoreStatic)) { + continue; + } + $here = $frame['class'] . '::' . $func; + break; + } else if (isset($frame['type']) && $frame['type'] == '->') { + if ($frame['object'] === $this && in_array($func, $ignore)) { + continue; + } + if (in_array($func, $ignoreStatic)) { + continue; // @fixme this shouldn't be needed? + } + $here = get_class($frame['object']) . '->' . $func; + break; + } + $here = $func; + break; + } + + if (php_sapi_name() == 'cli') { + $context = basename($_SERVER['PHP_SELF']); + } else { + $context = $_SERVER['REQUEST_METHOD']; + } + + // Slip the comment in after the first command, + // or DB_DataObject gets confused about handling inserts and such. + $parts = explode(' ', $string, 2); + $parts[0] .= " /* $context $here */"; + return implode(' ', $parts); + } + // Sanitize a query for logging // @fixme don't trim spaces in string literals function sanitizeQuery($string) -- cgit v1.2.3-54-g00ecf From 82326a3b690552cccd8c82950b7653556f6522c1 Mon Sep 17 00:00:00 2001 From: Evan Prodromou Date: Wed, 7 Apr 2010 10:43:14 -0400 Subject: show PEAR_Error message, not the object --- classes/Memcached_DataObject.php | 3 +++ 1 file changed, 3 insertions(+) (limited to 'classes/Memcached_DataObject.php') diff --git a/classes/Memcached_DataObject.php b/classes/Memcached_DataObject.php index bc4c3a000..920966b16 100644 --- a/classes/Memcached_DataObject.php +++ b/classes/Memcached_DataObject.php @@ -505,6 +505,9 @@ class Memcached_DataObject extends Safe_DataObject if ($this->id) { $id .= ':' . $this->id; } + if ($message instanceof PEAR_Error) { + $message = $message->getMessage(); + } throw new ServerException("[$id] DB_DataObject error [$type]: $message"); } -- cgit v1.2.3-54-g00ecf From f1ea678aae968c63e9f32adfe1b6da386adb3304 Mon Sep 17 00:00:00 2001 From: Evan Prodromou Date: Fri, 4 Jun 2010 12:52:05 -0400 Subject: memcache_dataobject supports some DB_DataObject_Cast objects as values --- classes/Memcached_DataObject.php | 25 ++++++++++++++++++++++--- 1 file changed, 22 insertions(+), 3 deletions(-) (limited to 'classes/Memcached_DataObject.php') diff --git a/classes/Memcached_DataObject.php b/classes/Memcached_DataObject.php index 0836c2019..bd9b528db 100644 --- a/classes/Memcached_DataObject.php +++ b/classes/Memcached_DataObject.php @@ -128,12 +128,31 @@ class Memcached_DataObject extends Safe_DataObject } static function cacheKey($cls, $k, $v) { - if (is_object($cls) || is_object($k) || is_object($v)) { + if (is_object($cls) || is_object($k) || (is_object($v) && !($v instanceof DB_DataObject_Cast))) { $e = new Exception(); common_log(LOG_ERR, __METHOD__ . ' object in param: ' . str_replace("\n", " ", $e->getTraceAsString())); } - return common_cache_key(strtolower($cls).':'.$k.':'.$v); + if (is_object($v) && $v instanceof DB_DataObject_Cast) { + switch ($v->type) { + case 'date': + $vstr = $v->year . '-' . $v->month . '-' . $v->day; + break; + case 'blob': + case 'string': + case 'sql': + case 'datetime': + case 'time': + throw new ServerException("Unhandled DB_DataObject_Cast type passed as cacheKey value: '$v->type'"); + break; + default: + throw new ServerException("Unknown DB_DataObject_Cast type passed as cacheKey value: '$v->type'"); + break; + } + } else { + $vstr = $v; + } + return common_cache_key(strtolower($cls).':'.$k.':'.$vstr); } static function getcached($cls, $k, $v) { @@ -351,7 +370,7 @@ class Memcached_DataObject extends Safe_DataObject * low-level database function and add a comment to the * query string. This should then be visible in process lists * and slow query logs, to help identify problem areas. - * + * * Also marks whether this was a web GET/POST or which daemon * was running it. * -- cgit v1.2.3-54-g00ecf From 6d39a7513787c6747fc9ff50a33a733eb3148682 Mon Sep 17 00:00:00 2001 From: Evan Prodromou Date: Fri, 4 Jun 2010 15:29:38 -0400 Subject: use DB_DataObject_Cast objects in a couple of different places in the code --- classes/Memcached_DataObject.php | 49 +++++++++++++++++++++++----------------- 1 file changed, 28 insertions(+), 21 deletions(-) (limited to 'classes/Memcached_DataObject.php') diff --git a/classes/Memcached_DataObject.php b/classes/Memcached_DataObject.php index bd9b528db..4579f64df 100644 --- a/classes/Memcached_DataObject.php +++ b/classes/Memcached_DataObject.php @@ -133,25 +133,7 @@ class Memcached_DataObject extends Safe_DataObject common_log(LOG_ERR, __METHOD__ . ' object in param: ' . str_replace("\n", " ", $e->getTraceAsString())); } - if (is_object($v) && $v instanceof DB_DataObject_Cast) { - switch ($v->type) { - case 'date': - $vstr = $v->year . '-' . $v->month . '-' . $v->day; - break; - case 'blob': - case 'string': - case 'sql': - case 'datetime': - case 'time': - throw new ServerException("Unhandled DB_DataObject_Cast type passed as cacheKey value: '$v->type'"); - break; - default: - throw new ServerException("Unknown DB_DataObject_Cast type passed as cacheKey value: '$v->type'"); - break; - } - } else { - $vstr = $v; - } + $vstr = self::valueString($v); return common_cache_key(strtolower($cls).':'.$k.':'.$vstr); } @@ -248,10 +230,10 @@ class Memcached_DataObject extends Safe_DataObject if (empty($this->$key)) { continue; } - $ckeys[] = $this->cacheKey($this->tableName(), $key, $this->$key); + $ckeys[] = $this->cacheKey($this->tableName(), $key, self::valueString($this->$key)); } else if ($type == 'K' || $type == 'N') { $pkey[] = $key; - $pval[] = $this->$key; + $pval[] = self::valueString($this->$key); } else { throw new Exception("Unknown key type $key => $type for " . $this->tableName()); } @@ -623,5 +605,30 @@ class Memcached_DataObject extends Safe_DataObject return $c->set($cacheKey, $value); } + + static function valueString($v) + { + $vstr = null; + if (is_object($v) && $v instanceof DB_DataObject_Cast) { + switch ($v->type) { + case 'date': + $vstr = $v->year . '-' . $v->month . '-' . $v->day; + break; + case 'blob': + case 'string': + case 'sql': + case 'datetime': + case 'time': + throw new ServerException("Unhandled DB_DataObject_Cast type passed as cacheKey value: '$v->type'"); + break; + default: + throw new ServerException("Unknown DB_DataObject_Cast type passed as cacheKey value: '$v->type'"); + break; + } + } else { + $vstr = strval($v); + } + return $vstr; + } } -- cgit v1.2.3-54-g00ecf From d73feb82d89d66593fd81f8bb5d10b1873fc9458 Mon Sep 17 00:00:00 2001 From: Evan Prodromou Date: Wed, 14 Jul 2010 10:38:34 -0400 Subject: cache sitemap notice and user counts for 4h --- classes/Memcached_DataObject.php | 4 ++-- plugins/Sitemap/Sitemap_notice_count.php | 4 +++- plugins/Sitemap/Sitemap_user_count.php | 4 +++- 3 files changed, 8 insertions(+), 4 deletions(-) (limited to 'classes/Memcached_DataObject.php') diff --git a/classes/Memcached_DataObject.php b/classes/Memcached_DataObject.php index 4579f64df..a7fec365e 100644 --- a/classes/Memcached_DataObject.php +++ b/classes/Memcached_DataObject.php @@ -593,7 +593,7 @@ class Memcached_DataObject extends Safe_DataObject return $c->get($cacheKey); } - static function cacheSet($keyPart, $value) + static function cacheSet($keyPart, $value, $flag=null, $expiry=null) { $c = self::memcache(); @@ -603,7 +603,7 @@ class Memcached_DataObject extends Safe_DataObject $cacheKey = common_cache_key($keyPart); - return $c->set($cacheKey, $value); + return $c->set($cacheKey, $value, $flag, $expiry); } static function valueString($v) diff --git a/plugins/Sitemap/Sitemap_notice_count.php b/plugins/Sitemap/Sitemap_notice_count.php index 2a375b3e4..6e0061e97 100644 --- a/plugins/Sitemap/Sitemap_notice_count.php +++ b/plugins/Sitemap/Sitemap_notice_count.php @@ -153,7 +153,9 @@ class Sitemap_notice_count extends Memcached_DataObject $noticeCounts[$snc->notice_date] = $snc->notice_count; } - self::cacheSet('sitemap:notice:counts', $noticeCounts); + // Cache notice counts for 4 hours. + + self::cacheSet('sitemap:notice:counts', $noticeCounts, null, time() + 4 * 60 * 60); } return $noticeCounts; diff --git a/plugins/Sitemap/Sitemap_user_count.php b/plugins/Sitemap/Sitemap_user_count.php index 64b4c3442..98dd05bfe 100644 --- a/plugins/Sitemap/Sitemap_user_count.php +++ b/plugins/Sitemap/Sitemap_user_count.php @@ -154,7 +154,9 @@ class Sitemap_user_count extends Memcached_DataObject $userCounts[$suc->registration_date] = $suc->user_count; } - self::cacheSet('sitemap:user:counts', $userCounts); + // Cache user counts for 4 hours. + + self::cacheSet('sitemap:user:counts', $userCounts, null, time() + 4 * 60 * 60); } return $userCounts; -- cgit v1.2.3-54-g00ecf From e7acb45b5733e97ab9504aff5e4255a17fe5c6de Mon Sep 17 00:00:00 2001 From: Siebrand Mazeland Date: Thu, 29 Jul 2010 12:58:48 +0200 Subject: * add string for translation with translator documentation * add FIXMEs for strings that may need i18n, but leaving decision to other dev(s) --- classes/Memcached_DataObject.php | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) (limited to 'classes/Memcached_DataObject.php') diff --git a/classes/Memcached_DataObject.php b/classes/Memcached_DataObject.php index a7fec365e..04cd6c120 100644 --- a/classes/Memcached_DataObject.php +++ b/classes/Memcached_DataObject.php @@ -235,6 +235,7 @@ class Memcached_DataObject extends Safe_DataObject $pkey[] = $key; $pval[] = self::valueString($this->$key); } else { + // FIXME: i18n? throw new Exception("Unknown key type $key => $type for " . $this->tableName()); } } @@ -282,6 +283,7 @@ class Memcached_DataObject extends Safe_DataObject } else if ($type == 'fulltext') { $search_engine = new MySQLSearch($this, $table); } else { + // FIXME: i18n? throw new ServerException('Unknown search type: ' . $type); } } else { @@ -527,7 +529,8 @@ class Memcached_DataObject extends Safe_DataObject } if (!$dsn) { - throw new Exception("No database name / dsn found anywhere"); + // TRANS: Exception thrown when database name or Data Source Name could not be found. + throw new Exception(_("No database name / DSN found anywhere")); } return $dsn; @@ -577,6 +580,7 @@ class Memcached_DataObject extends Safe_DataObject if ($message instanceof PEAR_Error) { $message = $message->getMessage(); } + // FIXME: i18n? throw new ServerException("[$id] DB_DataObject error [$type]: $message"); } @@ -619,9 +623,11 @@ class Memcached_DataObject extends Safe_DataObject case 'sql': case 'datetime': case 'time': + // FIXME: i18n? throw new ServerException("Unhandled DB_DataObject_Cast type passed as cacheKey value: '$v->type'"); break; default: + // FIXME: i18n? throw new ServerException("Unknown DB_DataObject_Cast type passed as cacheKey value: '$v->type'"); break; } -- cgit v1.2.3-54-g00ecf From e7534224802f3de009d5d29563070392c376f100 Mon Sep 17 00:00:00 2001 From: Siebrand Mazeland Date: Fri, 30 Jul 2010 19:15:07 +0200 Subject: * Address i18n related FIXMEs after talk with Brion. * Tweak message --- classes/Memcached_DataObject.php | 12 ++++++------ classes/Safe_DataObject.php | 4 ++-- 2 files changed, 8 insertions(+), 8 deletions(-) (limited to 'classes/Memcached_DataObject.php') diff --git a/classes/Memcached_DataObject.php b/classes/Memcached_DataObject.php index 04cd6c120..7768fe757 100644 --- a/classes/Memcached_DataObject.php +++ b/classes/Memcached_DataObject.php @@ -235,7 +235,7 @@ class Memcached_DataObject extends Safe_DataObject $pkey[] = $key; $pval[] = self::valueString($this->$key); } else { - // FIXME: i18n? + // Low level exception. No need for i18n as discussed with Brion. throw new Exception("Unknown key type $key => $type for " . $this->tableName()); } } @@ -283,7 +283,7 @@ class Memcached_DataObject extends Safe_DataObject } else if ($type == 'fulltext') { $search_engine = new MySQLSearch($this, $table); } else { - // FIXME: i18n? + // Low level exception. No need for i18n as discussed with Brion. throw new ServerException('Unknown search type: ' . $type); } } else { @@ -530,7 +530,7 @@ class Memcached_DataObject extends Safe_DataObject if (!$dsn) { // TRANS: Exception thrown when database name or Data Source Name could not be found. - throw new Exception(_("No database name / DSN found anywhere")); + throw new Exception(_("No database name or DSN found anywhere.")); } return $dsn; @@ -580,7 +580,7 @@ class Memcached_DataObject extends Safe_DataObject if ($message instanceof PEAR_Error) { $message = $message->getMessage(); } - // FIXME: i18n? + // Low level exception. No need for i18n as discussed with Brion. throw new ServerException("[$id] DB_DataObject error [$type]: $message"); } @@ -623,11 +623,11 @@ class Memcached_DataObject extends Safe_DataObject case 'sql': case 'datetime': case 'time': - // FIXME: i18n? + // Low level exception. No need for i18n as discussed with Brion. throw new ServerException("Unhandled DB_DataObject_Cast type passed as cacheKey value: '$v->type'"); break; default: - // FIXME: i18n? + // Low level exception. No need for i18n as discussed with Brion. throw new ServerException("Unknown DB_DataObject_Cast type passed as cacheKey value: '$v->type'"); break; } diff --git a/classes/Safe_DataObject.php b/classes/Safe_DataObject.php index 16d7165d6..f0ea6b136 100644 --- a/classes/Safe_DataObject.php +++ b/classes/Safe_DataObject.php @@ -116,7 +116,7 @@ class Safe_DataObject extends DB_DataObject if ($this->_call($method, $params, $return)) { return $return; } else { - // FIXME: i18n? + // Low level exception. No need for i18n as discussed with Brion. throw new Exception('Call to undefined method ' . get_class($this) . '::' . $method); } @@ -242,7 +242,7 @@ class Safe_DataObject extends DB_DataObject $this->debug("Cant find database schema: {$this->_database}/{$this->__table} \n". "in links file data: " . print_r($_DB_DATAOBJECT['INI'],true),"databaseStructure",5); // we have to die here!! - it causes chaos if we don't (including looping forever!) - // FIXME: i18n? + // Low level exception. No need for i18n as discussed with Brion. $this->raiseError( "Unable to load schema for database and table (turn debugging up to 5 for full error message)", DB_DATAOBJECT_ERROR_INVALIDARGS, PEAR_ERROR_DIE); return false; } -- cgit v1.2.3-54-g00ecf From 7e55fc00447923b40b2ffc87329fd95347d776f5 Mon Sep 17 00:00:00 2001 From: Brion Vibber Date: Fri, 6 Aug 2010 10:56:18 -0700 Subject: 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 --- classes/Memcached_DataObject.php | 2 +- classes/Profile.php | 4 +- plugins/OStatus/OStatusPlugin.php | 18 ++++++ plugins/OStatus/classes/FeedSub.php | 30 +++++++++ plugins/OStatus/classes/Ostatus_profile.php | 45 ++++++++------ plugins/SubMirror/SubMirrorPlugin.php | 23 +++++++ plugins/SubMirror/actions/addmirror.php | 92 +++------------------------- plugins/SubMirror/actions/editmirror.php | 9 ++- plugins/SubMirror/actions/mirrorsettings.php | 5 +- plugins/SubMirror/lib/addmirrorform.php | 44 +++---------- 10 files changed, 124 insertions(+), 148 deletions(-) (limited to 'classes/Memcached_DataObject.php') diff --git a/classes/Memcached_DataObject.php b/classes/Memcached_DataObject.php index 7768fe757..0f1ed0489 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 0d0463b73..d7617f0b7 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 70971c5b3..3b073a5d1 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 b10509dae..9cd35e29c 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 5d3f37cd0..2d7c632e6 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 8678cc3dd..00c04ad0b 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 df6939491..5acdf1dfe 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 7ddd32ef3..c7fdab0d6 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 edb024183..5463a8dc0 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 9ee59661a..0a798c9ea 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; } -- cgit v1.2.3-54-g00ecf