Realtime_channel.php 7.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246
  1. <?php
  2. /**
  3. * StatusNet - the distributed open-source microblogging tool
  4. * Copyright (C) 2011, StatusNet, Inc.
  5. *
  6. * A channel for real-time browser data
  7. *
  8. * PHP version 5
  9. *
  10. * This program is free software: you can redistribute it and/or modify
  11. * it under the terms of the GNU Affero General Public License as published by
  12. * the Free Software Foundation, either version 3 of the License, or
  13. * (at your option) any later version.
  14. *
  15. * This program is distributed in the hope that it will be useful,
  16. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  17. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  18. * GNU Affero General Public License for more details.
  19. *
  20. * You should have received a copy of the GNU Affero General Public License
  21. * along with this program. If not, see <http://www.gnu.org/licenses/>.
  22. *
  23. * @category Realtime
  24. * @package StatusNet
  25. * @author Evan Prodromou <evan@status.net>
  26. * @copyright 2011 StatusNet, Inc.
  27. * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html AGPL 3.0
  28. * @link http://status.net/
  29. */
  30. if (!defined('STATUSNET')) {
  31. exit(1);
  32. }
  33. /**
  34. * A channel for real-time browser data
  35. *
  36. * For each user currently browsing the site, we want to know which page they're on
  37. * so we can send real-time updates to their browser.
  38. *
  39. * @category Realtime
  40. * @package StatusNet
  41. * @author Evan Prodromou <evan@status.net>
  42. * @license http://www.fsf.org/licensing/licenses/agpl.html AGPLv3
  43. * @link http://status.net/
  44. *
  45. * @see DB_DataObject
  46. */
  47. class Realtime_channel extends Managed_DataObject
  48. {
  49. const TIMEOUT = 1800; // 30 minutes
  50. public $__table = 'realtime_channel'; // table name
  51. public $user_id; // int -> user.id, can be null
  52. public $action; // varchar(191) not 255 because utf8mb4 takes more space
  53. public $arg1; // varchar(191) argument not 255 because utf8mb4 takes more space
  54. public $arg2; // varchar(191) usually null not 255 because utf8mb4 takes more space
  55. public $channel_key; // 128-bit shared secret key
  56. public $audience; // listener count
  57. public $created; // created date
  58. public $modified; // modified date
  59. /**
  60. * The One True Thingy that must be defined and declared.
  61. */
  62. public static function schemaDef()
  63. {
  64. return array(
  65. 'description' => 'A channel of realtime notice data',
  66. 'fields' => array(
  67. 'user_id' => array('type' => 'int',
  68. 'not null' => false,
  69. 'description' => 'user viewing page; can be null'),
  70. 'action' => array('type' => 'varchar',
  71. 'length' => 191,
  72. 'not null' => true,
  73. 'description' => 'page being viewed'),
  74. 'arg1' => array('type' => 'varchar',
  75. 'length' => 191,
  76. 'not null' => false,
  77. 'description' => 'page argument, like username or tag'),
  78. 'arg2' => array('type' => 'varchar',
  79. 'length' => 191,
  80. 'not null' => false,
  81. 'description' => 'second page argument, like tag for showstream'),
  82. 'channel_key' => array('type' => 'varchar',
  83. 'length' => 32,
  84. 'not null' => true,
  85. 'description' => 'shared secret key for this channel'),
  86. 'audience' => array('type' => 'integer',
  87. 'not null' => true,
  88. 'default' => 0,
  89. 'description' => 'reference count'),
  90. 'created' => array('type' => 'datetime',
  91. 'not null' => true,
  92. 'description' => 'date this record was created'),
  93. 'modified' => array('type' => 'datetime',
  94. 'not null' => true,
  95. 'description' => 'date this record was modified'),
  96. ),
  97. 'primary key' => array('channel_key'),
  98. 'unique keys' => array('realtime_channel_user_page_idx' => array('user_id', 'action', 'arg1', 'arg2')),
  99. 'foreign keys' => array(
  100. 'realtime_channel_user_id_fkey' => array('user', array('user_id' => 'id')),
  101. ),
  102. 'indexes' => array(
  103. 'realtime_channel_modified_idx' => array('modified'),
  104. 'realtime_channel_page_idx' => array('action', 'arg1', 'arg2')
  105. ),
  106. );
  107. }
  108. static function saveNew($user_id, $action, $arg1, $arg2)
  109. {
  110. $channel = new Realtime_channel();
  111. $channel->user_id = $user_id;
  112. $channel->action = $action;
  113. $channel->arg1 = $arg1;
  114. $channel->arg2 = $arg2;
  115. $channel->audience = 1;
  116. $channel->channel_key = common_random_hexstr(16); // 128-bit key, 32 hex chars
  117. $channel->created = common_sql_now();
  118. $channel->modified = $channel->created;
  119. $channel->insert();
  120. return $channel;
  121. }
  122. static function getChannel($user_id, $action, $arg1, $arg2)
  123. {
  124. $channel = self::fetchChannel($user_id, $action, $arg1, $arg2);
  125. // Ignore (and delete!) old channels
  126. if (!empty($channel)) {
  127. $modTime = strtotime($channel->modified);
  128. if ((time() - $modTime) > self::TIMEOUT) {
  129. $channel->delete();
  130. $channel = null;
  131. }
  132. }
  133. if (empty($channel)) {
  134. $channel = self::saveNew($user_id, $action, $arg1, $arg2);
  135. }
  136. return $channel;
  137. }
  138. static function getAllChannels($action, $arg1, $arg2)
  139. {
  140. $channel = new Realtime_channel();
  141. $channel->action = $action;
  142. if (is_null($arg1)) {
  143. $channel->whereAdd('arg1 is null');
  144. } else {
  145. $channel->arg1 = $arg1;
  146. }
  147. if (is_null($arg2)) {
  148. $channel->whereAdd('arg2 is null');
  149. } else {
  150. $channel->arg2 = $arg2;
  151. }
  152. $channel->whereAdd('modified > "' . common_sql_date(time() - self::TIMEOUT) . '"');
  153. $channels = array();
  154. if ($channel->find()) {
  155. $channels = $channel->fetchAll();
  156. }
  157. return $channels;
  158. }
  159. static function fetchChannel($user_id, $action, $arg1, $arg2)
  160. {
  161. $channel = new Realtime_channel();
  162. if (is_null($user_id)) {
  163. $channel->whereAdd('user_id is null');
  164. } else {
  165. $channel->user_id = $user_id;
  166. }
  167. $channel->action = $action;
  168. if (is_null($arg1)) {
  169. $channel->whereAdd('arg1 is null');
  170. } else {
  171. $channel->arg1 = $arg1;
  172. }
  173. if (is_null($arg2)) {
  174. $channel->whereAdd('arg2 is null');
  175. } else {
  176. $channel->arg2 = $arg2;
  177. }
  178. if ($channel->find(true)) {
  179. $channel->increment();
  180. return $channel;
  181. } else {
  182. return null;
  183. }
  184. }
  185. function increment()
  186. {
  187. // XXX: race
  188. $orig = clone($this);
  189. $this->audience++;
  190. $this->modified = common_sql_now();
  191. $this->update($orig);
  192. }
  193. function touch()
  194. {
  195. // XXX: race
  196. $orig = clone($this);
  197. $this->modified = common_sql_now();
  198. $this->update($orig);
  199. }
  200. function decrement()
  201. {
  202. // XXX: race
  203. if ($this->audience == 1) {
  204. $this->delete();
  205. } else {
  206. $orig = clone($this);
  207. $this->audience--;
  208. $this->modified = common_sql_now();
  209. $this->update($orig);
  210. }
  211. }
  212. }