@@ -26,20 +26,77 @@ def __init__(self, env, data=None):
2626 self .__dict__ = data
2727 self .env = env
2828
29+ @classmethod
30+ def select (cls , env , args = {}):
31+ select = 'SELECT * FROM code_comments_subscriptions'
32+ if len (args ) > 0 :
33+ select += ' WHERE '
34+ criteria = []
35+ for key , value in args .iteritems ():
36+ template = '{0}={1}'
37+ if isinstance (value , str ):
38+ template = '{0}=\' {1}\' '
39+ criteria .append (template .format (key , value ))
40+ select += ' AND ' .join (criteria )
41+ cursor = env .get_read_db ().cursor ()
42+ cursor .execute (select )
43+ for row in cursor :
44+ yield cls ._from_row (env , row )
45+
2946 def insert (self , db = None ):
3047 """
31- Insert a new subscription.
48+ Insert a new subscription. Returns bool to indicate success.
49+ """
50+ if self .id > 0 :
51+ # Already has an id, don't insert
52+ return False
53+ else :
54+ @self .env .with_transaction ()
55+ def do_insert (db ):
56+ cursor = db .cursor ()
57+ insert = ("INSERT INTO code_comments_subscriptions "
58+ "(user, role, type, path, repos, rev, notify) "
59+ "VALUES (%s, %s, %s, %s, %s, %s, %s)" )
60+ values = (self .user , self .role , self .type , self .path ,
61+ self .repos , self .rev , self .notify )
62+ cursor .execute (insert , values )
63+ self .id = db .get_last_id (cursor , 'code_comments_subscriptions' )
64+ return True
65+
66+ def update (self , db = None ):
67+ """
68+ Update an existing subscription. Returns bool to indicate success.
69+ """
70+ if self .id == 0 :
71+ # Doesn't have a valid id, don't update
72+ return False
73+ else :
74+ @self .env .with_transaction ()
75+ def do_update (db ):
76+ cursor = db .cursor ()
77+ update = ("UPDATE code_comments_subscriptions SET "
78+ "user=%s, role=%s, type=%s, path=%s, repos=%s, "
79+ "rev=%s, notify=%s WHERE id=%s" )
80+ values = (self .user , self .role , self .type , self .path ,
81+ self .repos , self .rev , self .notify , self .id )
82+ try :
83+ cursor .execute (update , values )
84+ except db .IntegrityError :
85+ self .env .log .warning ("Subscription update failed." )
86+ return False
87+ return True
88+
89+ def delete (self , db = None ):
90+ """
91+ Delete an existing subscription.
3292 """
33- @self .env .with_transaction (db )
34- def do_insert (db ):
35- cursor = db .cursor ()
36- insert = ("INSERT INTO code_comments_subscriptions "
37- "(user, role, type, path, repos, rev, notify) "
38- "VALUES (%s, %s, %s, %s, %s, %s, %s)" )
39- values = (self .user , self .role , self .type , self .path , self .repos ,
40- self .rev , self .notify )
41- cursor .execute (insert , values )
42- self .id = db .get_last_id (cursor , 'code_comments_subscriptions' )
93+ if self .id > 0 :
94+ @self .env .with_transaction ()
95+ def do_delete (db ):
96+ cursor = db .cursor ()
97+ delete = ("DELETE FROM code_comments_subscriptions WHERE "
98+ "id=%s" )
99+ cursor .execute (delete , (self .id ,))
43100
44101 @classmethod
45102 def _from_row (cls , env , row ):
@@ -48,7 +105,7 @@ def _from_row(cls, env, row):
48105 """
49106 try :
50107 subscription = cls (env )
51- subscription .id = row [0 ]
108+ subscription .id = int ( row [0 ])
52109 subscription .user = row [1 ]
53110 subscription .role = row [2 ]
54111 subscription .type = row [3 ]
@@ -87,7 +144,8 @@ def _from_dict(cls, env, dict_):
87144 return subscription
88145
89146 @classmethod
90- def from_attachment (cls , env , attachment ):
147+ def from_attachment (cls , env , attachment , user = None , role = 'author' ,
148+ notify = 'always' ):
91149 """
92150 Creates a subscription from an Attachment object.
93151 """
@@ -96,42 +154,44 @@ def from_attachment(cls, env, attachment):
96154 attachment .filename )
97155
98156 sub = {
99- 'user' : attachment .author ,
100- 'role' : 'author' ,
157+ 'user' : user or attachment .author ,
158+ 'role' : role ,
101159 'type' : 'attachment' ,
102160 'path' : _path ,
103161 'repos' : '' ,
104162 'rev' : '' ,
105- 'notify' : 'always' ,
163+ 'notify' : notify ,
106164 }
107165 return cls ._from_dict (env , sub )
108166
109167 @classmethod
110- def from_changeset (cls , env , changeset ):
168+ def from_changeset (cls , env , changeset , user = None , role = 'author' ,
169+ notify = 'always' ):
111170 """
112171 Creates a subscription from a Changeset object.
113172 """
114173 sub = {
115- 'user' : changeset .author ,
116- 'role' : 'author' ,
174+ 'user' : user or changeset .author ,
175+ 'role' : role ,
117176 'type' : 'changeset' ,
118177 'path' : '' ,
119178 'repos' : changeset .repos .reponame ,
120179 'rev' : changeset .rev ,
121- 'notify' : 'always' ,
180+ 'notify' : notify ,
122181 }
123182 return cls ._from_dict (env , sub )
124183
125184 @classmethod
126- def from_comment (cls , env , comment ):
185+ def from_comment (cls , env , comment , user = None , role = 'commenter' ,
186+ notify = 'always' ):
127187 """
128188 Creates a subscription from a Comment object.
129189 """
130190 sub = {
131- 'user' : comment .author ,
132- 'role' : 'commenter' ,
191+ 'user' : user or comment .author ,
192+ 'role' : user ,
133193 'type' : comment .type ,
134- 'notify' : 'always'
194+ 'notify' : notify ,
135195 }
136196
137197 # Munge attachments
@@ -160,6 +220,34 @@ def from_comment(cls, env, comment):
160220
161221 return cls ._from_dict (env , sub )
162222
223+ @classmethod
224+ def for_attachment (cls , env , attachment , path = None ):
225+ """
226+ Returns all subscriptions for an attachment. The path can be
227+ overridden.
228+ """
229+ path_template = "/{0}/{1}/{2}"
230+ _path = path or path_template .format (attachment .parent_realm ,
231+ attachment .parent_id ,
232+ attachment .filename )
233+ args = {
234+ 'type' : 'attachment' ,
235+ 'path' : _path ,
236+ }
237+ return cls .select (env , args )
238+
239+ @classmethod
240+ def for_changeset (cls , env , changeset ):
241+ """
242+ Returns all subscriptions for an changeset.
243+ """
244+ args = {
245+ 'type' : 'changeset' ,
246+ 'repos' : changeset .repos .reponame ,
247+ 'rev' : changeset .rev ,
248+ }
249+ return cls .select (env , args )
250+
163251
164252class SubscriptionAdmin (Component ):
165253 """
@@ -217,11 +305,47 @@ class SubscriptionListeners(Component):
217305 def attachment_added (self , attachment ):
218306 Subscription .from_attachment (self .env , attachment )
219307
308+ def attachment_deleted (self , attachment ):
309+ for subscription in Subscription .for_attachment (self .env , attachment ):
310+ subscription .delete ()
311+
312+ def attachment_reparented (self , attachment , old_parent_realm ,
313+ old_parent_id ):
314+ path_template = "/{0}/{1}/{2}"
315+ old_path = path_template .format (old_parent_realm ,
316+ old_parent_id ,
317+ attachment .filename )
318+ new_path = path_template .format (attachment .parent_realm ,
319+ attachment .parent_id ,
320+ attachment .filename )
321+
322+ for subscription in Subscription .for_attachment (self .env , attachment ,
323+ old_path ):
324+ subscription .path = new_path
325+ subscription .update ()
326+
220327 # IRepositoryChangeListener methods
221328
222329 def changeset_added (self , repos , changeset ):
223330 Subscription .from_changeset (self .env , changeset )
224331
332+ def changeset_modified (self , repos , changeset , old_changeset ):
333+ # Handle existing subscriptions
334+ if changeset .author != old_changeset .author :
335+ for subscription in Subscription .for_changeset (self .env ,
336+ changeset ):
337+ if (subscription .user == old_changeset .author and
338+ subscription .role == 'author' ):
339+ subscription .role = 'subscriber'
340+
341+ if subscription .user == changeset .author :
342+ subscription .role = 'author'
343+
344+ subscription .update ()
345+
346+ # Create a new author subscription
347+ Subscription .from_changeset (self .env , changeset )
348+
225349 # ICodeCommentChangeListener methods
226350
227351 def comment_created (self , comment ):
0 commit comments