'plugins' =>
array('core' => array(
'AuthCrypt' => array(),
- 'Cron' => array(),
+ 'Cronish' => array(),
'LRDD' => array(),
+ 'OpportunisticQM' => array(),
'StrictTransportSecurity' => array(),
),
'default' => array(
self::$qm = new UnQueueManager();
} else {
switch ($type) {
- case 'cron':
- self::$qm = new CronQueueManager();
+ case 'opportunistic':
+ self::$qm = new OpportunisticQueueManager();
break;
case 'db':
self::$qm = new DBQueueManager();
--- /dev/null
+<?php
+
+class CronishPlugin extends Plugin {
+ public function onCronHourly()
+ {
+ common_debug('CRON: Running hourly cron job!');
+ }
+
+ public function onCronDaily()
+ {
+ common_debug('CRON: Running daily cron job!');
+ }
+
+ public function onCronWeekly()
+ {
+ common_debug('CRON: Running weekly cron job!');
+ }
+
+ /**
+ * When the page has finished rendering, let's do some cron jobs
+ * if we have the time.
+ */
+ public function onEndActionExecute($status, Action $action)
+ {
+ $cron = new Cronish();
+ $cron->callTimedEvents();
+
+ return true;
+ }
+
+ public function onPluginVersion(&$versions)
+ {
+ $versions[] = array('name' => 'Cronish',
+ 'version' => GNUSOCIAL_VERSION,
+ 'author' => 'Mikael Nordfeldth',
+ 'homepage' => 'http://www.gnu.org/software/social/',
+ 'description' =>
+ // TRANS: Plugin description.
+ _m('Cronish plugin that executes events on a near-hour/day/week basis.'));
+ return true;
+ }
+}
--- /dev/null
+<?php
+/**
+ * GNU social cron-on-visit class
+ *
+ * Keeps track, through Config dataobject class, of relative time since the
+ * last run in order to to run event handlers with certain intervals.
+ *
+ * @category Cron
+ * @package GNUsocial
+ * @author Mikael Nordfeldth <mmn@hethane.se>
+ * @copyright 2013 Free Software Foundation, Inc.
+ * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html AGPL 3.0
+ * @link http://status.net/
+ */
+class Cronish
+{
+ /**
+ * Will call events as close as it gets to one hour. Event handlers
+ * which use this MUST be as quick as possible, maybe only adding a
+ * queue item to be handled later or something. Otherwise execution
+ * will timeout for PHP - or at least cause unnecessary delays for
+ * the unlucky user who visits the site exactly at one of these events.
+ */
+ public function callTimedEvents()
+ {
+ $timers = array('hourly' => 3600,
+ 'daily' => 86400,
+ 'weekly' => 604800);
+
+ foreach($timers as $name=>$interval) {
+ $run = false;
+
+ $lastrun = new Config();
+ $lastrun->section = 'cron';
+ $lastrun->setting = 'last_' . $name;
+ $found = $lastrun->find(true);
+
+ if (!$found) {
+ $lastrun->value = time();
+ if ($lastrun->insert() === false) {
+ common_log(LOG_WARNING, "Could not save 'cron' setting '{$name}'");
+ continue;
+ }
+ $run = true;
+ } elseif ($lastrun->value < time() - $interval) {
+ $orig = clone($lastrun);
+ $lastrun->value = time();
+ $lastrun->update($orig);
+ $run = true;
+ }
+
+ if ($run === true) {
+ // such as CronHourly, CronDaily, CronWeekly
+ Event::handle('Cron' . ucfirst($name));
+ }
+ }
+ }
+}
--- /dev/null
+<?php
+
+class OpportunisticQMPlugin extends Plugin {
+ public $qmkey = false;
+ public $secs_per_action = 1; // total seconds to run script per action
+ public $rel_to_pageload = true; // relative to pageload or queue start
+
+ public function onRouterInitialized($m)
+ {
+ $m->connect('main/runqueue', array('action' => 'runqueue'));
+ }
+
+ /**
+ * When the page has finished rendering, let's do some cron jobs
+ * if we have the time.
+ */
+ public function onEndActionExecute($status, Action $action)
+ {
+ if ($action instanceof RunqueueAction) {
+ return true;
+ }
+
+ global $_startTime;
+
+ $args = array(
+ 'qmkey' => common_config('opportunisticqm', 'qmkey'),
+ 'max_execution_time' => $this->secs_per_action,
+ 'started_at' => $this->rel_to_pageload ? $_startTime : null,
+ );
+ $qm = new OpportunisticQueueManager($args);
+ $qm->runQueue();
+ return true;
+ }
+
+ public function onPluginVersion(&$versions)
+ {
+ $versions[] = array('name' => 'OpportunisticQM',
+ 'version' => GNUSOCIAL_VERSION,
+ 'author' => 'Mikael Nordfeldth',
+ 'homepage' => 'http://www.gnu.org/software/social/',
+ 'description' =>
+ // TRANS: Plugin description.
+ _m('Opportunistic queue manager plugin for background processing.'));
+ return true;
+ }
+}
--- /dev/null
+<?php
+/*
+ * StatusNet - the distributed open-source microblogging tool
+ * Copyright (C) 2008, 2009, StatusNet, Inc.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+if (!defined('GNUSOCIAL')) { exit(1); }
+
+class RunqueueAction extends Action
+{
+ protected $qm = null;
+
+ protected function prepare(array $args=array())
+ {
+ parent::prepare($args);
+
+ $args = array();
+
+ foreach (array('qmkey') as $key) {
+ if ($this->arg($key) !== null) {
+ $args[$key] = $this->arg($key);
+ }
+ }
+
+ try {
+ $this->qm = new OpportunisticQueueManager($args);
+ } catch (RunQueueBadKeyException $e) {
+ return false;
+ }
+
+ header('Content-type: text/plain; charset=utf-8');
+
+ return true;
+ }
+
+ protected function handle() {
+ // We don't need any of the parent functionality from parent::handle() here.
+
+ // runQueue is a loop that works until limits have passed or there is no more work
+ if ($this->qm->runQueue() === true) {
+ // We don't have any more work
+ $this->text('0');
+ } else {
+ // There were still items left in queue when we aborted
+ $this->text('1');
+ }
+ }
+}
--- /dev/null
+<?php
+/**
+ * GNU social queue-manager-on-visit class
+ *
+ * Will run events for a certain time, or until finished.
+ *
+ * Configure remote key if wanted with $config['opportunisticqm']['qmkey'] and
+ * use with /main/runqueue?qmkey=abc123
+ *
+ * @category Cron
+ * @package GNUsocial
+ * @author Mikael Nordfeldth <mmn@hethane.se>
+ * @copyright 2013 Free Software Foundation, Inc.
+ * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html AGPL 3.0
+ * @link http://status.net/
+ */
+class OpportunisticQueueManager extends DBQueueManager
+{
+ protected $qmkey = false;
+ protected $max_execution_time = null;
+ protected $max_queue_items = null;
+
+ protected $started_at = null;
+ protected $handled_items = 0;
+
+ const MAXEXECTIME = 30; // typically just used for the /main/cron action
+
+ public function __construct(array $args=array()) {
+ foreach (get_class_vars(get_class($this)) as $key=>$val) {
+ if (array_key_exists($key, $args)) {
+ $this->$key = $args[$key];
+ }
+ }
+ $this->verifyKey();
+
+ if ($this->started_at === null) {
+ $this->started_at = time();
+ }
+
+ if ($this->max_execution_time === null) {
+ $this->max_execution_time = ini_get('max_execution_time') ?: self::MAXEXECTIME;
+ }
+
+ return parent::__construct();
+ }
+
+ protected function verifyKey()
+ {
+ if ($this->qmkey !== common_config('opportunisticqm', 'qmkey')) {
+ throw new RunQueueBadKeyException($this->qmkey);
+ }
+ }
+
+ public function canContinue()
+ {
+ $time_passed = time() - $this->started_at;
+
+ // Only continue if limit values are sane
+ if ($time_passed <= 0 && (!is_null($this->max_queue_items) && $this->max_queue_items <= 0)) {
+ return false;
+ }
+ // If too much time has passed, stop
+ if ($time_passed >= $this->max_execution_time) {
+ return false;
+ }
+ // If we have a max-item-limit, check if it has been passed
+ if (!is_null($this->max_queue_items) && $this->handled_items >= $this->max_queue_items) {
+ return false;
+ }
+
+ return true;
+ }
+
+ public function poll()
+ {
+ $this->handled_items++;
+ if (!parent::poll()) {
+ throw new RunQueueOutOfWorkException();
+ }
+ return true;
+ }
+
+ /**
+ * Takes care of running through the queue items, returning when
+ * the limits setup in __construct are met.
+ *
+ * @return true on workqueue finished, false if there are still items in the queue
+ */
+ public function runQueue()
+ {
+ while ($this->canContinue()) {
+ try {
+ $this->poll();
+ } catch (RunQueueOutOfWorkException $e) {
+ common_debug('Opportunistic queue manager finished.');
+ return true;
+ }
+ }
+ common_debug('Opportunistic queue manager passed execution time/item handling limit without being out of work.');
+ return false;
+ }
+}
--- /dev/null
+<?php
+/**
+ * Class for the GNU social cron exception when a bad key is used
+ *
+ * PHP version 5
+ *
+ * LICENCE: This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * @category Exception
+ * @package GNUsocial
+ * @author Mikael Nordfeldth <mmn@hethane.se>
+ * @copyright 2013 Free Software Foundation, Inc.
+ * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html AGPLv3
+ * @link http://www.gnu.org/software/social/
+ */
+
+if (!defined('GNUSOCIAL')) { exit(1); }
+
+class RunQueueBadKeyException extends ClientException
+{
+ public $qmkey;
+
+ public function __construct($qmkey)
+ {
+ $this->qmkey = $qmkey;
+ parent::__construct(_('Bad queue manager key was used.'));
+ }
+}
--- /dev/null
+<?php
+/**
+ * Class for the GNU social cron exception when there is no more work to be done
+ *
+ * PHP version 5
+ *
+ * LICENCE: This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * @category Exception
+ * @package GNUsocial
+ * @author Mikael Nordfeldth <mmn@hethane.se>
+ * @copyright 2013 Free Software Foundation, Inc.
+ * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html AGPLv3
+ * @link http://www.gnu.org/software/social/
+ */
+
+if (!defined('GNUSOCIAL')) { exit(1); }
+
+class RunQueueOutOfWorkException extends ServerException
+{
+ public function __construct()
+ {
+ parent::__construct(_('Opportunistic queue manager is out of work (no more items).'));
+ }
+}