/loggerhead/trunk

To get this branch, use:
bzr branch http://gegoxaren.bato24.eu/bzr/loggerhead/trunk

« back to all changes in this revision

Viewing changes to loggerhead/util.py

  • Committer: Matt Nordhoff
  • Date: 2010-02-26 04:37:13 UTC
  • mfrom: (400 trunk)
  • mto: This revision was merged to the branch mainline in revision 401.
  • Revision ID: mnordhoff@mattnordhoff.com-20100226043713-7mw3r6dr9qowutmi
Merge trunk for NEWS

Show diffs side-by-side

added added

removed removed

Lines of Context:
19
19
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
20
20
#
21
21
 
22
 
try:
23
 
    from xml.etree import ElementTree as ET
24
 
except ImportError:
25
 
    from elementtree import ElementTree as ET
26
 
 
27
22
import base64
28
23
import cgi
29
24
import datetime
32
27
import struct
33
28
import threading
34
29
import time
35
 
import types
 
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
36
40
 
37
41
log = logging.getLogger("loggerhead.controllers")
38
42
 
 
43
 
39
44
def fix_year(year):
40
45
    if year < 70:
41
46
        year += 2000
54
59
# displaydate and approximatedate return an elementtree <span> Element
55
60
# with the full date in a tooltip.
56
61
 
 
62
 
57
63
def date_day(value):
58
64
    return value.strftime('%Y-%m-%d')
59
65
 
123
129
    return _wrap_with_date_time_title(date, _displaydate(date))
124
130
 
125
131
 
126
 
class Container (object):
 
132
class Container(object):
127
133
    """
128
134
    Convert a dict into an object with attributes.
129
135
    """
 
136
 
130
137
    def __init__(self, _dict=None, **kw):
131
138
        if _dict is not None:
132
139
            for key, value in _dict.iteritems():
137
144
    def __repr__(self):
138
145
        out = '{ '
139
146
        for key, value in self.__dict__.iteritems():
140
 
            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):
141
149
                continue
142
150
            out += '%r => %r, ' % (key, value)
143
151
        out += '}'
153
161
STANDARD_PATTERN = re.compile(r'^(.*?)\s*<(.*?)>\s*$')
154
162
EMAIL_PATTERN = re.compile(r'[-\w\d\+_!%\.]+@[-\w\d\+_!%\.]+')
155
163
 
 
164
 
156
165
def hide_email(email):
157
166
    """
158
167
    try to obsure any email address in a bazaar committer's name.
172
181
        return '%s at %s' % (username, domains[-2])
173
182
    return '%s at %s' % (username, domains[0])
174
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
175
192
 
176
193
# only do this if unicode turns out to be a problem
177
194
#_BADCHARS_RE = re.compile(ur'[\u007f-\uffff]')
178
195
 
179
196
# FIXME: get rid of this method; use fixed_width() and avoid XML().
 
197
 
 
198
 
180
199
def html_clean(s):
181
200
    """
182
201
    clean up a string for html display.  expand any tabs, encode any html
187
206
    s = s.replace(' ', '&nbsp;')
188
207
    return s
189
208
 
 
209
 
190
210
NONBREAKING_SPACE = u'\N{NO-BREAK SPACE}'
191
211
 
 
212
 
192
213
def fill_div(s):
193
214
    """
194
215
    CSS is stupid. In some cases we need to replace an empty value with
196
217
 
197
218
    return: the same value recieved if not empty, and a '&nbsp;' if it is.
198
219
    """
199
 
    
200
 
 
201
220
    if s is None:
202
221
        return '&nbsp;'
203
222
    elif isinstance(s, int):
211
230
            s = s.decode('iso-8859-15')
212
231
        return s
213
232
 
 
233
HSC = HTMLStructureCleaner()
214
234
 
215
235
def fixed_width(s):
216
236
    """
227
247
            s = s.decode('utf-8')
228
248
        except UnicodeDecodeError:
229
249
            s = s.decode('iso-8859-15')
230
 
    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/>')
231
254
 
232
255
 
233
256
def fake_permissions(kind, executable):
266
289
P95_MEG = int(0.9 * MEG)
267
290
P95_GIG = int(0.9 * GIG)
268
291
 
 
292
 
269
293
def human_size(size, min_divisor=0):
270
294
    size = int(size)
271
295
    if (size == 0) and (min_divisor == 0):
311
335
        navigation.position = 0
312
336
    navigation.count = len(navigation.revid_list)
313
337
    navigation.page_position = navigation.position // navigation.pagesize + 1
314
 
    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
315
340
 
316
341
    def get_offset(offset):
317
 
        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):
318
344
            return None
319
345
        return navigation.revid_list[navigation.position + offset]
320
346
 
327
353
            navigation.next_page_revid)
328
354
    start_revno = navigation.history.get_revno(navigation.start_revid)
329
355
 
330
 
    params = { 'filter_file_id': navigation.filter_file_id }
 
356
    params = {'filter_file_id': navigation.filter_file_id}
331
357
    if getattr(navigation, 'query', None) is not None:
332
358
        params['q'] = navigation.query
333
359
 
341
367
        navigation.next_page_url = navigation.branch.context_url(
342
368
            [navigation.scan_url, next_page_revno], **params)
343
369
 
 
370
 
344
371
def directory_breadcrumbs(path, is_root, view):
345
372
    """
346
373
    Generate breadcrumb information from the directory path given
347
 
    
 
374
 
348
375
    The path given should be a path up to any branch that is currently being
349
376
    served
350
 
    
 
377
 
351
378
    Arguments:
352
379
    path -- The path to convert into breadcrumbs
353
380
    is_root -- Whether or not loggerhead is serving a branch at its root
355
382
    """
356
383
    # Is our root directory itself a branch?
357
384
    if is_root:
358
 
        if view == 'directory':
359
 
            directory = 'files'
360
385
        breadcrumbs = [{
361
386
            'dir_name': path,
362
387
            'path': '',
383
408
                breadcrumbs[-1]['suffix'] = '/' + view
384
409
    return breadcrumbs
385
410
 
 
411
 
386
412
def branch_breadcrumbs(path, inv, view):
387
413
    """
388
414
    Generate breadcrumb information from the branch path given
389
 
    
 
415
 
390
416
    The path given should be a path that exists within a branch
391
 
    
 
417
 
392
418
    Arguments:
393
419
    path -- The path to convert into breadcrumbs
394
420
    inv -- Inventory to get file information from
400
426
        inner_breadcrumbs.append({
401
427
            'dir_name': dir_name,
402
428
            'file_id': inv.path2id('/'.join(dir_parts[:index + 1])),
403
 
            'suffix': '/' + view ,
 
429
            'suffix': '/' + view,
404
430
        })
405
431
    return inner_breadcrumbs
406
432
 
 
433
 
407
434
def decorator(unbound):
 
435
 
408
436
    def new_decorator(f):
409
437
        g = unbound(f)
410
438
        g.__name__ = f.__name__
417
445
    return new_decorator
418
446
 
419
447
 
420
 
# common threading-lock decorator
421
 
def with_lock(lockname, debug_name=None):
422
 
    if debug_name is None:
423
 
        debug_name = lockname
424
 
    @decorator
425
 
    def _decorator(unbound):
426
 
        def locked(self, *args, **kw):
427
 
            getattr(self, lockname).acquire()
428
 
            try:
429
 
                return unbound(self, *args, **kw)
430
 
            finally:
431
 
                getattr(self, lockname).release()
432
 
        return locked
433
 
    return _decorator
434
 
 
435
448
 
436
449
@decorator
437
450
def lsprof(f):
 
451
 
438
452
    def _f(*a, **kw):
439
453
        from loggerhead.lsprof import profile
440
454
        import cPickle
441
455
        z = time.time()
442
456
        ret, stats = profile(f, *a, **kw)
443
 
        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)))
444
459
        stats.sort()
445
460
        stats.freeze()
446
461
        now = time.time()
447
462
        msec = int(now * 1000) % 1000
448
 
        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,))
449
465
        filename = f.__name__ + '-' + timestr + '.lsprof'
450
466
        cPickle.dump(stats, open(filename, 'w'), 2)
451
467
        return ret
507
523
    overrides = dict((k, v) for (k, v) in overrides.iteritems() if k in _valid)
508
524
    map.update(overrides)
509
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