From 21eac91a51f2a578c84383ed5b139f9c9eb98cea Mon Sep 17 00:00:00 2001 From: David Kobia Date: Mon, 7 Oct 2013 17:00:32 -0400 Subject: [PATCH] Integrating Minion Tasks + Redis * Messages now queued instead of an immediate send * Messages will then be popped out of the Redis queue to be dealt with individually * Added multiple checks to prevent ping overload --- README.md | 9 +- application/classes/Controller/Messages.php | 70 +++--------- application/classes/Model/Ping.php | 13 ++- application/classes/PingApp/Ping.php | 100 +++++++++++++++++- application/classes/PingApp/Redis.php | 2 +- application/classes/Task/Ping.php | 11 +- application/migrations/1/20131005173410.php | 98 ++++++++++++++++- application/views/pages/send.php | 2 +- composer.json | 2 +- .../twilio/classes/Controller/Ivr/Twilio.php | 2 +- .../twilio/classes/Controller/Sms/Twilio.php | 2 +- 11 files changed, 236 insertions(+), 75 deletions(-) diff --git a/README.md b/README.md index f16b2bb..1b5168b 100644 --- a/README.md +++ b/README.md @@ -16,6 +16,8 @@ To install the platform on your computer/server, the target system must meet the * An HTTP Server. PingApp is known to work with the following web servers: - Apache 2.2+ - nginx +* Redis + - required for Message Queuing * Unicode support in the operating system ### Downloading @@ -63,9 +65,10 @@ To install the platform on your computer/server, the target system must meet the 6. Edit ```application/config/init.php``` and change base_url to point the the httpdocs directory in your deployment 7. ```cp application/config/auth.template application/config/auth.php``` 8. ```cp application/config/modules.template application/config/modules.php``` -9. ```cp httpdocs/template.htaccess httpdocs/.htaccess``` -10. Edit ```httpdocs/.htaccess``` and change the RewriteBase value to match your deployment url -11. Create directories ```application/cache``` and ```application/logs``` and make them writable +9. ```cp application/config/redis.template application/config/redis.php``` +10. ```cp httpdocs/template.htaccess httpdocs/.htaccess``` +11. Edit ```httpdocs/.htaccess``` and change the RewriteBase value to match your deployment url +12. Create directories ```application/cache``` and ```application/logs``` and make them writable ### Upgrading diff --git a/application/classes/Controller/Messages.php b/application/classes/Controller/Messages.php index d1ebdc3..b925a5f 100644 --- a/application/classes/Controller/Messages.php +++ b/application/classes/Controller/Messages.php @@ -87,68 +87,32 @@ private function _broadcast_message() } // Create the message - $message = new Model_Message(); + $message = ORM::factory('Message'); try { // Set values and save $message->values(array( 'message' => $this->request->post('message'), 'user_id' => $this->user->id, - 'type' => 'phone' - )) - ->save(); - - // Tracks the no. of pings sent out - $ping_count = 0; - - $_columns = array('message_id', 'tracking_id', 'contact_id', 'provider', 'type', 'status', 'created'); - $query = DB::insert('pings', $_columns); - - // Ping! + 'type' => 'sms' + )); + $message->save(); + + // Save Ping foreach ($contacts as $contact) { - if (($tracking_id = $this->_provider->send($contact->contact, $message->message)) !== FALSE) - { - $query->values(array( - 'message_id' => $message->id, - 'tracking_id' => $tracking_id, - 'contact_id' => $contact->id, - 'provider' => strtolower(PingApp_SMS_Provider::$sms_provider), - 'type' => 'phone', - 'status' => 'pending', - 'created' => date('Y-m-d H:i:s') + $ping = ORM::factory('Ping'); + $ping->values(array( + 'message_id' => $message->id, + 'tracking_id' => '0', + 'type' => 'sms', + 'contact_id' => $contact->id, + 'provider' => strtolower(PingApp_SMS_Provider::$sms_provider), + 'status' => 'pending', + 'sent' => 0 )); - $ping_count++; - } - } - - // Any pings go out? - if ($ping_count) - { - try - { - // Create the pings - $query->execute(); - Kohana::$log->add(Log::INFO, __("Successfully dispatched :count pings", array(":count" => $ping_count))); - } - catch (Database_Exception $e) - { - // Rollback message creation - $messsage->delete(); - - Kohana::$log->add(Log::ERROR, $e->getMessage()); - - return FALSE; - } - } - else - { - // An error ocurred while trying to send the messages - // Rollback the message - $message->delete(); - Kohana::$log->add(Log::INFO, "No messages sent"); - } - + $ping->save(); + } } catch (ORM_Validation_Exception $e) { diff --git a/application/classes/Model/Ping.php b/application/classes/Model/Ping.php index f889740..4c38cfe 100644 --- a/application/classes/Model/Ping.php +++ b/application/classes/Model/Ping.php @@ -12,17 +12,26 @@ class Model_Ping extends ORM { /** * A ping belongs to a message, and a contact + * A ping also belongs to a parent ping (retries) */ protected $_belongs_to = array( 'message' => array(), 'contact' => array(), + 'parent' => array( + 'model' => 'Ping', + 'foreign_key' => 'parent_id', + ), ); /** - * A ping has many pongs + * A ping has many pongs and children pings (retries) */ protected $_has_many = array( 'pongs' => array(), + 'children' => array( + 'model' => 'Ping', + 'foreign_key' => 'parent_id', + ), ); // Insert/Update Timestamps @@ -33,7 +42,7 @@ public function rules() { return array( 'status' => array( - array('in_array', array(':value', array('pending', 'sent', 'received', 'replied')) ), + array('in_array', array(':value', array('pending', 'received', 'expired', 'failed', 'cancelled')) ), ), ); } diff --git a/application/classes/PingApp/Ping.php b/application/classes/PingApp/Ping.php index f2ec3ed..c5cf522 100644 --- a/application/classes/PingApp/Ping.php +++ b/application/classes/PingApp/Ping.php @@ -8,5 +8,103 @@ * @license http://www.gnu.org/copyleft/gpl.html GNU General Public License Version 3 (GPLv3) */ class PingApp_Ping { - + + public static function process($ping_id = 0) + { + if ($ping_id) + { + // 1. Ping Exists, Is Pending and not Sent? + $ping = ORM::factory('Ping') + ->where('id', '=', $ping_id) + ->where('status', '=', 'pending') + ->where('sent', '!=', 1) + ->find(); + + if ( $ping->loaded() ) + { + // Is this an old Ping? (> 24 hours old) + if ( ( time() - strtotime($ping->created) ) > 86400) + { + $ping->status = 'expired'; + $ping->save(); + + return; + } + + // 2. Contacts Exists? + $contact = $ping->contact; + if ( $contact->loaded() ) + { + // How many times has this contact been pinged + // in the last 24 hours + $pings = $contact->pings + ->where('sent', '=', 1) + ->where('created', '>=', DB::expr('DATE_SUB(NOW(), INTERVAL 1 DAY)')) + ->order_by('created', 'DESC') + ->find_all(); + + // If less than 3, we can safely proceed + if ($pings->count() < 3) + { + // But we need to space the pings out + if ($pings->count() > 0) + { + // Get the last ping to this contact + foreach ($pings as $_ping) + { + // 10 minute spacer + if ( ( time() - strtotime($_ping->updated) ) < 600) + { + return; + } + + break; + } + } + + // Phew - Okay we can now send this ping + if ($ping->type == 'sms') + { + self::_sms($ping, $contact); + } + elseif ($ping->type == 'email') + { + self::_email($ping, $contact); + } + } + // Else its time to contact secondary folks + // or just STOP!!! No More Messages! + else + { + + } + } + } + } + } + + private static function _sms($ping, $contact) + { + // Get the SMS provider to use + try + { + $provider = PingApp_SMS_Provider::instance(); + } + catch (PingApp_Exception $e) + { + Kohana::$log->add(Log::ERROR, $e->getMessage()); + } + + if (($tracking_id = $provider->send($contact->contact, $ping->message->message)) !== FALSE) + { + $ping->tracking_id = $tracking_id; + $ping->sent = 1; + $ping->save(); + } + } + + private static function _email($ping, $contact) + { + + } } \ No newline at end of file diff --git a/application/classes/PingApp/Redis.php b/application/classes/PingApp/Redis.php index 8323b2c..f4304d4 100644 --- a/application/classes/PingApp/Redis.php +++ b/application/classes/PingApp/Redis.php @@ -29,7 +29,7 @@ public static function factory() if ( ! PingApp_Redis::$redis) { // No config file found - if (!Kohana::$config->load('redis')) + if ( ! Kohana::$config->load('redis')) { PingApp_Redis::$redis = new Predis\Client(array( 'scheme' => 'tcp', diff --git a/application/classes/Task/Ping.php b/application/classes/Task/Ping.php index 181e5e2..4ac7c9d 100644 --- a/application/classes/Task/Ping.php +++ b/application/classes/Task/Ping.php @@ -47,7 +47,8 @@ protected function _queue() // Get All Unsent Pings $pings = ORM::factory('Ping') - ->where('pending', '=', 'pending') + ->where('status', '=', 'pending') + ->where('sent', '!=', 1) ->find_all(); foreach ($pings as $ping) @@ -77,10 +78,7 @@ protected function _process() if ( isset($item[1]) ) { - // Remove remaining dupes - $redis->lRem('searches', $item[1], 0); - - PingApp_Ping::run($item[1]); + PingApp_Ping::process((int) $item[1]); } else { @@ -92,11 +90,8 @@ protected function _process() } catch (Exception $e) { - //Log::instance()->add(Log::ERROR, 'SEARCHES TASK: '.Kohana_Exception::text($e)); break; } } - - //echo View::factory('profiler/stats'); } } \ No newline at end of file diff --git a/application/migrations/1/20131005173410.php b/application/migrations/1/20131005173410.php index 45cbc16..76e688c 100644 --- a/application/migrations/1/20131005173410.php +++ b/application/migrations/1/20131005173410.php @@ -9,10 +9,21 @@ class Migration_1_20131005173410 extends Minion_Migration_Base { */ public function up(Kohana_Database $db) { - $db->query(NULL, "ALTER TABLE `pings` CHANGE `status` `status` VARCHAR(20) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL DEFAULT 'pending' COMMENT 'pending, sent, received, replied, expired';"); - + $db->query(NULL, "ALTER TABLE `pings` CHANGE `status` `status` VARCHAR(20) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL DEFAULT 'pending' COMMENT 'pending, received, expired, cancelled, failed';"); + $db->query(NULL, "ALTER TABLE `pings` ADD `sent` TINYINT(1) NOT NULL DEFAULT '0' AFTER `status`;"); $db->query(NULL, "ALTER TABLE `pings` ADD `updated` DATETIME NOT NULL DEFAULT '1001-01-01 00:00:00' AFTER `created`;"); + $db->query(NULL, "ALTER TABLE `pings` ADD `parent_id` INT(11) UNSIGNED NOT NULL DEFAULT '0' AFTER `id`;"); + $db->query(NULL, "ALTER TABLE `pings` ADD INDEX `idx_parent_id` (`parent_id`);"); + $db->query(NULL, "ALTER TABLE `pings` CHANGE `type` `type` VARCHAR(20) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL DEFAULT 'phone' COMMENT 'email, sms, twitter';"); + $db->query(NULL, "ALTER TABLE `pongs` CHANGE `type` `type` VARCHAR(20) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL DEFAULT 'phone' COMMENT 'email, sms, voice, twitter';"); + $db->query(NULL, "ALTER TABLE `messages` CHANGE `type` `type` VARCHAR(20) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL DEFAULT 'phone' COMMENT 'email, sms, twitter';"); + $db->query(NULL, "ALTER TABLE `pings` DROP INDEX `idx_provider_tracking_id`;"); + $db->query(NULL, "ALTER TABLE `pings` ADD INDEX `idx_tracking_id` (`tracking_id`);"); + $db->query(NULL, "ALTER TABLE `pings` ADD INDEX `idx_provider` (`provider`);"); + + // Migrate types + $this->_migrate_old(); } /** @@ -23,8 +34,89 @@ public function up(Kohana_Database $db) public function down(Kohana_Database $db) { $db->query(NULL, "ALTER TABLE `pings` CHANGE `status` `status` VARCHAR(20) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL DEFAULT 'pending' COMMENT 'pending, received';"); - + $db->query(NULL, "ALTER TABLE `pings` CHANGE `type` `type` VARCHAR(20) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL DEFAULT 'phone' COMMENT 'email, phone, twitter';"); + $db->query(NULL, "ALTER TABLE `pings` DROP `sent`;"); $db->query(NULL, "ALTER TABLE `pings` DROP `updated`;"); + $db->query(NULL, "ALTER TABLE `pings` DROP `parent_id`;"); + $db->query(NULL, "ALTER TABLE `pongs` CHANGE `type` `type` VARCHAR(20) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL DEFAULT 'phone' COMMENT 'email, phone, twitter';"); + $db->query(NULL, "ALTER TABLE `messages` CHANGE `type` `type` VARCHAR(20) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL DEFAULT 'phone' COMMENT 'email, phone, twitter';"); + + // Migrate types + $this->_un_migrate_old(); + } + + /** + * Migrate Old Types + * @return void + */ + private function _migrate_old() + { + $pings = ORM::factory('Ping') + ->where('type', '=', 'phone') + ->find_all(); + + foreach ($pings as $ping) + { + $ping->type = 'sms'; + $ping->save(); + } + + $pongs = ORM::factory('Pong') + ->where('type', '=', 'phone') + ->find_all(); + + foreach ($pongs as $pong) + { + $pong->type = 'sms'; + $pong->save(); + } + + $messages = ORM::factory('Message') + ->where('type', '=', 'phone') + ->find_all(); + + foreach ($messages as $message) + { + $message->type = 'sms'; + $message->save(); + } + } + + /** + * UnMigrate Old Type + * @return void + */ + private function _un_migrate_old() + { + $pings = ORM::factory('Ping') + ->where('type', '=', 'sms') + ->find_all(); + + foreach ($pings as $ping) + { + $ping->type = 'phone'; + $ping->save(); + } + + $pongs = ORM::factory('Pong') + ->where('type', '=', 'sms') + ->find_all(); + + foreach ($pongs as $pong) + { + $pong->type = 'phone'; + $pong->save(); + } + + $messages = ORM::factory('Message') + ->where('type', '=', 'sms') + ->find_all(); + + foreach ($messages as $message) + { + $message->type = 'phone'; + $message->save(); + } } } diff --git a/application/views/pages/send.php b/application/views/pages/send.php index f6817cc..a41d989 100644 --- a/application/views/pages/send.php +++ b/application/views/pages/send.php @@ -9,7 +9,7 @@
- + ×
diff --git a/composer.json b/composer.json index 9e5aa7d..ded0668 100644 --- a/composer.json +++ b/composer.json @@ -3,7 +3,7 @@ "phpunit/phpunit": "3.7.*", "phpunit/dbunit": ">=1.2", "phing/phing": "dev-master", - "predis/predis": ">=0.8", + "predis/predis": ">=0.8" }, "config": { "bin-dir": "bin/" diff --git a/plugins/twilio/classes/Controller/Ivr/Twilio.php b/plugins/twilio/classes/Controller/Ivr/Twilio.php index f7aae4c..417c780 100644 --- a/plugins/twilio/classes/Controller/Ivr/Twilio.php +++ b/plugins/twilio/classes/Controller/Ivr/Twilio.php @@ -95,7 +95,7 @@ public function action_gather() ->values(array( 'content' => $message_text, 'contact_id' => $contact->id, - 'type' => 'phone', + 'type' => 'voice', 'ping_id' => $ping->id )) ->save(); diff --git a/plugins/twilio/classes/Controller/Sms/Twilio.php b/plugins/twilio/classes/Controller/Sms/Twilio.php index 2e600c8..4670c9d 100644 --- a/plugins/twilio/classes/Controller/Sms/Twilio.php +++ b/plugins/twilio/classes/Controller/Sms/Twilio.php @@ -62,7 +62,7 @@ public function action_reply() ->values(array( 'content' => $message_text, 'contact_id' => $contact->id, - 'type' => 'phone', + 'type' => 'sms', 'ping_id' => $ping->id )) ->save();