Skip to content

Commit

Permalink
Merge pull request #10 from antirotor/feature/older-windows-compatibi…
Browse files Browse the repository at this point in the history
…lity

compatibility for windws <= 7, docstrings and few fixes
  • Loading branch information
antirotor authored Jun 9, 2020
2 parents 6047db0 + 5eb8993 commit 172348a
Show file tree
Hide file tree
Showing 5 changed files with 162 additions and 62 deletions.
17 changes: 11 additions & 6 deletions setup.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
from setuptools import setup, find_packages
# -*- coding: utf-8 -*-
"""Metadata file."""
from setuptools import setup
import os
import imp
from io import open
Expand All @@ -22,20 +24,23 @@
"Programming Language :: Python :: 3.5",
"Programming Language :: Python :: 3.6",
"Programming Language :: Python :: 3.7",
"Programming Language :: Python :: 3.8",
"Topic :: Software Development :: Libraries :: Python Modules",
"Topic :: System :: Filesystems"
"Topic :: System :: Filesystems"

]

setup(name='speedcopy',
version=version,
description=('Windows xcopy patch for shutil.copyfile'
', based on pyfastcopy'),
description=('Replacement or alternative for python copyfile()'
'utilizing server side copy on network shares for faster'
'copying.'),
author='Ondrej Samohel',
author_email='[email protected]',
url='https://github.com/antirotor/speedcopy',
long_description=long_description,
long_description_content_type='text/markdown',
packages=['speedcopy'],
classifiers=classifiers
packages=['speedcopy'],
classifiers=classifiers,
tests_require=['pytest'],
)
117 changes: 87 additions & 30 deletions speedcopy/__init__.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,17 @@
"""
Speed up shutil.copyfile by using sendfile system call and robocopy for
windows. Based on `pyfastcopy`, extending it to windows.
# -*- coding: utf-8 -*-
"""Speedcopy copyfile replacement.
Speedcopy speeds up copying files over network by utilizing system specific
calls taking advantage of server side copy. This speed increase is visible
when copying file on same share and only if share server supports server
side copy (samba 4.1.0).
See:
https://wiki.samba.org/index.php/Server-Side_Copy
Attributes:
SPEEDCOPY_DEBUG (bool): set to print debug messages.
"""
import errno
import os
Expand All @@ -13,6 +24,7 @@


def debug(msg):
"""Print debug message to console."""
if SPEEDCOPY_DEBUG:
print(msg)

Expand Down Expand Up @@ -55,21 +67,25 @@ def debug(msg):
IOC_READ = 2

def IOC_TYPECHECK(t):
"""
"""Return the size of given ioctl type.
Returns the size of given type, and check its suitability for use in an
ioctl command number.
"""
result = ctypes.sizeof(t)
assert result <= _IOC_SIZEMASK, result
return result

def IOC(dir, type, nr, size):
"""
dir
One of IOC_NONE, IOC_WRITE, IOC_READ, or IOC_READ|IOC_WRITE.
Direction is from the application's point of view, not kernel's.
size (14-bits unsigned integer)
Size of the buffer passed to ioctl's "arg" argument.
"""Prepare command for ioctl.
Args:
dir (int): One of ``IOC_NONE``, ``IOC_WRITE``, ``IOC_READ``
or ``IOC_READ|IOC_WRITE``. Direction is from the
application's point of view, not kernel's.
size (int): (14-bits unsigned integer) Size of the buffer passed
to ioctl's "arg" argument.
"""
assert dir <= _IOC_DIRMASK, dir
assert type <= _IOC_TYPEMASK, type
Expand All @@ -78,10 +94,11 @@ def IOC(dir, type, nr, size):
return (dir << _IOC_DIRSHIFT) | (type << _IOC_TYPESHIFT) | (nr << _IOC_NRSHIFT) | (size << _IOC_SIZESHIFT) # noqa: E501

def IOW(type, nr, size):
"""
An ioctl with write parameters.
size (ctype type or instance)
Type/structure of the argument passed to ioctl's "arg" argument.
"""Ioctl with write parameters.
Args:
size (ctype type or instance): Type/structure of the argument
passed to ioctl's "arg" argument.
"""
return IOC(IOC_WRITE, type, nr, IOC_TYPECHECK(size))

Expand All @@ -91,8 +108,15 @@ def IOW(type, nr, size):
"EBADF", "ENOTSOCK", "EOPNOTSUPP")}

def _copyfile_sendfile(fsrc, fdst):
"""
Copy data from fsrc to fdst using sendfile, return True if success.
"""Copy data from fsrc to fdst using sendfile.
Args:
fsrc (str): Source file.
fdst (str): Destination file.
Returns:
bool: True on success.
"""
if not _sendfile:
return False
Expand Down Expand Up @@ -121,8 +145,21 @@ def _copyfile_sendfile(fsrc, fdst):

def copyfile(src, dst, follow_symlinks=True):
"""Copy data from src to dst.
If follow_symlinks is not set and src is a symbolic link, a new
symlink will be created instead of copying the file it points to.
Args:
src (str): Source file.
dst (str): Destination file.
follow_symlinks (bool): If ``follow_symlinks`` is not set and
``src`` is a symbolic link, a new symlink will be created
instead of copying the file it points to.
Returns:
str: Destination on success
Raises:
shutil.SpecialFileError: when source/destination is invalid.
shutil.SameFileError: if ``src`` and ``dst`` are same.
"""
if shutil._samefile(src, dst):
raise shutil.SameFileError(
Expand Down Expand Up @@ -185,10 +222,28 @@ def copyfile(src, dst, follow_symlinks=True):
else:
def copyfile(src, dst, follow_symlinks=True):
"""Copy data from src to dst.
It uses windows native CopyFileW method to do so, making advantage of
server-side copy where available.
"""
It uses windows native ``CopyFile2`` method to do so, making advantage
of server-side copy where available. If this method is not available
it will fallback to ``CopyFileW`` (on Windows 7 and older).
Args:
src (str): Source file.
dst (str): Destination file.
follow_symlinks (bool): If ``follow_symlinks`` is not set and
``src`` is a symbolic link, a new symlink will be created
instead of copying the file it points to.
Returns:
str: Destination on success
Raises:
shutil.SpecialFileError: when source/destination is invalid.
shutil.SameFileError: if ``src`` and ``dst`` are same.
OSError: if file no exist
IOError: if copying failed on windows API level.
"""
if shutil._samefile(src, dst):
# Get shutil.SameFileError if available (Python 3.4+)
# else fall back to original behavior using shutil.Error
Expand All @@ -212,7 +267,12 @@ def copyfile(src, dst, follow_symlinks=True):
else:
kernel32 = ctypes.WinDLL('kernel32',
use_last_error=True, use_errno=True)
copyfile = kernel32.CopyFile2
try:
copyfile = kernel32.CopyFile2
except AttributeError:
# on windows 7 and older
copyfile = kernel32.CopyFileW

copyfile.argtypes = (ctypes.c_wchar_p,
ctypes.c_wchar_p,
ctypes.c_void_p)
Expand All @@ -228,7 +288,7 @@ def copyfile(src, dst, follow_symlinks=True):
ret = copyfile('\\\\?\\' + source_file,
'\\\\?\\' + dest_file, None)

if ret != 0:
if ret == 0:
error = ctypes.get_last_error()
if error == 0:
return dst
Expand All @@ -238,21 +298,18 @@ def copyfile(src, dst, follow_symlinks=True):
if error == 997:
return dst
raise IOError(
"File {!r} copy failed, error: {}".format(src, error))
"File {!r} copy failed, error: {}".format(
src, ctypes.FormatError(error)))
return dst


def patch_copyfile():
"""
Used to monkey patch shutil.copyfile()
"""
"""Monkey patch shutil.copyfile()."""
if shutil.copyfile != copyfile:
shutil._orig_copyfile = shutil.copyfile
shutil.copyfile = copyfile


def unpatch_copyfile():
"""
Restore original function
"""
"""Restore original function."""
shutil.copyfile = shutil._orig_copyfile
64 changes: 40 additions & 24 deletions speedcopy/fstatfs.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
"""
python fstatfs implementation taken from:
# -*- coding: utf-8 -*-
"""Python fstatfs implementation.
Taken from:
https://github.com/mithro/rcfiles
"""
import os
import ctypes
Expand All @@ -11,8 +14,12 @@


class Fs_types:
# Constants for filesystem magic
# https://www.gnu.org/software/coreutils/filesystems.html
"""Constants for filesystem magic.
See:
https://www.gnu.org/software/coreutils/filesystems.html
"""

filesystems = {
"ADFS_SUPER_MAGIC": 0xadf5,
"AFFS_SUPER_MAGIC": 0xADFF,
Expand Down Expand Up @@ -64,6 +71,7 @@ class Fs_types:
types = {}

def __init__(self):
"""Remove ``MAGIC`` and ``SUPER`` postfixes."""
for name, value in self.filesystems.items():
if name.endswith('MAGIC'):
hname = name[:-6]
Expand All @@ -72,19 +80,20 @@ def __init__(self):


class statfs_t(ctypes.Structure):
"""Describes the details about a filesystem.
Attributes:
f_type: type of file system (see below)
f_bsize: optimal transfer block size
f_blocks: total data blocks in file system
f_bfree: free blocks in fs
f_bavail: free blocks avail to non-superuser
f_files: total file nodes in file system
f_ffree: free file nodes in fs
f_fsid: file system id
f_namelen: maximum length of filenames
"""
Describes the details about a filesystem.
f_type: type of file system (see below)
f_bsize: optimal transfer block size
f_blocks: total data blocks in file system
f_bfree: free blocks in fs
f_bavail: free blocks avail to non-superuser
f_files: total file nodes in file system
f_ffree: free file nodes in fs
f_fsid: file system id
f_namelen: maximum length of filenames
"""

_fields_ = [
("f_type", ctypes.c_long), # type of file system (see below)
("f_bsize", ctypes.c_long), # optimal transfer block size
Expand All @@ -102,8 +111,10 @@ class statfs_t(ctypes.Structure):


class FilesystemInfo():
"""Get filesystem info."""

def __init__(self):
"""Prepare system calls."""
self._statfs = libc.statfs
self._statfs.argtypes = [ctypes.c_char_p, ctypes.POINTER(statfs_t)]
self._statfs.rettype = ctypes.c_int
Expand All @@ -113,12 +124,15 @@ def __init__(self):
self._fstatfs.rettype = ctypes.c_int

def statfs(self, path):
"""
The function statfs() returns information about a mounted file system.
"""Get information about mounted file system by path.
Args:
path: is the pathname of any file within the mounted file system.
path (str): is the pathname of any file within
the mounted file system.
Returns:
Returns a statfs_t object.
"""
buf = statfs_t()
err = self._statfs(path, ctypes.byref(buf))
Expand All @@ -128,12 +142,13 @@ def statfs(self, path):
return buf

def fstatfs(self, fd):
"""
The fuction fstatfs() returns information about a mounted file ssytem.
"""Get information about mounted file system by file descriptor.
Args:
fd: A file descriptor.
Returns:
Returns a statfs_t object.
"""
buf = statfs_t()
fileno = fd.fileno()
Expand All @@ -145,10 +160,11 @@ def fstatfs(self, fd):
return buf

def filesystem(self, path_or_fd):
"""
Get the filesystem type a file/path is on.
"""Get the filesystem type a file/path is on.
Args:
path_or_fd: A string path or an object which has a fileno function.
path_or_fd (str or IOBase): A string path or an object which has
a :meth:`IOBase.fileno()` function.
Returns:
A string name of the file system.
"""
Expand Down
6 changes: 4 additions & 2 deletions speedcopy/version.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
# -*- coding: utf-8 -*-
"""Version definition."""
VERSION_MAJOR = 2
VERSION_MINOR = 0
VERSION_PATCH = 1
VERSION_MINOR = 1
VERSION_PATCH = 0

version_info = (VERSION_MAJOR, VERSION_MINOR, VERSION_PATCH)
version = '%i.%i.%i' % version_info
Expand Down
Loading

0 comments on commit 172348a

Please sign in to comment.