# vim: tabstop=4 shiftwidth=4 softtabstop=4

# Copyright 2010 OpenStack, LLC
# All Rights Reserved.
#
#    Licensed under the Apache License, Version 2.0 (the "License"); you may
#    not use this file except in compliance with the License. You may obtain
#    a copy of the License at
#
#         http://www.apache.org/licenses/LICENSE-2.0
#
#    Unless required by applicable law or agreed to in writing, software
#    distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
#    WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
#    License for the specific language governing permissions and limitations
#    under the License.

"""
A simple filesystem-backed store
"""

import logging
import os
import urlparse

from glance.common import exception
import glance.store

logger = logging.getLogger('glance.store.filesystem')


class ChunkedFile(object):

    """
    We send this back to the Glance API server as
    something that can iterate over a large file
    """

    CHUNKSIZE = 65536

    def __init__(self, filepath):
        self.filepath = filepath
        self.fp = open(self.filepath, 'rb')

    def __iter__(self):
        """Return an iterator over the image file"""
        try:
            while True:
                chunk = self.fp.read(ChunkedFile.CHUNKSIZE)
                if chunk:
                    yield chunk
                else:
                    break
        finally:
            self.close()

    def close(self):
        """Close the internal file pointer"""
        if self.fp:
            self.fp.close()
            self.fp = None


class FilesystemBackend(glance.store.Backend):
    @classmethod
    def get(cls, parsed_uri, expected_size=None, options=None):
        """ Filesystem-based backend

        file:///path/to/file.tar.gz.0
        """

        filepath = parsed_uri.path
        if not os.path.exists(filepath):
            raise exception.NotFound("Image file %s not found" % filepath)
        else:
            logger.debug("Found image at %s. Returning in ChunkedFile.",
                         filepath)
            return ChunkedFile(filepath)

    @classmethod
    def delete(cls, parsed_uri):
        """
        Removes a file from the filesystem backend.

        :param parsed_uri: Parsed pieces of URI in form of::
            file:///path/to/filename.ext

        :raises NotFound if file does not exist
        :raises NotAuthorized if cannot delete because of permissions
        """
        fn = parsed_uri.path
        if os.path.exists(fn):
            try:
                logger.debug("Deleting image at %s", fn)
                os.unlink(fn)
            except OSError:
                raise exception.NotAuthorized("You cannot delete file %s" % fn)
        else:
            raise exception.NotFound("Image file %s does not exist" % fn)

    @classmethod
    def add(cls, id, data, options):
        """
        Stores image data to disk and returns a location that the image was
        written to. By default, the backend writes the image data to a file
        `/<DATADIR>/<ID>`, where <DATADIR> is the value of
        options['filesystem_store_datadir'] and <ID> is the supplied image ID.

        :param id: The opaque image identifier
        :param data: The image data to write, as a file-like object
        :param options: Conf mapping

        :retval Tuple with (location, size)
                The location that was written, with file:// scheme prepended
                and the size in bytes of the data written
        """
        datadir = options['filesystem_store_datadir']

        if not os.path.exists(datadir):
            logger.info("Directory to write image files does not exist "
                        "(%s). Creating.", datadir)
            os.makedirs(datadir)

        filepath = os.path.join(datadir, str(id))

        if os.path.exists(filepath):
            raise exception.Duplicate("Image file %s already exists!"
                                      % filepath)

        bytes_written = 0
        with open(filepath, 'wb') as f:
            while True:
                buf = data.read(ChunkedFile.CHUNKSIZE)
                if not buf:
                    break
                bytes_written += len(buf)
                f.write(buf)

        logger.debug("Wrote %(bytes_written)d bytes to %(filepath)s"
                     % locals())
        return ('file://%s' % filepath, bytes_written)

    @classmethod
    def add_options(cls, parser):
        """
        Adds specific configuration options for this store

        :param parser: An optparse.OptionParser object
        :retval None
        """

        parser.add_option('--filesystem-store-datadir', metavar="DIR",
                          default="/var/lib/glance/images/",
                          help="Location to write image data. This directory "
                          "should be writeable by the user that runs the "
                          "glance-api program. Default: %default")
