From d21f4071c3229e18b5038909c2371ab58702d559 Mon Sep 17 00:00:00 2001
From: david-sarah <david-sarah@jacaranda.org>
Date: Tue, 18 Jan 2011 23:59:11 -0800
Subject: [PATCH] Eliminate direct dependencies of Tahoe-LAFS on pywin32
 (rebased to trunk). refs #1274

---
 src/allmydata/_auto_deps.py     |  3 --
 src/allmydata/util/fileutil.py  | 65 ++++++++++++++++++++++-----------
 src/allmydata/windows/fixups.py | 19 +++++++---
 3 files changed, 57 insertions(+), 30 deletions(-)

diff --git a/src/allmydata/_auto_deps.py b/src/allmydata/_auto_deps.py
index f02155d4..f77db1c0 100644
--- a/src/allmydata/_auto_deps.py
+++ b/src/allmydata/_auto_deps.py
@@ -69,9 +69,6 @@ if sys.version_info < (2, 5):
 ##    # it is going to offer iocp reactor.  We currently require process management.  It would be
 ##    # better if Twisted would declare that it requires pywin32 if it is going to offer process
 ##    # management.  That is twisted ticket #3238 -- http://twistedmatrix.com/trac/ticket/3238 .
-##    # On the other hand, Tahoe also depends on pywin32 for getting free disk space statistics
-##    # (although that is not a hard requirement: if win32api can't be imported then we don't
-##    # rely on having the disk stats).
 ##    install_requires.append('pywin32')
 
 if hasattr(sys, 'frozen'): # for py2exe
diff --git a/src/allmydata/util/fileutil.py b/src/allmydata/util/fileutil.py
index 9a41c237..bfcffd8f 100644
--- a/src/allmydata/util/fileutil.py
+++ b/src/allmydata/util/fileutil.py
@@ -305,16 +305,27 @@ def abspath_expanduser_unicode(path):
     # there is always at least one Unicode path component.
     return os.path.normpath(path)
 
-windows = False
-try:
-    import win32api, win32con
-except ImportError:
-    pass
-else:
-    windows = True
-    # <http://msdn.microsoft.com/en-us/library/ms680621%28VS.85%29.aspx>
-    win32api.SetErrorMode(win32con.SEM_FAILCRITICALERRORS |
-                          win32con.SEM_NOOPENFILEERRORBOX)
+
+have_GetDiskFreeSpaceExW = False
+if sys.platform == "win32":
+    try:
+        from ctypes import WINFUNCTYPE, windll, POINTER, byref, c_ulonglong
+        from ctypes.wintypes import BOOL, DWORD, LPCWSTR
+
+        # <http://msdn.microsoft.com/en-us/library/aa383742%28v=VS.85%29.aspx>
+        PULARGE_INTEGER = POINTER(c_ulonglong)
+
+        # <http://msdn.microsoft.com/en-us/library/aa364937%28VS.85%29.aspx>
+        GetDiskFreeSpaceExW = WINFUNCTYPE(BOOL, LPCWSTR, PULARGE_INTEGER, PULARGE_INTEGER, PULARGE_INTEGER)(
+            ("GetDiskFreeSpaceExW", windll.kernel32))
+
+        # <http://msdn.microsoft.com/en-us/library/ms679360%28v=VS.85%29.aspx>
+        GetLastError = WINFUNCTYPE(DWORD)(("GetLastError", windll.kernel32))
+
+        have_GetDiskFreeSpaceExW = True
+    except Exception:
+        import traceback
+        traceback.print_exc()
 
 def get_disk_stats(whichdir, reserved_space=0):
     """Return disk statistics for the storage disk, in the form of a dict
@@ -338,15 +349,24 @@ def get_disk_stats(whichdir, reserved_space=0):
     filesystem as reserved_space.
     """
 
-    if windows:
-        # For Windows systems, where os.statvfs is not available, use GetDiskFreeSpaceEx.
-        # <http://docs.activestate.com/activepython/2.5/pywin32/win32api__GetDiskFreeSpaceEx_meth.html>
-        #
-        # Although the docs say that the argument should be the root directory
-        # of a disk, GetDiskFreeSpaceEx actually accepts any path on that disk
-        # (like its Win32 equivalent).
-
-        (free_for_nonroot, total, free_for_root) = win32api.GetDiskFreeSpaceEx(whichdir)
+    if have_GetDiskFreeSpaceExW:
+        # If this is a Windows system and GetDiskFreeSpaceExW is available, use it.
+        # (This might put up an error dialog unless
+        # SetErrorMode(SEM_FAILCRITICALERRORS | SEM_NOOPENFILEERRORBOX) has been called,
+        # which we do in allmydata.windows.fixups.initialize().)
+
+        n_free_for_nonroot = c_ulonglong(0)
+        n_total            = c_ulonglong(0)
+        n_free_for_root    = c_ulonglong(0)
+        retval = GetDiskFreeSpaceExW(whichdir, byref(n_free_for_nonroot),
+                                               byref(n_total),
+                                               byref(n_free_for_root))
+        if retval == 0:
+            raise OSError("Windows error %d attempting to get disk statistics for %r"
+                          % (GetLastError(), whichdir))
+        free_for_nonroot = n_free_for_nonroot.value
+        total            = n_total.value
+        free_for_root    = n_free_for_root.value
     else:
         # For Unix-like systems.
         # <http://docs.python.org/library/os.html#os.statvfs>
@@ -372,9 +392,12 @@ def get_disk_stats(whichdir, reserved_space=0):
     used = total - free_for_root
     avail = max(free_for_nonroot - reserved_space, 0)
 
-    return { 'total': total, 'free_for_root': free_for_root,
+    return { 'total': total,
+             'free_for_root': free_for_root,
              'free_for_nonroot': free_for_nonroot,
-             'used': used, 'avail': avail, }
+             'used': used,
+             'avail': avail,
+           }
 
 def get_available_space(whichdir, reserved_space):
     """Returns available space for share storage in bytes, or None if no
diff --git a/src/allmydata/windows/fixups.py b/src/allmydata/windows/fixups.py
index 35e048b7..719abd2d 100644
--- a/src/allmydata/windows/fixups.py
+++ b/src/allmydata/windows/fixups.py
@@ -8,6 +8,19 @@ def initialize():
         return True
     done = True
 
+    import codecs, re
+    from ctypes import WINFUNCTYPE, windll, POINTER, byref, c_int
+    from ctypes.wintypes import BOOL, HANDLE, DWORD, UINT, LPWSTR, LPCWSTR, LPVOID
+    from allmydata.util import log
+    from allmydata.util.encodingutil import canonical_encoding
+
+    # <http://msdn.microsoft.com/en-us/library/ms680621%28VS.85%29.aspx>
+    SetErrorMode = WINFUNCTYPE(UINT, UINT)(("SetErrorMode", windll.kernel32))
+    SEM_FAILCRITICALERRORS = 0x0001
+    SEM_NOOPENFILEERRORBOX = 0x8000
+
+    SetErrorMode(SEM_FAILCRITICALERRORS | SEM_NOOPENFILEERRORBOX)
+
     original_stderr = sys.stderr
 
     # If any exception occurs in this code, we'll probably try to print it on stderr,
@@ -18,12 +31,6 @@ def initialize():
         print >>original_stderr, isinstance(message, str) and message or repr(message)
         log.msg(message, level=log.WEIRD)
 
-    import codecs, re
-    from ctypes import WINFUNCTYPE, windll, POINTER, byref, c_int
-    from ctypes.wintypes import BOOL, HANDLE, DWORD, LPWSTR, LPCWSTR, LPVOID
-    from allmydata.util import log
-    from allmydata.util.encodingutil import canonical_encoding
-
     # Work around <http://bugs.python.org/issue6058>.
     codecs.register(lambda name: name == 'cp65001' and codecs.lookup('utf-8') or None)
 
-- 
2.45.2