~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/osutils.py

  • Committer: John Arbash Meinel
  • Date: 2008-09-26 22:14:42 UTC
  • mto: This revision was merged to the branch mainline in revision 3747.
  • Revision ID: john@arbash-meinel.com-20080926221442-3r67j99sr9rwe9w0
Make message optional, don't check the memory flag directly.

Show diffs side-by-side

added added

removed removed

Lines of Context:
53
53
    )
54
54
""")
55
55
 
 
56
 
56
57
import bzrlib
57
58
from bzrlib import symbol_versioning
58
59
from bzrlib.symbol_versioning import (
59
60
    deprecated_function,
60
 
    zero_nine,
61
61
    )
62
62
from bzrlib.trace import mutter
63
63
 
69
69
# OR with 0 on those platforms
70
70
O_BINARY = getattr(os, 'O_BINARY', 0)
71
71
 
72
 
# On posix, use lstat instead of stat so that we can
73
 
# operate on broken symlinks. On Windows revert to stat.
74
 
lstat = getattr(os, 'lstat', os.stat)
75
72
 
76
73
def make_readonly(filename):
77
74
    """Make a filename read-only."""
78
 
    mod = lstat(filename).st_mode
 
75
    mod = os.lstat(filename).st_mode
79
76
    if not stat.S_ISLNK(mod):
80
77
        mod = mod & 0777555
81
78
        os.chmod(filename, mod)
82
79
 
83
80
 
84
81
def make_writable(filename):
85
 
    mod = lstat(filename).st_mode
 
82
    mod = os.lstat(filename).st_mode
86
83
    if not stat.S_ISLNK(mod):
87
84
        mod = mod | 0200
88
85
        os.chmod(filename, mod)
89
86
 
90
87
 
 
88
def minimum_path_selection(paths):
 
89
    """Return the smallset subset of paths which are outside paths.
 
90
 
 
91
    :param paths: A container (and hence not None) of paths.
 
92
    :return: A set of paths sufficient to include everything in paths via
 
93
        is_inside_any, drawn from the paths parameter.
 
94
    """
 
95
    search_paths = set()
 
96
    paths = set(paths)
 
97
    for path in paths:
 
98
        other_paths = paths.difference([path])
 
99
        if not is_inside_any(other_paths, path):
 
100
            # this is a top level path, we must check it.
 
101
            search_paths.add(path)
 
102
    return search_paths
 
103
 
 
104
 
91
105
_QUOTE_RE = None
92
106
 
93
107
 
135
149
    try:
136
150
        return _mapper(_lstat(f).st_mode)
137
151
    except OSError, e:
138
 
        if getattr(e, 'errno', None) == errno.ENOENT:
 
152
        if getattr(e, 'errno', None) in (errno.ENOENT, errno.ENOTDIR):
139
153
            raise errors.NoSuchFile(f)
140
154
        raise
141
155
 
220
234
 
221
235
    success = False
222
236
    try:
223
 
        # This may throw an exception, in which case success will
224
 
        # not be set.
225
 
        rename_func(old, new)
226
 
        success = True
 
237
        try:
 
238
            # This may throw an exception, in which case success will
 
239
            # not be set.
 
240
            rename_func(old, new)
 
241
            success = True
 
242
        except (IOError, OSError), e:
 
243
            # source and target may be aliases of each other (e.g. on a
 
244
            # case-insensitive filesystem), so we may have accidentally renamed
 
245
            # source by when we tried to rename target
 
246
            if not (file_existed and e.errno in (None, errno.ENOENT)):
 
247
                raise
227
248
    finally:
228
249
        if file_existed:
229
250
            # If the file used to exist, rename it back into place
339
360
 
340
361
 
341
362
def _mac_getcwd():
342
 
    return unicodedata.normalize('NFKC', os.getcwdu())
 
363
    return unicodedata.normalize('NFC', os.getcwdu())
343
364
 
344
365
 
345
366
# Default is to just use the python builtins, but these can be rebound on
447
468
        return pathjoin(F(p), e)
448
469
 
449
470
 
450
 
def backup_file(fn):
451
 
    """Copy a file to a backup.
452
 
 
453
 
    Backups are named in GNU-style, with a ~ suffix.
454
 
 
455
 
    If the file is already a backup, it's not copied.
456
 
    """
457
 
    if fn[-1] == '~':
458
 
        return
459
 
    bfn = fn + '~'
460
 
 
461
 
    if has_symlinks() and os.path.islink(fn):
462
 
        target = os.readlink(fn)
463
 
        os.symlink(target, bfn)
464
 
        return
465
 
    inf = file(fn, 'rb')
466
 
    try:
467
 
        content = inf.read()
468
 
    finally:
469
 
        inf.close()
470
 
    
471
 
    outf = file(bfn, 'wb')
472
 
    try:
473
 
        outf.write(content)
474
 
    finally:
475
 
        outf.close()
476
 
 
477
 
 
478
471
def isdir(f):
479
472
    """True if f is an accessible directory."""
480
473
    try:
537
530
    return False
538
531
 
539
532
 
540
 
def pumpfile(fromfile, tofile):
541
 
    """Copy contents of one file to another."""
542
 
    BUFSIZE = 32768
543
 
    while True:
544
 
        b = fromfile.read(BUFSIZE)
545
 
        if not b:
546
 
            break
547
 
        tofile.write(b)
 
533
def pumpfile(from_file, to_file, read_length=-1, buff_size=32768):
 
534
    """Copy contents of one file to another.
 
535
 
 
536
    The read_length can either be -1 to read to end-of-file (EOF) or
 
537
    it can specify the maximum number of bytes to read.
 
538
 
 
539
    The buff_size represents the maximum size for each read operation
 
540
    performed on from_file.
 
541
 
 
542
    :return: The number of bytes copied.
 
543
    """
 
544
    length = 0
 
545
    if read_length >= 0:
 
546
        # read specified number of bytes
 
547
 
 
548
        while read_length > 0:
 
549
            num_bytes_to_read = min(read_length, buff_size)
 
550
 
 
551
            block = from_file.read(num_bytes_to_read)
 
552
            if not block:
 
553
                # EOF reached
 
554
                break
 
555
            to_file.write(block)
 
556
 
 
557
            actual_bytes_read = len(block)
 
558
            read_length -= actual_bytes_read
 
559
            length += actual_bytes_read
 
560
    else:
 
561
        # read to EOF
 
562
        while True:
 
563
            block = from_file.read(buff_size)
 
564
            if not block:
 
565
                # EOF reached
 
566
                break
 
567
            to_file.write(block)
 
568
            length += len(block)
 
569
    return length
 
570
 
 
571
 
 
572
def pump_string_file(bytes, file_handle, segment_size=None):
 
573
    """Write bytes to file_handle in many smaller writes.
 
574
 
 
575
    :param bytes: The string to write.
 
576
    :param file_handle: The file to write to.
 
577
    """
 
578
    # Write data in chunks rather than all at once, because very large
 
579
    # writes fail on some platforms (e.g. Windows with SMB  mounted
 
580
    # drives).
 
581
    if not segment_size:
 
582
        segment_size = 5242880 # 5MB
 
583
    segments = range(len(bytes) / segment_size + 1)
 
584
    write = file_handle.write
 
585
    for segment_index in segments:
 
586
        segment = buffer(bytes, segment_index * segment_size, segment_size)
 
587
        write(segment)
548
588
 
549
589
 
550
590
def file_iterator(input_file, readsize=32768):
556
596
 
557
597
 
558
598
def sha_file(f):
559
 
    if getattr(f, 'tell', None) is not None:
560
 
        assert f.tell() == 0
 
599
    """Calculate the hexdigest of an open file.
 
600
 
 
601
    The file cursor should be already at the start.
 
602
    """
561
603
    s = sha.new()
562
604
    BUFSIZE = 128<<10
563
605
    while True:
568
610
    return s.hexdigest()
569
611
 
570
612
 
571
 
 
572
 
def sha_strings(strings):
 
613
def sha_file_by_name(fname):
 
614
    """Calculate the SHA1 of a file by reading the full text"""
 
615
    s = sha.new()
 
616
    f = os.open(fname, os.O_RDONLY | O_BINARY)
 
617
    try:
 
618
        while True:
 
619
            b = os.read(f, 1<<16)
 
620
            if not b:
 
621
                return s.hexdigest()
 
622
            s.update(b)
 
623
    finally:
 
624
        os.close(f)
 
625
 
 
626
 
 
627
def sha_strings(strings, _factory=sha.new):
573
628
    """Return the sha-1 of concatenation of strings"""
574
 
    s = sha.new()
 
629
    s = _factory()
575
630
    map(s.update, strings)
576
631
    return s.hexdigest()
577
632
 
578
633
 
579
 
def sha_string(f):
580
 
    s = sha.new()
581
 
    s.update(f)
582
 
    return s.hexdigest()
 
634
def sha_string(f, _factory=sha.new):
 
635
    return _factory(f).hexdigest()
583
636
 
584
637
 
585
638
def fingerprint_file(f):
586
 
    s = sha.new()
587
639
    b = f.read()
588
 
    s.update(b)
589
 
    size = len(b)
590
 
    return {'size': size,
591
 
            'sha1': s.hexdigest()}
 
640
    return {'size': len(b),
 
641
            'sha1': sha.new(b).hexdigest()}
592
642
 
593
643
 
594
644
def compare_files(a, b):
610
660
    offset = datetime.fromtimestamp(t) - datetime.utcfromtimestamp(t)
611
661
    return offset.days * 86400 + offset.seconds
612
662
 
 
663
weekdays = ['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun']
613
664
    
614
665
def format_date(t, offset=0, timezone='original', date_fmt=None,
615
666
                show_offset=True):
634
685
        tt = time.localtime(t)
635
686
        offset = local_time_offset(t)
636
687
    else:
637
 
        raise errors.BzrError("unsupported timezone format %r" % timezone,
638
 
                              ['options are "utc", "original", "local"'])
 
688
        raise errors.UnsupportedTimezoneFormat(timezone)
639
689
    if date_fmt is None:
640
690
        date_fmt = "%a %Y-%m-%d %H:%M:%S"
641
691
    if show_offset:
642
692
        offset_str = ' %+03d%02d' % (offset / 3600, (offset / 60) % 60)
643
693
    else:
644
694
        offset_str = ''
 
695
    # day of week depends on locale, so we do this ourself
 
696
    date_fmt = date_fmt.replace('%a', weekdays[tt[6]])
645
697
    return (time.strftime(date_fmt, tt) +  offset_str)
646
698
 
647
699
 
744
796
 
745
797
def splitpath(p):
746
798
    """Turn string into list of parts."""
747
 
    assert isinstance(p, basestring)
748
 
 
749
799
    # split on either delimiter because people might use either on
750
800
    # Windows
751
801
    ps = re.split(r'[\\/]', p)
761
811
    return rps
762
812
 
763
813
def joinpath(p):
764
 
    assert isinstance(p, (list, tuple))
765
814
    for f in p:
766
815
        if (f == '..') or (f is None) or (f == ''):
767
816
            raise errors.BzrError("sorry, %r not allowed in path" % f)
768
817
    return pathjoin(*p)
769
818
 
770
819
 
771
 
@deprecated_function(zero_nine)
772
 
def appendpath(p1, p2):
773
 
    if p1 == '':
774
 
        return p2
775
 
    else:
776
 
        return pathjoin(p1, p2)
777
 
    
778
 
 
779
820
def split_lines(s):
780
821
    """Split s into lines, but without removing the newline characters."""
781
822
    lines = s.split('\n')
801
842
            raise
802
843
        shutil.copyfile(src, dest)
803
844
 
804
 
def delete_any(full_path):
 
845
 
 
846
# Look Before You Leap (LBYL) is appropriate here instead of Easier to Ask for
 
847
# Forgiveness than Permission (EAFP) because:
 
848
# - root can damage a solaris file system by using unlink,
 
849
# - unlink raises different exceptions on different OSes (linux: EISDIR, win32:
 
850
#   EACCES, OSX: EPERM) when invoked on a directory.
 
851
def delete_any(path):
805
852
    """Delete a file or directory."""
806
 
    try:
807
 
        os.unlink(full_path)
808
 
    except OSError, e:
809
 
    # We may be renaming a dangling inventory id
810
 
        if e.errno not in (errno.EISDIR, errno.EACCES, errno.EPERM):
811
 
            raise
812
 
        os.rmdir(full_path)
 
853
    if isdir(path): # Takes care of symlinks
 
854
        os.rmdir(path)
 
855
    else:
 
856
        os.unlink(path)
813
857
 
814
858
 
815
859
def has_symlinks():
817
861
        return True
818
862
    else:
819
863
        return False
820
 
        
 
864
 
 
865
 
 
866
def has_hardlinks():
 
867
    if getattr(os, 'link', None) is not None:
 
868
        return True
 
869
    else:
 
870
        return False
 
871
 
 
872
 
 
873
def host_os_dereferences_symlinks():
 
874
    return (has_symlinks()
 
875
            and sys.platform not in ('cygwin', 'win32'))
 
876
 
821
877
 
822
878
def contains_whitespace(s):
823
879
    """True if there are any whitespace characters in s."""
859
915
    avoids that problem.
860
916
    """
861
917
 
862
 
    assert len(base) >= MIN_ABS_PATHLENGTH, ('Length of base must be equal or'
863
 
        ' exceed the platform minimum length (which is %d)' % 
864
 
        MIN_ABS_PATHLENGTH)
 
918
    if len(base) < MIN_ABS_PATHLENGTH:
 
919
        # must have space for e.g. a drive letter
 
920
        raise ValueError('%r is too short to calculate a relative path'
 
921
            % (base,))
865
922
 
866
923
    rp = abspath(path)
867
924
 
984
1041
    On platforms where the system does not normalize filenames 
985
1042
    (Windows, Linux), you have to access a file by its exact path.
986
1043
 
987
 
    Internally, bzr only supports NFC/NFKC normalization, since that is 
 
1044
    Internally, bzr only supports NFC normalization, since that is 
988
1045
    the standard for XML documents.
989
1046
 
990
1047
    So return the normalized path, and a flag indicating if the file
991
1048
    can be accessed by that path.
992
1049
    """
993
1050
 
994
 
    return unicodedata.normalize('NFKC', unicode(path)), True
 
1051
    return unicodedata.normalize('NFC', unicode(path)), True
995
1052
 
996
1053
 
997
1054
def _inaccessible_normalized_filename(path):
998
1055
    __doc__ = _accessible_normalized_filename.__doc__
999
1056
 
1000
 
    normalized = unicodedata.normalize('NFKC', unicode(path))
 
1057
    normalized = unicodedata.normalize('NFC', unicode(path))
1001
1058
    return normalized, normalized == path
1002
1059
 
1003
1060
 
1080
1137
        raise errors.IllegalPath(path)
1081
1138
 
1082
1139
 
 
1140
_WIN32_ERROR_DIRECTORY = 267 # Similar to errno.ENOTDIR
 
1141
 
 
1142
def _is_error_enotdir(e):
 
1143
    """Check if this exception represents ENOTDIR.
 
1144
 
 
1145
    Unfortunately, python is very inconsistent about the exception
 
1146
    here. The cases are:
 
1147
      1) Linux, Mac OSX all versions seem to set errno == ENOTDIR
 
1148
      2) Windows, Python2.4, uses errno == ERROR_DIRECTORY (267)
 
1149
         which is the windows error code.
 
1150
      3) Windows, Python2.5 uses errno == EINVAL and
 
1151
         winerror == ERROR_DIRECTORY
 
1152
 
 
1153
    :param e: An Exception object (expected to be OSError with an errno
 
1154
        attribute, but we should be able to cope with anything)
 
1155
    :return: True if this represents an ENOTDIR error. False otherwise.
 
1156
    """
 
1157
    en = getattr(e, 'errno', None)
 
1158
    if (en == errno.ENOTDIR
 
1159
        or (sys.platform == 'win32'
 
1160
            and (en == _WIN32_ERROR_DIRECTORY
 
1161
                 or (en == errno.EINVAL
 
1162
                     and getattr(e, 'winerror', None) == _WIN32_ERROR_DIRECTORY)
 
1163
        ))):
 
1164
        return True
 
1165
    return False
 
1166
 
 
1167
 
1083
1168
def walkdirs(top, prefix=""):
1084
1169
    """Yield data about all the directories in a tree.
1085
1170
    
1089
1174
    
1090
1175
    The data yielded is of the form:
1091
1176
    ((directory-relpath, directory-path-from-top),
1092
 
    [(directory-relpath, basename, kind, lstat, path-from-top), ...]),
 
1177
    [(relpath, basename, kind, lstat, path-from-top), ...]),
1093
1178
     - directory-relpath is the relative path of the directory being returned
1094
1179
       with respect to top. prefix is prepended to this.
1095
1180
     - directory-path-from-root is the path including top for this directory. 
1129
1214
 
1130
1215
        dirblock = []
1131
1216
        append = dirblock.append
1132
 
        for name in sorted(_listdir(top)):
1133
 
            abspath = top_slash + name
1134
 
            statvalue = _lstat(abspath)
1135
 
            kind = _kind_from_mode(statvalue.st_mode & 0170000, 'unknown')
1136
 
            append((relprefix + name, name, kind, statvalue, abspath))
 
1217
        try:
 
1218
            names = sorted(_listdir(top))
 
1219
        except OSError, e:
 
1220
            if not _is_error_enotdir(e):
 
1221
                raise
 
1222
        else:
 
1223
            for name in names:
 
1224
                abspath = top_slash + name
 
1225
                statvalue = _lstat(abspath)
 
1226
                kind = _kind_from_mode(statvalue.st_mode & 0170000, 'unknown')
 
1227
                append((relprefix + name, name, kind, statvalue, abspath))
1137
1228
        yield (relroot, top), dirblock
1138
1229
 
1139
1230
        # push the user specified dirs from dirblock
1140
1231
        pending.extend(d for d in reversed(dirblock) if d[2] == _directory)
1141
1232
 
1142
1233
 
 
1234
_real_walkdirs_utf8 = None
 
1235
 
1143
1236
def _walkdirs_utf8(top, prefix=""):
1144
1237
    """Yield data about all the directories in a tree.
1145
1238
 
1154
1247
        path-from-top might be unicode or utf8, but it is the correct path to
1155
1248
        pass to os functions to affect the file in question. (such as os.lstat)
1156
1249
    """
1157
 
    fs_encoding = _fs_enc.upper()
1158
 
    if (sys.platform == 'win32' or
1159
 
        fs_encoding not in ('UTF-8', 'US-ASCII', 'ANSI_X3.4-1968')): # ascii
1160
 
        return _walkdirs_unicode_to_utf8(top, prefix=prefix)
1161
 
    else:
1162
 
        return _walkdirs_fs_utf8(top, prefix=prefix)
 
1250
    global _real_walkdirs_utf8
 
1251
    if _real_walkdirs_utf8 is None:
 
1252
        fs_encoding = _fs_enc.upper()
 
1253
        if win32utils.winver == 'Windows NT':
 
1254
            # Win98 doesn't have unicode apis like FindFirstFileW
 
1255
            # TODO: We possibly could support Win98 by falling back to the
 
1256
            #       original FindFirstFile, and using TCHAR instead of WCHAR,
 
1257
            #       but that gets a bit tricky, and requires custom compiling
 
1258
            #       for win98 anyway.
 
1259
            try:
 
1260
                from bzrlib._walkdirs_win32 import _walkdirs_utf8_win32_find_file
 
1261
            except ImportError:
 
1262
                _real_walkdirs_utf8 = _walkdirs_unicode_to_utf8
 
1263
            else:
 
1264
                _real_walkdirs_utf8 = _walkdirs_utf8_win32_find_file
 
1265
        elif fs_encoding not in ('UTF-8', 'US-ASCII', 'ANSI_X3.4-1968'):
 
1266
            # ANSI_X3.4-1968 is a form of ASCII
 
1267
            _real_walkdirs_utf8 = _walkdirs_unicode_to_utf8
 
1268
        else:
 
1269
            _real_walkdirs_utf8 = _walkdirs_fs_utf8
 
1270
    return _real_walkdirs_utf8(top, prefix=prefix)
1163
1271
 
1164
1272
 
1165
1273
def _walkdirs_fs_utf8(top, prefix=""):
1170
1278
    """
1171
1279
    _lstat = os.lstat
1172
1280
    _directory = _directory_kind
1173
 
    _listdir = os.listdir
 
1281
    # Use C accelerated directory listing.
 
1282
    _listdir = _read_dir
1174
1283
    _kind_from_mode = _formats.get
1175
1284
 
1176
1285
    # 0 - relpath, 1- basename, 2- kind, 3- stat, 4-toppath
1186
1295
 
1187
1296
        dirblock = []
1188
1297
        append = dirblock.append
1189
 
        for name in sorted(_listdir(top)):
 
1298
        # read_dir supplies in should-stat order.
 
1299
        for _, name in sorted(_listdir(top)):
1190
1300
            abspath = top_slash + name
1191
1301
            statvalue = _lstat(abspath)
1192
1302
            kind = _kind_from_mode(statvalue.st_mode & 0170000, 'unknown')
1193
1303
            append((relprefix + name, name, kind, statvalue, abspath))
 
1304
        dirblock.sort()
1194
1305
        yield (relroot, top), dirblock
1195
1306
 
1196
1307
        # push the user specified dirs from dirblock
1337
1448
    # Windows returns 'cp0' to indicate there is no code page. So we'll just
1338
1449
    # treat that as ASCII, and not support printing unicode characters to the
1339
1450
    # console.
1340
 
    if user_encoding in (None, 'cp0'):
 
1451
    #
 
1452
    # For python scripts run under vim, we get '', so also treat that as ASCII
 
1453
    if user_encoding in (None, 'cp0', ''):
1341
1454
        user_encoding = 'ascii'
1342
1455
    else:
1343
1456
        # check encoding
1357
1470
    return user_encoding
1358
1471
 
1359
1472
 
 
1473
def get_host_name():
 
1474
    """Return the current unicode host name.
 
1475
 
 
1476
    This is meant to be used in place of socket.gethostname() because that
 
1477
    behaves inconsistently on different platforms.
 
1478
    """
 
1479
    if sys.platform == "win32":
 
1480
        import win32utils
 
1481
        return win32utils.get_host_name()
 
1482
    else:
 
1483
        import socket
 
1484
        return socket.gethostname().decode(get_user_encoding())
 
1485
 
 
1486
 
1360
1487
def recv_all(socket, bytes):
1361
1488
    """Receive an exact number of bytes.
1362
1489
 
1375
1502
        b += new
1376
1503
    return b
1377
1504
 
 
1505
 
 
1506
def send_all(socket, bytes):
 
1507
    """Send all bytes on a socket.
 
1508
 
 
1509
    Regular socket.sendall() can give socket error 10053 on Windows.  This
 
1510
    implementation sends no more than 64k at a time, which avoids this problem.
 
1511
    """
 
1512
    chunk_size = 2**16
 
1513
    for pos in xrange(0, len(bytes), chunk_size):
 
1514
        socket.sendall(bytes[pos:pos+chunk_size])
 
1515
 
 
1516
 
1378
1517
def dereference_path(path):
1379
1518
    """Determine the real path to a file.
1380
1519
 
1387
1526
    # The pathjoin for '.' is a workaround for Python bug #1213894.
1388
1527
    # (initial path components aren't dereferenced)
1389
1528
    return pathjoin(realpath(pathjoin('.', parent)), base)
 
1529
 
 
1530
 
 
1531
def supports_mapi():
 
1532
    """Return True if we can use MAPI to launch a mail client."""
 
1533
    return sys.platform == "win32"
 
1534
 
 
1535
 
 
1536
def resource_string(package, resource_name):
 
1537
    """Load a resource from a package and return it as a string.
 
1538
 
 
1539
    Note: Only packages that start with bzrlib are currently supported.
 
1540
 
 
1541
    This is designed to be a lightweight implementation of resource
 
1542
    loading in a way which is API compatible with the same API from
 
1543
    pkg_resources. See
 
1544
    http://peak.telecommunity.com/DevCenter/PkgResources#basic-resource-access.
 
1545
    If and when pkg_resources becomes a standard library, this routine
 
1546
    can delegate to it.
 
1547
    """
 
1548
    # Check package name is within bzrlib
 
1549
    if package == "bzrlib":
 
1550
        resource_relpath = resource_name
 
1551
    elif package.startswith("bzrlib."):
 
1552
        package = package[len("bzrlib."):].replace('.', os.sep)
 
1553
        resource_relpath = pathjoin(package, resource_name)
 
1554
    else:
 
1555
        raise errors.BzrError('resource package %s not in bzrlib' % package)
 
1556
 
 
1557
    # Map the resource to a file and read its contents
 
1558
    base = dirname(bzrlib.__file__)
 
1559
    if getattr(sys, 'frozen', None):    # bzr.exe
 
1560
        base = abspath(pathjoin(base, '..', '..'))
 
1561
    filename = pathjoin(base, resource_relpath)
 
1562
    return open(filename, 'rU').read()
 
1563
 
 
1564
 
 
1565
try:
 
1566
    from bzrlib._readdir_pyx import read_dir as _read_dir
 
1567
except ImportError:
 
1568
    from bzrlib._readdir_py import read_dir as _read_dir