##// END OF EJS Templates
changes transient to expunge on cached repo instances, due to odd very rare problems with previos approach.
marcink -
r753:ee801aa6 beta
parent child Browse files
Show More
@@ -1,347 +1,349 b''
1 #!/usr/bin/env python
1 #!/usr/bin/env python
2 # encoding: utf-8
2 # encoding: utf-8
3 # Model for RhodeCode
3 # Model for RhodeCode
4 # Copyright (C) 2009-2010 Marcin Kuzminski <marcin@python-works.com>
4 # Copyright (C) 2009-2010 Marcin Kuzminski <marcin@python-works.com>
5 #
5 #
6 # This program is free software; you can redistribute it and/or
6 # This program is free software; you can redistribute it and/or
7 # modify it under the terms of the GNU General Public License
7 # modify it under the terms of the GNU General Public License
8 # as published by the Free Software Foundation; version 2
8 # as published by the Free Software Foundation; version 2
9 # of the License or (at your opinion) any later version of the license.
9 # of the License or (at your opinion) any later version of the license.
10 #
10 #
11 # This program is distributed in the hope that it will be useful,
11 # This program is distributed in the hope that it will be useful,
12 # but WITHOUT ANY WARRANTY; without even the implied warranty of
12 # but WITHOUT ANY WARRANTY; without even the implied warranty of
13 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 # GNU General Public License for more details.
14 # GNU General Public License for more details.
15 #
15 #
16 # You should have received a copy of the GNU General Public License
16 # You should have received a copy of the GNU General Public License
17 # along with this program; if not, write to the Free Software
17 # along with this program; if not, write to the Free Software
18 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
18 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
19 # MA 02110-1301, USA.
19 # MA 02110-1301, USA.
20 """
20 """
21 Created on April 9, 2010
21 Created on April 9, 2010
22 Model for RhodeCode
22 Model for RhodeCode
23 @author: marcink
23 @author: marcink
24 """
24 """
25 from beaker.cache import cache_region, region_invalidate
25 from beaker.cache import cache_region, region_invalidate
26 from mercurial import ui
26 from mercurial import ui
27 from rhodecode import BACKENDS
27 from rhodecode import BACKENDS
28 from rhodecode.lib import helpers as h
28 from rhodecode.lib import helpers as h
29 from rhodecode.lib.auth import HasRepoPermissionAny
29 from rhodecode.lib.auth import HasRepoPermissionAny
30 from rhodecode.lib.utils import get_repos, make_ui, action_logger
30 from rhodecode.lib.utils import get_repos, make_ui, action_logger
31 from rhodecode.model import BaseModel
31 from rhodecode.model import BaseModel
32 from rhodecode.model.db import Repository, User, RhodeCodeUi, CacheInvalidation, \
32 from rhodecode.model.db import Repository, User, RhodeCodeUi, CacheInvalidation, \
33 UserFollowing
33 UserFollowing
34 from rhodecode.model.caching_query import FromCache
34 from rhodecode.model.caching_query import FromCache
35 from sqlalchemy.orm import joinedload
35 from sqlalchemy.orm import joinedload
36 from sqlalchemy.orm.session import make_transient
36 from sqlalchemy.orm.session import make_transient
37 from vcs import get_backend
37 from vcs import get_backend
38 from vcs.utils.helpers import get_scm
38 from vcs.utils.helpers import get_scm
39 from vcs.exceptions import RepositoryError, VCSError
39 from vcs.exceptions import RepositoryError, VCSError
40 from vcs.utils.lazy import LazyProperty
40 from vcs.utils.lazy import LazyProperty
41 import traceback
41 import traceback
42 import logging
42 import logging
43 import os
43 import os
44 import time
44 import time
45
45
46 log = logging.getLogger(__name__)
46 log = logging.getLogger(__name__)
47
47
48 class UserTemp(object):
48 class UserTemp(object):
49 def __init__(self, user_id):
49 def __init__(self, user_id):
50 self.user_id = user_id
50 self.user_id = user_id
51
51
52 class RepoTemp(object):
52 class RepoTemp(object):
53 def __init__(self, repo_id):
53 def __init__(self, repo_id):
54 self.repo_id = repo_id
54 self.repo_id = repo_id
55
55
56
56
57 class ScmModel(BaseModel):
57 class ScmModel(BaseModel):
58 """
58 """
59 Mercurial Model
59 Mercurial Model
60 """
60 """
61
61
62 @LazyProperty
62 @LazyProperty
63 def repos_path(self):
63 def repos_path(self):
64 """
64 """
65 Get's the repositories root path from database
65 Get's the repositories root path from database
66 """
66 """
67 q = self.sa.query(RhodeCodeUi).filter(RhodeCodeUi.ui_key == '/').one()
67 q = self.sa.query(RhodeCodeUi).filter(RhodeCodeUi.ui_key == '/').one()
68
68
69 return q.ui_value
69 return q.ui_value
70
70
71 def repo_scan(self, repos_path, baseui, initial=False):
71 def repo_scan(self, repos_path, baseui, initial=False):
72 """
72 """
73 Listing of repositories in given path. This path should not be a
73 Listing of repositories in given path. This path should not be a
74 repository itself. Return a dictionary of repository objects
74 repository itself. Return a dictionary of repository objects
75
75
76 :param repos_path: path to directory containing repositories
76 :param repos_path: path to directory containing repositories
77 :param baseui
77 :param baseui
78 :param initial: initial scan
78 :param initial: initial scan
79 """
79 """
80 log.info('scanning for repositories in %s', repos_path)
80 log.info('scanning for repositories in %s', repos_path)
81
81
82 if not isinstance(baseui, ui.ui):
82 if not isinstance(baseui, ui.ui):
83 baseui = make_ui('db')
83 baseui = make_ui('db')
84 repos_list = {}
84 repos_list = {}
85
85
86 for name, path in get_repos(repos_path):
86 for name, path in get_repos(repos_path):
87 try:
87 try:
88 if repos_list.has_key(name):
88 if repos_list.has_key(name):
89 raise RepositoryError('Duplicate repository name %s '
89 raise RepositoryError('Duplicate repository name %s '
90 'found in %s' % (name, path))
90 'found in %s' % (name, path))
91 else:
91 else:
92
92
93 klass = get_backend(path[0])
93 klass = get_backend(path[0])
94
94
95 if path[0] == 'hg' and path[0] in BACKENDS.keys():
95 if path[0] == 'hg' and path[0] in BACKENDS.keys():
96 repos_list[name] = klass(path[1], baseui=baseui)
96 repos_list[name] = klass(path[1], baseui=baseui)
97
97
98 if path[0] == 'git' and path[0] in BACKENDS.keys():
98 if path[0] == 'git' and path[0] in BACKENDS.keys():
99 repos_list[name] = klass(path[1])
99 repos_list[name] = klass(path[1])
100 except OSError:
100 except OSError:
101 continue
101 continue
102
102
103 return repos_list
103 return repos_list
104
104
105 def get_repos(self, all_repos=None):
105 def get_repos(self, all_repos=None):
106 """
106 """
107 Get all repos from db and for each repo create it's backend instance.
107 Get all repos from db and for each repo create it's backend instance.
108 and fill that backed with information from database
108 and fill that backed with information from database
109
109
110 :param all_repos: give specific repositories list, good for filtering
110 :param all_repos: give specific repositories list, good for filtering
111 """
111 """
112 if not all_repos:
112 if not all_repos:
113 all_repos = self.sa.query(Repository)\
113 all_repos = self.sa.query(Repository)\
114 .order_by(Repository.repo_name).all()
114 .order_by(Repository.repo_name).all()
115
115
116 invalidation_list = [str(x.cache_key) for x in \
116 invalidation_list = [str(x.cache_key) for x in \
117 self.sa.query(CacheInvalidation.cache_key)\
117 self.sa.query(CacheInvalidation.cache_key)\
118 .filter(CacheInvalidation.cache_active == False)\
118 .filter(CacheInvalidation.cache_active == False)\
119 .all()]
119 .all()]
120
120
121 for r in all_repos:
121 for r in all_repos:
122
122
123 repo = self.get(r.repo_name, invalidation_list)
123 repo = self.get(r.repo_name, invalidation_list)
124
124
125 if repo is not None:
125 if repo is not None:
126 last_change = repo.last_change
126 last_change = repo.last_change
127 tip = h.get_changeset_safe(repo, 'tip')
127 tip = h.get_changeset_safe(repo, 'tip')
128
128
129 tmp_d = {}
129 tmp_d = {}
130 tmp_d['name'] = repo.name
130 tmp_d['name'] = repo.name
131 tmp_d['name_sort'] = tmp_d['name'].lower()
131 tmp_d['name_sort'] = tmp_d['name'].lower()
132 tmp_d['description'] = repo.dbrepo.description
132 tmp_d['description'] = repo.dbrepo.description
133 tmp_d['description_sort'] = tmp_d['description']
133 tmp_d['description_sort'] = tmp_d['description']
134 tmp_d['last_change'] = last_change
134 tmp_d['last_change'] = last_change
135 tmp_d['last_change_sort'] = time.mktime(last_change.timetuple())
135 tmp_d['last_change_sort'] = time.mktime(last_change.timetuple())
136 tmp_d['tip'] = tip.raw_id
136 tmp_d['tip'] = tip.raw_id
137 tmp_d['tip_sort'] = tip.revision
137 tmp_d['tip_sort'] = tip.revision
138 tmp_d['rev'] = tip.revision
138 tmp_d['rev'] = tip.revision
139 tmp_d['contact'] = repo.dbrepo.user.full_contact
139 tmp_d['contact'] = repo.dbrepo.user.full_contact
140 tmp_d['contact_sort'] = tmp_d['contact']
140 tmp_d['contact_sort'] = tmp_d['contact']
141 tmp_d['repo_archives'] = list(repo._get_archives())
141 tmp_d['repo_archives'] = list(repo._get_archives())
142 tmp_d['last_msg'] = tip.message
142 tmp_d['last_msg'] = tip.message
143 tmp_d['repo'] = repo
143 tmp_d['repo'] = repo
144 yield tmp_d
144 yield tmp_d
145
145
146 def get_repo(self, repo_name):
146 def get_repo(self, repo_name):
147 return self.get(repo_name)
147 return self.get(repo_name)
148
148
149 def get(self, repo_name, invalidation_list=None):
149 def get(self, repo_name, invalidation_list=None):
150 """
150 """
151 Get's repository from given name, creates BackendInstance and
151 Get's repository from given name, creates BackendInstance and
152 propagates it's data from database with all additional information
152 propagates it's data from database with all additional information
153 :param repo_name:
153 :param repo_name:
154 """
154 """
155 if not HasRepoPermissionAny('repository.read', 'repository.write',
155 if not HasRepoPermissionAny('repository.read', 'repository.write',
156 'repository.admin')(repo_name, 'get repo check'):
156 'repository.admin')(repo_name, 'get repo check'):
157 return
157 return
158
158
159 @cache_region('long_term')
159 @cache_region('long_term')
160 def _get_repo(repo_name):
160 def _get_repo(repo_name):
161
161
162 repo_path = os.path.join(self.repos_path, repo_name)
162 repo_path = os.path.join(self.repos_path, repo_name)
163 alias = get_scm(repo_path)[0]
163 alias = get_scm(repo_path)[0]
164
164
165 log.debug('Creating instance of %s repository', alias)
165 log.debug('Creating instance of %s repository', alias)
166 backend = get_backend(alias)
166 backend = get_backend(alias)
167
167
168 #TODO: get the baseui from somewhere for this
168 #TODO: get the baseui from somewhere for this
169 if alias == 'hg':
169 if alias == 'hg':
170 from pylons import app_globals as g
170 from pylons import app_globals as g
171 repo = backend(repo_path, create=False, baseui=g.baseui)
171 repo = backend(repo_path, create=False, baseui=g.baseui)
172 #skip hidden web repository
172 #skip hidden web repository
173 if repo._get_hidden():
173 if repo._get_hidden():
174 return
174 return
175 else:
175 else:
176 repo = backend(repo_path, create=False)
176 repo = backend(repo_path, create=False)
177
177
178 dbrepo = self.sa.query(Repository)\
178 dbrepo = self.sa.query(Repository)\
179 .options(joinedload(Repository.fork))\
179 .options(joinedload(Repository.fork))\
180 .options(joinedload(Repository.user))\
180 .options(joinedload(Repository.user))\
181 .filter(Repository.repo_name == repo_name)\
181 .filter(Repository.repo_name == repo_name)\
182 .scalar()
182 .scalar()
183 make_transient(dbrepo)
183
184 self.sa.expunge(dbrepo)
185
184 repo.dbrepo = dbrepo
186 repo.dbrepo = dbrepo
185 return repo
187 return repo
186
188
187 pre_invalidate = True
189 pre_invalidate = True
188 if invalidation_list:
190 if invalidation_list:
189 pre_invalidate = repo_name in invalidation_list
191 pre_invalidate = repo_name in invalidation_list
190
192
191 if pre_invalidate:
193 if pre_invalidate:
192 invalidate = self._should_invalidate(repo_name)
194 invalidate = self._should_invalidate(repo_name)
193
195
194 if invalidate:
196 if invalidate:
195 log.info('invalidating cache for repository %s', repo_name)
197 log.info('invalidating cache for repository %s', repo_name)
196 region_invalidate(_get_repo, None, repo_name)
198 region_invalidate(_get_repo, None, repo_name)
197 self._mark_invalidated(invalidate)
199 self._mark_invalidated(invalidate)
198
200
199 return _get_repo(repo_name)
201 return _get_repo(repo_name)
200
202
201
203
202
204
203 def mark_for_invalidation(self, repo_name):
205 def mark_for_invalidation(self, repo_name):
204 """
206 """
205 Puts cache invalidation task into db for
207 Puts cache invalidation task into db for
206 further global cache invalidation
208 further global cache invalidation
207
209
208 :param repo_name: this repo that should invalidation take place
210 :param repo_name: this repo that should invalidation take place
209 """
211 """
210 log.debug('marking %s for invalidation', repo_name)
212 log.debug('marking %s for invalidation', repo_name)
211 cache = self.sa.query(CacheInvalidation)\
213 cache = self.sa.query(CacheInvalidation)\
212 .filter(CacheInvalidation.cache_key == repo_name).scalar()
214 .filter(CacheInvalidation.cache_key == repo_name).scalar()
213
215
214 if cache:
216 if cache:
215 #mark this cache as inactive
217 #mark this cache as inactive
216 cache.cache_active = False
218 cache.cache_active = False
217 else:
219 else:
218 log.debug('cache key not found in invalidation db -> creating one')
220 log.debug('cache key not found in invalidation db -> creating one')
219 cache = CacheInvalidation(repo_name)
221 cache = CacheInvalidation(repo_name)
220
222
221 try:
223 try:
222 self.sa.add(cache)
224 self.sa.add(cache)
223 self.sa.commit()
225 self.sa.commit()
224 except:
226 except:
225 log.error(traceback.format_exc())
227 log.error(traceback.format_exc())
226 self.sa.rollback()
228 self.sa.rollback()
227
229
228
230
229 def toggle_following_repo(self, follow_repo_id, user_id):
231 def toggle_following_repo(self, follow_repo_id, user_id):
230
232
231 f = self.sa.query(UserFollowing)\
233 f = self.sa.query(UserFollowing)\
232 .filter(UserFollowing.follows_repo_id == follow_repo_id)\
234 .filter(UserFollowing.follows_repo_id == follow_repo_id)\
233 .filter(UserFollowing.user_id == user_id).scalar()
235 .filter(UserFollowing.user_id == user_id).scalar()
234
236
235 if f is not None:
237 if f is not None:
236
238
237 try:
239 try:
238 self.sa.delete(f)
240 self.sa.delete(f)
239 self.sa.commit()
241 self.sa.commit()
240 action_logger(UserTemp(user_id),
242 action_logger(UserTemp(user_id),
241 'stopped_following_repo',
243 'stopped_following_repo',
242 RepoTemp(follow_repo_id))
244 RepoTemp(follow_repo_id))
243 return
245 return
244 except:
246 except:
245 log.error(traceback.format_exc())
247 log.error(traceback.format_exc())
246 self.sa.rollback()
248 self.sa.rollback()
247 raise
249 raise
248
250
249
251
250 try:
252 try:
251 f = UserFollowing()
253 f = UserFollowing()
252 f.user_id = user_id
254 f.user_id = user_id
253 f.follows_repo_id = follow_repo_id
255 f.follows_repo_id = follow_repo_id
254 self.sa.add(f)
256 self.sa.add(f)
255 self.sa.commit()
257 self.sa.commit()
256 action_logger(UserTemp(user_id),
258 action_logger(UserTemp(user_id),
257 'started_following_repo',
259 'started_following_repo',
258 RepoTemp(follow_repo_id))
260 RepoTemp(follow_repo_id))
259 except:
261 except:
260 log.error(traceback.format_exc())
262 log.error(traceback.format_exc())
261 self.sa.rollback()
263 self.sa.rollback()
262 raise
264 raise
263
265
264 def toggle_following_user(self, follow_user_id , user_id):
266 def toggle_following_user(self, follow_user_id , user_id):
265 f = self.sa.query(UserFollowing)\
267 f = self.sa.query(UserFollowing)\
266 .filter(UserFollowing.follows_user_id == follow_user_id)\
268 .filter(UserFollowing.follows_user_id == follow_user_id)\
267 .filter(UserFollowing.user_id == user_id).scalar()
269 .filter(UserFollowing.user_id == user_id).scalar()
268
270
269 if f is not None:
271 if f is not None:
270 try:
272 try:
271 self.sa.delete(f)
273 self.sa.delete(f)
272 self.sa.commit()
274 self.sa.commit()
273 return
275 return
274 except:
276 except:
275 log.error(traceback.format_exc())
277 log.error(traceback.format_exc())
276 self.sa.rollback()
278 self.sa.rollback()
277 raise
279 raise
278
280
279 try:
281 try:
280 f = UserFollowing()
282 f = UserFollowing()
281 f.user_id = user_id
283 f.user_id = user_id
282 f.follows_user_id = follow_user_id
284 f.follows_user_id = follow_user_id
283 self.sa.add(f)
285 self.sa.add(f)
284 self.sa.commit()
286 self.sa.commit()
285 except:
287 except:
286 log.error(traceback.format_exc())
288 log.error(traceback.format_exc())
287 self.sa.rollback()
289 self.sa.rollback()
288 raise
290 raise
289
291
290 def is_following_repo(self, repo_name, user_id):
292 def is_following_repo(self, repo_name, user_id):
291 r = self.sa.query(Repository)\
293 r = self.sa.query(Repository)\
292 .filter(Repository.repo_name == repo_name).scalar()
294 .filter(Repository.repo_name == repo_name).scalar()
293
295
294 f = self.sa.query(UserFollowing)\
296 f = self.sa.query(UserFollowing)\
295 .filter(UserFollowing.follows_repository == r)\
297 .filter(UserFollowing.follows_repository == r)\
296 .filter(UserFollowing.user_id == user_id).scalar()
298 .filter(UserFollowing.user_id == user_id).scalar()
297
299
298 return f is not None
300 return f is not None
299
301
300 def is_following_user(self, username, user_id):
302 def is_following_user(self, username, user_id):
301 u = self.sa.query(User)\
303 u = self.sa.query(User)\
302 .filter(User.username == username).scalar()
304 .filter(User.username == username).scalar()
303
305
304 f = self.sa.query(UserFollowing)\
306 f = self.sa.query(UserFollowing)\
305 .filter(UserFollowing.follows_user == u)\
307 .filter(UserFollowing.follows_user == u)\
306 .filter(UserFollowing.user_id == user_id).scalar()
308 .filter(UserFollowing.user_id == user_id).scalar()
307
309
308 return f is not None
310 return f is not None
309
311
310 def get_followers(self, repo_id):
312 def get_followers(self, repo_id):
311 return self.sa.query(UserFollowing)\
313 return self.sa.query(UserFollowing)\
312 .filter(UserFollowing.follows_repo_id == repo_id).count()
314 .filter(UserFollowing.follows_repo_id == repo_id).count()
313
315
314 def get_forks(self, repo_id):
316 def get_forks(self, repo_id):
315 return self.sa.query(Repository)\
317 return self.sa.query(Repository)\
316 .filter(Repository.fork_id == repo_id).count()
318 .filter(Repository.fork_id == repo_id).count()
317
319
318 def _should_invalidate(self, repo_name):
320 def _should_invalidate(self, repo_name):
319 """
321 """
320 Looks up database for invalidation signals for this repo_name
322 Looks up database for invalidation signals for this repo_name
321 :param repo_name:
323 :param repo_name:
322 """
324 """
323
325
324 ret = self.sa.query(CacheInvalidation)\
326 ret = self.sa.query(CacheInvalidation)\
325 .options(FromCache('sql_cache_short',
327 .options(FromCache('sql_cache_short',
326 'get_invalidation_%s' % repo_name))\
328 'get_invalidation_%s' % repo_name))\
327 .filter(CacheInvalidation.cache_key == repo_name)\
329 .filter(CacheInvalidation.cache_key == repo_name)\
328 .filter(CacheInvalidation.cache_active == False)\
330 .filter(CacheInvalidation.cache_active == False)\
329 .scalar()
331 .scalar()
330
332
331 return ret
333 return ret
332
334
333 def _mark_invalidated(self, cache_key):
335 def _mark_invalidated(self, cache_key):
334 """
336 """
335 Marks all occurences of cache to invaldation as already invalidated
337 Marks all occurences of cache to invaldation as already invalidated
336 @param repo_name:
338 @param repo_name:
337 """
339 """
338 if cache_key:
340 if cache_key:
339 log.debug('marking %s as already invalidated', cache_key)
341 log.debug('marking %s as already invalidated', cache_key)
340 try:
342 try:
341 cache_key.cache_active = True
343 cache_key.cache_active = True
342 self.sa.add(cache_key)
344 self.sa.add(cache_key)
343 self.sa.commit()
345 self.sa.commit()
344 except:
346 except:
345 log.error(traceback.format_exc())
347 log.error(traceback.format_exc())
346 self.sa.rollback()
348 self.sa.rollback()
347
349
General Comments 0
You need to be logged in to leave comments. Login now