~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/_walkdirs_win32.pyx

Turn completion assertions into separate methods.

Many common assertions used to be expressed as arguments to the complete
method.  This makes the checks more explicit, and the code easier to read.

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
 
# Copyright (C) 2008-2011 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 os
73
 
import stat
74
 
 
75
 
from bzrlib import _readdir_py
76
 
 
77
 
cdef object osutils
78
 
osutils = None
79
 
 
80
 
 
81
 
cdef class _Win32Stat:
82
 
    """Represent a 'stat' result generated from WIN32_FIND_DATA"""
83
 
 
84
 
    cdef readonly int st_mode
85
 
    cdef readonly double st_ctime
86
 
    cdef readonly double st_mtime
87
 
    cdef readonly double st_atime
88
 
    # We can't just declare this as 'readonly' because python2.4 doesn't define
89
 
    # T_LONGLONG as a structure member. So instead we just use a property that
90
 
    # will convert it correctly anyway.
91
 
    cdef __int64 _st_size
92
 
 
93
 
    property st_size:
94
 
        def __get__(self):
95
 
            return self._st_size
96
 
 
97
 
    # os.stat always returns 0, so we hard code it here
98
 
    cdef readonly int st_dev
99
 
    cdef readonly int st_ino
100
 
 
101
 
    def __repr__(self):
102
 
        """Repr is the same as a Stat object.
103
 
 
104
 
        (mode, ino, dev, nlink, uid, gid, size, atime, mtime, ctime)
105
 
        """
106
 
        return repr((self.st_mode, 0, 0, 0, 0, 0, self.st_size, self.st_atime,
107
 
                     self.st_mtime, self.st_ctime))
108
 
 
109
 
 
110
 
cdef object _get_name(WIN32_FIND_DATAW *data):
111
 
    """Extract the Unicode name for this file/dir."""
112
 
    return PyUnicode_FromUnicode(data.cFileName,
113
 
                                 wcslen(data.cFileName))
114
 
 
115
 
 
116
 
cdef int _get_mode_bits(WIN32_FIND_DATAW *data): # cannot_raise
117
 
    cdef int mode_bits
118
 
 
119
 
    mode_bits = 0100666 # writeable file, the most common
120
 
    if data.dwFileAttributes & FILE_ATTRIBUTE_READONLY == FILE_ATTRIBUTE_READONLY:
121
 
        mode_bits = mode_bits ^ 0222 # remove the write bits
122
 
    if data.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY == FILE_ATTRIBUTE_DIRECTORY:
123
 
        # Remove the FILE bit, set the DIR bit, and set the EXEC bits
124
 
        mode_bits = mode_bits ^ 0140111
125
 
    return mode_bits
126
 
 
127
 
 
128
 
cdef __int64 _get_size(WIN32_FIND_DATAW *data): # cannot_raise
129
 
    # Pyrex casts a DWORD into a PyLong anyway, so it is safe to do << 32
130
 
    # on a DWORD
131
 
    return ((<__int64>data.nFileSizeHigh) << 32) + data.nFileSizeLow
132
 
 
133
 
 
134
 
cdef double _ftime_to_timestamp(FILETIME *ft): # cannot_raise
135
 
    """Convert from a FILETIME struct into a floating point timestamp.
136
 
 
137
 
    The fields of a FILETIME structure are the hi and lo part
138
 
    of a 64-bit value expressed in 100 nanosecond units.
139
 
    1e7 is one second in such units; 1e-7 the inverse.
140
 
    429.4967296 is 2**32 / 1e7 or 2**32 * 1e-7.
141
 
    It also uses the epoch 1601-01-01 rather than 1970-01-01
142
 
    (taken from posixmodule.c)
143
 
    """
144
 
    cdef __int64 val
145
 
    # NB: This gives slightly different results versus casting to a 64-bit
146
 
    #     integer and doing integer math before casting into a floating
147
 
    #     point number. But the difference is in the sub millisecond range,
148
 
    #     which doesn't seem critical here.
149
 
    # secs between epochs: 11,644,473,600
150
 
    val = ((<__int64>ft.dwHighDateTime) << 32) + ft.dwLowDateTime
151
 
    return (val * 1.0e-7) - 11644473600.0
152
 
 
153
 
 
154
 
cdef int _should_skip(WIN32_FIND_DATAW *data): # cannot_raise
155
 
    """Is this '.' or '..' so we should skip it?"""
156
 
    if (data.cFileName[0] != c'.'):
157
 
        return 0
158
 
    if data.cFileName[1] == c'\0':
159
 
        return 1
160
 
    if data.cFileName[1] == c'.' and data.cFileName[2] == c'\0':
161
 
        return 1
162
 
    return 0
163
 
 
164
 
 
165
 
cdef class Win32ReadDir:
166
 
    """Read directories on win32."""
167
 
 
168
 
    cdef object _directory_kind
169
 
    cdef object _file_kind
170
 
 
171
 
    def __init__(self):
172
 
        self._directory_kind = _readdir_py._directory
173
 
        self._file_kind = _readdir_py._file
174
 
 
175
 
    def top_prefix_to_starting_dir(self, top, prefix=""):
176
 
        """See DirReader.top_prefix_to_starting_dir."""
177
 
        global osutils
178
 
        if osutils is None:
179
 
            from bzrlib import osutils
180
 
        return (osutils.safe_utf8(prefix), None, None, None,
181
 
                osutils.safe_unicode(top))
182
 
 
183
 
    cdef object _get_kind(self, WIN32_FIND_DATAW *data):
184
 
        if data.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY:
185
 
            return self._directory_kind
186
 
        return self._file_kind
187
 
 
188
 
    cdef _Win32Stat _get_stat_value(self, WIN32_FIND_DATAW *data):
189
 
        """Get the filename and the stat information."""
190
 
        cdef _Win32Stat statvalue
191
 
 
192
 
        statvalue = _Win32Stat()
193
 
        statvalue.st_mode = _get_mode_bits(data)
194
 
        statvalue.st_ctime = _ftime_to_timestamp(&data.ftCreationTime)
195
 
        statvalue.st_mtime = _ftime_to_timestamp(&data.ftLastWriteTime)
196
 
        statvalue.st_atime = _ftime_to_timestamp(&data.ftLastAccessTime)
197
 
        statvalue._st_size = _get_size(data)
198
 
        statvalue.st_ino = 0
199
 
        statvalue.st_dev = 0
200
 
        return statvalue
201
 
 
202
 
    def read_dir(self, prefix, top):
203
 
        """Win32 implementation of DirReader.read_dir.
204
 
 
205
 
        :seealso: DirReader.read_dir
206
 
        """
207
 
        cdef WIN32_FIND_DATAW search_data
208
 
        cdef HANDLE hFindFile
209
 
        cdef int last_err
210
 
        cdef WCHAR *query
211
 
        cdef int result
212
 
 
213
 
        if prefix:
214
 
            relprefix = prefix + '/'
215
 
        else:
216
 
            relprefix = ''
217
 
        top_slash = top + '/'
218
 
 
219
 
        top_star = top_slash + '*'
220
 
 
221
 
        dirblock = []
222
 
 
223
 
        query = PyUnicode_AS_UNICODE(top_star)
224
 
        hFindFile = FindFirstFileW(query, &search_data)
225
 
        if hFindFile == INVALID_HANDLE_VALUE:
226
 
            # Raise an exception? This path doesn't seem to exist
227
 
            raise WindowsError(GetLastError(), top_star)
228
 
 
229
 
        try:
230
 
            result = 1
231
 
            while result:
232
 
                # Skip '.' and '..'
233
 
                if _should_skip(&search_data):
234
 
                    result = FindNextFileW(hFindFile, &search_data)
235
 
                    continue
236
 
                name_unicode = _get_name(&search_data)
237
 
                name_utf8 = PyUnicode_AsUTF8String(name_unicode)
238
 
                PyList_Append(dirblock,
239
 
                    (relprefix + name_utf8, name_utf8,
240
 
                     self._get_kind(&search_data),
241
 
                     self._get_stat_value(&search_data),
242
 
                     top_slash + name_unicode))
243
 
 
244
 
                result = FindNextFileW(hFindFile, &search_data)
245
 
            # FindNextFileW sets GetLastError() == ERROR_NO_MORE_FILES when it
246
 
            # actually finishes. If we have anything else, then we have a
247
 
            # genuine problem
248
 
            last_err = GetLastError()
249
 
            if last_err != ERROR_NO_MORE_FILES:
250
 
                raise WindowsError(last_err)
251
 
        finally:
252
 
            result = FindClose(hFindFile)
253
 
            if result == 0:
254
 
                last_err = GetLastError()
255
 
                # TODO: We should probably raise an exception if FindClose
256
 
                #       returns an error, however, I don't want to supress an
257
 
                #       earlier Exception, so for now, I'm ignoring this
258
 
        dirblock.sort(key=operator.itemgetter(1))
259
 
        return dirblock
260
 
 
261
 
 
262
 
def lstat(path):
263
 
    """Equivalent to os.lstat, except match Win32ReadDir._get_stat_value.
264
 
    """
265
 
    return wrap_stat(os.lstat(path))
266
 
 
267
 
 
268
 
def fstat(fd):
269
 
    """Like os.fstat, except match Win32ReadDir._get_stat_value
270
 
 
271
 
    :seealso: wrap_stat
272
 
    """
273
 
    return wrap_stat(os.fstat(fd))
274
 
 
275
 
 
276
 
def wrap_stat(st):
277
 
    """Return a _Win32Stat object, based on the given stat result.
278
 
 
279
 
    On Windows, os.fstat(open(fname).fileno()) != os.lstat(fname). This is
280
 
    generally because os.lstat and os.fstat differ in what they put into st_ino
281
 
    and st_dev. What gets set where seems to also be dependent on the python
282
 
    version. So we always set it to 0 to avoid worrying about it.
283
 
    """
284
 
    cdef _Win32Stat statvalue
285
 
    statvalue = _Win32Stat()
286
 
    statvalue.st_mode = st.st_mode
287
 
    statvalue.st_ctime = st.st_ctime
288
 
    statvalue.st_mtime = st.st_mtime
289
 
    statvalue.st_atime = st.st_atime
290
 
    statvalue._st_size = st.st_size
291
 
    statvalue.st_ino = 0
292
 
    statvalue.st_dev = 0
293
 
    return statvalue