diff --git a/.project b/.project
new file mode 100644
index 0000000..6bacee1
--- /dev/null
+++ b/.project
@@ -0,0 +1,17 @@
+
+
+ splitviewfuse
+
+
+
+
+
+ org.python.pydev.PyDevBuilder
+
+
+
+
+
+ org.python.pydev.pythonNature
+
+
diff --git a/.pydevproject b/.pydevproject
new file mode 100644
index 0000000..037bd25
--- /dev/null
+++ b/.pydevproject
@@ -0,0 +1,8 @@
+
+
+
+/${PROJECT_DIR_NAME}
+
+python 2.7
+Default
+
diff --git a/LICENSE b/LICENSE
index 701c823..76fca2a 100644
--- a/LICENSE
+++ b/LICENSE
@@ -1,6 +1,6 @@
The MIT License (MIT)
-Copyright (c) 2015 Stephan Seifermann
+Copyright (c) 2014 Stephan Seifermann
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
@@ -19,4 +19,3 @@ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
-
diff --git a/README.rst b/README.rst
new file mode 100644
index 0000000..44e4ab7
--- /dev/null
+++ b/README.rst
@@ -0,0 +1,4 @@
+splitviewfuse
+===========
+
+A view on a given directory that splits large files into segmentes implemented as FUSE file system.
\ No newline at end of file
diff --git a/setup.cfg b/setup.cfg
new file mode 100644
index 0000000..11e9ec4
--- /dev/null
+++ b/setup.cfg
@@ -0,0 +1,2 @@
+[metadata]
+description-file = README.rst
\ No newline at end of file
diff --git a/setup.py b/setup.py
new file mode 100644
index 0000000..8150b32
--- /dev/null
+++ b/setup.py
@@ -0,0 +1,101 @@
+from setuptools import setup, find_packages # Always prefer setuptools over distutils
+from codecs import open # To use a consistent encoding
+from os import path
+
+here = path.abspath(path.dirname(__file__))
+
+# Get the long description from the relevant file
+with open(path.join(here, 'README.rst'), encoding='utf-8') as f:
+ long_description = f.read()
+
+setup(
+ name='splitviewfuse',
+
+ # Versions should comply with PEP440. For a discussion on single-sourcing
+ # the version across setup.py and the project code, see
+ # https://packaging.python.org/en/latest/development.html#single-sourcing-the-version
+ version='0.1.0b1',
+
+ description='A fuse implementation for an segmented view on a given directory.',
+ long_description=long_description,
+
+ # The project's main homepage.
+ url='https://github.com/seiferma/splitviewfuse',
+
+ # Author details
+ author='Stephan Seifermann',
+ author_email='seiferma@t-online.de',
+
+ # Choose your license
+ license='MIT',
+
+ # See https://pypi.python.org/pypi?%3Aaction=list_classifiers
+ classifiers=[
+ # How mature is this project? Common values are
+ # 3 - Alpha
+ # 4 - Beta
+ # 5 - Production/Stable
+ 'Development Status :: 4 - Beta',
+
+ 'Environment :: No Input/Output (Daemon)',
+
+ # Indicate who your project is intended for
+ 'Intended Audience :: System Administrators',
+ 'Topic :: System :: Filesystems',
+
+ # Pick your license as you wish (should match "license" above)
+ 'License :: OSI Approved :: MIT License',
+
+ 'Operating System :: MacOS :: MacOS X',
+ 'Operating System :: POSIX :: BSD :: FreeBSD',
+ 'Operating System :: POSIX :: Linux',
+
+ # Specify the Python versions you support here. In particular, ensure
+ # that you indicate whether you support Python 2, Python 3 or both.
+ 'Programming Language :: Python :: 2.7'
+ ],
+
+ # What does your project relate to?
+ keywords='fuse view split segments',
+
+ # You can just specify the packages manually here if your project is
+ # simple. Or you can use find_packages().
+ packages=find_packages(exclude=['contrib', 'docs', 'tests*']),
+
+ # List run-time dependencies here. These will be installed by pip when your
+ # project is installed. For an analysis of "install_requires" vs pip's
+ # requirements files see:
+ # https://packaging.python.org/en/latest/technical.html#install-requires-vs-requirements-files
+ install_requires=['fusepy'],
+
+ # List additional groups of dependencies here (e.g. development dependencies).
+ # You can install these using the following syntax, for example:
+ # $ pip install -e .[dev,test]
+# extras_require = {
+# 'dev': ['check-manifest'],
+# 'test': ['coverage'],
+# },
+
+ # If there are data files included in your packages that need to be
+ # installed, specify them here. If using Python 2.6 or less, then these
+ # have to be included in MANIFEST.in as well.
+# package_data={
+# 'sample': ['package_data.dat'],
+# },
+
+ # Although 'package_data' is the preferred approach, in some case you may
+ # need to place data files outside of your packages.
+ # see http://docs.python.org/3.4/distutils/setupscript.html#installing-additional-files
+ # In this case, 'data_file' will be installed into '/my_data'
+# data_files=[('my_data', ['data/data_file'])],
+
+ # To provide executable scripts, use entry points in preference to the
+ # "scripts" keyword. Entry points provide cross-platform support and allow
+ # pip to create the appropriate form of executable for the target platform.
+ entry_points={
+ 'console_scripts': [
+ 'splitviewfuse=splitviewfuse.SplitViewFuse:main',
+ 'unionviewfuse=splitviewfuse.UnionViewFuse:main',
+ ],
+ },
+)
diff --git a/splitviewfuse/.gitignore b/splitviewfuse/.gitignore
new file mode 100644
index 0000000..2498651
--- /dev/null
+++ b/splitviewfuse/.gitignore
@@ -0,0 +1,3 @@
+/SegmentUtils.pyc
+/SplitViewFuseBase.pyc
+/__init__.pyc
diff --git a/splitviewfuse/SegmentUtils.py b/splitviewfuse/SegmentUtils.py
new file mode 100644
index 0000000..c401242
--- /dev/null
+++ b/splitviewfuse/SegmentUtils.py
@@ -0,0 +1,20 @@
+import re
+
+class SegmentUtils(object):
+
+ SEGMENT_SEPARATOR = '.seg.'
+
+ @staticmethod
+ def splitSegmentPath(path):
+ match = re.match('^(.*?)(' + re.escape(SegmentUtils.SEGMENT_SEPARATOR) + '([0-9]+))?$', path)
+ filePath = match.groups()[0]
+ segmentNumber = match.groups()[2]
+ if segmentNumber is not None:
+ segmentNumber = int(segmentNumber)
+ return filePath, segmentNumber
+
+ @staticmethod
+ def joinSegmentPath(segmentName, segmentNumber):
+ if segmentNumber is None:
+ return segmentName
+ return segmentName + SegmentUtils.SEGMENT_SEPARATOR + str(segmentNumber)
\ No newline at end of file
diff --git a/splitviewfuse/SplitViewFuse.py b/splitviewfuse/SplitViewFuse.py
new file mode 100644
index 0000000..1c516b4
--- /dev/null
+++ b/splitviewfuse/SplitViewFuse.py
@@ -0,0 +1,39 @@
+from fuse import FUSE
+from splitviewfuse import SplitViewFuseBase
+from splitviewfuse.filehandlecontainers.Single2SegmentVirtualFileHandleContainer import Single2SegmentVirtualFileHandleContainer
+from splitviewfuse.SegmentUtils import SegmentUtils
+import os
+
+
+class SplitViewFuse(SplitViewFuseBase.SplitViewFuseBase):
+
+ def __init__(self, root, maxSegmentSize):
+ super(SplitViewFuse, self).__init__(root, maxSegmentSize, Single2SegmentVirtualFileHandleContainer(maxSegmentSize))
+
+ def _SplitViewFuseBase__processReadDirEntry(self, absRootPath, entry):
+ dirContent = list()
+
+ absRootPathEntry = os.path.join(absRootPath, entry)
+
+ # split large files
+ if os.path.isfile(absRootPathEntry):
+ fileSize = os.path.getsize(absRootPathEntry)
+ if fileSize > self.maxFileSize:
+ numberOfParts = fileSize // self.maxFileSize + 1
+ for i in range(0, numberOfParts):
+ dirContent.append(SegmentUtils.joinSegmentPath(entry, i))
+ return dirContent
+
+ # return not splitted entry
+ dirContent.append(entry)
+ return dirContent
+
+
+
+def main():
+ args = SplitViewFuseBase.parseArguments()
+ _ = FUSE(SplitViewFuse(args.device, args.mountOptions['segmentsize']), args.dir, **args.mountOptions['other'])
+ #fuse = FUSE(SplitViewFuse(args.device, args.mountOptions['segmentsize']), args.dir, nothreads=True, foreground=True)
+
+if __name__ == '__main__':
+ main()
diff --git a/splitviewfuse/SplitViewFuseBase.py b/splitviewfuse/SplitViewFuseBase.py
new file mode 100644
index 0000000..f303ef8
--- /dev/null
+++ b/splitviewfuse/SplitViewFuseBase.py
@@ -0,0 +1,202 @@
+from abc import ABCMeta, abstractmethod
+from argparse import ArgumentParser, ArgumentTypeError, Action
+from errno import EACCES, EPERM
+from fuse import FuseOSError, Operations, LoggingMixIn
+import os, stat
+
+
+class SplitViewFuseBase(LoggingMixIn, Operations):
+
+ __metaclass__ = ABCMeta
+
+ def __init__(self, root, maxFileSize, fileHandleContainer):
+ self.root = os.path.realpath(root)
+ self.maxFileSize = maxFileSize
+ self.fileHandleContainer = fileHandleContainer
+
+ def __call__(self, op, path, *args):
+ return super(SplitViewFuseBase, self).__call__(op, path, *args)
+
+ def __getAbsolutePath(self, path):
+ return self.root + path
+
+ @abstractmethod
+ def __processReadDirEntry(self, absRootPath, entry):
+ '''
+ Returns the view entries that shall be displayed for the given entry
+ '''
+
+ def access(self, path, mode):
+ if mode is os.W_OK:
+ raise FuseOSError(EACCES)
+
+ pathToTest = self.__getAbsolutePath(path)
+ if not os.path.isdir(pathToTest):
+ with self.fileHandleContainer.createHandleObject(pathToTest) as virtualFile:
+ pathToTest = virtualFile.getPath()
+
+ if not os.access(pathToTest, mode):
+ raise FuseOSError(EACCES)
+
+ def chmod(self, path, mode):
+ raise FuseOSError(EPERM)
+
+ def chown(self, path, uid, gid):
+ raise FuseOSError(EPERM)
+
+ def create(self, path, mode):
+ raise FuseOSError(EPERM)
+
+ def flush(self, path, fh):
+ # we do not support writing, so we ignore this call
+ return
+
+ def fsync(self, path, datasync, fh):
+ # we do not support writing, so we ignore this call
+ return
+
+ def getattr(self, path, fh=None):
+ absRootPath = self.__getAbsolutePath(path)
+
+ st = None
+ size = None
+
+ if os.path.isdir(absRootPath):
+ st = os.lstat(absRootPath)
+ size = st.st_size
+ else:
+ with self.fileHandleContainer.createHandleObject(absRootPath) as virtualFile:
+ st = os.lstat(virtualFile.getPath())
+ size = virtualFile.size()
+
+ stats = dict((key, getattr(st, key)) for key in ('st_atime', 'st_ctime',
+ 'st_gid', 'st_mode', 'st_mtime', 'st_nlink', 'st_size', 'st_uid'))
+ stats['st_mode'] = st.st_mode & ~(stat.S_IWUSR | stat.S_IWGRP | stat.S_IWOTH)
+ stats['st_size'] = size
+
+ return stats
+
+ getxattr = None
+
+ def link(self, target, source):
+ raise FuseOSError(EPERM)
+
+ listxattr = None
+
+ def mknod(self, path, mode, dev):
+ raise FuseOSError(EPERM)
+
+ def mkdir(self, path, mode):
+ raise FuseOSError(EPERM)
+
+ def open(self, path, flags):
+ if flags & (os.O_CREAT | os.O_APPEND | os.O_RDWR | os.O_WRONLY) != 0:
+ raise FuseOSError(EPERM)
+ absRootPath = self.__getAbsolutePath(path)
+ return self.fileHandleContainer.registerHandle(absRootPath)
+
+ def read(self, path, size, offset, fh):
+ virtualFile = self.fileHandleContainer.getHandle(fh)
+ return virtualFile.read(offset, size)
+
+ def readdir(self, path, fh):
+ dirContent = ['.', '..']
+ absRootPath = self.__getAbsolutePath(path)
+ for entry in os.listdir(absRootPath):
+ dirContent.extend(self.__processReadDirEntry(absRootPath, entry))
+ return dirContent
+
+ def readlink(self, path, buf, bufsize):
+ raise FuseOSError(EPERM)
+
+ def release(self, path, fh):
+ self.fileHandleContainer.unregisterHandle(fh)
+
+ def rename(self, old, new):
+ raise FuseOSError(EPERM)
+
+ def rmdir(self, path):
+ raise FuseOSError(EPERM)
+
+ def statfs(self, path):
+ stv = os.statvfs(self.__getAbsolutePath(path))
+ return dict((key, getattr(stv, key)) for key in ('f_bavail', 'f_bfree',
+ 'f_blocks', 'f_bsize', 'f_favail', 'f_ffree', 'f_files', 'f_flag',
+ 'f_frsize', 'f_namemax'))
+
+ def symlink(self, target, source):
+ raise FuseOSError(EPERM)
+
+ def truncate(self, path, length, fh=None):
+ raise FuseOSError(EPERM)
+
+ def unlink(self, path):
+ raise FuseOSError(EPERM)
+
+ utimens = os.utime
+
+ def write(self, path, data, offset, fh):
+ raise FuseOSError(EPERM)
+
+
+
+
+
+
+class FullPaths(Action):
+ """Expand user- and relative-paths"""
+ def __call__(self, parser, namespace, values, option_string=None):
+ setattr(namespace, self.dest, os.path.abspath(os.path.expanduser(values)))
+
+class MountOptions(Action):
+ def __call__(self, parser, namespace, values, option_string=None):
+ resultObject = dict()
+ options = values.split(',')
+
+ resultObject["segmentsize"] = None
+ maxFileSizeOptions = [s for s in options if s.startswith("segmentsize=")]
+ resultObject["segmentsize"] = int(maxFileSizeOptions[0][len("segmentsize="):])
+
+ interestingOptions = ["segmentsize="]
+ filteredOptions = filter(lambda x: not any(x.startswith(string) for string in interestingOptions), options)
+ otherOptions = dict()
+ for option in filteredOptions:
+ parts = option.split('=')
+ if len(parts) == 1:
+ otherOptions[parts[0]] = True
+ else:
+ otherOptions[parts[0]] = parts[1]
+ resultObject["other"] = otherOptions
+
+ setattr(namespace, self.dest, resultObject)
+
+def __is_dir(dirname):
+ """Checks if a path is an actual directory"""
+ if not os.path.isdir(dirname):
+ msg = "{0} is not a directory".format(dirname)
+ raise ArgumentTypeError(msg)
+ else:
+ return dirname
+
+def __are_mount_options(options):
+ optionList = options.split(',')
+
+ if "" in optionList:
+ raise ArgumentTypeError("Empty option given.")
+
+ maxFileSizeOptions = [x for x in optionList if x.startswith("segmentsize=")]
+ if len(maxFileSizeOptions) != 1:
+ raise ArgumentTypeError("The segment size option is mandatory.")
+ if not maxFileSizeOptions[0][len("segmentsize="):].isdigit():
+ raise ArgumentTypeError("The segment size has to be a number.")
+
+ return options
+
+
+def parseArguments():
+ parser = ArgumentParser(description='Encrypted file system for large cloud backups')
+ parser.add_argument('device', action=FullPaths, type=__is_dir, help='the document root for the original files')
+ parser.add_argument('dir', action=FullPaths, type=__is_dir, help='the mount point')
+ parser.add_argument("-o", action=MountOptions, type=__are_mount_options, required=True, dest='mountOptions', help='mount options')
+ return parser.parse_args()
+
diff --git a/splitviewfuse/UnionViewFuse.py b/splitviewfuse/UnionViewFuse.py
new file mode 100644
index 0000000..708a933
--- /dev/null
+++ b/splitviewfuse/UnionViewFuse.py
@@ -0,0 +1,26 @@
+from fuse import FUSE
+from splitviewfuse import SplitViewFuseBase
+from splitviewfuse.filehandlecontainers.Segment2SingleVirtualFileHandleContainer import Segment2SingleVirtualFileHandleContainer
+from splitviewfuse.SegmentUtils import SegmentUtils
+
+
+class UnionViewFuse(SplitViewFuseBase.SplitViewFuseBase):
+
+ def __init__(self, root, maxSegmentSize):
+ super(UnionViewFuse, self).__init__(root, maxSegmentSize, Segment2SingleVirtualFileHandleContainer(maxSegmentSize))
+
+ def _SplitViewFuseBase__processReadDirEntry(self, absRootPath, entry):
+ dirContent = list()
+ segmentFreeEntry, segmentNumber = SegmentUtils.splitSegmentPath(entry)
+ if segmentNumber is None or segmentNumber is 0:
+ dirContent.append(segmentFreeEntry)
+ return dirContent
+
+
+def main():
+ args = SplitViewFuseBase.parseArguments()
+ _ = FUSE(UnionViewFuse(args.device, args.mountOptions['segmentsize']), args.dir, **args.mountOptions['other'])
+ #fuse = FUSE(UnionViewFuse(args.device, args.mountOptions['segmentsize']), args.dir, nothreads=True, foreground=True)
+
+if __name__ == '__main__':
+ main()
diff --git a/splitviewfuse/__init__.py b/splitviewfuse/__init__.py
new file mode 100644
index 0000000..e69de29
diff --git a/splitviewfuse/filehandlecontainers/FileHandleContainer.py b/splitviewfuse/filehandlecontainers/FileHandleContainer.py
new file mode 100644
index 0000000..c225fea
--- /dev/null
+++ b/splitviewfuse/filehandlecontainers/FileHandleContainer.py
@@ -0,0 +1,50 @@
+from abc import ABCMeta, abstractmethod
+from threading import Lock
+
+class FileHandleContainer(object):
+
+ __metaclass__ = ABCMeta
+
+ def __init__(self):
+ self.handles = dict()
+ self.freeIndices = list()
+ self.lock = Lock()
+
+ def registerHandle(self, path):
+ with self.lock:
+ fileHandleObject = self.createHandleObject(path)
+ fileHandleIndex = self.__getNextFreeIndex()
+ self.handles[fileHandleIndex] = fileHandleObject
+ return fileHandleIndex
+
+ def getHandle(self, index):
+ with self.lock:
+ return self.handles[index]
+
+ def unregisterHandle(self, index):
+ with self.lock:
+ if index not in self.handles.keys():
+ return None
+ self.freeIndices.append(index)
+ handleObject = self.handles.pop(index)
+ self.__cleanupHandleObject(handleObject)
+
+ def __getNextFreeIndex(self):
+ if len(self.freeIndices) > 0:
+ return self.freeIndices.pop()
+ return len(self.handles)
+
+ def createHandleObject(self, path):
+ return self.__createHandleObject(path)
+
+ @abstractmethod
+ def __createHandleObject(self, path):
+ '''
+ Creates the file handle object for the given path.
+ '''
+
+ @abstractmethod
+ def __cleanupHandleObject(self, handleObject):
+ '''
+ Cleans up the given handle object.
+ '''
diff --git a/splitviewfuse/filehandlecontainers/Segment2SingleVirtualFileHandleContainer.py b/splitviewfuse/filehandlecontainers/Segment2SingleVirtualFileHandleContainer.py
new file mode 100644
index 0000000..34affcd
--- /dev/null
+++ b/splitviewfuse/filehandlecontainers/Segment2SingleVirtualFileHandleContainer.py
@@ -0,0 +1,14 @@
+from splitviewfuse.filehandlecontainers.FileHandleContainer import FileHandleContainer
+from splitviewfuse.virtualfiles.Segment2SingleVirtualFile import Segment2SingleVirtualFile
+
+class Segment2SingleVirtualFileHandleContainer(FileHandleContainer):
+
+ def __init__(self, maxSegmentSize):
+ super(Segment2SingleVirtualFileHandleContainer, self).__init__()
+ self.maxSegmentSize = maxSegmentSize
+
+ def _FileHandleContainer__createHandleObject(self, path):
+ return Segment2SingleVirtualFile(path, self.maxSegmentSize)
+
+ def _FileHandleContainer__cleanupHandleObject(self, handleObject):
+ handleObject.closeFileHandle()
diff --git a/splitviewfuse/filehandlecontainers/Single2SegmentVirtualFileHandleContainer.py b/splitviewfuse/filehandlecontainers/Single2SegmentVirtualFileHandleContainer.py
new file mode 100644
index 0000000..70bbae4
--- /dev/null
+++ b/splitviewfuse/filehandlecontainers/Single2SegmentVirtualFileHandleContainer.py
@@ -0,0 +1,14 @@
+from splitviewfuse.filehandlecontainers.FileHandleContainer import FileHandleContainer
+from splitviewfuse.virtualfiles.Single2SegmentVirtualFile import Single2SegmentVirtualFile
+
+class Single2SegmentVirtualFileHandleContainer(FileHandleContainer):
+
+ def __init__(self, maxSegmentSize):
+ super(Single2SegmentVirtualFileHandleContainer, self).__init__()
+ self.maxSegmentSize = maxSegmentSize
+
+ def _FileHandleContainer__createHandleObject(self, path):
+ return Single2SegmentVirtualFile(path, self.maxSegmentSize)
+
+ def _FileHandleContainer__cleanupHandleObject(self, handleObject):
+ handleObject.closeFileHandle()
diff --git a/splitviewfuse/filehandlecontainers/__init__.py b/splitviewfuse/filehandlecontainers/__init__.py
new file mode 100644
index 0000000..e69de29
diff --git a/splitviewfuse/virtualfiles/Segment2SingleVirtualFile.py b/splitviewfuse/virtualfiles/Segment2SingleVirtualFile.py
new file mode 100644
index 0000000..3af9ac3
--- /dev/null
+++ b/splitviewfuse/virtualfiles/Segment2SingleVirtualFile.py
@@ -0,0 +1,54 @@
+from splitviewfuse.virtualfiles.VirtualFile import VirtualFile, LazyFile
+from splitviewfuse.SegmentUtils import SegmentUtils
+import os
+from errno import ENOENT
+
+class Segment2SingleVirtualFile(VirtualFile):
+
+ def __init__(self, absRootPath, maxSegmentSize):
+ self.allSegments = self.__findAllSegments(absRootPath)
+ self.maxSegmentSize = maxSegmentSize
+ if len(self.allSegments) < 1:
+ raise OSError(ENOENT)
+ super(Segment2SingleVirtualFile, self).__init__(self.allSegments[-1].getPath())
+ self.sz = (len(self.allSegments) - 1) * maxSegmentSize + os.path.getsize(self.allSegments[-1].getPath())
+
+ def read(self, offset, size):
+ firstSegmentIndex = offset // self.maxSegmentSize
+ segmentOffset = offset - (firstSegmentIndex * self.maxSegmentSize)
+
+ data = b""
+ remainingSize = size
+ for segment in self.allSegments[firstSegmentIndex:]:
+ readData = segment.read(segmentOffset, remainingSize)
+ remainingSize -= len(readData)
+ data += readData
+ segmentOffset = 0
+ if remainingSize <= 0:
+ break
+
+ return data
+
+ def size(self):
+ return self.sz
+
+ def closeFileHandle(self):
+ for segment in self.allSegments:
+ segment.close()
+
+ def __findAllSegments(self, path):
+ absRootPathDir = os.path.dirname(path)
+ fileName = os.path.basename(path)
+ segmentName, segmentNumber = SegmentUtils.splitSegmentPath(fileName)
+ if segmentNumber is None and os.path.exists(path):
+ return [LazyFile(path)]
+
+ entries = list()
+ for entry in os.listdir(absRootPathDir):
+ entryBase, _ = SegmentUtils.splitSegmentPath(entry)
+ if entryBase == segmentName:
+ entries.append(LazyFile(os.path.join(absRootPathDir, entry)))
+ entries.sort(key=lambda x: SegmentUtils.splitSegmentPath(x.getPath())[1])
+ return entries
+
+
\ No newline at end of file
diff --git a/splitviewfuse/virtualfiles/Single2SegmentVirtualFile.py b/splitviewfuse/virtualfiles/Single2SegmentVirtualFile.py
new file mode 100644
index 0000000..5c66e8c
--- /dev/null
+++ b/splitviewfuse/virtualfiles/Single2SegmentVirtualFile.py
@@ -0,0 +1,39 @@
+from splitviewfuse.virtualfiles.VirtualFile import VirtualFile, LazyFile
+from splitviewfuse.SegmentUtils import SegmentUtils
+import os
+from fuse import FuseOSError
+from errno import ENOENT
+
+class Single2SegmentVirtualFile(VirtualFile):
+
+ def __init__(self, absRootPath, maxSegmentSize):
+ super(Single2SegmentVirtualFile, self).__init__(absRootPath)
+
+ path, nr = SegmentUtils.splitSegmentPath(absRootPath)
+
+ if nr is None and os.path.isfile(absRootPath) and os.path.getsize(absRootPath) > maxSegmentSize:
+ raise FuseOSError(ENOENT)
+
+ if nr is None:
+ nr = 0
+
+ self.path = path
+ self.offset = nr * maxSegmentSize
+
+ realFileSize = os.path.getsize(self.path)
+ if realFileSize <= (nr + 1) * maxSegmentSize:
+ # last segment
+ self.sz = realFileSize % maxSegmentSize
+ else:
+ self.sz = maxSegmentSize
+
+ self.file = LazyFile(self.path)
+
+ def closeFileHandle(self):
+ self.file.close()
+
+ def read(self, offset, size):
+ return self.file.read(self.offset + offset, size)
+
+ def size(self):
+ return self.sz
diff --git a/splitviewfuse/virtualfiles/VirtualFile.py b/splitviewfuse/virtualfiles/VirtualFile.py
new file mode 100644
index 0000000..14e29a5
--- /dev/null
+++ b/splitviewfuse/virtualfiles/VirtualFile.py
@@ -0,0 +1,63 @@
+from abc import ABCMeta, abstractmethod
+from threading import Lock
+from _pyio import __metaclass__
+
+class VirtualFile(object):
+
+ __metaclass__ = ABCMeta
+
+ def __init__(self, absRootPath):
+ self.path = absRootPath
+
+ def __enter__(self):
+ return self
+
+ def __exit__(self, *exc):
+ self.closeFileHandle()
+
+ def getPath(self):
+ return self.path
+
+ @abstractmethod
+ def read(self, offset, size):
+ '''
+ Returns the read bytes from offset with the given size
+ or less if EOF is reached.
+ '''
+
+ @abstractmethod
+ def size(self):
+ '''
+ Returns the size of the file
+ '''
+
+ @abstractmethod
+ def closeFileHandle(self):
+ '''
+ Closes possibly open file handles
+ '''
+
+class LazyFile(object):
+ def __init__(self, absPath):
+ self.path = absPath
+ self.file = None
+ self.lock = Lock()
+
+ def getPath(self):
+ return self.path
+
+ def read(self, offset, length):
+ with self.lock:
+ f = self.__getFile()
+ f.seek(offset)
+ return f.read(length)
+
+ def close(self):
+ with self.lock:
+ if self.file is not None:
+ self.file.close()
+
+ def __getFile(self):
+ if self.file is None:
+ self.file = open(self.path, "rb")
+ return self.file
diff --git a/splitviewfuse/virtualfiles/__init__.py b/splitviewfuse/virtualfiles/__init__.py
new file mode 100644
index 0000000..e69de29