##########################################################################
#
# FileSystem.py: handle dirs and filesystem operations.
#
#  Python-CDD is a library to make easier to build applications to
#  Custom Debian Distributions.
#  See http://projetos.ossystems.com.br/python-cdd for more information.
#    
# ====================================================================
# Copyright (c) 2002-2005 O.S. Systems.  All rights reserved.
#
# This software is licensed as described in the file COPYING, which
# you should have received as part of this distribution.
#
#########################################################################
# Authors: Otavio Salvador <otavio@ossystems.com.br>
#          Marco Presi <zufus@debian.org>
#          Free Ekanayaka <free@agnula.org>
#          Vagrant Cascadian <vagrant@freegeek.org>
#
# Class to handle dirs and filesystem operations.


import os
import re
import md5
import sha
import sys
import stat
import logging
log = logging.getLogger("cdd.FileSystem")

from gzip import GzipFile
from bz2  import BZ2File

# Set the compress format to gzip.
GZIP = 2

# Set the compress format to bzip2.
BZIP2 = 4

class FileSystem:
    """
    This class provides methods to manage dirs and files.
    """

    def __init__(self, base, *args):
        """
        Initialize the object

        Parameters:

         - *base*: the base directory where all methods will run
        """
        log.debug("FileSystem: constructor")
    
        self.__errors = [13]

        others = list(args)
        self.__base = os.path.join(base, *others)

        ### add a alias to self.remove method
        self.unlink = self.remove

    def exists(self, name):
        """
        Check if a file or directory named 'name' exists inside of
        base directory.

        Parameters:

         - *name*: the name of file or directory to be checked
        """
        return os.path.exists(os.path.join(self.__base, name))

    def mkdir(self, name):
        """
        Create a directory 'name' inside of 'base'

        Parameters:

         - *name*: the directory name
        """
        log.debug("Creating %s..." % name)
        
        dirname = os.path.join(self.__base, name)
        try:
            if os.path.isdir(dirname):
                pass
            elif not self.exists(name):
                os.makedirs (dirname)
            else:
                log.debug("Unable to create directory %s" % dirname)
                return False
        except OSError, (errno,strerror):
            log.debug("%s" % strerror)
            return False

        return True

    def remove(self, name):
        """
        Remove file 'name' inside of 'base'

        p.s.: same as FileSystem.unlink

        Parameters:

         - *name*: the name of file to be removed 
        """
                
        target = os.path.join(self.__base, name)
        log.debug("Removing %s" % target)
        
        try:
            os.remove(target)
        except OSError, (errno, strerror):
            log.debug(strerror)
            return False

    def rmdir(self, name, empty = False):
        """
        Remove a directory and if empty == True it remove all empty
        between it.

        Parameters:

         - *name*: the name of directory to be removed

         - *empty*: a boolean variable to set if directory is empty 
           (default value: False) 
        """
        log.debug("Removing %s" % name)

        os.rmdir(os.path.join(self.__base, name))

        if empty:
            head, tail = os.path.split(name)
            if not tail:
                head, tail = os.path.split(head)
            while head and tail:
                log.debug("Removing %s" % head)
                try:
                    os.rmdir(os.path.join(self.__base, head))
                except:
                    break
                head, tail = os.path.split(head)

    def base(self):
        """
        Return FileSystem base
        """
        return self.__base

    def md5sum (self, filename):
        """
        Return md5sum for given file
        
        Parameters:

         - *filename*: the name of file to get md5sum
        """
        try:
            file_to_hash = open(os.path.join(self.__base, filename), 'r')
        except IOError:
            raise
                        
        md5hash = md5.new()
        md5hash.update(file_to_hash.read())
        file_to_hash.close()
        return md5hash.hexdigest()

    def sha1sum (self, filename):
        """
        Return sha1sum for given file
        
        Parameters:

         - *filename*: the name of file to get sha1sum
        """
        try:
            file_to_hash = open(os.path.join(self.__base, filename), 'r')
        except IOError:
            log.debug("Can't open %s for sha1sum." % filename)
            raise
                
        sha1hash = sha.new()
        sha1hash.update(file_to_hash.read())
        file_to_hash.close()
        return sha1hash.hexdigest()

    def size (self, filename):
        """
        Return the size of the given file

        Parameters:

         - *filename*: the name of file to get size
        """
        try:
            size = os.stat(os.path.join(self.__base, filename))[stat.ST_SIZE]
        except OSError:
            raise
        
        return size

    def uncompress (self, filename):
        """
        Uncompress a given file
        
        Parameters:

         - *filename*: the name of file to be uncompressed
        """
        log.debug('Uncompressing %s' % filename)

        for format in (GzipFile, BZ2File):
            try:
                compressed_file = format(os.path.join(self.__base, filename),
                                                      'r')
            except IOError, msg:
                raise
                
            try:
                data = compressed_file.read()

                # We already read all data, go out!
                break
            except IOError, msg:
                pass
        else:
            raise IOError, "Invalid format"
        
        compressed_file.close()
            
        output_file = open(os.path.join(self.__base,
                                       filename.rstrip('.gz')), "wb")
        output_file.write(data)
        output_file.close()
        return True
    
    def compress (self, filename, format = GZIP):
        """
        Compress a file
        
        Parameters:

         - *filename*: the name of file to be compressed

         - *format*: the compress format (default value: gzip)
        """
        log.debug('Compressing ' + filename)

        if format == GZIP:
            class_ref = GzipFile
            extension = '.gz'
        elif format == BZIP2:
            class_ref = BZ2File
            extension = '.bz2'
        else:
            raise SyntaxError
            
        try:
            input_file = open(os.path.join(self.__base, filename), 'r')
            output_file = class_ref(os.path.join(self.__base,
                                                filename + extension), "wb")
        except IOError, msg:
            raise

        output_file.write(input_file.read())
        output_file.close()
        input_file.close()
        return True
    
    def match (self, expression):
        """
        Find a file which any pattern
        
        Parameters:

         - *expression*: the pattern to be matched
        """
        log.debug("Matching a pattern")

        retfiles = []
        comp_exp = re.compile(expression)
        for root, dirs, files in os.walk(self.__base):
            for name in files:
                filename = os.path.join(root, name)
                if comp_exp.findall(filename):
                    retfiles.append(filename)

        return retfiles
