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