# -*- Mode:Python; indent-tabs-mode:nil; tab-width:4 -*-
#
# Copyright 2002 Ben Escoto <ben@emerose.org>
# Copyright 2007 Kenneth Loafman <kenneth@loafman.com>
#
# This file is part of duplicity.
#
# Duplicity 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.
#
# Duplicity 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 duplicity; if not, write to the Free Software Foundation,
# Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA

"""Miscellaneous classes and methods"""

import os

from duplicity import log
from duplicity import util


class MiscError(Exception):
    """Signifies a miscellaneous error..."""
    pass


class FileVolumeWriter:
    """Split up an incoming fileobj into multiple volumes on disk

    This class can also be used as an iterator.  It returns the
    filenames of the files it writes.

    """
    volume_size = 50 * 1024 * 1024
    blocksize = 64 * 1024
    def __init__(self, infp, file_prefix):
        """FileVolumeWriter initializer

        infp is a file object opened for reading.  It will be closed
        at end.  file_prefix is the full path of the volumes that will
        be written.  If more than one is required, it will be appended
        with .1, .2, etc.

        """
        self.infp = infp
        self.prefix = file_prefix
        self.current_index = 1
        self.finished = None # set to true when completely done
        self.buffer = "" # holds data that belongs in next volume

    def get_initial_buf(self):
        """Get first value of buffer, from self.buffer or infp"""
        if self.buffer:
            buf = self.buffer
            self.buffer = ""
            return buf
        else:
            return self.infp.read(self.blocksize)

    def write_volume(self, outfp):
        """Write self.volume_size bytes from self.infp to outfp

        Return None if we have reached end of infp without reaching
        volume size, and false otherwise.

        """
        bytes_written, buf = 0, self.get_initial_buf()
        while len(buf) + bytes_written <= self.volume_size:
            if not buf:
                # reached end of input
                outfp.close()
                return None
            if len(buf) + bytes_written > self.volume_size:
                break
            outfp.write(buf)
            bytes_written += len(buf)
            buf = self.infp.read(self.blocksize)

        remainder = self.volume_size - bytes_written
        assert remainder < len(buf)
        outfp.write(buf[:remainder])
        outfp.close()
        self.buffer = buf[remainder:]
        return 1

    def next(self):
        """Write next file, return filename"""
        if self.finished:
            raise StopIteration

        filename = "%s.%d" % (self.prefix, self.current_index)
        log.Info(_("Starting to write %s") % util.ufn(filename))
        outfp = open(filename, "wb")

        if not self.write_volume(outfp):
            # end of input
            self.finished = 1
            if self.current_index == 1:
                # special case first index
                log.Notice(_("One only volume required.\n"
                             "Renaming %s to %s") % (util.ufn(filename), util.ufn(self.prefix)))
                os.rename(filename, self.prefix)
                return self.prefix
        else:
            self.current_index += 1
        return filename

    def __iter__(self):
        return self


class BufferedFile:
    """Buffer file open for reading, so reads will happen in fixed sizes

    This is currently used to buffer a GzipFile, because that class
    apparently doesn't respond well to arbitrary read sizes.

    """
    def __init__(self, fileobj, blocksize = 32 * 1024):
        self.fileobj = fileobj
        self.buffer = ""
        self.blocksize = blocksize

    def read(self, length = -1):
        """Return length bytes, or all if length < 0"""
        if length < 0:
            while 1:
                buf = self.fileobj.read(self.blocksize)
                if not buf:
                    break
                self.buffer += buf
            real_length = len(self.buffer)
        else:
            while len(self.buffer) < length:
                buf = self.fileobj.read(self.blocksize)
                if not buf:
                    break
                self.buffer += buf
            real_length = min(length, len(self.buffer))
        result = self.buffer[:real_length]
        self.buffer = self.buffer[real_length:]
        return result

    def close(self):
        self.fileobj.close()


def copyfileobj(infp, outfp, byte_count = -1):
    """Copy byte_count bytes from infp to outfp, or all if byte_count < 0

    Returns the number of bytes actually written (may be less than
    byte_count if find eof.  Does not close either fileobj.

    """
    blocksize = 64 * 1024
    bytes_written = 0
    if byte_count < 0:
        while 1:
            buf = infp.read(blocksize)
            if not buf:
                break
            bytes_written += len(buf)
            outfp.write(buf)
    else:
        while bytes_written + blocksize <= byte_count:
            buf = infp.read(blocksize)
            if not buf:
                break
            bytes_written += len(buf)
            outfp.write(buf)
        buf = infp.read(byte_count - bytes_written)
        bytes_written += len(buf)
        outfp.write(buf)
    return bytes_written

def copyfileobj_close(infp, outfp):
    """Copy infp to outfp, closing afterwards"""
    copyfileobj(infp, outfp)
    if infp.close():
        raise MiscError("Error closing input file")
    if outfp.close():
        raise MiscError("Error closing output file")


