# -*- 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.
#
# Author: Olivier Tilloy <olivier@fluendo.com>

"""
Internet utilities.
"""

from elisa.core.utils import defer
from elisa.core.utils.cancellable_defer import CancellableDeferred, \
                                               CancelledError

from twisted.web.client import HTTPClientFactory
from twisted.internet import reactor

from urlparse import urlparse


def get_page(uri):
    """
    A web resource getter similar to L{twisted.web.client.getPage} that returns
    a cancellable deferred so that the query can be cancelled and the
    connection to the server closed.

    @warning: The C{uri} must be a C{str} encoded in ASCII and conforming to
              the URI syntax standard (see http://tools.ietf.org/html/rfc3986
              and C{urllib.quote}).

    @param uri: the URI of the resource to request
    @type uri:  C{str}

    @return: a cancellable deferred fired with the resource's data when done
    @rtype:  L{elisa.core.utils.cancellable_defer.CancellableDeferred}
    """
    factory = HTTPClientFactory(uri)

    parsed = urlparse(uri)
    if parsed.scheme == 'http':
        connector = \
            reactor.connectTCP(parsed.hostname, parsed.port or 80, factory)
    elif parsed.scheme == 'https':
        from twisted.internet import ssl
        connector = \
            reactor.connectSSL(parsed.hostname, parsed.port or 443, factory,
                               ssl.ClientContextFactory())
    else:
        return defer.fail(ValueError('Unknown scheme: %s' % parsed.scheme))

    f_dfr = factory.deferred
    if hasattr(f_dfr, 'cancel'):
        # Just in case it gets implemented in the original getPage someday.
        return f_dfr

    def cb(result, dfr):
        try:
            dfr.callback(result)
        except defer.AlreadyCalledError:
            # Was canceled
            pass

    def eb(failure, dfr):
        try:
            dfr.errback(failure)
        except defer.AlreadyCalledError:
            # Was canceled
            pass

    def cancel(dfr):
        # Errback first to prevent a twisted.internet.error.UserError from
        # being propagated in case the connection was not open yet.
        dfr.errback(CancelledError())
        connector.disconnect()

    # Wrap the original deferred in a cancellable deferred
    dfr = CancellableDeferred(canceller=cancel)
    f_dfr.addCallbacks(cb, eb, callbackArgs=(dfr,), errbackArgs=(dfr,))
    return dfr

