# -*- coding: utf-8 -*-
# Moovida - Home multimedia server
# Copyright (C) 2006-2009 Fluendo Embedded S.L. (www.fluendo.com).
# All rights reserved.
#
# This file is available under one of two license agreements.
#
# This file is licensed under the GPL version 3.
# See "LICENSE.GPL" in the root of this distribution including a special
# exception to use Moovida with Fluendo's plugins.
#
# The GPL part of Moovida is also available under a commercial licensing
# agreement from Fluendo.
# See "LICENSE.Moovida" in the root directory of this distribution package
# for details on that license.

"""
Access resources on the local file system
"""


from elisa.core.components.resource_provider import ResourceProvider

from elisa.core.utils import defer, locale_helper

from elisa.plugins.base.models.file import FileModel, DirectoryModel

from twisted.internet import threads, task

import os, platform

class LocalResource(ResourceProvider):
    """
    Access resources on the local file system
    """

    supported_uri = "^file://"
    def __init__(self):
        super(LocalResource, self).__init__()
        if platform.system() == 'Windows':
            from elisa.core.utils.mswin32.tools import ShortcutResolver
            self._shortcut_resolver = ShortcutResolver()

    def _listdir(self, path):
        """
        wrap os.listdir in a private method that can be overriden in
        the tests if needed
        """
        return os.listdir(path)

    def get(self, uri, model=None):
        """
        This reacts differently in the case you give it a directory. If you
        want to have the C{Files} of a directory, make sure you have an ending
        '/' in your request. Otherwise the implementation assumes that you just
        want to have information about that object (like the size and the
        modification time). The context model is reused if given. But in case
        it is a directory listing the C{Files} attribute is cleared before.

        Some example requests and the results they return:

         - C{file:///tmp} : empty L{DirectoryModel} with information about the
                            directory itself

         - C{file://tmp/} : L{DirectoryModel} with all the files (and
                            subdirectories) in it
        """

        shortcut_name = None

        if not os.path.exists(uri.path):
            msg = "[Errno 2] No such file or directory: '%s'" % uri.path
            return (None, defer.fail(IOError(msg)))

        # is it a win32 shortcut?
        if platform.system() == 'Windows' and uri.extension.lower() == 'lnk':
            shortcut_name = uri.filename[:-4]
            uri.path = self._shortcut_resolver.resolve(uri.path).replace('\\', '/')
            if not os.path.exists(uri.path):
                msg = "[Errno 2] No such file or directory: '%s'" % uri.path
                return (None, defer.fail(IOError(msg)))

        path = uri.path

        if model is None:
            # no model given, return just simple models
            if os.path.isdir(path):
                model = DirectoryModel()
            else:
                model = FileModel()

            model.uri = uri
            if not shortcut_name:
                model.name = uri.filename
            else:
                model.name = shortcut_name

        # read the stat and set it to the model
        stat = os.stat(path)
        model.size = stat.st_size
        model.mtime = stat.st_mtime
        model.atime = stat.st_atime

        dfr = defer.succeed(model)

        if uri.path[-1] == '/':
            # someone asks us for the content of the directory

            def wrong_file(error, file):
                self.warning("Skipping %s: %s" % (file, error.value))
                return None

            # read the files and append them
            def iterate_files(list, dir_model, uri):
                for filename in list:
                    try:
                        new_uri = uri.join(filename)
                        if not os.path.exists(new_uri.path):
                            filename = filename.decode(locale_helper.system_encoding())
                            new_uri = uri.join(filename)
                    except UnicodeError, exc:
                        self.warning("Skipping %s: %s" % (filename, exc))
                        continue

                    model, dfr = self.get(new_uri)
                    dfr.addCallbacks(dir_model.files.append, wrong_file,
                        errbackArgs=(filename,))
                    yield dfr

            def set_files(list, model, uri):
                dfr = task.coiterate(iterate_files(list,model,uri))
                dfr.addCallback(lambda x: model)
                return dfr

            def cancelled(failure):
                # eat up the AlreadyCalledError that happens when the
                # cancellable deferred was already cancelled.
                failure.trap(defer.AlreadyCalledError)
                return None

            cancellable_deferred = defer.Deferred()
            thread_dfr = threads.deferToThread(self._listdir, path)
            thread_dfr.addCallback(set_files, model, uri)
            thread_dfr.chainDeferred(cancellable_deferred)
            thread_dfr.addErrback(cancelled)

            # act like the cancellable defer. PFUI!
            dfr = cancellable_deferred

        return (model, dfr)

    def post(self, uri, time=None):
        """
        change the access and modifcation time with given time=value.
        """
        path = uri.path
        if not os.path.exists(path):
            msg = "[Errno 2] No such file or directory: '%s'" % path
            return defer.fail(IOError(msg))

        if time:
            os.utime(path, (time, time))
        return defer.succeed('Done')

    def delete(self, uri):
        """
        delete a file from the file system
        """

        path = uri.path
        if not os.path.exists(path):
            msg = "[Errno 2] No such file or directory: '%s'" % path
            return defer.fail(IOError(msg))

        if os.path.isdir(path):
            os.rmdir(path)
        else:
            os.remove(path)

        return defer.succeed(uri)
