~bzr-pqm/bzr/bzr.dev

3504.4.13 by John Arbash Meinel
Clean up according to review comments.
1
# Copyright (C) 2008 Canonical Ltd
3504.4.3 by John Arbash Meinel
Start working on an extension specifically for win32,
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., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
16
17
"""Helper functions for Walkdirs on win32."""
18
19
20
cdef extern from "_walkdirs_win32.h":
3557.2.8 by Alexander Belchenko
We don't need all the extra cdef statments in a cdef extern block
21
    struct _HANDLE:
3504.4.3 by John Arbash Meinel
Start working on an extension specifically for win32,
22
        pass
23
    ctypedef _HANDLE *HANDLE
3504.4.8 by John Arbash Meinel
Some code cleanups.
24
    ctypedef unsigned long DWORD
3504.4.10 by John Arbash Meinel
Move the helpers to be standalone, rather than members
25
    ctypedef long long __int64
3504.4.3 by John Arbash Meinel
Start working on an extension specifically for win32,
26
    ctypedef unsigned short WCHAR
3557.2.8 by Alexander Belchenko
We don't need all the extra cdef statments in a cdef extern block
27
    struct _FILETIME:
3504.4.6 by John Arbash Meinel
Start exposing the times on the stat, this now seems to be a complete walkdirs implementation.
28
        DWORD dwHighDateTime
29
        DWORD dwLowDateTime
3504.4.3 by John Arbash Meinel
Start working on an extension specifically for win32,
30
    ctypedef _FILETIME FILETIME
31
3557.2.8 by Alexander Belchenko
We don't need all the extra cdef statments in a cdef extern block
32
    struct _WIN32_FIND_DATAW:
3504.4.3 by John Arbash Meinel
Start working on an extension specifically for win32,
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
3557.2.8 by Alexander Belchenko
We don't need all the extra cdef statments in a cdef extern block
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()
3504.4.3 by John Arbash Meinel
Start working on an extension specifically for win32,
58
3504.4.4 by John Arbash Meinel
We have walkdirs basically working, only without timestamps
59
    # Wide character functions
60
    DWORD wcslen(WCHAR *)
61
3504.4.3 by John Arbash Meinel
Start working on an extension specifically for win32,
62
63
cdef extern from "Python.h":
64
    WCHAR *PyUnicode_AS_UNICODE(object)
65
    Py_ssize_t PyUnicode_GET_SIZE(object)
3504.4.4 by John Arbash Meinel
We have walkdirs basically working, only without timestamps
66
    object PyUnicode_FromUnicode(WCHAR *, Py_ssize_t)
3504.4.8 by John Arbash Meinel
Some code cleanups.
67
    int PyList_Append(object, object) except -1
68
    object PyUnicode_AsUTF8String(object)
69
70
3504.4.3 by John Arbash Meinel
Start working on an extension specifically for win32,
71
import operator
72
import stat
73
74
from bzrlib import osutils
75
76
3504.4.9 by John Arbash Meinel
Switch to using a cdef object with readonly attributes.
77
cdef class _Win32Stat:
3504.4.3 by John Arbash Meinel
Start working on an extension specifically for win32,
78
    """Represent a 'stat' result generated from WIN32_FIND_DATA"""
79
3504.4.10 by John Arbash Meinel
Move the helpers to be standalone, rather than members
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
    cdef readonly __int64 st_size
3504.4.3 by John Arbash Meinel
Start working on an extension specifically for win32,
85
86
    # os.stat always returns 0, so we hard code it here
3504.4.10 by John Arbash Meinel
Move the helpers to be standalone, rather than members
87
    cdef readonly int st_dev
88
    cdef readonly int st_ino
3504.4.3 by John Arbash Meinel
Start working on an extension specifically for win32,
89
90
    def __repr__(self):
91
        """Repr is the same as a Stat object.
92
93
        (mode, ino, dev, nlink, uid, gid, size, atime, mtime, ctime)
94
        """
95
        return repr((self.st_mode, 0, 0, 0, 0, 0, self.st_size, self.st_atime,
96
                     self.st_mtime, self.st_ctime))
97
98
3504.4.11 by John Arbash Meinel
A bit more reorganizing.
99
cdef object _get_name(WIN32_FIND_DATAW *data):
100
    """Extract the Unicode name for this file/dir."""
101
    return PyUnicode_FromUnicode(data.cFileName,
102
                                 wcslen(data.cFileName))
103
104
3504.4.10 by John Arbash Meinel
Move the helpers to be standalone, rather than members
105
cdef int _get_mode_bits(WIN32_FIND_DATAW *data):
106
    cdef int mode_bits
107
108
    mode_bits = 0100666 # writeable file, the most common
109
    if data.dwFileAttributes & FILE_ATTRIBUTE_READONLY == FILE_ATTRIBUTE_READONLY:
3557.2.5 by John Arbash Meinel
Test that the empty-directory logic for all _walkdirs implementations is correct.
110
        mode_bits = mode_bits ^ 0222 # remove the write bits
3504.4.10 by John Arbash Meinel
Move the helpers to be standalone, rather than members
111
    if data.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY == FILE_ATTRIBUTE_DIRECTORY:
112
        # Remove the FILE bit, set the DIR bit, and set the EXEC bits
3557.2.5 by John Arbash Meinel
Test that the empty-directory logic for all _walkdirs implementations is correct.
113
        mode_bits = mode_bits ^ 0140111
3504.4.10 by John Arbash Meinel
Move the helpers to be standalone, rather than members
114
    return mode_bits
115
116
117
cdef __int64 _get_size(WIN32_FIND_DATAW *data):
118
    # Pyrex casts a DWORD into a PyLong anyway, so it is safe to do << 32
119
    # on a DWORD
120
    return ((<__int64>data.nFileSizeHigh) << 32) + data.nFileSizeLow
121
122
123
cdef double _ftime_to_timestamp(FILETIME *ft):
124
    """Convert from a FILETIME struct into a floating point timestamp.
125
126
    The fields of a FILETIME structure are the hi and lo part
127
    of a 64-bit value expressed in 100 nanosecond units.
128
    1e7 is one second in such units; 1e-7 the inverse.
129
    429.4967296 is 2**32 / 1e7 or 2**32 * 1e-7.
130
    It also uses the epoch 1601-01-01 rather than 1970-01-01
131
    (taken from posixmodule.c)
132
    """
133
    cdef __int64 val
134
    # NB: This gives slightly different results versus casting to a 64-bit
135
    #     integer and doing integer math before casting into a floating
136
    #     point number. But the difference is in the sub millisecond range,
137
    #     which doesn't seem critical here.
138
    # secs between epochs: 11,644,473,600
139
    val = ((<__int64>ft.dwHighDateTime) << 32) + ft.dwLowDateTime
140
    return (val * 1.0e-7) - 11644473600.0
141
3504.4.3 by John Arbash Meinel
Start working on an extension specifically for win32,
142
3504.4.11 by John Arbash Meinel
A bit more reorganizing.
143
cdef int _should_skip(WIN32_FIND_DATAW *data):
144
    """Is this '.' or '..' so we should skip it?"""
145
    if (data.cFileName[0] != c'.'):
146
        return 0
147
    if data.cFileName[1] == c'\0':
148
        return 1
149
    if data.cFileName[1] == c'.' and data.cFileName[2] == c'\0':
150
        return 1
151
    return 0
152
153
3504.4.3 by John Arbash Meinel
Start working on an extension specifically for win32,
154
cdef class Win32Finder:
155
    """A class which encapsulates the search of files in a given directory"""
156
157
    cdef object _top
158
    cdef object _prefix
159
3504.4.4 by John Arbash Meinel
We have walkdirs basically working, only without timestamps
160
    cdef object _directory_kind
161
    cdef object _file_kind
3504.4.3 by John Arbash Meinel
Start working on an extension specifically for win32,
162
163
    cdef object _pending
164
    cdef object _last_dirblock
165
166
    def __init__(self, top, prefix=""):
167
        self._top = top
168
        self._prefix = prefix
169
3504.4.4 by John Arbash Meinel
We have walkdirs basically working, only without timestamps
170
        self._directory_kind = osutils._directory_kind
171
        self._file_kind = osutils._formats[stat.S_IFREG]
3504.4.3 by John Arbash Meinel
Start working on an extension specifically for win32,
172
3504.4.8 by John Arbash Meinel
Some code cleanups.
173
        self._pending = [(osutils.safe_utf8(prefix), osutils.safe_unicode(top))]
3504.4.3 by John Arbash Meinel
Start working on an extension specifically for win32,
174
        self._last_dirblock = None
175
176
    def __iter__(self):
177
        return self
178
3504.4.12 by John Arbash Meinel
A couple small cleanups, make test_osutils more correct
179
    cdef object _get_kind(self, WIN32_FIND_DATAW *data):
180
        if data.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY:
181
            return self._directory_kind
182
        return self._file_kind
183
3504.4.9 by John Arbash Meinel
Switch to using a cdef object with readonly attributes.
184
    cdef _Win32Stat _get_stat_value(self, WIN32_FIND_DATAW *data):
3504.4.4 by John Arbash Meinel
We have walkdirs basically working, only without timestamps
185
        """Get the filename and the stat information."""
3504.4.10 by John Arbash Meinel
Move the helpers to be standalone, rather than members
186
        cdef _Win32Stat statvalue
187
188
        statvalue = _Win32Stat()
189
        statvalue.st_mode = _get_mode_bits(data)
190
        statvalue.st_ctime = _ftime_to_timestamp(&data.ftCreationTime)
191
        statvalue.st_mtime = _ftime_to_timestamp(&data.ftLastWriteTime)
192
        statvalue.st_atime = _ftime_to_timestamp(&data.ftLastAccessTime)
193
        statvalue.st_size = _get_size(data)
194
        statvalue.st_ino = 0
195
        statvalue.st_dev = 0
196
        return statvalue
3504.4.4 by John Arbash Meinel
We have walkdirs basically working, only without timestamps
197
198
    def _get_files_in(self, directory, relprefix):
3557.2.2 by John Arbash Meinel
Include better documentation about what is going on. (suggested by Bialix)
199
        """Return the dirblock for all files in the given directory.
200
201
        :param directory: A path that can directly access the files on disk.
202
            Should be a Unicode object.
203
        :param relprefix: A psuedo path for these files (as inherited from the
204
            original 'prefix=XXX' when instantiating this class.)
205
            It should be a UTF-8 string.
206
        :return: A dirblock for all the files of the form
207
            [(utf8_relpath, utf8_fname, kind, _Win32Stat, unicode_abspath)]
208
        """
3504.4.3 by John Arbash Meinel
Start working on an extension specifically for win32,
209
        cdef WIN32_FIND_DATAW search_data
210
        cdef HANDLE hFindFile
211
        cdef int last_err
212
        cdef WCHAR *query
213
        cdef int result
214
215
        top_star = directory + '*'
216
217
        dirblock = []
218
219
        query = PyUnicode_AS_UNICODE(top_star)
220
        hFindFile = FindFirstFileW(query, &search_data)
221
        if hFindFile == INVALID_HANDLE_VALUE:
222
            # Raise an exception? This path doesn't seem to exist
3504.4.8 by John Arbash Meinel
Some code cleanups.
223
            raise WindowsError(GetLastError(), top_star)
3504.4.3 by John Arbash Meinel
Start working on an extension specifically for win32,
224
225
        try:
226
            result = 1
227
            while result:
228
                # Skip '.' and '..'
3504.4.11 by John Arbash Meinel
A bit more reorganizing.
229
                if _should_skip(&search_data):
3504.4.3 by John Arbash Meinel
Start working on an extension specifically for win32,
230
                    result = FindNextFileW(hFindFile, &search_data)
231
                    continue
3504.4.12 by John Arbash Meinel
A couple small cleanups, make test_osutils more correct
232
                name_unicode = _get_name(&search_data)
3504.4.8 by John Arbash Meinel
Some code cleanups.
233
                name_utf8 = PyUnicode_AsUTF8String(name_unicode)
234
                PyList_Append(dirblock, 
3557.2.2 by John Arbash Meinel
Include better documentation about what is going on. (suggested by Bialix)
235
                    (relprefix + name_utf8, name_utf8, 
3504.4.8 by John Arbash Meinel
Some code cleanups.
236
                     self._get_kind(&search_data),
237
                     self._get_stat_value(&search_data),
3557.2.2 by John Arbash Meinel
Include better documentation about what is going on. (suggested by Bialix)
238
                     directory + name_unicode))
3504.4.4 by John Arbash Meinel
We have walkdirs basically working, only without timestamps
239
3504.4.3 by John Arbash Meinel
Start working on an extension specifically for win32,
240
                result = FindNextFileW(hFindFile, &search_data)
3504.4.8 by John Arbash Meinel
Some code cleanups.
241
            # FindNextFileW sets GetLastError() == ERROR_NO_MORE_FILES when it
242
            # actually finishes. If we have anything else, then we have a
243
            # genuine problem
244
            last_err = GetLastError()
245
            if last_err != ERROR_NO_MORE_FILES:
246
                raise WindowsError(last_err)
3504.4.3 by John Arbash Meinel
Start working on an extension specifically for win32,
247
        finally:
248
            result = FindClose(hFindFile)
249
            if result == 0:
250
                last_err = GetLastError()
3557.2.2 by John Arbash Meinel
Include better documentation about what is going on. (suggested by Bialix)
251
                # TODO: We should probably raise an exception if FindClose
252
                #       returns an error, however, I don't want to supress an
253
                #       earlier Exception, so for now, I'm ignoring this
3504.4.3 by John Arbash Meinel
Start working on an extension specifically for win32,
254
        return dirblock
255
3504.4.8 by John Arbash Meinel
Some code cleanups.
256
    cdef _update_pending(self):
257
        """If we had a result before, add the subdirs to pending."""
3504.4.3 by John Arbash Meinel
Start working on an extension specifically for win32,
258
        if self._last_dirblock is not None:
259
            # push the entries left in the dirblock onto the pending queue
260
            # we do this here, because we allow the user to modified the
261
            # queue before the next iteration
262
            for d in reversed(self._last_dirblock):
3504.4.4 by John Arbash Meinel
We have walkdirs basically working, only without timestamps
263
                if d[2] == self._directory_kind:
3504.4.8 by John Arbash Meinel
Some code cleanups.
264
                    self._pending.append((d[0], d[-1]))
265
            self._last_dirblock = None
266
        
267
    def __next__(self):
268
        self._update_pending()
3504.4.3 by John Arbash Meinel
Start working on an extension specifically for win32,
269
        if not self._pending:
270
            raise StopIteration()
3504.4.8 by John Arbash Meinel
Some code cleanups.
271
        relroot, top = self._pending.pop()
3504.4.3 by John Arbash Meinel
Start working on an extension specifically for win32,
272
        # NB: At the moment Pyrex doesn't support Unicode literals, which means
273
        # that all of these string literals are going to be upcasted to Unicode
274
        # at runtime... :(
275
        # Maybe we could use unicode(x) during __init__?
276
        if relroot:
277
            relprefix = relroot + '/'
278
        else:
279
            relprefix = ''
280
        top_slash = top + '/'
281
3504.4.4 by John Arbash Meinel
We have walkdirs basically working, only without timestamps
282
        dirblock = self._get_files_in(top_slash, relprefix)
3504.4.3 by John Arbash Meinel
Start working on an extension specifically for win32,
283
        dirblock.sort(key=operator.itemgetter(1))
284
        self._last_dirblock = dirblock
285
        return (relroot, top), dirblock
286
287
288
def _walkdirs_utf8_win32_find_file(top, prefix=""):
289
    """Implement a version of walkdirs_utf8 for win32.
290
291
    This uses the find files api to both list the files and to stat them.
292
    """
3504.4.4 by John Arbash Meinel
We have walkdirs basically working, only without timestamps
293
    return Win32Finder(top, prefix=prefix)