# -*- coding: UTF-8 -*-


# Expression
# By: Frederic Peters <fpeters@entrouvert.com>
#     Emmanuel Raviart <eraviart@entrouvert.com>
#
# Copyright (C) 2004 Entr'ouvert, Frederic Peters & Emmanuel Raviart
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.


"""File Systems Module"""


import cStringIO
import errno
import os
import shutil
import time
import zipfile


class AbstractFileSystem(object):
    def access(self, filePath, mode):
        raise NotImplementedError

    def execute(self, filePath, globalVariables = None, localVariables = None):
        raise NotImplementedError

    def exists(self, filePath):
        raise NotImplementedError

    def getAbsolutePath(self, path):
        raise NotImplementedError

    def getModificationTime(self, filePath):
        raise NotImplementedError

    def isdir(self, filePath):
        raise NotImplementedError

    def listdir(self, filePath):
        raise NotImplementedError

    def makedirs(self, path, mode = 0777):
        raise NotImplementedError

    def mkdir(self, filePath):
        raise NotImplementedError

    def open(self, filePath, mode = None):
        raise NotImplementedError

    def remove(self, filePath):
        raise NotImplementedError

    def rename(self, oldFilePath, newFilePath):
        raise NotImplementedError

    def rmdir(self, filePath):
        raise NotImplementedError

    def rmtree(self, filePath, ignore_errors = False, onerror = None):
        raise NotImplementedError


class InheritedFileSystem(AbstractFileSystem):
    implementation = None
    prototype = None

    def __init__(self, implementation, prototype):
        self.implementation = implementation
        self.prototype = prototype

    def access(self, filePath, mode):
        if self.implementation.exists(filePath):
            return self.implementation.access(filePath, mode)
        elif mode & os.W_OK:
            return False
        else:
            return self.prototype.access(filePath, mode)

    def execute(self, filePath, globalVariables = None, localVariables = None):
        if self.implementation.exists(filePath):
            return self.implementation.execute(filePath, globalVariables, localVariables)
        else:
            return self.prototype.execute(filePath, globalVariables, localVariables)

    def exists(self, filePath):
        return self.implementation.exists(filePath) or self.prototype.exists(filePath)

    def getAbsolutePath(self, path):
        if self.implementation.exists(path):
            return self.implementation.getAbsolutePath(path)
        else:
            return self.prototype.getAbsolutePath(path)

    def getModificationTime(self, filePath):
        if self.implementation.exists(filePath):
            return self.implementation.getModificationTime(filePath)
        else:
            return self.prototype.getModificationTime(filePath)

    def isdir(self, filePath):
        if self.implementation.exists(filePath):
            return self.implementation.isdir(filePath)
        else:
            return self.prototype.isdir(filePath)

    def listdir(self, filePath):
        if self.implementation.exists(filePath):
            fileNames = self.implementation.listdir(filePath)
        else:
            fileNames = []
        if self.prototype.exists(filePath):
            for fileName in self.prototype.listdir(filePath):
                if fileName not in fileNames:
                    fileNames.append(fileName)
        return fileNames

    def makedirs(self, path, mode = 0777):
        return self.implementation.makedirs(path, mode)

    def mkdir(self, filePath):
        return self.implementation.mkdir(filePath)

    def open(self, filePath, mode = None):
        try:
            return self.implementation.open(filePath, mode)
        except IOError, error:
            if error.errno != errno.ENOENT:
                raise
            if mode not in (None, "r", "rb"):
                raise
            return self.prototype.open(filePath, mode)

    def remove(self, filePath):
        return self.implementation.remove(filePath)

    def rename(self, oldFilePath, newFilePath):
        return self.implementation.rename(oldFilePath, newFilePath)

    def rmdir(self, filePath):
        return self.implementation.rmdir(filePath)

    def rmtree(self, filePath, ignore_errors = False, onerror = None):
        return self.implementation.rmtree(filePath, ignore_errors, onerror)


class PartialFileSystem(AbstractFileSystem):
    path = None

    def __init__(self, path):
        self.path = path

    def access(self, filePath, mode):
        fullFilePath = os.path.normpath(os.path.join(self.path, filePath))
        return os.access(fullFilePath, mode)

    def execute(self, filePath, globalVariables = None, localVariables = None):
        fullFilePath = os.path.normpath(os.path.join(self.path, filePath))
        if globalVariables is None:
            assert localVariables is None
            execfile(fullFilePath)
        elif localVariables is None:
            execfile(fullFilePath, globalVariables)
        else:
            execfile(fullFilePath, globalVariables, localVariables)

    def exists(self, filePath):
        fullFilePath = os.path.normpath(os.path.join(self.path, filePath))
        return os.path.exists(fullFilePath)

    def getAbsolutePath(self, path):
        return os.path.normpath(os.path.join(self.path, path))

    def getModificationTime(self, filePath):
        fullFilePath = os.path.normpath(os.path.join(self.path, filePath))
        return time.gmtime(os.stat(fullFilePath)[-2])

    def isdir(self, filePath):
        fullFilePath = os.path.normpath(os.path.join(self.path, filePath))
        return os.path.isdir(fullFilePath)

    def listdir(self, filePath):
        fullFilePath = os.path.normpath(os.path.join(self.path, filePath))
        return os.listdir(fullFilePath)

    def makedirs(self, path, mode = 0777):
        fullPath = os.path.normpath(os.path.join(self.path, path))
        return os.makedirs(fullPath, mode)

    def mkdir(self, filePath):
        fullFilePath = os.path.normpath(os.path.join(self.path, filePath))
        return os.mkdir(fullFilePath)

    def open(self, filePath, mode = None):
        fullFilePath = os.path.normpath(os.path.join(self.path, filePath))
        if mode is None:
            mode = "r"
        return file(fullFilePath, mode)

    def remove(self, filePath):
        fullFilePath = os.path.normpath(os.path.join(self.path, filePath))
        os.remove(fullFilePath)

    def rename(self, oldFilePath, newFilePath):
        oldFullFilePath = os.path.normpath(os.path.join(self.path, oldFilePath))
        newFullFilePath = os.path.normpath(os.path.join(self.path, newFilePath))
        os.rename(oldFullFilePath, newFullFilePath)

    def rmdir(self, filePath):
        fullFilePath = os.path.normpath(os.path.join(self.path, filePath))
        os.rmdir(fullFilePath)

    def rmtree(self, filePath, ignore_errors = False, onerror = None):
        fullFilePath = os.path.normpath(os.path.join(self.path, filePath))
        shutil.rmtree(fullFilePath, ignore_errors, onerror)


class ZipFileSystem(AbstractFileSystem):
    archive = None

    def __init__(self, archiveFile):
        try:
            self.archive = zipfile.ZipFile(archiveFile, "r")
        except IOError:
            pass

    def access(self, filePath, mode):
        if mode in (os.F_OK, os.R_OK): 
            return self.archive is not None and filePath in self.archive.namelist()
        else:
            raise NotImplementedError

    def execute(self, filePath, globalVariables = None, localVariables = None):
        if globalVariables is None:
            assert localVariables is None
            exec self.archive.read(filePath)
        elif localVariables is None:
            exec self.archive.read(filePath) in globalVariables
        else:
            exec self.archive.read(filePath) in globalVariables, localVariables

    def exists(self, filePath):
        if self.archive is not None and filePath in self.archive.namelist():
            return True
        else:
            return self.isdir(filePath)

    def getAbsolutePath(self, path):
        return os.path.normpath(os.path.join(self.archive.filename, path))

    def getModificationTime(self, filePath):
        info = self.archive.getinfo(filePath)
        return time.gmtime(time.mktime(info.date_time + (0, 0, -1)))

    def isdir(self, filePath):
        if self.archive is not None:
            if not filePath:
                return True
            directoryPath = filePath + "/"
            for path in self.archive.namelist():
                if path.startswith(directoryPath):
                    return True
        return False

    def listdir(self, filePath):
        fileNames = []
        if self.archive is not None:
            if filePath:
                directoryPath = filePath + "/"
            else:
                directoryPath = ""
            for path in self.archive.namelist():
                if path.startswith(directoryPath):
                    name = path[len(directoryPath):]
                    i = name.find("/")
                    if i >= 0:
                        name = name[:i]
                    fileNames.append(name)
        return fileNames

    def open(self, filePath, mode = None):
        if mode not in (None, "r", "rb"):
            raise NotImplementedError
        try:
            return cStringIO.StringIO(self.archive.read(filePath))
        except KeyError:
            raise IOError(errno.ENOENT, "No such file or directory %s" % filePath)
