├── .gitignore ├── AUTHORS ├── CHANGES.txt ├── LICENSE.txt ├── MANIFEST.in ├── README.md ├── docs ├── Makefile ├── appdirfs.rst ├── base.rst ├── browsewin.rst ├── commands.rst ├── concepts.rst ├── conf.py ├── contrib.rst ├── contrib │ ├── bigfs.rst │ ├── davfs.rst │ ├── index.rst │ └── tahoelafs.rst ├── errors.rst ├── expose.rst ├── expose │ ├── django_storage.rst │ ├── dokan.rst │ ├── fuse.rst │ ├── importhook.rst │ ├── index.rst │ ├── sftp.rst │ └── xmlrpc.rst ├── filelike.rst ├── filesystems.rst ├── ftpfs.rst ├── getting_started.rst ├── httpfs.rst ├── implementersguide.rst ├── index.rst ├── interface.rst ├── introduction.rst ├── make.bat ├── memoryfs.rst ├── mountfs.rst ├── multifs.rst ├── opener.rst ├── opening.rst ├── osfs.rst ├── path.rst ├── releasenotes.rst ├── remote.rst ├── rpcfs.rst ├── s3fs.rst ├── sftpfs.rst ├── spelling_wordlist.txt ├── tempfs.rst ├── utilities.rst ├── utils.rst ├── watch.rst ├── wrapfs │ ├── .tmp_index.html.90192~ │ ├── base.rst │ ├── hidedotfiles.rst │ ├── index.rst │ ├── lazyfs.rst │ ├── limitsize.rst │ └── readonlyfs.rst └── zipfs.rst ├── fs ├── __init__.py ├── appdirfs.py ├── appdirs.py ├── base.py ├── browsewin.py ├── commands │ ├── __init__.py │ ├── fscat │ ├── fscat.py │ ├── fscp │ ├── fscp.py │ ├── fsinfo │ ├── fsinfo.py │ ├── fsls │ ├── fsls.py │ ├── fsmkdir │ ├── fsmkdir.py │ ├── fsmount │ ├── fsmount.py │ ├── fsmv │ ├── fsmv.py │ ├── fsrm │ ├── fsrm.py │ ├── fsserve │ ├── fsserve.py │ ├── fstree │ ├── fstree.py │ └── runner.py ├── compatibility.py ├── contrib │ ├── __init__.py │ ├── archivefs.py │ ├── bigfs │ │ ├── __init__.py │ │ └── subrangefile.py │ ├── davfs │ │ ├── __init__.py │ │ ├── util.py │ │ └── xmlobj.py │ ├── sqlitefs.py │ └── tahoelafs │ │ ├── __init__.py │ │ ├── connection.py │ │ ├── test_tahoelafs.py │ │ └── util.py ├── errors.py ├── expose │ ├── __init__.py │ ├── django_storage.py │ ├── dokan │ │ ├── __init__.py │ │ └── libdokan.py │ ├── ftp.py │ ├── fuse │ │ ├── __init__.py │ │ ├── fuse.py │ │ ├── fuse3.py │ │ └── fuse_ctypes.py │ ├── http.py │ ├── importhook.py │ ├── serve │ │ ├── __init__.py │ │ ├── packetstream.py │ │ ├── server.py │ │ └── threadpool.py │ ├── sftp.py │ ├── wsgi │ │ ├── __init__.py │ │ ├── dirtemplate.py │ │ ├── serve_home.py │ │ └── wsgi.py │ └── xmlrpc.py ├── filelike.py ├── ftpfs.py ├── httpfs.py ├── iotools.py ├── local_functools.py ├── memoryfs.py ├── mountfs.py ├── multifs.py ├── opener.py ├── osfs │ ├── __init__.py │ ├── watch.py │ ├── watch_inotify.py │ ├── watch_win32.py │ └── xattrs.py ├── path.py ├── remote.py ├── remotefs.py ├── rpcfs.py ├── s3fs.py ├── sftpfs.py ├── tempfs.py ├── tests │ ├── __init__.py │ ├── data │ │ ├── UTF-8-demo.txt │ │ └── __init__.py │ ├── test_archivefs.py │ ├── test_errors.py │ ├── test_expose.py │ ├── test_fs.py │ ├── test_ftpfs.py │ ├── test_importhook.py │ ├── test_iotools.py │ ├── test_mountfs.py │ ├── test_multifs.py │ ├── test_opener.py │ ├── test_path.py │ ├── test_remote.py │ ├── test_rpcfs.py │ ├── test_s3fs.py │ ├── test_sqlitefs.py │ ├── test_utils.py │ ├── test_watch.py │ ├── test_wrapfs.py │ ├── test_xattr.py │ ├── test_zipfs.py │ └── zipfs_binary_test.py ├── utils.py ├── watch.py ├── wrapfs │ ├── __init__.py │ ├── debugfs.py │ ├── hidedotfilesfs.py │ ├── hidefs.py │ ├── lazyfs.py │ ├── limitsizefs.py │ ├── readonlyfs.py │ └── subfs.py ├── xattrs.py └── zipfs.py ├── requirements.txt ├── setup.py └── tox.ini /.gitignore: -------------------------------------------------------------------------------- 1 | *.orig 2 | *.project 3 | *.pyc 4 | *.egg 5 | *.egg-info 6 | *.db 7 | *.sqlite 8 | *.sublime-* 9 | /**/__*__ 10 | .eggs 11 | .tox 12 | build/ 13 | -------------------------------------------------------------------------------- /AUTHORS: -------------------------------------------------------------------------------- 1 | 2 | Will McGugan (will@willmcgugan.com) 3 | Ryan Kelly (ryan@rfk.id.au) 4 | 5 | -------------------------------------------------------------------------------- /CHANGES.txt: -------------------------------------------------------------------------------- 1 | 2 | 0.3: 3 | 4 | * New FS implementations: 5 | * FTPFS: access a plain old FTP server 6 | * S3FS: access remote files stored in Amazon S3 7 | * RPCFS: access remote files using a simple XML-RPC protocol 8 | * SFTPFS: access remote files on a SFTP server 9 | * WrapFS: filesystem that wraps an FS object and transparently 10 | modifies its contents (think encryption, compression, ...) 11 | * LazyFS: lazily instantiate an FS object the first time it is used 12 | * ReadOnlyFS: a WrapFS that makes an fs read-only 13 | * Ability to expose FS objects to the outside world: 14 | * expose.fuse: expose an FS object using FUSE 15 | * expose.xmlrpc: expose an FS object a simple XML-RPC protocol 16 | * expose.sftp: expose an FS object SFTP 17 | * expose.django_storage: convert FS object to Django Storage object 18 | * Extended attribute support (getxattr/setxattr/delxattr/listxattrs) 19 | * Change watching support (add_watcher/del_watcher) 20 | * Insist on unicode paths throughout: 21 | * output paths are always unicode 22 | * bytestring input paths are decoded as early as possible 23 | * Renamed "fs.helpers" to "fs.path", and renamed the contained functions 24 | to match those offered by os.path 25 | * fs.remote: utilities for implementing FS classes that interface 26 | with a remote filesystem 27 | * fs.errors: updated exception hierarchy, with support for converting 28 | to/from standard OSError instances 29 | * Added cache_hint method to base.py 30 | * Added settimes method to base implementation 31 | * New implementation of print_fs, accessible through tree method on base class 32 | 33 | 34 | 0.4: 35 | 36 | * New FS implementations (under fs.contrib): 37 | * BigFS: read contents of a BIG file (C&C game file format) 38 | * DAVFS: access remote files stored on a WebDAV server 39 | * TahoeLAFS: access files stored in a Tahoe-LAFS grid 40 | * New fs.expose implementations: 41 | * dokan: mount an FS object as a drive using Dokan (win32-only) 42 | * importhook: import modules from files in an FS object 43 | * Modified listdir and walk methods to accept callables as well as strings 44 | for wildcards. 45 | * Added listdirinfo method, which yields both the entry names and the 46 | corresponding info dicts in a single operation. 47 | * Made SubFS a subclass of WrapFS, and moved it into its own module at 48 | fs.wrapfs.subfs. 49 | * Path-handling fixes for OSFS on win32: 50 | * Work properly when pointing to the root of a drive. 51 | * Better handling of remote UNC paths. 52 | * Add ability to switch off use of long UNC paths. 53 | * OSFSWatchMixin improvements: 54 | * watch_inotify: allow more than one watcher on a single path. 55 | * watch_win32: don't create immortal reference cycles. 56 | * watch_win32: report errors if the filesystem doesn't support 57 | ReadDirectoryChangesW. 58 | * MountFS: added support for mounting at the root directory, and for 59 | mounting over an existing mount. 60 | * Added 'getpathurl' and 'haspathurl' methods. 61 | * Added utils.isdir(fs,path,info) and utils.isfile(fs,path,info); these 62 | can often determine whether a path is a file or directory by inspecting 63 | the info dict and avoid an additional query to the filesystem. 64 | * Added utility module 'fs.filelike' with some helpers for building and 65 | manipulating file-like objects. 66 | * Added getmeta and hasmeta methods 67 | * Separated behaviour of setcontents and createfile 68 | * Added a getmmap to base 69 | * Added command line scripts fsls, fstree, fscat, fscp, fsmv 70 | * Added command line scripts fsmkdir, fsmount 71 | * Made SFTP automatically pick up keys if no other authentication 72 | is available 73 | * Optimized listdir and listdirinfo in SFTPFS 74 | * Made memoryfs work with threads 75 | * Added copyfile_non_atomic and movefile_non_atomic for improved performance of multi-threaded copies 76 | * Added a concept of a writeable FS to MultiFS 77 | * Added ilistdir() and ilistdirinfo() methods, which are generator-based 78 | variants of listdir() and listdirinfo(). 79 | * Removed obsolete module fs.objecttree; use fs.path.PathMap instead. 80 | * Added setcontents_async method to base 81 | * Added `appdirfs` module to abstract per-user application directories 82 | 83 | 0.5: 84 | 85 | * Ported to Python 3.X 86 | * Added a DeleteRootError to exceptions thrown when trying to delete '/' 87 | * Added a remove_all function to utils 88 | * Added sqlitefs to fs.contrib, contributed by Nitin Bhide 89 | * Added archivefs to fs.contrib, contributed by btimby 90 | * Added some polish to fstree command and unicode box lines rather than ascii art 91 | 92 | 0.5.1: 93 | 94 | * Fixed a hang bug in readline 95 | * Added copydir_progress to fs.utils 96 | 97 | 0.5.2: 98 | 99 | * Added utils.open_atomic_write 100 | 101 | 0.5.3: 102 | 103 | * Implemented scandir in listdir if available 104 | * Fix for issue where local.getpreferredencoding returns empty string 105 | 106 | 107 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | Copyright (c) 2009-2015, Will McGugan and contributors. 2 | All rights reserved. 3 | 4 | Redistribution and use in source and binary forms, with or without modification, 5 | are permitted provided that the following conditions are met: 6 | 7 | 1. Redistributions of source code must retain the above copyright notice, 8 | this list of conditions and the following disclaimer. 9 | 10 | 2. Redistributions in binary form must reproduce the above copyright 11 | notice, this list of conditions and the following disclaimer in the 12 | documentation and/or other materials provided with the distribution. 13 | 14 | 3. Neither the name of PyFilesystem nor the names of its contributors 15 | may be used to endorse or promote products derived from this software 16 | without specific prior written permission. 17 | 18 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 19 | ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 20 | WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 21 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR 22 | ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 23 | (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 24 | LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON 25 | ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 26 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 27 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 28 | 29 | -------------------------------------------------------------------------------- /MANIFEST.in: -------------------------------------------------------------------------------- 1 | 2 | include AUTHORS 3 | include README.md 4 | include LICENSE.txt 5 | include CHANGES.txt -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | PyFilesystem 2 | ============ 3 | 4 | **Note:** The project has largely been replaced by [PyFilesystem2](https://github.com/PyFilesystem/pyfilesystem2) which offers many improvements over the original. 5 | 6 | PyFilesystem is an abstraction layer for *filesystems*. In the same way that Python's file-like objects provide a common way of accessing files, PyFilesystem provides a common way of accessing entire filesystems. You can write platform-independent code to work with local files, that also works with any of the supported filesystems (zip, ftp, S3 etc.). 7 | 8 | Pyfilesystem works with Linux, Windows and Mac. 9 | 10 | Supported Filesystems 11 | --------------------- 12 | 13 | Here are a few of the filesystems that can be accessed with Pyfilesystem: 14 | 15 | * **DavFS** access files & directories on a WebDAV server 16 | * **FTPFS** access files & directories on an FTP server 17 | * **MemoryFS** access files & directories stored in memory (non-permanent but very fast) 18 | * **MountFS** creates a virtual directory structure built from other filesystems 19 | * **MultiFS** a virtual filesystem that combines a list of filesystems into one, and checks them in order when opening files 20 | * **OSFS** the native filesystem 21 | * **SFTPFS** access files & directories stored on a Secure FTP server 22 | * **S3FS** access files & directories stored on Amazon S3 storage 23 | * **TahoeLAFS** access files & directories stored on a Tahoe distributed filesystem 24 | * **ZipFS** access files and directories contained in a zip file 25 | 26 | Example 27 | ------- 28 | 29 | The following snippet prints the total number of bytes contained in all your Python files in `C:/projects` (including sub-directories):: 30 | 31 | from fs.osfs import OSFS 32 | projects_fs = OSFS('C:/projects') 33 | print sum(projects_fs.getsize(path) 34 | for path in projects_fs.walkfiles(wildcard="*.py")) 35 | 36 | That is, assuming you are on Windows and have a directory called 'projects' in your C drive. If you are on Linux / Mac, you might replace the second line with something like:: 37 | 38 | projects_fs = OSFS('~/projects') 39 | 40 | If you later want to display the total size of Python files stored in a zip file, you could make the following change to the first two lines:: 41 | 42 | from fs.zipfs import ZipFS 43 | projects_fs = ZipFS('source.zip') 44 | 45 | In fact, you could use any of the supported filesystems above, and the code would continue to work as before. 46 | 47 | An alternative to explicitly importing the filesystem class you want, is to use an FS opener which opens a filesystem from a URL-like syntax:: 48 | 49 | from fs.opener import fsopendir 50 | projects_fs = fsopendir('C:/projects') 51 | 52 | You could change ``C:/projects`` to ``zip://source.zip`` to open the zip file, or even ``ftp://ftp.example.org/code/projects/`` to sum up the bytes of Python stored on an ftp server. 53 | 54 | Documentation 55 | ------------- 56 | 57 | http://pyfilesystem.readthedocs.org 58 | 59 | Screencast 60 | ---------- 61 | 62 | This is from an early version of PyFilesystem, but still relevant 63 | 64 | http://vimeo.com/12680842 65 | 66 | Discussion Group 67 | ---------------- 68 | 69 | http://groups.google.com/group/pyfilesystem-discussion 70 | 71 | Further Information 72 | ------------------- 73 | 74 | http://www.willmcgugan.com/tag/fs/ 75 | -------------------------------------------------------------------------------- /docs/Makefile: -------------------------------------------------------------------------------- 1 | # Makefile for Sphinx documentation 2 | # 3 | 4 | # You can set these variables from the command line. 5 | SPHINXOPTS = 6 | SPHINXBUILD = sphinx-build 7 | PAPER = 8 | BUILDDIR = _build 9 | 10 | # Internal variables. 11 | PAPEROPT_a4 = -D latex_paper_size=a4 12 | PAPEROPT_letter = -D latex_paper_size=letter 13 | ALLSPHINXOPTS = -d $(BUILDDIR)/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) . 14 | 15 | .PHONY: help clean html dirhtml pickle json htmlhelp qthelp latex changes linkcheck doctest 16 | 17 | help: 18 | @echo "Please use \`make ' where is one of" 19 | @echo " html to make standalone HTML files" 20 | @echo " dirhtml to make HTML files named index.html in directories" 21 | @echo " pickle to make pickle files" 22 | @echo " json to make JSON files" 23 | @echo " htmlhelp to make HTML files and a HTML help project" 24 | @echo " qthelp to make HTML files and a qthelp project" 25 | @echo " latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter" 26 | @echo " changes to make an overview of all changed/added/deprecated items" 27 | @echo " linkcheck to check all external links for integrity" 28 | @echo " doctest to run all doctests embedded in the documentation (if enabled)" 29 | 30 | clean: 31 | -rm -rf $(BUILDDIR)/* 32 | 33 | html: 34 | $(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html 35 | @echo 36 | @echo "Build finished. The HTML pages are in $(BUILDDIR)/html." 37 | 38 | dirhtml: 39 | $(SPHINXBUILD) -b dirhtml $(ALLSPHINXOPTS) $(BUILDDIR)/dirhtml 40 | @echo 41 | @echo "Build finished. The HTML pages are in $(BUILDDIR)/dirhtml." 42 | 43 | pickle: 44 | $(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) $(BUILDDIR)/pickle 45 | @echo 46 | @echo "Build finished; now you can process the pickle files." 47 | 48 | json: 49 | $(SPHINXBUILD) -b json $(ALLSPHINXOPTS) $(BUILDDIR)/json 50 | @echo 51 | @echo "Build finished; now you can process the JSON files." 52 | 53 | htmlhelp: 54 | $(SPHINXBUILD) -b htmlhelp $(ALLSPHINXOPTS) $(BUILDDIR)/htmlhelp 55 | @echo 56 | @echo "Build finished; now you can run HTML Help Workshop with the" \ 57 | ".hhp project file in $(BUILDDIR)/htmlhelp." 58 | 59 | qthelp: 60 | $(SPHINXBUILD) -b qthelp $(ALLSPHINXOPTS) $(BUILDDIR)/qthelp 61 | @echo 62 | @echo "Build finished; now you can run "qcollectiongenerator" with the" \ 63 | ".qhcp project file in $(BUILDDIR)/qthelp, like this:" 64 | @echo "# qcollectiongenerator $(BUILDDIR)/qthelp/PyFilesystem.qhcp" 65 | @echo "To view the help file:" 66 | @echo "# assistant -collectionFile $(BUILDDIR)/qthelp/PyFilesystem.qhc" 67 | 68 | latex: 69 | $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex 70 | @echo 71 | @echo "Build finished; the LaTeX files are in $(BUILDDIR)/latex." 72 | @echo "Run \`make all-pdf' or \`make all-ps' in that directory to" \ 73 | "run these through (pdf)latex." 74 | 75 | changes: 76 | $(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) $(BUILDDIR)/changes 77 | @echo 78 | @echo "The overview file is in $(BUILDDIR)/changes." 79 | 80 | linkcheck: 81 | $(SPHINXBUILD) -b linkcheck $(ALLSPHINXOPTS) $(BUILDDIR)/linkcheck 82 | @echo 83 | @echo "Link check complete; look for any errors in the above output " \ 84 | "or in $(BUILDDIR)/linkcheck/output.txt." 85 | 86 | doctest: 87 | $(SPHINXBUILD) -b doctest $(ALLSPHINXOPTS) $(BUILDDIR)/doctest 88 | @echo "Testing of doctests in the sources finished, look at the " \ 89 | "results in $(BUILDDIR)/doctest/output.txt." 90 | 91 | spelling: 92 | $(SPHINXBUILD) -b spelling $(ALLSPHINXOPTS) $(BUILDDIR)/doctest 93 | @echo "Spell checking of docs is finished.the" 94 | 95 | -------------------------------------------------------------------------------- /docs/appdirfs.rst: -------------------------------------------------------------------------------- 1 | .. automodule:: fs.appdirfs 2 | :members: -------------------------------------------------------------------------------- /docs/base.rst: -------------------------------------------------------------------------------- 1 | fs.base 2 | ======= 3 | 4 | This module contains the basic FS interface and a number of other essential interfaces. 5 | 6 | fs.base.FS 7 | ---------- 8 | 9 | All Filesystem objects inherit from this class. 10 | 11 | .. autoclass:: fs.base.FS 12 | :members: 13 | 14 | fs.base.SubFS 15 | ------------- 16 | 17 | A SubFS is an FS implementation that represents a directory on another Filesystem. When you use the :meth:`~fs.base.FS.opendir` method it will return a SubFS instance. You should not need to instantiate a SubFS directly. 18 | 19 | For example:: 20 | 21 | from fs.osfs import OSFS 22 | home_fs = OSFS('foo') 23 | bar_fs = home_fs.opendir('bar') 24 | 25 | 26 | fs.base.NullFile 27 | ---------------- 28 | 29 | A NullFile is a file-like object with no functionality. It is used in situations where a file-like object is required but the caller doesn't have any data to read or write. 30 | 31 | The :meth:`~fs.base.FS.safeopen` method returns an NullFile instance, which can reduce error-handling code. 32 | 33 | For example, the following code may be written to append some text to a log file:: 34 | 35 | logfile = None 36 | try: 37 | logfile = myfs.open('log.txt', 'a') 38 | logfile.writeline('operation successful!') 39 | finally: 40 | if logfile is not None: 41 | logfile.close() 42 | 43 | This could be re-written using the `safeopen` method:: 44 | 45 | myfs.safeopen('log.txt', 'a').writeline('operation successful!') 46 | 47 | If the file doesn't exist then the call to writeline will be a null-operation (i.e. not do anything). 48 | 49 | -------------------------------------------------------------------------------- /docs/browsewin.rst: -------------------------------------------------------------------------------- 1 | fs.browsewin 2 | ============ 3 | 4 | .. automodule:: fs.browsewin 5 | :members: -------------------------------------------------------------------------------- /docs/commands.rst: -------------------------------------------------------------------------------- 1 | .. _commands: 2 | 3 | Command Line Applications 4 | ========================= 5 | 6 | PyFilesystem adds a number of applications that expose some of the PyFilesystem functionality to the command line. 7 | These commands use the opener syntax, as described in :doc:`opening`, to refer to filesystems. 8 | 9 | Most of these applications shadow existing shell commands and work in similar ways. 10 | 11 | All of the command line application support the `-h` (or `--help`) switch which will display a full list of options. 12 | 13 | 14 | Custom Filesystem Openers 15 | ------------------------- 16 | 17 | When opening filesystems, the command line applications will use the default openers. 18 | You can also 'point' the command line applications at an opener to add it to a list of available openers. 19 | For example, the following uses a custom opener to list the contents of a directory served with the 'myfs' protocol:: 20 | 21 | fsls --fs mypackage.mymodule.myfs.MyFSOpener myfs://127.0.0.1 22 | 23 | 24 | Listing Supported Filesystems 25 | ----------------------------- 26 | 27 | All of the command line applications support the ``--listopeners`` switch, which lists all available installed openers:: 28 | 29 | fsls --listopeners 30 | 31 | 32 | fsls 33 | ---- 34 | 35 | Lists the contents of a directory, similar to the ``ls`` command, e.g.:: 36 | 37 | fsls 38 | fsls ../ 39 | fsls ftp://example.org/pub 40 | fsls zip://photos.zip 41 | 42 | fstree 43 | ------ 44 | 45 | Displays an ASCII tree of a directory. e.g.:: 46 | 47 | fstree 48 | fstree -g 49 | fstree rpc://192.168.1.64/foo/bar -l3 50 | fstree zip://photos.zip 51 | 52 | fscat 53 | ----- 54 | 55 | Writes a file to stdout, e.g.:: 56 | 57 | fscat ~/.bashrc 58 | fscat http://www.willmcgugan.com 59 | fscat ftp://ftp.mozilla.org/pub/README 60 | 61 | fsinfo 62 | ------ 63 | 64 | Displays information regarding a file / directory, e.g.:: 65 | 66 | fsinfo C:\autoexec.bat 67 | fsinfo ftp://ftp.mozilla.org/pub/README 68 | 69 | fsmv 70 | ---- 71 | 72 | Moves a file from one location to another, e.g.:: 73 | 74 | fsmv foo bar 75 | fsmv *.jpg zip://photos.zip 76 | 77 | fsmkdir 78 | ------- 79 | 80 | Makes a directory on a filesystem, e.g.:: 81 | 82 | fsmkdir foo 83 | fsmkdir ftp://ftp.mozilla.org/foo 84 | fsmkdir rpc://127.0.0.1/foo 85 | 86 | fscp 87 | ---- 88 | 89 | Copies a file from one location to another, e.g.:: 90 | 91 | fscp foo bar 92 | fscp ftp://ftp.mozilla.org/pub/README readme.txt 93 | 94 | fsrm 95 | ---- 96 | 97 | Removes (deletes) a file from a filesystem, e.g.:: 98 | 99 | fsrm foo 100 | fsrm -r mydir 101 | 102 | fsserve 103 | ------- 104 | 105 | Serves the contents of a filesystem over a network with one of a number of methods; HTTP, RPC or SFTP, e.g.:: 106 | 107 | fsserve 108 | fsserve --type rpc 109 | fsserve --type http zip://photos.zip 110 | 111 | fsmount 112 | ------- 113 | 114 | Mounts a filesystem with FUSE (on Linux) and Dokan (on Windows), e.g.:: 115 | 116 | fsmount mem:// ram 117 | fsserve mem:// M 118 | fsserve ftp://ftp.mozilla.org/pub ftpgateway 119 | -------------------------------------------------------------------------------- /docs/concepts.rst: -------------------------------------------------------------------------------- 1 | Concepts 2 | ======== 3 | 4 | Working with PyFilesystem is generally easier than working with lower level interfaces, as long as you are aware these simple concepts. 5 | 6 | Sandboxing 7 | ---------- 8 | 9 | FS objects are not permitted to work with any files / directories outside of the Filesystem they represent. If you attempt to open a file or directory outside the root of the FS (e.g. by using "../" in the path) you will get a ``ValueError``. 10 | 11 | There is no concept of a current working directory in PyFilesystem, since it is a common source of bugs and not all filesystems even have such a notion. If you want to work with a sub-directory of a FS object, you can use the :meth:`~fs.base.FS.opendir` method which returns another FS object representing the sub-directory. 12 | 13 | For example, consider the following directory structure. The directory `foo` contains two sub-directories; `bar` and `baz`:: 14 | 15 | --foo 16 | |--bar 17 | | |--readme.txt 18 | | `--photo.jpg 19 | `--baz 20 | |--private.txt 21 | `--dontopen.jpg 22 | 23 | We can open the `foo` directory with the following code:: 24 | 25 | from fs.osfs import OSFS 26 | foo_fs = OSFS('foo') 27 | 28 | The `foo_fs` object can work with any of the contents of `bar` and `baz`, which may not be desirable, 29 | especially if we are passing `foo_fs` to an untrusted function or to a function that has the potential to delete files. 30 | Fortunately we can isolate a single sub-directory with then :meth:`~fs.base.FS.opendir` method:: 31 | 32 | bar_fs = foo_fs.opendir('bar') 33 | 34 | This creates a completely new FS object that represents everything in the `foo/bar` directory. The root directory of `bar_fs` has been re-positioned, so that from `bar_fs`'s point of view, the readme.txt and photo.jpg files are in the root:: 35 | 36 | --bar 37 | |--readme.txt 38 | `--photo.jpg 39 | 40 | PyFilesystem will catch any attempts to read outside of the root directory. For example, the following will not work:: 41 | 42 | bar_fs.open('../private.txt') # throws a ValueError 43 | 44 | 45 | Paths 46 | ----- 47 | 48 | Paths used within an FS object use the same common format, regardless of the underlying file system it represents (or the platform it resides on). 49 | 50 | When working with paths in FS objects, keep in mind the following: 51 | 52 | * Path components are separated by a forward slash (/) 53 | * Paths beginning with a forward slash are absolute (start at the root of the FS) 54 | * Paths not beginning with a forward slash are relative 55 | * A single dot means 'current directory' 56 | * A double dot means 'previous directory' 57 | 58 | Note that paths used by the FS interface will use this format, but the constructor or additional methods may not. 59 | Notably the :mod:`~fs.osfs.OSFS` constructor which requires an OS path -- the format of which is platform-dependent. 60 | 61 | There are many helpful functions for working with paths in the :mod:`~fs.path` module. 62 | 63 | System Paths 64 | ++++++++++++ 65 | 66 | Not all Python modules can use file-like objects, especially those which interface with C libraries. For these situations you will need to retrieve the `system path` from an FS object you are working with. You can do this with the :meth:`~fs.base.FS.getsyspath` method which converts a valid path in the context of the FS object to an absolute path on the system, should one exist. 67 | 68 | For example:: 69 | 70 | >>> from fs.osfs import OSFS 71 | >>> home_fs = OSFS('~/') 72 | >>> home_fs.getsyspath('test.txt') 73 | u'/home/will/test.txt' 74 | 75 | Not all FS implementation will map to a valid system path (e.g. the FTP FS object). 76 | If you call :meth:`~fs.base.FS.getsyspath` on such FS objects you will either get a :class:`~fs.errors.NoSysPathError` exception or a return value of ``None``, if you call ``getsyspath`` with ``allow_none=True``. 77 | 78 | Errors 79 | ------ 80 | 81 | PyFilesystem converts all exceptions to a common type, so that you need only write your exception handling code once. For example, if you try to open a file that doesn't exist, PyFilesystem will throw a :class:`~fs.errors.ResourceNotFoundError` regardless of whether the filesystem is local, on a ftp server or in a zip file:: 82 | 83 | >>> from fs.osfs import OSFS 84 | >>> root_fs = OSFS('/') 85 | >>> root_fs.open('doesnotexist.txt') 86 | Traceback (most recent call last): 87 | File "", line 1, in 88 | File "/usr/local/lib/python2.6/dist-packages/fs/errors.py", line 181, in wrapper 89 | return func(self,*args,**kwds) 90 | File "/usr/local/lib/python2.6/dist-packages/fs/osfs/__init__.py", line 107, in open 91 | return open(self.getsyspath(path), mode, kwargs.get("buffering", -1)) 92 | fs.errors.ResourceNotFoundError: Resource not found: doesnotexist.txt 93 | 94 | All PyFilesystem exceptions are derived from :class:`~fs.errors.FSError`, so you may use that if you want to catch all possible filesystem related exceptions. 95 | -------------------------------------------------------------------------------- /docs/contrib.rst: -------------------------------------------------------------------------------- 1 | 3rd-Party Filesystems 2 | ===================== 3 | 4 | This page lists filesystem implementations that have been contributed by 5 | third parties. They may be less well tested than those found in the main 6 | module namespace. 7 | 8 | DAV (WebDAV Protocol) 9 | ---------------------------- 10 | An interface to WebDAV file servers. See :mod:`~fs.contrib.davfs` 11 | 12 | 13 | Tahoe LAFS 14 | ---------- 15 | An interface to Tahoe Least-Authority File System. See :mod:`~fs.contrib.tahoelafs` 16 | 17 | 18 | BIG (BIG Archive File Format) 19 | ----------------------------- 20 | A read-only interface to the BIG archive file format used in some EA games titles (e.g. Command & Conquer 4). See :mod:`~fs.contrib.bigfs` 21 | 22 | 23 | -------------------------------------------------------------------------------- /docs/contrib/bigfs.rst: -------------------------------------------------------------------------------- 1 | .. automodule:: fs.contrib.bigfs 2 | :members: 3 | -------------------------------------------------------------------------------- /docs/contrib/davfs.rst: -------------------------------------------------------------------------------- 1 | .. automodule:: fs.contrib.davfs 2 | :members: 3 | -------------------------------------------------------------------------------- /docs/contrib/index.rst: -------------------------------------------------------------------------------- 1 | fs.contrib 2 | ========== 3 | 4 | The ``fs.contrib`` module contains a number of filesystem implementations provided by third parties. 5 | 6 | .. toctree:: 7 | :maxdepth: 3 8 | 9 | davfs.rst 10 | tahoelafs.rst 11 | bigfs.rst 12 | 13 | -------------------------------------------------------------------------------- /docs/contrib/tahoelafs.rst: -------------------------------------------------------------------------------- 1 | .. automodule:: fs.contrib.tahoelafs 2 | :members: 3 | -------------------------------------------------------------------------------- /docs/errors.rst: -------------------------------------------------------------------------------- 1 | fs.errors 2 | ========= 3 | 4 | .. automodule:: fs.errors 5 | :members: -------------------------------------------------------------------------------- /docs/expose.rst: -------------------------------------------------------------------------------- 1 | Exposing FS objects 2 | =================== 3 | 4 | The ``fs.expose`` module offers a number of ways of making an FS implementation available over an Internet connection, or to other processes on the system. 5 | 6 | 7 | FUSE 8 | ---- 9 | Makes an FS object available to other applications on the system. See :mod:`~fs.expose.fuse`. 10 | 11 | Dokan 12 | ----- 13 | Makes an FS object available to other applications on the system. See :mod:`~fs.expose.dokan`. 14 | 15 | Secure FTP 16 | ---------- 17 | Makes an FS object available via Secure FTP. See :mod:`~fs.expose.sftp`. 18 | 19 | XMLRPC 20 | ------ 21 | Makes an FS object available over XMLRPC. See :mod:`~fs.expose.xmlrpc` 22 | 23 | Import Hook 24 | ----------- 25 | Allows importing python modules from the files in an FS object. See :mod:`~fs.expose.importhook` 26 | 27 | Django Storage 28 | -------------- 29 | Connects FS objects to Django. See :mod:`~fs.expose.django_storage` 30 | -------------------------------------------------------------------------------- /docs/expose/django_storage.rst: -------------------------------------------------------------------------------- 1 | .. automodule:: fs.expose.django_storage 2 | :members: -------------------------------------------------------------------------------- /docs/expose/dokan.rst: -------------------------------------------------------------------------------- 1 | .. automodule:: fs.expose.dokan 2 | :members: 3 | -------------------------------------------------------------------------------- /docs/expose/fuse.rst: -------------------------------------------------------------------------------- 1 | .. automodule:: fs.expose.fuse 2 | :members: -------------------------------------------------------------------------------- /docs/expose/importhook.rst: -------------------------------------------------------------------------------- 1 | .. automodule:: fs.expose.importhook 2 | :members: 3 | -------------------------------------------------------------------------------- /docs/expose/index.rst: -------------------------------------------------------------------------------- 1 | fs.expose 2 | ========= 3 | 4 | The ``fs.expose`` module contains a number of options for making an FS implementation available over the Internet, or to other applications. 5 | 6 | .. toctree:: 7 | :maxdepth: 3 8 | 9 | fuse.rst 10 | dokan.rst 11 | sftp.rst 12 | xmlrpc.rst 13 | importhook.rst 14 | django_storage.rst 15 | -------------------------------------------------------------------------------- /docs/expose/sftp.rst: -------------------------------------------------------------------------------- 1 | .. automodule:: fs.expose.sftp 2 | :members: -------------------------------------------------------------------------------- /docs/expose/xmlrpc.rst: -------------------------------------------------------------------------------- 1 | .. automodule:: fs.expose.xmlrpc 2 | :members: -------------------------------------------------------------------------------- /docs/filelike.rst: -------------------------------------------------------------------------------- 1 | .. automodule:: fs.filelike 2 | :members: 3 | -------------------------------------------------------------------------------- /docs/filesystems.rst: -------------------------------------------------------------------------------- 1 | Filesystems 2 | =========== 3 | 4 | This page lists the builtin filesystems. 5 | 6 | 7 | FTP (File Transfer Protocol) 8 | ---------------------------- 9 | An interface to FTP servers. See :class:`~fs.ftpfs.FTPFS` 10 | 11 | Memory 12 | ------ 13 | A filesystem that exists entirely in memory. See :mod:`~fs.memoryfs` 14 | 15 | 16 | Mount 17 | ----- 18 | A filesystem that can map directories in to other filesystems (like a symlink). See :mod:`~fs.mountfs` 19 | 20 | 21 | Multi 22 | ----- 23 | A filesystem that overlays other filesystems. See :mod:`~fs.multifs` 24 | 25 | 26 | OS 27 | -- 28 | An interface to the OS Filesystem. See :mod:`~fs.osfs` 29 | 30 | 31 | RPCFS (Remote Procedure Call) 32 | ----------------------------- 33 | An interface to a file-system served over XML RPC, See :mod:`~fs.rpcfs` and :mod:`~fs.expose.xmlrpc` 34 | 35 | 36 | SFTP (Secure FTP) 37 | ----------------------- 38 | A secure FTP filesystem. See :mod:`~fs.sftpfs` 39 | 40 | 41 | S3 42 | -- 43 | A filesystem to access an Amazon S3 service. See :mod:`~fs.s3fs` 44 | 45 | 46 | Temporary 47 | --------- 48 | Creates a temporary filesystem in an OS provided location. See :mod:`~fs.tempfs` 49 | 50 | 51 | Wrap 52 | ---- 53 | A collection of wrappers that add new behavior / features to existing FS instances. See :mod:`~fs.wrapfs` 54 | 55 | 56 | Zip 57 | --- 58 | An interface to zip files. See :mod:`~fs.zipfs` 59 | 60 | 61 | -------------------------------------------------------------------------------- /docs/ftpfs.rst: -------------------------------------------------------------------------------- 1 | fs.ftpfs 2 | ======== 3 | 4 | .. autoclass:: fs.ftpfs.FTPFS 5 | :members: -------------------------------------------------------------------------------- /docs/getting_started.rst: -------------------------------------------------------------------------------- 1 | Getting Started 2 | =============== 3 | 4 | PyFilesystem is a Python-only module and can be installed from source or with `pip `_. PyFilesystem works on Linux, Mac and OSX. 5 | 6 | Installing 7 | ---------- 8 | 9 | To install with pip, use the following:: 10 | 11 | pip install fs 12 | 13 | Or to upgrade to the most recent version:: 14 | 15 | pip install fs --upgrade 16 | 17 | You can also install the cutting edge release by cloning the source from GIT:: 18 | 19 | git clone https://github.com/PyFilesystem/pyfilesystem.git 20 | cd pyfilesystem 21 | python setup.py install 22 | 23 | Whichever method you use, you should now have the `fs` module on your path (version number may vary):: 24 | 25 | >>> import fs 26 | >>> fs.__version__ 27 | '0.5.0' 28 | 29 | You should also have the command line applications installed. If you enter the following in the command line, you should see a tree display of the current working directory:: 30 | 31 | fstree -l 2 32 | 33 | Because the command line utilities use PyFilesystem, they also work with any of the supported filesystems. For example:: 34 | 35 | fstree ftp://ftp.mozilla.org -l 2 36 | 37 | See :doc:`commands` for more information on the command line applications. 38 | 39 | Prerequisites 40 | ------------- 41 | 42 | PyFilesystem requires at least **Python 2.6**. There are a few other dependencies if you want to use some of the more advanced filesystem interfaces, but for basic use all that is needed is the Python standard library. 43 | 44 | * Boto (required for :mod:`~fs.s3fs`) https://github.com/boto/boto 45 | * Paramiko (required for :mod:`~fs.sftpfs`) https://github.com/paramiko/paramiko 46 | * wxPython (required for :mod:`~fs.browsewin`) http://www.wxpython.org/ 47 | 48 | 49 | Quick Examples 50 | -------------- 51 | 52 | Before you dive in to the API documentation, here are a few interesting things you can do with PyFilesystem. 53 | 54 | The following will list all the files in your home directory:: 55 | 56 | >>> from fs.osfs import OSFS 57 | >>> home_fs = OSFS('~/') # 'c:\Users\' on Windows 58 | >>> home_fs.listdir() 59 | 60 | Here's how to browse your home folder with a graphical interface:: 61 | 62 | >>> home_fs.browse() 63 | 64 | This will display the total number of bytes store in '.py' files your home directory:: 65 | 66 | >>> sum(home_fs.getsize(f) for f in home_fs.walkfiles(wildcard='*.py')) 67 | -------------------------------------------------------------------------------- /docs/httpfs.rst: -------------------------------------------------------------------------------- 1 | .. automodule:: fs.httpfs 2 | :members: -------------------------------------------------------------------------------- /docs/index.rst: -------------------------------------------------------------------------------- 1 | .. PyFilesystem documentation master file, created by 2 | sphinx-quickstart on Tue Nov 24 14:59:15 2009. 3 | You can adapt this file completely to your liking, but it should at least 4 | contain the root `toctree` directive. 5 | 6 | .. _PyFilesystem2: https://docs.pyfilesystem.org/ 7 | 8 | Welcome to PyFilesystem's documentation! 9 | ======================================== 10 | 11 | PyFilesystem provides a simplified common interface to a variety of different filesystems, such as the local filesystem, zip files, ftp servers etc. 12 | 13 | **Note:** The project has largely been replaced by PyFilesystem2_ which offers many improvements over the original. 14 | 15 | Guide 16 | ----- 17 | 18 | .. toctree:: 19 | :maxdepth: 2 20 | 21 | introduction.rst 22 | getting_started.rst 23 | concepts.rst 24 | opening.rst 25 | interface.rst 26 | filesystems.rst 27 | contrib.rst 28 | expose.rst 29 | utilities.rst 30 | commands.rst 31 | implementersguide.rst 32 | releasenotes.rst 33 | 34 | Code Documentation 35 | ------------------ 36 | 37 | .. toctree:: 38 | :maxdepth: 3 39 | 40 | appdirfs.rst 41 | base.rst 42 | browsewin.rst 43 | contrib/index.rst 44 | errors.rst 45 | expose/index.rst 46 | filelike.rst 47 | ftpfs.rst 48 | httpfs.rst 49 | memoryfs.rst 50 | mountfs.rst 51 | multifs.rst 52 | osfs.rst 53 | opener.rst 54 | path.rst 55 | remote.rst 56 | rpcfs.rst 57 | s3fs.rst 58 | sftpfs.rst 59 | tempfs.rst 60 | utils.rst 61 | watch.rst 62 | wrapfs/index.rst 63 | zipfs.rst 64 | 65 | 66 | Indices and tables 67 | ================== 68 | 69 | * :ref:`genindex` 70 | * :ref:`modindex` 71 | * :ref:`search` 72 | 73 | 74 | 75 | 76 | -------------------------------------------------------------------------------- /docs/interface.rst: -------------------------------------------------------------------------------- 1 | .. _filesystem-interface: 2 | 3 | Filesystem Interface 4 | ==================== 5 | 6 | The following methods are available in all PyFilesystem implementation: 7 | 8 | * :meth:`~fs.base.FS.close` Close the filesystem and free any resources 9 | * :meth:`~fs.base.FS.copy` Copy a file to a new location 10 | * :meth:`~fs.base.FS.copydir` Recursively copy a directory to a new location 11 | * :meth:`~fs.base.FS.cachehint` Permit implementation to use aggressive caching for performance reasons 12 | * :meth:`~fs.base.FS.createfile` Create a file with data 13 | * :meth:`~fs.base.FS.desc` Return a short descriptive text regarding a path 14 | * :meth:`~fs.base.FS.exists` Check whether a path exists as file or directory 15 | * :meth:`~fs.base.FS.getcontents` Returns the contents of a file as a string 16 | * :meth:`~fs.base.FS.getinfo` Return information about the path e.g. size, mtime 17 | * :meth:`~fs.base.FS.getmeta` Get the value of a filesystem meta value, if it exists 18 | * :meth:`~fs.base.FS.getmmap` Gets an mmap object for the given resource, if supported 19 | * :meth:`~fs.base.FS.getpathurl` Get an external URL at which the given file can be accessed, if possible 20 | * :meth:`~fs.base.FS.getsize` Returns the number of bytes used for a given file or directory 21 | * :meth:`~fs.base.FS.getsyspath` Get a file's name in the local filesystem, if possible 22 | * :meth:`~fs.base.FS.hasmeta` Check if a filesystem meta value exists 23 | * :meth:`~fs.base.FS.haspathurl` Check if a path maps to an external URL 24 | * :meth:`~fs.base.FS.hassyspath` Check if a path maps to a system path (recognized by the OS) 25 | * :meth:`~fs.base.FS.ilistdir` Generator version of the :meth:`~fs.base.FS.listdir` method 26 | * :meth:`~fs.base.FS.ilistdirinfo` Generator version of the :meth:`~fs.base.FS.listdirinfo` method 27 | * :meth:`~fs.base.FS.isdir` Check whether a path exists and is a directory 28 | * :meth:`~fs.base.FS.isdirempty` Checks if a directory contains no files 29 | * :meth:`~fs.base.FS.isfile` Check whether the path exists and is a file 30 | * :meth:`~fs.base.FS.listdir` List the contents of a directory 31 | * :meth:`~fs.base.FS.listdirinfo` Get a directory listing along with the info dict for each entry 32 | * :meth:`~fs.base.FS.makedir` Create a new directory 33 | * :meth:`~fs.base.FS.makeopendir` Make a directory and returns the FS object that represents it 34 | * :meth:`~fs.base.FS.move` Move a file to a new location 35 | * :meth:`~fs.base.FS.movedir` Recursively move a directory to a new location 36 | * :meth:`~fs.base.FS.open` Opens a file for read/writing 37 | * :meth:`~fs.base.FS.opendir` Opens a directory and returns a FS object that represents it 38 | * :meth:`~fs.base.FS.remove` Remove an existing file 39 | * :meth:`~fs.base.FS.removedir` Remove an existing directory 40 | * :meth:`~fs.base.FS.rename` Atomically rename a file or directory 41 | * :meth:`~fs.base.FS.safeopen` Like :meth:`~fs.base.FS.open` but returns a :class:`~fs.base.NullFile` if the file could not be opened 42 | * :meth:`~fs.base.FS.setcontents` Sets the contents of a file as a string or file-like object 43 | * :meth:`~fs.base.FS.setcontents_async` Sets the contents of a file asynchronously 44 | * :meth:`~fs.base.FS.settimes` Sets the accessed and modified times of a path 45 | * :meth:`~fs.base.FS.tree` Display an ascii rendering of the directory structure 46 | * :meth:`~fs.base.FS.walk` Like :meth:`~fs.base.FS.listdir` but descends in to sub-directories 47 | * :meth:`~fs.base.FS.walkdirs` Returns an iterable of paths to sub-directories 48 | * :meth:`~fs.base.FS.walkfiles` Returns an iterable of file paths in a directory, and its sub-directories 49 | 50 | See :py:class:`~fs.base.FS` for the method signature and full details. 51 | 52 | If you intend to implement an FS object, see :ref:`implementers`. 53 | -------------------------------------------------------------------------------- /docs/introduction.rst: -------------------------------------------------------------------------------- 1 | Introduction 2 | ============ 3 | 4 | PyFilesystem is a Python module that provides a common interface to any filesystem. 5 | 6 | Think of PyFilesystem ``FS`` objects as the next logical step to Python's ``file`` class. Just as *file-like* objects abstract a single file, FS objects abstract the whole filesystem by providing a common interface to operations such as reading directories, getting file information, opening/copying/deleting files etc. 7 | 8 | Even if you only want to work with the local filesystem, PyFilesystem simplifies a number of common operations and reduces the chance of error. 9 | 10 | About PyFilesystem 11 | ------------------ 12 | 13 | PyFilesystem was initially created by Will McGugan and is now a joint effort from the following contributors: 14 | 15 | 16 | - Will McGugan (http://www.willmcgugan.com) 17 | - Ryan Kelly (http://www.rfk.id.au) 18 | - Andrew Scheller (http://www.andrewscheller.co.uk/) 19 | - Ben Timby (http://ben.timby.com/) 20 | 21 | And many others who have contributed bug reports and patches. 22 | 23 | 24 | Need Help? 25 | ---------- 26 | 27 | If you have any problems or questions, please contact the developers through one of the following channels: 28 | 29 | Bugs 30 | #### 31 | 32 | If you find a bug in PyFilesystem, please file an issue: https://github.com/PyFilesystem/pyfilesystem/issues 33 | 34 | Discussion Group 35 | ################ 36 | 37 | There is also a discussion group for PyFilesystem: http://groups.google.com/group/pyfilesystem-discussion 38 | 39 | -------------------------------------------------------------------------------- /docs/make.bat: -------------------------------------------------------------------------------- 1 | @ECHO OFF 2 | 3 | REM Command file for Sphinx documentation 4 | 5 | set SPHINXBUILD=sphinx-build 6 | set BUILDDIR=_build 7 | set ALLSPHINXOPTS=-d %BUILDDIR%/doctrees %SPHINXOPTS% . 8 | if NOT "%PAPER%" == "" ( 9 | set ALLSPHINXOPTS=-D latex_paper_size=%PAPER% %ALLSPHINXOPTS% 10 | ) 11 | 12 | if "%1" == "" goto help 13 | 14 | if "%1" == "help" ( 15 | :help 16 | echo.Please use `make ^` where ^ is one of 17 | echo. html to make standalone HTML files 18 | echo. dirhtml to make HTML files named index.html in directories 19 | echo. pickle to make pickle files 20 | echo. json to make JSON files 21 | echo. htmlhelp to make HTML files and a HTML help project 22 | echo. qthelp to make HTML files and a qthelp project 23 | echo. latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter 24 | echo. changes to make an overview over all changed/added/deprecated items 25 | echo. linkcheck to check all external links for integrity 26 | echo. doctest to run all doctests embedded in the documentation if enabled 27 | goto end 28 | ) 29 | 30 | if "%1" == "clean" ( 31 | for /d %%i in (%BUILDDIR%\*) do rmdir /q /s %%i 32 | del /q /s %BUILDDIR%\* 33 | goto end 34 | ) 35 | 36 | if "%1" == "html" ( 37 | %SPHINXBUILD% -b html %ALLSPHINXOPTS% %BUILDDIR%/html 38 | echo. 39 | echo.Build finished. The HTML pages are in %BUILDDIR%/html. 40 | goto end 41 | ) 42 | 43 | if "%1" == "dirhtml" ( 44 | %SPHINXBUILD% -b dirhtml %ALLSPHINXOPTS% %BUILDDIR%/dirhtml 45 | echo. 46 | echo.Build finished. The HTML pages are in %BUILDDIR%/dirhtml. 47 | goto end 48 | ) 49 | 50 | if "%1" == "pickle" ( 51 | %SPHINXBUILD% -b pickle %ALLSPHINXOPTS% %BUILDDIR%/pickle 52 | echo. 53 | echo.Build finished; now you can process the pickle files. 54 | goto end 55 | ) 56 | 57 | if "%1" == "json" ( 58 | %SPHINXBUILD% -b json %ALLSPHINXOPTS% %BUILDDIR%/json 59 | echo. 60 | echo.Build finished; now you can process the JSON files. 61 | goto end 62 | ) 63 | 64 | if "%1" == "htmlhelp" ( 65 | %SPHINXBUILD% -b htmlhelp %ALLSPHINXOPTS% %BUILDDIR%/htmlhelp 66 | echo. 67 | echo.Build finished; now you can run HTML Help Workshop with the ^ 68 | .hhp project file in %BUILDDIR%/htmlhelp. 69 | goto end 70 | ) 71 | 72 | if "%1" == "qthelp" ( 73 | %SPHINXBUILD% -b qthelp %ALLSPHINXOPTS% %BUILDDIR%/qthelp 74 | echo. 75 | echo.Build finished; now you can run "qcollectiongenerator" with the ^ 76 | .qhcp project file in %BUILDDIR%/qthelp, like this: 77 | echo.^> qcollectiongenerator %BUILDDIR%\qthelp\PyFilesystem.qhcp 78 | echo.To view the help file: 79 | echo.^> assistant -collectionFile %BUILDDIR%\qthelp\PyFilesystem.ghc 80 | goto end 81 | ) 82 | 83 | if "%1" == "latex" ( 84 | %SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex 85 | echo. 86 | echo.Build finished; the LaTeX files are in %BUILDDIR%/latex. 87 | goto end 88 | ) 89 | 90 | if "%1" == "changes" ( 91 | %SPHINXBUILD% -b changes %ALLSPHINXOPTS% %BUILDDIR%/changes 92 | echo. 93 | echo.The overview file is in %BUILDDIR%/changes. 94 | goto end 95 | ) 96 | 97 | if "%1" == "linkcheck" ( 98 | %SPHINXBUILD% -b linkcheck %ALLSPHINXOPTS% %BUILDDIR%/linkcheck 99 | echo. 100 | echo.Link check complete; look for any errors in the above output ^ 101 | or in %BUILDDIR%/linkcheck/output.txt. 102 | goto end 103 | ) 104 | 105 | if "%1" == "doctest" ( 106 | %SPHINXBUILD% -b doctest %ALLSPHINXOPTS% %BUILDDIR%/doctest 107 | echo. 108 | echo.Testing of doctests in the sources finished, look at the ^ 109 | results in %BUILDDIR%/doctest/output.txt. 110 | goto end 111 | ) 112 | 113 | :end 114 | -------------------------------------------------------------------------------- /docs/memoryfs.rst: -------------------------------------------------------------------------------- 1 | .. automodule:: fs.memoryfs 2 | :members: -------------------------------------------------------------------------------- /docs/mountfs.rst: -------------------------------------------------------------------------------- 1 | .. automodule:: fs.mountfs 2 | 3 | .. automethod:: fs.mountfs.MountFS.mountdir(self, path, fs) 4 | .. automethod:: fs.mountfs.MountFS.mountfile(self, path, open_callable=None, info_callable=None) 5 | .. automethod:: fs.mountfs.MountFS.unmount(path) -------------------------------------------------------------------------------- /docs/multifs.rst: -------------------------------------------------------------------------------- 1 | .. automodule:: fs.multifs 2 | :members: -------------------------------------------------------------------------------- /docs/opener.rst: -------------------------------------------------------------------------------- 1 | .. automodule:: fs.opener 2 | :members: -------------------------------------------------------------------------------- /docs/opening.rst: -------------------------------------------------------------------------------- 1 | Opening Filesystems 2 | =================== 3 | 4 | Generally, when you want to work with the files and directories of any of the supported filesystems, 5 | you create an instance of the appropriate class. For example, the following opens the directory ``/foo/bar``:: 6 | 7 | from fs.osfs import OSFS 8 | my_fs = OSFS('/foo/bar') 9 | 10 | This is fine if you know beforehand where the directory you want to work with is located, and on what medium. 11 | However, there are occasions where the location of the files may change at runtime or should be specified in a config file or from the command line. 12 | 13 | In these situations you can use an *opener*, which is a generic way of specifying a filesystem. For example, the following is equivalent to the code above:: 14 | 15 | from fs.opener import fsopendir 16 | my_fs = fsopendir('/foo/bar') 17 | 18 | The ``fsopendir`` callable takes a string that identifies the filesystem with a URI syntax, but if called with a regular path will return an :class:`~fs.osfs.OSFS` instance. 19 | To open a different kind of filesystem, precede the path with the required protocol. 20 | For example, the following code opens an FTP filesystem rather than a directory on your hard-drive:: 21 | 22 | from fs.opener import fsopendir 23 | my_fs = fsopendir('ftp://example.org/foo/bar') 24 | 25 | For further information regarding filesystem openers see :doc:`opener`. 26 | -------------------------------------------------------------------------------- /docs/osfs.rst: -------------------------------------------------------------------------------- 1 | .. automodule:: fs.osfs 2 | :members: -------------------------------------------------------------------------------- /docs/path.rst: -------------------------------------------------------------------------------- 1 | .. automodule:: fs.path 2 | :members: -------------------------------------------------------------------------------- /docs/releasenotes.rst: -------------------------------------------------------------------------------- 1 | Release Notes 2 | ============= 3 | 4 | PyFilesystem has reached a point where the interface is relatively stable. The were some backwards incompatibilities introduced with version 0.5.0, due to Python 3 support. 5 | 6 | Changes from 0.4.0 7 | ------------------ 8 | 9 | Python 3.X support was added. The interface has remained much the same, but the ``open`` method now works like Python 3's builtin, which handles text encoding more elegantly. i.e. if you open a file in text mode, you get a stream that reads or writes unicode rather than binary strings. 10 | 11 | The new signature to the ``open`` method (and ``safeopen``) is as follows:: 12 | 13 | def open(self, path, mode='r', buffering=-1, encoding=None, errors=None, newline=None, line_buffering=False, **kwargs): 14 | 15 | In order to keep the same signature across both Python 2 and 3, PyFilesystems uses the ``io`` module from the standard library. Unfortunately this is only available from Python 2.6 onwards, so Python 2.5 support has been dropped. If you need Python 2.5 support, consider sticking to PyFilesystem 0.4.0. 16 | 17 | By default the new ``open`` method now returns a unicode text stream, whereas 0.4.0 returned a binary file-like object. If you have code that runs on 0.4.0, you will probably want to either modify your code to work with unicode or explicitly open files in binary mode. The latter is as simple as changing the mode from "r" to "rb" (or "w" to "wb"), but if you were working with unicode, the new text streams will likely save you a few lines of code. 18 | 19 | The ``setcontents`` and ``getcontents`` methods have also grown a few parameters in order to work with text files. So you won't require an extra encode / decode step for text files. 20 | 21 | -------------------------------------------------------------------------------- /docs/remote.rst: -------------------------------------------------------------------------------- 1 | .. automodule:: fs.remote 2 | :members: 3 | -------------------------------------------------------------------------------- /docs/rpcfs.rst: -------------------------------------------------------------------------------- 1 | .. automodule:: fs.rpcfs 2 | :members: -------------------------------------------------------------------------------- /docs/s3fs.rst: -------------------------------------------------------------------------------- 1 | .. automodule:: fs.s3fs 2 | :members: -------------------------------------------------------------------------------- /docs/sftpfs.rst: -------------------------------------------------------------------------------- 1 | .. automodule:: fs.sftpfs 2 | :members: -------------------------------------------------------------------------------- /docs/spelling_wordlist.txt: -------------------------------------------------------------------------------- 1 | FS 2 | fs 3 | filesystem 4 | isfile 5 | isdir 6 | metadata 7 | makedir 8 | movedir 9 | listdir 10 | copyfile 11 | copydir 12 | syspath 13 | unicode 14 | TODO 15 | LAFS 16 | contrib 17 | tahoefs 18 | username 19 | dict 20 | Auth 21 | SSL 22 | url 23 | WebDAV 24 | dexml 25 | RemoteFileBuffer 26 | DAVFS 27 | davfs 28 | pyfilesystem 29 | filetimes 30 | TahoeFS 31 | ZipOpenError 32 | FileSystem 33 | zipfs 34 | wrapfs 35 | readonlyfs 36 | UnsupportedError 37 | LimitSizeFS 38 | limitsizefs 39 | args 40 | kwds 41 | LazyFS 42 | lazyfs 43 | unix 44 | WrapFS 45 | wrapfs 46 | HideDotFilesFS 47 | hidedotfilesfs 48 | dirs 49 | iterable 50 | jpg 51 | stdout 52 | OSFS 53 | osfs 54 | tempfs 55 | TempFS 56 | mkdtemp 57 | del 58 | iter 59 | WatchableFS 60 | Mixin 61 | Filelike 62 | Filesystems 63 | utils 64 | tempfile 65 | SFTP 66 | sftp 67 | paramiko 68 | hostname 69 | SFTPClient 70 | SFTPFS 71 | AWS 72 | src 73 | dst 74 | filesystem's 75 | RPCFS 76 | RPCFSServer 77 | xmlrpc 78 | setcontents 79 | RemoteConnectionError 80 | ConnectionManagerFS 81 | CacheFS 82 | mixin 83 | CacheFSMixin 84 | filesystems 85 | trie 86 | PathMap 87 | wildcard 88 | pathsplit 89 | ValueError 90 | abspath 91 | ftp 92 | config 93 | URIs 94 | builtin 95 | Indices 96 | UTF 97 | sftpfs 98 | RPC 99 | rpcfs 100 | basename 101 | classmethod 102 | writeable 103 | os 104 | getsyspath 105 | MultiFS 106 | multifs 107 | ResourceNotFoundError 108 | Unmounts 109 | MountFS 110 | mountfs 111 | StringIO 112 | memoryfs 113 | httpfs 114 | ftpfs 115 | SpooledTemporaryFile 116 | truncatable 117 | filelike 118 | EOF 119 | FileLikeBase 120 | readline 121 | isatty 122 | fileno 123 | docstrings 124 | Django 125 | django 126 | FSStorage 127 | prepend 128 | FSImportHook 129 | uninstalls 130 | Uninstall 131 | bytecode 132 | sourcecode 133 | automagically 134 | URLs 135 | urls 136 | importhook 137 | xmlrpclib 138 | RPFSInterface 139 | SimpleXmlRPCServer 140 | Twisted's 141 | FSError 142 | BaseSFTPServer 143 | ThreadingMixin 144 | SFTPServerInterface 145 | auth 146 | transport's 147 | publickey 148 | ServerInterface 149 | MountProcess 150 | dokan 151 | FSOperations 152 | FSOperationsClass 153 | DOKAN 154 | bitmask 155 | unmounts 156 | fsname 157 | numthreads 158 | Popen 159 | OSError 160 | autorun 161 | pickleable 162 | struct 163 | SafetyFS 164 | fusermount 165 | fds 166 | nowait 167 | nothreads 168 | PyFilesystem 169 | getmmap 170 | mmap 171 | FileWrapper 172 | py 173 | pyc 174 | RPCFSInterface 175 | SimpleXMLRPCServer 176 | SocketServer 177 | TCPServer 178 | auths 179 | RequestHandler 180 | ABI 181 | ctypes 182 | IOError 183 | FileUploadManager 184 | TahoeLAFS 185 | api 186 | tahoelafs 187 | wxPython 188 | browsewin 189 | NullFile 190 | writeline 191 | SubFS 192 | DestinationExistsError 193 | builtins 194 | datetime 195 | bool 196 | getinfo 197 | NoSysPathError 198 | str 199 | namespaces 200 | linux 201 | github 202 | McGugan 203 | PyFilesystem's 204 | OSX 205 | SVN 206 | Boto 207 | Sandboxing 208 | txt 209 | mtime 210 | ascii 211 | symlink 212 | namespace 213 | fsls 214 | fstree 215 | fscat 216 | fsinfo 217 | fsmv 218 | fsmount 219 | fsserver 220 | fscp 221 | fsrm 222 | fsmkdir 223 | listdirinfo 224 | override 225 | overridden 226 | readme 227 | -------------------------------------------------------------------------------- /docs/tempfs.rst: -------------------------------------------------------------------------------- 1 | .. automodule:: fs.tempfs 2 | :members: -------------------------------------------------------------------------------- /docs/utilities.rst: -------------------------------------------------------------------------------- 1 | Utility Modules 2 | =============== 3 | 4 | PyFilesystem also contains some miscellaneous utility modules to make writing 5 | new FS implementations easier. 6 | 7 | 8 | fs.path 9 | ------- 10 | Contains many utility functions for manipulating filesystem paths. See :mod:`~fs.path`. 11 | 12 | fs.errors 13 | --------- 14 | Contains all the standard error classes used by PyFilesystem, along with some useful error-handling decorators. See :mod:`~fs.errors`. 15 | 16 | fs.filelike 17 | ----------- 18 | Takes care of a lot of the groundwork for implementing and manipulating objects that support Python's standard "file-like" interface. See :mod:`~fs.filelike`. 19 | 20 | fs.remote 21 | --------- 22 | Contains useful functions and classes for implementing remote filesystems. See :mod:`~fs.remote`. 23 | 24 | -------------------------------------------------------------------------------- /docs/utils.rst: -------------------------------------------------------------------------------- 1 | fs.utils 2 | ======== 3 | 4 | .. automodule:: fs.utils 5 | :members: -------------------------------------------------------------------------------- /docs/watch.rst: -------------------------------------------------------------------------------- 1 | .. automodule:: fs.watch 2 | :members: -------------------------------------------------------------------------------- /docs/wrapfs/.tmp_index.html.90192~: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PyFilesystem/pyfilesystem/7dfe14ae6c3b9c53543c1c3890232d9f37579f34/docs/wrapfs/.tmp_index.html.90192~ -------------------------------------------------------------------------------- /docs/wrapfs/base.rst: -------------------------------------------------------------------------------- 1 | .. automodule:: fs.wrapfs 2 | :members: -------------------------------------------------------------------------------- /docs/wrapfs/hidedotfiles.rst: -------------------------------------------------------------------------------- 1 | .. automodule:: fs.wrapfs.hidedotfilesfs 2 | :members: -------------------------------------------------------------------------------- /docs/wrapfs/index.rst: -------------------------------------------------------------------------------- 1 | fs.wrapfs 2 | ========= 3 | 4 | The ``fs.wrapfs`` module adds additional functionality to existing FS objects. 5 | 6 | .. toctree:: 7 | :maxdepth: 3 8 | 9 | base.rst 10 | hidedotfiles.rst 11 | lazyfs.rst 12 | limitsize.rst 13 | readonlyfs.rst 14 | -------------------------------------------------------------------------------- /docs/wrapfs/lazyfs.rst: -------------------------------------------------------------------------------- 1 | .. automodule:: fs.wrapfs.lazyfs 2 | :members: -------------------------------------------------------------------------------- /docs/wrapfs/limitsize.rst: -------------------------------------------------------------------------------- 1 | .. automodule:: fs.wrapfs.limitsizefs 2 | :members: -------------------------------------------------------------------------------- /docs/wrapfs/readonlyfs.rst: -------------------------------------------------------------------------------- 1 | .. automodule:: fs.wrapfs.readonlyfs 2 | :members: -------------------------------------------------------------------------------- /docs/zipfs.rst: -------------------------------------------------------------------------------- 1 | .. automodule:: fs.zipfs 2 | :members: -------------------------------------------------------------------------------- /fs/__init__.py: -------------------------------------------------------------------------------- 1 | """ 2 | 3 | fs: a filesystem abstraction. 4 | 5 | This module provides an abstract base class 'FS' that defines a consistent 6 | interface to different kinds of filesystem, along with a range of concrete 7 | implementations of this interface such as: 8 | 9 | OSFS: access the local filesystem, through the 'os' module 10 | TempFS: a temporary filesystem that's automatically cleared on exit 11 | MemoryFS: a filesystem that exists only in memory 12 | ZipFS: access a zipfile like a filesystem 13 | SFTPFS: access files on a SFTP server 14 | S3FS: access files stored in Amazon S3 15 | 16 | """ 17 | 18 | __version__ = "0.5.5a1" 19 | __author__ = "Will McGugan (will@willmcgugan.com)" 20 | 21 | # provide these by default so people can use 'fs.path.basename' etc. 22 | from fs import errors 23 | from fs import path 24 | 25 | _thread_synchronize_default = True 26 | def set_thread_synchronize_default(sync): 27 | """Sets the default thread synchronisation flag. 28 | 29 | FS objects are made thread-safe through the use of a per-FS threading Lock 30 | object. Since this can introduce an small overhead it can be disabled with 31 | this function if the code is single-threaded. 32 | 33 | :param sync: Set whether to use thread synchronisation for new FS objects 34 | 35 | """ 36 | global _thread_synchronization_default 37 | _thread_synchronization_default = sync 38 | 39 | # Store some identifiers in the fs namespace 40 | import os 41 | SEEK_CUR = os.SEEK_CUR 42 | SEEK_END = os.SEEK_END 43 | SEEK_SET = os.SEEK_SET 44 | 45 | 46 | # Allow clean use of logging throughout the lib 47 | import logging as _logging 48 | class _NullHandler(_logging.Handler): 49 | def emit(self,record): 50 | pass 51 | _logging.getLogger("fs").addHandler(_NullHandler()) 52 | def getLogger(name): 53 | """Get a logger object for use within the pyfilesystem library.""" 54 | assert name.startswith("fs.") 55 | return _logging.getLogger(name) 56 | 57 | -------------------------------------------------------------------------------- /fs/appdirfs.py: -------------------------------------------------------------------------------- 1 | """ 2 | fs.appdirfs 3 | =========== 4 | 5 | A collection of filesystems that map to application specific locations. 6 | 7 | These classes abstract away the different requirements for user data across platforms, 8 | which vary in their conventions. They are all subclasses of :class:`~fs.osfs.OSFS`, 9 | all that differs from `OSFS` is the constructor which detects the appropriate 10 | location given the name of the application, author name and other parameters. 11 | 12 | Uses `appdirs` (https://github.com/ActiveState/appdirs), written by Trent Mick and Sridhar Ratnakumar 13 | 14 | """ 15 | 16 | from fs.osfs import OSFS 17 | from fs.appdirs import AppDirs 18 | 19 | __all__ = ['UserDataFS', 20 | 'SiteDataFS', 21 | 'UserCacheFS', 22 | 'UserLogFS'] 23 | 24 | 25 | class UserDataFS(OSFS): 26 | """A filesystem for per-user application data.""" 27 | def __init__(self, appname, appauthor=None, version=None, roaming=False, create=True): 28 | """ 29 | :param appname: the name of the application 30 | :param appauthor: the name of the author (used on Windows) 31 | :param version: optional version string, if a unique location per version of the application is required 32 | :param roaming: if True, use a *roaming* profile on Windows, see http://technet.microsoft.com/en-us/library/cc766489(WS.10).aspx 33 | :param create: if True (the default) the directory will be created if it does not exist 34 | 35 | """ 36 | app_dirs = AppDirs(appname, appauthor, version, roaming) 37 | super(UserDataFS, self).__init__(app_dirs.user_data_dir, create=create) 38 | 39 | 40 | class SiteDataFS(OSFS): 41 | """A filesystem for application site data.""" 42 | def __init__(self, appname, appauthor=None, version=None, roaming=False, create=True): 43 | """ 44 | :param appname: the name of the application 45 | :param appauthor: the name of the author (not used on linux) 46 | :param version: optional version string, if a unique location per version of the application is required 47 | :param roaming: if True, use a *roaming* profile on Windows, see http://technet.microsoft.com/en-us/library/cc766489(WS.10).aspx 48 | :param create: if True (the default) the directory will be created if it does not exist 49 | 50 | """ 51 | app_dirs = AppDirs(appname, appauthor, version, roaming) 52 | super(SiteDataFS, self).__init__(app_dirs.site_data_dir, create=create) 53 | 54 | 55 | class UserCacheFS(OSFS): 56 | """A filesystem for per-user application cache data.""" 57 | def __init__(self, appname, appauthor=None, version=None, roaming=False, create=True): 58 | """ 59 | :param appname: the name of the application 60 | :param appauthor: the name of the author (not used on linux) 61 | :param version: optional version string, if a unique location per version of the application is required 62 | :param roaming: if True, use a *roaming* profile on Windows, see http://technet.microsoft.com/en-us/library/cc766489(WS.10).aspx 63 | :param create: if True (the default) the directory will be created if it does not exist 64 | 65 | """ 66 | app_dirs = AppDirs(appname, appauthor, version, roaming) 67 | super(UserCacheFS, self).__init__(app_dirs.user_cache_dir, create=create) 68 | 69 | 70 | class UserLogFS(OSFS): 71 | """A filesystem for per-user application log data.""" 72 | def __init__(self, appname, appauthor=None, version=None, roaming=False, create=True): 73 | """ 74 | :param appname: the name of the application 75 | :param appauthor: the name of the author (not used on linux) 76 | :param version: optional version string, if a unique location per version of the application is required 77 | :param roaming: if True, use a *roaming* profile on Windows, see http://technet.microsoft.com/en-us/library/cc766489(WS.10).aspx 78 | :param create: if True (the default) the directory will be created if it does not exist 79 | 80 | """ 81 | app_dirs = AppDirs(appname, appauthor, version, roaming) 82 | super(UserLogFS, self).__init__(app_dirs.user_log_dir, create=create) 83 | 84 | 85 | if __name__ == "__main__": 86 | udfs = UserDataFS('exampleapp', appauthor='pyfs') 87 | print udfs 88 | udfs2 = UserDataFS('exampleapp2', appauthor='pyfs', create=False) 89 | print udfs2 90 | -------------------------------------------------------------------------------- /fs/commands/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PyFilesystem/pyfilesystem/7dfe14ae6c3b9c53543c1c3890232d9f37579f34/fs/commands/__init__.py -------------------------------------------------------------------------------- /fs/commands/fscat: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | from fs.commands.fscat import run 3 | run() -------------------------------------------------------------------------------- /fs/commands/fscat.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | from fs.commands.runner import Command 3 | import sys 4 | 5 | class FSCat(Command): 6 | 7 | usage = """fscat [OPTION]... [FILE]... 8 | Concetanate FILE(s)""" 9 | 10 | version = "1.0" 11 | 12 | def do_run(self, options, args): 13 | count = 0 14 | for fs, path, is_dir in self.get_resources(args): 15 | if is_dir: 16 | self.error('%s is a directory\n' % path) 17 | return 1 18 | self.output(fs.getcontents(path)) 19 | count += 1 20 | if self.is_terminal() and count: 21 | self.output('\n') 22 | 23 | def run(): 24 | return FSCat().run() 25 | 26 | if __name__ == "__main__": 27 | sys.exit(run()) 28 | 29 | -------------------------------------------------------------------------------- /fs/commands/fscp: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | from fs.commands.fscp import run 3 | run() -------------------------------------------------------------------------------- /fs/commands/fsinfo: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | from fs.commands.fsinfo import run 3 | run() -------------------------------------------------------------------------------- /fs/commands/fsinfo.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | from fs.commands.runner import Command 3 | import sys 4 | from datetime import datetime 5 | 6 | class FSInfo(Command): 7 | 8 | usage = """fsinfo [OPTION]... [PATH] 9 | Display information regarding an FS resource""" 10 | 11 | def get_optparse(self): 12 | optparse = super(FSInfo, self).get_optparse() 13 | optparse.add_option('-k', '--key', dest='keys', action='append', default=[], 14 | help='display KEYS only') 15 | optparse.add_option('-s', '--simple', dest='simple', action='store_true', default=False, 16 | help='info displayed in simple format (no table)') 17 | optparse.add_option('-o', '--omit', dest='omit', action='store_true', default=False, 18 | help='omit path name from output') 19 | optparse.add_option('-d', '--dirsonly', dest='dirsonly', action="store_true", default=False, 20 | help="list directories only", metavar="DIRSONLY") 21 | optparse.add_option('-f', '--filesonly', dest='filesonly', action="store_true", default=False, 22 | help="list files only", metavar="FILESONLY") 23 | return optparse 24 | 25 | 26 | def do_run(self, options, args): 27 | 28 | def wrap_value(val): 29 | if val.rstrip() == '\0': 30 | return self.wrap_error('... missing ...') 31 | return val 32 | 33 | def make_printable(text): 34 | if not isinstance(text, basestring): 35 | try: 36 | text = str(text) 37 | except: 38 | try: 39 | text = unicode(text) 40 | except: 41 | text = repr(text) 42 | return text 43 | 44 | 45 | keys = options.keys or None 46 | for fs, path, is_dir in self.get_resources(args, 47 | files_only=options.filesonly, 48 | dirs_only=options.dirsonly): 49 | if not options.omit: 50 | if options.simple: 51 | file_line = u'%s\n' % self.wrap_filename(path) 52 | else: 53 | file_line = u'[%s] %s\n' % (self.wrap_filename(path), self.wrap_faded(fs.desc(path))) 54 | self.output(file_line) 55 | info = fs.getinfo(path) 56 | 57 | for k, v in info.items(): 58 | if k.startswith('_'): 59 | del info[k] 60 | elif not isinstance(v, (basestring, int, long, float, bool, datetime)): 61 | del info[k] 62 | 63 | if keys: 64 | table = [(k, make_printable(info.get(k, '\0'))) for k in keys] 65 | else: 66 | keys = sorted(info.keys()) 67 | table = [(k, make_printable(info[k])) for k in sorted(info.keys())] 68 | 69 | if options.simple: 70 | for row in table: 71 | self.output(row[-1] + '\n') 72 | else: 73 | self.output_table(table, {0:self.wrap_table_header, 1:wrap_value}) 74 | 75 | 76 | def run(): 77 | return FSInfo().run() 78 | 79 | if __name__ == "__main__": 80 | sys.exit(run()) 81 | -------------------------------------------------------------------------------- /fs/commands/fsls: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | from fs.commands.fsls import run 3 | run() -------------------------------------------------------------------------------- /fs/commands/fsmkdir: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | from fs.commands.fsmkdir import run 3 | run() -------------------------------------------------------------------------------- /fs/commands/fsmkdir.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | from fs.commands.runner import Command 3 | import sys 4 | 5 | class FSMkdir(Command): 6 | 7 | usage = """fsmkdir [PATH] 8 | Make a directory""" 9 | 10 | version = "1.0" 11 | 12 | def do_run(self, options, args): 13 | 14 | for fs_url in args: 15 | self.open_fs(fs_url, create_dir=True) 16 | 17 | def run(): 18 | return FSMkdir().run() 19 | 20 | if __name__ == "__main__": 21 | sys.exit(run()) 22 | 23 | -------------------------------------------------------------------------------- /fs/commands/fsmount: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | from fs.commands.fsmount import run 3 | run() -------------------------------------------------------------------------------- /fs/commands/fsmount.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | from fs.commands.runner import Command 3 | import sys 4 | import platform 5 | import os 6 | import os.path 7 | 8 | 9 | platform = platform.system() 10 | 11 | 12 | class FSMount(Command): 13 | 14 | if platform == "Windows": 15 | usage = """fsmount [OPTIONS]... [FS] [DRIVE LETTER] 16 | or fsmount -u [DRIVER LETTER] 17 | Mounts a filesystem on a drive letter""" 18 | else: 19 | usage = """fsmount [OPTIONS]... [FS] [SYSTEM PATH] 20 | or fsmount -u [SYSTEM PATH] 21 | Mounts a file system on a system path""" 22 | 23 | version = "1.0" 24 | 25 | def get_optparse(self): 26 | optparse = super(FSMount, self).get_optparse() 27 | optparse.add_option('-f', '--foreground', dest='foreground', action="store_true", default=False, 28 | help="run the mount process in the foreground", metavar="FOREGROUND") 29 | optparse.add_option('-u', '--unmount', dest='unmount', action="store_true", default=False, 30 | help="unmount path", metavar="UNMOUNT") 31 | optparse.add_option('-n', '--nocache', dest='nocache', action="store_true", default=False, 32 | help="do not cache network filesystems", metavar="NOCACHE") 33 | 34 | return optparse 35 | 36 | def do_run(self, options, args): 37 | 38 | windows = platform == "Windows" 39 | 40 | if options.unmount: 41 | 42 | if windows: 43 | 44 | try: 45 | mount_path = args[0][:1] 46 | except IndexError: 47 | self.error('Driver letter required\n') 48 | return 1 49 | 50 | from fs.expose import dokan 51 | mount_path = mount_path[:1].upper() 52 | self.output('unmounting %s:...\n' % mount_path, True) 53 | dokan.unmount(mount_path) 54 | return 55 | 56 | else: 57 | try: 58 | mount_path = args[0] 59 | except IndexError: 60 | self.error(self.usage + '\n') 61 | return 1 62 | 63 | from fs.expose import fuse 64 | self.output('unmounting %s...\n' % mount_path, True) 65 | fuse.unmount(mount_path) 66 | return 67 | 68 | try: 69 | fs_url = args[0] 70 | except IndexError: 71 | self.error(self.usage + '\n') 72 | return 1 73 | 74 | try: 75 | mount_path = args[1] 76 | except IndexError: 77 | if windows: 78 | mount_path = mount_path[:1].upper() 79 | self.error(self.usage + '\n') 80 | else: 81 | self.error(self.usage + '\n') 82 | return 1 83 | 84 | fs, path = self.open_fs(fs_url, create_dir=True) 85 | if path: 86 | if not fs.isdir(path): 87 | self.error('%s is not a directory on %s' % (fs_url, fs)) 88 | return 1 89 | fs = fs.opendir(path) 90 | path = '/' 91 | if not options.nocache: 92 | fs.cache_hint(True) 93 | 94 | if windows: 95 | from fs.expose import dokan 96 | 97 | if len(mount_path) > 1: 98 | self.error('Driver letter should be one character') 99 | return 1 100 | 101 | self.output("Mounting %s on %s:\n" % (fs, mount_path), True) 102 | flags = dokan.DOKAN_OPTION_REMOVABLE 103 | if options.debug: 104 | flags |= dokan.DOKAN_OPTION_DEBUG | dokan.DOKAN_OPTION_STDERR 105 | 106 | mp = dokan.mount(fs, 107 | mount_path, 108 | numthreads=5, 109 | foreground=options.foreground, 110 | flags=flags, 111 | volname=str(fs)) 112 | 113 | else: 114 | if not os.path.exists(mount_path): 115 | try: 116 | os.makedirs(mount_path) 117 | except: 118 | pass 119 | 120 | from fs.expose import fuse 121 | self.output("Mounting %s on %s\n" % (fs, mount_path), True) 122 | 123 | if options.foreground: 124 | fuse_process = fuse.mount(fs, 125 | mount_path, 126 | foreground=True) 127 | else: 128 | if not os.fork(): 129 | mp = fuse.mount(fs, 130 | mount_path, 131 | foreground=True) 132 | else: 133 | fs.close = lambda: None 134 | 135 | def run(): 136 | return FSMount().run() 137 | 138 | if __name__ == "__main__": 139 | sys.exit(run()) 140 | -------------------------------------------------------------------------------- /fs/commands/fsmv: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | from fs.commands.fsmv import run 3 | run() -------------------------------------------------------------------------------- /fs/commands/fsmv.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | from fs.utils import movefile, movefile_non_atomic, contains_files 4 | from fs.commands import fscp 5 | import sys 6 | 7 | class FSmv(fscp.FScp): 8 | 9 | usage = """fsmv [OPTION]... [SOURCE] [DESTINATION] 10 | Move files from SOURCE to DESTINATION""" 11 | 12 | def get_verb(self): 13 | return 'moving...' 14 | 15 | def get_action(self): 16 | if self.options.threads > 1: 17 | return movefile_non_atomic 18 | else: 19 | return movefile 20 | 21 | def post_actions(self): 22 | for fs, dirpath in self.root_dirs: 23 | if not contains_files(fs, dirpath): 24 | fs.removedir(dirpath, force=True) 25 | 26 | def run(): 27 | return FSmv().run() 28 | 29 | if __name__ == "__main__": 30 | sys.exit(run()) 31 | -------------------------------------------------------------------------------- /fs/commands/fsrm: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | from fs.commands.fsrm import run 3 | run() -------------------------------------------------------------------------------- /fs/commands/fsrm.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | from fs.errors import ResourceNotFoundError 4 | from fs.opener import opener 5 | from fs.commands.runner import Command 6 | import sys 7 | 8 | class FSrm(Command): 9 | 10 | usage = """fsrm [OPTION]... [PATH] 11 | Remove a file or directory at PATH""" 12 | 13 | def get_optparse(self): 14 | optparse = super(FSrm, self).get_optparse() 15 | optparse.add_option('-f', '--force', dest='force', action='store_true', default=False, 16 | help='ignore non-existent files, never prompt') 17 | optparse.add_option('-i', '--interactive', dest='interactive', action='store_true', default=False, 18 | help='prompt before removing') 19 | optparse.add_option('-r', '--recursive', dest='recursive', action='store_true', default=False, 20 | help='remove directories and their contents recursively') 21 | return optparse 22 | 23 | def do_run(self, options, args): 24 | 25 | interactive = options.interactive 26 | verbose = options.verbose 27 | 28 | for fs, path, is_dir in self.get_resources(args): 29 | if interactive: 30 | if is_dir: 31 | msg = "remove directory '%s'?" % path 32 | else: 33 | msg = "remove file '%s'?" % path 34 | if not self.ask(msg) in 'yY': 35 | continue 36 | try: 37 | if is_dir: 38 | fs.removedir(path, force=options.recursive) 39 | else: 40 | fs.remove(path) 41 | except ResourceNotFoundError: 42 | if not options.force: 43 | raise 44 | else: 45 | if verbose: 46 | self.output("removed '%s'\n" % path) 47 | 48 | 49 | def run(): 50 | return FSrm().run() 51 | 52 | if __name__ == "__main__": 53 | sys.exit(run()) 54 | 55 | -------------------------------------------------------------------------------- /fs/commands/fsserve: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | from fs.commands.fsserve import run 3 | run() -------------------------------------------------------------------------------- /fs/commands/fsserve.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | import sys 4 | 5 | from fs.opener import opener 6 | from fs.commands.runner import Command 7 | from fs.utils import print_fs 8 | import errno 9 | 10 | 11 | class FSServe(Command): 12 | 13 | usage = """fsserve [OPTION]... [PATH] 14 | Serves the contents of PATH with one of a number of methods""" 15 | 16 | def get_optparse(self): 17 | optparse = super(FSServe, self).get_optparse() 18 | optparse.add_option('-t', '--type', dest='type', type="string", default="http", 19 | help="Server type to create (http, rpc, sftp)", metavar="TYPE") 20 | optparse.add_option('-a', '--addr', dest='addr', type="string", default="127.0.0.1", 21 | help="Server address", metavar="ADDR") 22 | optparse.add_option('-p', '--port', dest='port', type="int", 23 | help="Port number", metavar="") 24 | return optparse 25 | 26 | def do_run(self, options, args): 27 | 28 | try: 29 | fs_url = args[0] 30 | except IndexError: 31 | fs_url = './' 32 | 33 | fs, path = self.open_fs(fs_url) 34 | 35 | if fs.isdir(path): 36 | fs = fs.opendir(path) 37 | path = '/' 38 | 39 | self.output("Opened %s\n" % fs, verbose=True) 40 | 41 | port = options.port 42 | 43 | try: 44 | 45 | if options.type == 'http': 46 | from fs.expose.http import serve_fs 47 | if port is None: 48 | port = 80 49 | self.output("Starting http server on %s:%i\n" % (options.addr, port), verbose=True) 50 | serve_fs(fs, options.addr, port) 51 | 52 | elif options.type == 'rpc': 53 | from fs.expose.xmlrpc import RPCFSServer 54 | if port is None: 55 | port = 80 56 | s = RPCFSServer(fs, (options.addr, port)) 57 | self.output("Starting rpc server on %s:%i\n" % (options.addr, port), verbose=True) 58 | s.serve_forever() 59 | 60 | elif options.type == 'ftp': 61 | from fs.expose.ftp import serve_fs 62 | if port is None: 63 | port = 21 64 | self.output("Starting ftp server on %s:%i\n" % (options.addr, port), verbose=True) 65 | serve_fs(fs, options.addr, port) 66 | 67 | elif options.type == 'sftp': 68 | from fs.expose.sftp import BaseSFTPServer 69 | import logging 70 | log = logging.getLogger('paramiko') 71 | if options.debug: 72 | log.setLevel(logging.DEBUG) 73 | elif options.verbose: 74 | log.setLevel(logging.INFO) 75 | ch = logging.StreamHandler() 76 | ch.setLevel(logging.DEBUG) 77 | log.addHandler(ch) 78 | 79 | if port is None: 80 | port = 22 81 | server = BaseSFTPServer((options.addr, port), fs) 82 | try: 83 | self.output("Starting sftp server on %s:%i\n" % (options.addr, port), verbose=True) 84 | server.serve_forever() 85 | except Exception, e: 86 | pass 87 | finally: 88 | server.server_close() 89 | 90 | else: 91 | self.error("Server type '%s' not recognised\n" % options.type) 92 | 93 | except IOError, e: 94 | if e.errno == errno.EACCES: 95 | self.error('Permission denied\n') 96 | return 1 97 | else: 98 | self.error(str(e) + '\n') 99 | return 1 100 | 101 | def run(): 102 | return FSServe().run() 103 | 104 | if __name__ == "__main__": 105 | sys.exit(run()) 106 | -------------------------------------------------------------------------------- /fs/commands/fstree: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | from fs.commands.fstree import run 3 | run() -------------------------------------------------------------------------------- /fs/commands/fstree.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | import sys 3 | 4 | from fs.opener import opener 5 | from fs.commands.runner import Command 6 | 7 | from fs.utils import print_fs 8 | 9 | class FSTree(Command): 10 | 11 | usage = """fstree [OPTION]... [PATH] 12 | Recursively display the contents of PATH in an ascii tree""" 13 | 14 | def get_optparse(self): 15 | optparse = super(FSTree, self).get_optparse() 16 | optparse.add_option('-l', '--level', dest='depth', type="int", default=5, 17 | help="Descend only LEVEL directories deep (-1 for infinite)", metavar="LEVEL") 18 | optparse.add_option('-g', '--gui', dest='gui', action='store_true', default=False, 19 | help="browse the tree with a gui") 20 | optparse.add_option('-a', '--all', dest='all', action='store_true', default=False, 21 | help="do not hide dot files") 22 | optparse.add_option('--dirsfirst', dest='dirsfirst', action='store_true', default=False, 23 | help="List directories before files") 24 | optparse.add_option('-P', dest="pattern", default=None, 25 | help="Only list files that match the given pattern") 26 | optparse.add_option('-d', dest="dirsonly", default=False, action='store_true', 27 | help="List directories only") 28 | return optparse 29 | 30 | def do_run(self, options, args): 31 | 32 | if not args: 33 | args = ['.'] 34 | 35 | for fs, path, is_dir in self.get_resources(args, single=True): 36 | if not is_dir: 37 | self.error(u"'%s' is not a dir\n" % path) 38 | return 1 39 | fs.cache_hint(True) 40 | if options.gui: 41 | from fs.browsewin import browse 42 | if path: 43 | fs = fs.opendir(path) 44 | browse(fs, hide_dotfiles=not options.all) 45 | else: 46 | if options.depth < 0: 47 | max_levels = None 48 | else: 49 | max_levels = options.depth 50 | self.output(self.wrap_dirname(args[0] + '\n')) 51 | dircount, filecount = print_fs(fs, path or '', 52 | file_out=self.output_file, 53 | max_levels=max_levels, 54 | terminal_colors=self.terminal_colors, 55 | hide_dotfiles=not options.all, 56 | dirs_first=options.dirsfirst, 57 | files_wildcard=options.pattern, 58 | dirs_only=options.dirsonly) 59 | self.output('\n') 60 | def pluralize(one, many, count): 61 | if count == 1: 62 | return '%i %s' % (count, one) 63 | else: 64 | return '%i %s' % (count, many) 65 | 66 | self.output("%s, %s\n" % (pluralize('directory', 'directories', dircount), 67 | pluralize('file', 'files', filecount))) 68 | 69 | def run(): 70 | return FSTree().run() 71 | 72 | if __name__ == "__main__": 73 | sys.exit(run()) 74 | 75 | -------------------------------------------------------------------------------- /fs/compatibility.py: -------------------------------------------------------------------------------- 1 | """ 2 | Some functions for Python3 compatibility. 3 | 4 | Not for general usage, the functionality in this file is exposed elsewhere 5 | 6 | """ 7 | 8 | import six 9 | from six import PY3 10 | 11 | 12 | def copy_file_to_fs(data, dst_fs, dst_path, chunk_size=64 * 1024, progress_callback=None, finished_callback=None): 13 | """Copy data from a string or a file-like object to a given fs/path""" 14 | if progress_callback is None: 15 | progress_callback = lambda bytes_written: None 16 | bytes_written = 0 17 | f = None 18 | try: 19 | progress_callback(bytes_written) 20 | if hasattr(data, "read"): 21 | read = data.read 22 | chunk = read(chunk_size) 23 | if isinstance(chunk, six.text_type): 24 | f = dst_fs.open(dst_path, 'w') 25 | else: 26 | f = dst_fs.open(dst_path, 'wb') 27 | write = f.write 28 | while chunk: 29 | write(chunk) 30 | bytes_written += len(chunk) 31 | progress_callback(bytes_written) 32 | chunk = read(chunk_size) 33 | else: 34 | if isinstance(data, six.text_type): 35 | f = dst_fs.open(dst_path, 'w') 36 | else: 37 | f = dst_fs.open(dst_path, 'wb') 38 | f.write(data) 39 | bytes_written += len(data) 40 | progress_callback(bytes_written) 41 | 42 | if hasattr(f, 'flush'): 43 | f.flush() 44 | if finished_callback is not None: 45 | finished_callback() 46 | 47 | finally: 48 | if f is not None: 49 | f.close() 50 | -------------------------------------------------------------------------------- /fs/contrib/__init__.py: -------------------------------------------------------------------------------- 1 | """ 2 | 3 | fs.contrib: third-party contributed FS implementations. 4 | 5 | """ 6 | 7 | 8 | -------------------------------------------------------------------------------- /fs/contrib/bigfs/subrangefile.py: -------------------------------------------------------------------------------- 1 | """ 2 | 3 | fs.contrib.bigfs.subrangefile 4 | ============================= 5 | 6 | A file-like object that allows wrapping of part of a binary file for reading. 7 | 8 | This avoids needless copies of data for large binary files if StringIO would 9 | be used. 10 | 11 | Written by Koen van de Sande 12 | http://www.tibed.net 13 | 14 | Contributed under the terms of the BSD License: 15 | http://www.opensource.org/licenses/bsd-license.php 16 | """ 17 | 18 | 19 | class SubrangeFile: 20 | """File-like class with read-only, binary mode restricting access to a subrange of the whole file""" 21 | def __init__(self, f, startOffset, fileSize): 22 | if not hasattr(f, 'read'): 23 | self.f = open(f, "rb") 24 | self.name = f 25 | else: 26 | self.f = f 27 | self.name = str(f) 28 | self.startOffset = startOffset 29 | self.fileSize = fileSize 30 | self.seek(0) 31 | 32 | def __str__(self): 33 | return "" % (self.name, self.startOffset, self.fileSize) 34 | 35 | def __unicode__(self): 36 | return unicode(self.__str__()) 37 | 38 | def size(self): 39 | return self.fileSize 40 | 41 | def seek(self, offset, whence=0): 42 | if whence == 0: 43 | offset = self.startOffset + offset 44 | elif whence == 1: 45 | offset = self.startOffset + self.tell() + offset 46 | elif whence == 2: 47 | if offset > 0: 48 | offset = 0 49 | offset = self.startOffset + self.fileSize + offset 50 | self.f.seek(offset) 51 | 52 | def tell(self): 53 | return self.f.tell() - self.startOffset 54 | 55 | def __maxSize(self,size=None): 56 | iSize = self.fileSize 57 | if not size is None: 58 | if size < iSize: 59 | iSize = size 60 | if self.tell() + iSize > self.fileSize: 61 | iSize = self.fileSize - self.tell() 62 | return iSize 63 | 64 | def readline(self,size=None): 65 | toRead = self.__maxSize(size) 66 | return self.f.readline(toRead) 67 | 68 | def read(self,size=None): 69 | toRead = self.__maxSize(size) 70 | return self.f.read(toRead) 71 | 72 | def readlines(self,size=None): 73 | toRead = self.__maxSize(size) 74 | temp = self.f.readlines(toRead) 75 | # now cut off more than we should read... 76 | result = [] 77 | counter = 0 78 | for line in temp: 79 | if counter + len(line) > toRead: 80 | if toRead == counter: 81 | break 82 | result.append(line[0:(toRead-counter)]) 83 | break 84 | else: 85 | result.append(line) 86 | counter += len(line) 87 | return result 88 | 89 | 90 | -------------------------------------------------------------------------------- /fs/contrib/davfs/util.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2009-2010, Cloud Matrix Pty. Ltd. 2 | # All rights reserved; available under the terms of the MIT License. 3 | """ 4 | 5 | fs.contrib.davfs.util: utils for FS WebDAV implementation. 6 | 7 | """ 8 | 9 | import os 10 | import re 11 | import cookielib 12 | 13 | 14 | def get_fileno(file): 15 | """Get the os-level fileno of a file-like object. 16 | 17 | This function decodes several common file wrapper structures in an attempt 18 | to determine the underlying OS-level fileno for an object. 19 | """ 20 | while not hasattr(file,"fileno"): 21 | if hasattr(file,"file"): 22 | file = file.file 23 | elif hasattr(file,"_file"): 24 | file = file._file 25 | elif hasattr(file,"_fileobj"): 26 | file = file._fileobj 27 | else: 28 | raise AttributeError 29 | return file.fileno() 30 | 31 | 32 | def get_filesize(file): 33 | """Get the "size" attribute of a file-like object.""" 34 | while not hasattr(file,"size"): 35 | if hasattr(file,"file"): 36 | file = file.file 37 | elif hasattr(file,"_file"): 38 | file = file._file 39 | elif hasattr(file,"_fileobj"): 40 | file = file._fileobj 41 | else: 42 | raise AttributeError 43 | return file.size 44 | 45 | 46 | def file_chunks(f,chunk_size=1024*64): 47 | """Generator yielding chunks of a file. 48 | 49 | This provides a simple way to iterate through binary data chunks from 50 | a file. Recall that using a file directly as an iterator generates the 51 | *lines* from that file, which is useless and very inefficient for binary 52 | data. 53 | """ 54 | chunk = f.read(chunk_size) 55 | while chunk: 56 | yield chunk 57 | chunk = f.read(chunk_size) 58 | 59 | 60 | def normalize_req_body(body,chunk_size=1024*64): 61 | """Convert given request body into (size,data_iter) pair. 62 | 63 | This function is used to accept a variety of different inputs in HTTP 64 | requests, converting them to a standard format. 65 | """ 66 | if hasattr(body,"getvalue"): 67 | value = body.getvalue() 68 | return (len(value),[value]) 69 | elif hasattr(body,"read"): 70 | try: 71 | size = int(get_filesize(body)) 72 | except (AttributeError,TypeError): 73 | try: 74 | size = os.fstat(get_fileno(body)).st_size 75 | except (AttributeError,OSError): 76 | size = None 77 | return (size,file_chunks(body,chunk_size)) 78 | else: 79 | body = str(body) 80 | return (len(body),[body]) 81 | 82 | 83 | class FakeReq: 84 | """Compatability interface to use cookielib with raw httplib objects.""" 85 | 86 | def __init__(self,connection,scheme,path): 87 | self.connection = connection 88 | self.scheme = scheme 89 | self.path = path 90 | 91 | def get_full_url(self): 92 | return self.scheme + "://" + self.connection.host + self.path 93 | 94 | def get_type(self): 95 | return self.scheme 96 | 97 | def get_host(self): 98 | return self.connection.host 99 | 100 | def is_unverifiable(self): 101 | return True 102 | 103 | def get_origin_req_host(self): 104 | return self.connection.host 105 | 106 | def has_header(self,header): 107 | return False 108 | 109 | def add_unredirected_header(self,header,value): 110 | self.connection.putheader(header,value) 111 | 112 | 113 | class FakeResp: 114 | """Compatability interface to use cookielib with raw httplib objects.""" 115 | 116 | def __init__(self,response): 117 | self.response = response 118 | 119 | def info(self): 120 | return self 121 | 122 | def getheaders(self,header): 123 | header = header.lower() 124 | headers = self.response.getheaders() 125 | return [v for (h,v) in headers if h.lower() == header] 126 | 127 | 128 | # The standard cooklielib cookie parser doesn't seem to handle multiple 129 | # cookies correctory, so we replace it with a better version. This code 130 | # is a tweaked version of the cookielib function of the same name. 131 | # 132 | _test_cookie = "sessionid=e9c9b002befa93bd865ce155270307ef; Domain=.cloud.me; expires=Wed, 10-Feb-2010 03:27:20 GMT; httponly; Max-Age=1209600; Path=/, sessionid_https=None; Domain=.cloud.me; expires=Wed, 10-Feb-2010 03:27:20 GMT; httponly; Max-Age=1209600; Path=/; secure" 133 | if len(cookielib.parse_ns_headers([_test_cookie])) != 2: 134 | def parse_ns_headers(ns_headers): 135 | """Improved parser for netscape-style cookies. 136 | 137 | This version can handle multiple cookies in a single header. 138 | """ 139 | known_attrs = ("expires", "domain", "path", "secure","port", "max-age") 140 | result = [] 141 | for ns_header in ns_headers: 142 | pairs = [] 143 | version_set = False 144 | for ii, param in enumerate(re.split(r"(;\s)|(,\s(?=[a-zA-Z0-9_\-]+=))", ns_header)): 145 | if param is None: 146 | continue 147 | param = param.rstrip() 148 | if param == "" or param[0] == ";": 149 | continue 150 | if param[0] == ",": 151 | if pairs: 152 | if not version_set: 153 | pairs.append(("version", "0")) 154 | result.append(pairs) 155 | pairs = [] 156 | continue 157 | if "=" not in param: 158 | k, v = param, None 159 | else: 160 | k, v = re.split(r"\s*=\s*", param, 1) 161 | k = k.lstrip() 162 | if ii != 0: 163 | lc = k.lower() 164 | if lc in known_attrs: 165 | k = lc 166 | if k == "version": 167 | # This is an RFC 2109 cookie. 168 | version_set = True 169 | if k == "expires": 170 | # convert expires date to seconds since epoch 171 | if v.startswith('"'): v = v[1:] 172 | if v.endswith('"'): v = v[:-1] 173 | v = cookielib.http2time(v) # None if invalid 174 | pairs.append((k, v)) 175 | if pairs: 176 | if not version_set: 177 | pairs.append(("version", "0")) 178 | result.append(pairs) 179 | return result 180 | cookielib.parse_ns_headers = parse_ns_headers 181 | assert len(cookielib.parse_ns_headers([_test_cookie])) == 2 182 | 183 | -------------------------------------------------------------------------------- /fs/contrib/tahoelafs/connection.py: -------------------------------------------------------------------------------- 1 | import platform 2 | import logging 3 | 4 | import fs.errors as errors 5 | from fs import SEEK_END 6 | 7 | python3 = int(platform.python_version_tuple()[0]) > 2 8 | 9 | if python3: 10 | from urllib.parse import urlencode, pathname2url, quote 11 | from urllib.request import Request, urlopen 12 | else: 13 | from urllib import urlencode, pathname2url 14 | from urllib2 import Request, urlopen, quote 15 | 16 | class PutRequest(Request): 17 | def __init__(self, *args, **kwargs): 18 | self.get_method = lambda: u'PUT' 19 | Request.__init__(self, *args, **kwargs) 20 | 21 | class DeleteRequest(Request): 22 | def __init__(self, *args, **kwargs): 23 | self.get_method = lambda: u'DELETE' 24 | Request.__init__(self, *args, **kwargs) 25 | 26 | class Connection: 27 | def __init__(self, webapi): 28 | self.webapi = webapi 29 | self.headers = {'Accept': 'text/plain'} 30 | 31 | def _get_headers(self, f, size=None): 32 | ''' 33 | Retrieve length of string or file object and prepare HTTP headers. 34 | ''' 35 | if isinstance(f, basestring): 36 | # Just set up content length 37 | size = len(f) 38 | elif getattr(f, 'read', None): 39 | if size == None: 40 | # When size is already known, skip this 41 | f.seek(0, SEEK_END) 42 | size = f.tell() 43 | f.seek(0) 44 | else: 45 | raise errors.UnsupportedError("Cannot handle type %s" % type(f)) 46 | 47 | headers = {'Content-Length': size} 48 | headers.update(self.headers) 49 | return headers 50 | 51 | def _urlencode(self, data): 52 | _data = {} 53 | for k, v in data.items(): 54 | _data[k.encode('utf-8')] = v.encode('utf-8') 55 | return urlencode(_data) 56 | 57 | def _quotepath(self, path, params={}): 58 | q = quote(path.encode('utf-8'), safe='/') 59 | if params: 60 | return u"%s?%s" % (q, self._urlencode(params)) 61 | return q 62 | 63 | def _urlopen(self, req): 64 | try: 65 | return urlopen(req) 66 | except Exception, e: 67 | if not getattr(e, 'getcode', None): 68 | raise errors.RemoteConnectionError(str(e)) 69 | code = e.getcode() 70 | if code == 500: 71 | # Probably out of space or unhappiness error 72 | raise errors.StorageSpaceError(e.fp.read()) 73 | elif code in (400, 404, 410): 74 | # Standard not found 75 | raise errors.ResourceNotFoundError(e.fp.read()) 76 | raise errors.ResourceInvalidError(e.fp.read()) 77 | 78 | def post(self, path, data={}, params={}): 79 | data = self._urlencode(data) 80 | path = self._quotepath(path, params) 81 | req = Request(''.join([self.webapi, path]), data, headers=self.headers) 82 | return self._urlopen(req) 83 | 84 | def get(self, path, data={}, offset=None, length=None): 85 | data = self._urlencode(data) 86 | path = self._quotepath(path) 87 | if data: 88 | path = u'?'.join([path, data]) 89 | 90 | headers = {} 91 | headers.update(self.headers) 92 | if offset: 93 | if length: 94 | headers['Range'] = 'bytes=%d-%d' % \ 95 | (int(offset), int(offset+length)) 96 | else: 97 | headers['Range'] = 'bytes=%d-' % int(offset) 98 | 99 | req = Request(''.join([self.webapi, path]), headers=headers) 100 | return self._urlopen(req) 101 | 102 | def put(self, path, data, size=None, params={}): 103 | path = self._quotepath(path, params) 104 | headers = self._get_headers(data, size=size) 105 | req = PutRequest(''.join([self.webapi, path]), data, headers=headers) 106 | return self._urlopen(req) 107 | 108 | def delete(self, path, data={}): 109 | path = self._quotepath(path) 110 | req = DeleteRequest(''.join([self.webapi, path]), data, headers=self.headers) 111 | return self._urlopen(req) 112 | -------------------------------------------------------------------------------- /fs/contrib/tahoelafs/test_tahoelafs.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | """ 3 | Test the TahoeLAFS 4 | 5 | @author: Marek Palatinus 6 | """ 7 | 8 | import sys 9 | import logging 10 | import unittest 11 | 12 | from fs.base import FS 13 | import fs.errors as errors 14 | from fs.tests import FSTestCases, ThreadingTestCases 15 | from fs.contrib.tahoelafs import TahoeLAFS, Connection 16 | 17 | logging.getLogger().setLevel(logging.DEBUG) 18 | logging.getLogger('fs.tahoelafs').addHandler(logging.StreamHandler(sys.stdout)) 19 | 20 | WEBAPI = 'http://insecure.tahoe-lafs.org' 21 | 22 | 23 | # The public grid is too slow for threading testcases, disabling for now... 24 | class TestTahoeLAFS(unittest.TestCase,FSTestCases):#,ThreadingTestCases): 25 | 26 | # Disabled by default because it takes a *really* long time. 27 | __test__ = False 28 | 29 | def setUp(self): 30 | self.dircap = TahoeLAFS.createdircap(WEBAPI) 31 | self.fs = TahoeLAFS(self.dircap, cache_timeout=0, webapi=WEBAPI) 32 | 33 | def tearDown(self): 34 | self.fs.close() 35 | 36 | def test_dircap(self): 37 | # Is dircap in correct format? 38 | self.assert_(self.dircap.startswith('URI:DIR2:') and len(self.dircap) > 50) 39 | 40 | def test_concurrent_copydir(self): 41 | # makedir() on TahoeLAFS is currently not atomic 42 | pass 43 | 44 | def test_makedir_winner(self): 45 | # makedir() on TahoeLAFS is currently not atomic 46 | pass 47 | 48 | def test_big_file(self): 49 | pass 50 | 51 | if __name__ == '__main__': 52 | unittest.main() 53 | -------------------------------------------------------------------------------- /fs/contrib/tahoelafs/util.py: -------------------------------------------------------------------------------- 1 | ''' 2 | Created on 25.9.2010 3 | 4 | @author: marekp 5 | ''' 6 | 7 | import sys 8 | import platform 9 | import stat as statinfo 10 | 11 | import fs.errors as errors 12 | from fs.path import pathsplit 13 | try: 14 | # For non-CPython or older CPython versions. 15 | # Simplejson also comes with C speedup module which 16 | # is not in standard CPython >=2.6 library. 17 | import simplejson as json 18 | except ImportError: 19 | try: 20 | import json 21 | except ImportError: 22 | print "simplejson (http://pypi.python.org/pypi/simplejson/) required" 23 | raise 24 | 25 | from .connection import Connection 26 | 27 | python3 = int(platform.python_version_tuple()[0]) > 2 28 | 29 | if python3: 30 | from urllib.error import HTTPError 31 | else: 32 | from urllib2 import HTTPError 33 | 34 | class TahoeUtil: 35 | def __init__(self, webapi): 36 | self.connection = Connection(webapi) 37 | 38 | def createdircap(self): 39 | return self.connection.post(u'/uri', params={u't': u'mkdir'}).read() 40 | 41 | def unlink(self, dircap, path=None): 42 | path = self.fixwinpath(path, False) 43 | self.connection.delete(u'/uri/%s%s' % (dircap, path)) 44 | 45 | def info(self, dircap, path): 46 | path = self.fixwinpath(path, False) 47 | meta = json.load(self.connection.get(u'/uri/%s%s' % (dircap, path), {u't': u'json'})) 48 | return self._info(path, meta) 49 | 50 | def fixwinpath(self, path, direction=True): 51 | ''' 52 | No, Tahoe really does not support file streams... 53 | This is ugly hack, because it is not Tahoe-specific. 54 | Should be move to middleware if will be any. 55 | ''' 56 | if platform.system() != 'Windows': 57 | return path 58 | 59 | if direction and ':' in path: 60 | path = path.replace(':', '__colon__') 61 | elif not direction and '__colon__' in path: 62 | path = path.replace('__colon__', ':') 63 | return path 64 | 65 | def _info(self, path, data): 66 | if isinstance(data, list): 67 | type = data[0] 68 | data = data[1] 69 | elif isinstance(data, dict): 70 | type = data['type'] 71 | else: 72 | raise errors.ResourceInvalidError('Metadata in unknown format!') 73 | 74 | if type == 'unknown': 75 | raise errors.ResourceNotFoundError(path) 76 | 77 | info = {'name': unicode(self.fixwinpath(path, True)), 78 | 'type': type, 79 | 'size': data.get('size', 0), 80 | 'ctime': None, 81 | 'uri': data.get('rw_uri', data.get('ro_uri'))} 82 | if 'metadata' in data: 83 | info['ctime'] = data['metadata'].get('ctime') 84 | 85 | if info['type'] == 'dirnode': 86 | info['st_mode'] = 0777 | statinfo.S_IFDIR 87 | else: 88 | info['st_mode'] = 0644 89 | 90 | return info 91 | 92 | def list(self, dircap, path=None): 93 | path = self.fixwinpath(path, False) 94 | 95 | data = json.load(self.connection.get(u'/uri/%s%s' % (dircap, path), {u't': u'json'})) 96 | 97 | if len(data) < 2 or data[0] != 'dirnode': 98 | raise errors.ResourceInvalidError('Metadata in unknown format!') 99 | 100 | data = data[1]['children'] 101 | for i in data.keys(): 102 | x = self._info(i, data[i]) 103 | yield x 104 | 105 | def mkdir(self, dircap, path): 106 | path = self.fixwinpath(path, False) 107 | path = pathsplit(path) 108 | 109 | self.connection.post(u"/uri/%s%s" % (dircap, path[0]), data={u't': u'mkdir', u'name': path[1]}) 110 | 111 | def move(self, dircap, src, dst): 112 | if src == '/' or dst == '/': 113 | raise errors.UnsupportedError("Too dangerous operation, aborting") 114 | 115 | src = self.fixwinpath(src, False) 116 | dst = self.fixwinpath(dst, False) 117 | 118 | src_tuple = pathsplit(src) 119 | dst_tuple = pathsplit(dst) 120 | 121 | if src_tuple[0] == dst_tuple[0]: 122 | # Move inside one directory 123 | self.connection.post(u"/uri/%s%s" % (dircap, src_tuple[0]), data={u't': u'rename', 124 | u'from_name': src_tuple[1], u'to_name': dst_tuple[1]}) 125 | return 126 | 127 | # Move to different directory. Firstly create link on dst, then remove from src 128 | try: 129 | self.info(dircap, dst) 130 | except errors.ResourceNotFoundError: 131 | pass 132 | else: 133 | self.unlink(dircap, dst) 134 | 135 | uri = self.info(dircap, src)['uri'] 136 | self.connection.put(u"/uri/%s%s" % (dircap, dst), data=uri, params={u't': u'uri'}) 137 | if uri != self.info(dircap, dst)['uri']: 138 | raise errors.OperationFailedError('Move failed') 139 | 140 | self.unlink(dircap, src) 141 | -------------------------------------------------------------------------------- /fs/expose/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PyFilesystem/pyfilesystem/7dfe14ae6c3b9c53543c1c3890232d9f37579f34/fs/expose/__init__.py -------------------------------------------------------------------------------- /fs/expose/django_storage.py: -------------------------------------------------------------------------------- 1 | """ 2 | fs.expose.django 3 | ================ 4 | 5 | Use an FS object for Django File Storage 6 | 7 | This module exposes the class "FSStorage", a simple adapter for using FS 8 | objects as Django storage objects. Simply include the following lines 9 | in your settings.py:: 10 | 11 | DEFAULT_FILE_STORAGE = fs.expose.django_storage.FSStorage 12 | DEFAULT_FILE_STORAGE_FS = OSFS('foo/bar') # Or whatever FS 13 | 14 | 15 | """ 16 | 17 | from django.conf import settings 18 | from django.core.files.storage import Storage 19 | from django.core.files import File 20 | 21 | from fs.path import abspath, dirname 22 | from fs.errors import convert_fs_errors, ResourceNotFoundError 23 | 24 | class FSStorage(Storage): 25 | """Expose an FS object as a Django File Storage object.""" 26 | 27 | def __init__(self, fs=None, base_url=None): 28 | """ 29 | :param fs: an FS object 30 | :param base_url: The url to prepend to the path 31 | 32 | """ 33 | if fs is None: 34 | fs = settings.DEFAULT_FILE_STORAGE_FS 35 | if base_url is None: 36 | base_url = settings.MEDIA_URL 37 | base_url = base_url.rstrip('/') 38 | self.fs = fs 39 | self.base_url = base_url 40 | 41 | def exists(self, name): 42 | return self.fs.isfile(name) 43 | 44 | def path(self, name): 45 | path = self.fs.getsyspath(name) 46 | if path is None: 47 | raise NotImplementedError 48 | return path 49 | 50 | @convert_fs_errors 51 | def size(self, name): 52 | return self.fs.getsize(name) 53 | 54 | @convert_fs_errors 55 | def url(self, name): 56 | return self.base_url + abspath(name) 57 | 58 | @convert_fs_errors 59 | def _open(self, name, mode): 60 | return File(self.fs.open(name, mode)) 61 | 62 | @convert_fs_errors 63 | def _save(self, name, content): 64 | self.fs.makedir(dirname(name), allow_recreate=True, recursive=True) 65 | self.fs.setcontents(name, content) 66 | return name 67 | 68 | @convert_fs_errors 69 | def delete(self, name): 70 | try: 71 | self.fs.remove(name) 72 | except ResourceNotFoundError: 73 | pass 74 | 75 | 76 | -------------------------------------------------------------------------------- /fs/expose/http.py: -------------------------------------------------------------------------------- 1 | __all__ = ["serve_fs"] 2 | 3 | import SimpleHTTPServer 4 | import SocketServer 5 | from fs.path import pathjoin, dirname 6 | from fs.errors import FSError 7 | from time import mktime 8 | from cStringIO import StringIO 9 | import cgi 10 | import urllib 11 | import posixpath 12 | import time 13 | import threading 14 | import socket 15 | 16 | def _datetime_to_epoch(d): 17 | return mktime(d.timetuple()) 18 | 19 | class FSHTTPRequestHandler(SimpleHTTPServer.SimpleHTTPRequestHandler): 20 | 21 | """A hacked together version of SimpleHTTPRequestHandler""" 22 | 23 | def __init__(self, fs, request, client_address, server): 24 | self._fs = fs 25 | SimpleHTTPServer.SimpleHTTPRequestHandler.__init__(self, request, client_address, server) 26 | 27 | def do_GET(self): 28 | """Serve a GET request.""" 29 | f = None 30 | try: 31 | f = self.send_head() 32 | if f: 33 | try: 34 | self.copyfile(f, self.wfile) 35 | except socket.error: 36 | pass 37 | finally: 38 | if f is not None: 39 | f.close() 40 | 41 | def send_head(self): 42 | """Common code for GET and HEAD commands. 43 | 44 | This sends the response code and MIME headers. 45 | 46 | Return value is either a file object (which has to be copied 47 | to the outputfile by the caller unless the command was HEAD, 48 | and must be closed by the caller under all circumstances), or 49 | None, in which case the caller has nothing further to do. 50 | 51 | """ 52 | path = self.translate_path(self.path) 53 | f = None 54 | if self._fs.isdir(path): 55 | if not self.path.endswith('/'): 56 | # redirect browser - doing basically what apache does 57 | self.send_response(301) 58 | self.send_header("Location", self.path + "/") 59 | self.end_headers() 60 | return None 61 | for index in ("index.html", "index.htm"): 62 | index = pathjoin(path, index) 63 | if self._fs.exists(index): 64 | path = index 65 | break 66 | else: 67 | return self.list_directory(path) 68 | ctype = self.guess_type(path) 69 | try: 70 | info = self._fs.getinfo(path) 71 | f = self._fs.open(path, 'rb') 72 | except FSError, e: 73 | self.send_error(404, str(e)) 74 | return None 75 | self.send_response(200) 76 | self.send_header("Content-type", ctype) 77 | self.send_header("Content-Length", str(info['size'])) 78 | if 'modified_time' in info: 79 | self.send_header("Last-Modified", self.date_time_string(_datetime_to_epoch(info['modified_time']))) 80 | self.end_headers() 81 | return f 82 | 83 | 84 | def list_directory(self, path): 85 | """Helper to produce a directory listing (absent index.html). 86 | 87 | Return value is either a file object, or None (indicating an 88 | error). In either case, the headers are sent, making the 89 | interface the same as for send_head(). 90 | 91 | """ 92 | try: 93 | dir_paths = self._fs.listdir(path, dirs_only=True) 94 | file_paths = self._fs.listdir(path, files_only=True) 95 | except FSError: 96 | self.send_error(404, "No permission to list directory") 97 | return None 98 | paths = [p+'/' for p in sorted(dir_paths, key=lambda p:p.lower())] + sorted(file_paths, key=lambda p:p.lower()) 99 | #list.sort(key=lambda a: a.lower()) 100 | f = StringIO() 101 | displaypath = cgi.escape(urllib.unquote(self.path)) 102 | f.write('') 103 | f.write("\nDirectory listing for %s\n" % displaypath) 104 | f.write("\n

Directory listing for %s

\n" % displaypath) 105 | f.write("
\n
    \n") 106 | 107 | parent = dirname(path) 108 | if path != parent: 109 | f.write('
  • ../
  • ' % urllib.quote(parent.rstrip('/') + '/')) 110 | 111 | for path in paths: 112 | f.write('
  • %s\n' 113 | % (urllib.quote(path), cgi.escape(path))) 114 | f.write("
\n
\n\n\n") 115 | length = f.tell() 116 | f.seek(0) 117 | self.send_response(200) 118 | self.send_header("Content-type", "text/html") 119 | self.send_header("Content-Length", str(length)) 120 | self.end_headers() 121 | return f 122 | 123 | def translate_path(self, path): 124 | # abandon query parameters 125 | path = path.split('?',1)[0] 126 | path = path.split('#',1)[0] 127 | path = posixpath.normpath(urllib.unquote(path)) 128 | return path 129 | 130 | 131 | def serve_fs(fs, address='', port=8000): 132 | 133 | """Serve an FS instance over http 134 | 135 | :param fs: an FS object 136 | :param address: IP address to serve on 137 | :param port: port number 138 | 139 | """ 140 | 141 | def Handler(request, client_address, server): 142 | return FSHTTPRequestHandler(fs, request, client_address, server) 143 | 144 | #class ThreadedTCPServer(SocketServer.ThreadingMixIn, SocketServer.TCPServer): 145 | # pass 146 | httpd = SocketServer.TCPServer((address, port), Handler, bind_and_activate=False) 147 | #httpd = ThreadedTCPServer((address, port), Handler, bind_and_activate=False) 148 | httpd.allow_reuse_address = True 149 | httpd.server_bind() 150 | httpd.server_activate() 151 | 152 | server_thread = threading.Thread(target=httpd.serve_forever) 153 | server_thread.start() 154 | try: 155 | while True: 156 | time.sleep(0.1) 157 | except (KeyboardInterrupt, SystemExit): 158 | httpd.shutdown() 159 | 160 | if __name__ == "__main__": 161 | 162 | from fs.osfs import OSFS 163 | serve_fs(OSFS('~/')) -------------------------------------------------------------------------------- /fs/expose/serve/__init__.py: -------------------------------------------------------------------------------- 1 | # Work in progress -------------------------------------------------------------------------------- /fs/expose/serve/server.py: -------------------------------------------------------------------------------- 1 | from __future__ import with_statement 2 | 3 | import socket 4 | import threading 5 | from packetstream import JSONDecoder, JSONFileEncoder 6 | 7 | 8 | 9 | class _SocketFile(object): 10 | def __init__(self, socket): 11 | self.socket = socket 12 | 13 | def read(self, size): 14 | try: 15 | return self.socket.recv(size) 16 | except socket.error: 17 | return '' 18 | 19 | def write(self, data): 20 | self.socket.sendall(data) 21 | 22 | 23 | def remote_call(method_name=None): 24 | method = method_name 25 | def deco(f): 26 | if not hasattr(f, '_remote_call_names'): 27 | f._remote_call_names = [] 28 | f._remote_call_names.append(method or f.__name__) 29 | return f 30 | return deco 31 | 32 | 33 | class RemoteResponse(Exception): 34 | def __init__(self, header, payload): 35 | self.header = header 36 | self.payload = payload 37 | 38 | class ConnectionHandlerBase(threading.Thread): 39 | 40 | _methods = {} 41 | 42 | def __init__(self, server, connection_id, socket, address): 43 | super(ConnectionHandlerBase, self).__init__() 44 | self.server = server 45 | self.connection_id = connection_id 46 | self.socket = socket 47 | self.transport = _SocketFile(socket) 48 | self.address = address 49 | self.encoder = JSONFileEncoder(self.transport) 50 | self.decoder = JSONDecoder(prelude_callback=self.on_stream_prelude) 51 | 52 | self._lock = threading.RLock() 53 | self.socket_error = None 54 | 55 | if not self._methods: 56 | for method_name in dir(self): 57 | method = getattr(self, method_name) 58 | if callable(method) and hasattr(method, '_remote_call_names'): 59 | for name in method._remote_call_names: 60 | 61 | self._methods[name] = method 62 | 63 | print self._methods 64 | 65 | self.fs = None 66 | 67 | def run(self): 68 | self.transport.write('pyfs/1.0\n') 69 | while True: 70 | try: 71 | data = self.transport.read(4096) 72 | except socket.error, socket_error: 73 | print socket_error 74 | self.socket_error = socket_error 75 | break 76 | print "data", repr(data) 77 | if data: 78 | for packet in self.decoder.feed(data): 79 | print repr(packet) 80 | self.on_packet(*packet) 81 | else: 82 | break 83 | self.on_connection_close() 84 | 85 | def close(self): 86 | with self._lock: 87 | self.socket.close() 88 | 89 | def on_connection_close(self): 90 | self.socket.shutdown(socket.SHUT_RDWR) 91 | self.socket.close() 92 | self.server.on_connection_close(self.connection_id) 93 | 94 | def on_stream_prelude(self, packet_stream, prelude): 95 | print "prelude", prelude 96 | return True 97 | 98 | def on_packet(self, header, payload): 99 | print '-' * 30 100 | print repr(header) 101 | print repr(payload) 102 | if header['type'] == 'rpc': 103 | method = header['method'] 104 | args = header['args'] 105 | kwargs = header['kwargs'] 106 | method_callable = self._methods[method] 107 | remote = dict(type='rpcresult', 108 | client_ref = header['client_ref']) 109 | try: 110 | response = method_callable(*args, **kwargs) 111 | remote['response'] = response 112 | self.encoder.write(remote, '') 113 | except RemoteResponse, response: 114 | self.encoder.write(response.header, response.payload) 115 | 116 | class RemoteFSConnection(ConnectionHandlerBase): 117 | 118 | @remote_call() 119 | def auth(self, username, password, resource): 120 | self.username = username 121 | self.password = password 122 | self.resource = resource 123 | from fs.memoryfs import MemoryFS 124 | self.fs = MemoryFS() 125 | 126 | class Server(object): 127 | 128 | def __init__(self, addr='', port=3000, connection_factory=RemoteFSConnection): 129 | self.addr = addr 130 | self.port = port 131 | self.connection_factory = connection_factory 132 | self.socket = None 133 | self.connection_id = 0 134 | self.threads = {} 135 | self._lock = threading.RLock() 136 | 137 | def serve_forever(self): 138 | sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) 139 | sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) 140 | sock.bind((self.addr, self.port)) 141 | 142 | sock.listen(5) 143 | 144 | try: 145 | while True: 146 | clientsocket, address = sock.accept() 147 | self.on_connect(clientsocket, address) 148 | except KeyboardInterrupt: 149 | pass 150 | 151 | try: 152 | self._close_graceful() 153 | except KeyboardInterrupt: 154 | self._close_harsh() 155 | 156 | def _close_graceful(self): 157 | """Tell all threads to exit and wait for them""" 158 | with self._lock: 159 | for connection in self.threads.itervalues(): 160 | connection.close() 161 | for connection in self.threads.itervalues(): 162 | connection.join() 163 | self.threads.clear() 164 | 165 | def _close_harsh(self): 166 | with self._lock: 167 | for connection in self.threads.itervalues(): 168 | connection.close() 169 | self.threads.clear() 170 | 171 | def on_connect(self, clientsocket, address): 172 | print "Connection from", address 173 | with self._lock: 174 | self.connection_id += 1 175 | thread = self.connection_factory(self, 176 | self.connection_id, 177 | clientsocket, 178 | address) 179 | self.threads[self.connection_id] = thread 180 | thread.start() 181 | 182 | def on_connection_close(self, connection_id): 183 | pass 184 | #with self._lock: 185 | # self.threads[connection_id].join() 186 | # del self.threads[connection_id] 187 | 188 | if __name__ == "__main__": 189 | server = Server() 190 | server.serve_forever() 191 | 192 | -------------------------------------------------------------------------------- /fs/expose/serve/threadpool.py: -------------------------------------------------------------------------------- 1 | import threading 2 | import Queue as queue 3 | 4 | def make_job(job_callable, *args, **kwargs): 5 | """ Returns a callable that calls the supplied callable with given arguements. """ 6 | def job(): 7 | return job_callable(*args, **kwargs) 8 | return job 9 | 10 | 11 | class _PoolThread(threading.Thread): 12 | """ Internal thread class that runs jobs. """ 13 | 14 | def __init__(self, queue, name): 15 | super(_PoolThread, self).__init__() 16 | self.queue = queue 17 | self.name = name 18 | 19 | def __str__(self): 20 | return self.name 21 | 22 | def run(self): 23 | 24 | while True: 25 | try: 26 | _priority, job = self.queue.get() 27 | except queue.Empty: 28 | break 29 | 30 | if job is None: 31 | break 32 | 33 | if callable(job): 34 | try: 35 | job() 36 | except Exception, e: 37 | print e 38 | self.queue.task_done() 39 | 40 | 41 | class ThreadPool(object): 42 | 43 | def __init__(self, num_threads, size=None, name=''): 44 | 45 | self.num_threads = num_threads 46 | self.name = name 47 | self.queue = queue.PriorityQueue(size) 48 | self.job_no = 0 49 | 50 | self.threads = [_PoolThread(self.queue, '%s #%i' % (name, i)) for i in xrange(num_threads)] 51 | 52 | for thread in self.threads: 53 | thread.start() 54 | 55 | def _make_priority_key(self, i): 56 | no = self.job_no 57 | self.job_no += 1 58 | return (i, no) 59 | 60 | def job(self, job_callable, *args, **kwargs): 61 | """ Post a job to the queue. """ 62 | def job(): 63 | return job_callable(*args, **kwargs) 64 | self.queue.put( (self._make_priority_key(1), job), True ) 65 | return self.job_no 66 | 67 | def flush_quit(self): 68 | """ Quit after all tasks on the queue have been processed. """ 69 | for thread in self.threads: 70 | self.queue.put( (self._make_priority_key(1), None) ) 71 | for thread in self.threads: 72 | thread.join() 73 | 74 | def quit(self): 75 | """ Quit as soon as possible, potentially leaving tasks on the queue. """ 76 | for thread in self.threads: 77 | self.queue.put( (self._make_priority_key(0), None) ) 78 | for thread in self.threads: 79 | thread.join() 80 | 81 | 82 | if __name__ == "__main__": 83 | import time 84 | 85 | 86 | def job(n): 87 | print "Starting #%i" % n 88 | time.sleep(1) 89 | print "Ending #%i" % n 90 | 91 | pool = ThreadPool(5, 'test thread') 92 | 93 | for n in range(20): 94 | pool.job(job, n) 95 | 96 | pool.flush_quit() 97 | 98 | 99 | 100 | -------------------------------------------------------------------------------- /fs/expose/wsgi/__init__.py: -------------------------------------------------------------------------------- 1 | from wsgi import serve_fs 2 | -------------------------------------------------------------------------------- /fs/expose/wsgi/dirtemplate.py: -------------------------------------------------------------------------------- 1 | template = """ 2 | 3 | 4 | 5 | ${path} 6 | 53 | 54 | 55 | 56 | 57 | 58 |
59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | % for i, entry in enumerate(dirlist): 71 | 72 | 73 | 74 | 75 | 76 | % endfor 77 | 78 | 79 |
File/DirectorySizeCreated Date
${entry['name']}${entry['size']}${entry['created_time']}
80 | 81 |
82 | 83 | 84 | 85 | 86 | 87 | """ -------------------------------------------------------------------------------- /fs/expose/wsgi/serve_home.py: -------------------------------------------------------------------------------- 1 | from wsgiref.simple_server import make_server 2 | 3 | from fs.osfs import OSFS 4 | from wsgi import serve_fs 5 | osfs = OSFS('~/') 6 | application = serve_fs(osfs) 7 | 8 | httpd = make_server('', 8000, application) 9 | print "Serving on http://127.0.0.1:8000" 10 | httpd.serve_forever() 11 | -------------------------------------------------------------------------------- /fs/expose/wsgi/wsgi.py: -------------------------------------------------------------------------------- 1 | 2 | import urlparse 3 | import mimetypes 4 | 5 | from fs.errors import FSError 6 | from fs.path import basename, pathsplit 7 | 8 | from datetime import datetime 9 | 10 | try: 11 | from mako.template import Template 12 | except ImportError: 13 | print "Requires mako templates http://www.makotemplates.org/" 14 | raise 15 | 16 | 17 | class Request(object): 18 | """Very simple request object""" 19 | def __init__(self, environ, start_response): 20 | self.environ = environ 21 | self.start_response = start_response 22 | self.path = environ.get('PATH_INFO') 23 | 24 | 25 | class WSGIServer(object): 26 | """Light-weight WSGI server that exposes an FS""" 27 | 28 | def __init__(self, serve_fs, indexes=True, dir_template=None, chunk_size=16*1024*1024): 29 | 30 | if dir_template is None: 31 | from dirtemplate import template as dir_template 32 | 33 | self.serve_fs = serve_fs 34 | self.indexes = indexes 35 | self.chunk_size = chunk_size 36 | 37 | self.dir_template = Template(dir_template) 38 | 39 | def __call__(self, environ, start_response): 40 | 41 | request = Request(environ, start_response) 42 | 43 | if not self.serve_fs.exists(request.path): 44 | return self.serve_404(request) 45 | 46 | if self.serve_fs.isdir(request.path): 47 | if not self.indexes: 48 | return self.serve_404(request) 49 | return self.serve_dir(request) 50 | else: 51 | return self.serve_file(request) 52 | 53 | 54 | def serve_file(self, request): 55 | """Serve a file, guessing a mime-type""" 56 | path = request.path 57 | serving_file = None 58 | try: 59 | serving_file = self.serve_fs.open(path, 'rb') 60 | except Exception, e: 61 | if serving_file is not None: 62 | serving_file.close() 63 | return self.serve_500(request, str(e)) 64 | 65 | mime_type = mimetypes.guess_type(basename(path))[0] or b'text/plain' 66 | file_size = self.serve_fs.getsize(path) 67 | headers = [(b'Content-Type', bytes(mime_type)), 68 | (b'Content-Length', bytes(file_size))] 69 | 70 | def gen_file(): 71 | chunk_size = self.chunk_size 72 | read = serving_file.read 73 | try: 74 | while 1: 75 | data = read(chunk_size) 76 | if not data: 77 | break 78 | yield data 79 | finally: 80 | serving_file.close() 81 | 82 | request.start_response(b'200 OK', 83 | headers) 84 | return gen_file() 85 | 86 | def serve_dir(self, request): 87 | """Serve an index page""" 88 | fs = self.serve_fs 89 | isdir = fs.isdir 90 | path = request.path 91 | dirinfo = fs.listdirinfo(path, full=True, absolute=True) 92 | entries = [] 93 | 94 | for p, info in dirinfo: 95 | entry = {} 96 | entry['path'] = p 97 | entry['name'] = basename(p) 98 | entry['size'] = info.get('size', 'unknown') 99 | entry['created_time'] = info.get('created_time') 100 | if isdir(p): 101 | entry['type'] = 'dir' 102 | else: 103 | entry['type'] = 'file' 104 | 105 | entries.append(entry) 106 | 107 | # Put dirs first, and sort by reverse created time order 108 | no_time = datetime(1970, 1, 1, 1, 0) 109 | entries.sort(key=lambda k:(k['type'] == 'dir', k.get('created_time') or no_time), reverse=True) 110 | 111 | # Turn datetime to text and tweak names 112 | for entry in entries: 113 | t = entry.get('created_time') 114 | if t and hasattr(t, 'ctime'): 115 | entry['created_time'] = t.ctime() 116 | if entry['type'] == 'dir': 117 | entry['name'] += '/' 118 | 119 | # Add an up dir link for non-root 120 | if path not in ('', '/'): 121 | entries.insert(0, dict(name='../', path='../', type="dir", size='', created_time='..')) 122 | 123 | # Render the mako template 124 | html = self.dir_template.render(**dict(fs=self.serve_fs, 125 | path=path, 126 | dirlist=entries)).encode('utf-8') 127 | request.start_response(b'200 OK', [(b'Content-Type', b'text/html'), 128 | (b'Content-Length', b'%i' % len(html))]) 129 | 130 | return [html] 131 | 132 | 133 | def serve_404(self, request, msg='Not found'): 134 | """Serves a Not found page""" 135 | request.start_response(b'404 NOT FOUND', [(b'Content-Type', b'text/html')]) 136 | return [msg] 137 | 138 | def serve_500(self, request, msg='Unable to complete request'): 139 | """Serves an internal server error page""" 140 | request.start_response(b'500 INTERNAL SERVER ERROR', [(b'Content-Type', b'text/html')]) 141 | return [msg] 142 | 143 | 144 | def serve_fs(fs, indexes=True): 145 | """Serves an FS object via WSGI""" 146 | application = WSGIServer(fs, indexes) 147 | return application 148 | 149 | -------------------------------------------------------------------------------- /fs/httpfs.py: -------------------------------------------------------------------------------- 1 | """ 2 | fs.httpfs 3 | ========= 4 | 5 | 6 | """ 7 | 8 | from fs.base import FS 9 | from fs.path import normpath 10 | from fs.errors import ResourceNotFoundError, UnsupportedError 11 | from fs.filelike import FileWrapper 12 | from fs import iotools 13 | 14 | from urllib2 import urlopen, URLError 15 | from datetime import datetime 16 | 17 | 18 | class HTTPFS(FS): 19 | 20 | """Can barely be called a filesystem, because HTTP servers generally don't support 21 | typical filesystem functionality. This class exists to allow the :doc:`opener` system 22 | to read files over HTTP. 23 | 24 | If you do need filesystem like functionality over HTTP, see :mod:`~fs.contrib.davfs`. 25 | 26 | """ 27 | 28 | _meta = {'read_only': True, 29 | 'network': True} 30 | 31 | def __init__(self, url): 32 | """ 33 | 34 | :param url: The base URL 35 | 36 | """ 37 | self.root_url = url 38 | 39 | def _make_url(self, path): 40 | path = normpath(path) 41 | url = '%s/%s' % (self.root_url.rstrip('/'), path.lstrip('/')) 42 | return url 43 | 44 | @iotools.filelike_to_stream 45 | def open(self, path, mode='r', buffering=-1, encoding=None, errors=None, newline=None, line_buffering=False, **kwargs): 46 | 47 | if '+' in mode or 'w' in mode or 'a' in mode: 48 | raise UnsupportedError('write') 49 | 50 | url = self._make_url(path) 51 | try: 52 | f = urlopen(url) 53 | except URLError, e: 54 | raise ResourceNotFoundError(path, details=e) 55 | except OSError, e: 56 | raise ResourceNotFoundError(path, details=e) 57 | 58 | return FileWrapper(f) 59 | 60 | def exists(self, path): 61 | return self.isfile(path) 62 | 63 | def isdir(self, path): 64 | return False 65 | 66 | def isfile(self, path): 67 | url = self._make_url(path) 68 | f = None 69 | try: 70 | try: 71 | f = urlopen(url) 72 | except (URLError, OSError): 73 | return False 74 | finally: 75 | if f is not None: 76 | f.close() 77 | 78 | return True 79 | 80 | def listdir(self, path="./", 81 | wildcard=None, 82 | full=False, 83 | absolute=False, 84 | dirs_only=False, 85 | files_only=False): 86 | return [] 87 | 88 | def getinfo(self, path): 89 | url = self._make_url(path) 90 | info = urlopen(url).info().dict 91 | if 'content-length' in info: 92 | info['size'] = info['content-length'] 93 | if 'last-modified' in info: 94 | info['modified_time'] = datetime.strptime(info['last-modified'], 95 | "%a, %d %b %Y %H:%M:%S %Z") 96 | return info 97 | -------------------------------------------------------------------------------- /fs/local_functools.py: -------------------------------------------------------------------------------- 1 | """ 2 | 3 | A version of functools.wraps for Python versions that don't support it. 4 | 5 | Note that this module can't be named "functools" because it would shadow the 6 | stdlib module that it tries to emulate. Absolute imports would fix this 7 | problem but are only availabe from Python 2.5. 8 | 9 | """ 10 | 11 | try: 12 | from functools import wraps as wraps 13 | except ImportError: 14 | wraps = lambda f: lambda f: f 15 | -------------------------------------------------------------------------------- /fs/osfs/watch.py: -------------------------------------------------------------------------------- 1 | """ 2 | fs.osfs.watch 3 | ============= 4 | 5 | Change watcher support for OSFS 6 | 7 | """ 8 | 9 | import os 10 | import sys 11 | import errno 12 | import threading 13 | 14 | from fs.errors import * 15 | from fs.path import * 16 | from fs.watch import * 17 | 18 | OSFSWatchMixin = None 19 | 20 | # Try using native implementation on win32 21 | if sys.platform == "win32": 22 | try: 23 | from fs.osfs.watch_win32 import OSFSWatchMixin 24 | except ImportError: 25 | pass 26 | 27 | # Try using pyinotify if available 28 | if OSFSWatchMixin is None: 29 | try: 30 | from fs.osfs.watch_inotify import OSFSWatchMixin 31 | except ImportError: 32 | pass 33 | 34 | # Fall back to raising UnsupportedError 35 | if OSFSWatchMixin is None: 36 | class OSFSWatchMixin(object): 37 | def __init__(self, *args, **kwargs): 38 | super(OSFSWatchMixin, self).__init__(*args, **kwargs) 39 | def add_watcher(self,*args,**kwds): 40 | raise UnsupportedError 41 | def del_watcher(self,watcher_or_callback): 42 | raise UnsupportedError 43 | 44 | 45 | -------------------------------------------------------------------------------- /fs/osfs/xattrs.py: -------------------------------------------------------------------------------- 1 | """ 2 | fs.osfs.xattrs 3 | ============== 4 | 5 | Extended-attribute support for OSFS 6 | 7 | """ 8 | 9 | 10 | import os 11 | import sys 12 | import errno 13 | 14 | from fs.errors import * 15 | from fs.path import * 16 | from fs.base import FS 17 | 18 | try: 19 | import xattr 20 | except ImportError: 21 | xattr = None 22 | 23 | 24 | if xattr is not None: 25 | 26 | class OSFSXAttrMixin(object): 27 | """Mixin providing extended-attribute support via the 'xattr' module""" 28 | 29 | def __init__(self, *args, **kwargs): 30 | super(OSFSXAttrMixin, self).__init__(*args, **kwargs) 31 | 32 | @convert_os_errors 33 | def setxattr(self, path, key, value): 34 | xattr.xattr(self.getsyspath(path))[key]=value 35 | 36 | @convert_os_errors 37 | def getxattr(self, path, key, default=None): 38 | try: 39 | return xattr.xattr(self.getsyspath(path)).get(key) 40 | except KeyError: 41 | return default 42 | 43 | @convert_os_errors 44 | def delxattr(self, path, key): 45 | try: 46 | del xattr.xattr(self.getsyspath(path))[key] 47 | except KeyError: 48 | pass 49 | 50 | @convert_os_errors 51 | def listxattrs(self, path): 52 | return xattr.xattr(self.getsyspath(path)).keys() 53 | 54 | else: 55 | 56 | class OSFSXAttrMixin(object): 57 | """Mixin disable extended-attribute support.""" 58 | 59 | def __init__(self, *args, **kwargs): 60 | super(OSFSXAttrMixin, self).__init__(*args, **kwargs) 61 | 62 | def getxattr(self,path,key,default=None): 63 | raise UnsupportedError 64 | 65 | def setxattr(self,path,key,value): 66 | raise UnsupportedError 67 | 68 | def delxattr(self,path,key): 69 | raise UnsupportedError 70 | 71 | def listxattrs(self,path): 72 | raise UnsupportedError 73 | 74 | -------------------------------------------------------------------------------- /fs/remotefs.py: -------------------------------------------------------------------------------- 1 | # Work in Progress - Do not use 2 | from __future__ import with_statement 3 | from fs.base import FS 4 | from fs.expose.serve import packetstream 5 | 6 | from collections import defaultdict 7 | import threading 8 | from threading import Lock, RLock 9 | from json import dumps 10 | import Queue as queue 11 | import socket 12 | 13 | from six import b 14 | 15 | 16 | class PacketHandler(threading.Thread): 17 | 18 | def __init__(self, transport, prelude_callback=None): 19 | super(PacketHandler, self).__init__() 20 | self.transport = transport 21 | self.encoder = packetstream.JSONFileEncoder(transport) 22 | self.decoder = packetstream.JSONDecoder(prelude_callback=None) 23 | 24 | self.queues = defaultdict(queue.Queue) 25 | self._encoder_lock = threading.Lock() 26 | self._queues_lock = threading.Lock() 27 | self._call_id_lock = threading.Lock() 28 | 29 | self.call_id = 0 30 | 31 | def run(self): 32 | decoder = self.decoder 33 | read = self.transport.read 34 | on_packet = self.on_packet 35 | while True: 36 | data = read(1024*16) 37 | if not data: 38 | print "No data" 39 | break 40 | print "data", repr(data) 41 | for header, payload in decoder.feed(data): 42 | print repr(header) 43 | print repr(payload) 44 | on_packet(header, payload) 45 | 46 | def _new_call_id(self): 47 | with self._call_id_lock: 48 | self.call_id += 1 49 | return self.call_id 50 | 51 | def get_thread_queue(self, queue_id=None): 52 | if queue_id is None: 53 | queue_id = threading.current_thread().ident 54 | with self._queues_lock: 55 | return self.queues[queue_id] 56 | 57 | def send_packet(self, header, payload=''): 58 | call_id = self._new_call_id() 59 | queue_id = threading.current_thread().ident 60 | client_ref = "%i:%i" % (queue_id, call_id) 61 | header['client_ref'] = client_ref 62 | 63 | with self._encoder_lock: 64 | self.encoder.write(header, payload) 65 | 66 | return call_id 67 | 68 | def get_packet(self, call_id): 69 | 70 | if call_id is not None: 71 | queue_id = threading.current_thread().ident 72 | client_ref = "%i:%i" % (queue_id, call_id) 73 | else: 74 | client_ref = None 75 | 76 | queue = self.get_thread_queue() 77 | 78 | while True: 79 | header, payload = queue.get() 80 | print repr(header) 81 | print repr(payload) 82 | if client_ref is not None and header.get('client_ref') != client_ref: 83 | continue 84 | break 85 | 86 | return header, payload 87 | 88 | def on_packet(self, header, payload): 89 | client_ref = header.get('client_ref', '') 90 | queue_id, call_id = client_ref.split(':', 1) 91 | queue_id = int(queue_id) 92 | #queue_id = header.get('queue_id', '') 93 | queue = self.get_thread_queue(queue_id) 94 | queue.put((header, payload)) 95 | 96 | 97 | class _SocketFile(object): 98 | def __init__(self, socket): 99 | self.socket = socket 100 | 101 | def read(self, size): 102 | try: 103 | return self.socket.recv(size) 104 | except: 105 | return b('') 106 | 107 | def write(self, data): 108 | self.socket.sendall(data) 109 | 110 | def close(self): 111 | self.socket.shutdown(socket.SHUT_RDWR) 112 | self.socket.close() 113 | 114 | 115 | class _RemoteFile(object): 116 | 117 | def __init__(self, path, connection): 118 | self.path = path 119 | self.connection = connection 120 | 121 | class RemoteFS(FS): 122 | 123 | _meta = { 'thead_safe' : True, 124 | 'network' : True, 125 | 'virtual' : False, 126 | 'read_only' : False, 127 | 'unicode_paths' : True, 128 | } 129 | 130 | def __init__(self, addr='', port=3000, username=None, password=None, resource=None, transport=None): 131 | self.addr = addr 132 | self.port = port 133 | self.username = None 134 | self.password = None 135 | self.resource = None 136 | self.transport = transport 137 | if self.transport is None: 138 | self.transport = self._open_connection() 139 | self.packet_handler = PacketHandler(self.transport) 140 | self.packet_handler.start() 141 | 142 | self._remote_call('auth', 143 | username=username, 144 | password=password, 145 | resource=resource) 146 | 147 | def _open_connection(self): 148 | sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) 149 | sock.connect((self.addr, self.port)) 150 | socket_file = _SocketFile(sock) 151 | socket_file.write(b('pyfs/0.1\n')) 152 | return socket_file 153 | 154 | def _make_call(self, method_name, *args, **kwargs): 155 | call = dict(type='rpc', 156 | method=method_name, 157 | args=args, 158 | kwargs=kwargs) 159 | return call 160 | 161 | def _remote_call(self, method_name, *args, **kwargs): 162 | call = self._make_call(method_name, *args, **kwargs) 163 | call_id = self.packet_handler.send_packet(call) 164 | header, payload = self.packet_handler.get_packet(call_id) 165 | return header, payload 166 | 167 | def ping(self, msg): 168 | call_id = self.packet_handler.send_packet({'type':'rpc', 'method':'ping'}, msg) 169 | header, payload = self.packet_handler.get_packet(call_id) 170 | print "PING" 171 | print header 172 | print payload 173 | 174 | def close(self): 175 | self.transport.close() 176 | self.packet_handler.join() 177 | 178 | def open(self, path, mode="r", **kwargs): 179 | pass 180 | 181 | def exists(self, path): 182 | remote = self._remote_call('exists', path) 183 | return remote.get('response') 184 | 185 | 186 | 187 | if __name__ == "__main__": 188 | 189 | rfs = RemoteFS() 190 | rfs.close() 191 | 192 | -------------------------------------------------------------------------------- /fs/tempfs.py: -------------------------------------------------------------------------------- 1 | """ 2 | fs.tempfs 3 | ========= 4 | 5 | Make a temporary file system that exists in a folder provided by the OS. All files contained in a TempFS are removed when the `close` method is called (or when the TempFS is cleaned up by Python). 6 | 7 | """ 8 | 9 | import os 10 | import os.path 11 | import time 12 | import tempfile 13 | 14 | from fs.base import synchronize 15 | from fs.osfs import OSFS 16 | from fs.errors import * 17 | 18 | from fs import _thread_synchronize_default 19 | 20 | 21 | class TempFS(OSFS): 22 | 23 | """Create a Filesystem in a temporary directory (with tempfile.mkdtemp), 24 | and removes it when the TempFS object is cleaned up.""" 25 | 26 | _meta = dict(OSFS._meta) 27 | _meta['pickle_contents'] = False 28 | _meta['network'] = False 29 | _meta['atomic.move'] = True 30 | _meta['atomic.copy'] = True 31 | 32 | def __init__(self, identifier=None, temp_dir=None, dir_mode=0700, thread_synchronize=_thread_synchronize_default): 33 | """Creates a temporary Filesystem 34 | 35 | identifier -- A string that is included in the name of the temporary directory, 36 | default uses "TempFS" 37 | 38 | """ 39 | self.identifier = identifier 40 | self.temp_dir = temp_dir 41 | self.dir_mode = dir_mode 42 | self._temp_dir = tempfile.mkdtemp(identifier or "TempFS", dir=temp_dir) 43 | self._cleaned = False 44 | super(TempFS, self).__init__(self._temp_dir, dir_mode=dir_mode, thread_synchronize=thread_synchronize) 45 | 46 | def __repr__(self): 47 | return '' % self._temp_dir 48 | 49 | __str__ = __repr__ 50 | 51 | def __unicode__(self): 52 | return u'' % self._temp_dir 53 | 54 | def __getstate__(self): 55 | # If we are picking a TempFS, we want to preserve its contents, 56 | # so we *don't* do the clean 57 | state = super(TempFS, self).__getstate__() 58 | self._cleaned = True 59 | return state 60 | 61 | def __setstate__(self, state): 62 | state = super(TempFS, self).__setstate__(state) 63 | self._cleaned = False 64 | #self._temp_dir = tempfile.mkdtemp(self.identifier or "TempFS", dir=self.temp_dir) 65 | #super(TempFS, self).__init__(self._temp_dir, 66 | # dir_mode=self.dir_mode, 67 | # thread_synchronize=self.thread_synchronize) 68 | 69 | @synchronize 70 | def close(self): 71 | """Removes the temporary directory. 72 | 73 | This will be called automatically when the object is cleaned up by 74 | Python, although it is advisable to call it manually. 75 | Note that once this method has been called, the FS object may 76 | no longer be used. 77 | """ 78 | super(TempFS, self).close() 79 | # Depending on how resources are freed by the OS, there could 80 | # be some transient errors when freeing a TempFS soon after it 81 | # was used. If they occur, do a small sleep and try again. 82 | try: 83 | self._close() 84 | except (ResourceLockedError, ResourceInvalidError): 85 | time.sleep(0.5) 86 | self._close() 87 | 88 | @convert_os_errors 89 | def _close(self): 90 | """Actual implementation of close(). 91 | 92 | This is a separate method so it can be re-tried in the face of 93 | transient errors. 94 | """ 95 | os_remove = convert_os_errors(os.remove) 96 | os_rmdir = convert_os_errors(os.rmdir) 97 | if not self._cleaned and self.exists("/"): 98 | self._lock.acquire() 99 | try: 100 | # shutil.rmtree doesn't handle long paths on win32, 101 | # so we walk the tree by hand. 102 | entries = os.walk(self.root_path, topdown=False) 103 | for (dir, dirnames, filenames) in entries: 104 | for filename in filenames: 105 | try: 106 | os_remove(os.path.join(dir, filename)) 107 | except ResourceNotFoundError: 108 | pass 109 | for dirname in dirnames: 110 | try: 111 | os_rmdir(os.path.join(dir, dirname)) 112 | except ResourceNotFoundError: 113 | pass 114 | try: 115 | os.rmdir(self.root_path) 116 | except OSError: 117 | pass 118 | self._cleaned = True 119 | finally: 120 | self._lock.release() 121 | super(TempFS, self).close() 122 | -------------------------------------------------------------------------------- /fs/tests/data/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PyFilesystem/pyfilesystem/7dfe14ae6c3b9c53543c1c3890232d9f37579f34/fs/tests/data/__init__.py -------------------------------------------------------------------------------- /fs/tests/test_errors.py: -------------------------------------------------------------------------------- 1 | # -*- encoding: utf-8 -*- 2 | """ 3 | 4 | fs.tests.test_errors: testcases for the fs error classes functions 5 | 6 | """ 7 | 8 | 9 | import unittest 10 | import fs.tests 11 | from fs.errors import * 12 | import pickle 13 | 14 | from fs.path import * 15 | 16 | class TestErrorPickling(unittest.TestCase): 17 | 18 | def test_pickling(self): 19 | def assert_dump_load(e): 20 | e2 = pickle.loads(pickle.dumps(e)) 21 | self.assertEqual(e.__dict__,e2.__dict__) 22 | assert_dump_load(FSError()) 23 | assert_dump_load(PathError("/some/path")) 24 | assert_dump_load(ResourceNotFoundError("/some/other/path")) 25 | assert_dump_load(UnsupportedError("makepony")) 26 | 27 | 28 | class TestFSError(unittest.TestCase): 29 | 30 | def test_unicode_representation_of_error_with_non_ascii_characters(self): 31 | path_error = PathError('/Shïrê/Frødø') 32 | _ = unicode(path_error) -------------------------------------------------------------------------------- /fs/tests/test_expose.py: -------------------------------------------------------------------------------- 1 | """ 2 | 3 | fs.tests.test_expose: testcases for fs.expose and associated FS classes 4 | 5 | """ 6 | 7 | import unittest 8 | import sys 9 | import os 10 | import os.path 11 | import socket 12 | import threading 13 | import time 14 | 15 | from fs.tests import FSTestCases, ThreadingTestCases 16 | from fs.tempfs import TempFS 17 | from fs.osfs import OSFS 18 | from fs.memoryfs import MemoryFS 19 | from fs.path import * 20 | from fs.errors import * 21 | 22 | from fs import rpcfs 23 | from fs.expose.xmlrpc import RPCFSServer 24 | 25 | import six 26 | from six import PY3, b 27 | 28 | from fs.tests.test_rpcfs import TestRPCFS 29 | 30 | try: 31 | from fs import sftpfs 32 | from fs.expose.sftp import BaseSFTPServer 33 | except ImportError: 34 | if not PY3: 35 | raise 36 | 37 | import logging 38 | logging.getLogger('paramiko').setLevel(logging.ERROR) 39 | logging.getLogger('paramiko.transport').setLevel(logging.ERROR) 40 | 41 | 42 | class TestSFTPFS(TestRPCFS): 43 | 44 | __test__ = not PY3 45 | 46 | def makeServer(self,fs,addr): 47 | return BaseSFTPServer(addr,fs) 48 | 49 | def setUp(self): 50 | self.startServer() 51 | self.fs = sftpfs.SFTPFS(self.server_addr, no_auth=True) 52 | 53 | def bump(self): 54 | # paramiko doesn't like being bumped, just wait for it to timeout. 55 | # TODO: do this using a paramiko.Transport() connection 56 | pass 57 | 58 | 59 | try: 60 | from fs.expose import fuse 61 | except ImportError: 62 | pass 63 | else: 64 | from fs.osfs import OSFS 65 | class TestFUSE(unittest.TestCase, FSTestCases, ThreadingTestCases): 66 | 67 | def setUp(self): 68 | self.temp_fs = TempFS() 69 | self.temp_fs.makedir("root") 70 | self.temp_fs.makedir("mount") 71 | self.mounted_fs = self.temp_fs.opendir("root") 72 | self.mount_point = self.temp_fs.getsyspath("mount") 73 | self.fs = OSFS(self.temp_fs.getsyspath("mount")) 74 | self.mount_proc = fuse.mount(self.mounted_fs, self.mount_point) 75 | 76 | def tearDown(self): 77 | self.mount_proc.unmount() 78 | try: 79 | self.temp_fs.close() 80 | except OSError: 81 | # Sometimes FUSE hangs onto the mountpoint if mount_proc is 82 | # forcibly killed. Shell out to fusermount to make sure. 83 | fuse.unmount(self.mount_point) 84 | self.temp_fs.close() 85 | 86 | def check(self, p): 87 | return self.mounted_fs.exists(p) 88 | 89 | 90 | from fs.expose import dokan 91 | if dokan.is_available: 92 | from fs.osfs import OSFS 93 | class DokanTestCases(FSTestCases): 94 | """Specialised testcases for filesystems exposed via Dokan. 95 | 96 | This modifies some of the standard tests to work around apparent 97 | bugs in the current Dokan implementation. 98 | """ 99 | 100 | def test_remove(self): 101 | self.fs.createfile("a.txt") 102 | self.assertTrue(self.check("a.txt")) 103 | self.fs.remove("a.txt") 104 | self.assertFalse(self.check("a.txt")) 105 | self.assertRaises(ResourceNotFoundError,self.fs.remove,"a.txt") 106 | self.fs.makedir("dir1") 107 | # This appears to be a bug in Dokan - DeleteFile will happily 108 | # delete an empty directory. 109 | #self.assertRaises(ResourceInvalidError,self.fs.remove,"dir1") 110 | self.fs.createfile("/dir1/a.txt") 111 | self.assertTrue(self.check("dir1/a.txt")) 112 | self.fs.remove("dir1/a.txt") 113 | self.assertFalse(self.check("/dir1/a.txt")) 114 | 115 | def test_open_on_directory(self): 116 | # Dokan seems quite happy to ask me to open a directory and 117 | # then treat it like a file. 118 | pass 119 | 120 | def test_settimes(self): 121 | # Setting the times does actually work, but there's some sort 122 | # of caching effect which prevents them from being read back 123 | # out. Disabling the test for now. 124 | pass 125 | 126 | def test_safety_wrapper(self): 127 | rawfs = MemoryFS() 128 | safefs = dokan.Win32SafetyFS(rawfs) 129 | rawfs.setcontents("autoRun.inf", b("evilcodeevilcode")) 130 | self.assertTrue(safefs.exists("_autoRun.inf")) 131 | self.assertTrue("autoRun.inf" not in safefs.listdir("/")) 132 | safefs.setcontents("file:stream",b("test")) 133 | self.assertFalse(rawfs.exists("file:stream")) 134 | self.assertTrue(rawfs.exists("file__colon__stream")) 135 | self.assertTrue("file:stream" in safefs.listdir("/")) 136 | 137 | class TestDokan(unittest.TestCase,DokanTestCases,ThreadingTestCases): 138 | 139 | def setUp(self): 140 | self.temp_fs = TempFS() 141 | self.drive = "K" 142 | while os.path.exists(self.drive+":\\") and self.drive <= "Z": 143 | self.drive = chr(ord(self.drive) + 1) 144 | if self.drive > "Z": 145 | raise RuntimeError("no free drive letters") 146 | fs_to_mount = OSFS(self.temp_fs.getsyspath("/")) 147 | self.mount_proc = dokan.mount(fs_to_mount,self.drive)#,flags=dokan.DOKAN_OPTION_DEBUG|dokan.DOKAN_OPTION_STDERR,numthreads=1) 148 | self.fs = OSFS(self.mount_proc.path) 149 | 150 | def tearDown(self): 151 | self.mount_proc.unmount() 152 | for _ in xrange(10): 153 | try: 154 | if self.mount_proc.poll() is None: 155 | self.mount_proc.terminate() 156 | except EnvironmentError: 157 | time.sleep(0.1) 158 | else: 159 | break 160 | else: 161 | if self.mount_proc.poll() is None: 162 | self.mount_proc.terminate() 163 | self.temp_fs.close() 164 | 165 | if __name__ == '__main__': 166 | unittest.main() 167 | -------------------------------------------------------------------------------- /fs/tests/test_fs.py: -------------------------------------------------------------------------------- 1 | """ 2 | 3 | fs.tests.test_fs: testcases for basic FS implementations 4 | 5 | """ 6 | 7 | from fs.tests import FSTestCases, ThreadingTestCases 8 | from fs.path import * 9 | from fs import errors 10 | 11 | import unittest 12 | 13 | import os 14 | import sys 15 | import shutil 16 | import tempfile 17 | 18 | 19 | from fs import osfs 20 | class TestOSFS(unittest.TestCase,FSTestCases,ThreadingTestCases): 21 | 22 | def setUp(self): 23 | self.temp_dir = tempfile.mkdtemp(u"fstest") 24 | self.fs = osfs.OSFS(self.temp_dir) 25 | 26 | def tearDown(self): 27 | shutil.rmtree(self.temp_dir) 28 | self.fs.close() 29 | 30 | def check(self, p): 31 | return os.path.exists(os.path.join(self.temp_dir, relpath(p))) 32 | 33 | def test_invalid_chars(self): 34 | super(TestOSFS, self).test_invalid_chars() 35 | 36 | self.assertRaises(errors.InvalidCharsInPathError, self.fs.open, 'invalid\0file', 'wb') 37 | self.assertFalse(self.fs.isvalidpath('invalid\0file')) 38 | self.assert_(self.fs.isvalidpath('validfile')) 39 | self.assert_(self.fs.isvalidpath('completely_valid/path/foo.bar')) 40 | 41 | 42 | class TestSubFS(unittest.TestCase,FSTestCases,ThreadingTestCases): 43 | 44 | def setUp(self): 45 | self.temp_dir = tempfile.mkdtemp(u"fstest") 46 | self.parent_fs = osfs.OSFS(self.temp_dir) 47 | self.parent_fs.makedir("foo/bar", recursive=True) 48 | self.fs = self.parent_fs.opendir("foo/bar") 49 | 50 | def tearDown(self): 51 | shutil.rmtree(self.temp_dir) 52 | self.fs.close() 53 | 54 | def check(self, p): 55 | p = os.path.join("foo/bar", relpath(p)) 56 | full_p = os.path.join(self.temp_dir, p) 57 | return os.path.exists(full_p) 58 | 59 | 60 | from fs import memoryfs 61 | class TestMemoryFS(unittest.TestCase,FSTestCases,ThreadingTestCases): 62 | 63 | def setUp(self): 64 | self.fs = memoryfs.MemoryFS() 65 | 66 | 67 | from fs import mountfs 68 | class TestMountFS(unittest.TestCase,FSTestCases,ThreadingTestCases): 69 | 70 | def setUp(self): 71 | self.mount_fs = mountfs.MountFS() 72 | self.mem_fs = memoryfs.MemoryFS() 73 | self.mount_fs.mountdir("mounted/memfs", self.mem_fs) 74 | self.fs = self.mount_fs.opendir("mounted/memfs") 75 | 76 | def tearDown(self): 77 | self.fs.close() 78 | 79 | def check(self, p): 80 | return self.mount_fs.exists(pathjoin("mounted/memfs", relpath(p))) 81 | 82 | class TestMountFS_atroot(unittest.TestCase,FSTestCases,ThreadingTestCases): 83 | 84 | def setUp(self): 85 | self.mem_fs = memoryfs.MemoryFS() 86 | self.fs = mountfs.MountFS() 87 | self.fs.mountdir("", self.mem_fs) 88 | 89 | def tearDown(self): 90 | self.fs.close() 91 | 92 | def check(self, p): 93 | return self.mem_fs.exists(p) 94 | 95 | class TestMountFS_stacked(unittest.TestCase,FSTestCases,ThreadingTestCases): 96 | 97 | def setUp(self): 98 | self.mem_fs1 = memoryfs.MemoryFS() 99 | self.mem_fs2 = memoryfs.MemoryFS() 100 | self.mount_fs = mountfs.MountFS() 101 | self.mount_fs.mountdir("mem", self.mem_fs1) 102 | self.mount_fs.mountdir("mem/two", self.mem_fs2) 103 | self.fs = self.mount_fs.opendir("/mem/two") 104 | 105 | def tearDown(self): 106 | self.fs.close() 107 | 108 | def check(self, p): 109 | return self.mount_fs.exists(pathjoin("mem/two", relpath(p))) 110 | 111 | 112 | from fs import tempfs 113 | class TestTempFS(unittest.TestCase,FSTestCases,ThreadingTestCases): 114 | 115 | def setUp(self): 116 | self.fs = tempfs.TempFS() 117 | 118 | def tearDown(self): 119 | td = self.fs._temp_dir 120 | self.fs.close() 121 | self.assert_(not os.path.exists(td)) 122 | 123 | def check(self, p): 124 | td = self.fs._temp_dir 125 | return os.path.exists(os.path.join(td, relpath(p))) 126 | 127 | def test_invalid_chars(self): 128 | super(TestTempFS, self).test_invalid_chars() 129 | 130 | self.assertRaises(errors.InvalidCharsInPathError, self.fs.open, 'invalid\0file', 'wb') 131 | self.assertFalse(self.fs.isvalidpath('invalid\0file')) 132 | self.assert_(self.fs.isvalidpath('validfile')) 133 | self.assert_(self.fs.isvalidpath('completely_valid/path/foo.bar')) 134 | -------------------------------------------------------------------------------- /fs/tests/test_ftpfs.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | from fs.tests import FSTestCases, ThreadingTestCases 3 | 4 | import unittest 5 | 6 | import os 7 | import sys 8 | import shutil 9 | import tempfile 10 | import subprocess 11 | import time 12 | from os.path import abspath 13 | import urllib 14 | 15 | from six import PY3 16 | 17 | 18 | try: 19 | from pyftpdlib.authorizers import DummyAuthorizer 20 | from pyftpdlib.handlers import FTPHandler 21 | from pyftpdlib.servers import FTPServer 22 | except ImportError: 23 | if not PY3: 24 | raise ImportError("Requires pyftpdlib ") 25 | 26 | from fs.path import * 27 | 28 | from fs import ftpfs 29 | 30 | ftp_port = 30000 31 | class TestFTPFS(unittest.TestCase, FSTestCases, ThreadingTestCases): 32 | 33 | __test__ = not PY3 34 | 35 | def setUp(self): 36 | global ftp_port 37 | ftp_port += 1 38 | use_port = str(ftp_port) 39 | #ftp_port = 10000 40 | self.temp_dir = tempfile.mkdtemp(u"ftpfstests") 41 | 42 | file_path = __file__ 43 | if ':' not in file_path: 44 | file_path = abspath(file_path) 45 | # Apparently Windows requires values from default environment, so copy the exisiting os.environ 46 | env = os.environ.copy() 47 | env['PYTHONPATH'] = os.getcwd() + os.pathsep + env.get('PYTHONPATH', '') 48 | self.ftp_server = subprocess.Popen([sys.executable, 49 | file_path, 50 | self.temp_dir, 51 | use_port], 52 | stdout=subprocess.PIPE, 53 | env=env) 54 | # Block until the server writes a line to stdout 55 | self.ftp_server.stdout.readline() 56 | 57 | # Poll until a connection can be made 58 | start_time = time.time() 59 | while time.time() - start_time < 5: 60 | try: 61 | ftpurl = urllib.urlopen('ftp://127.0.0.1:%s' % use_port) 62 | except IOError: 63 | time.sleep(0) 64 | else: 65 | ftpurl.read() 66 | ftpurl.close() 67 | break 68 | else: 69 | # Avoid a possible infinite loop 70 | raise Exception("Unable to connect to ftp server") 71 | 72 | self.fs = ftpfs.FTPFS('127.0.0.1', 'user', '12345', dircache=True, port=use_port, timeout=5.0) 73 | self.fs.cache_hint(True) 74 | 75 | 76 | def tearDown(self): 77 | #self.ftp_server.terminate() 78 | if sys.platform == 'win32': 79 | os.popen('TASKKILL /PID '+str(self.ftp_server.pid)+' /F') 80 | else: 81 | os.system('kill '+str(self.ftp_server.pid)) 82 | shutil.rmtree(self.temp_dir) 83 | self.fs.close() 84 | 85 | def check(self, p): 86 | check_path = self.temp_dir.rstrip(os.sep) + os.sep + p 87 | return os.path.exists(check_path.encode('utf-8')) 88 | 89 | 90 | if __name__ == "__main__": 91 | 92 | # Run an ftp server that exposes a given directory 93 | import sys 94 | authorizer = DummyAuthorizer() 95 | authorizer.add_user("user", "12345", sys.argv[1], perm="elradfmw") 96 | authorizer.add_anonymous(sys.argv[1]) 97 | 98 | #def nolog(*args): 99 | # pass 100 | #ftpserver.log = nolog 101 | #ftpserver.logline = nolog 102 | 103 | handler = FTPHandler 104 | handler.authorizer = authorizer 105 | address = ("127.0.0.1", int(sys.argv[2])) 106 | #print address 107 | 108 | ftpd = FTPServer(address, handler) 109 | 110 | sys.stdout.write('serving\n') 111 | sys.stdout.flush() 112 | ftpd.serve_forever() 113 | -------------------------------------------------------------------------------- /fs/tests/test_importhook.py: -------------------------------------------------------------------------------- 1 | 2 | import sys 3 | import unittest 4 | import marshal 5 | import imp 6 | import struct 7 | from textwrap import dedent 8 | 9 | from fs.expose.importhook import FSImportHook 10 | from fs.tempfs import TempFS 11 | from fs.zipfs import ZipFS 12 | 13 | from six import b 14 | 15 | 16 | class TestFSImportHook(unittest.TestCase): 17 | 18 | def setUp(self): 19 | pass 20 | 21 | def tearDown(self): 22 | for mph in list(sys.meta_path): 23 | if isinstance(mph,FSImportHook): 24 | sys.meta_path.remove(mph) 25 | for ph in list(sys.path_hooks): 26 | if isinstance(ph, type) and issubclass(ph,FSImportHook): 27 | sys.path_hooks.remove(ph) 28 | for (k,v) in sys.modules.items(): 29 | if k.startswith("fsih_"): 30 | del sys.modules[k] 31 | elif hasattr(v,"__loader__"): 32 | if isinstance(v.__loader__,FSImportHook): 33 | del sys.modules[k] 34 | sys.path_importer_cache.clear() 35 | 36 | def _init_modules(self,fs): 37 | fs.setcontents("fsih_hello.py",b(dedent(""" 38 | message = 'hello world!' 39 | """))) 40 | fs.makedir("fsih_pkg") 41 | fs.setcontents("fsih_pkg/__init__.py",b(dedent(""" 42 | a = 42 43 | """))) 44 | fs.setcontents("fsih_pkg/sub1.py",b(dedent(""" 45 | import fsih_pkg 46 | from fsih_hello import message 47 | a = fsih_pkg.a 48 | """))) 49 | fs.setcontents("fsih_pkg/sub2.pyc",self._getpyc(b(dedent(""" 50 | import fsih_pkg 51 | from fsih_hello import message 52 | a = fsih_pkg.a * 2 53 | """)))) 54 | 55 | def _getpyc(self,src): 56 | """Get the .pyc contents to match th given .py source code.""" 57 | code = imp.get_magic() + struct.pack(" 1024*1024*2) 78 | self.assertTrue(total_written < 1024*1024*2 + 1030) 79 | break 80 | else: 81 | self.assertTrue(False,"StorageSpaceError not raised") 82 | 83 | 84 | from fs.wrapfs.hidedotfilesfs import HideDotFilesFS 85 | class TestHideDotFilesFS(unittest.TestCase): 86 | 87 | def setUp(self): 88 | self.temp_dir = tempfile.mkdtemp(u"fstest") 89 | open(os.path.join(self.temp_dir, u".dotfile"), 'w').close() 90 | open(os.path.join(self.temp_dir, u"regularfile"), 'w').close() 91 | os.mkdir(os.path.join(self.temp_dir, u".dotdir")) 92 | os.mkdir(os.path.join(self.temp_dir, u"regulardir")) 93 | self.fs = HideDotFilesFS(osfs.OSFS(self.temp_dir)) 94 | 95 | def tearDown(self): 96 | shutil.rmtree(self.temp_dir) 97 | self.fs.close() 98 | 99 | def test_hidden(self): 100 | self.assertEquals(len(self.fs.listdir(hidden=False)), 2) 101 | self.assertEquals(len(list(self.fs.ilistdir(hidden=False))), 2) 102 | 103 | def test_nonhidden(self): 104 | self.assertEquals(len(self.fs.listdir(hidden=True)), 4) 105 | self.assertEquals(len(list(self.fs.ilistdir(hidden=True))), 4) 106 | 107 | def test_default(self): 108 | self.assertEquals(len(self.fs.listdir()), 2) 109 | self.assertEquals(len(list(self.fs.ilistdir())), 2) 110 | 111 | 112 | -------------------------------------------------------------------------------- /fs/tests/zipfs_binary_test.py: -------------------------------------------------------------------------------- 1 | """ 2 | Test case for ZipFS binary file reading/writing 3 | Passes ok on Linux, fails on Windows (tested: Win7, 64-bit): 4 | 5 | AssertionError: ' \r\n' != ' \n' 6 | """ 7 | 8 | import unittest 9 | from fs.zipfs import ZipFS 10 | import os 11 | 12 | from six import b 13 | 14 | class ZipFsBinaryWriteRead(unittest.TestCase): 15 | test_content = b(chr(32) + chr(10)) 16 | 17 | def setUp(self): 18 | self.z = ZipFS('test.zip', 'w') 19 | 20 | def tearDown(self): 21 | try: 22 | os.remove('test.zip') 23 | except: 24 | pass 25 | 26 | def test_binary_write_read(self): 27 | # GIVEN zipfs 28 | z = self.z 29 | 30 | # WHEN binary data is written to a test file in zipfs 31 | f = z.open('test.data', 'wb') 32 | f.write(self.test_content) 33 | f.close() 34 | z.close() 35 | 36 | # THEN the same binary data is retrieved when opened again 37 | z = ZipFS('test.zip', 'r') 38 | f = z.open('test.data', 'rb') 39 | content = f.read() 40 | f.close() 41 | z.close() 42 | self.assertEqual(content, self.test_content) 43 | 44 | if __name__ == '__main__': 45 | unittest.main() 46 | -------------------------------------------------------------------------------- /fs/wrapfs/debugfs.py: -------------------------------------------------------------------------------- 1 | ''' 2 | @author: Marek Palatinus 3 | @license: Public domain 4 | 5 | DebugFS is a wrapper around filesystems to help developers 6 | debug their work. I wrote this class mainly for debugging 7 | TahoeLAFS and for fine tuning TahoeLAFS over Dokan with higher-level 8 | aplications like Total Comander, Winamp etc. Did you know 9 | that Total Commander need to open file before it delete them? :-) 10 | 11 | I hope DebugFS can be helpful also for other filesystem developers, 12 | especially for those who are trying to implement their first one (like me). 13 | 14 | DebugFS prints to stdout (by default) all attempts to 15 | filesystem interface, prints parameters and results. 16 | 17 | Basic usage: 18 | fs = DebugFS(OSFS('~'), identifier='OSFS@home', \ 19 | skip=('_lock', 'listdir', 'listdirinfo')) 20 | print fs.listdir('.') 21 | print fs.unsupportedfunction() 22 | 23 | Error levels: 24 | DEBUG: Print everything (asking for methods, calls, response, exception) 25 | INFO: Print calls, responses, exception 26 | ERROR: Print only exceptions 27 | CRITICAL: Print only exceptions not derived from fs.errors.FSError 28 | 29 | How to change error level: 30 | import logging 31 | logger = logging.getLogger('fs.debugfs') 32 | logger.setLevel(logging.CRITICAL) 33 | fs = DebugFS(OSFS('~') 34 | print fs.listdir('.') 35 | 36 | ''' 37 | import logging 38 | from logging import DEBUG, INFO, ERROR, CRITICAL 39 | import sys 40 | 41 | import fs 42 | from fs.errors import FSError 43 | 44 | logger = fs.getLogger('fs.debugfs') 45 | logger.setLevel(logging.DEBUG) 46 | logger.addHandler(logging.StreamHandler()) 47 | 48 | class DebugFS(object): 49 | def __init__(self, fs, identifier=None, skip=(), verbose=True): 50 | ''' 51 | fs - Reference to object to debug 52 | identifier - Custom string-like object will be added 53 | to each log line as identifier. 54 | skip - list of method names which DebugFS should not log 55 | ''' 56 | self.__wrapped_fs = fs 57 | self.__identifier = identifier 58 | self.__skip = skip 59 | self.__verbose = verbose 60 | super(DebugFS, self).__init__() 61 | 62 | def __log(self, level, message): 63 | if self.__identifier: 64 | logger.log(level, '(%s) %s' % (self.__identifier, message)) 65 | else: 66 | logger.log(level, message) 67 | 68 | def __parse_param(self, value): 69 | if isinstance(value, basestring): 70 | if len(value) > 60: 71 | value = "%s ... (length %d)" % (repr(value[:60]), len(value)) 72 | else: 73 | value = repr(value) 74 | elif isinstance(value, list): 75 | value = "%s (%d items)" % (repr(value[:3]), len(value)) 76 | elif isinstance(value, dict): 77 | items = {} 78 | for k, v in value.items()[:3]: 79 | items[k] = v 80 | value = "%s (%d items)" % (repr(items), len(value)) 81 | else: 82 | value = repr(value) 83 | return value 84 | 85 | def __parse_args(self, *arguments, **kwargs): 86 | args = [self.__parse_param(a) for a in arguments] 87 | for k, v in kwargs.items(): 88 | args.append("%s=%s" % (k, self.__parse_param(v))) 89 | 90 | args = ','.join(args) 91 | if args: args = "(%s)" % args 92 | return args 93 | 94 | def __report(self, msg, key, value, *arguments, **kwargs): 95 | if key in self.__skip: return 96 | args = self.__parse_args(*arguments, **kwargs) 97 | value = self.__parse_param(value) 98 | self.__log(INFO, "%s %s%s -> %s" % (msg, str(key), args, value)) 99 | 100 | def __getattr__(self, key): 101 | 102 | if key.startswith('__'): 103 | # Internal calls, nothing interesting 104 | return object.__getattribute__(self, key) 105 | 106 | try: 107 | attr = getattr(self.__wrapped_fs, key) 108 | except AttributeError, e: 109 | self.__log(DEBUG, "Asking for not implemented method %s" % key) 110 | raise e 111 | except Exception, e: 112 | self.__log(CRITICAL, "Exception %s: %s" % \ 113 | (e.__class__.__name__, str(e))) 114 | raise e 115 | 116 | if not callable(attr): 117 | if key not in self.__skip: 118 | self.__report("Get attribute", key, attr) 119 | return attr 120 | 121 | def _method(*args, **kwargs): 122 | try: 123 | value = attr(*args, **kwargs) 124 | self.__report("Call method", key, value, *args, **kwargs) 125 | except FSError, e: 126 | self.__log(ERROR, "Call method %s%s -> Exception %s: %s" % \ 127 | (key, self.__parse_args(*args, **kwargs), \ 128 | e.__class__.__name__, str(e))) 129 | (exc_type,exc_inst,tb) = sys.exc_info() 130 | raise e, None, tb 131 | except Exception, e: 132 | self.__log(CRITICAL, 133 | "Call method %s%s -> Non-FS exception %s: %s" %\ 134 | (key, self.__parse_args(*args, **kwargs), \ 135 | e.__class__.__name__, str(e))) 136 | (exc_type,exc_inst,tb) = sys.exc_info() 137 | raise e, None, tb 138 | return value 139 | 140 | if self.__verbose: 141 | if key not in self.__skip: 142 | self.__log(DEBUG, "Asking for method %s" % key) 143 | return _method 144 | -------------------------------------------------------------------------------- /fs/wrapfs/hidedotfilesfs.py: -------------------------------------------------------------------------------- 1 | """ 2 | fs.wrapfs.hidedotfilesfs 3 | ======================== 4 | 5 | An FS wrapper class for hiding dot-files in directory listings. 6 | 7 | """ 8 | 9 | from fs.wrapfs import WrapFS 10 | from fs.path import * 11 | from fnmatch import fnmatch 12 | 13 | 14 | class HideDotFilesFS(WrapFS): 15 | """FS wrapper class that hides dot-files in directory listings. 16 | 17 | The listdir() function takes an extra keyword argument 'hidden' 18 | indicating whether hidden dot-files should be included in the output. 19 | It is False by default. 20 | """ 21 | 22 | def is_hidden(self, path): 23 | """Check whether the given path should be hidden.""" 24 | return path and basename(path)[0] == "." 25 | 26 | def _encode(self, path): 27 | return path 28 | 29 | def _decode(self, path): 30 | return path 31 | 32 | def listdir(self, path="", wildcard=None, full=False, absolute=False, dirs_only=False, files_only=False, hidden=False): 33 | kwds = dict(wildcard=wildcard, 34 | full=full, 35 | absolute=absolute, 36 | dirs_only=dirs_only, 37 | files_only=files_only) 38 | entries = self.wrapped_fs.listdir(path,**kwds) 39 | if not hidden: 40 | entries = [e for e in entries if not self.is_hidden(e)] 41 | return entries 42 | 43 | def ilistdir(self, path="", wildcard=None, full=False, absolute=False, dirs_only=False, files_only=False, hidden=False): 44 | kwds = dict(wildcard=wildcard, 45 | full=full, 46 | absolute=absolute, 47 | dirs_only=dirs_only, 48 | files_only=files_only) 49 | for e in self.wrapped_fs.ilistdir(path,**kwds): 50 | if hidden or not self.is_hidden(e): 51 | yield e 52 | 53 | def walk(self, path="/", wildcard=None, dir_wildcard=None, search="breadth",hidden=False): 54 | if search == "breadth": 55 | dirs = [path] 56 | while dirs: 57 | current_path = dirs.pop() 58 | paths = [] 59 | for filename in self.listdir(current_path,hidden=hidden): 60 | path = pathjoin(current_path, filename) 61 | if self.isdir(path): 62 | if dir_wildcard is not None: 63 | if fnmatch(path, dir_wildcard): 64 | dirs.append(path) 65 | else: 66 | dirs.append(path) 67 | else: 68 | if wildcard is not None: 69 | if fnmatch(path, wildcard): 70 | paths.append(filename) 71 | else: 72 | paths.append(filename) 73 | yield (current_path, paths) 74 | elif search == "depth": 75 | def recurse(recurse_path): 76 | for path in self.listdir(recurse_path, wildcard=dir_wildcard, full=True, dirs_only=True,hidden=hidden): 77 | for p in recurse(path): 78 | yield p 79 | yield (recurse_path, self.listdir(recurse_path, wildcard=wildcard, files_only=True,hidden=hidden)) 80 | for p in recurse(path): 81 | yield p 82 | else: 83 | raise ValueError("Search should be 'breadth' or 'depth'") 84 | 85 | 86 | def isdirempty(self, path): 87 | path = normpath(path) 88 | iter_dir = iter(self.listdir(path,hidden=True)) 89 | try: 90 | iter_dir.next() 91 | except StopIteration: 92 | return True 93 | return False 94 | 95 | 96 | -------------------------------------------------------------------------------- /fs/wrapfs/hidefs.py: -------------------------------------------------------------------------------- 1 | """ 2 | fs.wrapfs.hidefs 3 | ================ 4 | 5 | Removes resources from a directory listing if they match a given set of wildcards 6 | 7 | """ 8 | 9 | from fs.wrapfs import WrapFS 10 | from fs.path import iteratepath 11 | from fs.errors import ResourceNotFoundError 12 | import re 13 | import fnmatch 14 | 15 | 16 | class HideFS(WrapFS): 17 | """FS wrapper that hides resources if they match a wildcard(s). 18 | 19 | For example, to hide all pyc file and subversion directories from a filesystem:: 20 | 21 | hide_fs = HideFS(my_fs, "*.pyc", ".svn") 22 | 23 | """ 24 | 25 | def __init__(self, wrapped_fs, *hide_wildcards): 26 | self._hide_wildcards = [re.compile(fnmatch.translate(wildcard)) for wildcard in hide_wildcards] 27 | super(HideFS, self).__init__(wrapped_fs) 28 | 29 | def _should_hide(self, path): 30 | return any(any(wildcard.match(part) for wildcard in self._hide_wildcards) 31 | for part in iteratepath(path)) 32 | 33 | def _encode(self, path): 34 | if self._should_hide(path): 35 | raise ResourceNotFoundError(path) 36 | return path 37 | 38 | def _decode(self, path): 39 | return path 40 | 41 | def exists(self, path): 42 | if self._should_hide(path): 43 | return False 44 | return super(HideFS, self).exists(path) 45 | 46 | def listdir(self, path="", *args, **kwargs): 47 | entries = super(HideFS, self).listdir(path, *args, **kwargs) 48 | entries = [entry for entry in entries if not self._should_hide(entry)] 49 | return entries 50 | 51 | if __name__ == "__main__": 52 | from fs.osfs import OSFS 53 | hfs = HideFS(OSFS('~/projects/pyfilesystem'), "*.pyc", ".svn") 54 | hfs.tree() 55 | -------------------------------------------------------------------------------- /fs/wrapfs/lazyfs.py: -------------------------------------------------------------------------------- 1 | """ 2 | fs.wrapfs.lazyfs 3 | ================ 4 | 5 | A class for lazy initialization of an FS object. 6 | 7 | This module provides the class LazyFS, an FS wrapper class that can lazily 8 | initialize its underlying FS object. 9 | 10 | """ 11 | 12 | import sys 13 | 14 | try: 15 | from threading import Lock 16 | except ImportError: 17 | from fs.base import DummyLock as Lock 18 | 19 | from fs.base import FS 20 | from fs.wrapfs import WrapFS 21 | 22 | 23 | class LazyFS(WrapFS): 24 | """Simple 'lazy initialization' for FS objects. 25 | 26 | This FS wrapper can be created with an FS instance, an FS class, or a 27 | (class,args,kwds) tuple. The actual FS instance will be created on demand 28 | the first time it is accessed. 29 | """ 30 | 31 | def __init__(self, fs): 32 | super(LazyFS, self).__init__(fs) 33 | self._lazy_creation_lock = Lock() 34 | 35 | def __unicode__(self): 36 | try: 37 | wrapped_fs = self.__dict__["wrapped_fs"] 38 | except KeyError: 39 | # It appears that python2.5 has trouble printing out 40 | # classes that define a __unicode__ method. 41 | try: 42 | return u"" % (self._fsclass,) 43 | except TypeError: 44 | try: 45 | return u"" % (self._fsclass.__name__,) 46 | except AttributeError: 47 | return u">" 48 | else: 49 | return u"" % (wrapped_fs,) 50 | 51 | def __getstate__(self): 52 | state = super(LazyFS,self).__getstate__() 53 | del state["_lazy_creation_lock"] 54 | return state 55 | 56 | def __setstate__(self, state): 57 | super(LazyFS,self).__setstate__(state) 58 | self._lazy_creation_lock = Lock() 59 | 60 | def _get_wrapped_fs(self): 61 | """Obtain the wrapped FS instance, creating it if necessary.""" 62 | try: 63 | fs = self.__dict__["wrapped_fs"] 64 | except KeyError: 65 | self._lazy_creation_lock.acquire() 66 | try: 67 | try: 68 | fs = self.__dict__["wrapped_fs"] 69 | except KeyError: 70 | fs = self._fsclass(*self._fsargs,**self._fskwds) 71 | self.__dict__["wrapped_fs"] = fs 72 | finally: 73 | self._lazy_creation_lock.release() 74 | return fs 75 | 76 | def _set_wrapped_fs(self, fs): 77 | if isinstance(fs,FS): 78 | self.__dict__["wrapped_fs"] = fs 79 | elif isinstance(fs,type): 80 | self._fsclass = fs 81 | self._fsargs = [] 82 | self._fskwds = {} 83 | elif fs is None: 84 | del self.__dict__['wrapped_fs'] 85 | else: 86 | self._fsclass = fs[0] 87 | try: 88 | self._fsargs = fs[1] 89 | except IndexError: 90 | self._fsargs = [] 91 | try: 92 | self._fskwds = fs[2] 93 | except IndexError: 94 | self._fskwds = {} 95 | 96 | wrapped_fs = property(_get_wrapped_fs,_set_wrapped_fs) 97 | 98 | def setcontents(self, path, data, chunk_size=64*1024): 99 | return self.wrapped_fs.setcontents(path, data, chunk_size=chunk_size) 100 | 101 | def close(self): 102 | if not self.closed: 103 | # If it was never initialized, create a fake one to close. 104 | if "wrapped_fs" not in self.__dict__: 105 | self.__dict__["wrapped_fs"] = FS() 106 | super(LazyFS,self).close() 107 | 108 | 109 | -------------------------------------------------------------------------------- /fs/wrapfs/readonlyfs.py: -------------------------------------------------------------------------------- 1 | """ 2 | fs.wrapfs.readonlyfs 3 | ==================== 4 | 5 | An FS wrapper class for blocking operations that would modify the FS. 6 | 7 | """ 8 | 9 | from fs.base import NoDefaultMeta 10 | from fs.wrapfs import WrapFS 11 | from fs.errors import UnsupportedError, NoSysPathError 12 | 13 | 14 | class ReadOnlyFS(WrapFS): 15 | """ Makes a FS object read only. Any operation that could potentially modify 16 | the underlying file system will throw an UnsupportedError 17 | 18 | Note that this isn't a secure sandbox, untrusted code could work around the 19 | read-only restrictions by getting the base class. Its main purpose is to 20 | provide a degree of safety if you want to protect an FS object from 21 | accidental modification. 22 | 23 | """ 24 | 25 | def getmeta(self, meta_name, default=NoDefaultMeta): 26 | if meta_name == 'read_only': 27 | return True 28 | return self.wrapped_fs.getmeta(meta_name, default) 29 | 30 | def hasmeta(self, meta_name): 31 | if meta_name == 'read_only': 32 | return True 33 | return self.wrapped_fs.hasmeta(meta_name) 34 | 35 | def getsyspath(self, path, allow_none=False): 36 | """ Doesn't technically modify the filesystem but could be used to work 37 | around read-only restrictions. """ 38 | if allow_none: 39 | return None 40 | raise NoSysPathError(path) 41 | 42 | def open(self, path, mode='r', buffering=-1, encoding=None, errors=None, newline=None, line_buffering=False, **kwargs): 43 | """ Only permit read access """ 44 | if 'w' in mode or 'a' in mode or '+' in mode: 45 | raise UnsupportedError('write') 46 | return super(ReadOnlyFS, self).open(path, 47 | mode=mode, 48 | buffering=buffering, 49 | encoding=encoding, 50 | errors=errors, 51 | newline=newline, 52 | line_buffering=line_buffering, 53 | **kwargs) 54 | 55 | def _no_can_do(self, *args, **kwargs): 56 | """ Replacement method for methods that can modify the file system """ 57 | raise UnsupportedError('write') 58 | 59 | move = _no_can_do 60 | movedir = _no_can_do 61 | copy = _no_can_do 62 | copydir = _no_can_do 63 | makedir = _no_can_do 64 | rename = _no_can_do 65 | setxattr = _no_can_do 66 | delxattr = _no_can_do 67 | remove = _no_can_do 68 | removedir = _no_can_do 69 | settimes = _no_can_do 70 | setcontents = _no_can_do 71 | createfile = _no_can_do 72 | -------------------------------------------------------------------------------- /fs/wrapfs/subfs.py: -------------------------------------------------------------------------------- 1 | """ 2 | fs.wrapfs.subfs 3 | =============== 4 | 5 | An FS wrapper class for accessing just a subdirectory for an FS. 6 | 7 | """ 8 | 9 | from fs.wrapfs import WrapFS 10 | from fs.errors import * 11 | from fs.path import * 12 | 13 | 14 | class SubFS(WrapFS): 15 | """A SubFS represents a sub directory of another filesystem object. 16 | 17 | SubFS objects are returned by opendir, which effectively creates a 18 | 'sandbox' filesystem that can only access files/dirs under a root path 19 | within its 'parent' dir. 20 | """ 21 | 22 | def __init__(self, wrapped_fs, sub_dir): 23 | self.sub_dir = abspath(normpath(sub_dir)) 24 | super(SubFS, self).__init__(wrapped_fs) 25 | 26 | def _encode(self, path): 27 | return pathjoin(self.sub_dir, relpath(normpath(path))) 28 | 29 | def _decode(self, path): 30 | return abspath(normpath(path))[len(self.sub_dir):] 31 | 32 | def __str__(self): 33 | #return self.wrapped_fs.desc(self.sub_dir) 34 | return '' % (self.wrapped_fs, self.sub_dir.lstrip('/')) 35 | 36 | def __unicode__(self): 37 | return u'' % (self.wrapped_fs, self.sub_dir.lstrip('/')) 38 | 39 | def __repr__(self): 40 | return "SubFS(%r, %r)" % (self.wrapped_fs, self.sub_dir) 41 | 42 | def desc(self, path): 43 | if path in ('', '/'): 44 | return self.wrapped_fs.desc(self.sub_dir) 45 | return '%s!%s' % (self.wrapped_fs.desc(self.sub_dir), path) 46 | 47 | def setcontents(self, path, data, encoding=None, errors=None, chunk_size=64*1024): 48 | path = self._encode(path) 49 | return self.wrapped_fs.setcontents(path, data, chunk_size=chunk_size) 50 | 51 | def opendir(self, path): 52 | if not self.exists(path): 53 | raise ResourceNotFoundError(path) 54 | path = self._encode(path) 55 | return self.wrapped_fs.opendir(path) 56 | 57 | def close(self): 58 | self.closed = True 59 | 60 | def removedir(self, path, recursive=False, force=False): 61 | # Careful not to recurse outside the subdir 62 | path = normpath(path) 63 | if path in ('', '/'): 64 | raise RemoveRootError(path) 65 | super(SubFS, self).removedir(path, force=force) 66 | if recursive: 67 | try: 68 | if dirname(path) not in ('', '/'): 69 | self.removedir(dirname(path), recursive=True) 70 | except DirectoryNotEmptyError: 71 | pass 72 | 73 | # if path in ("","/"): 74 | # if not force: 75 | # for path2 in self.listdir(path): 76 | # raise DirectoryNotEmptyError(path) 77 | # else: 78 | # for path2 in self.listdir(path,absolute=True,files_only=True): 79 | # try: 80 | # self.remove(path2) 81 | # except ResourceNotFoundError: 82 | # pass 83 | # for path2 in self.listdir(path,absolute=True,dirs_only=True): 84 | # try: 85 | # self.removedir(path2,force=True) 86 | # except ResourceNotFoundError: 87 | # pass 88 | # else: 89 | # super(SubFS,self).removedir(path,force=force) 90 | # if recursive: 91 | # try: 92 | # if dirname(path): 93 | # self.removedir(dirname(path),recursive=True) 94 | # except DirectoryNotEmptyError: 95 | # pass 96 | 97 | 98 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | boto 2 | paramiko 3 | six 4 | django 5 | dexml 6 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | from setuptools import setup 4 | import sys 5 | PY3 = sys.version_info >= (3,) 6 | 7 | VERSION = "0.5.5a1" 8 | 9 | COMMANDS = ['fscat', 10 | 'fsinfo', 11 | 'fsls', 12 | 'fsmv', 13 | 'fscp', 14 | 'fsrm', 15 | 'fsserve', 16 | 'fstree', 17 | 'fsmkdir', 18 | 'fsmount'] 19 | 20 | 21 | CONSOLE_SCRIPTS = ['{0} = fs.commands.{0}:run'.format(command) 22 | for command in COMMANDS] 23 | 24 | classifiers = [ 25 | "Development Status :: 5 - Production/Stable", 26 | 'Intended Audience :: Developers', 27 | 'License :: OSI Approved :: BSD License', 28 | 'Operating System :: OS Independent', 29 | 'Programming Language :: Python', 30 | 'Programming Language :: Python :: 2.6', 31 | 'Programming Language :: Python :: 2.7', 32 | 'Programming Language :: Python :: 3', 33 | 'Topic :: System :: Filesystems', 34 | ] 35 | 36 | with open('README.md', 'r') as f: 37 | long_desc = f.read() 38 | 39 | 40 | extra = {} 41 | if PY3: 42 | extra["use_2to3"] = True 43 | 44 | setup(install_requires=['setuptools', 'six'], 45 | name='fs', 46 | version=VERSION, 47 | description="Filesystem abstraction layer", 48 | long_description=long_desc, 49 | license="BSD", 50 | author="Will McGugan", 51 | author_email="will@willmcgugan.com", 52 | url="http://pypi.python.org/pypi/fs/", 53 | platforms=['any'], 54 | packages=['fs', 55 | 'fs.expose', 56 | 'fs.expose.dokan', 57 | 'fs.expose.fuse', 58 | 'fs.expose.wsgi', 59 | 'fs.tests', 60 | 'fs.wrapfs', 61 | 'fs.osfs', 62 | 'fs.contrib', 63 | 'fs.contrib.bigfs', 64 | 'fs.contrib.davfs', 65 | 'fs.contrib.tahoelafs', 66 | 'fs.commands'], 67 | package_data={'fs': ['tests/data/*.txt']}, 68 | entry_points={"console_scripts": CONSOLE_SCRIPTS}, 69 | classifiers=classifiers, 70 | **extra 71 | ) 72 | -------------------------------------------------------------------------------- /tox.ini: -------------------------------------------------------------------------------- 1 | [tox] 2 | envlist = py26, py27, py33, py34, py35, pypy 3 | sitepackages = False 4 | 5 | [testenv] 6 | deps = 7 | six 8 | dexml 9 | nose 10 | py26,py27: paramiko 11 | py26,py27: boto 12 | py26,py27: mako 13 | py26,py27: pyftpdlib 14 | changedir=.tox 15 | commands = nosetests {posargs:-v fs.tests} 16 | --------------------------------------------------------------------------------