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