Changeset 3798
- Timestamp:
- 06/07/08 17:22:33 (7 months ago)
- Files:
-
- growlplugin/0.11/growl/htdocs/css/growl.css (modified) (1 diff)
- growlplugin/0.11/growl/htdocs/images/growl-180.png (added)
- growlplugin/0.11/growl/htdocs/images/growl.png (deleted)
- growlplugin/0.11/growl/notifier.py (modified) (19 diffs)
- growlplugin/0.11/growl/templates/pref_growl.html (modified) (1 diff)
- growlplugin/0.11/growl/web_ui.py (modified) (2 diffs)
- growlplugin/0.11/setup.py (modified) (3 diffs)
Legend:
- Unmodified
- Added
- Removed
- Modified
- Copied
- Moved
growlplugin/0.11/growl/htdocs/css/growl.css
r3781 r3798 1 div#growl-icon { 2 float: right; 3 margin-right: 40px; 4 min-width: 64px; min-height: 64px; 5 /* TODO: ask Growl team if growl icon inclusion is ok in the plugin... 6 and/or remove it */ 7 background: url(../images/growl.png) no-repeat 0 0; 1 div.growl { 2 /* TODO: waiting for Growl team to validate use of the growl image 3 in the plugin... */ 4 /*background: url(../images/growl-180.png) no-repeat 380px -15px;*/ 5 min-height: 150px; 8 6 } 9 7 div.buttons { 8 position: relative; 9 background: transparent; 10 margin-top: -30px; 11 } 10 12 label.growl { 11 13 padding-right: 1em; growlplugin/0.11/growl/notifier.py
r3794 r3798 19 19 from trac.ticket import ITicketChangeListener 20 20 from trac.wiki import IWikiChangeListener 21 from socket import AF_INET, SOCK_DGRAM, SOL_SOCKET, SO_BROADCAST, socket 21 from socket import AF_INET, SOCK_DGRAM, SOL_SOCKET, SO_BROADCAST, \ 22 socket, gaierror, gethostbyname 22 23 from netgrowl import GrowlRegistrationPacket, GrowlNotificationPacket 23 24 … … 37 38 payload = growlpacket.payload() 38 39 broadcast = False 40 # we do not want a Growl notification failure to be dispatched 41 # to the web client, so any error is catched, logged and ignored 39 42 for host in hosts: 40 43 if host != '<broadcast>': 41 self.log.info("Growl: send to %s" % host) 42 s.sendto(payload, (host, self.GROWL_UDP_PORT)) 44 self.log.debug("Growl: sendto %s" % host) 45 try: 46 s.sendto(payload, (host, self.GROWL_UDP_PORT)) 47 except Exception, e: 48 self.log.error('Grow notification error: %s', e) 43 49 else: 44 50 broadcast = True 45 51 if broadcast: 46 52 self.log.info("Growl: broadcast") 47 s.setsockopt(SOL_SOCKET, SO_BROADCAST, 1) 48 s.sendto(payload, ('<broadcast>', self.GROWL_UDP_PORT)) 53 try: 54 s.setsockopt(SOL_SOCKET, SO_BROADCAST, 1) 55 s.sendto(payload, ('<broadcast>', self.GROWL_UDP_PORT)) 56 except Exception, e: 57 self.log.error('Grow notification error: %s', e) 49 58 s.close() 50 59 … … 70 79 71 80 72 # settings81 # project settings 73 82 userprefs_enabled = BoolOption('growl', 'userprefs', 'false', 74 83 doc="""Enable per-user to define Growl notification option.""") … … 79 88 avail_sources = ListOption('growl', 'sources', ','.join(SOURCES), 80 89 doc="""List of event sources (default: all available sources)""") 81 90 82 91 83 92 # IAttachmentChangeListener Interface … … 90 99 title='Attachment added', 91 100 description=attachment.title) 92 self._notify(self._get_hosts('attachment'), gnp) 101 gs = GrowlSender(self.env) 102 gs.notify(self._get_hosts('attachment'), gnp) 93 103 94 104 def attachment_deleted(self, attachment): … … 99 109 title='Attachment deleted', 100 110 description=attachment.title) 101 self._notify(self._get_hosts('attachment'), gnp) 111 gs = GrowlSender(self.env) 112 gs.notify(self._get_hosts('attachment'), gnp) 102 113 103 114 … … 111 122 title='Ticket #%d created' % ticket.id, 112 123 description=self._ticket_repr(ticket)) 113 self._notify(self._get_hosts('ticket'), gnp) 124 gs = GrowlSender(self.env) 125 gs.notify(self._get_hosts('ticket'), gnp) 114 126 115 127 def ticket_changed(self, ticket, comment, author, old_values): … … 120 132 title='Ticket #%d updated' % ticket.id, 121 133 description=self._ticket_repr(ticket)) 122 self._notify(self._get_hosts('ticket'), gnp) 134 gs = GrowlSender(self.env) 135 gs.notify(self._get_hosts('ticket'), gnp) 123 136 124 137 def ticket_deleted(self, ticket): … … 129 142 title='Ticket #%d deleted' % ticket.id, 130 143 description=self._ticket_repr(ticket)) 131 self._notify(self._get_hosts('ticket'), gnp) 144 gs = GrowlSender(self.env) 145 gs.notify(self._get_hosts('ticket'), gnp) 132 146 133 147 … … 141 155 title='Page created', 142 156 description=page.name) 143 self._notify(self._get_hosts('wiki'), gnp) 157 gs = GrowlSender(self.env) 158 gs.notify(self._get_hosts('wiki'), gnp) 144 159 145 160 def wiki_page_changed(self, page, version, t, comment, author, ipnr): … … 151 166 description=self._wiki_repr(page, 152 167 comment)) 153 self._notify(self._get_hosts('wiki'), gnp) 168 gs = GrowlSender(self.env) 169 gs.notify(self._get_hosts('wiki'), gnp) 154 170 155 171 def wiki_page_deleted(self, page): … … 160 176 title='Page deleted', 161 177 description=self._wiki_repr(page)) 162 self._notify(self._get_hosts('wiki'), gnp) 178 gs = GrowlSender(self.env) 179 gs.notify(self._get_hosts('wiki'), gnp) 163 180 164 181 def wiki_page_version_deleted(self, page): … … 169 186 title='Page suppressed', 170 187 description=self._wiki_repr(page)) 171 self._notify(self._get_hosts('wiki'), gnp) 188 gs = GrowlSender(self.env) 189 gs.notify(self._get_hosts('wiki'), gnp) 172 190 173 191 … … 182 200 description=self._bitten_repr(build), 183 201 priority=-2) 184 self._notify(self._get_hosts('bitten'), gnp) 202 gs = GrowlSender(self.env) 203 gs.notify(self._get_hosts('bitten'), gnp) 185 204 186 205 def build_aborted(build): … … 191 210 title='Build aborted', 192 211 description=self._bitten_repr(build)) 193 self._notify(self._get_hosts('bitten'), gnp) 212 gs = GrowlSender(self.env) 213 gs.notify(self._get_hosts('bitten'), gnp) 194 214 195 215 def build_completed(build): … … 205 225 sticky=failure, 206 226 priority=failure and 2 or 0) 207 self._notify(self._get_hosts('bitten'), gnp) 227 gs = GrowlSender(self.env) 228 gs.notify(self._get_hosts('bitten'), gnp) 208 229 209 230 … … 223 244 for n in self.avail_sources: 224 245 grp.addNotification(n, n in self.sources) 225 self._notify(hosts, grp) 246 gs = GrowlSender(self.env) 247 gs.notify(hosts, grp) 226 248 227 249 def validate_host(self, admin, host): 228 250 if host == '<broadcast>': 229 raise PermissionError("Broadcast: GROWL_ADMIN") 251 if not admin: 252 raise PermissionError('Broadcast: GROWL_ADMIN') 253 return True 230 254 # TODO: implement host validation 255 try: 256 r = gethostbyname(host) 257 self.log.info("Address of %s: %s" % (host, r)) 258 except gaierror: 259 raise TracError("Host '%s' is invalid" % host) 231 260 return True 232 261 … … 239 268 self.register_notifications(self.hosts) 240 269 241 def _notify(self, hosts, gp):242 """Wrapper to notify growl clients"""243 try:244 # we do not want a Growl notification failure to be dispatched245 # to the web client246 gs = GrowlSender(self.env)247 gs.notify(hosts, gp)248 except IOError, e:249 self.log.error('Grow notification error: %s', e)250 251 270 def _ticket_repr(self, ticket): 252 271 """String representation of a Trac ticket""" … … 272 291 def _get_hosts(self, source): 273 292 # get user-specific hosts 274 hosts = self._get_user s_hosts()293 hosts = self._get_user_hosts(source) 275 294 # add hosts defined in the project config, removing duplicates 276 295 hosts.extend([h for h in self.hosts if h not in hosts]) 296 return hosts 277 297 278 298 def _get_user_hosts(self, source): 279 299 db = self.env.get_db_cnx() 280 300 cursor = db.cursor() 281 cursor.execute("SELECT DISTINCT H.value " \282 "FROM session_attribute src, session_attribute h" \283 "WHERE (S.name=%s AND S.value='1') " \284 "AND H.name='growl.host' AND S.sid=H.sid", 285 ( 'growl.source.%s' % source),)301 cursor.execute("SELECT DISTINCT H.value " \ 302 "FROM session_attribute S, session_attribute H " \ 303 "WHERE (S.name=%s AND S.value='1') " \ 304 "AND H.name='growl.host' AND S.sid=H.sid", 305 ("growl.source.%s" % source,)) 286 306 hosts = [] 287 for host in cursor:307 for host, in cursor: 288 308 if host: 289 309 hosts.append(host) 290 self.log. info("Hosts for %s: %s" % (source, hosts))310 self.log.debug("Hosts for %s: %s" % (source, hosts)) 291 311 # filter out empty hosts 292 312 return filter(None, hosts) growlplugin/0.11/growl/templates/pref_growl.html
r3781 r3798 12 12 13 13 <div class="field growl"> 14 <p class="hint">Trac notifies Growl-enabled clients.</p> 14 <p class="hint">Notify remote Growl clients when Trac events occur.<br/> 15 This plugin sends notifications onto standard Growl UDP port 9887.</p> 15 16 <div id="growl-icon"></div> 16 17 <table> 17 18 <tr class="field"> 18 <th><label for="host"> Host address:</label></th>19 <th><label for="host">Remote host address:</label></th> 19 20 <td><input type="text" id="host" name="host" size="30" 20 21 value="${host}" /></td> growlplugin/0.11/growl/web_ui.py
r3794 r3798 56 56 self.log.info("Growl: User notifications not enabled") 57 57 return 58 if not req.perm.has_permission('GROWL_MODIFY'):58 if 'GROWL_MODIFY' not in req.perm: 59 59 self.log.info("Growl: User does not have GROWL_MODIFY permission") 60 60 return … … 67 67 68 68 if req.method == 'POST': 69 if not req.perm.has_permission('GROWL_MODIFY'):69 if 'GROWL_MODIFY' not in req.perm: 70 70 raise PermissionError("No permission to change Growl settings") 71 71 host = req.args.get('host') 72 if notifier.validate_host(req.perm.has_permission('GROWL_ADMIN'), 73 host): 72 if notifier.validate_host('GROWL_ADMIN' in req.perm, host): 74 73 req.session['growl.host'] = host 75 74 # send a registration request to the host growlplugin/0.11/setup.py
r3781 r3798 16 16 17 17 PACKAGE = 'TracGrowlPlugin' 18 VERSION = '0. 1.0'18 VERSION = '0.2.0' 19 19 20 20 setup ( … … 31 31 package_data={ 32 32 'revtree': [ 33 'htdocs/css/*.css', 33 34 'htdocs/images/*.png', 34 35 'templates/*.html' … … 38 39 'trac.plugins': [ 39 40 'growl.notifier = growl.notifier', 41 'growl.web_ui = growl.web_ui' 40 42 ] 41 43 }
