Rev 4936: Move the functionality into osutils / extensions. in http://bazaar.launchpad.net/~jameinel/bzr/2.1.0rc1-set-mtime

John Arbash Meinel john at arbash-meinel.com
Tue Jan 5 21:15:42 GMT 2010


At http://bazaar.launchpad.net/~jameinel/bzr/2.1.0rc1-set-mtime

------------------------------------------------------------
revno: 4936
revision-id: john at arbash-meinel.com-20100105211522-s3vpbckrs9uvt11u
parent: john at arbash-meinel.com-20100105183525-6hbg9m5o76j1cb16
committer: John Arbash Meinel <john at arbash-meinel.com>
branch nick: 2.1.0rc1-set-mtime
timestamp: Tue 2010-01-05 15:15:22 -0600
message:
  Move the functionality into osutils / extensions.
  
  Aaron pointed out the functions should be on DiskTreeTransform not
  spread some to TreeTransform.
  
  Also, instead of using ctypes, we now just write our own function
  in the win32 extension module. Which seems to work just fine.
  And osutils.fset_mtime() is a thunk into that functionality.
-------------- next part --------------
=== modified file 'bzrlib/_walkdirs_win32.pyx'
--- a/bzrlib/_walkdirs_win32.pyx	2009-03-23 14:59:43 +0000
+++ b/bzrlib/_walkdirs_win32.pyx	2010-01-05 21:15:22 +0000
@@ -59,6 +59,31 @@
     # Wide character functions
     DWORD wcslen(WCHAR *)
 
+    ctypedef struct LARGE_INTEGER:
+        DWORD LowPart
+        DWORD HighPart
+
+    # TODO: Why does the win32 api use LARGE_INTEGER for this structure, but
+    #       FILETIME for the return from FindFirstFileW
+    ctypedef struct FILE_BASIC_INFO:
+         LARGE_INTEGER CreationTime
+         LARGE_INTEGER LastAccessTime
+         LARGE_INTEGER LastWriteTime
+         LARGE_INTEGER ChangeTime
+         DWORD FileAttributes
+
+    ctypedef enum FILE_INFO_BY_HANDLE_CLASS:
+        FileBasicInfo
+
+    int GetFileInformationByHandleEx(
+        HANDLE hFile, FILE_INFO_BY_HANDLE_CLASS type,
+        void* fileInformationBuffer, DWORD bufSize)
+    int SetFileInformationByHandle(
+        HANDLE hFile, FILE_INFO_BY_HANDLE_CLASS type,
+        void* fileInformationBuffer, DWORD bufSize)
+
+    long _get_osfhandle(int)
+
 
 cdef extern from "Python.h":
     WCHAR *PyUnicode_AS_UNICODE(object)
@@ -68,6 +93,7 @@
     object PyUnicode_AsUTF8String(object)
 
 
+import msvcrt
 import operator
 import stat
 
@@ -147,6 +173,17 @@
     return (val * 1.0e-7) - 11644473600.0
 
 
+cdef FILETIME _timestamp_to_ftime(double timestamp):
+    """Convert a time-since-epoch to a FILETIME."""
+    cdef __int64 val
+    cdef FILETIME result
+
+    val = <__int64>((timestamp + 11644473600.0) * 1.0e7)
+    result.dwHighDateTime = val >> 32
+    result.dwLowDateTime = val & 0xFFFFFFFF
+    return result
+
+
 cdef int _should_skip(WIN32_FIND_DATAW *data):
     """Is this '.' or '..' so we should skip it?"""
     if (data.cFileName[0] != c'.'):
@@ -250,3 +287,26 @@
                 #       earlier Exception, so for now, I'm ignoring this
         dirblock.sort(key=operator.itemgetter(1))
         return dirblock
+
+
+def fset_mtime(fileno, mtime):
+    """See osutils.fset_mtime."""
+    cdef HANDLE the_handle
+    cdef FILE_BASIC_INFO bi
+    cdef FILETIME ft
+    cdef int retval
+
+    ft = _timestamp_to_ftime(mtime)
+    the_handle = <HANDLE>(_get_osfhandle(fileno))
+    if the_handle == <HANDLE>(-1):
+        raise OSError('Invalid fileno') # IOError?
+    retval = GetFileInformationByHandleEx(the_handle, FileBasicInfo,
+                                          &bi, sizeof(FILE_BASIC_INFO))
+    if retval != 1:
+        raise OSError('Failed to GetFileInformationByHandleEx')
+    bi.LastWriteTime.LowPart = ft.dwLowDateTime
+    bi.LastWriteTime.HighPart = ft.dwHighDateTime
+    retval = SetFileInformationByHandle(the_handle, FileBasicInfo,
+                                        &bi, sizeof(FILE_BASIC_INFO))
+    if retval != 1:
+        raise OSError('Failed to SetFileInformationByHandle')

=== modified file 'bzrlib/osutils.py'
--- a/bzrlib/osutils.py	2009-12-23 00:15:34 +0000
+++ b/bzrlib/osutils.py	2010-01-05 21:15:22 +0000
@@ -1889,6 +1889,48 @@
     return b
 
 
+def _noop_fset_mtime(fileno, mtime):
+    """Do nothing.
+
+    This pretends to set the mtime of a file, but we were unable to import an
+    extension which would provide that functionality. So we just skip.
+
+    :seealso: fset_mtime
+    """
+
+
+_set_mtime_func = None
+def fset_mtime(fileno, mtime):
+    """Set the last-modified time (mtime) for this file handle.
+
+    This uses native OS functionality to set file times. As such, if extensions
+    are not compiled, this function becomes a no-op.
+
+    :param fileno: The fileno for the file, usually obtained from f.fileno()
+    :param mtime: time-since-epoch to set the mtime to. (same as time.time(),
+        or st.st_mtime, etc.). This can be a floating point number, but we
+        don't guarantee better than 1s resolution.
+    :return: None
+    """
+    global _set_mtime_func
+    if _set_mtime_func is None:
+        if sys.platform == "win32":
+            try:
+                from bzrlib._walkdirs_win32 import fset_mtime
+            except ImportError:
+                _set_mtime_func = _noop_fset_mtime
+            else:
+                _set_mtime_func = fset_mtime
+        else:
+            try:
+                from bzrlib._readdir_pyx import fset_mtime
+            except ImportError:
+                _set_mtime_func = _noop_fset_mtime
+            else:
+                _set_mtime_func = fset_mtime
+    return _set_mtime_func(fileno, mtime)
+
+
 def send_all(socket, bytes, report_activity=None):
     """Send all bytes on a socket.
 

=== modified file 'bzrlib/tests/test_osutils.py'
--- a/bzrlib/tests/test_osutils.py	2009-12-22 17:09:13 +0000
+++ b/bzrlib/tests/test_osutils.py	2010-01-05 21:15:22 +0000
@@ -2004,3 +2004,34 @@
         del os.environ['COLUMNS']
         # Whatever the result is, if we don't raise an exception, it's ok.
         osutils.terminal_width()
+
+
+class TestFSetMtime(tests.TestCaseInTempDir):
+
+    def have_extension(self):
+        return (UTF8DirReaderFeature.available()
+                or test__walkdirs_win32.win32_readdir_feature.available())
+
+    def test__noop(self):
+        # The _noop_fset_mtime function doesn't change the mtime
+        f = open('test', 'wb')
+        try:
+            mtime = os.fstat(f.fileno()).st_mtime
+            osutils._noop_fset_mtime(f.fileno(), time.time()-20)
+        finally:
+            f.close()
+        self.assertEqual(mtime, os.lstat('test').st_mtime)
+
+    def test_fset_mtime(self):
+        if not self.have_extension():
+            self.knownFailure('Pure python does not expose a way to set'
+                              ' the mtime of a file.')
+        f = open('test', 'wb')
+        new_mtime = time.time()-20.0
+        try:
+            mtime = os.fstat(f.fileno()).st_mtime
+            osutils.fset_mtime(f.fileno(), new_mtime)
+        finally:
+            f.close()
+        self.assertNotEqual(mtime, new_mtime)
+        self.assertEqual(new_mtime, os.lstat('test').st_mtime)

=== modified file 'bzrlib/transform.py'
--- a/bzrlib/transform.py	2010-01-05 18:35:25 +0000
+++ b/bzrlib/transform.py	2010-01-05 21:15:22 +0000
@@ -1024,6 +1024,7 @@
         self._limbo_children_names = {}
         # List of transform ids that need to be renamed from limbo into place
         self._needs_rename = set()
+        self._creation_mtime = None
 
     def finalize(self):
         """Release the working tree lock, if held, clean up limbo dir.
@@ -1147,6 +1148,15 @@
     def _read_symlink_target(self, trans_id):
         return os.readlink(self._limbo_name(trans_id))
 
+    def _set_mtime(self, f):
+        """All files that are created get the same mtime.
+
+        This time is set by the first object to be created.
+        """
+        if self._creation_mtime is None:
+            self._creation_mtime = time.time()
+        osutils.fset_mtime(f.fileno(), self._creation_mtime)
+
     def create_hardlink(self, path, trans_id):
         """Schedule creation of a hard link"""
         name = self._limbo_name(trans_id)
@@ -1300,7 +1310,6 @@
         DiskTreeTransform.__init__(self, tree, limbodir, pb,
                                    tree.case_sensitive)
         self._deletiondir = deletiondir
-        self._creation_mtime = None
 
     def canonical_path(self, path):
         """Get the canonical tree-relative path"""
@@ -1363,34 +1372,6 @@
         if typefunc(mode):
             os.chmod(self._limbo_name(trans_id), mode)
 
-    def _set_mtime(self, f):
-        """All files that are created get the same mtime.
-
-        This time is set by the first object to be created.
-        """
-        if self._creation_mtime is None:
-            self._creation_mtime = time.time()
-        import ctypes, msvcrt
-        class BASIC_INFO(ctypes.Structure):
-             _fields_ = [('CreationTime', ctypes.c_int64),
-                         ('LastAccessTime', ctypes.c_int64),
-                         ('LastWriteTime', ctypes.c_int64),
-                         ('ChangeTime', ctypes.c_int64),
-                         ('FileAttributes', ctypes.c_uint32),
-                        ]
-        bi = BASIC_INFO()
-        gfi = ctypes.windll.kernel32.GetFileInformationByHandleEx
-        handle = msvcrt.get_osfhandle(f.fileno())
-        ret = gfi(handle, 0, ctypes.byref(bi), ctypes.sizeof(bi))
-        assert ret, "failed to get file information: %d" % (
-            ctypes.GetLastError(),)
-        sfi = ctypes.windll.kernel32.SetFileInformationByHandle
-        bi.LastWriteTime = int((self._creation_mtime + 11644473600.0) * 1.0e7)
-        ret = sfi(handle, 0, ctypes.byref(bi), ctypes.sizeof(bi))
-        assert ret, "Failed to set file information: %d" % (
-            ctypes.GetLastError(),)
-
-
     def iter_tree_children(self, parent_id):
         """Iterate through the entry's tree children, if any"""
         try:



More information about the bazaar-commits mailing list