~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/_walkdirs_win32.pyx

  • Committer: John Arbash Meinel
  • Date: 2009-06-04 17:12:29 UTC
  • mto: This revision was merged to the branch mainline in revision 4410.
  • Revision ID: john@arbash-meinel.com-20090604171229-kbgfatt63y3u3uh1
Some small tweaks to decoding strings (avoid passing over the length 2x)

Down to 1.1s (from 1.4s) for decoding all of bzr.dev.
Also, favor decoding strings and then lists in _decode_object, since that is the
frequency we have those types inside Revisions.

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
# Copyright (C) 2008 Canonical Ltd
 
2
#
 
3
# This program is free software; you can redistribute it and/or modify
 
4
# it under the terms of the GNU General Public License as published by
 
5
# the Free Software Foundation; either version 2 of the License, or
 
6
# (at your option) any later version.
 
7
#
 
8
# This program is distributed in the hope that it will be useful,
 
9
# but WITHOUT ANY WARRANTY; without even the implied warranty of
 
10
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 
11
# GNU General Public License for more details.
 
12
#
 
13
# You should have received a copy of the GNU General Public License
 
14
# along with this program; if not, write to the Free Software
 
15
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
 
16
 
 
17
"""Helper functions for Walkdirs on win32."""
 
18
 
 
19
 
 
20
cdef extern from "python-compat.h":
 
21
    struct _HANDLE:
 
22
        pass
 
23
    ctypedef _HANDLE *HANDLE
 
24
    ctypedef unsigned long DWORD
 
25
    ctypedef long long __int64
 
26
    ctypedef unsigned short WCHAR
 
27
    struct _FILETIME:
 
28
        DWORD dwHighDateTime
 
29
        DWORD dwLowDateTime
 
30
    ctypedef _FILETIME FILETIME
 
31
 
 
32
    struct _WIN32_FIND_DATAW:
 
33
        DWORD dwFileAttributes
 
34
        FILETIME ftCreationTime
 
35
        FILETIME ftLastAccessTime
 
36
        FILETIME ftLastWriteTime
 
37
        DWORD nFileSizeHigh
 
38
        DWORD nFileSizeLow
 
39
        # Some reserved stuff here
 
40
        WCHAR cFileName[260] # MAX_PATH
 
41
        WCHAR cAlternateFilename[14]
 
42
 
 
43
    # We have to use the typedef trick, otherwise pyrex uses:
 
44
    #  struct WIN32_FIND_DATAW
 
45
    # which fails due to 'incomplete type'
 
46
    ctypedef _WIN32_FIND_DATAW WIN32_FIND_DATAW
 
47
 
 
48
    HANDLE INVALID_HANDLE_VALUE
 
49
    HANDLE FindFirstFileW(WCHAR *path, WIN32_FIND_DATAW *data)
 
50
    int FindNextFileW(HANDLE search, WIN32_FIND_DATAW *data)
 
51
    int FindClose(HANDLE search)
 
52
 
 
53
    DWORD FILE_ATTRIBUTE_READONLY
 
54
    DWORD FILE_ATTRIBUTE_DIRECTORY
 
55
    int ERROR_NO_MORE_FILES
 
56
 
 
57
    int GetLastError()
 
58
 
 
59
    # Wide character functions
 
60
    DWORD wcslen(WCHAR *)
 
61
 
 
62
 
 
63
cdef extern from "Python.h":
 
64
    WCHAR *PyUnicode_AS_UNICODE(object)
 
65
    Py_ssize_t PyUnicode_GET_SIZE(object)
 
66
    object PyUnicode_FromUnicode(WCHAR *, Py_ssize_t)
 
67
    int PyList_Append(object, object) except -1
 
68
    object PyUnicode_AsUTF8String(object)
 
69
 
 
70
 
 
71
import operator
 
72
import stat
 
73
 
 
74
from bzrlib import osutils, _readdir_py
 
75
 
 
76
 
 
77
cdef class _Win32Stat:
 
78
    """Represent a 'stat' result generated from WIN32_FIND_DATA"""
 
79
 
 
80
    cdef readonly int st_mode
 
81
    cdef readonly double st_ctime
 
82
    cdef readonly double st_mtime
 
83
    cdef readonly double st_atime
 
84
    # We can't just declare this as 'readonly' because python2.4 doesn't define
 
85
    # T_LONGLONG as a structure member. So instead we just use a property that
 
86
    # will convert it correctly anyway.
 
87
    cdef __int64 _st_size
 
88
 
 
89
    property st_size:
 
90
        def __get__(self):
 
91
            return self._st_size
 
92
 
 
93
    # os.stat always returns 0, so we hard code it here
 
94
    cdef readonly int st_dev
 
95
    cdef readonly int st_ino
 
96
 
 
97
    def __repr__(self):
 
98
        """Repr is the same as a Stat object.
 
99
 
 
100
        (mode, ino, dev, nlink, uid, gid, size, atime, mtime, ctime)
 
101
        """
 
102
        return repr((self.st_mode, 0, 0, 0, 0, 0, self.st_size, self.st_atime,
 
103
                     self.st_mtime, self.st_ctime))
 
104
 
 
105
 
 
106
cdef object _get_name(WIN32_FIND_DATAW *data):
 
107
    """Extract the Unicode name for this file/dir."""
 
108
    return PyUnicode_FromUnicode(data.cFileName,
 
109
                                 wcslen(data.cFileName))
 
110
 
 
111
 
 
112
cdef int _get_mode_bits(WIN32_FIND_DATAW *data):
 
113
    cdef int mode_bits
 
114
 
 
115
    mode_bits = 0100666 # writeable file, the most common
 
116
    if data.dwFileAttributes & FILE_ATTRIBUTE_READONLY == FILE_ATTRIBUTE_READONLY:
 
117
        mode_bits = mode_bits ^ 0222 # remove the write bits
 
118
    if data.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY == FILE_ATTRIBUTE_DIRECTORY:
 
119
        # Remove the FILE bit, set the DIR bit, and set the EXEC bits
 
120
        mode_bits = mode_bits ^ 0140111
 
121
    return mode_bits
 
122
 
 
123
 
 
124
cdef __int64 _get_size(WIN32_FIND_DATAW *data):
 
125
    # Pyrex casts a DWORD into a PyLong anyway, so it is safe to do << 32
 
126
    # on a DWORD
 
127
    return ((<__int64>data.nFileSizeHigh) << 32) + data.nFileSizeLow
 
128
 
 
129
 
 
130
cdef double _ftime_to_timestamp(FILETIME *ft):
 
131
    """Convert from a FILETIME struct into a floating point timestamp.
 
132
 
 
133
    The fields of a FILETIME structure are the hi and lo part
 
134
    of a 64-bit value expressed in 100 nanosecond units.
 
135
    1e7 is one second in such units; 1e-7 the inverse.
 
136
    429.4967296 is 2**32 / 1e7 or 2**32 * 1e-7.
 
137
    It also uses the epoch 1601-01-01 rather than 1970-01-01
 
138
    (taken from posixmodule.c)
 
139
    """
 
140
    cdef __int64 val
 
141
    # NB: This gives slightly different results versus casting to a 64-bit
 
142
    #     integer and doing integer math before casting into a floating
 
143
    #     point number. But the difference is in the sub millisecond range,
 
144
    #     which doesn't seem critical here.
 
145
    # secs between epochs: 11,644,473,600
 
146
    val = ((<__int64>ft.dwHighDateTime) << 32) + ft.dwLowDateTime
 
147
    return (val * 1.0e-7) - 11644473600.0
 
148
 
 
149
 
 
150
cdef int _should_skip(WIN32_FIND_DATAW *data):
 
151
    """Is this '.' or '..' so we should skip it?"""
 
152
    if (data.cFileName[0] != c'.'):
 
153
        return 0
 
154
    if data.cFileName[1] == c'\0':
 
155
        return 1
 
156
    if data.cFileName[1] == c'.' and data.cFileName[2] == c'\0':
 
157
        return 1
 
158
    return 0
 
159
 
 
160
 
 
161
cdef class Win32ReadDir:
 
162
    """Read directories on win32."""
 
163
 
 
164
    cdef object _directory_kind
 
165
    cdef object _file_kind
 
166
 
 
167
    def __init__(self):
 
168
        self._directory_kind = _readdir_py._directory
 
169
        self._file_kind = _readdir_py._file
 
170
 
 
171
    def top_prefix_to_starting_dir(self, top, prefix=""):
 
172
        """See DirReader.top_prefix_to_starting_dir."""
 
173
        return (osutils.safe_utf8(prefix), None, None, None,
 
174
                osutils.safe_unicode(top))
 
175
 
 
176
    cdef object _get_kind(self, WIN32_FIND_DATAW *data):
 
177
        if data.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY:
 
178
            return self._directory_kind
 
179
        return self._file_kind
 
180
 
 
181
    cdef _Win32Stat _get_stat_value(self, WIN32_FIND_DATAW *data):
 
182
        """Get the filename and the stat information."""
 
183
        cdef _Win32Stat statvalue
 
184
 
 
185
        statvalue = _Win32Stat()
 
186
        statvalue.st_mode = _get_mode_bits(data)
 
187
        statvalue.st_ctime = _ftime_to_timestamp(&data.ftCreationTime)
 
188
        statvalue.st_mtime = _ftime_to_timestamp(&data.ftLastWriteTime)
 
189
        statvalue.st_atime = _ftime_to_timestamp(&data.ftLastAccessTime)
 
190
        statvalue._st_size = _get_size(data)
 
191
        statvalue.st_ino = 0
 
192
        statvalue.st_dev = 0
 
193
        return statvalue
 
194
 
 
195
    def read_dir(self, prefix, top):
 
196
        """Win32 implementation of DirReader.read_dir.
 
197
 
 
198
        :seealso: DirReader.read_dir
 
199
        """
 
200
        cdef WIN32_FIND_DATAW search_data
 
201
        cdef HANDLE hFindFile
 
202
        cdef int last_err
 
203
        cdef WCHAR *query
 
204
        cdef int result
 
205
 
 
206
        if prefix:
 
207
            relprefix = prefix + '/'
 
208
        else:
 
209
            relprefix = ''
 
210
        top_slash = top + '/'
 
211
 
 
212
        top_star = top_slash + '*'
 
213
 
 
214
        dirblock = []
 
215
 
 
216
        query = PyUnicode_AS_UNICODE(top_star)
 
217
        hFindFile = FindFirstFileW(query, &search_data)
 
218
        if hFindFile == INVALID_HANDLE_VALUE:
 
219
            # Raise an exception? This path doesn't seem to exist
 
220
            raise WindowsError(GetLastError(), top_star)
 
221
 
 
222
        try:
 
223
            result = 1
 
224
            while result:
 
225
                # Skip '.' and '..'
 
226
                if _should_skip(&search_data):
 
227
                    result = FindNextFileW(hFindFile, &search_data)
 
228
                    continue
 
229
                name_unicode = _get_name(&search_data)
 
230
                name_utf8 = PyUnicode_AsUTF8String(name_unicode)
 
231
                PyList_Append(dirblock,
 
232
                    (relprefix + name_utf8, name_utf8,
 
233
                     self._get_kind(&search_data),
 
234
                     self._get_stat_value(&search_data),
 
235
                     top_slash + name_unicode))
 
236
 
 
237
                result = FindNextFileW(hFindFile, &search_data)
 
238
            # FindNextFileW sets GetLastError() == ERROR_NO_MORE_FILES when it
 
239
            # actually finishes. If we have anything else, then we have a
 
240
            # genuine problem
 
241
            last_err = GetLastError()
 
242
            if last_err != ERROR_NO_MORE_FILES:
 
243
                raise WindowsError(last_err)
 
244
        finally:
 
245
            result = FindClose(hFindFile)
 
246
            if result == 0:
 
247
                last_err = GetLastError()
 
248
                # TODO: We should probably raise an exception if FindClose
 
249
                #       returns an error, however, I don't want to supress an
 
250
                #       earlier Exception, so for now, I'm ignoring this
 
251
        dirblock.sort(key=operator.itemgetter(1))
 
252
        return dirblock