From da3089098817234627e48619f3a8b217970f8627 Mon Sep 17 00:00:00 2001 From: Evan Prodromou Date: Fri, 11 Dec 2009 10:22:56 -0500 Subject: add repeat_of column to notice class --- classes/Notice.php | 14 ++++++++------ classes/statusnet.ini | 2 ++ 2 files changed, 10 insertions(+), 6 deletions(-) mode change 100644 => 100755 classes/statusnet.ini (limited to 'classes') diff --git a/classes/Notice.php b/classes/Notice.php index c36c5a9c6..0a2f16271 100644 --- a/classes/Notice.php +++ b/classes/Notice.php @@ -55,13 +55,13 @@ class Notice extends Memcached_DataObject public $__table = 'notice'; // table name public $id; // int(4) primary_key not_null - public $profile_id; // int(4) not_null + public $profile_id; // int(4) multiple_key not_null public $uri; // varchar(255) unique_key - public $content; // text() - public $rendered; // text() + public $content; // text + public $rendered; // text public $url; // varchar(255) - public $created; // datetime() not_null - public $modified; // timestamp() not_null default_CURRENT_TIMESTAMP + public $created; // datetime multiple_key not_null default_0000-00-00%2000%3A00%3A00 + public $modified; // timestamp not_null default_CURRENT_TIMESTAMP public $reply_to; // int(4) public $is_local; // tinyint(1) public $source; // varchar(32) @@ -70,9 +70,11 @@ class Notice extends Memcached_DataObject public $lon; // decimal(10,7) public $location_id; // int(4) public $location_ns; // int(4) + public $repeat_of; // int(4) /* Static get */ - function staticGet($k,$v=NULL) { + function staticGet($k,$v=NULL) + { return Memcached_DataObject::staticGet('Notice',$k,$v); } diff --git a/classes/statusnet.ini b/classes/statusnet.ini old mode 100644 new mode 100755 index 835faeb0b..ff4ef2c14 --- a/classes/statusnet.ini +++ b/classes/statusnet.ini @@ -1,3 +1,4 @@ + [avatar] profile_id = 129 original = 17 @@ -306,6 +307,7 @@ lat = 1 lon = 1 location_id = 1 location_ns = 1 +repeat_of = 1 [notice__keys] id = N -- cgit v1.2.3-54-g00ecf From c9649f932153e6636214841858fd265b1476c05b Mon Sep 17 00:00:00 2001 From: Evan Prodromou Date: Fri, 11 Dec 2009 10:23:36 -0500 Subject: reset executable bit on Notice.php and statusnet.ini --- classes/statusnet.ini | 0 1 file changed, 0 insertions(+), 0 deletions(-) mode change 100755 => 100644 classes/statusnet.ini (limited to 'classes') diff --git a/classes/statusnet.ini b/classes/statusnet.ini old mode 100755 new mode 100644 -- cgit v1.2.3-54-g00ecf From 81843f2acd5375a9072d091fd58c6a6af079295e Mon Sep 17 00:00:00 2001 From: Evan Prodromou Date: Fri, 11 Dec 2009 10:49:26 -0500 Subject: show the repeat form in notice lists --- classes/Profile.php | 11 +++++++++++ lib/noticelist.php | 21 +++++++++++++++++++++ lib/repeatform.php | 26 ++++++++++++-------------- 3 files changed, 44 insertions(+), 14 deletions(-) (limited to 'classes') diff --git a/classes/Profile.php b/classes/Profile.php index 4b2e09006..03196447b 100644 --- a/classes/Profile.php +++ b/classes/Profile.php @@ -716,4 +716,15 @@ class Profile extends Memcached_DataObject } return $result; } + + function hasRepeated($notice_id) + { + // XXX: not really a pkey, but should work + + $notice = Memcached_DataObject::pkeyGet('Notice', + array('profile_id' => $this->id, + 'repeat_of' => $notice_id)); + + return !empty($notice); + } } diff --git a/lib/noticelist.php b/lib/noticelist.php index 21cec528f..924056ece 100644 --- a/lib/noticelist.php +++ b/lib/noticelist.php @@ -212,6 +212,7 @@ class NoticeListItem extends Widget $this->out->elementStart('div', 'notice-options'); $this->showFaveForm(); $this->showReplyLink(); + $this->showRepeatForm(); $this->showDeleteLink(); $this->out->elementEnd('div'); } @@ -551,6 +552,26 @@ class NoticeListItem extends Widget } } + /** + * show the form to repeat a notice + * + * @return void + */ + + function showRepeatForm() + { + $user = common_current_user(); + if ($user && $user->id != $this->notice->profile_id) { + $profile = $user->getProfile(); + if ($profile->hasRepeated($this->notice->id)) { + $this->out->text(_('Repeated')); + } else { + $rf = new RepeatForm($this->out, $this->notice); + $rf->show(); + } + } + } + /** * finish the notice * diff --git a/lib/repeatform.php b/lib/repeatform.php index 2052856ae..50e5d6dbe 100644 --- a/lib/repeatform.php +++ b/lib/repeatform.php @@ -2,7 +2,7 @@ /** * StatusNet, the distributed open-source microblogging tool * - * Form for forwarding a notice + * Form for repeating a notice * * PHP version 5 * @@ -27,14 +27,12 @@ * @link http://status.net/ */ -if (!defined('STATUSNET') && !defined('LACONICA')) { +if (!defined('STATUSNET')) { exit(1); } -require_once INSTALLDIR.'/lib/form.php'; - /** - * Form for forwarding a notice + * Form for repeating a notice * * @category Form * @package StatusNet @@ -43,10 +41,10 @@ require_once INSTALLDIR.'/lib/form.php'; * @link http://status.net/ */ -class ForwardForm extends Form +class RepeatForm extends Form { /** - * Notice to forward + * Notice to repeat */ var $notice = null; @@ -55,7 +53,7 @@ class ForwardForm extends Form * Constructor * * @param HTMLOutputter $out output channel - * @param Notice $notice notice to forward + * @param Notice $notice notice to repeat */ function __construct($out=null, $notice=null) @@ -73,7 +71,7 @@ class ForwardForm extends Form function id() { - return 'forward-' . $this->notice->id; + return 'repeat-' . $this->notice->id; } /** @@ -84,7 +82,7 @@ class ForwardForm extends Form function action() { - return common_local_url('forward'); + return common_local_url('repeat'); } /** @@ -106,7 +104,7 @@ class ForwardForm extends Form */ function formLegend() { - $this->out->element('legend', null, _('Forward this notice')); + $this->out->element('legend', null, _('Repeat this notice')); } /** @@ -130,8 +128,8 @@ class ForwardForm extends Form function formActions() { - $this->out->submit('forward-submit-' . $this->notice->id, - _('Forward'), 'submit', null, _('Forward this notice')); + $this->out->submit('repeat-submit-' . $this->notice->id, + _('Repeat'), 'submit', null, _('Repeat this notice')); } /** @@ -142,6 +140,6 @@ class ForwardForm extends Form function formClass() { - return 'form_forward'; + return 'form_repeat'; } } -- cgit v1.2.3-54-g00ecf From 79f81ad76d413908c097c121912eff233aebc483 Mon Sep 17 00:00:00 2001 From: Evan Prodromou Date: Fri, 11 Dec 2009 11:29:51 -0500 Subject: change Notice::saveNew() to use named arguments for little-used options --- actions/apistatusesupdate.php | 29 ++++++++++++++++------------- actions/newnotice.php | 10 ++++++---- classes/Notice.php | 15 ++++++++++++--- lib/command.php | 5 +++-- lib/oauthstore.php | 5 ++--- plugins/Facebook/facebookaction.php | 5 +++-- 6 files changed, 42 insertions(+), 27 deletions(-) (limited to 'classes') diff --git a/actions/apistatusesupdate.php b/actions/apistatusesupdate.php index 85a7c8c08..10bbc5ace 100644 --- a/actions/apistatusesupdate.php +++ b/actions/apistatusesupdate.php @@ -231,19 +231,22 @@ class ApiStatusesUpdateAction extends ApiAuthAction } } - $this->notice = Notice::saveNew( - $this->user->id, - html_entity_decode($status_shortened, ENT_NOQUOTES, 'UTF-8'), - $this->source, - 1, - $reply_to, - null, - null, - empty($location) ? null : $location->lat, - empty($location) ? null : $location->lon, - empty($location) ? null : $location->location_id, - empty($location) ? null : $location->location_ns - ); + $content = html_entity_decode($status_shortened, ENT_NOQUOTES, 'UTF-8'), + + $options = array('reply_to' => $reply_to); + + if (!empty($location)) { + $options['lat'] = $location->lat; + $options['lon'] = $location->lon; + $options['location_id'] = $location->location_id; + $options['location_ns'] = $location->location_ns; + } + + $this->notice = + Notice::saveNew($this->user->id, + $content, + $this->source, + $options); if (isset($upload)) { $upload->attachToNotice($this->notice); diff --git a/actions/newnotice.php b/actions/newnotice.php index dd6da0b01..c6c70e326 100644 --- a/actions/newnotice.php +++ b/actions/newnotice.php @@ -187,10 +187,12 @@ class NewnoticeAction extends Action } } - $notice = Notice::saveNew($user->id, $content_shortened, 'web', 1, - ($replyto == 'false') ? null : $replyto, - null, null, - $lat, $lon, $location_id, $location_ns); + $notice = Notice::saveNew($user->id, $content_shortened, 'web', + array('reply_to' => ($replyto == 'false') ? null : $replyto, + 'lat' => $lat, + 'lon' => $lon, + 'location_id' => $location_id, + 'location_ns' => $location_ns)); if (isset($upload)) { $upload->attachToNotice($notice); diff --git a/classes/Notice.php b/classes/Notice.php index c36c5a9c6..4422866fa 100644 --- a/classes/Notice.php +++ b/classes/Notice.php @@ -167,9 +167,18 @@ class Notice extends Memcached_DataObject } } - static function saveNew($profile_id, $content, $source=null, - $is_local=Notice::LOCAL_PUBLIC, $reply_to=null, $uri=null, $created=null, - $lat=null, $lon=null, $location_id=null, $location_ns=null) { + static function saveNew($profile_id, $content, $source, $options=null) { + + if (!empty($options)) { + extract($options); + if (!isset($reply_to)) { + $reply_to = NULL; + } + } + + if (empty($is_local)) { + $is_local = Notice::LOCAL_PUBLIC; + } $profile = Profile::staticGet($profile_id); diff --git a/lib/command.php b/lib/command.php index 450db9da3..085331f82 100644 --- a/lib/command.php +++ b/lib/command.php @@ -433,8 +433,9 @@ class ReplyCommand extends Command return; } - $notice = Notice::saveNew($this->user->id, $this->text, $channel->source(), 1, - $notice->id); + $notice = Notice::saveNew($this->user->id, $this->text, $channel->source(), + array('reply_to' => $notice->id)); + if ($notice) { $channel->output($this->user, sprintf(_('Reply to %s sent'), $recipient->nickname)); } else { diff --git a/lib/oauthstore.php b/lib/oauthstore.php index e34bf8a5e..df63cc151 100644 --- a/lib/oauthstore.php +++ b/lib/oauthstore.php @@ -359,9 +359,8 @@ class StatusNetOAuthDataStore extends OAuthDataStore $notice = Notice::saveNew($author->id, $omb_notice->getContent(), 'omb', - false, - null, - $omb_notice->getIdentifierURI()); + array('is_local' => Notice::REMOTE_OMB, + 'uri' => $omb_notice->getIdentifierURI())); common_broadcast_notice($notice, true); } diff --git a/plugins/Facebook/facebookaction.php b/plugins/Facebook/facebookaction.php index 705bb2780..24bf215fd 100644 --- a/plugins/Facebook/facebookaction.php +++ b/plugins/Facebook/facebookaction.php @@ -445,8 +445,9 @@ class FacebookAction extends Action $replyto = $this->trimmed('inreplyto'); try { - $notice = Notice::saveNew($user->id, $content, - 'web', 1, ($replyto == 'false') ? null : $replyto); + $notice = Notice::saveNew($user->id, $content, 'web', + array('reply_to' => ($replyto == 'false') ? null : $replyto)); + } catch (Exception $e) { $this->showPage($e->getMessage()); return; -- cgit v1.2.3-54-g00ecf From afc86a86d36dc8101c81d5d009546e4629db34a3 Mon Sep 17 00:00:00 2001 From: Evan Prodromou Date: Fri, 11 Dec 2009 11:51:09 -0500 Subject: save repeats from the form --- actions/repeat.php | 20 ++++++++++---------- classes/Notice.php | 23 ++++++++++++++++++++++- lib/router.php | 1 + 3 files changed, 33 insertions(+), 11 deletions(-) (limited to 'classes') diff --git a/actions/repeat.php b/actions/repeat.php index 194833fe0..a1c5f443f 100644 --- a/actions/repeat.php +++ b/actions/repeat.php @@ -1,7 +1,7 @@ user = common_current_user(); if (empty($this->user)) { - $this->clientError(_("Only logged-in users can forward notices.")); + $this->clientError(_("Only logged-in users can repeat notices.")); return false; } @@ -73,7 +73,7 @@ class ForwardAction extends Action } if ($this->user->id == $this->notice->profile_id) { - $this->clientError(_("You can't forward your own notice.")); + $this->clientError(_("You can't repeat your own notice.")); return false; } @@ -86,8 +86,8 @@ class ForwardAction extends Action $profile = $this->user->getProfile(); - if ($profile->hasForwarded($id)) { - $this->clientError(_("You already forwarded that notice.")); + if ($profile->hasRepeated($id)) { + $this->clientError(_("You already repeated that notice.")); return false; } @@ -104,15 +104,15 @@ class ForwardAction extends Action function handle($args) { - $forward = Forward::saveNew($this->user->id, $this->notice->id); + $repeat = $this->notice->repeat($this->user->id, 'web'); if ($this->boolean('ajax')) { $this->startHTML('text/xml;charset=utf-8'); $this->elementStart('head'); - $this->element('title', null, _('Forwarded')); + $this->element('title', null, _('Repeated')); $this->elementEnd('head'); $this->elementStart('body'); - $this->element('p', array('id' => 'forward_response'), _('Forwarded!')); + $this->element('p', array('id' => 'repeat_response'), _('Repeated!')); $this->elementEnd('body'); $this->elementEnd('html'); } else { diff --git a/classes/Notice.php b/classes/Notice.php index 228201188..82753fbdd 100644 --- a/classes/Notice.php +++ b/classes/Notice.php @@ -236,7 +236,14 @@ class Notice extends Memcached_DataObject $notice->source = $source; $notice->uri = $uri; - $notice->reply_to = self::getReplyTo($reply_to, $profile_id, $source, $final); + // Handle repeat case + + if (isset($repeat_of)) { + $notice->repeat_of = $repeat_of; + $notice->reply_to = $repeat_of; + } else { + $notice->reply_to = self::getReplyTo($reply_to, $profile_id, $source, $final); + } if (!empty($notice->reply_to)) { $reply = Notice::staticGet('id', $notice->reply_to); @@ -1434,4 +1441,18 @@ class Notice extends Memcached_DataObject return $location; } + + function repeat($repeater_id, $source) + { + $author = Profile::staticGet('id', $this->profile_id); + + // FIXME: truncate on long repeats...? + + $content = sprintf(_('RT @%1$s %2$s'), + $author->nickname, + $this->content); + + return self::saveNew($repeater_id, $content, $source, + array('repeat_of' => $this->id)); + } } diff --git a/lib/router.php b/lib/router.php index 37525319f..142206c16 100644 --- a/lib/router.php +++ b/lib/router.php @@ -99,6 +99,7 @@ class Router 'groupblock', 'groupunblock', 'sandbox', 'unsandbox', 'silence', 'unsilence', + 'repeat', 'deleteuser'); foreach ($main as $a) { -- cgit v1.2.3-54-g00ecf From c622d144400026dac65c39303994d11f114c0f5b Mon Sep 17 00:00:00 2001 From: Evan Prodromou Date: Sat, 12 Dec 2009 14:46:24 -0500 Subject: add statuses/retweets to API --- actions/apistatusesretweets.php | 116 ++++++++++++++++++++++++++++++++++++++++ classes/Notice.php | 67 +++++++++++++++++++++++ lib/router.php | 5 ++ 3 files changed, 188 insertions(+) create mode 100644 actions/apistatusesretweets.php (limited to 'classes') diff --git a/actions/apistatusesretweets.php b/actions/apistatusesretweets.php new file mode 100644 index 000000000..c54a374e2 --- /dev/null +++ b/actions/apistatusesretweets.php @@ -0,0 +1,116 @@ +. + * + * @category API + * @package StatusNet + * @author Evan Prodromou + * @copyright 2009 StatusNet, Inc. + * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0 + * @link http://status.net/ + */ + +if (!defined('STATUSNET')) { + exit(1); +} + +require_once INSTALLDIR . '/lib/apiauth.php'; +require_once INSTALLDIR . '/lib/mediafile.php'; + +/** + * Show up to 100 repeats of a notice + * + * @category API + * @package StatusNet + * @author Evan Prodromou + * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0 + * @link http://status.net/ + */ + +class ApiStatusesRetweetsAction extends ApiAuthAction +{ + const MAXCOUNT = 100; + + var $original = null; + var $cnt = self::MAXCOUNT; + + /** + * Take arguments for running + * + * @param array $args $_REQUEST args + * + * @return boolean success flag + * + */ + + function prepare($args) + { + parent::prepare($args); + + $id = $this->trimmed('id'); + + $this->original = Notice::staticGet('id', $id); + + if (empty($this->original)) { + $this->clientError(_('No such notice'), + 400, $this->format); + return false; + } + + $cnt = $this->trimmed('count'); + + if (empty($cnt) || !is_integer($cnt)) { + $cnt = 100; + } else { + $this->cnt = min((int)$cnt, self::MAXCOUNT); + } + + return true; + } + + /** + * Handle the request + * + * Make a new notice for the update, save it, and show it + * + * @param array $args $_REQUEST data (unused) + * + * @return void + */ + + function handle($args) + { + parent::handle($args); + + $strm = $this->original->repeatStream($this->cnt); + + switch ($this->format) { + case 'xml': + $this->showXmlTimeline($strm); + break; + case 'json': + $this->showJsonTimeline($strm); + break; + default: + $this->clientError(_('API method not found!'), $code = 404); + break; + } + } +} diff --git a/classes/Notice.php b/classes/Notice.php index 82753fbdd..ec80f763f 100644 --- a/classes/Notice.php +++ b/classes/Notice.php @@ -441,10 +441,23 @@ class Notice extends Memcached_DataObject $this->blowTagCache($blowLast); $this->blowGroupCache($blowLast); $this->blowConversationCache($blowLast); + $this->blowRepeatCache(); $profile = Profile::staticGet($this->profile_id); $profile->blowNoticeCount(); } + function blowRepeatCache() + { + if (!empty($this->repeat_of)) { + $cache = common_memcache(); + if (!empty($cache)) { + // XXX: only blow if <100 in cache + $ck = common_cache_key('notice:repeats:'.$this->repeat_of); + $result = $cache->delete($ck); + } + } + } + function blowConversationCache($blowLast=false) { $cache = common_memcache(); @@ -1455,4 +1468,58 @@ class Notice extends Memcached_DataObject return self::saveNew($repeater_id, $content, $source, array('repeat_of' => $this->id)); } + + // These are supposed to be in chron order! + + function repeatStream($limit=100) + { + $cache = common_memcache(); + + if (empty($cache)) { + $ids = $this->_repeatStreamDirect($limit); + } else { + $idstr = $cache->get(common_cache_key('notice:repeats:'.$this->id)); + if (!empty($idstr)) { + $ids = explode(',', $idstr); + } else { + $ids = $this->_repeatStreamDirect(100); + $cache->set(common_cache_key('notice:repeats:'.$this->id), implode(',', $ids)); + } + if ($limit < 100) { + // We do a max of 100, so slice down to limit + $ids = array_slice($ids, 0, $limit); + } + } + + return Notice::getStreamByIds($ids); + } + + function _repeatStreamDirect($limit) + { + $notice = new Notice(); + + $notice->selectAdd(); // clears it + $notice->selectAdd('id'); + + $notice->repeat_of = $this->id; + + $notice->orderBy('created'); // NB: asc! + + if (!is_null($offset)) { + $notice->limit($offset, $limit); + } + + $ids = array(); + + if ($notice->find()) { + while ($notice->fetch()) { + $ids[] = $notice->id; + } + } + + $notice->free(); + $notice = NULL; + + return $ids; + } } diff --git a/lib/router.php b/lib/router.php index 835339720..b0b95b080 100644 --- a/lib/router.php +++ b/lib/router.php @@ -364,6 +364,11 @@ class Router 'id' => '[0-9]+', 'format' => '(xml|json)')); + $m->connect('api/statuses/retweets/:id.:format', + array('action' => 'ApiStatusesRetweets', + 'id' => '[0-9]+', + 'format' => '(xml|json)')); + // users $m->connect('api/users/show.:format', -- cgit v1.2.3-54-g00ecf From 138ce0cd05e2e59c79b29f5eeea5c11d1e56e931 Mon Sep 17 00:00:00 2001 From: Evan Prodromou Date: Sat, 12 Dec 2009 15:35:05 -0500 Subject: add statuses/retweeted_by_me api action --- actions/apitimelineretweetedbyme.php | 126 +++++++++++++++++++++++++++++++++++ classes/Notice.php | 9 +++ classes/User.php | 52 +++++++++++++++ lib/router.php | 4 ++ 4 files changed, 191 insertions(+) create mode 100644 actions/apitimelineretweetedbyme.php (limited to 'classes') diff --git a/actions/apitimelineretweetedbyme.php b/actions/apitimelineretweetedbyme.php new file mode 100644 index 000000000..1e65678ad --- /dev/null +++ b/actions/apitimelineretweetedbyme.php @@ -0,0 +1,126 @@ +. + * + * @category API + * @package StatusNet + * @author Evan Prodromou + * @copyright 2009 StatusNet, Inc. + * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0 + * @link http://status.net/ + */ + +if (!defined('STATUSNET')) { + exit(1); +} + +require_once INSTALLDIR . '/lib/apiauth.php'; +require_once INSTALLDIR . '/lib/mediafile.php'; + +/** + * Show authenticating user's most recent repeats + * + * @category API + * @package StatusNet + * @author Evan Prodromou + * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0 + * @link http://status.net/ + */ + +class ApiTimelineRetweetedByMeAction extends ApiAuthAction +{ + const DEFAULTCOUNT = 20; + const MAXCOUNT = 200; + const MAXNOTICES = 3200; + + var $repeats = null; + var $cnt = self::DEFAULTCOUNT; + var $page = 1; + var $since_id = null; + var $max_id = null; + + /** + * Take arguments for running + * + * @param array $args $_REQUEST args + * + * @return boolean success flag + * + */ + + function prepare($args) + { + parent::prepare($args); + + $cnt = $this->int('count', self::DEFAULTCOUNT, self::MAXCOUNT, 1); + + $page = $this->int('page', 1, (self::MAXNOTICES/$this->cnt)); + + $since_id = $this->int('since_id'); + + $max_id = $this->int('max_id'); + + return true; + } + + /** + * Handle the request + * + * show a timeline of the user's repeated notices + * + * @param array $args $_REQUEST data (unused) + * + * @return void + */ + + function handle($args) + { + parent::handle($args); + + $offset = ($this->page-1) * $this->cnt; + $limit = $this->cnt; + + $strm = $this->auth_user->repeatedByMe($offset, $limit, $this->since_id, $this->max_id); + + switch ($this->format) { + case 'xml': + $this->showXmlTimeline($strm); + break; + case 'json': + $this->showJsonTimeline($strm); + break; + case 'atom': + $profile = $this->auth_user->getProfile(); + + $title = sprintf(_("Repeated by %s"), $this->auth_user->nickname); + $taguribase = common_config('integration', 'taguri'); + $id = "tag:$taguribase:RepeatedByMe:" . $this->auth_user->id; + $link = common_local_url('showstream', + array('nickname' => $this->auth_user->nickname)); + + $this->showAtomTimeline($strm, $title, $id, $link); + break; + + default: + $this->clientError(_('API method not found!'), $code = 404); + break; + } + } +} diff --git a/classes/Notice.php b/classes/Notice.php index ec80f763f..6a701ae0c 100644 --- a/classes/Notice.php +++ b/classes/Notice.php @@ -454,6 +454,15 @@ class Notice extends Memcached_DataObject // XXX: only blow if <100 in cache $ck = common_cache_key('notice:repeats:'.$this->repeat_of); $result = $cache->delete($ck); + + $user = User::staticGet('id', $this->profile_id); + + if (!empty($user)) { + $uk = common_cache_key('user:repeated_by_me:'.$user->id); + $cache->delete($uk); + $user->free(); + unset($user); + } } } } diff --git a/classes/User.php b/classes/User.php index 2a4fab7d4..4b7365fc7 100644 --- a/classes/User.php +++ b/classes/User.php @@ -741,4 +741,56 @@ class User extends Memcached_DataObject $profile = $this->getProfile(); return $profile->isSilenced(); } + + function repeatedByMe($offset=0, $limit=20, $since_id=null, $max_id=null) + { + $ids = Notice::stream(array($this, '_repeatedByMeDirect'), + array(), + 'user:repeated_by_me:'.$this->id, + $offset, $limit, $since_id, $max_id, null); + + return Notice::getStreamByIds($ids); + } + + function _repeatedByMeDirect($offset, $limit, $since_id, $max_id, $since) + { + $notice = new Notice(); + + $notice->selectAdd(); // clears it + $notice->selectAdd('id'); + + $notice->profile_id = $this->id; + $notice->whereAdd('repeat_of IS NOT NULL'); + + $notice->orderBy('id DESC'); + + if (!is_null($offset)) { + $notice->limit($offset, $limit); + } + + if ($since_id != 0) { + $notice->whereAdd('id > ' . $since_id); + } + + if ($max_id != 0) { + $notice->whereAdd('id <= ' . $max_id); + } + + if (!is_null($since)) { + $notice->whereAdd('created > \'' . date('Y-m-d H:i:s', $since) . '\''); + } + + $ids = array(); + + if ($notice->find()) { + while ($notice->fetch()) { + $ids[] = $notice->id; + } + } + + $notice->free(); + $notice = NULL; + + return $ids; + } } diff --git a/lib/router.php b/lib/router.php index b0b95b080..5c1bd3c4f 100644 --- a/lib/router.php +++ b/lib/router.php @@ -319,6 +319,10 @@ class Router 'id' => '[a-zA-Z0-9]+', 'format' => '(xml|json|rss|atom)')); + $m->connect('api/statuses/retweeted_by_me.:format', + array('action' => 'ApiTimelineRetweetedByMe', + 'format' => '(xml|json|atom)')); + $m->connect('api/statuses/friends.:format', array('action' => 'ApiUserFriends', 'format' => '(xml|json)')); -- cgit v1.2.3-54-g00ecf From cfe67a9c0192129448ea3f0b98aadf49e962fd4d Mon Sep 17 00:00:00 2001 From: Evan Prodromou Date: Sat, 12 Dec 2009 16:00:27 -0500 Subject: add statuses/retweets_of_me to API --- actions/apitimelineretweetsofme.php | 126 ++++++++++++++++++++++++++++++++++++ classes/Notice.php | 14 ++++ classes/User.php | 53 +++++++++++++++ lib/router.php | 4 ++ 4 files changed, 197 insertions(+) create mode 100644 actions/apitimelineretweetsofme.php (limited to 'classes') diff --git a/actions/apitimelineretweetsofme.php b/actions/apitimelineretweetsofme.php new file mode 100644 index 000000000..479bff431 --- /dev/null +++ b/actions/apitimelineretweetsofme.php @@ -0,0 +1,126 @@ +. + * + * @category API + * @package StatusNet + * @author Evan Prodromou + * @copyright 2009 StatusNet, Inc. + * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0 + * @link http://status.net/ + */ + +if (!defined('STATUSNET')) { + exit(1); +} + +require_once INSTALLDIR . '/lib/apiauth.php'; +require_once INSTALLDIR . '/lib/mediafile.php'; + +/** + * Show authenticating user's most recent notices that have been repeated + * + * @category API + * @package StatusNet + * @author Evan Prodromou + * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0 + * @link http://status.net/ + */ + +class ApiTimelineRetweetsOfMeAction extends ApiAuthAction +{ + const DEFAULTCOUNT = 20; + const MAXCOUNT = 200; + const MAXNOTICES = 3200; + + var $repeats = null; + var $cnt = self::DEFAULTCOUNT; + var $page = 1; + var $since_id = null; + var $max_id = null; + + /** + * Take arguments for running + * + * @param array $args $_REQUEST args + * + * @return boolean success flag + * + */ + + function prepare($args) + { + parent::prepare($args); + + $cnt = $this->int('count', self::DEFAULTCOUNT, self::MAXCOUNT, 1); + + $page = $this->int('page', 1, (self::MAXNOTICES/$this->cnt)); + + $since_id = $this->int('since_id'); + + $max_id = $this->int('max_id'); + + return true; + } + + /** + * Handle the request + * + * show a timeline of the user's repeated notices + * + * @param array $args $_REQUEST data (unused) + * + * @return void + */ + + function handle($args) + { + parent::handle($args); + + $offset = ($this->page-1) * $this->cnt; + $limit = $this->cnt; + + $strm = $this->auth_user->repeatsOfMe($offset, $limit, $this->since_id, $this->max_id); + + switch ($this->format) { + case 'xml': + $this->showXmlTimeline($strm); + break; + case 'json': + $this->showJsonTimeline($strm); + break; + case 'atom': + $profile = $this->auth_user->getProfile(); + + $title = sprintf(_("Repeats of %s"), $this->auth_user->nickname); + $taguribase = common_config('integration', 'taguri'); + $id = "tag:$taguribase:RepeatsOfMe:" . $this->auth_user->id; + $link = common_local_url('showstream', + array('nickname' => $this->auth_user->nickname)); + + $this->showAtomTimeline($strm, $title, $id, $link); + break; + + default: + $this->clientError(_('API method not found!'), $code = 404); + break; + } + } +} diff --git a/classes/Notice.php b/classes/Notice.php index 6a701ae0c..eb611f314 100644 --- a/classes/Notice.php +++ b/classes/Notice.php @@ -463,6 +463,20 @@ class Notice extends Memcached_DataObject $user->free(); unset($user); } + + $original = Notice::staticGet('id', $this->repeat_of); + + if (!empty($original)) { + $originalUser = User::staticGet('id', $original->profile_id); + if (!empty($originalUser)) { + $ouk = common_cache_key('user:repeats_of_me:'.$originalUser->id); + $cache->delete($ouk); + $originalUser->free(); + unset($originalUser); + } + $original->free(); + unset($original); + } } } } diff --git a/classes/User.php b/classes/User.php index 4b7365fc7..b3da448a6 100644 --- a/classes/User.php +++ b/classes/User.php @@ -793,4 +793,57 @@ class User extends Memcached_DataObject return $ids; } + + function repeatsOfMe($offset=0, $limit=20, $since_id=null, $max_id=null) + { + $ids = Notice::stream(array($this, '_repeatsOfMeDirect'), + array(), + 'user:repeats_of_me:'.$this->id, + $offset, $limit, $since_id, $max_id, null); + + return Notice::getStreamByIds($ids); + } + + function _repeatsOfMeDirect($offset, $limit, $since_id, $max_id, $since) + { + $qry = + 'SELECT DISTINCT original.id AS id ' . + 'FROM notice original JOIN notice rept ON original.id = rept.repeat_of ' . + 'WHERE original.profile_id = ' . $this->id . ' '; + + if ($since_id != 0) { + $qry .= 'AND original.id > ' . $since_id . ' '; + } + + if ($max_id != 0) { + $qry .= 'AND original.id <= ' . $max_id . ' '; + } + + if (!is_null($since)) { + $qry .= 'AND original.modified > \'' . date('Y-m-d H:i:s', $since) . '\' '; + } + + // NOTE: we sort by fave time, not by notice time! + + $qry .= 'ORDER BY original.id DESC '; + + if (!is_null($offset)) { + $qry .= "LIMIT $limit OFFSET $offset"; + } + + $ids = array(); + + $notice = new Notice(); + + $notice->query($qry); + + while ($notice->fetch()) { + $ids[] = $notice->id; + } + + $notice->free(); + $notice = NULL; + + return $ids; + } } diff --git a/lib/router.php b/lib/router.php index 5c1bd3c4f..732caa1c7 100644 --- a/lib/router.php +++ b/lib/router.php @@ -323,6 +323,10 @@ class Router array('action' => 'ApiTimelineRetweetedByMe', 'format' => '(xml|json|atom)')); + $m->connect('api/statuses/retweets_of_me.:format', + array('action' => 'ApiTimelineRetweetsOfMe', + 'format' => '(xml|json|atom)')); + $m->connect('api/statuses/friends.:format', array('action' => 'ApiUserFriends', 'format' => '(xml|json)')); -- cgit v1.2.3-54-g00ecf From 698b28c95c9d4fa5cb404395b2c90b10a438fcd5 Mon Sep 17 00:00:00 2001 From: Evan Prodromou Date: Sat, 12 Dec 2009 16:02:44 -0500 Subject: clear repeat_of flag when a notice is deleted --- classes/Notice.php | 6 ++++++ 1 file changed, 6 insertions(+) (limited to 'classes') diff --git a/classes/Notice.php b/classes/Notice.php index eb611f314..a7b0f8cdb 100644 --- a/classes/Notice.php +++ b/classes/Notice.php @@ -115,6 +115,12 @@ class Notice extends Memcached_DataObject //Null any notices that are replies to this notice $this->query(sprintf("UPDATE notice set reply_to = null WHERE reply_to = %d", $this->id)); + + //Null any notices that are repeats of this notice + //XXX: probably need to uncache these, too + + $this->query(sprintf("UPDATE notice set repeat_of = null WHERE repeat_of = %d", $this->id)); + $related = array('Reply', 'Fave', 'Notice_tag', -- cgit v1.2.3-54-g00ecf From 1ec54d3433a79f56c14c0a99114bb1e607348899 Mon Sep 17 00:00:00 2001 From: Evan Prodromou Date: Sat, 12 Dec 2009 16:15:23 -0500 Subject: add statuses/retweeted_to_me to API --- actions/apitimelineretweetedtome.php | 125 +++++++++++++++++++++++++++++++++++ classes/Notice.php | 14 ++++ classes/User.php | 54 +++++++++++++++ lib/router.php | 4 ++ 4 files changed, 197 insertions(+) create mode 100644 actions/apitimelineretweetedtome.php (limited to 'classes') diff --git a/actions/apitimelineretweetedtome.php b/actions/apitimelineretweetedtome.php new file mode 100644 index 000000000..681b0b9e9 --- /dev/null +++ b/actions/apitimelineretweetedtome.php @@ -0,0 +1,125 @@ +. + * + * @category API + * @package StatusNet + * @author Evan Prodromou + * @copyright 2009 StatusNet, Inc. + * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0 + * @link http://status.net/ + */ + +if (!defined('STATUSNET')) { + exit(1); +} + +require_once INSTALLDIR . '/lib/apiauth.php'; + +/** + * Show most recent notices that are repeats in user's inbox + * + * @category API + * @package StatusNet + * @author Evan Prodromou + * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0 + * @link http://status.net/ + */ + +class ApiTimelineRetweetedToMeAction extends ApiAuthAction +{ + const DEFAULTCOUNT = 20; + const MAXCOUNT = 200; + const MAXNOTICES = 3200; + + var $repeats = null; + var $cnt = self::DEFAULTCOUNT; + var $page = 1; + var $since_id = null; + var $max_id = null; + + /** + * Take arguments for running + * + * @param array $args $_REQUEST args + * + * @return boolean success flag + * + */ + + function prepare($args) + { + parent::prepare($args); + + $cnt = $this->int('count', self::DEFAULTCOUNT, self::MAXCOUNT, 1); + + $page = $this->int('page', 1, (self::MAXNOTICES/$this->cnt)); + + $since_id = $this->int('since_id'); + + $max_id = $this->int('max_id'); + + return true; + } + + /** + * Handle the request + * + * show a timeline of the user's repeated notices + * + * @param array $args $_REQUEST data (unused) + * + * @return void + */ + + function handle($args) + { + parent::handle($args); + + $offset = ($this->page-1) * $this->cnt; + $limit = $this->cnt; + + $strm = $this->auth_user->repeatedToMe($offset, $limit, $this->since_id, $this->max_id); + + switch ($this->format) { + case 'xml': + $this->showXmlTimeline($strm); + break; + case 'json': + $this->showJsonTimeline($strm); + break; + case 'atom': + $profile = $this->auth_user->getProfile(); + + $title = sprintf(_("Repeated to %s"), $this->auth_user->nickname); + $taguribase = common_config('integration', 'taguri'); + $id = "tag:$taguribase:RepeatedToMe:" . $this->auth_user->id; + $link = common_local_url('all', + array('nickname' => $this->auth_user->nickname)); + + $this->showAtomTimeline($strm, $title, $id, $link); + break; + + default: + $this->clientError(_('API method not found!'), $code = 404); + break; + } + } +} diff --git a/classes/Notice.php b/classes/Notice.php index a7b0f8cdb..7d2b898d2 100644 --- a/classes/Notice.php +++ b/classes/Notice.php @@ -483,6 +483,20 @@ class Notice extends Memcached_DataObject $original->free(); unset($original); } + + $ni = new Notice_inbox(); + + $ni->notice_id = $this->id; + + if ($ni->find()) { + while ($ni->fetch()) { + $tmk = common_cache_key('user:repeated_to_me:'.$ni->user_id); + $cache->delete($tmk); + } + } + + $ni->free(); + unset($ni); } } } diff --git a/classes/User.php b/classes/User.php index b3da448a6..9c071e06b 100644 --- a/classes/User.php +++ b/classes/User.php @@ -846,4 +846,58 @@ class User extends Memcached_DataObject return $ids; } + + function repeatedToMe($offset=0, $limit=20, $since_id=null, $max_id=null) + { + $ids = Notice::stream(array($this, '_repeatedToMeDirect'), + array(), + 'user:repeated_to_me:'.$this->id, + $offset, $limit, $since_id, $max_id, null); + + return Notice::getStreamByIds($ids); + } + + function _repeatedToMeDirect($offset, $limit, $since_id, $max_id, $since) + { + $qry = + 'SELECT notice.id AS id ' . + 'FROM notice JOIN notice_inbox ON notice.id = notice_inbox.notice_id ' . + 'WHERE notice_inbox.user_id = ' . $this->id . ' ' . + 'AND notice.repeat_of IS NOT NULL '; + + if ($since_id != 0) { + $qry .= 'AND notice.id > ' . $since_id . ' '; + } + + if ($max_id != 0) { + $qry .= 'AND notice.id <= ' . $max_id . ' '; + } + + if (!is_null($since)) { + $qry .= 'AND notice.modified > \'' . date('Y-m-d H:i:s', $since) . '\' '; + } + + // NOTE: we sort by fave time, not by notice time! + + $qry .= 'ORDER BY notice.id DESC '; + + if (!is_null($offset)) { + $qry .= "LIMIT $limit OFFSET $offset"; + } + + $ids = array(); + + $notice = new Notice(); + + $notice->query($qry); + + while ($notice->fetch()) { + $ids[] = $notice->id; + } + + $notice->free(); + $notice = NULL; + + return $ids; + } } diff --git a/lib/router.php b/lib/router.php index 732caa1c7..8f68f86ac 100644 --- a/lib/router.php +++ b/lib/router.php @@ -323,6 +323,10 @@ class Router array('action' => 'ApiTimelineRetweetedByMe', 'format' => '(xml|json|atom)')); + $m->connect('api/statuses/retweeted_to_me.:format', + array('action' => 'ApiTimelineRetweetedToMe', + 'format' => '(xml|json|atom)')); + $m->connect('api/statuses/retweets_of_me.:format', array('action' => 'ApiTimelineRetweetsOfMe', 'format' => '(xml|json|atom)')); -- cgit v1.2.3-54-g00ecf From 438a0d7f1c23d176f380d6f5dba181ec54722b83 Mon Sep 17 00:00:00 2001 From: Evan Prodromou Date: Sat, 12 Dec 2009 16:58:39 -0500 Subject: remove obsoleted getStream, getStreamDirect, getCachedStream from Notice; use stream() instead --- classes/Notice.php | 187 ----------------------------------------------------- 1 file changed, 187 deletions(-) (limited to 'classes') diff --git a/classes/Notice.php b/classes/Notice.php index 7d2b898d2..66d9cf5d4 100644 --- a/classes/Notice.php +++ b/classes/Notice.php @@ -655,193 +655,6 @@ class Notice extends Memcached_DataObject } } - # XXX: too many args; we need to move to named params or even a separate - # class for notice streams - - static function getStream($qry, $cachekey, $offset=0, $limit=20, $since_id=0, $max_id=0, $order=null, $since=null) { - - if (common_config('memcached', 'enabled')) { - - # Skip the cache if this is a since, since_id or max_id qry - if ($since_id > 0 || $max_id > 0 || $since) { - return Notice::getStreamDirect($qry, $offset, $limit, $since_id, $max_id, $order, $since); - } else { - return Notice::getCachedStream($qry, $cachekey, $offset, $limit, $order); - } - } - - return Notice::getStreamDirect($qry, $offset, $limit, $since_id, $max_id, $order, $since); - } - - static function getStreamDirect($qry, $offset, $limit, $since_id, $max_id, $order, $since) { - - $needAnd = false; - $needWhere = true; - - if (preg_match('/\bWHERE\b/i', $qry)) { - $needWhere = false; - $needAnd = true; - } - - if ($since_id > 0) { - - if ($needWhere) { - $qry .= ' WHERE '; - $needWhere = false; - } else { - $qry .= ' AND '; - } - - $qry .= ' notice.id > ' . $since_id; - } - - if ($max_id > 0) { - - if ($needWhere) { - $qry .= ' WHERE '; - $needWhere = false; - } else { - $qry .= ' AND '; - } - - $qry .= ' notice.id <= ' . $max_id; - } - - if ($since) { - - if ($needWhere) { - $qry .= ' WHERE '; - $needWhere = false; - } else { - $qry .= ' AND '; - } - - $qry .= ' notice.created > \'' . date('Y-m-d H:i:s', $since) . '\''; - } - - # Allow ORDER override - - if ($order) { - $qry .= $order; - } else { - $qry .= ' ORDER BY notice.created DESC, notice.id DESC '; - } - - if (common_config('db','type') == 'pgsql') { - $qry .= ' LIMIT ' . $limit . ' OFFSET ' . $offset; - } else { - $qry .= ' LIMIT ' . $offset . ', ' . $limit; - } - - $notice = new Notice(); - - $notice->query($qry); - - return $notice; - } - - # XXX: this is pretty long and should probably be broken up into - # some helper functions - - static function getCachedStream($qry, $cachekey, $offset, $limit, $order) { - - # If outside our cache window, just go to the DB - - if ($offset + $limit > NOTICE_CACHE_WINDOW) { - return Notice::getStreamDirect($qry, $offset, $limit, null, null, $order, null); - } - - # Get the cache; if we can't, just go to the DB - - $cache = common_memcache(); - - if (empty($cache)) { - return Notice::getStreamDirect($qry, $offset, $limit, null, null, $order, null); - } - - # Get the notices out of the cache - - $notices = $cache->get(common_cache_key($cachekey)); - - # On a cache hit, return a DB-object-like wrapper - - if ($notices !== false) { - $wrapper = new ArrayWrapper(array_slice($notices, $offset, $limit)); - return $wrapper; - } - - # If the cache was invalidated because of new data being - # added, we can try and just get the new stuff. We keep an additional - # copy of the data at the key + ';last' - - # No cache hit. Try to get the *last* cached version - - $last_notices = $cache->get(common_cache_key($cachekey) . ';last'); - - if ($last_notices) { - - # Reverse-chron order, so last ID is last. - - $last_id = $last_notices[0]->id; - - # XXX: this assumes monotonically increasing IDs; a fair - # bet with our DB. - - $new_notice = Notice::getStreamDirect($qry, 0, NOTICE_CACHE_WINDOW, - $last_id, null, $order, null); - - if ($new_notice) { - $new_notices = array(); - while ($new_notice->fetch()) { - $new_notices[] = clone($new_notice); - } - $new_notice->free(); - $notices = array_slice(array_merge($new_notices, $last_notices), - 0, NOTICE_CACHE_WINDOW); - - # Store the array in the cache for next time - - $result = $cache->set(common_cache_key($cachekey), $notices); - $result = $cache->set(common_cache_key($cachekey) . ';last', $notices); - - # return a wrapper of the array for use now - - return new ArrayWrapper(array_slice($notices, $offset, $limit)); - } - } - - # Otherwise, get the full cache window out of the DB - - $notice = Notice::getStreamDirect($qry, 0, NOTICE_CACHE_WINDOW, null, null, $order, null); - - # If there are no hits, just return the value - - if (empty($notice)) { - return $notice; - } - - # Pack results into an array - - $notices = array(); - - while ($notice->fetch()) { - $notices[] = clone($notice); - } - - $notice->free(); - - # Store the array in the cache for next time - - $result = $cache->set(common_cache_key($cachekey), $notices); - $result = $cache->set(common_cache_key($cachekey) . ';last', $notices); - - # return a wrapper of the array for use now - - $wrapper = new ArrayWrapper(array_slice($notices, $offset, $limit)); - - return $wrapper; - } - function getStreamByIds($ids) { $cache = common_memcache(); -- cgit v1.2.3-54-g00ecf From 2a8eee0e0b33ce15289484270d3211f502940920 Mon Sep 17 00:00:00 2001 From: Evan Prodromou Date: Mon, 14 Dec 2009 16:41:25 -0500 Subject: add friends_timeline with no repeats in it --- actions/apitimelinefriends.php | 245 +++++++++++++++++++++++++++++++++++++++++ classes/Notice.php | 18 +++ classes/User.php | 71 ++++++++++++ 3 files changed, 334 insertions(+) create mode 100644 actions/apitimelinefriends.php (limited to 'classes') diff --git a/actions/apitimelinefriends.php b/actions/apitimelinefriends.php new file mode 100644 index 000000000..9ec7447e6 --- /dev/null +++ b/actions/apitimelinefriends.php @@ -0,0 +1,245 @@ +. + * + * @category API + * @package StatusNet + * @author Craig Andrews + * @author Evan Prodromou + * @author Jeffery To + * @author mac65 + * @author Mike Cochrane + * @author Robin Millette + * @author Zach Copley + * @copyright 2009 StatusNet, Inc. + * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0 + * @link http://status.net/ + */ + +if (!defined('STATUSNET')) { + exit(1); +} + +require_once INSTALLDIR . '/lib/apibareauth.php'; + +/** + * Returns the most recent notices (default 20) posted by the target user. + * This is the equivalent of 'You and friends' page accessed via Web. + * + * @category API + * @package StatusNet + * @author Craig Andrews + * @author Evan Prodromou + * @author Jeffery To + * @author mac65 + * @author Mike Cochrane + * @author Robin Millette + * @author Zach Copley + * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0 + * @link http://status.net/ + */ + +class ApiTimelineFriendsAction extends ApiBareAuthAction +{ + var $notices = null; + + /** + * Take arguments for running + * + * @param array $args $_REQUEST args + * + * @return boolean success flag + * + */ + + function prepare($args) + { + parent::prepare($args); + common_debug("api friends_timeline"); + $this->user = $this->getTargetUser($this->arg('id')); + + if (empty($this->user)) { + $this->clientError(_('No such user.'), 404, $this->format); + return; + } + + $this->notices = $this->getNotices(); + + return true; + } + + /** + * Handle the request + * + * Just show the notices + * + * @param array $args $_REQUEST data (unused) + * + * @return void + */ + + function handle($args) + { + parent::handle($args); + $this->showTimeline(); + } + + /** + * Show the timeline of notices + * + * @return void + */ + + function showTimeline() + { + $profile = $this->user->getProfile(); + $avatar = $profile->getAvatar(AVATAR_PROFILE_SIZE); + $sitename = common_config('site', 'name'); + $title = sprintf(_("%s and friends"), $this->user->nickname); + $taguribase = common_config('integration', 'taguri'); + $id = "tag:$taguribase:FriendsTimeline:" . $this->user->id; + $link = common_local_url( + 'all', array('nickname' => $this->user->nickname) + ); + $subtitle = sprintf( + _('Updates from %1$s and friends on %2$s!'), + $this->user->nickname, $sitename + ); + $logo = ($avatar) ? $avatar->displayUrl() : Avatar::defaultImage(AVATAR_PROFILE_SIZE); + + switch($this->format) { + case 'xml': + $this->showXmlTimeline($this->notices); + break; + case 'rss': + $this->showRssTimeline($this->notices, $title, $link, $subtitle, null, $logo); + break; + case 'atom': + + $target_id = $this->arg('id'); + + if (isset($target_id)) { + $selfuri = common_root_url() . + 'api/statuses/friends_timeline/' . + $target_id . '.atom'; + } else { + $selfuri = common_root_url() . + 'api/statuses/friends_timeline.atom'; + } + + $this->showAtomTimeline( + $this->notices, $title, $id, $link, + $subtitle, null, $selfuri, $logo + ); + break; + case 'json': + $this->showJsonTimeline($this->notices); + break; + default: + $this->clientError(_('API method not found!'), $code = 404); + break; + } + } + + /** + * Get notices + * + * @return array notices + */ + + function getNotices() + { + $notices = array(); + + if (!empty($this->auth_user) && $this->auth_user->id == $this->user->id) { + $notice = $this->user->ownFriendsTimeline(($this->page-1) * $this->count, + $this->count, $this->since_id, + $this->max_id, $this->since); + } else { + $notice = $this->user->friendsTimeline(($this->page-1) * $this->count, + $this->count, $this->since_id, + $this->max_id, $this->since); + } + + while ($notice->fetch()) { + $notices[] = clone($notice); + } + + return $notices; + } + + /** + * Is this action read only? + * + * @param array $args other arguments + * + * @return boolean true + */ + + function isReadOnly($args) + { + return true; + } + + /** + * When was this feed last modified? + * + * @return string datestamp of the latest notice in the stream + */ + + function lastModified() + { + if (!empty($this->notices) && (count($this->notices) > 0)) { + return strtotime($this->notices[0]->created); + } + + return null; + } + + /** + * An entity tag for this stream + * + * Returns an Etag based on the action name, language, user ID, and + * timestamps of the first and last notice in the timeline + * + * @return string etag + */ + + function etag() + { + if (!empty($this->notices) && (count($this->notices) > 0)) { + + $last = count($this->notices) - 1; + + return '"' . implode( + ':', + array($this->arg('action'), + common_language(), + $this->user->id, + strtotime($this->notices[0]->created), + strtotime($this->notices[$last]->created)) + ) + . '"'; + } + + return null; + } + +} diff --git a/classes/Notice.php b/classes/Notice.php index 66d9cf5d4..4aec4ed55 100644 --- a/classes/Notice.php +++ b/classes/Notice.php @@ -530,8 +530,18 @@ class Notice extends Memcached_DataObject if ($member->find()) { while ($member->fetch()) { $cache->delete(common_cache_key('notice_inbox:by_user:' . $member->profile_id)); + $cache->delete(common_cache_key('notice_inbox:by_user_own:' . $member->profile_id)); + if (empty($this->repeat_of)) { + $cache->delete(common_cache_key('user:friends_timeline:' . $member->profile_id)); + $cache->delete(common_cache_key('user:friends_timeline_own:' . $member->profile_id)); + } if ($blowLast) { $cache->delete(common_cache_key('notice_inbox:by_user:' . $member->profile_id . ';last')); + $cache->delete(common_cache_key('notice_inbox:by_user_own:' . $member->profile_id . ';last')); + if (empty($this->repeat_of)) { + $cache->delete(common_cache_key('user:friends_timeline:' . $member->profile_id . ';last')); + $cache->delete(common_cache_key('user:friends_timeline_own:' . $member->profile_id . ';last')); + } } } } @@ -579,9 +589,17 @@ class Notice extends Memcached_DataObject while ($user->fetch()) { $cache->delete(common_cache_key('notice_inbox:by_user:'.$user->id)); $cache->delete(common_cache_key('notice_inbox:by_user_own:'.$user->id)); + if (empty($this->repeat_of)) { + $cache->delete(common_cache_key('user:friends_timeline:'.$user->id)); + $cache->delete(common_cache_key('user:friends_timeline_own:'.$user->id)); + } if ($blowLast) { $cache->delete(common_cache_key('notice_inbox:by_user:'.$user->id.';last')); $cache->delete(common_cache_key('notice_inbox:by_user_own:'.$user->id.';last')); + if (empty($this->repeat_of)) { + $cache->delete(common_cache_key('user:friends_timeline:'.$user->id.';last')); + $cache->delete(common_cache_key('user:friends_timeline_own:'.$user->id.';last')); + } } } $user->free(); diff --git a/classes/User.php b/classes/User.php index 9c071e06b..d04f7d679 100644 --- a/classes/User.php +++ b/classes/User.php @@ -473,6 +473,77 @@ class User extends Memcached_DataObject return Notice::getStreamByIds($ids); } + function friendsTimeline($offset=0, $limit=NOTICES_PER_PAGE, $since_id=0, $before_id=0, $since=null) + { + $ids = Notice::stream(array($this, '_friendsTimelineDirect'), + array(false), + 'user:friends_timeline:'.$this->id, + $offset, $limit, $since_id, $before_id, $since); + + return Notice::getStreamByIds($ids); + } + + function ownFriendsTimeline($offset=0, $limit=NOTICES_PER_PAGE, $since_id=0, $before_id=0, $since=null) + { + $ids = Notice::stream(array($this, '_friendsTimelineDirect'), + array(true), + 'user:friends_timeline_own:'.$this->id, + $offset, $limit, $since_id, $before_id, $since); + + return Notice::getStreamByIds($ids); + } + + function _friendsTimelineDirect($own, $offset, $limit, $since_id, $max_id, $since) + { + $qry = + 'SELECT notice.id AS id ' . + 'FROM notice JOIN notice_inbox ON notice.id = notice_inbox.notice_id ' . + 'WHERE notice_inbox.user_id = ' . $this->id . ' ' . + 'AND notice.repeat_of IS NULL '; + + if (!$own) { + // XXX: autoload notice inbox for constant + $inbox = new Notice_inbox(); + + $qry .= 'AND notice_inbox.source != ' . NOTICE_INBOX_SOURCE_GATEWAY . ' '; + } + + if ($since_id != 0) { + $qry .= 'AND notice.id > ' . $since_id . ' '; + } + + if ($max_id != 0) { + $qry .= 'AND notice.id <= ' . $max_id . ' '; + } + + if (!is_null($since)) { + $qry .= 'AND notice.modified > \'' . date('Y-m-d H:i:s', $since) . '\' '; + } + + // NOTE: we sort by fave time, not by notice time! + + $qry .= 'ORDER BY notice.id DESC '; + + if (!is_null($offset)) { + $qry .= "LIMIT $limit OFFSET $offset"; + } + + $ids = array(); + + $notice = new Notice(); + + $notice->query($qry); + + while ($notice->fetch()) { + $ids[] = $notice->id; + } + + $notice->free(); + $notice = NULL; + + return $ids; + } + function blowFavesCache() { $cache = common_memcache(); -- cgit v1.2.3-54-g00ecf From b9040a7cc4d9f051f00e3b0d9010971262319d84 Mon Sep 17 00:00:00 2001 From: Brion Vibber Date: Mon, 14 Dec 2009 16:36:01 -0800 Subject: Add destructor on Memcached_DataObject to free DB_DataObject's global storage for an object when that object itself is destroyed. Reduces some, but not all, memory leakage for long-running processes. --- classes/Memcached_DataObject.php | 11 +++++++++++ 1 file changed, 11 insertions(+) (limited to 'classes') diff --git a/classes/Memcached_DataObject.php b/classes/Memcached_DataObject.php index 753fe954e..8fdb56088 100644 --- a/classes/Memcached_DataObject.php +++ b/classes/Memcached_DataObject.php @@ -23,6 +23,17 @@ require_once INSTALLDIR.'/classes/Memcached_DataObject.php'; class Memcached_DataObject extends DB_DataObject { + /** + * Destructor to free global memory resources associated with + * this data object when it's unset or goes out of scope. + * DB_DataObject doesn't do this yet by itself. + */ + function __destruct() + { + $this->free(); + parent::__destruct(); + } + function &staticGet($cls, $k, $v=null) { if (is_null($v)) { -- cgit v1.2.3-54-g00ecf From 797a0d79fbf4e105ee4ccdce093c1595c1f08a4a Mon Sep 17 00:00:00 2001 From: Evan Prodromou Date: Tue, 15 Dec 2009 10:31:25 -0500 Subject: create a method for notification for new messages, and use it --- actions/apidirectmessagenew.php | 2 +- actions/newmessage.php | 8 +------- classes/Message.php | 8 ++++++++ lib/command.php | 2 +- 4 files changed, 11 insertions(+), 9 deletions(-) (limited to 'classes') diff --git a/actions/apidirectmessagenew.php b/actions/apidirectmessagenew.php index e6c39ce4a..b9ac92d77 100644 --- a/actions/apidirectmessagenew.php +++ b/actions/apidirectmessagenew.php @@ -175,7 +175,7 @@ class ApiDirectMessageNewAction extends ApiAuthAction return; } - mail_notify_message($message, $this->user, $this->other); + $message->notify(); if ($this->format == 'xml') { $this->showSingleXmlDirectMessage($message); diff --git a/actions/newmessage.php b/actions/newmessage.php index 0db2e7181..350452091 100644 --- a/actions/newmessage.php +++ b/actions/newmessage.php @@ -173,7 +173,7 @@ class NewmessageAction extends Action return; } - $this->notify($user, $this->other, $message); + $message->notify(); if ($this->boolean('ajax')) { $this->startHTML('text/xml;charset=utf-8'); @@ -247,12 +247,6 @@ class NewmessageAction extends Action } } - function notify($from, $to, $message) - { - mail_notify_message($message, $from, $to); - // XXX: Jabber, SMS notifications... probably queued - } - // Do nothing (override) function showNoticeForm() diff --git a/classes/Message.php b/classes/Message.php index 718a9d922..16d0c60b3 100644 --- a/classes/Message.php +++ b/classes/Message.php @@ -89,4 +89,12 @@ class Message extends Memcached_DataObject $contentlimit = self::maxContent(); return ($contentlimit > 0 && !empty($content) && (mb_strlen($content) > $contentlimit)); } + + function notify() + { + $from = User::staticGet('id', $this->from_profile); + $to = User::staticGet('id', $this->to_profile); + + mail_notify_message($this, $from, $to); + } } diff --git a/lib/command.php b/lib/command.php index b30780bfb..67140c348 100644 --- a/lib/command.php +++ b/lib/command.php @@ -372,7 +372,7 @@ class MessageCommand extends Command } $message = Message::saveNew($this->user->id, $other->id, $this->text, $channel->source()); if ($message) { - mail_notify_message($message, $this->user, $other); + $message->notify(); $channel->output($this->user, sprintf(_('Direct message to %s sent'), $this->other)); } else { $channel->error($this->user, _('Error sending direct message.')); -- cgit v1.2.3-54-g00ecf From 945661d9426ef90a6ff6feaab3733c6b569dac7e Mon Sep 17 00:00:00 2001 From: Evan Prodromou Date: Tue, 15 Dec 2009 12:33:17 -0500 Subject: take out DB_DataObject destructor --- classes/Memcached_DataObject.php | 1 - 1 file changed, 1 deletion(-) (limited to 'classes') diff --git a/classes/Memcached_DataObject.php b/classes/Memcached_DataObject.php index 8fdb56088..360fb4424 100644 --- a/classes/Memcached_DataObject.php +++ b/classes/Memcached_DataObject.php @@ -31,7 +31,6 @@ class Memcached_DataObject extends DB_DataObject function __destruct() { $this->free(); - parent::__destruct(); } function &staticGet($cls, $k, $v=null) -- cgit v1.2.3-54-g00ecf From 22f02b35ad58030a093563a7df683962b4d165c5 Mon Sep 17 00:00:00 2001 From: Evan Prodromou Date: Tue, 15 Dec 2009 12:38:15 -0500 Subject: call DB_DataObject::__destruct() if it exists --- classes/Memcached_DataObject.php | 4 ++++ 1 file changed, 4 insertions(+) (limited to 'classes') diff --git a/classes/Memcached_DataObject.php b/classes/Memcached_DataObject.php index 360fb4424..70e9e351d 100644 --- a/classes/Memcached_DataObject.php +++ b/classes/Memcached_DataObject.php @@ -28,9 +28,13 @@ class Memcached_DataObject extends DB_DataObject * this data object when it's unset or goes out of scope. * DB_DataObject doesn't do this yet by itself. */ + function __destruct() { $this->free(); + if (method_exists('DB_DataObject', '__destruct')) { + parent::__destruct(); + } } function &staticGet($cls, $k, $v=null) -- cgit v1.2.3-54-g00ecf