~loggerhead-team/loggerhead/trunk-rich

« back to all changes in this revision

Viewing changes to loggerhead/util.py

  • Committer: Max Kanat-Alexander
  • Date: 2010-12-01 08:30:02 UTC
  • mto: (157.1.21 devel)
  • mto: This revision was merged to the branch mainline in revision 423.
  • Revision ID: mkanat@bugzilla.org-20101201083002-tum61bojpypc9a7b
Merge in a cherrypick of the "view" controller, to help performance on
Launchpad.

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
36
31
import os
37
32
import subprocess
38
33
 
 
34
try:
 
35
    from xml.etree import ElementTree as ET
 
36
except ImportError:
 
37
    from elementtree import ElementTree as ET
 
38
 
 
39
from bzrlib import urlutils
 
40
 
 
41
from simpletal.simpleTALUtils import HTMLStructureCleaner
 
42
 
39
43
log = logging.getLogger("loggerhead.controllers")
40
44
 
 
45
 
41
46
def fix_year(year):
42
47
    if year < 70:
43
48
        year += 2000
56
61
# displaydate and approximatedate return an elementtree <span> Element
57
62
# with the full date in a tooltip.
58
63
 
 
64
 
59
65
def date_day(value):
60
66
    return value.strftime('%Y-%m-%d')
61
67
 
62
68
 
63
69
def date_time(value):
64
70
    if value is not None:
65
 
        return value.strftime('%Y-%m-%d %T')
 
71
        return value.strftime('%Y-%m-%d %H:%M:%S')
66
72
    else:
67
73
        return 'N/A'
68
74
 
125
131
    return _wrap_with_date_time_title(date, _displaydate(date))
126
132
 
127
133
 
128
 
class Container (object):
 
134
class Container(object):
129
135
    """
130
136
    Convert a dict into an object with attributes.
131
137
    """
 
138
 
132
139
    def __init__(self, _dict=None, **kw):
 
140
        self._properties = {}
133
141
        if _dict is not None:
134
142
            for key, value in _dict.iteritems():
135
143
                setattr(self, key, value)
139
147
    def __repr__(self):
140
148
        out = '{ '
141
149
        for key, value in self.__dict__.iteritems():
142
 
            if key.startswith('_') or (getattr(self.__dict__[key], '__call__', None) is not None):
 
150
            if key.startswith('_') or (getattr(self.__dict__[key],
 
151
                                       '__call__', None) is not None):
143
152
                continue
144
153
            out += '%r => %r, ' % (key, value)
145
154
        out += '}'
146
155
        return out
147
156
 
 
157
    def __getattr__(self, attr):
 
158
        """Used for handling things that aren't already available."""
 
159
        if attr.startswith('_') or attr not in self._properties:
 
160
            raise AttributeError('No attribute: %s' % (attr,))
 
161
        val = self._properties[attr](self, attr)
 
162
        setattr(self, attr, val)
 
163
        return val
 
164
 
 
165
    def _set_property(self, attr, prop_func):
 
166
        """Set a function that will be called when an attribute is desired.
 
167
 
 
168
        We will cache the return value, so the function call should be
 
169
        idempotent. We will pass 'self' and the 'attr' name when triggered.
 
170
        """
 
171
        if attr.startswith('_'):
 
172
            raise ValueError("Cannot create properties that start with _")
 
173
        self._properties[attr] = prop_func
 
174
 
148
175
 
149
176
def trunc(text, limit=10):
150
177
    if len(text) <= limit:
155
182
STANDARD_PATTERN = re.compile(r'^(.*?)\s*<(.*?)>\s*$')
156
183
EMAIL_PATTERN = re.compile(r'[-\w\d\+_!%\.]+@[-\w\d\+_!%\.]+')
157
184
 
 
185
 
158
186
def hide_email(email):
159
187
    """
160
188
    try to obsure any email address in a bazaar committer's name.
174
202
        return '%s at %s' % (username, domains[-2])
175
203
    return '%s at %s' % (username, domains[0])
176
204
 
 
205
def hide_emails(emails):
 
206
    """
 
207
    try to obscure any email address in a list of bazaar committers' names.
 
208
    """
 
209
    result = []
 
210
    for email in emails:
 
211
        result.append(hide_email(email))
 
212
    return result
177
213
 
178
214
# only do this if unicode turns out to be a problem
179
215
#_BADCHARS_RE = re.compile(ur'[\u007f-\uffff]')
180
216
 
181
217
# FIXME: get rid of this method; use fixed_width() and avoid XML().
 
218
 
 
219
 
182
220
def html_clean(s):
183
221
    """
184
222
    clean up a string for html display.  expand any tabs, encode any html
189
227
    s = s.replace(' ', '&nbsp;')
190
228
    return s
191
229
 
 
230
 
192
231
NONBREAKING_SPACE = u'\N{NO-BREAK SPACE}'
193
232
 
 
233
 
194
234
def fill_div(s):
195
235
    """
196
236
    CSS is stupid. In some cases we need to replace an empty value with
198
238
 
199
239
    return: the same value recieved if not empty, and a '&nbsp;' if it is.
200
240
    """
201
 
    
202
 
 
203
241
    if s is None:
204
242
        return '&nbsp;'
205
243
    elif isinstance(s, int):
213
251
            s = s.decode('iso-8859-15')
214
252
        return s
215
253
 
 
254
HSC = HTMLStructureCleaner()
216
255
 
217
256
def fixed_width(s):
218
257
    """
229
268
            s = s.decode('utf-8')
230
269
        except UnicodeDecodeError:
231
270
            s = s.decode('iso-8859-15')
232
 
    return s.expandtabs().replace(' ', NONBREAKING_SPACE)
 
271
 
 
272
    s = cgi.escape(s).expandtabs().replace(' ', NONBREAKING_SPACE)
 
273
 
 
274
    return HSC.clean(s).replace('\n', '<br/>')
233
275
 
234
276
 
235
277
def fake_permissions(kind, executable):
268
310
P95_MEG = int(0.9 * MEG)
269
311
P95_GIG = int(0.9 * GIG)
270
312
 
 
313
 
271
314
def human_size(size, min_divisor=0):
272
315
    size = int(size)
273
316
    if (size == 0) and (min_divisor == 0):
302
345
    return out
303
346
 
304
347
 
 
348
def local_path_from_url(url):
 
349
    """Convert Bazaar URL to local path, ignoring readonly+ prefix"""
 
350
    readonly_prefix = 'readonly+'
 
351
    if url.startswith(readonly_prefix):
 
352
        url = url[len(readonly_prefix):]
 
353
    return urlutils.local_path_from_url(url)
 
354
 
 
355
 
305
356
def fill_in_navigation(navigation):
306
357
    """
307
358
    given a navigation block (used by the template for the page header), fill
313
364
        navigation.position = 0
314
365
    navigation.count = len(navigation.revid_list)
315
366
    navigation.page_position = navigation.position // navigation.pagesize + 1
316
 
    navigation.page_count = (len(navigation.revid_list) + (navigation.pagesize - 1)) // navigation.pagesize
 
367
    navigation.page_count = (len(navigation.revid_list) + (navigation.pagesize\
 
368
 - 1)) // navigation.pagesize
317
369
 
318
370
    def get_offset(offset):
319
 
        if (navigation.position + offset < 0) or (navigation.position + offset > navigation.count - 1):
 
371
        if (navigation.position + offset < 0) or (
 
372
           navigation.position + offset > navigation.count - 1):
320
373
            return None
321
374
        return navigation.revid_list[navigation.position + offset]
322
375
 
329
382
            navigation.next_page_revid)
330
383
    start_revno = navigation.history.get_revno(navigation.start_revid)
331
384
 
332
 
    params = { 'filter_file_id': navigation.filter_file_id }
 
385
    params = {'filter_file_id': navigation.filter_file_id}
333
386
    if getattr(navigation, 'query', None) is not None:
334
387
        params['q'] = navigation.query
335
388
 
358
411
    """
359
412
    # Is our root directory itself a branch?
360
413
    if is_root:
361
 
        if view == 'directory':
362
 
            directory = 'files'
363
414
        breadcrumbs = [{
364
415
            'dir_name': path,
365
416
            'path': '',
404
455
        inner_breadcrumbs.append({
405
456
            'dir_name': dir_name,
406
457
            'file_id': inv.path2id('/'.join(dir_parts[:index + 1])),
407
 
            'suffix': '/' + view ,
 
458
            'suffix': '/' + view,
408
459
        })
409
460
    return inner_breadcrumbs
410
461
 
411
462
 
412
463
def decorator(unbound):
 
464
 
413
465
    def new_decorator(f):
414
466
        g = unbound(f)
415
467
        g.__name__ = f.__name__
422
474
    return new_decorator
423
475
 
424
476
 
425
 
# common threading-lock decorator
426
 
def with_lock(lockname, debug_name=None):
427
 
    if debug_name is None:
428
 
        debug_name = lockname
429
 
    @decorator
430
 
    def _decorator(unbound):
431
 
        def locked(self, *args, **kw):
432
 
            getattr(self, lockname).acquire()
433
 
            try:
434
 
                return unbound(self, *args, **kw)
435
 
            finally:
436
 
                getattr(self, lockname).release()
437
 
        return locked
438
 
    return _decorator
439
 
 
440
477
 
441
478
@decorator
442
479
def lsprof(f):
 
480
 
443
481
    def _f(*a, **kw):
444
482
        from loggerhead.lsprof import profile
445
483
        import cPickle
446
484
        z = time.time()
447
485
        ret, stats = profile(f, *a, **kw)
448
 
        log.debug('Finished profiled %s in %d msec.' % (f.__name__, int((time.time() - z) * 1000)))
 
486
        log.debug('Finished profiled %s in %d msec.' % (f.__name__,
 
487
            int((time.time() - z) * 1000)))
449
488
        stats.sort()
450
489
        stats.freeze()
451
490
        now = time.time()
452
491
        msec = int(now * 1000) % 1000
453
 
        timestr = time.strftime('%Y%m%d%H%M%S', time.localtime(now)) + ('%03d' % msec)
 
492
        timestr = time.strftime('%Y%m%d%H%M%S',
 
493
                                time.localtime(now)) + ('%03d' % (msec,))
454
494
        filename = f.__name__ + '-' + timestr + '.lsprof'
455
495
        cPickle.dump(stats, open(filename, 'w'), 2)
456
496
        return ret
522
562
    _reloader_environ_key = 'PYTHON_RELOADER_SHOULD_RUN'
523
563
 
524
564
    @classmethod
525
 
    def _turn_sigterm_into_systemexit(self):
 
565
    def _turn_sigterm_into_systemexit(cls):
526
566
        """
527
567
        Attempts to turn a SIGTERM exception into a SystemExit exception.
528
568
        """
530
570
            import signal
531
571
        except ImportError:
532
572
            return
 
573
 
533
574
        def handle_term(signo, frame):
534
575
            raise SystemExit
535
576
        signal.signal(signal.SIGTERM, handle_term)
536
577
 
537
578
    @classmethod
538
 
    def is_installed(self):
539
 
        return os.environ.get(self._reloader_environ_key)
540
 
    
 
579
    def is_installed(cls):
 
580
        return os.environ.get(cls._reloader_environ_key)
 
581
 
541
582
    @classmethod
542
 
    def install(self):
 
583
    def install(cls):
543
584
        from paste import reloader
544
585
        reloader.install(int(1))
545
 
    
546
 
    @classmethod    
547
 
    def restart_with_reloader(self):
 
586
 
 
587
    @classmethod
 
588
    def restart_with_reloader(cls):
548
589
        """Based on restart_with_monitor from paste.script.serve."""
549
590
        print 'Starting subprocess with file monitor'
550
 
        while 1:
 
591
        while True:
551
592
            args = [sys.executable] + sys.argv
552
593
            new_environ = os.environ.copy()
553
 
            new_environ[self._reloader_environ_key] = 'true'
 
594
            new_environ[cls._reloader_environ_key] = 'true'
554
595
            proc = None
555
596
            try:
556
597
                try:
557
 
                    self._turn_sigterm_into_systemexit()
 
598
                    cls._turn_sigterm_into_systemexit()
558
599
                    proc = subprocess.Popen(args, env=new_environ)
559
600
                    exit_code = proc.wait()
560
601
                    proc = None
563
604
                    return 1
564
605
            finally:
565
606
                if (proc is not None
566
 
                    and hasattr(os, 'kill')):
 
607
                    and getattr(os, 'kill', None) is not None):
567
608
                    import signal
568
609
                    try:
569
610
                        os.kill(proc.pid, signal.SIGTERM)
570
611
                    except (OSError, IOError):
571
612
                        pass
572
 
                
 
613
 
573
614
            # Reloader always exits with code 3; but if we are
574
615
            # a monitor, any exit code will restart
575
616
            if exit_code != 3:
576
617
                return exit_code
577
618
            print '-'*20, 'Restarting', '-'*20
 
619
 
 
620
 
 
621
def convert_file_errors(application):
 
622
    """WSGI wrapper to convert some file errors to Paste exceptions"""
 
623
    def new_application(environ, start_response):
 
624
        try:
 
625
            return application(environ, start_response)
 
626
        except (IOError, OSError), e:
 
627
            import errno
 
628
            from paste import httpexceptions
 
629
            if e.errno == errno.ENOENT:
 
630
                raise httpexceptions.HTTPNotFound()
 
631
            elif e.errno == errno.EACCES:
 
632
                raise httpexceptions.HTTPForbidden()
 
633
            else:
 
634
                raise
 
635
    return new_application