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