~loggerhead-team/loggerhead/trunk-rich

« back to all changes in this revision

Viewing changes to loggerhead/util.py

  • Committer: Max Kanat-Alexander
  • Date: 2010-01-14 00:14:59 UTC
  • mto: This revision was merged to the branch mainline in revision 400.
  • Revision ID: mkanat@everythingsolved.com-20100114001459-dl5piuj7jtrq1zku
Be super-paranoid about releasing the revision_graph_check_lock.

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
1
#
 
2
# Copyright (C) 2008  Canonical Ltd.
 
3
#                     (Authored by Martin Albisetti <argentina@gmail.com)
2
4
# Copyright (C) 2006  Robey Pointer <robey@lag.net>
3
5
# Copyright (C) 2006  Goffredo Baroncelli <kreijack@inwind.it>
4
6
#
17
19
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
18
20
#
19
21
 
20
 
try:
21
 
    from xml.etree import ElementTree as ET
22
 
except ImportError:
23
 
    from elementtree import ElementTree as ET
24
 
 
25
22
import base64
26
23
import cgi
27
24
import datetime
30
27
import struct
31
28
import threading
32
29
import time
33
 
 
 
30
import sys
 
31
import os
 
32
import subprocess
 
33
 
 
34
try:
 
35
    from xml.etree import ElementTree as ET
 
36
except ImportError:
 
37
    from elementtree import ElementTree as ET
 
38
 
 
39
from simpletal.simpleTALUtils import HTMLStructureCleaner
34
40
 
35
41
log = logging.getLogger("loggerhead.controllers")
36
42
 
 
43
 
37
44
def fix_year(year):
38
45
    if year < 70:
39
46
        year += 2000
52
59
# displaydate and approximatedate return an elementtree <span> Element
53
60
# with the full date in a tooltip.
54
61
 
 
62
 
55
63
def date_day(value):
56
64
    return value.strftime('%Y-%m-%d')
57
65
 
58
66
 
59
67
def date_time(value):
60
 
    return value.strftime('%Y-%m-%d %T')
 
68
    if value is not None:
 
69
        return value.strftime('%Y-%m-%d %T')
 
70
    else:
 
71
        return 'N/A'
61
72
 
62
73
 
63
74
def _displaydate(date):
118
129
    return _wrap_with_date_time_title(date, _displaydate(date))
119
130
 
120
131
 
121
 
class Container (object):
 
132
class Container(object):
122
133
    """
123
134
    Convert a dict into an object with attributes.
124
135
    """
 
136
 
125
137
    def __init__(self, _dict=None, **kw):
126
138
        if _dict is not None:
127
139
            for key, value in _dict.iteritems():
132
144
    def __repr__(self):
133
145
        out = '{ '
134
146
        for key, value in self.__dict__.iteritems():
135
 
            if key.startswith('_') or (getattr(self.__dict__[key], '__call__', None) is not None):
 
147
            if key.startswith('_') or (getattr(self.__dict__[key],
 
148
                                       '__call__', None) is not None):
136
149
                continue
137
150
            out += '%r => %r, ' % (key, value)
138
151
        out += '}'
148
161
STANDARD_PATTERN = re.compile(r'^(.*?)\s*<(.*?)>\s*$')
149
162
EMAIL_PATTERN = re.compile(r'[-\w\d\+_!%\.]+@[-\w\d\+_!%\.]+')
150
163
 
 
164
 
151
165
def hide_email(email):
152
166
    """
153
167
    try to obsure any email address in a bazaar committer's name.
167
181
        return '%s at %s' % (username, domains[-2])
168
182
    return '%s at %s' % (username, domains[0])
169
183
 
 
184
def hide_emails(emails):
 
185
    """
 
186
    try to obscure any email address in a list of bazaar committers' names.
 
187
    """
 
188
    result = []
 
189
    for email in emails:
 
190
        result.append(hide_email(email))
 
191
    return result
170
192
 
171
193
# only do this if unicode turns out to be a problem
172
194
#_BADCHARS_RE = re.compile(ur'[\u007f-\uffff]')
173
195
 
174
196
# FIXME: get rid of this method; use fixed_width() and avoid XML().
 
197
 
 
198
 
175
199
def html_clean(s):
176
200
    """
177
201
    clean up a string for html display.  expand any tabs, encode any html
183
207
    return s
184
208
 
185
209
 
186
 
 
187
210
NONBREAKING_SPACE = u'\N{NO-BREAK SPACE}'
188
211
 
 
212
 
 
213
def fill_div(s):
 
214
    """
 
215
    CSS is stupid. In some cases we need to replace an empty value with
 
216
    a non breaking space (&nbsp;). There has to be a better way of doing this.
 
217
 
 
218
    return: the same value recieved if not empty, and a '&nbsp;' if it is.
 
219
    """
 
220
    if s is None:
 
221
        return '&nbsp;'
 
222
    elif isinstance(s, int):
 
223
        return s
 
224
    elif not s.strip():
 
225
        return '&nbsp;'
 
226
    else:
 
227
        try:
 
228
            s = s.decode('utf-8')
 
229
        except UnicodeDecodeError:
 
230
            s = s.decode('iso-8859-15')
 
231
        return s
 
232
 
 
233
HSC = HTMLStructureCleaner()
 
234
 
189
235
def fixed_width(s):
190
236
    """
191
237
    expand tabs and turn spaces into "non-breaking spaces", so browsers won't
201
247
            s = s.decode('utf-8')
202
248
        except UnicodeDecodeError:
203
249
            s = s.decode('iso-8859-15')
204
 
    return s.expandtabs().replace(' ', NONBREAKING_SPACE)
 
250
 
 
251
    s = cgi.escape(s).expandtabs().replace(' ', NONBREAKING_SPACE)
 
252
 
 
253
    return HSC.clean(s).replace('\n', '<br/>')
205
254
 
206
255
 
207
256
def fake_permissions(kind, executable):
240
289
P95_MEG = int(0.9 * MEG)
241
290
P95_GIG = int(0.9 * GIG)
242
291
 
 
292
 
243
293
def human_size(size, min_divisor=0):
244
294
    size = int(size)
245
295
    if (size == 0) and (min_divisor == 0):
285
335
        navigation.position = 0
286
336
    navigation.count = len(navigation.revid_list)
287
337
    navigation.page_position = navigation.position // navigation.pagesize + 1
288
 
    navigation.page_count = (len(navigation.revid_list) + (navigation.pagesize - 1)) // navigation.pagesize
 
338
    navigation.page_count = (len(navigation.revid_list) + (navigation.pagesize\
 
339
 - 1)) // navigation.pagesize
289
340
 
290
341
    def get_offset(offset):
291
 
        if (navigation.position + offset < 0) or (navigation.position + offset > navigation.count - 1):
 
342
        if (navigation.position + offset < 0) or (
 
343
           navigation.position + offset > navigation.count - 1):
292
344
            return None
293
345
        return navigation.revid_list[navigation.position + offset]
294
346
 
 
347
    navigation.last_in_page_revid = get_offset(navigation.pagesize - 1)
295
348
    navigation.prev_page_revid = get_offset(-1 * navigation.pagesize)
296
349
    navigation.next_page_revid = get_offset(1 * navigation.pagesize)
297
350
    prev_page_revno = navigation.history.get_revno(
300
353
            navigation.next_page_revid)
301
354
    start_revno = navigation.history.get_revno(navigation.start_revid)
302
355
 
303
 
    params = { 'filter_file_id': navigation.filter_file_id }
 
356
    params = {'filter_file_id': navigation.filter_file_id}
304
357
    if getattr(navigation, 'query', None) is not None:
305
358
        params['q'] = navigation.query
306
359
 
315
368
            [navigation.scan_url, next_page_revno], **params)
316
369
 
317
370
 
 
371
def directory_breadcrumbs(path, is_root, view):
 
372
    """
 
373
    Generate breadcrumb information from the directory path given
 
374
 
 
375
    The path given should be a path up to any branch that is currently being
 
376
    served
 
377
 
 
378
    Arguments:
 
379
    path -- The path to convert into breadcrumbs
 
380
    is_root -- Whether or not loggerhead is serving a branch at its root
 
381
    view -- The type of view we are showing (files, changes etc)
 
382
    """
 
383
    # Is our root directory itself a branch?
 
384
    if is_root:
 
385
        breadcrumbs = [{
 
386
            'dir_name': path,
 
387
            'path': '',
 
388
            'suffix': view,
 
389
        }]
 
390
    else:
 
391
        # Create breadcrumb trail for the path leading up to the branch
 
392
        breadcrumbs = [{
 
393
            'dir_name': "(root)",
 
394
            'path': '',
 
395
            'suffix': '',
 
396
        }]
 
397
        if path != '/':
 
398
            dir_parts = path.strip('/').split('/')
 
399
            for index, dir_name in enumerate(dir_parts):
 
400
                breadcrumbs.append({
 
401
                    'dir_name': dir_name,
 
402
                    'path': '/'.join(dir_parts[:index + 1]),
 
403
                    'suffix': '',
 
404
                })
 
405
            # If we are not in the directory view, the last crumb is a branch,
 
406
            # so we need to specify a view
 
407
            if view != 'directory':
 
408
                breadcrumbs[-1]['suffix'] = '/' + view
 
409
    return breadcrumbs
 
410
 
 
411
 
 
412
def branch_breadcrumbs(path, inv, view):
 
413
    """
 
414
    Generate breadcrumb information from the branch path given
 
415
 
 
416
    The path given should be a path that exists within a branch
 
417
 
 
418
    Arguments:
 
419
    path -- The path to convert into breadcrumbs
 
420
    inv -- Inventory to get file information from
 
421
    view -- The type of view we are showing (files, changes etc)
 
422
    """
 
423
    dir_parts = path.strip('/').split('/')
 
424
    inner_breadcrumbs = []
 
425
    for index, dir_name in enumerate(dir_parts):
 
426
        inner_breadcrumbs.append({
 
427
            'dir_name': dir_name,
 
428
            'file_id': inv.path2id('/'.join(dir_parts[:index + 1])),
 
429
            'suffix': '/' + view,
 
430
        })
 
431
    return inner_breadcrumbs
 
432
 
 
433
 
318
434
def decorator(unbound):
 
435
 
319
436
    def new_decorator(f):
320
437
        g = unbound(f)
321
438
        g.__name__ = f.__name__
328
445
    return new_decorator
329
446
 
330
447
 
331
 
# common threading-lock decorator
332
 
def with_lock(lockname, debug_name=None):
333
 
    if debug_name is None:
334
 
        debug_name = lockname
335
 
    @decorator
336
 
    def _decorator(unbound):
337
 
        def locked(self, *args, **kw):
338
 
            getattr(self, lockname).acquire()
339
 
            try:
340
 
                return unbound(self, *args, **kw)
341
 
            finally:
342
 
                getattr(self, lockname).release()
343
 
        return locked
344
 
    return _decorator
345
 
 
346
448
 
347
449
@decorator
348
450
def lsprof(f):
 
451
 
349
452
    def _f(*a, **kw):
350
453
        from loggerhead.lsprof import profile
351
454
        import cPickle
352
455
        z = time.time()
353
456
        ret, stats = profile(f, *a, **kw)
354
 
        log.debug('Finished profiled %s in %d msec.' % (f.__name__, int((time.time() - z) * 1000)))
 
457
        log.debug('Finished profiled %s in %d msec.' % (f.__name__,
 
458
            int((time.time() - z) * 1000)))
355
459
        stats.sort()
356
460
        stats.freeze()
357
461
        now = time.time()
358
462
        msec = int(now * 1000) % 1000
359
 
        timestr = time.strftime('%Y%m%d%H%M%S', time.localtime(now)) + ('%03d' % msec)
 
463
        timestr = time.strftime('%Y%m%d%H%M%S',
 
464
                                time.localtime(now)) + ('%03d' % (msec,))
360
465
        filename = f.__name__ + '-' + timestr + '.lsprof'
361
466
        cPickle.dump(stats, open(filename, 'w'), 2)
362
467
        return ret
418
523
    overrides = dict((k, v) for (k, v) in overrides.iteritems() if k in _valid)
419
524
    map.update(overrides)
420
525
    return map
 
526
 
 
527
 
 
528
class Reloader(object):
 
529
    """
 
530
    This class wraps all paste.reloader logic. All methods are @classmethod.
 
531
    """
 
532
 
 
533
    _reloader_environ_key = 'PYTHON_RELOADER_SHOULD_RUN'
 
534
 
 
535
    @classmethod
 
536
    def _turn_sigterm_into_systemexit(cls):
 
537
        """
 
538
        Attempts to turn a SIGTERM exception into a SystemExit exception.
 
539
        """
 
540
        try:
 
541
            import signal
 
542
        except ImportError:
 
543
            return
 
544
 
 
545
        def handle_term(signo, frame):
 
546
            raise SystemExit
 
547
        signal.signal(signal.SIGTERM, handle_term)
 
548
 
 
549
    @classmethod
 
550
    def is_installed(cls):
 
551
        return os.environ.get(cls._reloader_environ_key)
 
552
 
 
553
    @classmethod
 
554
    def install(cls):
 
555
        from paste import reloader
 
556
        reloader.install(int(1))
 
557
 
 
558
    @classmethod
 
559
    def restart_with_reloader(cls):
 
560
        """Based on restart_with_monitor from paste.script.serve."""
 
561
        print 'Starting subprocess with file monitor'
 
562
        while True:
 
563
            args = [sys.executable] + sys.argv
 
564
            new_environ = os.environ.copy()
 
565
            new_environ[cls._reloader_environ_key] = 'true'
 
566
            proc = None
 
567
            try:
 
568
                try:
 
569
                    cls._turn_sigterm_into_systemexit()
 
570
                    proc = subprocess.Popen(args, env=new_environ)
 
571
                    exit_code = proc.wait()
 
572
                    proc = None
 
573
                except KeyboardInterrupt:
 
574
                    print '^C caught in monitor process'
 
575
                    return 1
 
576
            finally:
 
577
                if (proc is not None
 
578
                    and getattr(os, 'kill', None) is not None):
 
579
                    import signal
 
580
                    try:
 
581
                        os.kill(proc.pid, signal.SIGTERM)
 
582
                    except (OSError, IOError):
 
583
                        pass
 
584
 
 
585
            # Reloader always exits with code 3; but if we are
 
586
            # a monitor, any exit code will restart
 
587
            if exit_code != 3:
 
588
                return exit_code
 
589
            print '-'*20, 'Restarting', '-'*20
 
590
 
 
591
 
 
592
def convert_file_errors(application):
 
593
    """WSGI wrapper to convert some file errors to Paste exceptions"""
 
594
    def new_application(environ, start_response):
 
595
        try:
 
596
            return application(environ, start_response)
 
597
        except (IOError, OSError), e:
 
598
            import errno
 
599
            from paste import httpexceptions
 
600
            if e.errno == errno.ENOENT:
 
601
                raise httpexceptions.HTTPNotFound()
 
602
            elif e.errno == errno.EACCES:
 
603
                raise httpexceptions.HTTPForbidden()
 
604
            else:
 
605
                raise
 
606
    return new_application