From 16b219f1efbb9ed078a279a798b60ce1ac4ed100 Mon Sep 17 00:00:00 2001 From: Evan Prodromou Date: Sat, 4 Sep 2010 23:45:55 -0400 Subject: [PATCH] Save notice-to-status mapping in its own table Introduce a table mapping notices to Twitter statuses. Initialize this table at checkSchema() time. Save the mapping when we push or pull statuses. Use the table to determine if a notice has a Twitter equivalent. --- plugins/TwitterBridge/Notice_to_status.php | 166 ++++++++++++++++++ plugins/TwitterBridge/TwitterBridgePlugin.php | 60 ++++++- .../daemons/twitterstatusfetcher.php | 2 + plugins/TwitterBridge/twitter.php | 25 +-- 4 files changed, 236 insertions(+), 17 deletions(-) create mode 100644 plugins/TwitterBridge/Notice_to_status.php diff --git a/plugins/TwitterBridge/Notice_to_status.php b/plugins/TwitterBridge/Notice_to_status.php new file mode 100644 index 0000000000..ecd4905dcc --- /dev/null +++ b/plugins/TwitterBridge/Notice_to_status.php @@ -0,0 +1,166 @@ + + * @license http://www.fsf.org/licensing/licenses/agpl.html AGPLv3 + * @link http://status.net/ + * + * StatusNet - the distributed open-source microblogging tool + * Copyright (C) 2010, StatusNet, Inc. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +if (!defined('STATUSNET')) { + exit(1); +} + +require_once INSTALLDIR . '/classes/Memcached_DataObject.php'; + +/** + * Data class for mapping notices to statuses + * + * Notices flow back and forth between Twitter and StatusNet. We use this + * table to remember which StatusNet notice corresponds to which Twitter + * status. + * + * Note that notice_id is unique only within a single database; if you + * want to share this data for some reason, get the notice's URI and use + * that instead, since it's universally unique. + * + * @category Action + * @package StatusNet + * @author Evan Prodromou + * @license http://www.fsf.org/licensing/licenses/agpl.html AGPLv3 + * @link http://status.net/ + * + * @see DB_DataObject + */ + +class Notice_to_status extends Memcached_DataObject +{ + public $__table = 'notice_to_status'; // table name + public $notice_id; // int(4) primary_key not_null + public $status_id; // int(4) + public $created; // datetime + + /** + * Get an instance by key + * + * This is a utility method to get a single instance with a given key value. + * + * @param string $k Key to use to lookup + * @param mixed $v Value to lookup + * + * @return Notice_to_status object found, or null for no hits + * + */ + + function staticGet($k, $v=null) + { + return Memcached_DataObject::staticGet('Notice_to_status', $k, $v); + } + + /** + * return table definition for DB_DataObject + * + * DB_DataObject needs to know something about the table to manipulate + * instances. This method provides all the DB_DataObject needs to know. + * + * @return array array of column definitions + */ + + function table() + { + return array('notice_id' => DB_DATAOBJECT_INT + DB_DATAOBJECT_NOTNULL, + 'status_id' => DB_DATAOBJECT_INT + DB_DATAOBJECT_NOTNULL, + 'created' => DB_DATAOBJECT_STR + DB_DATAOBJECT_DATE + DB_DATAOBJECT_TIME + DB_DATAOBJECT_NOTNULL); + } + + /** + * return key definitions for DB_DataObject + * + * DB_DataObject needs to know about keys that the table has, since it + * won't appear in StatusNet's own keys list. In most cases, this will + * simply reference your keyTypes() function. + * + * @return array list of key field names + */ + + function keys() + { + return array_keys($this->keyTypes()); + } + + /** + * return key definitions for Memcached_DataObject + * + * Our caching system uses the same key definitions, but uses a different + * method to get them. This key information is used to store and clear + * cached data, so be sure to list any key that will be used for static + * lookups. + * + * @return array associative array of key definitions, field name to type: + * 'K' for primary key: for compound keys, add an entry for each component; + * 'U' for unique keys: compound keys are not well supported here. + */ + + function keyTypes() + { + return array('notice_id' => 'K', 'status_id' => 'U'); + } + + /** + * Magic formula for non-autoincrementing integer primary keys + * + * If a table has a single integer column as its primary key, DB_DataObject + * assumes that the column is auto-incrementing and makes a sequence table + * to do this incrementation. Since we don't need this for our class, we + * overload this method and return the magic formula that DB_DataObject needs. + * + * @return array magic three-false array that stops auto-incrementing. + */ + + function sequenceKey() + { + return array(false, false, false); + } + + /** + * Save a mapping between a notice and a status + * + * @param integer $notice_id ID of the notice in StatusNet + * @param integer $status_id ID of the status in Twitter + * + * @return Notice_to_status new object for this value + */ + + static function saveNew($notice_id, $status_id) + { + $n2s = new Notice_to_status(); + + $n2s->notice_id = $notice_id; + $n2s->status_id = $status_id; + $n2s->created = common_sql_now(); + + $n2s->insert(); + + return $n2s; + } +} diff --git a/plugins/TwitterBridge/TwitterBridgePlugin.php b/plugins/TwitterBridge/TwitterBridgePlugin.php index 8e3eba3186..5676025c22 100644 --- a/plugins/TwitterBridge/TwitterBridgePlugin.php +++ b/plugins/TwitterBridge/TwitterBridgePlugin.php @@ -194,18 +194,21 @@ class TwitterBridgePlugin extends Plugin */ function onAutoload($cls) { + $dir = dirname(__FILE__); + switch ($cls) { case 'TwittersettingsAction': case 'TwitterauthorizationAction': case 'TwitterloginAction': case 'TwitteradminpanelAction': - include_once INSTALLDIR . '/plugins/TwitterBridge/' . - strtolower(mb_substr($cls, 0, -6)) . '.php'; + include_once $dir . '/' . strtolower(mb_substr($cls, 0, -6)) . '.php'; return false; case 'TwitterOAuthClient': case 'TwitterQueueHandler': - include_once INSTALLDIR . '/plugins/TwitterBridge/' . - strtolower($cls) . '.php'; + include_once $dir . '/' . strtolower($cls) . '.php'; + return false; + case 'Notice_to_status': + include_once $dir . '/' . $cls . '.php'; return false; default: return true; @@ -360,5 +363,52 @@ class TwitterBridgePlugin extends Plugin } } -} + /** + * Database schema setup + * + * We maintain a table mapping StatusNet notices to Twitter statuses + * + * @see Schema + * @see ColumnDef + * + * @return boolean hook value; true means continue processing, false means stop. + */ + + function onCheckSchema() + { + $schema = Schema::get(); + + // For storing user-submitted flags on profiles + + $schema->ensureTable('notice_to_status', + array(new ColumnDef('notice_id', 'integer', null, + false, 'PRI'), + new ColumnDef('status_id', 'integer', null, + false, 'UNI'), + new ColumnDef('created', 'datetime', null, + false))); + // We update any notices that may have come in from + // Twitter that we don't have a status_id for. Note that + // this won't catch notices that originated at this StatusNet site. + + $n = new Notice(); + + $n->query('SELECT notice.id, notice.uri ' . + 'FROM notice LEFT JOIN notice_to_status ' . + 'ON notice.id = notice_to_status.notice_id ' . + 'WHERE notice.source = "twitter"' . + 'AND notice_to_status.status_id = NULL'); + + while ($n->fetch()) { + if (preg_match('#^http://twitter.com/[\w_.]+/status/(\d+)$#', $n->uri, $match)) { + + $status_id = $match[1]; + + Notice_to_status::saveNew($n->id, $status_id); + } + } + + return true; + } +} diff --git a/plugins/TwitterBridge/daemons/twitterstatusfetcher.php b/plugins/TwitterBridge/daemons/twitterstatusfetcher.php index 4e4befe06e..ae66c50aed 100755 --- a/plugins/TwitterBridge/daemons/twitterstatusfetcher.php +++ b/plugins/TwitterBridge/daemons/twitterstatusfetcher.php @@ -280,6 +280,7 @@ class TwitterStatusFetcher extends ParallelizingDaemon array('repeat_of' => $original->id, 'uri' => $statusUri)); common_log(LOG_INFO, "Saved {$repeat->id} as a repeat of {$original->id}"); + Notice_to_status::saveNew($repeat->id, $status->id); return $repeat; } } @@ -338,6 +339,7 @@ class TwitterStatusFetcher extends ParallelizingDaemon Event::handle('EndNoticeSave', array($notice)); } + Notice_to_status::saveNew($notice->id, $status->id); $notice->blowOnInsert(); return $notice; diff --git a/plugins/TwitterBridge/twitter.php b/plugins/TwitterBridge/twitter.php index 33d5443c22..94eaedee51 100644 --- a/plugins/TwitterBridge/twitter.php +++ b/plugins/TwitterBridge/twitter.php @@ -129,14 +129,9 @@ function is_twitter_bound($notice, $flink) { function is_twitter_notice($id) { - $notice = Notice::staticGet('id', $id); + $n2s = Notice_to_status::staticGet('notice_id', $id); - if (empty($notice)) { - // it's not any kind of notice, so it's definitely not a Twitter notice. - return false; - } - - return ($notice->source == 'twitter'); + return (!empty($n2s)); } function broadcast_twitter($notice) @@ -166,6 +161,9 @@ function retweet_notice($flink, $notice) try { $status = $client->statusesRetweet($id); + if (!empty($status)) { + Notice_to_status::saveNew($notice->id, $status->id); + } } catch (OAuthClientException $e) { return process_error($e, $flink, $notice); } @@ -173,12 +171,12 @@ function retweet_notice($flink, $notice) function twitter_status_id($notice) { - if ($notice->source == 'twitter' && - preg_match('#^http://twitter.com/[\w_.]+/status/(\d+)$#', $notice->uri, $match)) { - return $match[1]; + $n2s = Notice_to_status::staticGet('notice_id', $id); + if (empty($n2s)) { + return null; + } else { + return $n2s->status_id; } - - return null; } /** @@ -214,6 +212,9 @@ function broadcast_oauth($notice, $flink) { try { $status = $client->statusesUpdate($statustxt, $params); + if (!empty($status)) { + Notice_to_status::saveNew($notice->id, $status->id); + } } catch (OAuthClientException $e) { return process_error($e, $flink, $notice); } -- 2.39.5