Growl.php 8.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247
  1. <?php
  2. /**
  3. * Lithium: the most rad php framework
  4. *
  5. * @copyright Copyright 2012, Union of RAD (http://union-of-rad.org)
  6. * @license http://opensource.org/licenses/bsd-license.php The BSD License
  7. */
  8. namespace lithium\analysis\logger\adapter;
  9. use lithium\util\Inflector;
  10. use lithium\core\NetworkException;
  11. /**
  12. * The `Growl` logger implements support for the [ Growl](http://growl.info/) notification system
  13. * for Mac OS X. Writing to this logger will display small, customizable status messages on the
  14. * screen.
  15. */
  16. class Growl extends \lithium\core\Object {
  17. /**
  18. * Array that maps `Logger` message priority names to Growl-compatible priority levels.
  19. *
  20. * @var array
  21. */
  22. protected $_priorities = array(
  23. 'emergency' => 2,
  24. 'alert' => 1,
  25. 'critical' => 1,
  26. 'error' => 1,
  27. 'warning' => 0,
  28. 'notice' => -1,
  29. 'info' => -2,
  30. 'debug' => -2
  31. );
  32. /**
  33. * The Growl protocol version used to send messages.
  34. */
  35. const PROTOCOL_VERSION = 1;
  36. /**
  37. * There are two types of messages sent to Growl: one to register applications, and one to send
  38. * notifications. This type registers the application with Growl's settings.
  39. */
  40. const TYPE_REG = 0;
  41. /**
  42. * This message type is for sending notifications to Growl.
  43. */
  44. const TYPE_NOTIFY = 1;
  45. /**
  46. * Holds the connection resource used to send messages to Growl.
  47. *
  48. * @var resource
  49. */
  50. protected $_connection = null;
  51. /**
  52. * Flag indicating whether the logger has successfully registered with the Growl server.
  53. * Registration only needs to happen once, but may fail for several reasons, including inability
  54. * to connect to the server, or the server requires a password which has not been specified.
  55. *
  56. * @var boolean
  57. */
  58. protected $_registered = false;
  59. /**
  60. * Allow the Growl connection resource to be auto-configured from constructor parameters.
  61. *
  62. * @var array
  63. */
  64. protected $_autoConfig = array('connection', 'registered');
  65. /**
  66. * Growl logger constructor. Accepts an array of settings which are merged with the default
  67. * settings and used to create the connection and handle notifications.
  68. *
  69. * @see lithium\analysis\Logger::write()
  70. * @param array $config The settings to configure the logger. Available settings are as follows:
  71. * - `'name`' _string_: The name of the application as it should appear in Growl's
  72. * system settings. Defaults to the directory name containing your application.
  73. * - `'host'` _string_: The Growl host with which to communicate, usually your
  74. * local machine. Use this setting to send notifications to another machine on
  75. * the network. Defaults to `'127.0.0.1'`.
  76. * - `'port'` _integer_: Port of the host machine. Defaults to the standard Growl
  77. * port, `9887`.
  78. * - `'password'` _string_: Only required if the host machine requires a password.
  79. * If notification or registration fails, check this against the host machine's
  80. * Growl settings.
  81. * - '`protocol'` _string_: Protocol to use when opening socket communication to
  82. * Growl. Defaults to `'udp'`.
  83. * - `'title'` _string_: The default title to display when showing Growl messages.
  84. * The default value is the same as `'name'`, but can be changed on a per-message
  85. * basis by specifying a `'title'` key in the `$options` parameter of
  86. * `Logger::write()`.
  87. * - `'notification'` _array_: A list of message types you wish to register with
  88. * Growl to be able to send. Defaults to `array('Errors', 'Messages')`.
  89. */
  90. public function __construct(array $config = array()) {
  91. $name = basename(LITHIUM_APP_PATH);
  92. $defaults = compact('name') + array(
  93. 'host' => '127.0.0.1',
  94. 'port' => 9887,
  95. 'password' => null,
  96. 'protocol' => 'udp',
  97. 'title' => Inflector::humanize($name),
  98. 'notifications' => array('Errors', 'Messages'),
  99. 'registered' => false
  100. );
  101. parent::__construct($config + $defaults);
  102. }
  103. /**
  104. * Writes `$message` to a new Growl notification.
  105. *
  106. * @param string $type The `Logger`-based priority of the message. This value is mapped to
  107. * a Growl-specific priority value if possible.
  108. * @param string $message Message to be shown.
  109. * @param array $options Any options that are passed to the `notify()` method. See the
  110. * `$options` parameter of `notify()`.
  111. * @return closure Function returning boolean `true` on successful write, `false` otherwise.
  112. */
  113. public function write($type, $message, array $options = array()) {
  114. $_self =& $this;
  115. $_priorities = $this->_priorities;
  116. return function($self, $params) use (&$_self, $_priorities) {
  117. $priority = 0;
  118. $options = $params['options'];
  119. if (isset($options['priority']) && isset($_priorities[$options['priority']])) {
  120. $priority = $_priorities[$options['priority']];
  121. }
  122. return $_self->notify($params['message'], compact('priority') + $options);
  123. };
  124. }
  125. /**
  126. * Posts a new notification to the Growl server.
  127. *
  128. * @param string $description Message to be displayed.
  129. * @param array $options Options consists of:
  130. * -'title': The title of the displayed notification. Displays the
  131. * name of the application's parent folder by default.
  132. * @return boolean Always returns `true`.
  133. */
  134. public function notify($description = '', $options = array()) {
  135. $this->_register();
  136. $defaults = array('sticky' => false, 'priority' => 0, 'type' => 'Messages');
  137. $options += $defaults + array('title' => $this->_config['title']);
  138. $type = $options['type'];
  139. $title = $options['title'];
  140. $message = compact('type', 'title', 'description') + array('app' => $this->_config['name']);
  141. $message = array_map('utf8_encode', $message);
  142. $flags = ($options['priority'] & 7) * 2;
  143. $flags = ($options['priority'] < 0) ? $flags |= 8 : $flags;
  144. $flags = ($options['sticky']) ? $flags | 256 : $flags;
  145. $params = array('c2n5', static::PROTOCOL_VERSION, static::TYPE_NOTIFY, $flags);
  146. $lengths = array_map('strlen', $message);
  147. $data = call_user_func_array('pack', array_merge($params, $lengths));
  148. $data .= join('', $message);
  149. $data .= pack('H32', md5($data . $this->_config['password']));
  150. $this->_send($data);
  151. return true;
  152. }
  153. /**
  154. * Growl server connection registration and initialization.
  155. *
  156. * @return boolean True
  157. */
  158. protected function _register() {
  159. if ($this->_registered) {
  160. return true;
  161. }
  162. $ct = count($this->_config['notifications']);
  163. $app = utf8_encode($this->_config['name']);
  164. $nameEnc = $defaultEnc = '';
  165. foreach ($this->_config['notifications'] as $i => $name) {
  166. $name = utf8_encode($name);
  167. $nameEnc .= pack('n', strlen($name)) . $name;
  168. $defaultEnc .= pack('c', $i);
  169. }
  170. $data = pack('c2nc2', static::PROTOCOL_VERSION, static::TYPE_REG, strlen($app), $ct, $ct);
  171. $data .= $app . $nameEnc . $defaultEnc;
  172. $checksum = pack('H32', md5($data . $this->_config['password']));
  173. $data .= $checksum;
  174. $this->_send($data);
  175. return $this->_registered = true;
  176. }
  177. /**
  178. * Creates a connection to the Growl server using the protocol, host and port configurations
  179. * specified in the constructor.
  180. *
  181. * @return resource Returns a connection resource created by `fsockopen()`.
  182. */
  183. protected function _connection() {
  184. if ($this->_connection) {
  185. return $this->_connection;
  186. }
  187. $host = "{$this->_config['protocol']}://{$this->_config['host']}";
  188. if ($this->_connection = fsockopen($host, $this->_config['port'], $message, $code)) {
  189. return $this->_connection;
  190. }
  191. throw new NetworkException("Growl connection failed: (`{$code}`) `{$message}`.");
  192. }
  193. /**
  194. * Sends binary data to the Growl server.
  195. *
  196. * @throws NetworkException Throws an exception if the server connection could not be written
  197. * to.
  198. * @param string $data The raw binary data to send to the Growl server.
  199. * @return boolean Always returns `true`.
  200. */
  201. protected function _send($data) {
  202. if (fwrite($this->_connection(), $data, strlen($data)) === false) {
  203. throw new NetworkException('Could not send registration to Growl Server.');
  204. }
  205. return true;
  206. }
  207. /**
  208. * Destructor method. Closes and releases the socket connection to Growl.
  209. */
  210. public function __destruct() {
  211. if (is_resource($this->_connection)) {
  212. fclose($this->_connection);
  213. unset($this->_connection);
  214. }
  215. }
  216. }
  217. ?>