source: announcerplugin/trunk/announcerplugin/subscribers/watchers.py @ 7280

Last change on this file since 7280 was 7138, checked in by Robert Corsaro, 14 years ago

reverse ctxtnames and check for exist
fixes #5682

File size: 9.1 KB
Line 
1import re
2from trac.core import *
3from trac.config import ListOption
4from trac.web.api import IRequestFilter, IRequestHandler, Href
5from trac.web.chrome import ITemplateProvider, add_ctxtnav, add_stylesheet, \
6                            add_script
7from trac.resource import get_resource_url
8from trac.ticket.api import ITicketChangeListener
9from trac.wiki.api import IWikiChangeListener
10from trac.util.text import to_unicode
11from genshi.builder import tag
12from announcerplugin.api import IAnnouncementSubscriber
13
14class WatchSubscriber(Component):
15
16    implements(IRequestFilter, IRequestHandler, IAnnouncementSubscriber,
17        ITicketChangeListener, IWikiChangeListener)
18
19    watchable_paths = ListOption('announcer', 'watchable_paths', 
20        'wiki/*,ticket/*',
21        doc='List of URL paths to allow watching. Globs are supported.')
22    ctxtnav_names = ListOption('announcer', 'ctxtnav_names',
23        ['Watch This','Unwatch This'],
24        doc="Text of context navigation entries. "
25            "An empty list removes them from the context navigation bar.")
26
27    path_match = re.compile(r'/watch/(.*)')
28
29    # IRequestHandler methods
30    def match_request(self, req):
31        if self.path_match.match(req.path_info):
32            realm = self.normalise_resource(req.path_info).split('/')[1]
33            return "%s_VIEW" % realm.upper() in req.perm
34        return False
35
36    def process_request(self, req):
37        match = self.path_match.match(req.path_info)
38        resource = self.normalise_resource(match.groups()[0])
39        realm, _ = resource.split('/', 1)
40        req.perm.require('%s_VIEW' % realm.upper())
41        self.toggle_watched(req.session.sid, (not req.authname == \
42                'anonymous') and 1 or 0, resource, req)
43        req.redirect(req.href(resource))
44
45    def toggle_watched(self, sid, authenticated, resource, req=None):
46        realm, resource = resource.split('/', 1)
47        if self.is_watching(sid, authenticated, realm, resource):
48            self.set_unwatch(sid, authenticated, realm, resource)
49            self._schedule_notice(req, 'You are no longer receiving ' \
50                    'change notifications about this resource.')
51        else:
52            self.set_watch(sid, authenticated, realm, resource)
53            self._schedule_notice(req, 'You are now receiving ' \
54                    'change notifications about this resource.')
55           
56    def _schedule_notice(self, req, message):
57        req.session['_announcer_watch_message_'] = message
58                   
59    def _add_notice(self, req):
60        if '_announcer_watch_message_' in req.session:
61            from trac.web.chrome import add_notice
62            add_notice(req, req.session['_announcer_watch_message_'])
63            del req.session['_announcer_watch_message_']
64                   
65    def is_watching(self, sid, authenticated, realm, resource):
66        db = self.env.get_db_cnx()
67        cursor = db.cursor()
68        cursor.execute("""
69            SELECT id
70              FROM subscriptions
71             WHERE sid=%s AND authenticated=%s
72               AND enabled=1 AND managed=%s
73               AND realm=%s
74               AND category=%s
75               AND rule=%s
76        """, (sid, int(authenticated), 'watcher', realm, '*', 
77                to_unicode(resource)))
78        result = cursor.fetchone()
79        if result:
80            return True
81        else:
82            return False
83   
84    def set_watch(self, sid, authenticated, realm, resource):
85        db = self.env.get_db_cnx()
86        cursor = db.cursor()
87        self.set_unwatch(sid, authenticated, realm, resource, use_db=db)
88        cursor.execute("""
89            INSERT INTO subscriptions
90                        (sid, authenticated,
91                         enabled, managed,
92                         realm, category,
93                         rule, transport)
94                 VALUES
95                        (%s, %s,
96                         1, %s,
97                         %s, %s,
98                         %s, %s)
99        """, (
100                sid, int(authenticated), 
101                'watcher', realm, '*', 
102                resource, 'email'
103            )
104        )
105        db.commit()
106       
107    def set_unwatch(self, sid, authenticated, realm, resource, use_db=None):
108        if not use_db:
109            db = self.env.get_db_cnx()
110        else:
111            db = use_db
112        cursor = db.cursor()
113        cursor.execute("""
114            DELETE
115              FROM subscriptions
116             WHERE sid=%s AND authenticated=%s
117               AND enabled=1 AND managed=%s
118               AND realm=%s
119               AND category=%s
120               AND rule=%s
121        """, (sid, int(authenticated), 'watcher', realm, '*', 
122            to_unicode(resource)))
123        if not use_db:
124            db.commit()
125           
126    # IRequestFilter methods
127    def pre_process_request(self, req, handler):
128        return handler
129       
130    def post_process_request(self, req, template, data, content_type):
131        self._add_notice(req)
132       
133        if req.authname != "anonymous" or (req.authname == 'anonymous' and \
134                'email' in req.session):
135            for pattern in self.watchable_paths:
136                path = self.normalise_resource(req.path_info)
137                if re.match(pattern, path):
138                    realm, _ = path.split('/', 1)
139                    if '%s_VIEW'%realm.upper() not in req.perm:
140                        return (template, data, content_type)
141                    self.render_watcher(req)
142                    break
143        return (template, data, content_type)
144
145    # Internal methods
146    def render_watcher(self, req):
147        if not self.ctxtnav_names:
148          return
149        resource = self.normalise_resource(req.path_info)
150        realm, resource = resource.split('/', 1)
151        if self.is_watching(req.session.sid, not req.authname == 'anonymous', 
152                realm, resource):
153            action_name = len(self.ctxtnav_names) >= 2 and \
154                    self.ctxtnav_names[1] or 'Unwatch This'
155        else:
156            action_name = len(self.ctxtnav_names) and \
157                    self.ctxtnav_names[0] or 'Watch This'
158        add_ctxtnav(req, 
159            tag.a(
160                action_name, href=req.href.watch(realm, resource)
161            )
162        )
163
164    def normalise_resource(self, resource):
165        if isinstance(resource, basestring):
166            resource = resource.strip('/')
167            # Special-case start page
168            if not resource:
169                resource = "wiki/WikiStart"
170            elif resource == 'wiki':
171                resource += '/WikiStart'
172            return resource
173        return get_resource_url(self.env, resource, Href('')).strip('/')
174       
175    # IWikiChangeListener
176    def wiki_page_added(*args):
177        pass
178       
179    def wiki_page_changed(*args):
180        pass
181       
182    def wiki_page_deleted(self, page):
183        db = self.env.get_db_cnx()
184        cursor = db.cursor()
185        cursor.execute("""
186            DELETE
187              FROM subscriptions
188             WHERE managed=%s
189               AND realm=%s
190               AND rule=%s
191        """, ('watcher', 'wiki', to_unicode(page.name)))
192        db.commit()
193
194    def wiki_page_version_deleted(*args):
195        pass
196
197    # ITicketChangeListener
198    def ticket_created(*args):
199        pass
200       
201    def ticket_changed(*args):
202        pass
203       
204    def ticket_deleted(self, ticket):
205        db = self.env.get_db_cnx()
206        cursor = db.cursor()
207        cursor.execute("""
208            DELETE
209              FROM subscriptions
210             WHERE managed=%s
211               AND realm=%s
212               AND rule=%s
213        """, ('watcher', 'ticket', to_unicode(ticket.id)))
214        db.commit()
215   
216    # IAnnouncementSubscriber   
217    def get_subscription_realms(self):
218        return ('wiki', 'ticket')
219       
220    def get_subscription_categories(self, realm):
221        return ('created', 'changed', 'attachment added')
222       
223    def get_subscriptions_for_event(self, event):
224        if event.realm in self.get_subscription_realms():
225            if event.category in self.get_subscription_categories(event.realm):
226                db = self.env.get_db_cnx()
227                cursor = db.cursor()
228                cursor.execute("""
229                    SELECT transport, sid, authenticated
230                      FROM subscriptions
231                     WHERE enabled=1 AND managed=%s
232                       AND realm=%s
233                       AND category=%s
234                       AND rule=%s
235                """, ('watcher', event.realm, '*', 
236                    to_unicode(self._get_target_identifier(event.realm, 
237                    event.target))))
238           
239                for transport, sid, authenticated in cursor.fetchall():
240                    self.log.debug("WatchSubscriber added '%s (%s)' because " \
241                        "of rule: watched"%(sid,authenticated and \
242                        'authenticated' or 'not authenticated'))
243                    yield (transport, sid, authenticated, None)
244                   
245    def _get_target_identifier(self, realm, target):
246        if realm == "wiki":
247            return target.name
248        elif realm == "ticket":
249            return target.id
250
Note: See TracBrowser for help on using the repository browser.