Skip to content
Projects
Groups
Snippets
Help
This project
Loading...
Sign in / Register
Toggle navigation
P
pyfs
Overview
Overview
Details
Activity
Cycle Analytics
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Issues
0
Issues
0
List
Board
Labels
Milestones
Merge Requests
0
Merge Requests
0
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Charts
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
OpenEdx
pyfs
Commits
25868cb2
Commit
25868cb2
authored
Aug 04, 2010
by
rfkelly0
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
dokan: basic outputting of file/directory attributes
parent
ab0c29e9
Hide whitespace changes
Inline
Side-by-side
Showing
2 changed files
with
198 additions
and
49 deletions
+198
-49
fs/expose/dokan/__init__.py
+182
-42
fs/expose/dokan/dokan_ctypes.py
+16
-7
No files found.
fs/expose/dokan/__init__.py
View file @
25868cb2
...
@@ -64,13 +64,13 @@ from fs.path import *
...
@@ -64,13 +64,13 @@ from fs.path import *
from
fs.functools
import
wraps
from
fs.functools
import
wraps
try
:
try
:
import
dokan_ctypes
as
dokan
import
dokan_ctypes
as
lib
dokan
except
NotImplementedError
:
except
NotImplementedError
:
raise
ImportError
(
"Dokan found but not usable"
)
raise
ImportError
(
"Dokan found but not usable"
)
DokanMain
=
dokan
.
DokanMain
DokanMain
=
lib
dokan
.
DokanMain
DokanOperations
=
dokan
.
DokanOperations
DokanOperations
=
lib
dokan
.
DokanOperations
# Options controlling the behaiour of the Dokan filesystem
# Options controlling the behaiour of the Dokan filesystem
DOKAN_OPTION_DEBUG
=
1
DOKAN_OPTION_DEBUG
=
1
...
@@ -95,6 +95,16 @@ FILE_SHARE_WRITE = 0x02
...
@@ -95,6 +95,16 @@ FILE_SHARE_WRITE = 0x02
FILE_FLAG_BACKUP_SEMANTICS
=
0x02000000
FILE_FLAG_BACKUP_SEMANTICS
=
0x02000000
FILE_FLAG_OVERLAPPED
=
0x40000000
FILE_FLAG_OVERLAPPED
=
0x40000000
FILE_ATTRIBUTE_ARCHIVE
=
32
FILE_ATTRIBUTE_COMPRESSED
=
2048
FILE_ATTRIBUTE_DIRECTORY
=
16
FILE_ATTRIBUTE_HIDDEN
=
2
FILE_ATTRIBUTE_NORMAL
=
128
FILE_ATTRIBUTE_OFFLINE
=
4096
FILE_ATTRIBUTE_READONLY
=
1
FILE_ATTRIBUTE_SYSTEM
=
4
FILE_ATTRIBUTE_TEMPORARY
=
4
CREATE_NEW
=
1
CREATE_NEW
=
1
CREATE_ALWAYS
=
2
CREATE_ALWAYS
=
2
OPEN_EXISTING
=
3
OPEN_EXISTING
=
3
...
@@ -104,9 +114,12 @@ TRUNCATE_EXISTING = 5
...
@@ -104,9 +114,12 @@ TRUNCATE_EXISTING = 5
GENERIC_READ
=
128
GENERIC_READ
=
128
GENERIC_WRITE
=
1180054
GENERIC_WRITE
=
1180054
STARTUP_TIME
=
time
.
time
()
# Some useful per-process global information
NATIVE_ENCODING
=
sys
.
getfilesystemencoding
()
NATIVE_ENCODING
=
sys
.
getfilesystemencoding
()
DATETIME_ZERO
=
datetime
.
datetime
(
1
,
1
,
1
,
0
,
0
,
0
)
DATETIME_STARTUP
=
datetime
.
datetime
.
now
()
def
handle_fs_errors
(
func
):
def
handle_fs_errors
(
func
):
"""Method decorator to report FS errors in the appropriate way.
"""Method decorator to report FS errors in the appropriate way.
...
@@ -148,6 +161,7 @@ class FSOperations(DokanOperations):
...
@@ -148,6 +161,7 @@ class FSOperations(DokanOperations):
self
.
_files_by_handle
=
{}
self
.
_files_by_handle
=
{}
self
.
_files_lock
=
threading
.
Lock
()
self
.
_files_lock
=
threading
.
Lock
()
self
.
_next_handle
=
100
self
.
_next_handle
=
100
# TODO: do we need this for dokan? It's a hangover from FUSE.
# Dokan expects a succesful write() to be reflected in the file's
# Dokan expects a succesful write() to be reflected in the file's
# reported size, but the FS might buffer writes and prevent this.
# reported size, but the FS might buffer writes and prevent this.
# We explicitly keep track of the size FUSE expects a file to be.
# We explicitly keep track of the size FUSE expects a file to be.
...
@@ -184,7 +198,8 @@ class FSOperations(DokanOperations):
...
@@ -184,7 +198,8 @@ class FSOperations(DokanOperations):
finally
:
finally
:
self
.
_files_lock
.
release
()
self
.
_files_lock
.
release
()
def
unmount
(
self
,
info
):
def
Unmount
(
self
,
info
):
raise
ValueError
(
"HERE"
)
if
self
.
_on_unmount
:
if
self
.
_on_unmount
:
self
.
_on_unmount
()
self
.
_on_unmount
()
...
@@ -199,6 +214,7 @@ class FSOperations(DokanOperations):
...
@@ -199,6 +214,7 @@ class FSOperations(DokanOperations):
raise
ResourceNotFoundError
(
path
)
raise
ResourceNotFoundError
(
path
)
return
return
# Convert the various access rights into an appropriate mode string.
# Convert the various access rights into an appropriate mode string.
# TODO: I'm sure this misses some important semantics.
retcode
=
0
retcode
=
0
if
access
&
GENERIC_READ
:
if
access
&
GENERIC_READ
:
if
access
&
GENERIC_WRITE
:
if
access
&
GENERIC_WRITE
:
...
@@ -239,7 +255,7 @@ class FSOperations(DokanOperations):
...
@@ -239,7 +255,7 @@ class FSOperations(DokanOperations):
info
.
contents
.
IsDirectory
=
True
info
.
contents
.
IsDirectory
=
True
except
FSError
:
except
FSError
:
# Sadly, win32 OSFS will raise all kinds of strange errors
# Sadly, win32 OSFS will raise all kinds of strange errors
# if you try to open() a directory.
# if you try to open() a directory.
Need to check by hand.
if
self
.
fs
.
isdir
(
path
):
if
self
.
fs
.
isdir
(
path
):
info
.
contents
.
IsDirectory
=
True
info
.
contents
.
IsDirectory
=
True
else
:
else
:
...
@@ -325,34 +341,146 @@ class FSOperations(DokanOperations):
...
@@ -325,34 +341,146 @@ class FSOperations(DokanOperations):
def
GetFileInformation
(
self
,
path
,
buffer
,
info
):
def
GetFileInformation
(
self
,
path
,
buffer
,
info
):
path
=
normpath
(
path
)
path
=
normpath
(
path
)
info
=
self
.
fs
.
getinfo
(
path
)
info
=
self
.
fs
.
getinfo
(
path
)
if
"name"
not
in
info
:
info
[
"name"
]
=
basename
(
path
)
data
=
buffer
.
contents
data
=
buffer
.
contents
data
.
dwFileAttributes
=
0
_info2finddataw
(
info
,
data
)
data
.
ftCreationTime
=
dokan
.
FILETIME
(
0
,
0
)
data
.
ftCreationTime
=
dokan
.
FILETIME
(
0
,
0
)
@handle_fs_errors
data
.
ftAccessTime
=
dokan
.
FILETIME
(
0
,
0
)
def
FindFiles
(
self
,
path
,
fillFindData
,
info
):
data
.
ftWriteTime
=
dokan
.
FILETIME
(
0
,
0
)
path
=
normpath
(
path
)
data
.
nFileSizeHigh
=
0
for
nm
in
self
.
fs
.
listdir
(
path
):
data
.
nFileSizeLow
=
7
fpath
=
pathjoin
(
path
,
nm
)
data
.
cFileName
=
basename
(
path
)
data
=
_info2finddataw
(
self
.
fs
.
getinfo
(
fpath
)
)
data
.
cAlternateFileName
=
None
fillFindData
(
ctypes
.
byref
(
data
),
info
)
@handle_fs_errors
@handle_fs_errors
def
FindFilesWithPattern
(
self
,
path
,
pattern
,
fillFindData
,
info
):
def
FindFilesWithPattern
(
self
,
path
,
pattern
,
fillFindData
,
info
):
path
=
normpath
(
path
)
path
=
normpath
(
path
)
datas
=
[]
infolist
=
[]
for
nm
in
self
.
fs
.
listdir
(
path
,
wildcard
=
pattern
):
try
:
data
=
dokan
.
WIN32_FIND_DATAW
()
for
finfo
in
self
.
fs
.
listdir
(
path
,
info
=
True
):
data
.
dwFileAttributes
=
0
nm
=
finfo
[
"name"
]
data
.
ftCreateTime
=
dokan
.
FILETIME
(
0
,
0
)
if
not
libdokan
.
DokanIsNameInExpression
(
pattern
,
nm
,
True
):
data
.
ftAccessTime
=
dokan
.
FILETIME
(
0
,
0
)
continue
data
.
ftWriteTime
=
dokan
.
FILETIME
(
0
,
0
)
infolist
.
append
(
finfo
)
data
.
nFileSizeHigh
=
0
except
(
TypeError
,
KeyError
,
UnsupportedError
):
data
.
nFileSizeLow
=
0
filtered
=
True
data
.
cFileName
=
nm
for
nm
in
self
.
fs
.
listdir
(
path
):
data
.
cAlternateFileName
=
""
if
not
libdokan
.
DokanIsNameInExpression
(
pattern
,
nm
,
True
):
continue
finfo
=
self
.
fs
.
getinfo
(
pathjoin
(
path
,
nm
))
finfo
[
"name"
]
=
nm
infolist
.
append
(
finfo
)
for
finfo
in
infolist
:
data
=
_info2finddataw
(
finfo
)
fillFindData
(
ctypes
.
byref
(
data
),
info
)
fillFindData
(
ctypes
.
byref
(
data
),
info
)
datas
.
append
(
data
)
@handle_fs_errors
def
SetFileAttributes
(
self
,
path
,
attrs
,
info
):
path
=
normpath
(
path
)
# TODO: decode various file attributes
@handle_fs_errors
def
SetFileTime
(
self
,
path
,
ctime
,
atime
,
mtime
,
info
):
path
=
normpath
(
path
)
if
ctime
is
not
None
:
raise
UnsupportedError
(
"cannot set creation time"
)
if
atime
is
not
None
:
atime
=
_filetime_to_datetime
(
atime
)
if
mtime
is
not
None
:
mtime
=
_filetime_to_datetime
(
mtime
)
self
.
fs
.
settimes
(
path
,
atime
,
mtime
,
ctime
)
@handle_fs_errors
def
DeleteFile
(
self
,
path
,
info
):
path
=
normpath
(
path
)
self
.
fs
.
remove
(
path
)
@handle_fs_errors
def
DeleteDirectory
(
self
,
path
,
info
):
path
=
normpath
(
path
)
self
.
fs
.
removedir
(
path
)
@handle_fs_errors
def
MoveFile
(
self
,
src
,
dst
,
overwrite
,
info
):
src
=
normpath
(
src
)
dst
=
normpath
(
dst
)
try
:
self
.
fs
.
move
(
src
,
dst
,
overwrite
=
overwrite
)
except
ResourceInvalidError
:
self
.
fs
.
movedir
(
src
,
dst
,
overwrite
=
overwrite
)
@handle_fs_errors
def
SetEndOfFile
(
self
,
path
,
dst
,
overwrite
,
info
):
path
=
normpath
(
path
)
(
file
,
_
,
lock
)
=
self
.
_get_file
(
info
.
contents
.
Context
)
lock
.
acquire
()
try
:
f
.
truncate
()
finally
:
lock
.
release
()
@handle_fs_errors
def
GetDiskFreeSpaceEx
(
self
,
nBytesAvail
,
nBytesTotal
,
nBytesFree
,
info
):
nBytesAvail
[
0
]
=
8
*
1024
*
1024
nBytesTotal
[
0
]
=
20
*
1024
*
1024
nBytesFree
[
0
]
=
12
*
1024
*
1024
@handle_fs_errors
def
GetVolumeInformation
(
self
,
vnmBuf
,
vnmSz
,
sNum
,
maxLen
,
flags
,
fnmBuf
,
fnmSz
,
info
):
vnmBuf
vnm
=
ctypes
.
create_unicode_buffer
(
"fs.expose.dokan"
[:
vnmSz
-
1
])
ctypes
.
memmove
(
vnmBuf
,
vnm
,
len
(
vnm
.
value
))
fnm
=
ctypes
.
create_unicode_buffer
(
"fs.expose.dokan"
[:
fnmSz
-
1
])
ctypes
.
memmove
(
fnmBuf
,
fnm
,
len
(
fnm
.
value
))
sNum
[
0
]
=
0
maxLen
[
0
]
=
libdokan
.
MAX_PATH
flags
=
0
def
_info2attrmask
(
info
):
"""Convert a file/directory info dict to a win32 file attributes mask."""
attrs
=
0
st_mode
=
info
.
get
(
"st_mode"
,
None
)
if
st_mode
:
if
statinfo
.
S_ISDIR
(
st_mode
):
attrs
|=
FILE_ATTRIBUTE_DIRECTORY
elif
statinfo
.
S_ISREG
(
st_mode
):
attrs
|=
FILE_ATTRIBUTE_NORMAL
return
attrs
def
_info2finddataw
(
info
,
data
=
None
):
"""Convert a file/directory info dict into a WIN32_FIND_DATAW struct."""
if
data
is
None
:
data
=
libdokan
.
WIN32_FIND_DATAW
()
data
.
dwFileAttributes
=
_info2attrmask
(
info
)
data
.
ftCreateTime
=
_datetime2filetime
(
info
.
get
(
"created_time"
,
None
))
data
.
ftAccessTime
=
_datetime2filetime
(
info
.
get
(
"accessed_time"
,
None
))
data
.
ftWriteTime
=
_datetime2filetime
(
info
.
get
(
"modified_time"
,
None
))
data
.
nFileSizeHigh
=
info
.
get
(
"size"
,
0
)
>>
32
data
.
nFileSizeLow
=
info
.
get
(
"size"
,
0
)
&
0xffffff
data
.
cFileName
=
info
.
get
(
"name"
,
""
)
data
.
cAlternateFileName
=
""
return
data
def
_filetime2datetime
(
ftime
):
"""Convert a FILETIME struct info datetime.datetime object."""
if
ftime
is
None
:
return
DATETIME_ZERO
if
ftime
.
dwLowDateTime
==
0
and
ftime
.
dwHighDateTime
==
0
:
return
DATETIME_ZERO
t
=
ftime
.
dwLowDateTime
&
(
ftime
.
dwHighDateTime
<<
32
)
return
datetime
.
datetime
.
fromtimestamp
(
t
)
def
_datetime2filetime
(
dtime
):
"""Convert a FILETIME struct info datetime.datetime object."""
if
dtime
is
None
:
return
libdokan
.
FILETIME
(
0
,
0
)
if
dtime
==
DATETIME_ZERO
:
return
libdokan
.
FILETIME
(
0
,
0
)
# TODO: this doesn't work on win32
t
=
int
(
dtime
.
strftime
(
"
%
s"
))
return
libdokan
.
FILETIME
(
t
>>
32
,
t
&
0xffffff
)
def
mount
(
fs
,
drive
,
foreground
=
False
,
ready_callback
=
None
,
unmount_callback
=
None
,
**
kwds
):
def
mount
(
fs
,
drive
,
foreground
=
False
,
ready_callback
=
None
,
unmount_callback
=
None
,
**
kwds
):
...
@@ -371,22 +499,34 @@ def mount(fs, drive, foreground=False, ready_callback=None, unmount_callback=Non
...
@@ -371,22 +499,34 @@ def mount(fs, drive, foreground=False, ready_callback=None, unmount_callback=Non
keyword arguments will be passed through as options to the underlying
keyword arguments will be passed through as options to the underlying
Dokan library. Some interesting options include:
Dokan library. Some interesting options include:
* TODO: what options?
* numthreads: number of threads to use for handling Dokan requests
* fsname: name to display in explorer etc
* flags: DOKAN_OPTIONS bitmask
"""
"""
def
check_ready
(
mp
=
None
):
if
ready_callback
:
for
_
in
xrange
(
100
):
try
:
os
.
stat
(
mp
.
path
)
except
EnvironmentError
:
time
.
sleep
(
0.01
)
else
:
if
mp
and
mp
.
poll
()
!=
None
:
raise
OSError
(
"dokan mount process exited prematurely"
)
return
ready_callback
()
if
foreground
:
if
foreground
:
# We use OPTION_REMOVABLE for now as it gives an "eject" option
numthreads
=
kwds
.
pop
(
"numthreads"
,
0
)
# in the context menu. Will remove this later.
flags
=
kwds
.
pop
(
"flags"
,
0
)
# We use a single thread, also for debugging.
opts
=
libdokan
.
DOKAN_OPTIONS
(
drive
,
numthreads
,
flags
)
op
ts
=
dokan
.
DOKAN_OPTIONS
(
drive
,
1
,
DOKAN_OPTION_DEBUG
|
DOKAN_OPTION_STDERR
|
DOKAN_OPTION_REMOVABLE
)
op
s
=
FSOperations
(
fs
,
on_unmount
=
unmount_callback
)
ops
=
FSOperations
(
fs
,
on_init
=
ready_callback
,
on_unmount
=
unmount_callback
)
threading
.
Thread
(
target
=
check_ready
)
.
start
(
)
res
=
DokanMain
(
ctypes
.
byref
(
opts
),
ctypes
.
byref
(
ops
.
buffer
))
res
=
DokanMain
(
ctypes
.
byref
(
opts
),
ctypes
.
byref
(
ops
.
buffer
))
if
res
!=
DOKAN_SUCCESS
:
if
res
!=
DOKAN_SUCCESS
:
raise
Runtime
Error
(
"Dokan failed with error:
%
d"
%
(
res
,))
raise
OS
Error
(
"Dokan failed with error:
%
d"
%
(
res
,))
else
:
else
:
mp
=
MountProcess
(
fs
,
drive
,
kwds
)
mp
=
MountProcess
(
fs
,
drive
,
kwds
)
if
ready_callback
:
check_ready
(
mp
)
ready_callback
()
if
unmount_callback
:
if
unmount_callback
:
orig_unmount
=
mp
.
unmount
orig_unmount
=
mp
.
unmount
def
new_unmount
():
def
new_unmount
():
...
@@ -403,7 +543,7 @@ def unmount(drive):
...
@@ -403,7 +543,7 @@ def unmount(drive):
It works but may leave dangling processes; its better to use the "unmount"
It works but may leave dangling processes; its better to use the "unmount"
method on the MountProcess class if you have one.
method on the MountProcess class if you have one.
"""
"""
if
not
dokan
.
DokanUnmount
(
drive
):
if
not
lib
dokan
.
DokanUnmount
(
drive
):
raise
OSError
(
"filesystem could not be unmounted:
%
s"
%
(
drive
,))
raise
OSError
(
"filesystem could not be unmounted:
%
s"
%
(
drive
,))
...
@@ -442,7 +582,7 @@ class MountProcess(subprocess.Popen):
...
@@ -442,7 +582,7 @@ class MountProcess(subprocess.Popen):
def
unmount
(
self
):
def
unmount
(
self
):
"""Cleanly unmount the Dokan filesystem, terminating this subprocess."""
"""Cleanly unmount the Dokan filesystem, terminating this subprocess."""
if
not
dokan
.
DokanUnmount
(
self
.
drive
):
if
not
lib
dokan
.
DokanUnmount
(
self
.
drive
):
raise
OSError
(
"the filesystem could not be unmounted:
%
s"
%
(
self
.
drive
,))
raise
OSError
(
"the filesystem could not be unmounted:
%
s"
%
(
self
.
drive
,))
self
.
terminate
()
self
.
terminate
()
...
@@ -470,9 +610,9 @@ class MountProcess(subprocess.Popen):
...
@@ -470,9 +610,9 @@ class MountProcess(subprocess.Popen):
if
__name__
==
"__main__"
:
if
__name__
==
"__main__"
:
import
os
,
os
.
path
import
os
,
os
.
path
from
fs.tempfs
import
TempFS
from
fs.tempfs
import
TempFS
def
ready_callback
():
print
"READY"
fs
=
TempFS
()
fs
=
TempFS
()
fs
.
setcontents
(
"test1.txt"
,
"test one"
)
fs
.
setcontents
(
"test1.txt"
,
"test one"
)
mount
(
fs
,
"Q"
,
foreground
=
True
,
ready_callback
=
ready_callback
)
flags
=
DOKAN_OPTION_DEBUG
|
DOKAN_OPTION_STDERR
|
DOKAN_OPTION_REMOVABLE
mount
(
fs
,
"Q"
,
foreground
=
True
,
numthreads
=
1
,
flags
=
flags
)
fs/expose/dokan/dokan_ctypes.py
View file @
25868cb2
"""
fs.expose.dokan.dokan_ctypes: low-level ctypes interface to Dokan
"""
from
ctypes
import
*
from
ctypes
import
*
try
:
try
:
DokanMain
=
windll
.
Dokan
.
DokanMain
DokanMain
=
windll
.
Dokan
.
DokanMain
DokanVersion
=
windll
.
Dokan
.
DokanVersion
except
AttributeError
:
except
AttributeError
:
raise
ImportError
(
"Dokan DLL not found"
)
raise
ImportError
(
"Dokan DLL not found"
)
...
@@ -15,6 +21,13 @@ UCHAR = c_ubyte
...
@@ -15,6 +21,13 @@ UCHAR = c_ubyte
LPDWORD
=
POINTER
(
DWORD
)
LPDWORD
=
POINTER
(
DWORD
)
LONGLONG
=
c_longlong
LONGLONG
=
c_longlong
DokanVersion
.
restype
=
ULONG
DokanVersion
.
argtypes
=
()
if
DokanVersion
()
<
0
:
# TODO: find min supported version
raise
ImportError
(
"Dokan DLL is too old"
)
MAX_PATH
=
260
MAX_PATH
=
260
class
FILETIME
(
Structure
):
class
FILETIME
(
Structure
):
...
@@ -197,7 +210,9 @@ class DokanOperations(object):
...
@@ -197,7 +210,9 @@ class DokanOperations(object):
try
:
try
:
setattr
(
self
.
buffer
,
nm
,
typ
(
getattr
(
self
,
nm
)))
setattr
(
self
.
buffer
,
nm
,
typ
(
getattr
(
self
,
nm
)))
except
AttributeError
:
except
AttributeError
:
setattr
(
self
.
buffer
,
nm
,
typ
(
self
.
_noop
))
#setattr(self.buffer,nm,typ(self._noop))
# This bizarre syntax creates a NULL function pointer.
setattr
(
self
.
buffer
,
nm
,
typ
())
def
_noop
(
self
,
*
args
):
def
_noop
(
self
,
*
args
):
return
-
1
return
-
1
...
@@ -226,12 +241,6 @@ DokanUnmount.argtypes = (
...
@@ -226,12 +241,6 @@ DokanUnmount.argtypes = (
BOOL
,
# ignore case
BOOL
,
# ignore case
)
)
DokanVersion
=
windll
.
Dokan
.
DokanVersion
DokanVersion
.
restype
=
ULONG
DokanVersion
.
argtypes
=
(
)
DokanDriverVersion
=
windll
.
Dokan
.
DokanDriverVersion
DokanDriverVersion
=
windll
.
Dokan
.
DokanDriverVersion
DokanDriverVersion
.
restype
=
ULONG
DokanDriverVersion
.
restype
=
ULONG
DokanDriverVersion
.
argtypes
=
(
DokanDriverVersion
.
argtypes
=
(
...
...
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment