Commit ec145b73fc91dd54695dd374c8a71a11e233b8c0

  • Tree SHA1: d4e718b
  • Parent SHA1: 2b10e35 (Avoid PHP notice when outputting API data for remote users; no $user means no $user->timezone :))
  • raw diff | raw patch
Major refactoring of queue handlers to support running multiple sites in one daemon.

Key changes:
* Initialization code moved from common.php to StatusNet class;
  can now switch configurations during runtime.
* As a consequence, configuration files must now be idempotent...
  Be careful with constant, function or class definitions.
* Control structure for daemons/QueueManager/QueueHandler has been refactored;
  the run loop is now managed by IoMaster run via scripts/queuedaemon.php
  IoManager subclasses are woken to handle socket input or polling, and may
  cover multiple sites.
* Plugins can implement notice queue handlers more easily by registering a
  QueueHandler class; no more need to add a daemon.

The new QueueDaemon runs from scripts/queuedaemon.php:

* This replaces most of the old *handler.php scripts; they've been refactored
  to the bare handler classes.
* Spawns multiple child processes to spread load; defaults to CPU count on
  Linux and Mac OS X systems, or override with --threads=N
* When multithreaded, child processes are automatically respawned on failure.
* Threads gracefully shut down and restart when passing a soft memory limit
  (defaults to 90% of memory_limit), limiting damage from memory leaks.
* Support for UDP-based monitoring: http://www.gitorious.org/snqmon

Rough control flow diagram:
QueueDaemon -> IoMaster -> IoManager
                           QueueManager [listen or poll] -> QueueHandler
                           XmppManager [ping & keepalive]
                           XmppConfirmManager [poll updates]

Todo:

* Respawning features not currently available running single-threaded.
* When running single-site, configuration changes aren't picked up.
* New sites or config changes affecting queue subscriptions are not yet
  handled without a daemon restart.
* SNMP monitoring output to integrate with general tools (nagios, ganglia)
* Convert XMPP confirmation message sends to use stomp queue instead of polling
* Convert xmppdaemon.php to IoManager?
* Convert Twitter status, friends import polling daemons to IoManager
* Clean up some error reporting and failure modes
* May need to adjust queue priorities for best perf in backlog/flood cases

Detailed code history available in my daemon-work branch:
http://www.gitorious.org/~brion/statusnet/brion-fixes/commits/daemon-work
  
331331 $exists = false;
332332 }
333333
334 // @fixme horrible evil hack!
335 //
336 // In multisite configuration we don't want to keep around a separate
337 // connection for every database; we could end up with thousands of
338 // connections open per thread. In an ideal world we might keep
339 // a connection per server and select different databases, but that'd
340 // be reliant on having the same db username/pass as well.
341 //
342 // MySQL connections are cheap enough we're going to try just
343 // closing out the old connection and reopening when we encounter
344 // a new DSN.
345 //
346 // WARNING WARNING if we end up actually using multiple DBs at a time
347 // we'll need some fancier logic here.
348 if (!$exists && !empty($_DB_DATAOBJECT['CONNECTIONS'])) {
349 foreach ($_DB_DATAOBJECT['CONNECTIONS'] as $index => $conn) {
350 if (!empty($conn)) {
351 $conn->disconnect();
352 }
353 unset($_DB_DATAOBJECT['CONNECTIONS'][$index]);
354 }
355 }
356
334357 $result = parent::_connect();
335358
336359 if ($result && !$exists) {
  
2525 function sequenceKey()
2626 { return array(false, false); }
2727
28 static function top($transport) {
28 static function top($transport=null) {
2929
3030 $qi = new Queue_item();
31 $qi->transport = $transport;
31 if ($transport) {
32 $qi->transport = $transport;
33 }
3234 $qi->orderBy('created');
3335 $qi->whereAdd('claimed is null');
3436
4242 # XXX: potential race condition
4343 # can we force it to only update if claimed is still null
4444 # (or old)?
45 common_log(LOG_INFO, 'claiming queue item = ' . $qi->notice_id . ' for transport ' . $transport);
45 common_log(LOG_INFO, 'claiming queue item = ' . $qi->notice_id .
46 ' for transport ' . $qi->transport);
4647 $orig = clone($qi);
4748 $qi->claimed = common_sql_now();
4849 $result = $qi->update($orig);
  
4949 static $cache = null;
5050 static $base = null;
5151
52 /**
53 * @param string $dbhost
54 * @param string $dbuser
55 * @param string $dbpass
56 * @param string $dbname
57 * @param array $servers memcached servers to use for caching config info
58 */
5259 static function setupDB($dbhost, $dbuser, $dbpass, $dbname, $servers)
5360 {
5461 global $config;
6767 if (class_exists('Memcache')) {
6868 self::$cache = new Memcache();
6969
70 // Can't close persistent connections, making forking painful.
71 //
72 // @fixme only do this in *parent* CLI processes.
73 // single-process and child-processes *should* use persistent.
74 $persist = php_sapi_name() != 'cli';
7075 if (is_array($servers)) {
7176 foreach($servers as $server) {
72 self::$cache->addServer($server);
77 self::$cache->addServer($server, 11211, $persist);
7378 }
7479 } else {
75 self::$cache->addServer($servers);
80 self::$cache->addServer($servers, 11211, $persist);
7681 }
7782 }
7883
101101 if (empty($sn)) {
102102 $sn = self::staticGet($k, $v);
103103 if (!empty($sn)) {
104 self::$cache->set($ck, $sn);
104 self::$cache->set($ck, clone($sn));
105105 }
106106 }
107107
133133 return parent::delete();
134134 }
135135
136 /**
137 * @param string $servername hostname
138 * @param string $pathname URL base path
139 * @param string $wildcard hostname suffix to match wildcard config
140 */
136141 static function setupSite($servername, $pathname, $wildcard)
137142 {
138143 global $config;
  
179179
180180 return $success;
181181 }
182
183 /**
184 * Close or reconnect any remote connections, such as to give
185 * daemon processes a chance to reconnect on a fresh socket.
186 *
187 * @return boolean success flag
188 */
189
190 function reconnect()
191 {
192 $success = false;
193
194 if (Event::handle('StartCacheReconnect', array(&$success))) {
195 $success = true;
196 Event::handle('EndCacheReconnect', array());
197 }
198
199 return $success;
200 }
182201}
lib/common.php
(14 / 180)
  
7676require_once(INSTALLDIR.'/lib/event.php');
7777require_once(INSTALLDIR.'/lib/plugin.php');
7878
79function _sn_to_path($sn)
80{
81 $past_root = substr($sn, 1);
82 $last_slash = strrpos($past_root, '/');
83 if ($last_slash > 0) {
84 $p = substr($past_root, 0, $last_slash);
85 } else {
86 $p = '';
87 }
88 return $p;
89}
90
91// Save our sanity when code gets loaded through subroutines such as PHPUnit tests
92global $default, $config, $_server, $_path;
93
94// try to figure out where we are. $server and $path
95// can be set by including module, else we guess based
96// on HTTP info.
97
98if (isset($server)) {
99 $_server = $server;
100} else {
101 $_server = array_key_exists('SERVER_NAME', $_SERVER) ?
102 strtolower($_SERVER['SERVER_NAME']) :
103 null;
104}
105
106if (isset($path)) {
107 $_path = $path;
108} else {
109 $_path = (array_key_exists('SERVER_NAME', $_SERVER) && array_key_exists('SCRIPT_NAME', $_SERVER)) ?
110 _sn_to_path($_SERVER['SCRIPT_NAME']) :
111 null;
112}
113
114require_once(INSTALLDIR.'/lib/default.php');
115
116// Set config values initially to default values
117
118$config = $default;
119
120// default configuration, overwritten in config.php
121
122$config['db'] = &PEAR::getStaticProperty('DB_DataObject','options');
123
124$config['db'] = $default['db'];
125
126// Backward compatibility
127
128$config['site']['design'] =& $config['design'];
129
130if (function_exists('date_default_timezone_set')) {
131 /* Work internally in UTC */
132 date_default_timezone_set('UTC');
133}
134
13579function addPlugin($name, $attrs = null)
13680{
137 $name = ucfirst($name);
138 $pluginclass = "{$name}Plugin";
139
140 if (!class_exists($pluginclass)) {
141
142 $files = array("local/plugins/{$pluginclass}.php",
143 "local/plugins/{$name}/{$pluginclass}.php",
144 "local/{$pluginclass}.php",
145 "local/{$name}/{$pluginclass}.php",
146 "plugins/{$pluginclass}.php",
147 "plugins/{$name}/{$pluginclass}.php");
148
149 foreach ($files as $file) {
150 $fullpath = INSTALLDIR.'/'.$file;
151 if (@file_exists($fullpath)) {
152 include_once($fullpath);
153 break;
154 }
155 }
156 }
157
158 $inst = new $pluginclass();
159
160 if (!empty($attrs)) {
161 foreach ($attrs as $aname => $avalue) {
162 $inst->$aname = $avalue;
163 }
164 }
165 return $inst;
81 return StatusNet::addPlugin($name, $attrs);
16682}
16783
168// From most general to most specific:
169// server-wide, then vhost-wide, then for a path,
170// finally for a dir (usually only need one of the last two).
171
172if (isset($conffile)) {
173 $_config_files = array($conffile);
174} else {
175 $_config_files = array('/etc/statusnet/statusnet.php',
176 '/etc/statusnet/laconica.php',
177 '/etc/laconica/laconica.php',
178 '/etc/statusnet/'.$_server.'.php',
179 '/etc/laconica/'.$_server.'.php');
180
181 if (strlen($_path) > 0) {
182 $_config_files[] = '/etc/statusnet/'.$_server.'_'.$_path.'.php';
183 $_config_files[] = '/etc/laconica/'.$_server.'_'.$_path.'.php';
184 }
185
186 $_config_files[] = INSTALLDIR.'/config.php';
187}
188
189global $_have_a_config;
190$_have_a_config = false;
191
192foreach ($_config_files as $_config_file) {
193 if (@file_exists($_config_file)) {
194 include_once($_config_file);
195 $_have_a_config = true;
196 }
197}
198
19984function _have_config()
20085{
201 global $_have_a_config;
202 return $_have_a_config;
86 return StatusNet::haveConfig();
20387}
20488
205// XXX: Throw a conniption if database not installed
206// XXX: Find a way to use htmlwriter for this instead of handcoded markup
207if (!_have_config()) {
208 echo '<p>'. _('No configuration file found. ') .'</p>';
209 echo '<p>'. _('I looked for configuration files in the following places: ') .'<br /> '. implode($_config_files, '<br />');
210 echo '<p>'. _('You may wish to run the installer to fix this.') .'</p>';
211 echo '<a href="install.php">'. _('Go to the installer.') .'</a>';
212 exit;
213}
214// Fixup for statusnet.ini
215
216$_db_name = substr($config['db']['database'], strrpos($config['db']['database'], '/') + 1);
217
218if ($_db_name != 'statusnet' && !array_key_exists('ini_'.$_db_name, $config['db'])) {
219 $config['db']['ini_'.$_db_name] = INSTALLDIR.'/classes/statusnet.ini';
220}
221
222// Backwards compatibility
223
224if (array_key_exists('memcached', $config)) {
225 if ($config['memcached']['enabled']) {
226 addPlugin('Memcache', array('servers' => $config['memcached']['server']));
227 }
228
229 if (!empty($config['memcached']['base'])) {
230 $config['cache']['base'] = $config['memcached']['base'];
231 }
232}
233
23489function __autoload($cls)
23590{
23691 if (file_exists(INSTALLDIR.'/classes/' . $cls . '.php')) {
102102 }
103103}
104104
105// Load default plugins
106
107foreach ($config['plugins']['default'] as $name => $params) {
108 if (is_null($params)) {
109 addPlugin($name);
110 } else if (is_array($params)) {
111 if (count($params) == 0) {
112 addPlugin($name);
113 } else {
114 $keys = array_keys($params);
115 if (is_string($keys[0])) {
116 addPlugin($name, $params);
117 } else {
118 foreach ($params as $paramset) {
119 addPlugin($name, $paramset);
120 }
121 }
122 }
123 }
124}
125
126105// XXX: how many of these could be auto-loaded on use?
127106// XXX: note that these files should not use config options
128107// at compile time since DB config options are not yet loaded.
117117require_once INSTALLDIR.'/lib/clientexception.php';
118118require_once INSTALLDIR.'/lib/serverexception.php';
119119
120// Load settings from database; note we need autoload for this
121
122Config::loadSettings();
123
124// XXX: if plugins should check the schema at runtime, do that here.
125
126if ($config['db']['schemacheck'] == 'runtime') {
127 Event::handle('CheckSchema');
120try {
121 StatusNet::init(@$server, @$path, @$conffile);
122} catch (NoConfigException $e) {
123 // XXX: Throw a conniption if database not installed
124 // XXX: Find a way to use htmlwriter for this instead of handcoded markup
125 echo '<p>'. _('No configuration file found. ') .'</p>';
126 echo '<p>'. _('I looked for configuration files in the following places: ') .'<br/> ';
127 echo implode($e->configFiles, '<br/>');
128 echo '<p>'. _('You may wish to run the installer to fix this.') .'</p>';
129 echo '<a href="install.php">'. _('Go to the installer.') .'</a>';
130 exit;
128131}
129132
133
130134// XXX: other formats here
131135
132136define('NICKNAME_FMT', VALIDATE_NUM.VALIDATE_ALPHA_LOWER);
133
134// Give plugins a chance to initialize in a fully-prepared environment
135
136Event::handle('InitializePlugin');
  
2222 * @category QueueManager
2323 * @package StatusNet
2424 * @author Evan Prodromou <evan@status.net>
25 * @copyright 2009 StatusNet, Inc.
25 * @author Brion Vibber <brion@status.net>
26 * @copyright 2009-2010 StatusNet, Inc.
2627 * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0
2728 * @link http://status.net/
2829 */
2930
3031class DBQueueManager extends QueueManager
3132{
32 var $qis = array();
33
34 function enqueue($object, $queue)
33 /**
34 * Saves a notice object reference into the queue item table.
35 * @return boolean true on success
36 * @throws ServerException on failure
37 */
38 public function enqueue($object, $queue)
3539 {
3640 $notice = $object;
3741
5151 throw new ServerException('DB error inserting queue item');
5252 }
5353
54 $this->stats('enqueued', $queue);
55
5456 return true;
5557 }
5658
57 function service($queue, $handler)
59 /**
60 * Poll every minute for new events during idle periods.
61 * We'll look in more often when there's data available.
62 *
63 * @return int seconds
64 */
65 public function pollInterval()
5866 {
59 while (true) {
60 $this->_log(LOG_DEBUG, 'Checking for notices...');
61 $timeout = $handler->timeout();
62 $notice = $this->_nextItem($queue, $timeout);
63 if (empty($notice)) {
64 $this->_log(LOG_DEBUG, 'No notices waiting; idling.');
65 // Nothing in the queue. Do you
66 // have other tasks, like servicing your
67 // XMPP connection, to do?
68 $handler->idle(QUEUE_HANDLER_MISS_IDLE);
67 return 60;
68 }
69
70 /**
71 * Run a polling cycle during idle processing in the input loop.
72 * @return boolean true if we had a hit
73 */
74 public function poll()
75 {
76 $this->_log(LOG_DEBUG, 'Checking for notices...');
77 $item = $this->_nextItem();
78 if ($item === false) {
79 $this->_log(LOG_DEBUG, 'No notices waiting; idling.');
80 return false;
81 }
82 if ($item === true) {
83 // We dequeued an entry for a deleted or invalid notice.
84 // Consider it a hit for poll rate purposes.
85 return true;
86 }
87
88 list($queue, $notice) = $item;
89 $this->_log(LOG_INFO, 'Got notice '. $notice->id . ' for transport ' . $queue);
90
91 // Yay! Got one!
92 $handler = $this->getHandler($queue);
93 if ($handler) {
94 if ($handler->handle_notice($notice)) {
95 $this->_log(LOG_INFO, "[$queue:notice $notice->id] Successfully handled notice");
96 $this->_done($notice, $queue);
6997 } else {
70 $this->_log(LOG_INFO, 'Got notice '. $notice->id);
71 // Yay! Got one!
72 if ($handler->handle_notice($notice)) {
73 $this->_log(LOG_INFO, 'Successfully handled notice '. $notice->id);
74 $this->_done($notice, $queue);
75 } else {
76 $this->_log(LOG_INFO, 'Failed to handle notice '. $notice->id);
77 $this->_fail($notice, $queue);
78 }
79 // Chance to e.g. service your XMPP connection
80 $this->_log(LOG_DEBUG, 'Idling after success.');
81 $handler->idle(QUEUE_HANDLER_HIT_IDLE);
98 $this->_log(LOG_INFO, "[$queue:notice $notice->id] Failed to handle notice");
99 $this->_fail($notice, $queue);
82100 }
83 // XXX: when do we give up?
101 } else {
102 $this->_log(LOG_INFO, "[$queue:notice $notice->id] No handler for queue $queue");
103 $this->_fail($notice, $queue);
84104 }
105 return true;
85106 }
86107
87 function _nextItem($queue, $timeout=null)
108 /**
109 * Pop the oldest unclaimed item off the queue set and claim it.
110 *
111 * @return mixed false if no items; true if bogus hit; otherwise array(string, Notice)
112 * giving the queue transport name.
113 */
114 protected function _nextItem()
88115 {
89116 $start = time();
90117 $result = null;
91118
92 $sleeptime = 1;
119 $qi = Queue_item::top();
120 if (empty($qi)) {
121 return false;
122 }
93123
94 do {
95 $qi = Queue_item::top($queue);
96 if (empty($qi)) {
97 $this->_log(LOG_DEBUG, "No new queue items, sleeping $sleeptime seconds.");
98 sleep($sleeptime);
99 $sleeptime *= 2;
100 } else {
101 $notice = Notice::staticGet('id', $qi->notice_id);
102 if (!empty($notice)) {
103 $result = $notice;
104 } else {
105 $this->_log(LOG_INFO, 'dequeued non-existent notice ' . $notice->id);
106 $qi->delete();
107 $qi->free();
108 $qi = null;
109 }
110 $sleeptime = 1;
111 }
112 } while (empty($result) && (is_null($timeout) || (time() - $start) < $timeout));
124 $queue = $qi->transport;
125 $notice = Notice::staticGet('id', $qi->notice_id);
126 if (empty($notice)) {
127 $this->_log(LOG_INFO, "[$queue:notice $notice->id] dequeued non-existent notice");
128 $qi->delete();
129 return true;
130 }
113131
114 return $result;
132 $result = $notice;
133 return array($queue, $notice);
115134 }
116135
117 function _done($object, $queue)
136 /**
137 * Delete our claimed item from the queue after successful processing.
138 *
139 * @param Notice $object
140 * @param string $queue
141 */
142 protected function _done($object, $queue)
118143 {
119144 // XXX: right now, we only handle notices
120145
149149 'transport' => $queue));
150150
151151 if (empty($qi)) {
152 $this->_log(LOG_INFO, 'Cannot find queue item for notice '.$notice->id.', queue '.$queue);
152 $this->_log(LOG_INFO, "[$queue:notice $notice->id] Cannot find queue item");
153153 } else {
154154 if (empty($qi->claimed)) {
155 $this->_log(LOG_WARNING, 'Reluctantly releasing unclaimed queue item '.
156 'for '.$notice->id.', queue '.$queue);
155 $this->_log(LOG_WARNING, "[$queue:notice $notice->id] Reluctantly releasing unclaimed queue item");
157156 }
158157 $qi->delete();
159158 $qi->free();
160 $qi = null;
161159 }
162160
163 $this->_log(LOG_INFO, 'done with notice ID = ' . $notice->id);
161 $this->_log(LOG_INFO, "[$queue:notice $notice->id] done with item");
162 $this->stats('handled', $queue);
164163
165164 $notice->free();
166 $notice = null;
167165 }
168166
169 function _fail($object, $queue)
167 /**
168 * Free our claimed queue item for later reprocessing in case of
169 * temporary failure.
170 *
171 * @param Notice $object
172 * @param string $queue
173 */
174 protected function _fail($object, $queue)
170175 {
171176 // XXX: right now, we only handle notices
172177
181181 'transport' => $queue));
182182
183183 if (empty($qi)) {
184 $this->_log(LOG_INFO, 'Cannot find queue item for notice '.$notice->id.', queue '.$queue);
184 $this->_log(LOG_INFO, "[$queue:notice $notice->id] Cannot find queue item");
185185 } else {
186186 if (empty($qi->claimed)) {
187 $this->_log(LOG_WARNING, 'Ignoring failure for unclaimed queue item '.
188 'for '.$notice->id.', queue '.$queue);
187 $this->_log(LOG_WARNING, "[$queue:notice $notice->id] Ignoring failure for unclaimed queue item");
189188 } else {
190189 $orig = clone($qi);
191190 $qi->claimed = null;
193193 }
194194 }
195195
196 $this->_log(LOG_INFO, 'done with notice ID = ' . $notice->id);
196 $this->_log(LOG_INFO, "[$queue:notice $notice->id] done with queue item");
197 $this->stats('error', $queue);
197198
198199 $notice->free();
199 $notice = null;
200200 }
201201
202 function _log($level, $msg)
202 protected function _log($level, $msg)
203203 {
204204 common_log($level, 'DBQueueManager: '.$msg);
205205 }
  
7979 'queue_basename' => '/queue/statusnet/',
8080 'stomp_username' => null,
8181 'stomp_password' => null,
82 'monitor' => null, // URL to monitor ping endpoint (work in progress)
83 'softlimit' => '90%', // total size or % of memory_limit at which to restart queue threads gracefully
8284 ),
8385 'license' =>
8486 array('url' => 'http://creativecommons.org/licenses/by/3.0/',
  
138138 }
139139 return false;
140140 }
141
142 /**
143 * Disables any and all handlers that have been set up so far;
144 * use only if you know it's safe to reinitialize all plugins.
145 */
146 public static function clearHandlers() {
147 Event::$_handlers = array();
148 }
141149}
  
1<?php
2/**
3 * StatusNet, the distributed open-source microblogging tool
4 *
5 * Abstract class for i/o managers
6 *
7 * PHP version 5
8 *
9 * LICENCE: This program is free software: you can redistribute it and/or modify
10 * it under the terms of the GNU Affero General Public License as published by
11 * the Free Software Foundation, either version 3 of the License, or
12 * (at your option) any later version.
13 *
14 * This program is distributed in the hope that it will be useful,
15 * but WITHOUT ANY WARRANTY; without even the implied warranty of
16 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17 * GNU Affero General Public License for more details.
18 *
19 * You should have received a copy of the GNU Affero General Public License
20 * along with this program. If not, see <http://www.gnu.org/licenses/>.
21 *
22 * @category QueueManager
23 * @package StatusNet
24 * @author Evan Prodromou <evan@status.net>
25 * @author Sarven Capadisli <csarven@status.net>
26 * @author Brion Vibber <brion@status.net>
27 * @copyright 2009-2010 StatusNet, Inc.
28 * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0
29 * @link http://status.net/
30 */
31
32abstract class IoManager
33{
34 const SINGLE_ONLY = 0;
35 const INSTANCE_PER_SITE = 1;
36 const INSTANCE_PER_PROCESS = 2;
37
38 /**
39 * Factory function to get an appropriate subclass.
40 */
41 public abstract static function get();
42
43 /**
44 * Tell the i/o queue master if and how we can handle multi-site
45 * processes.
46 *
47 * Return one of:
48 * IoManager::SINGLE_ONLY
49 * IoManager::INSTANCE_PER_SITE
50 * IoManager::INSTANCE_PER_PROCESS
51 */
52 public static function multiSite()
53 {
54 return IoManager::SINGLE_ONLY;
55 }
56
57 /**
58 * If in a multisite configuration, the i/o master will tell
59 * your manager about each site you'll have to handle so you
60 * can do any necessary per-site setup.
61 *
62 * @param string $site target site server name
63 */
64 public function addSite($site)
65 {
66 /* no-op */
67 }
68
69 /**
70 * This method is called when data is available on one of your
71 * i/o manager's sockets. The socket with data is passed in,
72 * in case you have multiple sockets.
73 *
74 * If your i/o manager is based on polling during idle processing,
75 * you don't need to implement this.
76 *
77 * @param resource $socket
78 * @return boolean true on success, false on failure
79 */
80 public function handleInput($socket)
81 {
82 return true;
83 }
84
85 /**
86 * Return any open sockets that the run loop should listen
87 * for input on. If input comes in on a listed socket,
88 * the matching manager's handleInput method will be called.
89 *
90 * @return array of resources
91 */
92 function getSockets()
93 {
94 return array();
95 }
96
97 /**
98 * Maximum planned time between poll() calls when input isn't waiting.
99 * Actual time may vary!
100 *
101 * When we get a polling hit, the timeout will be cut down to 0 while
102 * input is coming in, then will back off to this amount if no further
103 * input shows up.
104 *
105 * By default polling is disabled; you must override this to enable
106 * polling for this manager.
107 *
108 * @return int max poll interval in seconds, or 0 to disable polling
109 */
110 function pollInterval()
111 {
112 return 0;
113 }
114
115 /**
116 * Request a maximum timeout for listeners before the next idle period.
117 * Actual wait may be shorter, so don't go crazy in your idle()!
118 * Wait could be longer if other handlers performed some slow activity.
119 *
120 * Return 0 to request that listeners return immediately if there's no
121 * i/o and speed up the idle as much as possible; but don't do that all
122 * the time as this will burn CPU.
123 *
124 * @return int seconds
125 */
126 function timeout()
127 {
128 return 60;
129 }
130
131 /**
132 * Called by IoManager after each handled item or empty polling cycle.
133 * This is a good time to e.g. service your XMPP connection.
134 *
135 * Doesn't need to be overridden if there's no maintenance to do.
136 */
137 function idle()
138 {
139 return true;
140 }
141
142 /**
143 * The meat of a polling manager... check for something to do
144 * and do it! Note that you should not take too long, as other
145 * i/o managers may need to do some work too!
146 *
147 * On a successful hit, the next poll() call will come as soon
148 * as possible followed by exponential backoff up to pollInterval()
149 * if no more data is available.
150 *
151 * @return boolean true if events were hit
152 */
153 public function poll()
154 {
155 return false;
156 }
157
158 /**
159 * Initialization, run when the queue manager starts.
160 * If this function indicates failure, the handler run will be aborted.
161 *
162 * @param IoMaster $master process/event controller
163 * @return boolean true on success, false on failure
164 */
165 public function start($master)
166 {
167 $this->master = $master;
168 return true;
169 }
170
171 /**
172 * Cleanup, run when the queue manager ends.
173 * If this function indicates failure, a warning will be logged.
174 *
175 * @return boolean true on success, false on failure
176 */
177 public function finish()
178 {
179 return true;
180 }
181
182 /**
183 * Ping iomaster's queue status monitor with a stats update.
184 * Only valid during input loop!
185 *
186 * @param string $counter keyword for counter to increment
187 */
188 public function stats($counter, $owners=array())
189 {
190 $this->master->stats($counter, $owners);
191 }
192}
  
1<?php
2/**
3 * StatusNet, the distributed open-source microblogging tool
4 *
5 * I/O manager to wrap around socket-reading and polling queue & connection managers.
6 *
7 * PHP version 5
8 *
9 * LICENCE: This program is free software: you can redistribute it and/or modify
10 * it under the terms of the GNU Affero General Public License as published by
11 * the Free Software Foundation, either version 3 of the License, or
12 * (at your option) any later version.
13 *
14 * This program is distributed in the hope that it will be useful,
15 * but WITHOUT ANY WARRANTY; without even the implied warranty of
16 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17 * GNU Affero General Public License for more details.
18 *
19 * You should have received a copy of the GNU Affero General Public License
20 * along with this program. If not, see <http://www.gnu.org/licenses/>.
21 *
22 * @category QueueManager
23 * @package StatusNet
24 * @author Brion Vibber <brion@status.net>
25 * @copyright 2009 StatusNet, Inc.
26 * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0
27 * @link http://status.net/
28 */
29
30class IoMaster
31{
32 public $id;
33
34 protected $multiSite = false;
35 protected $managers = array();
36 protected $singletons = array();
37
38 protected $pollTimeouts = array();
39 protected $lastPoll = array();
40
41 /**
42 * @param string $id process ID to use in logging/monitoring
43 */
44 public function __construct($id)
45 {
46 $this->id = $id;
47 $this->monitor = new QueueMonitor();
48 }
49
50 public function init($multiSite=null)
51 {
52 if ($multiSite !== null) {
53 $this->multiSite = $multiSite;
54 }
55 if ($this->multiSite) {
56 $this->sites = $this->findAllSites();
57 } else {
58 $this->sites = array(common_config('site', 'server'));
59 }
60
61 if (empty($this->sites)) {
62 throw new Exception("Empty status_network table, cannot init");
63 }
64
65 foreach ($this->sites as $site) {
66 if ($site != common_config('site', 'server')) {
67 StatusNet::init($site);
68 }
69
70 $classes = array();
71 if (Event::handle('StartIoManagerClasses', array(&$classes))) {
72 $classes[] = 'QueueManager';
73 if (common_config('xmpp', 'enabled')) {
74 $classes[] = 'XmppManager'; // handles pings/reconnects
75 $classes[] = 'XmppConfirmManager'; // polls for outgoing confirmations
76 }
77 }
78 Event::handle('EndIoManagerClasses', array(&$classes));
79
80 foreach ($classes as $class) {
81 $this->instantiate($class);
82 }
83 }
84 }
85
86 /**
87 * Pull all local sites from status_network table.
88 * @return array of hostnames
89 */
90 protected function findAllSites()
91 {
92 $hosts = array();
93 $sn = new Status_network();
94 $sn->find();
95 while ($sn->fetch()) {
96 $hosts[] = $sn->hostname;
97 }
98 return $hosts;
99 }
100
101 /**
102 * Instantiate an i/o manager class for the current site.
103 * If a multi-site capable handler is already present,
104 * we don't need to build a new one.
105 *
106 * @param string $class
107 */
108 protected function instantiate($class)
109 {
110 if (isset($this->singletons[$class])) {
111 // Already instantiated a multi-site-capable handler.
112 // Just let it know it should listen to this site too!
113 $this->singletons[$class]->addSite(common_config('site', 'server'));
114 return;
115 }
116
117 $manager = $this->getManager($class);
118
119 if ($this->multiSite) {
120 $caps = $manager->multiSite();
121 if ($caps == IoManager::SINGLE_ONLY) {
122 throw new Exception("$class can't run with --all; aborting.");
123 }
124 if ($caps == IoManager::INSTANCE_PER_PROCESS) {
125 // Save this guy for later!
126 // We'll only need the one to cover multiple sites.
127 $this->singletons[$class] = $manager;
128 $manager->addSite(common_config('site', 'server'));
129 }
130 }
131
132 $this->managers[] = $manager;
133 }
134
135 protected function getManager($class)
136 {
137 return call_user_func(array($class, 'get'));
138 }
139
140 /**
141 * Basic run loop...
142 *
143 * Initialize all io managers, then sit around waiting for input.
144 * Between events or timeouts, pass control back to idle() method
145 * to allow for any additional background processing.
146 */
147 function service()
148 {
149 $this->logState('init');
150 $this->start();
151
152 while (true) {
153 $timeouts = array_values($this->pollTimeouts);
154 $timeouts[] = 60; // default max timeout
155
156 // Wait for something on one of our sockets
157 $sockets = array();
158 $managers = array();
159 foreach ($this->managers as $manager) {
160 foreach ($manager->getSockets() as $socket) {
161 $sockets[] = $socket;
162 $managers[] = $manager;
163 }
164 $timeouts[] = intval($manager->timeout());
165 }
166
167 $timeout = min($timeouts);
168 if ($sockets) {
169 $read = $sockets;
170 $write = array();
171 $except = array();
172 $this->logState('listening');
173 common_log(LOG_INFO, "Waiting up to $timeout seconds for socket data...");
174 $ready = stream_select($read, $write, $except, $timeout, 0);
175
176 if ($ready === false) {
177 common_log(LOG_ERR, "Error selecting on sockets");
178 } else if ($ready > 0) {
179 foreach ($read as $socket) {
180 $index = array_search($socket, $sockets, true);
181 if ($index !== false) {
182 $this->logState('queue');
183 $managers[$index]->handleInput($socket);
184 } else {
185 common_log(LOG_ERR, "Saw input on a socket we didn't listen to");
186 }
187 }
188 }
189 }
190
191 if ($timeout > 0 && empty($sockets)) {
192 // If we had no listeners, sleep until the pollers' next requested wakeup.
193 common_log(LOG_INFO, "Sleeping $timeout seconds until next poll cycle...");
194 $this->logState('sleep');
195 sleep($timeout);
196 }
197
198 $this->logState('poll');
199 $this->poll();
200
201 $this->logState('idle');
202 $this->idle();
203
204 $memoryLimit = $this->softMemoryLimit();
205 if ($memoryLimit > 0) {
206 $usage = memory_get_usage();
207 if ($usage > $memoryLimit) {
208 common_log(LOG_INFO, "Queue thread hit soft memory limit ($usage > $memoryLimit); gracefully restarting.");
209 break;
210 }
211 }
212 }
213
214 $this->logState('shutdown');
215 $this->finish();
216 }
217
218 /**
219 * Return fully-parsed soft memory limit in bytes.
220 * @return intval 0 or -1 if not set
221 */
222 function softMemoryLimit()
223 {
224 $softLimit = trim(common_config('queue', 'softlimit'));
225 if (substr($softLimit, -1) == '%') {
226 $limit = trim(ini_get('memory_limit'));
227 $limit = $this->parseMemoryLimit($limit);
228 if ($limit > 0) {
229 return intval(substr($softLimit, 0, -1) * $limit / 100);
230 } else {
231 return -1;
232 }
233 } else {
234 return $this->parseMemoryLimit($limit);
235 }
236 return $softLimit;
237 }
238
239 /**
240 * Interpret PHP shorthand for memory_limit and friends.
241 * Why don't they just expose the actual numeric value? :P
242 * @param string $mem
243 * @return int
244 */
245 protected function parseMemoryLimit($mem)
246 {
247 // http://www.php.net/manual/en/faq.using.php#faq.using.shorthandbytes
248 $size = array('k' => 1024,
249 'm' => 1024*1024,
250 'g' => 1024*1024*1024);
251 if (empty($mem)) {
252 return 0;
253 } else if (is_numeric($mem)) {
254 return intval($mem);
255 } else {
256 $mult = strtolower(substr($mem, -1));
257 if (isset($size[$mult])) {
258 return substr($mem, 0, -1) * $size[$mult];
259 } else {
260 return intval($mem);
261 }
262 }
263 }
264
265 function start()
266 {
267 foreach ($this->managers as $index => $manager) {
268 $manager->start($this);
269 // @fixme error check
270 if ($manager->pollInterval()) {
271 // We'll want to check for input on the first pass
272 $this->pollTimeouts[$index] = 0;
273 $this->lastPoll[$index] = 0;
274 }
275 }
276 }
277
278 function finish()
279 {
280 foreach ($this->managers as $manager) {
281 $manager->finish();
282 // @fixme error check
283 }
284 }
285
286 /**
287 * Called during the idle portion of the runloop to see which handlers
288 */
289 function poll()
290 {
291 foreach ($this->managers as $index => $manager) {
292 $interval = $manager->pollInterval();
293 if ($interval <= 0) {
294 // Not a polling manager.
295 continue;
296 }
297
298 if (isset($this->pollTimeouts[$index])) {
299 $timeout = $this->pollTimeouts[$index];
300 if (time() - $this->lastPoll[$index] < $timeout) {
301 // Not time to poll yet.
302 continue;
303 }
304 } else {
305 $timeout = 0;
306 }
307 $hit = $manager->poll();
308
309 $this->lastPoll[$index] = time();
310 if ($hit) {
311 // Do the next poll quickly, there may be more input!
312 $this->pollTimeouts[$index] = 0;
313 } else {
314 // Empty queue. Exponential backoff up to the maximum poll interval.
315 if ($timeout > 0) {
316 $timeout = min($timeout * 2, $interval);
317 } else {
318 $timeout = 1;
319 }
320 $this->pollTimeouts[$index] = $timeout;
321 }
322 }
323 }
324
325 /**
326 * Called after each handled item or empty polling cycle.
327 * This is a good time to e.g. service your XMPP connection.
328 */
329 function idle()
330 {
331 foreach ($this->managers as $manager) {
332 $manager->idle();
333 }
334 }
335
336 /**
337 * Send thread state update to the monitoring server, if configured.
338 *
339 * @param string $state ('init', 'queue', 'shutdown' etc)
340 * @param string $substate (optional, eg queue name 'omb' 'sms' etc)
341 */
342 protected function logState($state, $substate='')
343 {
344 $this->monitor->logState($this->id, $state, $substate);
345 }
346
347 /**
348 * Send thread stats.
349 * Thread ID will be implicit; other owners can be listed as well
350 * for per-queue and per-site records.
351 *
352 * @param string $key counter name
353 * @param array $owners list of owner keys like 'queue:jabber' or 'site:stat01'
354 */
355 public function stats($key, $owners=array())
356 {
357 $owners[] = "thread:" . $this->id;
358 $this->monitor->stats($key, $owners);
359 }
360}
  
8686}
8787
8888/**
89 * connect the configured Jabber account to the configured server
89 * Lazy-connect the configured Jabber account to the configured server;
90 * if already opened, the same connection will be returned.
9091 *
92 * In a multi-site background process, each site configuration
93 * will get its own connection.
94 *
9195 * @param string $resource Resource to connect (defaults to configured resource)
9296 *
9397 * @return XMPPHP connection to the configured server
9999
100100function jabber_connect($resource=null)
101101{
102 static $conn = null;
103 if (!$conn) {
102 static $connections = array();
103 $site = common_config('site', 'server');
104 if (empty($connections[$site])) {
105 if (empty($resource)) {
106 $resource = common_config('xmpp', 'resource');
107 }
104108 $conn = new Sharing_XMPP(common_config('xmpp', 'host') ?
105109 common_config('xmpp', 'host') :
106110 common_config('xmpp', 'server'),
107111 common_config('xmpp', 'port'),
108112 common_config('xmpp', 'user'),
109113 common_config('xmpp', 'password'),
110 ($resource) ? $resource :
111 common_config('xmpp', 'resource'),
114 $resource,
112115 common_config('xmpp', 'server'),
113116 common_config('xmpp', 'debug') ?
114117 true : false,
122122 if (!$conn) {
123123 return false;
124124 }
125 $connections[$site] = $conn;
125126
126127 $conn->autoSubscribe();
127128 $conn->useEncryption(common_config('xmpp', 'encryption'));
128129
129130 try {
130 $conn->connect(true); // true = persistent connection
131 common_log(LOG_INFO, __METHOD__ . ": connecting " .
132 common_config('xmpp', 'user') . '/' . $resource);
133 //$conn->connect(true); // true = persistent connection
134 $conn->connect(); // persistent connections break multisite
131135 } catch (XMPPHP_Exception $e) {
132136 common_log(LOG_ERR, $e->getMessage());
133137 return false;
139139
140140 $conn->processUntil('session_start');
141141 }
142 return $conn;
142 return $connections[$site];
143143}
144144
145145/**
  
1<?php
2/*
3 * StatusNet - the distributed open-source microblogging tool
4 * Copyright (C) 2008, 2009, StatusNet, Inc.
5 *
6 * This program is free software: you can redistribute it and/or modify
7 * it under the terms of the GNU Affero General Public License as published by
8 * the Free Software Foundation, either version 3 of the License, or
9 * (at your option) any later version.
10 *
11 * This program is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 * GNU Affero General Public License for more details.
15 *
16 * You should have received a copy of the GNU Affero General Public License
17 * along with this program. If not, see <http://www.gnu.org/licenses/>.
18 */
19
20if (!defined('STATUSNET') && !defined('LACONICA')) {
21 exit(1);
22}
23
24/**
25 * Queue handler for pushing new notices to Jabber users.
26 * @fixme this exception handling doesn't look very good.
27 */
28class JabberQueueHandler extends QueueHandler
29{
30 var $conn = null;
31
32 function transport()
33 {
34 return 'jabber';
35 }
36
37 function handle_notice($notice)
38 {
39 require_once(INSTALLDIR.'/lib/jabber.php');
40 try {
41 return jabber_broadcast_notice($notice);
42 } catch (XMPPHP_Exception $e) {
43 $this->log(LOG_ERR, "Got an XMPPHP_Exception: " . $e->getMessage());
44 exit(1);
45 }
46 }
47}
  
1<?php
2
3/**
4 * Based on code from Stomp PHP library, working around bugs in the base class.
5 *
6 * Original code is copyright 2005-2006 The Apache Software Foundation
7 * Modifications copyright 2009 StatusNet Inc by Brion Vibber <brion@status.net>
8 *
9 * Licensed under the Apache License, Version 2.0 (the "License");
10 * you may not use this file except in compliance with the License.
11 * You may obtain a copy of the License at
12 *
13 * http://www.apache.org/licenses/LICENSE-2.0
14 *
15 * Unless required by applicable law or agreed to in writing, software
16 * distributed under the License is distributed on an "AS IS" BASIS,
17 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
18 * See the License for the specific language governing permissions and
19 * limitations under the License.
20 */
21
22class LiberalStomp extends Stomp
23{
24 /**
25 * We need to be able to get the socket so advanced daemons can
26 * do a select() waiting for input both from the queue and from
27 * other sources such as an XMPP connection.
28 *
29 * @return resource
30 */
31 function getSocket()
32 {
33 return $this->_socket;
34 }
35
36 /**
37 * Make socket connection to the server
38 * We also set the stream to non-blocking mode, since we'll be
39 * select'ing to wait for updates. In blocking mode it seems
40 * to get confused sometimes.
41 *
42 * @throws StompException
43 */
44 protected function _makeConnection ()
45 {
46 parent::_makeConnection();
47 stream_set_blocking($this->_socket, 0);
48 }
49
50 /**
51 * Version 1.0.0 of the Stomp library gets confused if messages
52 * come in too fast over the connection. This version will read
53 * out as many frames as are ready to be read from the socket.
54 *
55 * Modified from Stomp::readFrame()
56 *
57 * @return StompFrame False when no frame to read
58 */
59 public function readFrames ()
60 {
61 if (!$this->hasFrameToRead()) {
62 return false;
63 }
64
65 $rb = 1024;
66 $data = '';
67 $end = false;
68 $frames = array();
69
70 do {
71 // @fixme this sometimes hangs in blocking mode...
72 // shouldn't we have been idle until we found there's more data?
73 $read = fread($this->_socket, $rb);
74 if ($read === false) {
75 $this->_reconnect();
76 // @fixme this will lose prior items
77 return $this->readFrames();
78 }
79 $data .= $read;
80 if (strpos($data, "\x00") !== false) {
81 // Frames are null-delimited, but some servers
82 // may append an extra \n according to old bug reports.
83 $data = str_replace("\x00\n", "\x00", $data);
84 $chunks = explode("\x00", $data);
85
86 $data = array_pop($chunks);
87 $frames = array_merge($frames, $chunks);
88 if ($data == '') {
89 // We're at the end of a frame; stop reading.
90 break;
91 } else {
92 // In the middle of a frame; keep going.
93 }
94 }
95 // @fixme find out why this len < 2 check was there
96 //$len = strlen($data);
97 } while (true);//$len < 2 || $end == false);
98
99 return array_map(array($this, 'parseFrame'), $frames);
100 }
101
102 /**
103 * Parse a raw Stomp frame into an object.
104 * Extracted from Stomp::readFrame()
105 *
106 * @param string $data
107 * @return StompFrame
108 */
109 function parseFrame($data)
110 {
111 list ($header, $body) = explode("\n\n", $data, 2);
112 $header = explode("\n", $header);
113 $headers = array();
114 $command = null;
115 foreach ($header as $v) {
116 if (isset($command)) {
117 list ($name, $value) = explode(':', $v, 2);
118 $headers[$name] = $value;
119 } else {
120 $command = $v;
121 }
122 }
123 $frame = new StompFrame($command, $headers, trim($body));
124 if (isset($frame->headers['transformation']) && $frame->headers['transformation'] == 'jms-map-json') {
125 require_once 'Stomp/Message/Map.php';
126 return new StompMessageMap($frame);
127 } else {
128 return $frame;
129 }
130 return $frame;
131 }
132}
  
1<?php
2/*
3 * StatusNet - the distributed open-source microblogging tool
4 * Copyright (C) 2008, 2009, StatusNet, Inc.
5 *
6 * This program is free software: you can redistribute it and/or modify
7 * it under the terms of the GNU Affero General Public License as published by
8 * the Free Software Foundation, either version 3 of the License, or
9 * (at your option) any later version.
10 *
11 * This program is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 * GNU Affero General Public License for more details.
15 *
16 * You should have received a copy of the GNU Affero General Public License
17 * along with this program. If not, see <http://www.gnu.org/licenses/>.
18 */
19
20if (!defined('STATUSNET') && !defined('LACONICA')) {
21 exit(1);
22}
23
24/**
25 * Queue handler for pushing new notices to OpenMicroBlogging subscribers.
26 */
27class OmbQueueHandler extends QueueHandler
28{
29
30 function transport()
31 {
32 return 'omb';
33 }
34
35 /**
36 * @fixme doesn't currently report failure back to the queue manager
37 * because omb_broadcast_notice() doesn't report it to us
38 */
39 function handle_notice($notice)
40 {
41 if ($this->is_remote($notice)) {
42 $this->log(LOG_DEBUG, 'Ignoring remote notice ' . $notice->id);
43 return true;
44 } else {
45 require_once(INSTALLDIR.'/lib/omb.php');
46 omb_broadcast_notice($notice);
47 return true;
48 }
49 }
50
51 function is_remote($notice)
52 {
53 $user = User::staticGet($notice->profile_id);
54 return is_null($user);
55 }
56}
  
1<?php
2/*
3 * StatusNet - the distributed open-source microblogging tool
4 * Copyright (C) 2008, 2009, StatusNet, Inc.
5 *
6 * This program is free software: you can redistribute it and/or modify
7 * it under the terms of the GNU Affero General Public License as published by
8 * the Free Software Foundation, either version 3 of the License, or
9 * (at your option) any later version.
10 *
11 * This program is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 * GNU Affero General Public License for more details.
15 *
16 * You should have received a copy of the GNU Affero General Public License
17 * along with this program. If not, see <http://www.gnu.org/licenses/>.
18 */
19
20if (!defined('STATUSNET') && !defined('LACONICA')) {
21 exit(1);
22}
23
24/**
25 * Queue handler for pushing new notices to ping servers.
26 */
27class PingQueueHandler extends QueueHandler {
28
29 function transport() {
30 return 'ping';
31 }
32
33 function handle_notice($notice) {
34 require_once INSTALLDIR . '/lib/ping.php';
35 return ping_broadcast_notice($notice);
36 }
37}
  
1<?php
2/*
3 * StatusNet - the distributed open-source microblogging tool
4 * Copyright (C) 2008, 2009, StatusNet, Inc.
5 *
6 * This program is free software: you can redistribute it and/or modify
7 * it under the terms of the GNU Affero General Public License as published by
8 * the Free Software Foundation, either version 3 of the License, or
9 * (at your option) any later version.
10 *
11 * This program is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 * GNU Affero General Public License for more details.
15 *
16 * You should have received a copy of the GNU Affero General Public License
17 * along with this program. If not, see <http://www.gnu.org/licenses/>.
18 */
19
20if (!defined('STATUSNET') && !defined('LACONICA')) {
21 exit(1);
22}
23
24/**
25 * Queue handler for letting plugins handle stuff.
26 *
27 * The plugin queue handler accepts notices over the "plugin" queue
28 * and simply passes them through the "HandleQueuedNotice" event.
29 *
30 * This gives plugins a chance to do background processing without
31 * actually registering their own queue and ensuring that things
32 * are queued into it.
33 *
34 * Fancier plugins may wish to instead hook the 'GetQueueHandlerClass'
35 * event with their own class, in which case they must ensure that
36 * their notices get enqueued when they need them.
37 */
38class PluginQueueHandler extends QueueHandler
39{
40 function transport()
41 {
42 return 'plugin';
43 }
44
45 function handle_notice($notice)
46 {
47 Event::handle('HandleQueuedNotice', array(&$notice));
48 return true;
49 }
50}
  
1<?php
2/*
3 * StatusNet - the distributed open-source microblogging tool
4 * Copyright (C) 2008, 2009, StatusNet, Inc.
5 *
6 * This program is free software: you can redistribute it and/or modify
7 * it under the terms of the GNU Affero General Public License as published by
8 * the Free Software Foundation, either version 3 of the License, or
9 * (at your option) any later version.
10 *
11 * This program is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 * GNU Affero General Public License for more details.
15 *
16 * You should have received a copy of the GNU Affero General Public License
17 * along with this program. If not, see <http://www.gnu.org/licenses/>.
18 */
19
20if (!defined('STATUSNET') && !defined('LACONICA')) {
21 exit(1);
22}
23
24/**
25 * Queue handler for pushing new notices to public XMPP subscribers.
26 * @fixme correct this exception handling
27 */
28class PublicQueueHandler extends QueueHandler
29{
30
31 function transport()
32 {
33 return 'public';
34 }
35
36 function handle_notice($notice)
37 {
38 require_once(INSTALLDIR.'/lib/jabber.php');
39 try {
40 return jabber_public_notice($notice);
41 } catch (XMPPHP_Exception $e) {
42 $this->log(LOG_ERR, "Got an XMPPHP_Exception: " . $e->getMessage());
43 die($e->getMessage());
44 }
45 return true;
46 }
47}
  
1919
2020if (!defined('STATUSNET') && !defined('LACONICA')) { exit(1); }
2121
22require_once(INSTALLDIR.'/lib/daemon.php');
23require_once(INSTALLDIR.'/classes/Queue_item.php');
24require_once(INSTALLDIR.'/classes/Notice.php');
25
26define('CLAIM_TIMEOUT', 1200);
27define('QUEUE_HANDLER_MISS_IDLE', 10);
28define('QUEUE_HANDLER_HIT_IDLE', 0);
29
3022/**
3123 * Base class for queue handlers.
3224 *
2828 *
2929 * Subclasses must override at least the following methods:
3030 * - transport
31 * - start
32 * - finish
3331 * - handle_notice
34 *
35 * Some subclasses will also want to override the idle handler:
36 * - idle
3732 */
38class QueueHandler extends Daemon
33#class QueueHandler extends Daemon
34class QueueHandler
3935{
4036
41 function __construct($id=null, $daemonize=true)
42 {
43 parent::__construct($daemonize);
37# function __construct($id=null, $daemonize=true)
38# {
39# parent::__construct($daemonize);
40#
41# if ($id) {
42# $this->set_id($id);
43# }
44# }
4445
45 if ($id) {
46 $this->set_id($id);
47 }
48 }
49
5046 /**
5147 * How many seconds a polling-based queue manager should wait between
5248 * checks for new items to handle.
5349 *
5450 * Defaults to 60 seconds; override to speed up or slow down.
5551 *
52 * @fixme not really compatible with global queue manager
5653 * @return int timeout in seconds
5754 */
58 function timeout()
59 {
60 return 60;
61 }
55# function timeout()
56# {
57# return 60;
58# }
6259
63 function class_name()
64 {
65 return ucfirst($this->transport()) . 'Handler';
66 }
60# function class_name()
61# {
62# return ucfirst($this->transport()) . 'Handler';
63# }
6764
68 function name()
69 {
70 return strtolower($this->class_name().'.'.$this->get_id());
71 }
65# function name()
66# {
67# return strtolower($this->class_name().'.'.$this->get_id());
68# }
7269
7370 /**
7471 * Return transport keyword which identifies items this queue handler
8282 }
8383
8484 /**
85 * Initialization, run when the queue handler starts.
86 * If this function indicates failure, the handler run will be aborted.
87 *
88 * @fixme run() will abort if this doesn't return true,
89 * but some subclasses don't bother.
90 * @return boolean true on success, false on failure
91 */
92 function start()
93 {
94 }
95
96 /**
97 * Cleanup, run when the queue handler ends.
98 * If this function indicates failure, a warning will be logged.
99 *
100 * @fixme run() will throw warnings if this doesn't return true,
101 * but many subclasses don't bother.
102 * @return boolean true on success, false on failure
103 */
104 function finish()
105 {
106 }
107
108 /**
10985 * Here's the meat of your queue handler -- you're handed a Notice
11086 * object, which you may do as you will with.
11187 *
134134 return true;
135135 }
136136
137 /**
138 * Called by QueueHandler after each handled item or empty polling cycle.
139 * This is a good time to e.g. service your XMPP connection.
140 *
141 * Doesn't need to be overridden if there's no maintenance to do.
142 *
143 * @param int $timeout seconds to sleep if there's nothing to do
144 */
145 function idle($timeout=0)
146 {
147 if ($timeout > 0) {
148 sleep($timeout);
149 }
150 }
151137
152138 function log($level, $msg)
153139 {
154140 common_log($level, $this->class_name() . ' ('. $this->get_id() .'): '.$msg);
155 }
156
157 function getSockets()
158 {
159 return array();
160141 }
161142}
  
22/**
33 * StatusNet, the distributed open-source microblogging tool
44 *
5 * Abstract class for queue managers
5 * Abstract class for i/o managers
66 *
77 * PHP version 5
88 *
2323 * @package StatusNet
2424 * @author Evan Prodromou <evan@status.net>
2525 * @author Sarven Capadisli <csarven@status.net>
26 * @copyright 2009 StatusNet, Inc.
26 * @author Brion Vibber <brion@status.net>
27 * @copyright 2009-2010 StatusNet, Inc.
2728 * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0
2829 * @link http://status.net/
2930 */
3031
31class QueueManager
32/**
33 * Completed child classes must implement the enqueue() method.
34 *
35 * For background processing, classes should implement either socket-based
36 * input (handleInput(), getSockets()) or idle-loop polling (idle()).
37 */
38abstract class QueueManager extends IoManager
3239{
3340 static $qm = null;
3441
35 static function get()
42 /**
43 * Factory function to pull the appropriate QueueManager object
44 * for this site's configuration. It can then be used to queue
45 * events for later processing or to spawn a processing loop.
46 *
47 * Plugins can add to the built-in types by hooking StartNewQueueManager.
48 *
49 * @return QueueManager
50 */
51 public static function get()
3652 {
3753 if (empty(self::$qm)) {
3854
7878 return self::$qm;
7979 }
8080
81 function enqueue($object, $queue)
81 /**
82 * @fixme wouldn't necessarily work with other class types.
83 * Better to change the interface...?
84 */
85 public static function multiSite()
8286 {
83 throw ServerException("Unimplemented function 'enqueue' called");
87 if (common_config('queue', 'subsystem') == 'stomp') {
88 return IoManager::INSTANCE_PER_PROCESS;
89 } else {
90 return IoManager::SINGLE_ONLY;
91 }
8492 }
8593
86 function service($queue, $handler)
94 function __construct()
8795 {
88 throw ServerException("Unimplemented function 'service' called");
96 $this->initialize();
97 }
98
99 /**
100 * Store an object (usually/always a Notice) into the given queue
101 * for later processing. No guarantee is made on when it will be
102 * processed; it could be immediately or at some unspecified point
103 * in the future.
104 *
105 * Must be implemented by any queue manager.
106 *
107 * @param Notice $object
108 * @param string $queue
109 */
110 abstract function enqueue($object, $queue);
111
112 /**
113 * Instantiate the appropriate QueueHandler class for the given queue.
114 *
115 * @param string $queue
116 * @return mixed QueueHandler or null
117 */
118 function getHandler($queue)
119 {
120 if (isset($this->handlers[$queue])) {
121 $class = $this->handlers[$queue];
122 if (class_exists($class)) {
123 return new $class();
124 } else {
125 common_log(LOG_ERR, "Nonexistent handler class '$class' for queue '$queue'");
126 }
127 } else {
128 common_log(LOG_ERR, "Requested handler for unkown queue '$queue'");
129 }
130 return null;
131 }
132
133 /**
134 * Get a list of all registered queue transport names.
135 *
136 * @return array of strings
137 */
138 function getQueues()
139 {
140 return array_keys($this->handlers);
141 }
142
143 /**
144 * Initialize the list of queue handlers
145 *
146 * @event StartInitializeQueueManager
147 * @event EndInitializeQueueManager
148 */
149 function initialize()
150 {
151 if (Event::handle('StartInitializeQueueManager', array($this))) {
152 $this->connect('plugin', 'PluginQueueHandler');
153 $this->connect('omb', 'OmbQueueHandler');
154 $this->connect('ping', 'PingQueueHandler');
155 if (common_config('sms', 'enabled')) {
156 $this->connect('sms', 'SmsQueueHandler');
157 }
158
159 // XMPP output handlers...
160 if (common_config('xmpp', 'enabled')) {
161 $this->connect('jabber', 'JabberQueueHandler');
162 $this->connect('public', 'PublicQueueHandler');
163
164 // @fixme this should move up a level or should get an actual queue
165 $this->connect('confirm', 'XmppConfirmHandler');
166 }
167
168 // For compat with old plugins not registering their own handlers.
169 $this->connect('plugin', 'PluginQueueHandler');
170 }
171 Event::handle('EndInitializeQueueManager', array($this));
172 }
173
174 /**
175 * Register a queue transport name and handler class for your plugin.
176 * Only registered transports will be reliably picked up!
177 *
178 * @param string $transport
179 * @param string $class
180 */
181 public function connect($transport, $class)
182 {
183 $this->handlers[$transport] = $class;
184 }
185
186 /**
187 * Send a statistic ping to the queue monitoring system,
188 * optionally with a per-queue id.
189 *
190 * @param string $key
191 * @param string $queue
192 */
193 function stats($key, $queue=false)
194 {
195 $owners = array();
196 if ($queue) {
197 $owners[] = "queue:$queue";
198 $owners[] = "site:" . common_config('site', 'server');
199 }
200 if (isset($this->master)) {
201 $this->master->stats($key, $owners);
202 } else {
203 $monitor = new QueueMonitor();
204 $monitor->stats($key, $owners);
205 }
89206 }
90207}
  
1<?php
2/**
3 * StatusNet, the distributed open-source microblogging tool
4 *
5 * Monitoring output helper for IoMaster and IoManager/QueueManager
6 *
7 * PHP version 5
8 *
9 * LICENCE: This program is free software: you can redistribute it and/or modify
10 * it under the terms of the GNU Affero General Public License as published by
11 * the Free Software Foundation, either version 3 of the License, or
12 * (at your option) any later version.
13 *
14 * This program is distributed in the hope that it will be useful,
15 * but WITHOUT ANY WARRANTY; without even the implied warranty of
16 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17 * GNU Affero General Public License for more details.
18 *
19 * You should have received a copy of the GNU Affero General Public License
20 * along with this program. If not, see <http://www.gnu.org/licenses/>.
21 *
22 * @category QueueManager
23 * @package StatusNet
24 * @author Brion Vibber <brion@status.net>
25 * @copyright 2010 StatusNet, Inc.
26 * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0
27 * @link http://status.net/
28 */
29
30class QueueMonitor
31{
32 protected $monSocket = null;
33
34 /**
35 * Increment monitoring statistics for a given counter, if configured.
36 * Only explicitly listed thread/site/queue owners will be incremented.
37 *
38 * @param string $key counter name
39 * @param array $owners list of owner keys like 'queue:jabber' or 'site:stat01'
40 */
41 public function stats($key, $owners=array())
42 {
43 $this->ping(array('counter' => $key,
44 'owners' => $owners));
45 }
46
47 /**
48 * Send thread state update to the monitoring server, if configured.
49 *
50 * @param string $thread ID (eg 'generic.1')
51 * @param string $state ('init', 'queue', 'shutdown' etc)
52 * @param string $substate (optional, eg queue name 'omb' 'sms' etc)
53 */
54 public function logState($threadId, $state, $substate='')
55 {
56 $this->ping(array('thread_id' => $threadId,
57 'state' => $state,
58 'substate' => $substate,
59 'ts' => microtime(true)));
60 }
61
62 /**
63 * General call to the monitoring server
64 */
65 protected function ping($data)
66 {
67 $target = common_config('queue', 'monitor');
68 if (empty($target)) {
69 return;
70 }
71
72 $data = $this->prepMonitorData($data);
73
74 if (substr($target, 0, 4) == 'udp:') {
75 $this->pingUdp($target, $data);
76 } else if (substr($target, 0, 5) == 'http:') {
77 $this->pingHttp($target, $data);
78 } else {
79 common_log(LOG_ERR, __METHOD__ . ' unknown monitor target type ' . $target);
80 }
81 }
82
83 protected function pingUdp($target, $data)
84 {
85 if (!$this->monSocket) {
86 $this->monSocket = stream_socket_client($target, $errno, $errstr);
87 }
88 if ($this->monSocket) {
89 $post = http_build_query($data, '', '&');
90 stream_socket_sendto($this->monSocket, $post);
91 } else {
92 common_log(LOG_ERR, __METHOD__ . " UDP logging fail: $errstr");
93 }
94 }
95
96 protected function pingHttp($target, $data)
97 {
98 $client = new HTTPClient();
99 $result = $client->post($target, array(), $data);
100
101 if (!$result->isOk()) {
102 common_log(LOG_ERR, __METHOD__ . ' HTTP ' . $result->getStatus() .
103 ': ' . $result->getBody());
104 }
105 }
106
107 protected function prepMonitorData($data)
108 {
109 #asort($data);
110 #$macdata = http_build_query($data, '', '&');
111 #$key = 'This is a nice old key';
112 #$data['hmac'] = hash_hmac('sha256', $macdata, $key);
113 return $data;
114 }
115
116}
  
1<?php
2/*
3 * StatusNet - the distributed open-source microblogging tool
4 * Copyright (C) 2008, 2009, StatusNet, Inc.
5 *
6 * This program is free software: you can redistribute it and/or modify
7 * it under the terms of the GNU Affero General Public License as published by
8 * the Free Software Foundation, either version 3 of the License, or
9 * (at your option) any later version.
10 *
11 * This program is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 * GNU Affero General Public License for more details.
15 *
16 * You should have received a copy of the GNU Affero General Public License
17 * along with this program. If not, see <http://www.gnu.org/licenses/>.
18 */
19
20if (!defined('STATUSNET') && !defined('LACONICA')) {
21 exit(1);
22}
23
24/**
25 * Queue handler for pushing new notices to local subscribers using SMS.
26 */
27class SmsQueueHandler extends QueueHandler
28{
29 function transport()
30 {
31 return 'sms';
32 }
33
34 function handle_notice($notice)
35 {
36 require_once(INSTALLDIR.'/lib/mail.php');
37 return mail_broadcast_notice_sms($notice);
38 }
39}
  
1<?php
2/*
3 * StatusNet - the distributed open-source microblogging tool
4 * Copyright (C) 2009, StatusNet, Inc.
5 *
6 * This program is free software: you can redistribute it and/or modify
7 * it under the terms of the GNU Affero General Public License as published by
8 * the Free Software Foundation, either version 3 of the License, or
9 * (at your option) any later version.
10 *
11 * This program is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 * GNU Affero General Public License for more details.
15 *
16 * You should have received a copy of the GNU Affero General Public License
17 * along with this program. If not, see <http://www.gnu.org/licenses/>.
18 */
19
20if (!defined('STATUSNET') && !defined('LACONICA')) { exit(1); }
21
22global $config, $_server, $_path;
23
24/**
25 * Global configuration setup and management.
26 */
27class StatusNet
28{
29 protected static $have_config;
30
31 /**
32 * Configure and instantiate a plugin into the current configuration.
33 * Class definitions will be loaded from standard paths if necessary.
34 * Note that initialization events won't be fired until later.
35 *
36 * @param string $name class name & plugin file/subdir name
37 * @param array $attrs key/value pairs of public attributes to set on plugin instance
38 *
39 * @throws ServerException if plugin can't be found
40 */
41 public static function addPlugin($name, $attrs = null)
42 {
43 $name = ucfirst($name);
44 $pluginclass = "{$name}Plugin";
45
46 if (!class_exists($pluginclass)) {
47
48 $files = array("local/plugins/{$pluginclass}.php",
49 "local/plugins/{$name}/{$pluginclass}.php",
50 "local/{$pluginclass}.php",
51 "local/{$name}/{$pluginclass}.php",
52 "plugins/{$pluginclass}.php",
53 "plugins/{$name}/{$pluginclass}.php");
54
55 foreach ($files as $file) {
56 $fullpath = INSTALLDIR.'/'.$file;
57 if (@file_exists($fullpath)) {
58 include_once($fullpath);
59 break;
60 }
61 }
62 if (!class_exists($pluginclass)) {
63 throw new ServerException(500, "Plugin $name not found.");
64 }
65 }
66
67 $inst = new $pluginclass();
68 if (!empty($attrs)) {
69 foreach ($attrs as $aname => $avalue) {
70 $inst->$aname = $avalue;
71 }
72 }
73 return true;
74 }
75
76 /**
77 * Initialize, or re-initialize, StatusNet global configuration
78 * and plugins.
79 *
80 * If switching site configurations during script execution, be
81 * careful when working with leftover objects -- global settings
82 * affect many things and they may not behave as you expected.
83 *
84 * @param $server optional web server hostname for picking config
85 * @param $path optional URL path for picking config
86 * @param $conffile optional configuration file path
87 *
88 * @throws NoConfigException if config file can't be found
89 */
90 public static function init($server=null, $path=null, $conffile=null)
91 {
92 StatusNet::initDefaults($server, $path);
93 StatusNet::loadConfigFile($conffile);
94
95 // Load settings from database; note we need autoload for this
96 Config::loadSettings();
97
98 self::initPlugins();
99 }
100
101 /**
102 * Fire initialization events for all instantiated plugins.
103 */
104 protected static function initPlugins()
105 {
106 // Load default plugins
107 foreach (common_config('plugins', 'default') as $name => $params) {
108 if (is_null($params)) {
109 addPlugin($name);
110 } else if (is_array($params)) {
111 if (count($params) == 0) {
112 addPlugin($name);
113 } else {
114 $keys = array_keys($params);
115 if (is_string($keys[0])) {
116 addPlugin($name, $params);
117 } else {
118 foreach ($params as $paramset) {
119 addPlugin($name, $paramset);
120 }
121 }
122 }
123 }
124 }
125
126 // XXX: if plugins should check the schema at runtime, do that here.
127 if (common_config('db', 'schemacheck') == 'runtime') {
128 Event::handle('CheckSchema');
129 }
130
131 // Give plugins a chance to initialize in a fully-prepared environment
132 Event::handle('InitializePlugin');
133 }
134
135 /**
136 * Quick-check if configuration has been established.
137 * Useful for functions which may get used partway through
138 * initialization to back off from fancier things.
139 *
140 * @return bool
141 */
142 public function haveConfig()
143 {
144 return self::$have_config;
145 }
146
147 /**
148 * Build default configuration array
149 * @return array
150 */
151 protected static function defaultConfig()
152 {
153 global $_server, $_path;
154 require(INSTALLDIR.'/lib/default.php');
155 return $default;
156 }
157
158 /**
159 * Establish default configuration based on given or default server and path
160 * Sets global $_server, $_path, and $config
161 */
162 protected static function initDefaults($server, $path)
163 {
164 global $_server, $_path, $config;
165
166 Event::clearHandlers();
167
168 // try to figure out where we are. $server and $path
169 // can be set by including module, else we guess based
170 // on HTTP info.
171
172 if (isset($server)) {
173 $_server = $server;
174 } else {
175 $_server = array_key_exists('SERVER_NAME', $_SERVER) ?
176 strtolower($_SERVER['SERVER_NAME']) :
177 null;
178 }
179
180 if (isset($path)) {
181 $_path = $path;
182 } else {
183 $_path = (array_key_exists('SERVER_NAME', $_SERVER) && array_key_exists('SCRIPT_NAME', $_SERVER)) ?
184 self::_sn_to_path($_SERVER['SCRIPT_NAME']) :
185 null;
186 }
187
188 // Set config values initially to default values
189 $default = self::defaultConfig();
190 $config = $default;
191
192 // default configuration, overwritten in config.php
193 // Keep DB_DataObject's db config synced to ours...
194
195 $config['db'] = &PEAR::getStaticProperty('DB_DataObject','options');
196
197 $config['db'] = $default['db'];
198
199 // Backward compatibility
200
201 $config['site']['design'] =& $config['design'];
202
203 if (function_exists('date_default_timezone_set')) {
204 /* Work internally in UTC */
205 date_default_timezone_set('UTC');
206 }
207 }
208
209 protected function _sn_to_path($sn)
210 {
211 $past_root = substr($sn, 1);
212 $last_slash = strrpos($past_root, '/');
213 if ($last_slash > 0) {
214 $p = substr($past_root, 0, $last_slash);
215 } else {
216 $p = '';
217 }
218 return $p;
219 }
220
221 /**
222 * Load the default or specified configuration file.
223 * Modifies global $config and may establish plugins.
224 *
225 * @throws NoConfigException
226 */
227 protected function loadConfigFile($conffile=null)
228 {
229 global $_server, $_path, $config;
230
231 // From most general to most specific:
232 // server-wide, then vhost-wide, then for a path,
233 // finally for a dir (usually only need one of the last two).
234
235 if (isset($conffile)) {
236 $config_files = array($conffile);
237 } else {
238 $config_files = array('/etc/statusnet/statusnet.php',
239 '/etc/statusnet/laconica.php',
240 '/etc/laconica/laconica.php',
241 '/etc/statusnet/'.$_server.'.php',
242 '/etc/laconica/'.$_server.'.php');
243
244 if (strlen($_path) > 0) {
245 $config_files[] = '/etc/statusnet/'.$_server.'_'.$_path.'.php';
246 $config_files[] = '/etc/laconica/'.$_server.'_'.$_path.'.php';
247 }
248
249 $config_files[] = INSTALLDIR.'/config.php';
250 }
251
252 self::$have_config = false;
253
254 foreach ($config_files as $_config_file) {
255 if (@file_exists($_config_file)) {
256 include($_config_file);
257 self::$have_config = true;
258 }
259 }
260
261 if (!self::$have_config) {
262 throw new NoConfigException("No configuration file found.",
263 $config_files);
264 }
265
266 // Fixup for statusnet.ini
267 $_db_name = substr($config['db']['database'], strrpos($config['db']['database'], '/') + 1);
268
269 if ($_db_name != 'statusnet' && !array_key_exists('ini_'.$_db_name, $config['db'])) {
270 $config['db']['ini_'.$_db_name] = INSTALLDIR.'/classes/statusnet.ini';
271 }
272
273 // Backwards compatibility
274
275 if (array_key_exists('memcached', $config)) {
276 if ($config['memcached']['enabled']) {
277 addPlugin('Memcache', array('servers' => $config['memcached']['server']));
278 }
279
280 if (!empty($config['memcached']['base'])) {
281 $config['cache']['base'] = $config['memcached']['base'];
282 }
283 }
284 }
285}
286
287class NoConfigException extends Exception
288{
289 public $config_files;
290
291 function __construct($msg, $config_files) {
292 parent::__construct($msg);
293 $this->config_files = $config_files;
294 }
295}
  
3030
3131require_once 'Stomp.php';
3232
33class LiberalStomp extends Stomp
34{
35 function getSocket()
36 {
37 return $this->_socket;
38 }
39}
4033
41class StompQueueManager
34class StompQueueManager extends QueueManager
4235{
4336 var $server = null;
4437 var $username = null;
4538 var $password = null;
4639 var $base = null;
4740 var $con = null;
41
42 protected $master = null;
43 protected $sites = array();
4844
4945 function __construct()
5046 {
47 parent::__construct();
5148 $this->server = common_config('queue', 'stomp_server');
5249 $this->username = common_config('queue', 'stomp_username');
5350 $this->password = common_config('queue', 'stomp_password');
5451 $this->base = common_config('queue', 'queue_basename');
5552 }
5653
57 function _connect()
54 /**
55 * Tell the i/o master we only need a single instance to cover
56 * all sites running in this process.
57 */
58 public static function multiSite()
5859 {
59 if (empty($this->con)) {
60 $this->_log(LOG_INFO, "Connecting to '$this->server' as '$this->username'...");
61 $this->con = new LiberalStomp($this->server);
60 return IoManager::INSTANCE_PER_PROCESS;
61 }
6262
63 if ($this->con->connect($this->username, $this->password)) {
64 $this->_log(LOG_INFO, "Connected.");
65 } else {
66 $this->_log(LOG_ERR, 'Failed to connect to queue server');
67 throw new ServerException('Failed to connect to queue server');
68 }
69 }
63 /**
64 * Record each site we'll be handling input for in this process,
65 * so we can listen to the necessary queues for it.
66 *
67 * @fixme possibly actually do subscription here to save another
68 * loop over all sites later?
69 */
70 public function addSite($server)
71 {
72 $this->sites[] = $server;
7073 }
7174
72 function enqueue($object, $queue)
75 /**
76 * Saves a notice object reference into the queue item table.
77 * @return boolean true on success
78 */
79 public function enqueue($object, $queue)
7380 {
7481 $notice = $object;
7582
8484
8585 // XXX: serialize and send entire notice
8686
87 $result = $this->con->send($this->_queueName($queue),
87 $result = $this->con->send($this->queueName($queue),
8888 $notice->id, // BODY of the message
8989 array ('created' => $notice->created));
9090
9595
9696 common_log(LOG_DEBUG, 'complete remote queueing notice ID = '
9797 . $notice->id . ' for ' . $queue);
98 $this->stats('enqueued', $queue);
9899 }
99100
100 function service($queue, $handler)
101 /**
102 * Send any sockets we're listening on to the IO manager
103 * to wait for input.
104 *
105 * @return array of resources
106 */
107 public function getSockets()
101108 {
102 $result = null;
109 return array($this->con->getSocket());
110 }
103111
104 $this->_connect();
112 /**
113 * We've got input to handle on our socket!
114 * Read any waiting Stomp frame(s) and process them.
115 *
116 * @param resource $socket
117 * @return boolean ok on success
118 */
119 public function handleInput($socket)
120 {
121 assert($socket === $this->con->getSocket());
122 $ok = true;
123 $frames = $this->con->readFrames();
124 foreach ($frames as $frame) {
125 $ok = $ok && $this->_handleNotice($frame);
126 }
127 return $ok;
128 }
105129
106 $this->con->setReadTimeout($handler->timeout());
130 /**
131 * Initialize our connection and subscribe to all the queues
132 * we're going to need to handle...
133 *
134 * Side effects: in multi-site mode, may reset site configuration.
135 *
136 * @param IoMaster $master process/event controller
137 * @return bool return false on failure
138 */
139 public function start($master)
140 {
141 parent::start($master);
142 if ($this->sites) {
143 foreach ($this->sites as $server) {
144 StatusNet::init($server);
145 $this->doSubscribe();
146 }
147 } else {
148 $this->doSubscribe();
149 }
150 return true;
151 }
152
153 /**
154 * Subscribe to all the queues we're going to need to handle...
155 *
156 * Side effects: in multi-site mode, may reset site configuration.
157 *
158 * @return bool return false on failure
159 */
160 public function finish()
161 {
162 if ($this->sites) {
163 foreach ($this->sites as $server) {
164 StatusNet::init($server);
165 $this->doUnsubscribe();
166 }
167 } else {
168 $this->doUnsubscribe();
169 }
170 return true;
171 }
172
173 /**
174 * Lazy open connection to Stomp queue server.
175 */
176 protected function _connect()
177 {
178 if (empty($this->con)) {
179 $this->_log(LOG_INFO, "Connecting to '$this->server' as '$this->username'...");
180 $this->con = new LiberalStomp($this->server);
107181
108 $this->con->subscribe($this->_queueName($queue));
182 if ($this->con->connect($this->username, $this->password)) {
183 $this->_log(LOG_INFO, "Connected.");
184 } else {
185 $this->_log(LOG_ERR, 'Failed to connect to queue server');
186 throw new ServerException('Failed to connect to queue server');
187 }
188 }
189 }
109190
110 while (true) {
191 /**
192 * Subscribe to all enabled notice queues for the current site.
193 */
194 protected function doSubscribe()
195 {
196 $this->_connect();
197 foreach ($this->getQueues() as $queue) {
198 $rawqueue = $this->queueName($queue);
199 $this->_log(LOG_INFO, "Subscribing to $rawqueue");
200 $this->con->subscribe($rawqueue);
201 }
202 }
203
204 /**
205 * Subscribe from all enabled notice queues for the current site.
206 */
207 protected function doUnsubscribe()
208 {
209 $this->_connect();
210 foreach ($this->getQueues() as $queue) {
211 $this->con->unsubscribe($this->queueName($queue));
212 }
213 }
111214
112 // Wait for something on one of our sockets
215 /**
216 * Handle and acknowledge a notice event that's come in through a queue.
217 *
218 * If the queue handler reports failure, the message is requeued for later.
219 * Missing notices or handler classes will drop the message.
220 *
221 * Side effects: in multi-site mode, may reset site configuration to
222 * match the site that queued the event.
223 *
224 * @param StompFrame $frame
225 * @return bool
226 */
227 protected function _handleNotice($frame)
228 {
229 list($site, $queue) = $this->parseDestination($frame->headers['destination']);
230 if ($site != common_config('site', 'server')) {
231 $this->stats('switch');
232 StatusNet::init($site);
233 }
113234
114 $stompsock = $this->con->getSocket();
235 $id = intval($frame->body);
236 $info = "notice $id posted at {$frame->headers['created']} in queue $queue";
115237
116 $handsocks = $handler->getSockets();
238 $notice = Notice::staticGet('id', $id);
239 if (empty($notice)) {
240 $this->_log(LOG_WARNING, "Skipping missing $info");
241 $this->con->ack($frame);
242 $this->stats('badnotice', $queue);
243 return false;
244 }
117245
118 $socks = array_merge(array($stompsock), $handsocks);
246 $handler = $this->getHandler($queue);
247 if (!$handler) {
248 $this->_log(LOG_ERROR, "Missing handler class; skipping $info");
249 $this->con->ack($frame);
250 $this->stats('badhandler', $queue);
251 return false;
252 }
119253
120 $read = $socks;
121 $write = array();
122 $except = array();
254 $ok = $handler->handle_notice($notice);
123255
124 $ready = stream_select($read, $write, $except, $handler->timeout(), 0);
125
126 if ($ready === false) {
127 $this->_log(LOG_ERR, "Error selecting on sockets");
128 } else if ($ready > 0) {
129 if (in_array($stompsock, $read)) {
130 $this->_handleNotice($queue, $handler);
131 }
132 $handler->idle(QUEUE_HANDLER_HIT_IDLE);
133 }
256 if (!$ok) {
257 $this->_log(LOG_WARNING, "Failed handling $info");
258 // FIXME we probably shouldn't have to do
259 // this kind of queue management ourselves;
260 // if we don't ack, it should resend...
261 $this->con->ack($frame);
262 $this->enqueue($notice, $queue);
263 $this->stats('requeued', $queue);
264 return false;
134265 }
135266
136 $this->con->unsubscribe($this->_queueName($queue));
267 $this->_log(LOG_INFO, "Successfully handled $info");
268 $this->con->ack($frame);
269 $this->stats('handled', $queue);
270 return true;
137271 }
138272
139 function _handleNotice($queue, $handler)
273 /**
274 * Combines the queue_basename from configuration with the
275 * site server name and queue name to give eg:
276 *
277 * /queue/statusnet/identi.ca/sms
278 *
279 * @param string $queue
280 * @return string
281 */
282 protected function queueName($queue)
140283 {
141 $frame = $this->con->readFrame();
142
143 if (!empty($frame)) {
144 $notice = Notice::staticGet('id', $frame->body);
145
146 if (empty($notice)) {
147 $this->_log(LOG_WARNING, 'Got ID '. $frame->body .' for non-existent notice in queue '. $queue);
148 $this->con->ack($frame);
149 } else {
150 if ($handler->handle_notice($notice)) {
151 $this->_log(LOG_INFO, 'Successfully handled notice '. $notice->id .' posted at ' . $frame->headers['created'] . ' in queue '. $queue);
152 $this->con->ack($frame);
153 } else {
154 $this->_log(LOG_WARNING, 'Failed handling notice '. $notice->id .' posted at ' . $frame->headers['created'] . ' in queue '. $queue);
155 // FIXME we probably shouldn't have to do
156 // this kind of queue management ourselves
157 $this->con->ack($frame);
158 $this->enqueue($notice, $queue);
159 }
160 unset($notice);
161 }
162
163 unset($frame);
164 }
284 return common_config('queue', 'queue_basename') .
285 common_config('site', 'server') . '/' . $queue;
165286 }
166287
167 function _queueName($queue)
288 /**
289 * Returns the site and queue name from the server-side queue.
290 *
291 * @param string queue destination (eg '/queue/statusnet/identi.ca/sms')
292 * @return array of site and queue: ('identi.ca','sms') or false if unrecognized
293 */
294 protected function parseDestination($dest)
168295 {
169 return common_config('queue', 'queue_basename') . $queue;
296 $prefix = common_config('queue', 'queue_basename');
297 if (substr($dest, 0, strlen($prefix)) == $prefix) {
298 $rest = substr($dest, strlen($prefix));
299 return explode("/", $rest, 2);
300 } else {
301 common_log(LOG_ERR, "Got a message from unrecognized stomp queue: $dest");
302 return array(false, false);
303 }
170304 }
171305
172306 function _log($level, $msg)
  
2323 * @package StatusNet
2424 * @author Evan Prodromou <evan@status.net>
2525 * @author Sarven Capadisli <csarven@status.net>
26 * @author Brion Vibber <brion@status.net>
2627 * @copyright 2009 StatusNet, Inc.
2728 * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0
2829 * @link http://status.net/
2930 */
3031
31class UnQueueManager
32class UnQueueManager extends QueueManager
3233{
34
35 /**
36 * Dummy queue storage manager: instead of saving events for later,
37 * we just process them immediately. This is only suitable for events
38 * that can be processed quickly and don't need polling or long-running
39 * connections to another server such as XMPP.
40 *
41 * @param Notice $object
42 * @param string $queue
43 */
3344 function enqueue($object, $queue)
3445 {
3546 $notice = $object;
36
37 switch ($queue)
38 {
39 case 'omb':
40 if ($this->_isLocal($notice)) {
41 require_once(INSTALLDIR.'/lib/omb.php');
42 omb_broadcast_notice($notice);
43 }
44 break;
45 case 'public':
46 if ($this->_isLocal($notice)) {
47 require_once(INSTALLDIR.'/lib/jabber.php');
48 jabber_public_notice($notice);
49 }
50 break;
51 case 'ping':
52 if ($this->_isLocal($notice)) {
53 require_once INSTALLDIR . '/lib/ping.php';
54 return ping_broadcast_notice($notice);
55 }
56 case 'sms':
57 require_once(INSTALLDIR.'/lib/mail.php');
58 mail_broadcast_notice_sms($notice);
59 break;
60 case 'jabber':
61 require_once(INSTALLDIR.'/lib/jabber.php');
62 jabber_broadcast_notice($notice);
63 break;
64 case 'plugin':
65 Event::handle('HandleQueuedNotice', array(&$notice));
66 break;
67 default:
47
48 $handler = $this->getHandler($queue);
49 if ($handler) {
50 $handler->handle_notice($notice);
51 } else {
6852 if (Event::handle('UnqueueHandleNotice', array(&$notice, $queue))) {
6953 throw new ServerException("UnQueueManager: Unknown queue: $queue");
7054 }
7155 }
72 }
73
74 function _isLocal($notice)
75 {
76 return ($notice->is_local == Notice::LOCAL_PUBLIC ||
77 $notice->is_local == Notice::LOCAL_NONPUBLIC);
7856 }
7957}
  
11321132function common_request_id()
11331133{
11341134 $pid = getmypid();
1135 $server = common_config('site', 'server');
11351136 if (php_sapi_name() == 'cli') {
1136 return $pid;
1137 return "$server:$pid";
11371138 } else {
11381139 static $req_id = null;
11391140 if (!isset($req_id)) {
11441144 $url = $_SERVER['REQUEST_URI'];
11451145 }
11461146 $method = $_SERVER['REQUEST_METHOD'];
1147 return "$pid.$req_id $method $url";
1147 return "$server:$pid.$req_id $method $url";
11481148 }
11491149}
11501150
  
1<?php
2/*
3 * StatusNet - the distributed open-source microblogging tool
4 * Copyright (C) 2008-2010 StatusNet, Inc.
5 *
6 * This program is free software: you can redistribute it and/or modify
7 * it under the terms of the GNU Affero General Public License as published by
8 * the Free Software Foundation, either version 3 of the License, or
9 * (at your option) any later version.
10 *
11 * This program is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 * GNU Affero General Public License for more details.
15 *
16 * You should have received a copy of the GNU Affero General Public License
17 * along with this program. If not, see <http://www.gnu.org/licenses/>.
18 */
19
20if (!defined('STATUSNET') && !defined('LACONICA')) {
21 exit(1);
22}
23
24/**
25 * Event handler for pushing new confirmations to Jabber users.
26 * @fixme recommend redoing this on a queue-trigger model
27 * @fixme expiration of old items got dropped in the past, put it back?
28 */
29class XmppConfirmManager extends IoManager
30{
31
32 /**
33 * @return mixed XmppConfirmManager, or false if unneeded
34 */
35 public static function get()
36 {
37 if (common_config('xmpp', 'enabled')) {
38 $site = common_config('site', 'server');
39 return new XmppConfirmManager();
40 } else {
41 return false;
42 }
43 }
44
45 /**
46 * Tell the i/o master we need one instance for each supporting site
47 * being handled in this process.
48 */
49 public static function multiSite()
50 {
51 return IoManager::INSTANCE_PER_SITE;
52 }
53
54 function __construct()
55 {
56 $this->site = common_config('site', 'server');
57 }
58
59 /**
60 * 10 seconds? Really? That seems a bit frequent.
61 */
62 function pollInterval()
63 {
64 return 10;
65 }
66
67 /**
68 * Ping!
69 * @return boolean true if we found something
70 */
71 function poll()
72 {
73 $this->switchSite();
74 $confirm = $this->next_confirm();
75 if ($confirm) {
76 $this->handle_confirm($confirm);
77 return true;
78 } else {
79 return false;
80 }
81 }
82
83 protected function handle_confirm($confirm)
84 {
85 require_once INSTALLDIR . '/lib/jabber.php';
86
87 common_log(LOG_INFO, 'Sending confirmation for ' . $confirm->address);
88 $user = User::staticGet($confirm->user_id);
89 if (!$user) {
90 common_log(LOG_WARNING, 'Confirmation for unknown user ' . $confirm->user_id);
91 return;
92 }
93 $success = jabber_confirm_address($confirm->code,
94 $user->nickname,
95 $confirm->address);
96 if (!$success) {
97 common_log(LOG_ERR, 'Confirmation failed for ' . $confirm->address);
98 # Just let the claim age out; hopefully things work then
99 return;
100 } else {
101 common_log(LOG_INFO, 'Confirmation sent for ' . $confirm->address);
102 # Mark confirmation sent; need a dupe so we don't have the WHERE clause
103 $dupe = Confirm_address::staticGet('code', $confirm->code);
104 if (!$dupe) {
105 common_log(LOG_WARNING, 'Could not refetch confirm', __FILE__);
106 return;
107 }
108 $orig = clone($dupe);
109 $dupe->sent = $dupe->claimed;
110 $result = $dupe->update($orig);
111 if (!$result) {
112 common_log_db_error($dupe, 'UPDATE', __FILE__);
113 # Just let the claim age out; hopefully things work then
114 return;
115 }
116 }
117 return true;
118 }
119
120 protected function next_confirm()
121 {
122 $confirm = new Confirm_address();
123 $confirm->whereAdd('claimed IS null');
124 $confirm->whereAdd('sent IS null');
125 # XXX: eventually we could do other confirmations in the queue, too
126 $confirm->address_type = 'jabber';
127 $confirm->orderBy('modified DESC');
128 $confirm->limit(1);
129 if ($confirm->find(true)) {
130 common_log(LOG_INFO, 'Claiming confirmation for ' . $confirm->address);
131 # working around some weird DB_DataObject behaviour
132 $confirm->whereAdd(''); # clears where stuff
133 $original = clone($confirm);
134 $confirm->claimed = common_sql_now();
135 $result = $confirm->update($original);
136 if ($result) {
137 common_log(LOG_INFO, 'Succeeded in claim! '. $result);
138 return $confirm;
139 } else {
140 common_log(LOG_INFO, 'Failed in claim!');
141 return false;
142 }
143 }
144 return null;
145 }
146
147 protected function clear_old_confirm_claims()
148 {
149 $confirm = new Confirm();
150 $confirm->claimed = null;
151 $confirm->whereAdd('now() - claimed > '.CLAIM_TIMEOUT);
152 $confirm->update(DB_DATAOBJECT_WHEREADD_ONLY);
153 $confirm->free();
154 unset($confirm);
155 }
156
157 /**
158 * Make sure we're on the right site configuration
159 */
160 protected function switchSite()
161 {
162 if ($this->site != common_config('site', 'server')) {
163 common_log(LOG_DEBUG, __METHOD__ . ": switching to site $this->site");
164 $this->stats('switch');
165 StatusNet::init($this->site);
166 }
167 }
168}
  
1<?php
2/*
3 * StatusNet - the distributed open-source microblogging tool
4 * Copyright (C) 2008, 2009, StatusNet, Inc.
5 *
6 * This program is free software: you can redistribute it and/or modify
7 * it under the terms of the GNU Affero General Public License as published by
8 * the Free Software Foundation, either version 3 of the License, or
9 * (at your option) any later version.
10 *
11 * This program is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 * GNU Affero General Public License for more details.
15 *
16 * You should have received a copy of the GNU Affero General Public License
17 * along with this program. If not, see <http://www.gnu.org/licenses/>.
18 */
19
20if (!defined('STATUSNET') && !defined('LACONICA')) { exit(1); }
21
22/**
23 * XMPP background connection manager for XMPP-using queue handlers,
24 * allowing them to send outgoing messages on the right connection.
25 *
26 * Input is handled during socket select loop, keepalive pings during idle.
27 * Any incoming messages will be forwarded to the main XmppDaemon process,
28 * which handles direct user interaction.
29 *
30 * In a multi-site queuedaemon.php run, one connection will be instantiated
31 * for each site being handled by the current process that has XMPP enabled.
32 */
33
34class XmppManager extends IoManager
35{
36 protected $site = null;
37 protected $pingid = 0;
38 protected $lastping = null;
39
40 static protected $singletons = array();
41
42 const PING_INTERVAL = 120;
43
44 /**
45 * Fetch the singleton XmppManager for the current site.
46 * @return mixed XmppManager, or false if unneeded
47 */
48 public static function get()
49 {
50 if (common_config('xmpp', 'enabled')) {
51 $site = common_config('site', 'server');
52 if (empty(self::$singletons[$site])) {
53 self::$singletons[$site] = new XmppManager();
54 }
55 return self::$singletons[$site];
56 } else {
57 return false;
58 }
59 }
60
61 /**
62 * Tell the i/o master we need one instance for each supporting site
63 * being handled in this process.
64 */
65 public static function multiSite()
66 {
67 return IoManager::INSTANCE_PER_SITE;
68 }
69
70 function __construct()
71 {
72 $this->site = common_config('site', 'server');
73 }
74
75 /**
76 * Initialize connection to server.
77 * @return boolean true on success
78 */
79 public function start($master)
80 {
81 parent::start($master);
82 $this->switchSite();
83
84 require_once "lib/jabber.php";
85
86 # Low priority; we don't want to receive messages
87
88 common_log(LOG_INFO, "INITIALIZE");
89 $this->conn = jabber_connect($this->resource());
90
91 if (empty($this->conn)) {
92 common_log(LOG_ERR, "Couldn't connect to server.");
93 return false;
94 }
95
96 $this->conn->addEventHandler('message', 'forward_message', $this);
97 $this->conn->addEventHandler('reconnect', 'handle_reconnect', $this);
98 $this->conn->setReconnectTimeout(600);
99 jabber_send_presence("Send me a message to post a notice", 'available', null, 'available', -1);
100
101 return !is_null($this->conn);
102 }
103
104 /**
105 * Message pump is triggered on socket input, so we only need an idle()
106 * call often enough to trigger our outgoing pings.
107 */
108 function timeout()
109 {
110 return self::PING_INTERVAL;
111 }
112
113 /**
114 * Lists the XMPP connection socket to allow i/o master to wake
115 * when input comes in here as well as from the queue source.
116 *
117 * @return array of resources
118 */
119 public function getSockets()
120 {
121 return array($this->conn->getSocket());
122 }
123
124 /**
125 * Process XMPP events that have come in over the wire.
126 * Side effects: may switch site configuration
127 * @fixme may kill process on XMPP error
128 * @param resource $socket
129 */
130 public function handleInput($socket)
131 {
132 $this->switchSite();
133
134 # Process the queue for as long as needed
135 try {
136 if ($this->conn) {
137 assert($socket === $this->conn->getSocket());
138
139 common_log(LOG_DEBUG, "Servicing the XMPP queue.");
140 $this->stats('xmpp_process');
141 $this->conn->processTime(0);
142 }
143 } catch (XMPPHP_Exception $e) {
144 common_log(LOG_ERR, "Got an XMPPHP_Exception: " . $e->getMessage());
145 die($e->getMessage());
146 }
147 }
148
149 /**
150 * Idle processing for io manager's execution loop.
151 * Send keepalive pings to server.
152 *
153 * Side effect: kills process on exception from XMPP library.
154 *
155 * @fixme non-dying error handling
156 */
157 public function idle($timeout=0)
158 {
159 if ($this->conn) {
160 $now = time();
161 if (empty($this->lastping) || $now - $this->lastping > self::PING_INTERVAL) {
162 $this->switchSite();
163 try {
164 $this->sendPing();
165 $this->lastping = $now;
166 } catch (XMPPHP_Exception $e) {
167 common_log(LOG_ERR, "Got an XMPPHP_Exception: " . $e->getMessage());
168 die($e->getMessage());
169 }
170 }
171 }
172 }
173
174 /**
175 * Send a keepalive ping to the XMPP server.
176 */
177 protected function sendPing()
178 {
179 $jid = jabber_daemon_address().'/'.$this->resource();
180 $server = common_config('xmpp', 'server');
181
182 if (!isset($this->pingid)) {
183 $this->pingid = 0;
184 } else {
185 $this->pingid++;
186 }
187
188 common_log(LOG_DEBUG, "Sending ping #{$this->pingid}");
189
190 $this->conn->send("<iq from='{$jid}' to='{$server}' id='ping_{$this->pingid}' type='get'><ping xmlns='urn:xmpp:ping'/></iq>");
191 }
192
193 /**
194 * Callback for Jabber reconnect event
195 * @param $pl
196 */
197 function handle_reconnect(&$pl)
198 {
199 common_log(LOG_NOTICE, 'XMPP reconnected');
200
201 $this->conn->processUntil('session_start');
202 $this->conn->presence(null, 'available', null, 'available', -1);
203 }
204
205 /**
206 * Callback for Jabber message event.
207 *
208 * This connection handles output; if we get a message straight to us,
209 * forward it on to our XmppDaemon listener for processing.
210 *
211 * @param $pl
212 */
213 function forward_message(&$pl)
214 {
215 if ($pl['type'] != 'chat') {
216 common_log(LOG_DEBUG, 'Ignoring message of type ' . $pl['type'] . ' from ' . $pl['from']);
217 return;
218 }
219 $listener = $this->listener();
220 if (strtolower($listener) == strtolower($pl['from'])) {
221 common_log(LOG_WARNING, 'Ignoring loop message.');
222 return;
223 }
224 common_log(LOG_INFO, 'Forwarding message from ' . $pl['from'] . ' to ' . $listener);
225 $this->conn->message($this->listener(), $pl['body'], 'chat', null, $this->ofrom($pl['from']));
226 }
227
228 /**
229 * Build an <addresses> block with an ofrom entry for forwarded messages
230 *
231 * @param string $from Jabber ID of original sender
232 * @return string XML fragment
233 */
234 protected function ofrom($from)
235 {
236 $address = "<addresses xmlns='http://jabber.org/protocol/address'>\n";
237 $address .= "<address type='ofrom' jid='$from' />\n";
238 $address .= "</addresses>\n";
239 return $address;
240 }
241
242 /**
243 * Build the complete JID of the XmppDaemon process which
244 * handles primary XMPP input for this site.
245 *
246 * @return string Jabber ID
247 */
248 protected function listener()
249 {
250 if (common_config('xmpp', 'listener')) {
251 return common_config('xmpp', 'listener');
252 } else {
253 return jabber_daemon_address() . '/' . common_config('xmpp','resource') . 'daemon';
254 }
255 }
256
257 protected function resource()
258 {
259 return 'queue' . posix_getpid(); // @fixme PIDs won't be host-unique
260 }
261
262 /**
263 * Make sure we're on the right site configuration
264 */
265 protected function switchSite()
266 {
267 if ($this->site != common_config('site', 'server')) {
268 common_log(LOG_DEBUG, __METHOD__ . ": switching to site $this->site");
269 $this->stats('switch');
270 StatusNet::init($this->site);
271 }
272 }
273}
  
1<?php
2/*
3 * StatusNet - the distributed open-source microblogging tool
4 * Copyright (C) 2008, 2009, StatusNet, Inc.
5 *
6 * This program is free software: you can redistribute it and/or modify
7 * it under the terms of the GNU Affero General Public License as published by
8 * the Free Software Foundation, either version 3 of the License, or
9 * (at your option) any later version.
10 *
11 * This program is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 * GNU Affero General Public License for more details.
15 *
16 * You should have received a copy of the GNU Affero General Public License
17 * along with this program. If not, see <http://www.gnu.org/licenses/>.
18 */
19
20if (!defined('STATUSNET') && !defined('LACONICA')) { exit(1); }
21
22require_once(INSTALLDIR.'/lib/queuehandler.php');
23
24define('PING_INTERVAL', 120);
25
26/**
27 * Common superclass for all XMPP-using queue handlers. They all need to
28 * service their message queues on idle, and forward any incoming messages
29 * to the XMPP listener connection. So, we abstract out common code to a
30 * superclass.
31 */
32
33class XmppQueueHandler extends QueueHandler
34{
35 var $pingid = 0;
36 var $lastping = null;
37
38 function start()
39 {
40 # Low priority; we don't want to receive messages
41
42 $this->log(LOG_INFO, "INITIALIZE");
43 $this->conn = jabber_connect($this->_id.$this->transport());
44
45 if (empty($this->conn)) {
46 $this->log(LOG_ERR, "Couldn't connect to server.");
47 return false;
48 }
49
50 $this->conn->addEventHandler('message', 'forward_message', $this);
51 $this->conn->addEventHandler('reconnect', 'handle_reconnect', $this);
52 $this->conn->setReconnectTimeout(600);
53 jabber_send_presence("Send me a message to post a notice", 'available', null, 'available', -1);
54
55 return !is_null($this->conn);
56 }
57
58 function timeout()
59 {
60 return 10;
61 }
62
63 function handle_reconnect(&$pl)
64 {
65 $this->log(LOG_NOTICE, 'reconnected');
66
67 $this->conn->processUntil('session_start');
68 $this->conn->presence(null, 'available', null, 'available', -1);
69 }
70
71 function idle($timeout=0)
72 {
73 # Process the queue for as long as needed
74 try {
75 if ($this->conn) {
76 $this->log(LOG_DEBUG, "Servicing the XMPP queue.");
77 $this->conn->processTime($timeout);
78 $now = time();
79 if (empty($this->lastping) || $now - $this->lastping > PING_INTERVAL) {
80 $this->sendPing();
81 $this->lastping = $now;
82 }
83 }
84 } catch (XMPPHP_Exception $e) {
85 $this->log(LOG_ERR, "Got an XMPPHP_Exception: " . $e->getMessage());
86 die($e->getMessage());
87 }
88 }
89
90 function sendPing()
91 {
92 $jid = jabber_daemon_address().'/'.$this->_id.$this->transport();
93 $server = common_config('xmpp', 'server');
94
95 if (!isset($this->pingid)) {
96 $this->pingid = 0;
97 } else {
98 $this->pingid++;
99 }
100
101 $this->log(LOG_DEBUG, "Sending ping #{$this->pingid}");
102
103 $this->conn->send("<iq from='{$jid}' to='{$server}' id='ping_{$this->pingid}' type='get'><ping xmlns='urn:xmpp:ping'/></iq>");
104 }
105
106 function forward_message(&$pl)
107 {
108 if ($pl['type'] != 'chat') {
109 $this->log(LOG_DEBUG, 'Ignoring message of type ' . $pl['type'] . ' from ' . $pl['from']);
110 return;
111 }
112 $listener = $this->listener();
113 if (strtolower($listener) == strtolower($pl['from'])) {
114 $this->log(LOG_WARNING, 'Ignoring loop message.');
115 return;
116 }
117 $this->log(LOG_INFO, 'Forwarding message from ' . $pl['from'] . ' to ' . $listener);
118 $this->conn->message($this->listener(), $pl['body'], 'chat', null, $this->ofrom($pl['from']));
119 }
120
121 function ofrom($from)
122 {
123 $address = "<addresses xmlns='http://jabber.org/protocol/address'>\n";
124 $address .= "<address type='ofrom' jid='$from' />\n";
125 $address .= "</addresses>\n";
126 return $address;
127 }
128
129 function listener()
130 {
131 if (common_config('xmpp', 'listener')) {
132 return common_config('xmpp', 'listener');
133 } else {
134 return jabber_daemon_address() . '/' . common_config('xmpp','resource') . 'daemon';
135 }
136 }
137
138 function getSockets()
139 {
140 return array($this->conn->getSocket());
141 }
142}
  
1This doesn't seem to have been functional for a while; can't find other references
2to the enjit configuration or transport enqueuing. Keeping it in case someone
3wants to bring it up to date.
4
5-- brion vibber <brion@status.net> 2009-12-03
  
1<?php
2/*
3 * StatusNet - the distributed open-source microblogging tool
4 * Copyright (C) 2008, 2009, StatusNet, Inc.
5 *
6 * This program is free software: you can redistribute it and/or modify
7 * it under the terms of the GNU Affero General Public License as published by
8 * the Free Software Foundation, either version 3 of the License, or
9 * (at your option) any later version.
10 *
11 * This program is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 * GNU Affero General Public License for more details.
15 *
16 * You should have received a copy of the GNU Affero General Public License
17 * along with this program. If not, see <http://www.gnu.org/licenses/>.
18 */
19
20if (!defined('STATUSNET') && !defined('LACONICA')) {
21 exit(1);
22}
23
24/**
25 * Queue handler for watching new notices and posting to enjit.
26 * @fixme is this actually being used/functional atm?
27 */
28class EnjitQueueHandler extends QueueHandler
29{
30 function transport()
31 {
32 return 'enjit';
33 }
34
35 function start()
36 {
37 $this->log(LOG_INFO, "Starting EnjitQueueHandler");
38 $this->log(LOG_INFO, "Broadcasting to ".common_config('enjit', 'apiurl'));
39 return true;
40 }
41
42 function handle_notice($notice)
43 {
44
45 $profile = Profile::staticGet($notice->profile_id);
46
47 $this->log(LOG_INFO, "Posting Notice ".$notice->id." from ".$profile->nickname);
48
49 if ( ! $notice->is_local ) {
50 $this->log(LOG_INFO, "Skipping remote notice");
51 return "skipped";
52 }
53
54 #
55 # Build an Atom message from the notice
56 #
57 $noticeurl = common_local_url('shownotice', array('notice' => $notice->id));
58 $msg = $profile->nickname . ': ' . $notice->content;
59
60 $atom = "<entry xmlns='http://www.w3.org/2005/Atom'>\n";
61 $atom .= "<apisource>".common_config('enjit','source')."</apisource>\n";
62 $atom .= "<source>\n";
63 $atom .= "<title>" . $profile->nickname . " - " . common_config('site', 'name') . "</title>\n";
64 $atom .= "<link href='" . $profile->profileurl . "'/>\n";
65 $atom .= "<link rel='self' type='application/rss+xml' href='" . common_local_url('userrss', array('nickname' => $profile->nickname)) . "'/>\n";
66 $atom .= "<author><name>" . $profile->nickname . "</name></author>\n";
67 $atom .= "<icon>" . $profile->avatarUrl(AVATAR_PROFILE_SIZE) . "</icon>\n";
68 $atom .= "</source>\n";
69 $atom .= "<title>" . htmlspecialchars($msg) . "</title>\n";
70 $atom .= "<summary>" . htmlspecialchars($msg) . "</summary>\n";
71 $atom .= "<link rel='alternate' href='" . $noticeurl . "' />\n";
72 $atom .= "<id>". $notice->uri . "</id>\n";
73 $atom .= "<published>".common_date_w3dtf($notice->created)."</published>\n";
74 $atom .= "<updated>".common_date_w3dtf($notice->modified)."</updated>\n";
75 $atom .= "</entry>\n";
76
77 $url = common_config('enjit', 'apiurl') . "/submit/". common_config('enjit','apikey');
78 $data = array(
79 'msg' => $atom,
80 );
81
82 #
83 # POST the message to $config['enjit']['apiurl']
84 #
85 $request = HTTPClient::start();
86 $response = $request->post($url, null, $data);
87
88 return $response->isOk();
89 }
90
91}
  
114114 case 'FBCSettingsNav':
115115 include_once INSTALLDIR . '/plugins/Facebook/FBCSettingsNav.php';
116116 return false;
117 case 'FacebookQueueHandler':
118 include_once INSTALLDIR . '/plugins/Facebook/facebookqueuehandler.php';
119 return false;
117120 default:
118121 return true;
119122 }
511511 }
512512
513513 /**
514 * broadcast the message when not using queuehandler
514 * Register Facebook notice queue handler
515515 *
516 * @param Notice &$notice the notice
517 * @param array $queue destination queue
516 * @param QueueManager $manager
518517 *
519518 * @return boolean hook return
520519 */
521
522 function onUnqueueHandleNotice(&$notice, $queue)
520 function onEndInitializeQueueManager($manager)
523521 {
524 if (($queue == 'facebook') && ($this->_isLocal($notice))) {
525 facebookBroadcastNotice($notice);
526 return false;
527 }
528 return true;
529 }
530
531 /**
532 * Determine whether the notice was locally created
533 *
534 * @param Notice $notice the notice
535 *
536 * @return boolean locality
537 */
538
539 function _isLocal($notice)
540 {
541 return ($notice->is_local == Notice::LOCAL_PUBLIC ||
542 $notice->is_local == Notice::LOCAL_NONPUBLIC);
543 }
544
545 /**
546 * Add Facebook queuehandler to the list of daemons to start
547 *
548 * @param array $daemons the list fo daemons to run
549 *
550 * @return boolean hook return
551 *
552 */
553
554 function onGetValidDaemons($daemons)
555 {
556 array_push($daemons, INSTALLDIR .
557 '/plugins/Facebook/facebookqueuehandler.php');
522 $manager->connect('facebook', 'FacebookQueueHandler');
558523 return true;
559524 }
560525
  
1#!/usr/bin/env php
21<?php
32/*
43 * StatusNet - the distributed open-source microblogging tool
1717 * along with this program. If not, see <http://www.gnu.org/licenses/>.
1818 */
1919
20define('INSTALLDIR', realpath(dirname(__FILE__) . '/../..'));
20if (!defined('STATUSNET') && !defined('LACONICA')) { exit(1); }
2121
22$shortoptions = 'i::';
23$longoptions = array('id::');
24
25$helptext = <<<END_OF_FACEBOOK_HELP
26Daemon script for pushing new notices to Facebook.
27
28 -i --id Identity (default none)
29
30END_OF_FACEBOOK_HELP;
31
32require_once INSTALLDIR . '/scripts/commandline.inc';
3322require_once INSTALLDIR . '/plugins/Facebook/facebookutil.php';
34require_once INSTALLDIR . '/lib/queuehandler.php';
3523
3624class FacebookQueueHandler extends QueueHandler
3725{
2828 return 'facebook';
2929 }
3030
31 function start()
31 function handle_notice($notice)
3232 {
33 $this->log(LOG_INFO, "INITIALIZE");
33 if ($this->_isLocal($notice)) {
34 return facebookBroadcastNotice($notice);
35 }
3436 return true;
3537 }
3638
37 function handle_notice($notice)
39 /**
40 * Determine whether the notice was locally created
41 *
42 * @param Notice $notice the notice
43 *
44 * @return boolean locality
45 */
46 function _isLocal($notice)
3847 {
39 return facebookBroadcastNotice($notice);
48 return ($notice->is_local == Notice::LOCAL_PUBLIC ||
49 $notice->is_local == Notice::LOCAL_NONPUBLIC);
4050 }
41
42 function finish()
43 {
44 }
45
4651}
47
48if (have_option('i')) {
49 $id = get_option_value('i');
50} else if (have_option('--id')) {
51 $id = get_option_value('--id');
52} else if (count($args) > 0) {
53 $id = $args[0];
54} else {
55 $id = null;
56}
57
58$handler = new FacebookQueueHandler($id);
59
60$handler->runOnce();
  
133133 return false;
134134 }
135135
136 function onStartCacheReconnect(&$success)
137 {
138 if (empty($this->_conn)) {
139 // nothing to do
140 return true;
141 }
142 if ($this->persistent) {
143 common_log(LOG_ERR, "Cannot close persistent memcached connection");
144 $success = false;
145 } else {
146 common_log(LOG_INFO, "Closing memcached connection");
147 $success = $this->_conn->close();
148 $this->_conn = null;
149 }
150 return false;
151 }
152
136153 /**
137154 * Ensure that a connection exists
138155 *
  
112112 strtolower(mb_substr($cls, 0, -6)) . '.php';
113113 return false;
114114 case 'TwitterOAuthClient':
115 include_once INSTALLDIR . '/plugins/TwitterBridge/twitteroauthclient.php';
115 case 'TwitterQueueHandler':
116 include_once INSTALLDIR . '/plugins/TwitterBridge/' .
117 strtolower($cls) . '.php';
116118 return false;
117119 default:
118120 return true;
141141 }
142142
143143 /**
144 * broadcast the message when not using queuehandler
145 *
146 * @param Notice &$notice the notice
147 * @param array $queue destination queue
148 *
149 * @return boolean hook return
150 */
151 function onUnqueueHandleNotice(&$notice, $queue)
152 {
153 if (($queue == 'twitter') && ($this->_isLocal($notice))) {
154 broadcast_twitter($notice);
155 return false;
156 }
157 return true;
158 }
159
160 /**
161 * Determine whether the notice was locally created
162 *
163 * @param Notice $notice
164 *
165 * @return boolean locality
166 */
167 function _isLocal($notice)
168 {
169 return ($notice->is_local == Notice::LOCAL_PUBLIC ||
170 $notice->is_local == Notice::LOCAL_NONPUBLIC);
171 }
172
173 /**
174144 * Add Twitter bridge daemons to the list of daemons to start
175145 *
176146 * @param array $daemons the list fo daemons to run
177147 *
178148 * @return boolean hook return
179 *
180149 */
181150 function onGetValidDaemons($daemons)
182151 {
183152 array_push($daemons, INSTALLDIR .
184 '/plugins/TwitterBridge/daemons/twitterqueuehandler.php');
185 array_push($daemons, INSTALLDIR .
186153 '/plugins/TwitterBridge/daemons/synctwitterfriends.php');
187154
188155 if (common_config('twitterimport', 'enabled')) {
157157 . '/plugins/TwitterBridge/daemons/twitterstatusfetcher.php');
158158 }
159159
160 return true;
161 }
162
163 /**
164 * Register Twitter notice queue handler
165 *
166 * @param QueueManager $manager
167 *
168 * @return boolean hook return
169 */
170 function onEndInitializeQueueManager($manager)
171 {
172 $manager->connect('twitter', 'TwitterQueueHandler');
160173 return true;
161174 }
162175
  
1#!/usr/bin/env php
2<?php
3/*
4 * StatusNet - the distributed open-source microblogging tool
5 * Copyright (C) 2008, 2009, StatusNet, Inc.
6 *
7 * This program is free software: you can redistribute it and/or modify
8 * it under the terms of the GNU Affero General Public License as published by
9 * the Free Software Foundation, either version 3 of the License, or
10 * (at your option) any later version.
11 *
12 * This program is distributed in the hope that it will be useful,
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 * GNU Affero General Public License for more details.
16 *
17 * You should have received a copy of the GNU Affero General Public License
18 * along with this program. If not, see <http://www.gnu.org/licenses/>.
19 */
20
21define('INSTALLDIR', realpath(dirname(__FILE__) . '/../../..'));
22
23$shortoptions = 'i::';
24$longoptions = array('id::');
25
26$helptext = <<<END_OF_ENJIT_HELP
27Daemon script for pushing new notices to Twitter.
28
29 -i --id Identity (default none)
30
31END_OF_ENJIT_HELP;
32
33require_once INSTALLDIR . '/scripts/commandline.inc';
34require_once INSTALLDIR . '/lib/queuehandler.php';
35require_once INSTALLDIR . '/plugins/TwitterBridge/twitter.php';
36
37class TwitterQueueHandler extends QueueHandler
38{
39 function transport()
40 {
41 return 'twitter';
42 }
43
44 function start()
45 {
46 $this->log(LOG_INFO, "INITIALIZE");
47 return true;
48 }
49
50 function handle_notice($notice)
51 {
52 return broadcast_twitter($notice);
53 }
54
55 function finish()
56 {
57 }
58
59}
60
61if (have_option('i')) {
62 $id = get_option_value('i');
63} else if (have_option('--id')) {
64 $id = get_option_value('--id');
65} else if (count($args) > 0) {
66 $id = $args[0];
67} else {
68 $id = null;
69}
70
71$handler = new TwitterQueueHandler($id);
72
73$handler->runOnce();
  
1<?php
2/*
3 * StatusNet - the distributed open-source microblogging tool
4 * Copyright (C) 2008, 2009, StatusNet, Inc.
5 *
6 * This program is free software: you can redistribute it and/or modify
7 * it under the terms of the GNU Affero General Public License as published by
8 * the Free Software Foundation, either version 3 of the License, or
9 * (at your option) any later version.
10 *
11 * This program is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 * GNU Affero General Public License for more details.
15 *
16 * You should have received a copy of the GNU Affero General Public License
17 * along with this program. If not, see <http://www.gnu.org/licenses/>.
18 */
19
20if (!defined('STATUSNET') && !defined('LACONICA')) { exit(1); }
21
22require_once INSTALLDIR . '/plugins/TwitterBridge/twitter.php';
23
24class TwitterQueueHandler extends QueueHandler
25{
26 function transport()
27 {
28 return 'twitter';
29 }
30
31 function handle_notice($notice)
32 {
33 return broadcast_twitter($notice);
34 }
35}
  
1#!/usr/bin/env php
2<?php
3/*
4 * StatusNet - the distributed open-source microblogging tool
5 * Copyright (C) 2008, 2009, StatusNet, Inc.
6 *
7 * This program is free software: you can redistribute it and/or modify
8 * it under the terms of the GNU Affero General Public License as published by
9 * the Free Software Foundation, either version 3 of the License, or
10 * (at your option) any later version.
11 *
12 * This program is distributed in the hope that it will be useful,
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 * GNU Affero General Public License for more details.
16 *
17 * You should have received a copy of the GNU Affero General Public License
18 * along with this program. If not, see <http://www.gnu.org/licenses/>.
19 */
20
21define('INSTALLDIR', realpath(dirname(__FILE__) . '/..'));
22
23$shortoptions = 'i::';
24$longoptions = array('id::');
25
26$helptext = <<<END_OF_ENJIT_HELP
27Daemon script for watching new notices and posting to enjit.
28
29 -i --id Identity (default none)
30
31END_OF_ENJIT_HELP;
32
33require_once INSTALLDIR.'/scripts/commandline.inc';
34
35require_once INSTALLDIR . '/lib/mail.php';
36require_once INSTALLDIR . '/lib/queuehandler.php';
37
38set_error_handler('common_error_handler');
39
40class EnjitQueueHandler extends QueueHandler
41{
42 function transport()
43 {
44 return 'enjit';
45 }
46
47 function start()
48 {
49 $this->log(LOG_INFO, "Starting EnjitQueueHandler");
50 $this->log(LOG_INFO, "Broadcasting to ".common_config('enjit', 'apiurl'));
51 return true;
52 }
53
54 function handle_notice($notice)
55 {
56
57 $profile = Profile::staticGet($notice->profile_id);
58
59 $this->log(LOG_INFO, "Posting Notice ".$notice->id." from ".$profile->nickname);
60
61 if ( ! $notice->is_local ) {
62 $this->log(LOG_INFO, "Skipping remote notice");
63 return "skipped";
64 }
65
66 #
67 # Build an Atom message from the notice
68 #
69 $noticeurl = common_local_url('shownotice', array('notice' => $notice->id));
70 $msg = $profile->nickname . ': ' . $notice->content;
71
72 $atom = "<entry xmlns='http://www.w3.org/2005/Atom'>\n";
73 $atom .= "<apisource>".common_config('enjit','source')."</apisource>\n";
74 $atom .= "<source>\n";
75 $atom .= "<title>" . $profile->nickname . " - " . common_config('site', 'name') . "</title>\n";
76 $atom .= "<link href='" . $profile->profileurl . "'/>\n";
77 $atom .= "<link rel='self' type='application/rss+xml' href='" . common_local_url('userrss', array('nickname' => $profile->nickname)) . "'/>\n";
78 $atom .= "<author><name>" . $profile->nickname . "</name></author>\n";
79 $atom .= "<icon>" . $profile->avatarUrl(AVATAR_PROFILE_SIZE) . "</icon>\n";
80 $atom .= "</source>\n";
81 $atom .= "<title>" . htmlspecialchars($msg) . "</title>\n";
82 $atom .= "<summary>" . htmlspecialchars($msg) . "</summary>\n";
83 $atom .= "<link rel='alternate' href='" . $noticeurl . "' />\n";
84 $atom .= "<id>". $notice->uri . "</id>\n";
85 $atom .= "<published>".common_date_w3dtf($notice->created)."</published>\n";
86 $atom .= "<updated>".common_date_w3dtf($notice->modified)."</updated>\n";
87 $atom .= "</entry>\n";
88
89 $url = common_config('enjit', 'apiurl') . "/submit/". common_config('enjit','apikey');
90 $data = array(
91 'msg' => $atom,
92 );
93
94 #
95 # POST the message to $config['enjit']['apiurl']
96 #
97 $request = HTTPClient::start();
98 $response = $request->post($url, null, $data);
99
100 return $response->isOk();
101 }
102
103}
104
105if (have_option('-i')) {
106 $id = get_option_value('-i');
107} else if (have_option('--id')) {
108 $id = get_option_value('--id');
109} else if (count($args) > 0) {
110 $id = $args[0];
111} else {
112 $id = null;
113}
114
115$handler = new EnjitQueueHandler($id);
116
117if ($handler->start()) {
118 $handler->handle_queue();
119}
120
121$handler->finish();
  
3737
3838$daemons = array();
3939
40$daemons[] = INSTALLDIR.'/scripts/pluginqueuehandler.php';
41$daemons[] = INSTALLDIR.'/scripts/ombqueuehandler.php';
42$daemons[] = INSTALLDIR.'/scripts/pingqueuehandler.php';
40$daemons[] = INSTALLDIR.'/scripts/queuedaemon.php';
4341
4442if(common_config('xmpp','enabled')) {
4543 $daemons[] = INSTALLDIR.'/scripts/xmppdaemon.php';
46 $daemons[] = INSTALLDIR.'/scripts/jabberqueuehandler.php';
47 $daemons[] = INSTALLDIR.'/scripts/publicqueuehandler.php';
48 $daemons[] = INSTALLDIR.'/scripts/xmppconfirmhandler.php';
49}
50
51if (common_config('sms', 'enabled')) {
52 $daemons[] = INSTALLDIR.'/scripts/smsqueuehandler.php';
5344}
5445
5546if (Event::handle('GetValidDaemons', array(&$daemons))) {
  
1#!/usr/bin/env php
2<?php
3/*
4 * StatusNet - the distributed open-source microblogging tool
5 * Copyright (C) 2008, 2009, StatusNet, Inc.
6 *
7 * This program is free software: you can redistribute it and/or modify
8 * it under the terms of the GNU Affero General Public License as published by
9 * the Free Software Foundation, either version 3 of the License, or
10 * (at your option) any later version.
11 *
12 * This program is distributed in the hope that it will be useful,
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 * GNU Affero General Public License for more details.
16 *
17 * You should have received a copy of the GNU Affero General Public License
18 * along with this program. If not, see <http://www.gnu.org/licenses/>.
19 */
20
21define('INSTALLDIR', realpath(dirname(__FILE__) . '/..'));
22
23$helptext = <<<END_OF_QUEUE_HELP
24USAGE: handlequeued.php <queue> <notice id>
25Run a single queued notice through background processing
26as if it were being run through the queue.
27
28
29END_OF_QUEUE_HELP;
30
31require_once INSTALLDIR.'/scripts/commandline.inc';
32
33if (count($args) != 2) {
34 show_help();
35}
36
37$queue = trim($args[0]);
38$noticeId = intval($args[1]);
39
40$qm = QueueManager::get();
41$handler = $qm->getHandler($queue);
42if (!$handler) {
43 print "No handler for queue '$queue'.\n";
44 exit(1);
45}
46
47$notice = Notice::staticGet('id', $noticeId);
48if (empty($notice)) {
49 print "Invalid notice id $noticeId\n";
50 exit(1);
51}
52
53if (!$handler->handle_notice($notice)) {
54 print "Failed to handle notice id $noticeId on queue '$queue'.\n";
55 exit(1);
56}
  
1#!/usr/bin/env php
2<?php
3/*
4 * StatusNet - the distributed open-source microblogging tool
5 * Copyright (C) 2008, 2009, StatusNet, Inc.
6 *
7 * This program is free software: you can redistribute it and/or modify
8 * it under the terms of the GNU Affero General Public License as published by
9 * the Free Software Foundation, either version 3 of the License, or
10 * (at your option) any later version.
11 *
12 * This program is distributed in the hope that it will be useful,
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 * GNU Affero General Public License for more details.
16 *
17 * You should have received a copy of the GNU Affero General Public License
18 * along with this program. If not, see <http://www.gnu.org/licenses/>.
19 */
20
21define('INSTALLDIR', realpath(dirname(__FILE__) . '/..'));
22
23$shortoptions = 'i::';
24$longoptions = array('id::');
25
26$helptext = <<<END_OF_JABBER_HELP
27Daemon script for pushing new notices to Jabber users.
28
29 -i --id Identity (default none)
30
31END_OF_JABBER_HELP;
32
33require_once INSTALLDIR.'/scripts/commandline.inc';
34
35require_once INSTALLDIR . '/lib/common.php';
36require_once INSTALLDIR . '/lib/jabber.php';
37require_once INSTALLDIR . '/lib/xmppqueuehandler.php';
38
39class JabberQueueHandler extends XmppQueueHandler
40{
41 var $conn = null;
42
43 function transport()
44 {
45 return 'jabber';
46 }
47
48 function handle_notice($notice)
49 {
50 try {
51 return jabber_broadcast_notice($notice);
52 } catch (XMPPHP_Exception $e) {
53 $this->log(LOG_ERR, "Got an XMPPHP_Exception: " . $e->getMessage());
54 exit(1);
55 }
56 }
57}
58
59// Abort immediately if xmpp is not enabled, otherwise the daemon chews up
60// lots of CPU trying to connect to unconfigured servers
61if (common_config('xmpp','enabled')==false) {
62 print "Aborting daemon - xmpp is disabled\n";
63 exit();
64}
65
66if (have_option('i')) {
67 $id = get_option_value('i');
68} else if (have_option('--id')) {
69 $id = get_option_value('--id');
70} else if (count($args) > 0) {
71 $id = $args[0];
72} else {
73 $id = null;
74}
75
76$handler = new JabberQueueHandler($id);
77
78$handler->runOnce();
  
1#!/usr/bin/env php
2<?php
3/*
4 * StatusNet - the distributed open-source microblogging tool
5 * Copyright (C) 2008, 2009, StatusNet, Inc.
6 *
7 * This program is free software: you can redistribute it and/or modify
8 * it under the terms of the GNU Affero General Public License as published by
9 * the Free Software Foundation, either version 3 of the License, or
10 * (at your option) any later version.
11 *
12 * This program is distributed in the hope that it will be useful,
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 * GNU Affero General Public License for more details.
16 *
17 * You should have received a copy of the GNU Affero General Public License
18 * along with this program. If not, see <http://www.gnu.org/licenses/>.
19 */
20
21define('INSTALLDIR', realpath(dirname(__FILE__) . '/..'));
22
23$shortoptions = 'i::';
24$longoptions = array('id::');
25
26$helptext = <<<END_OF_OMB_HELP
27Daemon script for pushing new notices to OpenMicroBlogging subscribers.
28
29 -i --id Identity (default none)
30
31END_OF_OMB_HELP;
32
33require_once INSTALLDIR.'/scripts/commandline.inc';
34
35require_once INSTALLDIR . '/lib/omb.php';
36require_once INSTALLDIR . '/lib/queuehandler.php';
37
38set_error_handler('common_error_handler');
39
40class OmbQueueHandler extends QueueHandler
41{
42
43 function transport()
44 {
45 return 'omb';
46 }
47
48 function start()
49 {
50 $this->log(LOG_INFO, "INITIALIZE");
51 return true;
52 }
53
54 function handle_notice($notice)
55 {
56 if ($this->is_remote($notice)) {
57 $this->log(LOG_DEBUG, 'Ignoring remote notice ' . $notice->id);
58 return true;
59 } else {
60 return omb_broadcast_notice($notice);
61 }
62 }
63
64 function finish()
65 {
66 }
67
68 function is_remote($notice)
69 {
70 $user = User::staticGet($notice->profile_id);
71 return is_null($user);
72 }
73}
74
75if (have_option('i')) {
76 $id = get_option_value('i');
77} else if (have_option('--id')) {
78 $id = get_option_value('--id');
79} else if (count($args) > 0) {
80 $id = $args[0];
81} else {
82 $id = null;
83}
84
85$handler = new OmbQueueHandler($id);
86
87$handler->runOnce();
  
1#!/usr/bin/env php
2<?php
3/*
4 * StatusNet - the distributed open-source microblogging tool
5 * Copyright (C) 2008, 2009, StatusNet, Inc.
6 *
7 * This program is free software: you can redistribute it and/or modify
8 * it under the terms of the GNU Affero General Public License as published by
9 * the Free Software Foundation, either version 3 of the License, or
10 * (at your option) any later version.
11 *
12 * This program is distributed in the hope that it will be useful,
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 * GNU Affero General Public License for more details.
16 *
17 * You should have received a copy of the GNU Affero General Public License
18 * along with this program. If not, see <http://www.gnu.org/licenses/>.
19 */
20
21define('INSTALLDIR', realpath(dirname(__FILE__) . '/..'));
22
23$shortoptions = 'i::';
24$longoptions = array('id::');
25
26$helptext = <<<END_OF_PING_HELP
27Daemon script for pushing new notices to ping servers.
28
29 -i --id Identity (default none)
30
31END_OF_PING_HELP;
32
33require_once INSTALLDIR.'/scripts/commandline.inc';
34
35require_once INSTALLDIR . '/lib/ping.php';
36require_once INSTALLDIR . '/lib/queuehandler.php';
37
38class PingQueueHandler extends QueueHandler {
39
40 function transport() {
41 return 'ping';
42 }
43
44 function start() {
45 $this->log(LOG_INFO, "INITIALIZE");
46 return true;
47 }
48
49 function handle_notice($notice) {
50 return ping_broadcast_notice($notice);
51 }
52
53 function finish() {
54 }
55}
56
57if (have_option('i')) {
58 $id = get_option_value('i');
59} else if (have_option('--id')) {
60 $id = get_option_value('--id');
61} else if (count($args) > 0) {
62 $id = $args[0];
63} else {
64 $id = null;
65}
66
67$handler = new PingQueueHandler($id);
68
69$handler->runOnce();
  
1#!/usr/bin/env php
2<?php
3/*
4 * StatusNet - the distributed open-source microblogging tool
5 * Copyright (C) 2008, 2009, StatusNet, Inc.
6 *
7 * This program is free software: you can redistribute it and/or modify
8 * it under the terms of the GNU Affero General Public License as published by
9 * the Free Software Foundation, either version 3 of the License, or
10 * (at your option) any later version.
11 *
12 * This program is distributed in the hope that it will be useful,
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 * GNU Affero General Public License for more details.
16 *
17 * You should have received a copy of the GNU Affero General Public License
18 * along with this program. If not, see <http://www.gnu.org/licenses/>.
19 */
20
21define('INSTALLDIR', realpath(dirname(__FILE__) . '/..'));
22
23$shortoptions = 'i::';
24$longoptions = array('id::');
25
26$helptext = <<<END_OF_OMB_HELP
27Daemon script for letting plugins handle stuff at queue time
28
29 -i --id Identity (default none)
30
31END_OF_OMB_HELP;
32
33require_once INSTALLDIR.'/scripts/commandline.inc';
34require_once INSTALLDIR . '/lib/queuehandler.php';
35
36class PluginQueueHandler extends QueueHandler
37{
38
39 function transport()
40 {
41 return 'plugin';
42 }
43
44 function start()
45 {
46 $this->log(LOG_INFO, "INITIALIZE");
47 return true;
48 }
49
50 function handle_notice($notice)
51 {
52 Event::handle('HandleQueuedNotice', array(&$notice));
53 return true;
54 }
55}
56
57if (have_option('i', 'id')) {
58 $id = get_option_value('i', 'id');
59} else {
60 $id = null;
61}
62
63$handler = new PluginQueueHandler($id);
64$handler->runOnce();
  
1#!/usr/bin/env php
2<?php
3/*
4 * StatusNet - the distributed open-source microblogging tool
5 * Copyright (C) 2008, 2009, StatusNet, Inc.
6 *
7 * This program is free software: you can redistribute it and/or modify
8 * it under the terms of the GNU Affero General Public License as published by
9 * the Free Software Foundation, either version 3 of the License, or
10 * (at your option) any later version.
11 *
12 * This program is distributed in the hope that it will be useful,
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 * GNU Affero General Public License for more details.
16 *
17 * You should have received a copy of the GNU Affero General Public License
18 * along with this program. If not, see <http://www.gnu.org/licenses/>.
19 */
20
21define('INSTALLDIR', realpath(dirname(__FILE__) . '/..'));
22
23$shortoptions = 'i::';
24$longoptions = array('id::');
25
26$helptext = <<<END_OF_PUBLIC_HELP
27Daemon script for pushing new notices to public XMPP subscribers.
28
29 -i --id Identity (default none)
30
31END_OF_PUBLIC_HELP;
32
33require_once INSTALLDIR.'/scripts/commandline.inc';
34
35require_once INSTALLDIR . '/lib/jabber.php';
36require_once INSTALLDIR . '/lib/xmppqueuehandler.php';
37
38class PublicQueueHandler extends XmppQueueHandler
39{
40
41 function transport()
42 {
43 return 'public';
44 }
45
46 function handle_notice($notice)
47 {
48 try {
49 return jabber_public_notice($notice);
50 } catch (XMPPHP_Exception $e) {
51 $this->log(LOG_ERR, "Got an XMPPHP_Exception: " . $e->getMessage());
52 die($e->getMessage());
53 }
54 }
55}
56
57// Abort immediately if xmpp is not enabled, otherwise the daemon chews up
58// lots of CPU trying to connect to unconfigured servers
59if (common_config('xmpp','enabled')==false) {
60 print "Aborting daemon - xmpp is disabled\n";
61 exit();
62}
63
64if (have_option('i')) {
65 $id = get_option_value('i');
66} else if (have_option('--id')) {
67 $id = get_option_value('--id');
68} else if (count($args) > 0) {
69 $id = $args[0];
70} else {
71 $id = null;
72}
73
74$handler = new PublicQueueHandler($id);
75
76$handler->runOnce();
  
1#!/usr/bin/env php
2<?php
3/*
4 * StatusNet - the distributed open-source microblogging tool
5 * Copyright (C) 2008, 2009, StatusNet, Inc.
6 *
7 * This program is free software: you can redistribute it and/or modify
8 * it under the terms of the GNU Affero General Public License as published by
9 * the Free Software Foundation, either version 3 of the License, or
10 * (at your option) any later version.
11 *
12 * This program is distributed in the hope that it will be useful,
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 * GNU Affero General Public License for more details.
16 *
17 * You should have received a copy of the GNU Affero General Public License
18 * along with this program. If not, see <http://www.gnu.org/licenses/>.
19 */
20
21define('INSTALLDIR', realpath(dirname(__FILE__) . '/..'));
22
23$shortoptions = 'fi:at:';
24$longoptions = array('id=', 'foreground', 'all', 'threads=');
25
26/**
27 * Attempts to get a count of the processors available on the current system
28 * to fan out multiple threads.
29 *
30 * Recognizes Linux and Mac OS X; others will return default of 1.
31 *
32 * @return intval
33 */
34function getProcessorCount()
35{
36 $cpus = 0;
37 switch (PHP_OS) {
38 case 'Linux':
39 $cpuinfo = file('/proc/cpuinfo');
40 foreach (file('/proc/cpuinfo') as $line) {
41 if (preg_match('/^processor\s+:\s+(\d+)\s?$/', $line)) {
42 $cpus++;
43 }
44 }
45 break;
46 case 'Darwin':
47 $cpus = intval(shell_exec("/usr/sbin/sysctl -n hw.ncpu 2>/dev/null"));
48 break;
49 }
50 if ($cpus) {
51 return $cpus;
52 }
53 return 1;
54}
55
56$threads = getProcessorCount();
57$helptext = <<<END_OF_QUEUE_HELP
58Daemon script for running queued items.
59
60 -i --id Identity (default none)
61 -f --foreground Stay in the foreground (default background)
62 -a --all Handle queues for all local sites
63 (requires Stomp queue handler, status_network setup)
64 -t --threads=<n> Spawn <n> processing threads (default $threads)
65
66
67END_OF_QUEUE_HELP;
68
69require_once INSTALLDIR.'/scripts/commandline.inc';
70
71require_once(INSTALLDIR.'/lib/daemon.php');
72require_once(INSTALLDIR.'/classes/Queue_item.php');
73require_once(INSTALLDIR.'/classes/Notice.php');
74
75define('CLAIM_TIMEOUT', 1200);
76
77/**
78 * Queue handling daemon...
79 *
80 * The queue daemon by default launches in the background, at which point
81 * it'll pass control to the configured QueueManager class to poll for updates.
82 *
83 * We can then pass individual items through the QueueHandler subclasses
84 * they belong to.
85 */
86class QueueDaemon extends Daemon
87{
88 protected $allsites;
89 protected $threads=1;
90
91 function __construct($id=null, $daemonize=true, $threads=1, $allsites=false)
92 {
93 parent::__construct($daemonize);
94
95 if ($id) {
96 $this->set_id($id);
97 }
98 $this->all = $allsites;
99 $this->threads = $threads;
100 }
101
102 /**
103 * How many seconds a polling-based queue manager should wait between
104 * checks for new items to handle.
105 *
106 * Defaults to 60 seconds; override to speed up or slow down.
107 *
108 * @return int timeout in seconds
109 */
110 function timeout()
111 {
112 return 60;
113 }
114
115 function name()
116 {
117 return strtolower(get_class($this).'.'.$this->get_id());
118 }
119
120 function run()
121 {
122 if ($this->threads > 1) {
123 return $this->runThreads();
124 } else {
125 return $this->runLoop();
126 }
127 }
128
129 function runThreads()
130 {
131 $children = array();
132 for ($i = 1; $i <= $this->threads; $i++) {
133 $pid = pcntl_fork();
134 if ($pid < 0) {
135 print "Couldn't fork for thread $i; aborting\n";
136 exit(1);
137 } else if ($pid == 0) {
138 $this->runChild($i);
139 exit(0);
140 } else {
141 $this->log(LOG_INFO, "Spawned thread $i as pid $pid");
142 $children[$i] = $pid;
143 }
144 }
145
146 $this->log(LOG_INFO, "Waiting for children to complete.");
147 while (count($children) > 0) {
148 $status = null;
149 $pid = pcntl_wait($status);
150 if ($pid > 0) {
151 $i = array_search($pid, $children);
152 if ($i === false) {
153 $this->log(LOG_ERR, "Unrecognized child pid $pid exited!");
154 continue;
155 }
156 unset($children[$i]);
157 $this->log(LOG_INFO, "Thread $i pid $pid exited.");
158
159 $pid = pcntl_fork();
160 if ($pid < 0) {
161 print "Couldn't fork to respawn thread $i; aborting thread.\n";
162 } else if ($pid == 0) {
163 $this->runChild($i);
164 exit(0);
165 } else {
166 $this->log(LOG_INFO, "Respawned thread $i as pid $pid");
167 $children[$i] = $pid;
168 }
169 }
170 }
171 $this->log(LOG_INFO, "All child processes complete.");
172 return true;
173 }
174
175 function runChild($thread)
176 {
177 $this->set_id($this->get_id() . "." . $thread);
178 $this->resetDb();
179 $this->runLoop();
180 }
181
182 /**
183 * Reconnect to the database for each child process,
184 * or they'll get very confused trying to use the
185 * same socket.
186 */
187 function resetDb()
188 {
189 // @fixme do we need to explicitly open the db too
190 // or is this implied?
191 global $_DB_DATAOBJECT;
192 unset($_DB_DATAOBJECT['CONNECTIONS']);
193
194 // Reconnect main memcached, or threads will stomp on
195 // each other and corrupt their requests.
196 $cache = common_memcache();
197 if ($cache) {
198 $cache->reconnect();
199 }
200
201 // Also reconnect memcached for status_network table.
202 if (!empty(Status_network::$cache)) {
203 Status_network::$cache->close();
204 Status_network::$cache = null;
205 }
206 }
207
208 /**
209 * Setup and start of run loop for this queue handler as a daemon.
210 * Most of the heavy lifting is passed on to the QueueManager's service()
211 * method, which passes control on to the QueueHandler's handle_notice()
212 * method for each notice that comes in on the queue.
213 *
214 * Most of the time this won't need to be overridden in a subclass.
215 *
216 * @return boolean true on success, false on failure
217 */
218 function runLoop()
219 {
220 $this->log(LOG_INFO, 'checking for queued notices');
221
222 $master = new IoMaster($this->get_id());
223 $master->init($this->all);
224 $master->service();
225
226 $this->log(LOG_INFO, 'finished servicing the queue');
227
228 $this->log(LOG_INFO, 'terminating normally');
229
230 return true;
231 }
232
233 function log($level, $msg)
234 {
235 common_log($level, get_class($this) . ' ('. $this->get_id() .'): '.$msg);
236 }
237}
238
239if (have_option('i')) {
240 $id = get_option_value('i');
241} else if (have_option('--id')) {
242 $id = get_option_value('--id');
243} else if (count($args) > 0) {
244 $id = $args[0];
245} else {
246 $id = null;
247}
248
249if (have_option('t')) {
250 $threads = intval(get_option_value('t'));
251} else if (have_option('--threads')) {
252 $threads = intval(get_option_value('--threads'));
253} else {
254 $threads = 0;
255}
256if (!$threads) {
257 $threads = getProcessorCount();
258}
259
260$daemonize = !(have_option('f') || have_option('--foreground'));
261$all = have_option('a') || have_option('--all');
262
263$daemon = new QueueDaemon($id, $daemonize, $threads, $all);
264$daemon->runOnce();
  
1#!/usr/bin/env php
2<?php
3/*
4 * StatusNet - the distributed open-source microblogging tool
5 * Copyright (C) 2008, 2009, StatusNet, Inc.
6 *
7 * This program is free software: you can redistribute it and/or modify
8 * it under the terms of the GNU Affero General Public License as published by
9 * the Free Software Foundation, either version 3 of the License, or
10 * (at your option) any later version.
11 *
12 * This program is distributed in the hope that it will be useful,
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 * GNU Affero General Public License for more details.
16 *
17 * You should have received a copy of the GNU Affero General Public License
18 * along with this program. If not, see <http://www.gnu.org/licenses/>.
19 */
20
21define('INSTALLDIR', realpath(dirname(__FILE__) . '/..'));
22
23$shortoptions = 'i::';
24$longoptions = array('id::');
25
26$helptext = <<<END_OF_SMS_HELP
27Daemon script for pushing new notices to local subscribers using SMS.
28
29 -i --id Identity (default none)
30
31END_OF_SMS_HELP;
32
33require_once INSTALLDIR.'/scripts/commandline.inc';
34
35require_once INSTALLDIR . '/lib/mail.php';
36require_once INSTALLDIR . '/lib/queuehandler.php';
37
38class SmsQueueHandler extends QueueHandler
39{
40 function transport()
41 {
42 return 'sms';
43 }
44
45 function start()
46 {
47 $this->log(LOG_INFO, "INITIALIZE");
48 return true;
49 }
50
51 function handle_notice($notice)
52 {
53 return mail_broadcast_notice_sms($notice);
54 }
55
56 function finish()
57 {
58 }
59}
60
61if (have_option('i')) {
62 $id = get_option_value('i');
63} else if (have_option('--id')) {
64 $id = get_option_value('--id');
65} else if (count($args) > 0) {
66 $id = $args[0];
67} else {
68 $id = null;
69}
70
71$handler = new SmsQueueHandler($id);
72
73$handler->runOnce();
  
1#!/usr/bin/env php
2<?php
3/*
4 * StatusNet - the distributed open-source microblogging tool
5 * Copyright (C) 2008, 2009, StatusNet, Inc.
6 *
7 * This program is free software: you can redistribute it and/or modify
8 * it under the terms of the GNU Affero General Public License as published by
9 * the Free Software Foundation, either version 3 of the License, or
10 * (at your option) any later version.
11 *
12 * This program is distributed in the hope that it will be useful,
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 * GNU Affero General Public License for more details.
16 *
17 * You should have received a copy of the GNU Affero General Public License
18 * along with this program. If not, see <http://www.gnu.org/licenses/>.
19 */
20
21define('INSTALLDIR', realpath(dirname(__FILE__) . '/..'));
22
23$shortoptions = 'i::';
24$longoptions = array('id::');
25
26$helptext = <<<END_OF_JABBER_HELP
27Daemon script for pushing new confirmations to Jabber users.
28
29 -i --id Identity (default none)
30
31END_OF_JABBER_HELP;
32
33require_once INSTALLDIR.'/scripts/commandline.inc';
34require_once INSTALLDIR . '/lib/jabber.php';
35require_once INSTALLDIR . '/lib/xmppqueuehandler.php';
36
37class XmppConfirmHandler extends XmppQueueHandler
38{
39 var $_id = 'confirm';
40
41 function class_name()
42 {
43 return 'XmppConfirmHandler';
44 }
45
46 function run()
47 {
48 if (!$this->start()) {
49 return false;
50 }
51 $this->log(LOG_INFO, 'checking for queued confirmations');
52 do {
53 $confirm = $this->next_confirm();
54 if ($confirm) {
55 $this->log(LOG_INFO, 'Sending confirmation for ' . $confirm->address);
56 $user = User::staticGet($confirm->user_id);
57 if (!$user) {
58 $this->log(LOG_WARNING, 'Confirmation for unknown user ' . $confirm->user_id);
59 continue;
60 }
61 $success = jabber_confirm_address($confirm->code,
62 $user->nickname,
63 $confirm->address);
64 if (!$success) {
65 $this->log(LOG_ERR, 'Confirmation failed for ' . $confirm->address);
66 # Just let the claim age out; hopefully things work then
67 continue;
68 } else {
69 $this->log(LOG_INFO, 'Confirmation sent for ' . $confirm->address);
70 # Mark confirmation sent; need a dupe so we don't have the WHERE clause
71 $dupe = Confirm_address::staticGet('code', $confirm->code);
72 if (!$dupe) {
73 common_log(LOG_WARNING, 'Could not refetch confirm', __FILE__);
74 continue;
75 }
76 $orig = clone($dupe);
77 $dupe->sent = $dupe->claimed;
78 $result = $dupe->update($orig);
79 if (!$result) {
80 common_log_db_error($dupe, 'UPDATE', __FILE__);
81 # Just let the claim age out; hopefully things work then
82 continue;
83 }
84 $dupe->free();
85 unset($dupe);
86 }
87 $user->free();
88 unset($user);
89 $confirm->free();
90 unset($confirm);
91 $this->idle(0);
92 } else {
93# $this->clear_old_confirm_claims();
94 $this->idle(10);
95 }
96 } while (true);
97 if (!$this->finish()) {
98 return false;
99 }
100 return true;
101 }
102
103 function next_confirm()
104 {
105 $confirm = new Confirm_address();
106 $confirm->whereAdd('claimed IS null');
107 $confirm->whereAdd('sent IS null');
108 # XXX: eventually we could do other confirmations in the queue, too
109 $confirm->address_type = 'jabber';
110 $confirm->orderBy('modified DESC');
111 $confirm->limit(1);
112 if ($confirm->find(true)) {
113 $this->log(LOG_INFO, 'Claiming confirmation for ' . $confirm->address);
114 # working around some weird DB_DataObject behaviour
115 $confirm->whereAdd(''); # clears where stuff
116 $original = clone($confirm);
117 $confirm->claimed = common_sql_now();
118 $result = $confirm->update($original);
119 if ($result) {
120 $this->log(LOG_INFO, 'Succeeded in claim! '. $result);
121 return $confirm;
122 } else {
123 $this->log(LOG_INFO, 'Failed in claim!');
124 return false;
125 }
126 }
127 return null;
128 }
129
130 function clear_old_confirm_claims()
131 {
132 $confirm = new Confirm();
133 $confirm->claimed = null;
134 $confirm->whereAdd('now() - claimed > '.CLAIM_TIMEOUT);
135 $confirm->update(DB_DATAOBJECT_WHEREADD_ONLY);
136 $confirm->free();
137 unset($confirm);
138 }
139}
140
141// Abort immediately if xmpp is not enabled, otherwise the daemon chews up
142// lots of CPU trying to connect to unconfigured servers
143if (common_config('xmpp','enabled')==false) {
144 print "Aborting daemon - xmpp is disabled\n";
145 exit();
146}
147
148if (have_option('i')) {
149 $id = get_option_value('i');
150} else if (have_option('--id')) {
151 $id = get_option_value('--id');
152} else if (count($args) > 0) {
153 $id = $args[0];
154} else {
155 $id = null;
156}
157
158$handler = new XmppConfirmHandler($id);
159
160$handler->runOnce();