<commit_before># VOEvent TCP transport protocol using Twisted.
# John Swinbank, <swinbank@transientskp.org>, 2011-12.

# XML parsing using lxml
import lxml.etree as ElementTree

# Twisted protocol definition
from twisted.python import log
from twisted.internet import reactor
from twisted.internet import defer
from twisted.internet.threads import deferToThread
from twisted.protocols.basic import Int32StringReceiver
from twisted.internet.task import LoopingCall
from twisted.internet.protocol import ClientFactory
from twisted.internet.protocol import ServerFactory
from twisted.internet.protocol import ReconnectingClientFactory

# Constructors for transport protocol messages
from .messages import ack, nak
from .messages import iamalive, iamaliveresponse
from .messages import authenticate, authenticateresponse

# Constructor for our perodic test events
from ..utility.voevent import broker_test_message

from ..utility.xml import xml_document

# Constants
VOEVENT_ROLES = ('observation', 'prediction', 'utility', 'test')

"""
Implements the VOEvent Transport Protocol; see
<http://www.ivoa.net/Documents/Notes/VOEventTransport/>.

All messages consist of a 4-byte network ordered payload size followed by the
payload data. Twisted's Int32StringReceiver handles this for us automatically.

There are four different VOEvent protocols to implement:

* VOEventSubscriber

    * Opens connection to remote broker, receives VOEvent messages.

* VOEventPublisher

    * Listens for connections from subscribers, sends VOEvent messages.

* VOEventSender

    * Connects to VOEventReceiver and publishes a new message.

* VOEventReceiver

    * Receives messages from VOEventSender.

To implement the broker, we need the Subscriber, Publisher & Receiver, but not
the Sender. All four are implemented here for completeness.
"""

class ElementSender(Int32StringReceiver):
    """
    Superclass for protocols which will send XML messages which must be
    deserialized from ET elements.
    """
    def send_xml(self, document):
        """
        Takes an xml_document and sents it as XML.
        """
        self.sendString(document.text)

class EventHandler(Int32StringReceiver):
    """
    Superclass for protocols which will receive events (ie, Subscriber and
    Receiver) providing event handling support.
    """
    def validate_event(self, event):
        """
        Call a set of event validators on a given event (an xml_document).

        If a validator raises an exception (ie, calls an errback), the event
        is invalid. Otherwise, it's ok.
        """
        return defer.gatherResults(
            [
                defer.maybeDeferred(validator, event)
                for validator in self.factory.validators
            ],
            consumeErrors=True,
        )

    def handle_event(self, event):
        """
        Call a set of event handlers on a given event (itself an ElementTree
        element).
        """
        return defer.gatherResults(
            [
                defer.maybeDeferred(handler, event)
                for handler in self.factory.handlers
            ],
            consumeErrors=True
        )

    def process_event(self, event):
        def handle_valid(status):
            self.send_xml(
                ack(self.factory.local_ivo, event.attrib['ivorn'])
            )
            self.handle_event(event).addCallbacks(
                lambda x: log.msg("Event processed"),
                lambda x: log.err("Event handlers failed")
            )

        def handle_invalid(failure):
            # Should unpack exception from failure to include useful output
            # in Nak
            self.send_xml(
                nak(self.factory.local_ivo, event.attrib['ivorn'])
            )
            log.msg("Event invalid; discarding")
        self.validate_event(event).addCallbacks(handle_valid, handle_invalid)

class VOEventSubscriber(EventHandler, ElementSender):
    ALIVE_INTERVAL = 120 # If we get no iamalive for ALIVE_INTERVAL seconds,
                         # assume our peer forgot us.
    def __init__(self, filters=[]):
        self.check_alive = reactor.callLater(self.ALIVE_INTERVAL, self.timed_out)
        self.filters = filters

    def timed_out(self):
        log.msg(
            "No iamalive received for %d seconds; disconecting" %
            self.ALIVE_INTERVAL
        )
        self.transport.loseConnection()

    def stringReceived(self, data):
        """
        Called when a complete new message is received.
        """
        try:
            incoming = xml_document(data)
        except ElementTree.ParseError:
            log.err("Unparsable message received")
            return

        # The root element of both VOEvent and Transport packets has a
        # "role" element which we use to identify the type of message we
        # have received.
        if incoming.get('role') == "iamalive":
            log.msg("IAmAlive received from %s" % str(self.transport.getPeer()))
            self.check_alive.cancel()
            self.send_xml(
                iamaliveresponse(self.factory.local_ivo, incoming.find('Origin').text)
            )
            self.check_alive = reactor.callLater(self.ALIVE_INTERVAL, self.timed_out)
        elif incoming.get('role') == "authenticate":
            log.msg("Authenticate received from %s" % str(self.transport.getPeer()))
            self.send_xml(
                authenticateresponse(
                    self.factory.local_ivo,
                    incoming.find('Origin').text,
                    self.filters
                )
            )
        elif incoming.get('role') in VOEVENT_ROLES:
            log.msg(
                "VOEvent %s received from %s" % (
                    incoming.attrib['ivorn'],
                    str(self.transport.getPeer())
                )
            )
            self.process_event(incoming)
        else:
            log.err("Incomprehensible data received")

class VOEventSubscriberFactory(ReconnectingClientFactory):
    protocol = VOEventSubscriber

    def __init__(self, local_ivo, validators=[], handlers=[], filters=[]):
        self.local_ivo = local_ivo
        self.handlers = handlers
        self.validators = validators
        self.filters = filters

    def buildProtocol(self, addr):
        self.resetDelay()
        p = self.protocol(self.filters)
        p.factory = self
        return p

class VOEventPublisher(ElementSender):
    MAX_ALIVE_COUNT = 1      # Drop connection if peer misses too many iamalives
    MAX_OUTSTANDING_ACK = 10 # Drop connection if peer misses too many acks

    def __init__(self):
        self.filters = []

    def connectionMade(self):
        log.msg("New subscriber at %s" % str(self.transport.getPeer()))
        self.factory.publishers.append(self)
        self.alive_count = 0
        self.send_xml(authenticate(self.factory.local_ivo))
        self.outstanding_ack = 0

    def connectionLost(self, reason):
        log.msg("Subscriber at %s disconnected" % str(self.transport.getPeer()))
        self.factory.publishers.remove(self)

    def sendIAmAlive(self):
        if self.alive_count > self.MAX_ALIVE_COUNT:
            log.msg("Peer appears to be dead; dropping connection")
            self.transport.loseConnection()
        elif self.outstanding_ack > self.MAX_OUTSTANDING_ACK:
            log.msg("Peer is not acknowledging events; dropping connection")
            self.transport.loseConnection()
        else:
            self.send_xml(iamalive(self.factory.local_ivo))
            self.alive_count += 1

    def stringReceived(self, data):
        try:
            incoming = xml_document(data)
        except ElementTree.ParseError:
            log.err("Unparsable message received")
            return

        if incoming.get('role') == "iamalive":
            log.msg("IAmAlive received from %s" % str(self.transport.getPeer()))
            self.alive_count -= 1
        elif incoming.get('role') == "ack":
            log.msg("Ack received from %s" % str(self.transport.getPeer()))
            self.outstanding_ack -= 1
        elif incoming.get('role') == "nak":
            log.msg("Nak received from %s; terminating" % str(self.transport.getPeer()))
            self.transport.loseConnection()
        elif incoming.get('role') == "authenticate":
            log.msg("Authentication received from %s" % str(self.transport.getPeer()))
            for xpath in incoming.findall("Meta/filter[@type=\"xpath\"]"):
                log.msg(
                    "Installing filter %s for %s" %
                    (xpath.text, str(self.transport.getPeer()))
                )
            self.filters = [
                ElementTree.XPath(xpath.text)
                for xpath in incoming.findall("Meta/filter[@type=\"xpath\"]")
            ]
        else:
            log.err(incoming.get('role'))
            log.err("Incomprehensible data received from %s" % str(self.transport.getPeer()))

    def send_event(self, event):
        # Check the event against our filters and, if one or more pass, then
        # we send the event to our subscriber.
        def check_filters(result):
            if not self.filters or any([value for success, value in result if success]):
                log.msg("Event matches filter criteria: forwarding")
                self.send_xml(event)
            else:
                log.msg("Event rejected by filter")

        defer.DeferredList(
            [
                deferToThread(xpath, event.element)
                for xpath in self.filters
            ],
            consumeErrors=True,
        ).addCallback(check_filters)

class VOEventPublisherFactory(ServerFactory):
    IAMALIVE_INTERVAL = 60 # Sent iamalive every IAMALIVE_INTERVAL seconds
    TEST_INTERVAL = 3600   # Sent test event every TEST_INTERVAL seconds
    protocol = VOEventPublisher

    def __init__(self, local_ivo):
        self.local_ivo = local_ivo
        self.publishers = []
        self.alive_loop = LoopingCall(self.sendIAmAlive)
        self.alive_loop.start(self.IAMALIVE_INTERVAL)
        self.test_loop = LoopingCall(self.sendTestEvent)
        self.test_loop.start(self.TEST_INTERVAL)

    def sendIAmAlive(self):
        log.msg("Broadcasting iamalive")
        for publisher in self.publishers:
            publisher.sendIAmAlive()

    def sendTestEvent(self):
        log.msg("Broadcasting test event")
        test_event = broker_test_message(self.local_ivo)
        for publisher in self.publishers:
            publisher.send_event(test_event)

class VOEventSender(ElementSender):
    """
    Implements the VOEvent Transport Protocol; see
    <http://www.ivoa.net/Documents/Notes/VOEventTransport/>.

    All messages consist of a 4-byte network ordered payload size followed by
    the payload data. Twisted's Int32StringReceiver handles this for us
    automatically.
    """
    def connectionMade(self):
        self.send_xml(self.factory.event)

    def stringReceived(self, data):
        """
        Called when a complete new message is received.
        """
        log.msg("Got response from %s" % str(self.transport.getPeer()))
        try:
            incoming = xml_document(data)
        except ElementTree.ParseError:
            log.err("Unparsable message received from %s" % str(self.transport.getPeer()))
            return

        if incoming.get('role') == "ack":
            log.msg("Acknowledgement received from %s" % str(self.transport.getPeer()))
        elif incoming.get('role') == "nak":
            log.err("Nak received: %s refused to accept VOEvent" % str(self.transport.getPeer()))
        else:
            log.err("Incomprehensible data received from %s" % str(self.transport.getPeer()))

        # After receiving a message, we shut down the connection.
        self.transport.loseConnection()

class VOEventSenderFactory(ClientFactory):
    protocol = VOEventSender
    def __init__(self, event):
        self.event = event

class VOEventReceiver(EventHandler, ElementSender):
    """
    A receiver waits for a one-shot submission from a connecting client.
    """
    def stringReceived(self, data):
        """
        Called when a complete new message is received.
        """
        try:
            incoming = xml_document(data)
        except ElementTree.ParseError:
            log.err("Unparsable message received from %s" % str(self.transport.getPeer()))
            return

        # The root element of both VOEvent and Transport packets has a
        # "role" element which we use to identify the type of message we
        # have received.
        if incoming.get('role') in VOEVENT_ROLES:
            log.msg(
                "VOEvent %s received from %s" % (
                    incoming.attrib['ivorn'],
                    str(self.transport.getPeer())
                )
            )
            self.process_event(incoming)
        else:
            log.err("Incomprehensible data received from %s" % str(self.transport.getPeer()))

class VOEventReceiverFactory(ServerFactory):
    protocol = VOEventReceiver
    def __init__(self, local_ivo, validators=[], handlers=[]):
        self.local_ivo = local_ivo
        self.validators = validators
        self.handlers = handlers
<commit_msg>More helpful logging.<commit_after># VOEvent TCP transport protocol using Twisted.
# John Swinbank, <swinbank@transientskp.org>, 2011-12.

# XML parsing using lxml
import lxml.etree as ElementTree

# Twisted protocol definition
from twisted.python import log
from twisted.internet import reactor
from twisted.internet import defer
from twisted.internet.threads import deferToThread
from twisted.protocols.basic import Int32StringReceiver
from twisted.internet.task import LoopingCall
from twisted.internet.protocol import ClientFactory
from twisted.internet.protocol import ServerFactory
from twisted.internet.protocol import ReconnectingClientFactory

# Constructors for transport protocol messages
from .messages import ack, nak
from .messages import iamalive, iamaliveresponse
from .messages import authenticate, authenticateresponse

# Constructor for our perodic test events
from ..utility.voevent import broker_test_message

from ..utility.xml import xml_document

# Constants
VOEVENT_ROLES = ('observation', 'prediction', 'utility', 'test')

"""
Implements the VOEvent Transport Protocol; see
<http://www.ivoa.net/Documents/Notes/VOEventTransport/>.

All messages consist of a 4-byte network ordered payload size followed by the
payload data. Twisted's Int32StringReceiver handles this for us automatically.

There are four different VOEvent protocols to implement:

* VOEventSubscriber

    * Opens connection to remote broker, receives VOEvent messages.

* VOEventPublisher

    * Listens for connections from subscribers, sends VOEvent messages.

* VOEventSender

    * Connects to VOEventReceiver and publishes a new message.

* VOEventReceiver

    * Receives messages from VOEventSender.

To implement the broker, we need the Subscriber, Publisher & Receiver, but not
the Sender. All four are implemented here for completeness.
"""

class ElementSender(Int32StringReceiver):
    """
    Superclass for protocols which will send XML messages which must be
    deserialized from ET elements.
    """
    def send_xml(self, document):
        """
        Takes an xml_document and sents it as XML.
        """
        self.sendString(document.text)

class EventHandler(Int32StringReceiver):
    """
    Superclass for protocols which will receive events (ie, Subscriber and
    Receiver) providing event handling support.
    """
    def validate_event(self, event):
        """
        Call a set of event validators on a given event (an xml_document).

        If a validator raises an exception (ie, calls an errback), the event
        is invalid. Otherwise, it's ok.
        """
        return defer.gatherResults(
            [
                defer.maybeDeferred(validator, event)
                for validator in self.factory.validators
            ],
            consumeErrors=True,
        )

    def handle_event(self, event):
        """
        Call a set of event handlers on a given event (itself an ElementTree
        element).
        """
        return defer.gatherResults(
            [
                defer.maybeDeferred(handler, event)
                for handler in self.factory.handlers
            ],
            consumeErrors=True
        )

    def process_event(self, event):
        def handle_valid(status):
            self.send_xml(
                ack(self.factory.local_ivo, event.attrib['ivorn'])
            )
            self.handle_event(event).addCallbacks(
                lambda x: log.msg("Event processed"),
                lambda x: log.err("Event handlers failed")
            )

        def handle_invalid(failure):
            # Should unpack exception from failure to include useful output
            # in Nak
            self.send_xml(
                nak(self.factory.local_ivo, event.attrib['ivorn'])
            )
            log.msg("Event invalid; discarding")
        self.validate_event(event).addCallbacks(handle_valid, handle_invalid)

class VOEventSubscriber(EventHandler, ElementSender):
    ALIVE_INTERVAL = 120 # If we get no iamalive for ALIVE_INTERVAL seconds,
                         # assume our peer forgot us.
    def __init__(self, filters=[]):
        self.check_alive = reactor.callLater(self.ALIVE_INTERVAL, self.timed_out)
        self.filters = filters

    def timed_out(self):
        log.msg(
            "No iamalive received for %d seconds; disconecting" %
            self.ALIVE_INTERVAL
        )
        self.transport.loseConnection()

    def stringReceived(self, data):
        """
        Called when a complete new message is received.
        """
        try:
            incoming = xml_document(data)
        except ElementTree.ParseError:
            log.err("Unparsable message received")
            return

        # The root element of both VOEvent and Transport packets has a
        # "role" element which we use to identify the type of message we
        # have received.
        if incoming.get('role') == "iamalive":
            log.msg("IAmAlive received from %s" % str(self.transport.getPeer()))
            self.check_alive.cancel()
            self.send_xml(
                iamaliveresponse(self.factory.local_ivo, incoming.find('Origin').text)
            )
            self.check_alive = reactor.callLater(self.ALIVE_INTERVAL, self.timed_out)
        elif incoming.get('role') == "authenticate":
            log.msg("Authenticate received from %s" % str(self.transport.getPeer()))
            self.send_xml(
                authenticateresponse(
                    self.factory.local_ivo,
                    incoming.find('Origin').text,
                    self.filters
                )
            )
        elif incoming.get('role') in VOEVENT_ROLES:
            log.msg(
                "VOEvent %s received from %s" % (
                    incoming.attrib['ivorn'],
                    str(self.transport.getPeer())
                )
            )
            self.process_event(incoming)
        else:
            log.err(
                "Incomprehensible data received from %s (role=%s)" %
                (self.transport.getPeer(), incoming.get("role"))
            )

class VOEventSubscriberFactory(ReconnectingClientFactory):
    protocol = VOEventSubscriber

    def __init__(self, local_ivo, validators=[], handlers=[], filters=[]):
        self.local_ivo = local_ivo
        self.handlers = handlers
        self.validators = validators
        self.filters = filters

    def buildProtocol(self, addr):
        self.resetDelay()
        p = self.protocol(self.filters)
        p.factory = self
        return p

class VOEventPublisher(ElementSender):
    MAX_ALIVE_COUNT = 1      # Drop connection if peer misses too many iamalives
    MAX_OUTSTANDING_ACK = 10 # Drop connection if peer misses too many acks

    def __init__(self):
        self.filters = []

    def connectionMade(self):
        log.msg("New subscriber at %s" % str(self.transport.getPeer()))
        self.factory.publishers.append(self)
        self.alive_count = 0
        self.send_xml(authenticate(self.factory.local_ivo))
        self.outstanding_ack = 0

    def connectionLost(self, reason):
        log.msg("Subscriber at %s disconnected" % str(self.transport.getPeer()))
        self.factory.publishers.remove(self)

    def sendIAmAlive(self):
        if self.alive_count > self.MAX_ALIVE_COUNT:
            log.msg("Peer appears to be dead; dropping connection")
            self.transport.loseConnection()
        elif self.outstanding_ack > self.MAX_OUTSTANDING_ACK:
            log.msg("Peer is not acknowledging events; dropping connection")
            self.transport.loseConnection()
        else:
            self.send_xml(iamalive(self.factory.local_ivo))
            self.alive_count += 1

    def stringReceived(self, data):
        try:
            incoming = xml_document(data)
        except ElementTree.ParseError:
            log.err("Unparsable message received")
            return

        if incoming.get('role') == "iamalive":
            log.msg("IAmAlive received from %s" % str(self.transport.getPeer()))
            self.alive_count -= 1
        elif incoming.get('role') == "ack":
            log.msg("Ack received from %s" % str(self.transport.getPeer()))
            self.outstanding_ack -= 1
        elif incoming.get('role') == "nak":
            log.msg("Nak received from %s; terminating" % str(self.transport.getPeer()))
            self.transport.loseConnection()
        elif incoming.get('role') == "authenticate":
            log.msg("Authentication received from %s" % str(self.transport.getPeer()))
            for xpath in incoming.findall("Meta/filter[@type=\"xpath\"]"):
                log.msg(
                    "Installing filter %s for %s" %
                    (xpath.text, str(self.transport.getPeer()))
                )
            self.filters = [
                ElementTree.XPath(xpath.text)
                for xpath in incoming.findall("Meta/filter[@type=\"xpath\"]")
            ]
        else:
            log.err(
                "Incomprehensible data received from %s (role=%s)" %
                (self.transport.getPeer(), incoming.get("role"))
            )

    def send_event(self, event):
        # Check the event against our filters and, if one or more pass, then
        # we send the event to our subscriber.
        def check_filters(result):
            if not self.filters or any([value for success, value in result if success]):
                log.msg("Event matches filter criteria: forwarding")
                self.send_xml(event)
            else:
                log.msg("Event rejected by filter")

        defer.DeferredList(
            [
                deferToThread(xpath, event.element)
                for xpath in self.filters
            ],
            consumeErrors=True,
        ).addCallback(check_filters)

class VOEventPublisherFactory(ServerFactory):
    IAMALIVE_INTERVAL = 60 # Sent iamalive every IAMALIVE_INTERVAL seconds
    TEST_INTERVAL = 3600   # Sent test event every TEST_INTERVAL seconds
    protocol = VOEventPublisher

    def __init__(self, local_ivo):
        self.local_ivo = local_ivo
        self.publishers = []
        self.alive_loop = LoopingCall(self.sendIAmAlive)
        self.alive_loop.start(self.IAMALIVE_INTERVAL)
        self.test_loop = LoopingCall(self.sendTestEvent)
        self.test_loop.start(self.TEST_INTERVAL)

    def sendIAmAlive(self):
        log.msg("Broadcasting iamalive")
        for publisher in self.publishers:
            publisher.sendIAmAlive()

    def sendTestEvent(self):
        log.msg("Broadcasting test event")
        test_event = broker_test_message(self.local_ivo)
        for publisher in self.publishers:
            publisher.send_event(test_event)

class VOEventSender(ElementSender):
    """
    Implements the VOEvent Transport Protocol; see
    <http://www.ivoa.net/Documents/Notes/VOEventTransport/>.

    All messages consist of a 4-byte network ordered payload size followed by
    the payload data. Twisted's Int32StringReceiver handles this for us
    automatically.
    """
    def connectionMade(self):
        self.send_xml(self.factory.event)

    def stringReceived(self, data):
        """
        Called when a complete new message is received.
        """
        log.msg("Got response from %s" % str(self.transport.getPeer()))
        try:
            incoming = xml_document(data)
        except ElementTree.ParseError:
            log.err("Unparsable message received from %s" % str(self.transport.getPeer()))
            return

        if incoming.get('role') == "ack":
            log.msg("Acknowledgement received from %s" % str(self.transport.getPeer()))
        elif incoming.get('role') == "nak":
            log.err("Nak received: %s refused to accept VOEvent" % str(self.transport.getPeer()))
        else:
            log.err(
                "Incomprehensible data received from %s (role=%s)" %
                (self.transport.getPeer(), incoming.get("role"))
            )

        # After receiving a message, we shut down the connection.
        self.transport.loseConnection()

class VOEventSenderFactory(ClientFactory):
    protocol = VOEventSender
    def __init__(self, event):
        self.event = event

class VOEventReceiver(EventHandler, ElementSender):
    """
    A receiver waits for a one-shot submission from a connecting client.
    """
    def stringReceived(self, data):
        """
        Called when a complete new message is received.
        """
        try:
            incoming = xml_document(data)
        except ElementTree.ParseError:
            log.err("Unparsable message received from %s" % str(self.transport.getPeer()))
            return

        # The root element of both VOEvent and Transport packets has a
        # "role" element which we use to identify the type of message we
        # have received.
        if incoming.get('role') in VOEVENT_ROLES:
            log.msg(
                "VOEvent %s received from %s" % (
                    incoming.attrib['ivorn'],
                    str(self.transport.getPeer())
                )
            )
            self.process_event(incoming)
        else:
            log.err(
                "Incomprehensible data received from %s (role=%s)" %
                (self.transport.getPeer(), incoming.get("role"))
            )

class VOEventReceiverFactory(ServerFactory):
    protocol = VOEventReceiver
    def __init__(self, local_ivo, validators=[], handlers=[]):
        self.local_ivo = local_ivo
        self.validators = validators
        self.handlers = handlers
<|endoftext|>


<commit_before>import os

from django.conf import settings
from django.db import models
from django.core.files.storage import FileSystemStorage
from django.core.urlresolvers import reverse
from django.core.cache import cache

from comics.core.managers import ComicManager

class Comic(models.Model):
    LANGUAGES = (
        ('en', 'English'),
        ('no', 'Norwegian'),
    )

    # Required fields
    name = models.CharField(max_length=100,
        help_text='Name of the comic')
    slug = models.SlugField(max_length=100, unique=True,
        verbose_name='Short name',
        help_text='For file paths and URLs')
    language = models.CharField(max_length=2, choices=LANGUAGES,
        help_text='The language of the comic')

    # Optional fields
    url = models.URLField(verbose_name='URL', blank=True,
        help_text='URL to the official website')
    start_date = models.DateField(blank=True, null=True,
        help_text='First published at')
    end_date = models.DateField(blank=True, null=True,
        help_text='Last published at, if comic has been cancelled')
    rights = models.CharField(max_length=100, blank=True,
        help_text='Author, copyright, and/or licensing information')

    # Automatically populated fields (i.e. for denormalization)
    number_of_sets = models.PositiveIntegerField(default=0,
        help_text='Number of sets the comic is in (automatically updated)')

    objects = ComicManager()

    class Meta:
        db_table = 'comics_comic'
        ordering = ['name']

    def __unicode__(self):
        return u'%s [%s]' % (self.name, self.language)

    def get_absolute_url(self):
        return reverse('comic-latest', kwargs={'comic': self.slug})

    def get_feed_url(self):
        return reverse('feeds', kwargs={'url': 'c/%s' % self.slug})

    def get_redirect_url(self):
        return reverse('redirect', kwargs={'comic': self.slug})

class Release(models.Model):
    # Required fields
    comic = models.ForeignKey(Comic)
    pub_date = models.DateField(verbose_name='publication date')
    images = models.ManyToManyField('Image', related_name='releases')

    # Automatically populated fields
    fetched = models.DateTimeField(auto_now_add=True)

    class Meta:
        db_table = 'comics_release'
        get_latest_by = 'pub_date'

    def __unicode__(self):
        return u'%s published %s' % (self.comic, self.pub_date)

    def get_absolute_url(self):
        return reverse('comic-date', kwargs={
            'comic': self.comic.slug,
            'year': self.pub_date.year,
            'month': self.pub_date.month,
            'day': self.pub_date.day,
        })

    def get_images_first_release(self):
        key = 'release_images_first_release:%s' % self.id
        first = cache.get(key)

        if first is not None:
            return first

        try:
            first = self.images.all()[0].get_first_release()
        except IndexError:
            return

        cache.set(key, first)
        return first

    def set_ordered_images(self, images):
        self._ordered_images = images

    def get_ordered_images(self):
        if not getattr(self, '_ordered_images', []):
            self._ordered_images = list(self.images.order_by('id'))

        return self._ordered_images

# Let all created dirs and files be writable by the group
os.umask(0002)

image_storage = FileSystemStorage(
    location=settings.COMICS_MEDIA_ROOT, base_url=settings.COMICS_MEDIA_URL)

def image_file_path(instance, filename):
    return u'%s/%s/%s' % (instance.comic.slug, filename[0], filename)

class Image(models.Model):
    # Required fields
    comic = models.ForeignKey(Comic)
    file = models.ImageField(storage=image_storage, upload_to=image_file_path,
        height_field='height', width_field='width')
    checksum = models.CharField(max_length=64, db_index=True)

    # Optional fields
    title = models.CharField(max_length=255, blank=True)
    text = models.TextField(blank=True)

    # Automatically populated fields
    fetched = models.DateTimeField(auto_now_add=True)
    height = models.IntegerField()
    width = models.IntegerField()

    class Meta:
        db_table = 'comics_image'

    def delete(self, *args, **kwargs):
        super(Image, self).delete(*args, **kwargs)
        os.remove(self.file.path)

    def __unicode__(self):
        return u'%s image %s' % (self.comic, self.checksum)

    def get_first_release(self):
        return self.releases.select_related('comic').order_by('pub_date')[0]
<commit_msg>Remove explicit file deletion which Django takes care off. Fixes ValueError when deleting images from Django Admin<commit_after>import os

from django.conf import settings
from django.db import models
from django.core.files.storage import FileSystemStorage
from django.core.urlresolvers import reverse
from django.core.cache import cache

from comics.core.managers import ComicManager

class Comic(models.Model):
    LANGUAGES = (
        ('en', 'English'),
        ('no', 'Norwegian'),
    )

    # Required fields
    name = models.CharField(max_length=100,
        help_text='Name of the comic')
    slug = models.SlugField(max_length=100, unique=True,
        verbose_name='Short name',
        help_text='For file paths and URLs')
    language = models.CharField(max_length=2, choices=LANGUAGES,
        help_text='The language of the comic')

    # Optional fields
    url = models.URLField(verbose_name='URL', blank=True,
        help_text='URL to the official website')
    start_date = models.DateField(blank=True, null=True,
        help_text='First published at')
    end_date = models.DateField(blank=True, null=True,
        help_text='Last published at, if comic has been cancelled')
    rights = models.CharField(max_length=100, blank=True,
        help_text='Author, copyright, and/or licensing information')

    # Automatically populated fields (i.e. for denormalization)
    number_of_sets = models.PositiveIntegerField(default=0,
        help_text='Number of sets the comic is in (automatically updated)')

    objects = ComicManager()

    class Meta:
        db_table = 'comics_comic'
        ordering = ['name']

    def __unicode__(self):
        return u'%s [%s]' % (self.name, self.language)

    def get_absolute_url(self):
        return reverse('comic-latest', kwargs={'comic': self.slug})

    def get_feed_url(self):
        return reverse('feeds', kwargs={'url': 'c/%s' % self.slug})

    def get_redirect_url(self):
        return reverse('redirect', kwargs={'comic': self.slug})

class Release(models.Model):
    # Required fields
    comic = models.ForeignKey(Comic)
    pub_date = models.DateField(verbose_name='publication date')
    images = models.ManyToManyField('Image', related_name='releases')

    # Automatically populated fields
    fetched = models.DateTimeField(auto_now_add=True)

    class Meta:
        db_table = 'comics_release'
        get_latest_by = 'pub_date'

    def __unicode__(self):
        return u'%s published %s' % (self.comic, self.pub_date)

    def get_absolute_url(self):
        return reverse('comic-date', kwargs={
            'comic': self.comic.slug,
            'year': self.pub_date.year,
            'month': self.pub_date.month,
            'day': self.pub_date.day,
        })

    def get_images_first_release(self):
        key = 'release_images_first_release:%s' % self.id
        first = cache.get(key)

        if first is not None:
            return first

        try:
            first = self.images.all()[0].get_first_release()
        except IndexError:
            return

        cache.set(key, first)
        return first

    def set_ordered_images(self, images):
        self._ordered_images = images

    def get_ordered_images(self):
        if not getattr(self, '_ordered_images', []):
            self._ordered_images = list(self.images.order_by('id'))

        return self._ordered_images

# Let all created dirs and files be writable by the group
os.umask(0002)

image_storage = FileSystemStorage(
    location=settings.COMICS_MEDIA_ROOT, base_url=settings.COMICS_MEDIA_URL)

def image_file_path(instance, filename):
    return u'%s/%s/%s' % (instance.comic.slug, filename[0], filename)

class Image(models.Model):
    # Required fields
    comic = models.ForeignKey(Comic)
    file = models.ImageField(storage=image_storage, upload_to=image_file_path,
        height_field='height', width_field='width')
    checksum = models.CharField(max_length=64, db_index=True)

    # Optional fields
    title = models.CharField(max_length=255, blank=True)
    text = models.TextField(blank=True)

    # Automatically populated fields
    fetched = models.DateTimeField(auto_now_add=True)
    height = models.IntegerField()
    width = models.IntegerField()

    class Meta:
        db_table = 'comics_image'

    def __unicode__(self):
        return u'%s image %s' % (self.comic, self.checksum)

    def get_first_release(self):
        return self.releases.select_related('comic').order_by('pub_date')[0]
<|endoftext|>


<commit_before>"""distutils.command.bdist_dumb

Implements the Distutils 'bdist_dumb' command (create a "dumb" built
distribution -- i.e., just an archive to be unpacked under $prefix or
$exec_prefix)."""

# created 2000/03/29, Greg Ward

__revision__ = "$Id$"

import os
from distutils.core import Command
from distutils.util import get_platform, create_tree

class bdist_dumb (Command):

    description = "create a \"dumb\" built distribution"

    user_options = [('format=', 'f',
                     "archive format to create (tar, ztar, gztar, zip)"),
                   ]

    default_format = { 'posix': 'gztar',
                       'nt': 'zip', }

    def initialize_options (self):
        self.format = None

    # initialize_options()

    def finalize_options (self):
        if self.format is None:
            try:
                self.format = self.default_format[os.name]
            except KeyError:
                raise DistutilsPlatformError, \
                      ("don't know how to create dumb built distributions " +
                       "on platform %s") % os.name

    # finalize_options()

    def run (self):

        self.run_peer ('build')
        install = self.find_peer ('install')
        inputs = install.get_inputs ()
        outputs = install.get_outputs ()
        assert (len (inputs) == len (outputs))

        # First, strip the installation base directory (prefix or
        # exec-prefix) from all the output filenames.
        self.strip_base_dirs (outputs, install)

        # Figure out where to copy them to: "build/bdist" by default; this
        # directory masquerades as prefix/exec-prefix (ie.  we'll make the
        # archive from 'output_dir').
        build_base = self.get_peer_option ('build', 'build_base')
        output_dir = os.path.join (build_base, "bdist")

        # Copy the built files to the pseudo-installation tree.
        self.make_install_tree (output_dir, inputs, outputs)

        # And make an archive relative to the root of the
        # pseudo-installation tree.
        archive_basename = "%s.%s" % (self.distribution.get_full_name(),
                                      get_platform())
        self.make_archive (archive_basename, self.format,
                           root_dir=output_dir)

    # run()

    def strip_base_dirs (self, outputs, install_cmd):
        # XXX this throws away the prefix/exec-prefix distinction, and
        # means we can only correctly install the resulting archive on a
        # system where prefix == exec-prefix (but at least we can *create*
        # it on one where they differ).  I don't see a way to fix this
        # without either 1) generating two archives, one for prefix and one
        # for exec-prefix, or 2) putting absolute paths in the archive
        # rather than making them relative to one of the prefixes.

        base = install_cmd.install_base + os.sep
        platbase = install_cmd.install_platbase + os.sep
        b_len = len (base)
        pb_len = len (platbase)
        for i in range (len (outputs)):
            if outputs[i][0:b_len] == base:
                outputs[i] = outputs[i][b_len:]
            elif outputs[i][0:pb_len] == platbase:
                outputs[i] = outputs[i][pb_len:]
            else:
                raise DistutilsInternalError, \
                      ("installation output filename '%s' doesn't start " +
                       "with either install_base ('%s') or " +
                       "install_platbase ('%s')") % \
                      (outputs[i], base, platbase)

    # strip_base_dirs()

    def make_install_tree (self, output_dir, inputs, outputs):

        assert (len(inputs) == len(outputs))

        # Create all the directories under 'output_dir' necessary to
        # put 'outputs' there.
        create_tree (output_dir, outputs,
                     verbose=self.verbose, dry_run=self.dry_run)

        # XXX this bit of logic is duplicated in sdist.make_release_tree():
        # would be nice to factor it out...
        if hasattr (os, 'link'):        # can make hard links on this system
            link = 'hard'
            msg = "making hard links in %s..." % output_dir
        else:                           # nope, have to copy
            link = None
            msg = "copying files to %s..." % output_dir

        for i in range (len(inputs)):
            output = os.path.join (output_dir, outputs[i])
            self.copy_file (inputs[i], output, link=link)

    # make_install_tree ()

# class bdist_dumb
<commit_msg>Added code to blow away the pseudo-installation tree and a 'keep_tree' option to disable this (by default, it's false and we clean up).<commit_after>"""distutils.command.bdist_dumb

Implements the Distutils 'bdist_dumb' command (create a "dumb" built
distribution -- i.e., just an archive to be unpacked under $prefix or
$exec_prefix)."""

# created 2000/03/29, Greg Ward

__revision__ = "$Id$"

import os
from distutils.core import Command
from distutils.util import get_platform, create_tree, remove_tree

class bdist_dumb (Command):

    description = "create a \"dumb\" built distribution"

    user_options = [('format=', 'f',
                     "archive format to create (tar, ztar, gztar, zip)"),
                    ('keep-tree', 'k',
                     "keep the pseudo-installation tree around after " +
                     "creating the distribution archive"),
                   ]

    default_format = { 'posix': 'gztar',
                       'nt': 'zip', }

    def initialize_options (self):
        self.format = None
        self.keep_tree = 0

    # initialize_options()

    def finalize_options (self):
        if self.format is None:
            try:
                self.format = self.default_format[os.name]
            except KeyError:
                raise DistutilsPlatformError, \
                      ("don't know how to create dumb built distributions " +
                       "on platform %s") % os.name

    # finalize_options()

    def run (self):

        self.run_peer ('build')
        install = self.find_peer ('install')
        inputs = install.get_inputs ()
        outputs = install.get_outputs ()
        assert (len (inputs) == len (outputs))

        # First, strip the installation base directory (prefix or
        # exec-prefix) from all the output filenames.
        self.strip_base_dirs (outputs, install)

        # Figure out where to copy them to: "build/bdist" by default; this
        # directory masquerades as prefix/exec-prefix (ie.  we'll make the
        # archive from 'output_dir').
        build_base = self.get_peer_option ('build', 'build_base')
        output_dir = os.path.join (build_base, "bdist")

        # Copy the built files to the pseudo-installation tree.
        self.make_install_tree (output_dir, inputs, outputs)

        # And make an archive relative to the root of the
        # pseudo-installation tree.
        archive_basename = "%s.%s" % (self.distribution.get_full_name(),
                                      get_platform())
        print "output_dir = %s" % output_dir
        print "self.format = %s" % self.format
        self.make_archive (archive_basename, self.format,
                           root_dir=output_dir)

        if not self.keep_tree:
            remove_tree (output_dir, self.verbose, self.dry_run)

    # run()

    def strip_base_dirs (self, outputs, install_cmd):
        # XXX this throws away the prefix/exec-prefix distinction, and
        # means we can only correctly install the resulting archive on a
        # system where prefix == exec-prefix (but at least we can *create*
        # it on one where they differ).  I don't see a way to fix this
        # without either 1) generating two archives, one for prefix and one
        # for exec-prefix, or 2) putting absolute paths in the archive
        # rather than making them relative to one of the prefixes.

        base = install_cmd.install_base + os.sep
        platbase = install_cmd.install_platbase + os.sep
        b_len = len (base)
        pb_len = len (platbase)
        for i in range (len (outputs)):
            if outputs[i][0:b_len] == base:
                outputs[i] = outputs[i][b_len:]
            elif outputs[i][0:pb_len] == platbase:
                outputs[i] = outputs[i][pb_len:]
            else:
                raise DistutilsInternalError, \
                      ("installation output filename '%s' doesn't start " +
                       "with either install_base ('%s') or " +
                       "install_platbase ('%s')") % \
                      (outputs[i], base, platbase)

    # strip_base_dirs()

    def make_install_tree (self, output_dir, inputs, outputs):

        assert (len(inputs) == len(outputs))

        # Create all the directories under 'output_dir' necessary to
        # put 'outputs' there.
        create_tree (output_dir, outputs,
                     verbose=self.verbose, dry_run=self.dry_run)

        # XXX this bit of logic is duplicated in sdist.make_release_tree():
        # would be nice to factor it out...
        if hasattr (os, 'link'):        # can make hard links on this system
            link = 'hard'
            msg = "making hard links in %s..." % output_dir
        else:                           # nope, have to copy
            link = None
            msg = "copying files to %s..." % output_dir

        for i in range (len(inputs)):
            output = os.path.join (output_dir, outputs[i])
            self.copy_file (inputs[i], output, link=link)

    # make_install_tree ()

# class bdist_dumb
<|endoftext|>


<commit_before>'''
Collection of tests for orbital correction.

Created on 31/3/13
@author: Ben Davies
'''

import unittest
from glob import glob
from os.path import join
from numpy import nan, isnan, array, reshape, ones, zeros, float32, meshgrid
from numpy.testing import assert_array_equal, assert_array_almost_equal

import algorithm
from shared import Ifg
from orbital import OrbitalCorrectionError
from orbital import orbital_correction, get_design_matrix, get_network_design_matrix
from orbital import INDEPENDENT_METHOD, NETWORK_METHOD, PLANAR, QUADRATIC
from algorithm_tests import MockIfg, sydney_test_setup

class OrbitalTests(unittest.TestCase):
	'''Test cases for the orbital correction component of PyRate.'''

	def setUp(self):
		xstep, ystep = 0.6, 0.7  # fake cell sizes

		shape = (6, 2)
		designm = zeros(shape) # planar design matrix for 2x3 orig array
		designm[0] = [0, 0]
		designm[1] = [0, xstep]
		designm[2] = [ystep, 0]
		designm[3] = [ystep, xstep]
		designm[4] = [2*ystep, 0]
		designm[5] = [2*ystep, xstep]

		self.designm = designm
		self.xstep = xstep
		self.ystep = ystep

	def test_design_matrix_planar(self):
		_, ifgs = sydney_test_setup()

		xs, ys = 2, 3
		m = MockIfg(ifgs[0], xs, ys)
		m.X_STEP = self.xstep
		m.Y_STEP = self.ystep

		# no offsets
		design_mat = get_design_matrix(m, PLANAR, False)
		assert_array_almost_equal(design_mat, self.designm)

		# with offset
		design_mat = get_design_matrix(m, PLANAR, True)
		nys, nxs = self.designm.shape
		nxs += 1 # for offset col
		exp = ones((nys,nxs), dtype=float32)
		exp[:,:2] = self.designm
		assert_array_almost_equal(design_mat, exp)

	def test_design_matrix_quadratic(self):
		dm = []
		ys, xs = (3, 5)
		for y in xrange(ys):
			for x in xrange(xs):
				dm.append([(x * self.xstep)**2,
									(y * self.ystep)**2,
									(x * self.xstep) * (y * self.ystep),
									x * self.xstep,
									y * self.ystep])

		exp_dm = array(dm, dtype=float32) # planar design matrix

		# get design matrix from an ifg
		ifg = Ifg("../../tests/sydney_test/obs/geo_060619-061002.unw")
		ifg.open()
		m = MockIfg(ifg, xs, ys)
		m.X_STEP = self.xstep
		m.Y_STEP = self.ystep

		# no offset
		design_mat = get_design_matrix(m, QUADRATIC, False)
		assert_array_almost_equal(design_mat, exp_dm, decimal=3)

		# with offset
		design_mat = get_design_matrix(m, QUADRATIC, True)
		nys, nxs = exp_dm.shape
		nxs += 1 # for offset col
		exp2 = ones((nys,nxs), dtype=float32)
		exp2[:,:5] = exp_dm
		assert_array_almost_equal(design_mat, exp2, decimal=3)

	def test_ifg_to_vector(self):
		# test numpy reshaping order
		ifg = zeros((2, 3), dtype=float32)
		a = [24, 48, 1000]
		b = [552, 42, 68]
		ifg[0] = a
		ifg[1] = b
		res = reshape(ifg, (len(a+b)))
		exp = array(a + b)
		assert_array_equal(exp, res)

		# ensure order is maintained when converting back
		tmp = reshape(res, ifg.shape)
		assert_array_equal(tmp, ifg)

	def test_orbital_correction(self):
		# verify top level orbital correction function

		def test_results():
			for i, c in zip(ifgs, corrections):
				# are corrections same size as the original array?
				ys, xs = c.shape
				self.assertEqual(i.FILE_LENGTH, ys)
				self.assertEqual(i.WIDTH, xs)
				self.assertFalse(isnan(i.phase_data).all())
				self.assertFalse(isnan(c).all())
				self.assertTrue(c.ptp() != 0) # ensure range of values in grid
				# TODO: do the results need to be checked at all?

		_, ifgs = sydney_test_setup()[:5] # TODO: replace with faked ifglist?
		ifgs[0].phase_data[1, 1:3] = nan # add some NODATA

		# test both models with no offsets
		corrections = orbital_correction(ifgs, PLANAR, INDEPENDENT_METHOD, False)
		test_results()
		corrections = orbital_correction(ifgs, QUADRATIC, INDEPENDENT_METHOD, False)
		test_results()

		# test both with offsets
		corrections = orbital_correction(ifgs, PLANAR, INDEPENDENT_METHOD)
		test_results()
		corrections = orbital_correction(ifgs, QUADRATIC, INDEPENDENT_METHOD)
		test_results()

class OrbitalCorrectionNetwork(unittest.TestCase):
	'''TODO'''

	def setUp(self):
		base = "../../tests/sydney_test/obs"
		self.ifgs = [Ifg(join(base, p)) for p in IFMS5.split()]

	# TODO: check DM/correction with NaN rows

	def test_invalid_ifgs_arg(self):
		args = (PLANAR, True) # some default args
		self.assertRaises(OrbitalCorrectionError, get_network_design_matrix, [], *args)
		self.assertRaises(OrbitalCorrectionError, get_network_design_matrix, [None], *args)
		# TODO: what is the minimum required? 2 ifgs/3 epochs?)

	def test_invalid_degree_arg(self):
		oex = OrbitalCorrectionError
		ifgs = [None] * 5

		for deg in range(-5,1):
			self.assertRaises(oex, get_network_design_matrix, ifgs, deg, True)
		for deg in range(3,6):
			self.assertRaises(oex, get_network_design_matrix, [None], deg, True)

	def test_design_matrix_shape(self):
		# verify shape of design matrix is correct
		num_ifgs = len(self.ifgs)
		num_epochs = num_ifgs + 1
		num_params = 2 # without offsets 1st

		# without offsets
		head = self.ifgs[0]
		exp_num_rows = head.FILE_LENGTH * head.WIDTH * num_ifgs
		exp_num_cols = num_epochs * num_params
		exp_shape = (exp_num_rows, exp_num_cols)
		act_dm = get_network_design_matrix(self.ifgs, PLANAR, False)
		self.assertEqual(exp_shape, act_dm.shape)

		# with offsets
		num_params += 1
		exp_num_cols = num_epochs * num_params
		exp_shape = (exp_num_rows, exp_num_cols)
		act_dm = get_network_design_matrix(self.ifgs, PLANAR, True)
		self.assertEqual(exp_shape, act_dm.shape)

		# quadratic method without offsets
		num_params = 5 # without offsets
		exp_num_cols = num_epochs * num_params
		exp_shape = (exp_num_rows, exp_num_cols)
		act_dm = get_network_design_matrix(self.ifgs, QUADRATIC, False)
		self.assertEqual(exp_shape, act_dm.shape)

		# with offsets
		num_params += 1
		exp_num_cols = num_epochs * num_params
		exp_shape = (exp_num_rows, exp_num_cols)
		act_dm = get_network_design_matrix(self.ifgs, QUADRATIC, True)
		self.assertEqual(exp_shape, act_dm.shape)

	def test_planar_matrix_content(self):
		# verify creation of sparse matrix comprised of smaller design matricies
		# TODO: do the master/slave indices need to be sorted? Sorted = less ambiguity

		dates = []
		masters = [i.MASTER for i in self.ifgs]
		slaves = [i.SLAVE for i in self.ifgs]
		date_ids = algorithm.master_slave_ids(masters + slaves)

		ncells = self.ifgs[0].FILE_LENGTH * self.ifgs[0].WIDTH
		ncoef = 2 # planar without offsets

		# TODO: test with/without offsets
		act_dm = get_network_design_matrix(self.ifgs, PLANAR, False)
		self.assertNotEqual(act_dm.ptp(), 0)

		for i, ifg in enumerate(self.ifgs):
			exp_dm = dmplanar(ifg, ifg.X_STEP, ifg.Y_STEP, False)

			# use slightly refactored version of Hua's code to test
			ib1 = i * ncells # start row for subsetting the sparse matrix
			ib2 = (i+1) * ncells # last row of subset of sparse matrix
			jbm = date_ids[ifg.MASTER] * ncoef # starting row index for master
			jbs = date_ids[ifg.SLAVE] * ncoef # row start for slave
			assert_array_almost_equal(-exp_dm, act_dm[ib1:ib2, jbm:jbm+ncoef])
			assert_array_almost_equal(exp_dm, act_dm[ib1:ib2, jbs:jbs+ncoef])

# FIXME: add derived field to ifgs to convert X|Y_STEP degrees to metres
def dmplanar(ifg, xs, ys, offset=False):
	ncells = ifg.WIDTH * ifg.FILE_LENGTH
	ncoef = 2
	if offset is True:
		ncoef += 1

	out = ones((ncells, ncoef), dtype=float32)
	X, Y = meshgrid(range(ifg.WIDTH), range(ifg.FILE_LENGTH))
	X = X.reshape(ncells) * xs
	Y = Y.reshape(ncells) * ys
	out[:,0] = Y
	out[:,1] = X
	return out

	# TODO:
	#def test_network_design_matrix_quadratic(self):
		#pass

# small dummy ifg list to limit # of ifgs
IFMS5 = """geo_060828-061211.unw
geo_061106-061211.unw
geo_061106-070115.unw
geo_061106-070326.unw
geo_070326-070917.unw
"""
<commit_msg>Expanded network DM test to incorporate optional offsets.<commit_after>'''
Collection of tests for orbital correction.

Created on 31/3/13
@author: Ben Davies
'''

import unittest
from glob import glob
from os.path import join
from numpy import nan, isnan, array, reshape, ones, zeros, float32, meshgrid
from numpy.testing import assert_array_equal, assert_array_almost_equal

import algorithm
from shared import Ifg
from orbital import OrbitalCorrectionError
from orbital import orbital_correction, get_design_matrix, get_network_design_matrix
from orbital import INDEPENDENT_METHOD, NETWORK_METHOD, PLANAR, QUADRATIC
from algorithm_tests import MockIfg, sydney_test_setup

class OrbitalTests(unittest.TestCase):
	'''Test cases for the orbital correction component of PyRate.'''

	def setUp(self):
		xstep, ystep = 0.6, 0.7  # fake cell sizes

		shape = (6, 2)
		designm = zeros(shape) # planar design matrix for 2x3 orig array
		designm[0] = [0, 0]
		designm[1] = [0, xstep]
		designm[2] = [ystep, 0]
		designm[3] = [ystep, xstep]
		designm[4] = [2*ystep, 0]
		designm[5] = [2*ystep, xstep]

		self.designm = designm
		self.xstep = xstep
		self.ystep = ystep

	def test_design_matrix_planar(self):
		_, ifgs = sydney_test_setup()

		xs, ys = 2, 3
		m = MockIfg(ifgs[0], xs, ys)
		m.X_STEP = self.xstep
		m.Y_STEP = self.ystep

		# no offsets
		design_mat = get_design_matrix(m, PLANAR, False)
		assert_array_almost_equal(design_mat, self.designm)

		# with offset
		design_mat = get_design_matrix(m, PLANAR, True)
		nys, nxs = self.designm.shape
		nxs += 1 # for offset col
		exp = ones((nys,nxs), dtype=float32)
		exp[:,:2] = self.designm
		assert_array_almost_equal(design_mat, exp)

	def test_design_matrix_quadratic(self):
		dm = []
		ys, xs = (3, 5)
		for y in xrange(ys):
			for x in xrange(xs):
				dm.append([(x * self.xstep)**2,
									(y * self.ystep)**2,
									(x * self.xstep) * (y * self.ystep),
									x * self.xstep,
									y * self.ystep])

		exp_dm = array(dm, dtype=float32) # planar design matrix

		# get design matrix from an ifg
		ifg = Ifg("../../tests/sydney_test/obs/geo_060619-061002.unw")
		ifg.open()
		m = MockIfg(ifg, xs, ys)
		m.X_STEP = self.xstep
		m.Y_STEP = self.ystep

		# no offset
		design_mat = get_design_matrix(m, QUADRATIC, False)
		assert_array_almost_equal(design_mat, exp_dm, decimal=3)

		# with offset
		design_mat = get_design_matrix(m, QUADRATIC, True)
		nys, nxs = exp_dm.shape
		nxs += 1 # for offset col
		exp2 = ones((nys,nxs), dtype=float32)
		exp2[:,:5] = exp_dm
		assert_array_almost_equal(design_mat, exp2, decimal=3)

	def test_ifg_to_vector(self):
		# test numpy reshaping order
		ifg = zeros((2, 3), dtype=float32)
		a = [24, 48, 1000]
		b = [552, 42, 68]
		ifg[0] = a
		ifg[1] = b
		res = reshape(ifg, (len(a+b)))
		exp = array(a + b)
		assert_array_equal(exp, res)

		# ensure order is maintained when converting back
		tmp = reshape(res, ifg.shape)
		assert_array_equal(tmp, ifg)

	def test_orbital_correction(self):
		# verify top level orbital correction function

		def test_results():
			for i, c in zip(ifgs, corrections):
				# are corrections same size as the original array?
				ys, xs = c.shape
				self.assertEqual(i.FILE_LENGTH, ys)
				self.assertEqual(i.WIDTH, xs)
				self.assertFalse(isnan(i.phase_data).all())
				self.assertFalse(isnan(c).all())
				self.assertTrue(c.ptp() != 0) # ensure range of values in grid
				# TODO: do the results need to be checked at all?

		_, ifgs = sydney_test_setup()[:5] # TODO: replace with faked ifglist?
		ifgs[0].phase_data[1, 1:3] = nan # add some NODATA

		# test both models with no offsets
		corrections = orbital_correction(ifgs, PLANAR, INDEPENDENT_METHOD, False)
		test_results()
		corrections = orbital_correction(ifgs, QUADRATIC, INDEPENDENT_METHOD, False)
		test_results()

		# test both with offsets
		corrections = orbital_correction(ifgs, PLANAR, INDEPENDENT_METHOD)
		test_results()
		corrections = orbital_correction(ifgs, QUADRATIC, INDEPENDENT_METHOD)
		test_results()

class OrbitalCorrectionNetwork(unittest.TestCase):
	'''TODO'''

	def setUp(self):
		base = "../../tests/sydney_test/obs"
		self.ifgs = [Ifg(join(base, p)) for p in IFMS5.split()]

	# TODO: check DM/correction with NaN rows

	def test_invalid_ifgs_arg(self):
		args = (PLANAR, True) # some default args
		self.assertRaises(OrbitalCorrectionError, get_network_design_matrix, [], *args)
		self.assertRaises(OrbitalCorrectionError, get_network_design_matrix, [None], *args)
		# TODO: what is the minimum required? 2 ifgs/3 epochs?)

	def test_invalid_degree_arg(self):
		oex = OrbitalCorrectionError
		ifgs = [None] * 5

		for deg in range(-5,1):
			self.assertRaises(oex, get_network_design_matrix, ifgs, deg, True)
		for deg in range(3,6):
			self.assertRaises(oex, get_network_design_matrix, [None], deg, True)

	def test_design_matrix_shape(self):
		# verify shape of design matrix is correct
		num_ifgs = len(self.ifgs)
		num_epochs = num_ifgs + 1
		num_params = 2 # without offsets 1st

		# without offsets
		head = self.ifgs[0]
		exp_num_rows = head.FILE_LENGTH * head.WIDTH * num_ifgs
		exp_num_cols = num_epochs * num_params
		exp_shape = (exp_num_rows, exp_num_cols)
		act_dm = get_network_design_matrix(self.ifgs, PLANAR, False)
		self.assertEqual(exp_shape, act_dm.shape)

		# with offsets
		num_params += 1
		exp_num_cols = num_epochs * num_params
		exp_shape = (exp_num_rows, exp_num_cols)
		act_dm = get_network_design_matrix(self.ifgs, PLANAR, True)
		self.assertEqual(exp_shape, act_dm.shape)

		# quadratic method without offsets
		num_params = 5 # without offsets
		exp_num_cols = num_epochs * num_params
		exp_shape = (exp_num_rows, exp_num_cols)
		act_dm = get_network_design_matrix(self.ifgs, QUADRATIC, False)
		self.assertEqual(exp_shape, act_dm.shape)

		# with offsets
		num_params += 1
		exp_num_cols = num_epochs * num_params
		exp_shape = (exp_num_rows, exp_num_cols)
		act_dm = get_network_design_matrix(self.ifgs, QUADRATIC, True)
		self.assertEqual(exp_shape, act_dm.shape)

	def test_planar_matrix_content(self):
		# verify creation of sparse matrix comprised of smaller design matricies
		# TODO: do the master/slave indices need to be sorted? Sorted = less ambiguity

		dates = []
		for ifg in self.ifgs:
			dates += list(ifg.DATE12)
		date_ids = algorithm.master_slave_ids(dates)

		# TODO: add num_cells() method to ifgs
		ncells = self.ifgs[0].FILE_LENGTH * self.ifgs[0].WIDTH
		ncoef = 2 # planar without offsets

		for offset in [False, True]:
			if offset: ncoef += 1
			act_dm = get_network_design_matrix(self.ifgs, PLANAR, offset)
			self.assertNotEqual(act_dm.ptp(), 0)

			for i, ifg in enumerate(self.ifgs):
				exp_dm = dmplanar(ifg, ifg.X_STEP, ifg.Y_STEP, offset)

				# use slightly refactored version of Hua's code to test
				ib1 = i * ncells # start row for subsetting the sparse matrix
				ib2 = (i+1) * ncells # last row of subset of sparse matrix
				jbm = date_ids[ifg.MASTER] * ncoef # starting row index for master
				jbs = date_ids[ifg.SLAVE] * ncoef # row start for slave
				assert_array_almost_equal(-exp_dm, act_dm[ib1:ib2, jbm:jbm+ncoef])
				assert_array_almost_equal(exp_dm, act_dm[ib1:ib2, jbs:jbs+ncoef])

# FIXME: add derived field to ifgs to convert X|Y_STEP degrees to metres
def dmplanar(ifg, xs, ys, offset=False):
	ncells = ifg.WIDTH * ifg.FILE_LENGTH
	ncoef = 2
	if offset is True:
		ncoef += 1

	out = ones((ncells, ncoef), dtype=float32)
	X, Y = meshgrid(range(ifg.WIDTH), range(ifg.FILE_LENGTH))
	X = X.reshape(ncells) * xs
	Y = Y.reshape(ncells) * ys
	out[:,0] = Y
	out[:,1] = X
	return out

	# TODO:
	#def test_network_design_matrix_quadratic(self):
		#pass

# small dummy ifg list to limit # of ifgs
IFMS5 = """geo_060828-061211.unw
geo_061106-061211.unw
geo_061106-070115.unw
geo_061106-070326.unw
geo_070326-070917.unw
"""
<|endoftext|>


<commit_before>from __future__ import division

try:
  # try importing scipy.linalg before any cctbx modules, otherwise we
  # sometimes get a segmentation fault/core dump if it is imported after
  # scipy.linalg is a dependency of sklearn.cluster.DBSCAN
  import scipy.linalg # import dependency
except ImportError, e:
  pass

from libtbx.phil import command_line
import iotbx.phil
from dials.util.options import OptionParser
from dials.util.options import flatten_reflections
from dials.util.options import flatten_datablocks
from dials.util.options import flatten_experiments

help_message = '''

This program attempts to perform autoindexing on strong spots output by the
program dials.find_spots. The program is called with a "datablock.json" file
(as generated by dials.import) and a "strong.pickle" file (as generated by
dials.find_spots). If one or more lattices are identified given the input
list of strong spots, then the crystal orientation and experimental geometry
are refined to minimise the differences between the observed and predicted
spot centroids. The program will output an "experiments.json" file which
is similar to the input "datablock.json" file, but with the addition of the
crystal model(s), and an "indexed.pickle" file which is similar to the input
"strong.pickle" file, but with the addition of miller indices and predicted
spot centroids.

dials.index provides both one-dimensional and three-dimensional fast Fourier
transform (FFT) based methods. These can be chosen by setting the parameters
indexing.method=fft1d or indexing.method=fft3d. By default the program searches
for a primitive lattice, and then proceeds with refinement in space group P1.
If the unit_cell and space_group parameters are set, then the program will
only accept solutions which are consistent with these parameters. Space group
constraints will be enforced in refinement as appropriate.

Examples::

  dials.index datablock.json strong.pickle

  dials.index datablock.json strong.pickle unit_cell=37,79,79,90,90,90 space_group=P43212

  dials.index datablock.json strong.pickle indexing.method=fft1d

'''

phil_scope = iotbx.phil.parse("""\
include scope dials.algorithms.indexing.indexer.master_phil_scope
output {
  experiments = experiments.json
    .type = path
  reflections = indexed.pickle
    .type = path
}

verbosity = 1
  .type = int(value_min=0)
  .help = "The verbosity level"
""", process_includes=True)

def run(args):
  import libtbx.load_env
  from libtbx.utils import Sorry
  from dials.util import log
  usage = "%s [options] datablock.json strong.pickle" %libtbx.env.dispatcher_name

  parser = OptionParser(
    usage=usage,
    phil=phil_scope,
    read_reflections=True,
    read_datablocks=True,
    read_experiments=True,
    check_format=False,
    epilog=help_message)

  params, options = parser.parse_args(show_diff_phil=True)

  # Configure the logging
  log.config(
    params.verbosity, info='dials.index.log', debug='dials.index.debug.log')

  datablocks = flatten_datablocks(params.input.datablock)
  experiments = flatten_experiments(params.input.experiments)
  reflections = flatten_reflections(params.input.reflections)

  if len(datablocks) == 0:
    if len(experiments) > 0:
      imagesets = experiments.imagesets()
    else:
      parser.print_help()
      return
  elif len(datablocks) > 1:
    raise Sorry("Only one DataBlock can be processed at a time")
  else:
    imagesets = datablocks[0].extract_imagesets()
  if len(experiments):
    known_crystal_models = experiments.crystals()
  else:
    known_crystal_models = None
  assert(len(reflections) == 1)
  reflections = reflections[0]

  for imageset in imagesets:
    if (imageset.get_goniometer() is not None and
        imageset.get_scan() is not None and
        imageset.get_scan().get_oscillation()[1] == 0):
      imageset.set_goniometer(None)
      imageset.set_scan(None)

  if known_crystal_models is not None:
    from dials.algorithms.indexing.known_orientation \
         import indexer_known_orientation
    idxr = indexer_known_orientation(
      reflections, imagesets, params, known_crystal_models)
  elif params.indexing.method == "fft3d":
    from dials.algorithms.indexing.fft3d import indexer_fft3d
    idxr = indexer_fft3d(reflections, imagesets, params=params)
  elif params.indexing.method == "fft1d":
    from dials.algorithms.indexing.fft1d import indexer_fft1d
    idxr = indexer_fft1d(reflections, imagesets, params=params)
  elif params.indexing.method == "real_space_grid_search":
    from dials.algorithms.indexing.real_space_grid_search \
         import indexer_real_space_grid_search
    idxr = indexer_real_space_grid_search(reflections, imagesets, params=params)
  refined_experiments = idxr.refined_experiments
  refined_reflections = idxr.refined_reflections
  if len(refined_experiments):
    idxr.export_as_json(refined_experiments,
                        file_name=params.output.experiments)
    idxr.export_reflections(
      refined_reflections, file_name=params.output.reflections)

  return

if __name__ == '__main__':
  import sys
  run(sys.argv[1:])
<commit_msg>More graceful error trapping if no reflections passed<commit_after>from __future__ import division

try:
  # try importing scipy.linalg before any cctbx modules, otherwise we
  # sometimes get a segmentation fault/core dump if it is imported after
  # scipy.linalg is a dependency of sklearn.cluster.DBSCAN
  import scipy.linalg # import dependency
except ImportError, e:
  pass

from libtbx.phil import command_line
import iotbx.phil
from dials.util.options import OptionParser
from dials.util.options import flatten_reflections
from dials.util.options import flatten_datablocks
from dials.util.options import flatten_experiments

help_message = '''

This program attempts to perform autoindexing on strong spots output by the
program dials.find_spots. The program is called with a "datablock.json" file
(as generated by dials.import) and a "strong.pickle" file (as generated by
dials.find_spots). If one or more lattices are identified given the input
list of strong spots, then the crystal orientation and experimental geometry
are refined to minimise the differences between the observed and predicted
spot centroids. The program will output an "experiments.json" file which
is similar to the input "datablock.json" file, but with the addition of the
crystal model(s), and an "indexed.pickle" file which is similar to the input
"strong.pickle" file, but with the addition of miller indices and predicted
spot centroids.

dials.index provides both one-dimensional and three-dimensional fast Fourier
transform (FFT) based methods. These can be chosen by setting the parameters
indexing.method=fft1d or indexing.method=fft3d. By default the program searches
for a primitive lattice, and then proceeds with refinement in space group P1.
If the unit_cell and space_group parameters are set, then the program will
only accept solutions which are consistent with these parameters. Space group
constraints will be enforced in refinement as appropriate.

Examples::

  dials.index datablock.json strong.pickle

  dials.index datablock.json strong.pickle unit_cell=37,79,79,90,90,90 space_group=P43212

  dials.index datablock.json strong.pickle indexing.method=fft1d

'''

phil_scope = iotbx.phil.parse("""\
include scope dials.algorithms.indexing.indexer.master_phil_scope
output {
  experiments = experiments.json
    .type = path
  reflections = indexed.pickle
    .type = path
}

verbosity = 1
  .type = int(value_min=0)
  .help = "The verbosity level"
""", process_includes=True)

def run(args):
  import libtbx.load_env
  from libtbx.utils import Sorry
  from dials.util import log
  usage = "%s [options] datablock.json strong.pickle" %libtbx.env.dispatcher_name

  parser = OptionParser(
    usage=usage,
    phil=phil_scope,
    read_reflections=True,
    read_datablocks=True,
    read_experiments=True,
    check_format=False,
    epilog=help_message)

  params, options = parser.parse_args(show_diff_phil=True)

  # Configure the logging
  log.config(
    params.verbosity, info='dials.index.log', debug='dials.index.debug.log')

  datablocks = flatten_datablocks(params.input.datablock)
  experiments = flatten_experiments(params.input.experiments)
  reflections = flatten_reflections(params.input.reflections)

  if len(datablocks) == 0:
    if len(experiments) > 0:
      imagesets = experiments.imagesets()
    else:
      parser.print_help()
      return
  elif len(datablocks) > 1:
    raise Sorry("Only one DataBlock can be processed at a time")
  else:
    imagesets = datablocks[0].extract_imagesets()
  if len(experiments):
    known_crystal_models = experiments.crystals()
  else:
    known_crystal_models = None

  if len(reflections) == 0:
    raise Sorry("No reflection lists found in input")
  if len(reflections) > 1:
    raise Sorry("Multiple reflections lists provided in input")

  assert(len(reflections) == 1)
  reflections = reflections[0]

  for imageset in imagesets:
    if (imageset.get_goniometer() is not None and
        imageset.get_scan() is not None and
        imageset.get_scan().get_oscillation()[1] == 0):
      imageset.set_goniometer(None)
      imageset.set_scan(None)

  if known_crystal_models is not None:
    from dials.algorithms.indexing.known_orientation \
         import indexer_known_orientation
    idxr = indexer_known_orientation(
      reflections, imagesets, params, known_crystal_models)
  elif params.indexing.method == "fft3d":
    from dials.algorithms.indexing.fft3d import indexer_fft3d
    idxr = indexer_fft3d(reflections, imagesets, params=params)
  elif params.indexing.method == "fft1d":
    from dials.algorithms.indexing.fft1d import indexer_fft1d
    idxr = indexer_fft1d(reflections, imagesets, params=params)
  elif params.indexing.method == "real_space_grid_search":
    from dials.algorithms.indexing.real_space_grid_search \
         import indexer_real_space_grid_search
    idxr = indexer_real_space_grid_search(reflections, imagesets, params=params)
  refined_experiments = idxr.refined_experiments
  refined_reflections = idxr.refined_reflections
  if len(refined_experiments):
    idxr.export_as_json(refined_experiments,
                        file_name=params.output.experiments)
    idxr.export_reflections(
      refined_reflections, file_name=params.output.reflections)

  return

if __name__ == '__main__':
  import sys
  run(sys.argv[1:])
<|endoftext|>


<commit_before># libavg - Media Playback Engine.
# Copyright (C) 2003-2008 Ulrich von Zadow
#
# This library is free software; you can redistribute it and/or
# modify it under the terms of the GNU Lesser General Public
# License as published by the Free Software Foundation; either
# version 2 of the License, or (at your option) any later version.
#
# This library 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
# Lesser General Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public
# License along with this library; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
#
# Current versions can be found at www.libavg.de
#
# Original author of this file is Martin Heistermann <mh at sponc dot de>
#

import os
import gc

from libavg import avg, Point2D
import graph
from mtemu import MTemu
import apphelpers

try:
    from alib.clicktest import ClickTest
except ImportError:
    ClickTest = None

DEFAULT_RESOLUTION = (640, 480)

g_player = avg.Player.get()
g_log = avg.Logger.get()

class AppStarter(object):
    """Starts an AVGApp"""
    def __init__(self, appClass, resolution=DEFAULT_RESOLUTION,
            debugWindowSize=None, fakeFullscreen=False):
        if fakeFullscreen:
            if os.name != 'nt':
                raise RuntimeError('fakeFullscreen works only under windows')

            g_player.setTimeout(0, self.__fakeFullscreen)

        resolution = Point2D(resolution)
        testMode = not 'AVG_DEPLOY' in os.environ

        if testMode and debugWindowSize is not None:
            debugWindowSize = Point2D(debugWindowSize)
        else:
            debugWindowSize = Point2D(0, 0)

        width = int(resolution.x)
        height = int(resolution.y)
        # dynamic avg creation in order to set resolution
        g_player.loadString("""
<?xml version="1.0"?>
<!DOCTYPE avg SYSTEM "../../libavg/doc/avg.dtd">
<avg width="%(width)u" height="%(height)u">
</avg>""" % {
            'width': width,
            'height': height,
            })
        rootNode = g_player.getRootNode()

        self._appNode = g_player.createNode('div',{
            'opacity': 0,
            'sensitive': False})
        # the app should know the size of its "root" node:
        self._appNode.size = rootNode.size
        rootNode.appendChild(self._appNode)

        self.__showMTEvents = False
        self.__touchViss = {}
        self.__touchVisOverlay = avg.DivNode(sensitive=False, size=resolution,
                parent=rootNode)

        g_player.showCursor(testMode)

        if appClass.fakeFullscreen:
            fullscreen = False
        else:
            fullscreen = not testMode

        g_player.setResolution(
                fullscreen,
                int(debugWindowSize.x), int(debugWindowSize.y),
                0 # color depth
                )

        self._onBeforePlay()
        g_player.setTimeout(0, self._onStart)
        self._appInstance = appClass(self._appNode)
        self._keyManager = apphelpers.KeyManager(self._appInstance.onKeyDown,
                self._appInstance.onKeyUp)

        self._setupDefaultKeys()

        self._appInstance.setStarter(self)
        g_player.play()
        self._appInstance.exit()

    def _setupDefaultKeys(self):
        pass

    def _onBeforePlay(self):
        pass

    def _onStart(self):
        self._appInstance.init()
        self._appNode.opacity = 1
        self._appNode.sensitive = True
        self._activeApp = self._appInstance
        self._appInstance.enter()

    def __fakeFullscreen(self):
        import win32gui
        import win32con
        import win32api

        def findWindow(title):
            def enumWinProc(h, lparams):
                lparams.append(h)
            winList=[]
            win32gui.EnumWindows(enumWinProc, winList)
            for hwnd in winList:
                curTitle = win32gui.GetWindowText(hwnd)
                if win32gui.IsWindowVisible(hwnd) and title == curTitle:
                    return hwnd
            return None

        hDesk = win32gui.GetDesktopWindow()
        (desktopLeft, desktopTop, desktopRight,
                desktopBottom) = win32gui.GetWindowRect(hDesk)
        w = findWindow("AVG Renderer")
        offSetX = 2
        offSetY = 3
        win32gui.SetWindowPos(w, win32con.HWND_TOP,
                -(win32api.GetSystemMetrics(win32con.SM_CYBORDER) + offSetX),
                -(win32api.GetSystemMetrics(win32con.SM_CYCAPTION) + offSetY),
                desktopRight, desktopBottom + 30, 0)

class AVGAppStarter(AppStarter):
    def __init__(self, *args, **kwargs):
        self.__graphs = 0
        self._mtEmu = None
        self.__showingMemGraph = False
        self.__showingFrGraph = False
        self.__runningClickTest = False
        self.__notifyNode = None
        # self._initClickTest()

        super(AVGAppStarter, self).__init__(*args, **kwargs)

    def _setupDefaultKeys(self):
        self._keyManager.bindKey('o', self.__dumpObjects, 'Dump objects')
        self._keyManager.bindKey('m', self.__showMemoryUsage, 'Show memory usage')
        self._keyManager.bindKey('f', self.__showFrameRateUsage, 'Show frameTime usage')
        # self._keyManager.bindKey('.', self.__switchClickTest, 'Start clicktest')
        self._keyManager.bindKey('t', self.__switchMtemu, 'Activate multitouch emulation')
        self._keyManager.bindKey('e', self.__switchShowMTEvents, 'Show multitouch events')
        self._keyManager.bindKey('s', self.__screenshot, 'Take screenshot')

    # def _initClickTest(self):
    #     if ClickTest:
    #         self._clickTest = ClickTest(self._appNode, multiClick=False)
    #     else:
    #         self._clickTest = None

    def __dumpObjects(self):
        gc.collect()
        testHelper = g_player.getTestHelper()
        testHelper.dumpObjects()
        print "Num anims: ", avg.getNumRunningAnims()
        print "Num python objects: ", len(gc.get_objects())

    def __showMemoryUsage(self):
        if self.__showingMemGraph:
            self.__memGraph.delete()
            self.__memGraph = None
            self.__graphs = self.__graphs -1
            if(self.__graphs == 1 ):
                self.__frGraph.setYpos(10)
        else:
            self.__memGraph = graph.MemGraph("Memory Graph",
                    getValue = avg.getMemoryUsage)
            self.__graphs = self.__graphs +1
            if(self.__graphs > 1 ):
                self.__memGraph.setYpos(190)
        self.__showingMemGraph = not(self.__showingMemGraph)

    def __showFrameRateUsage(self):
        if self.__showingFrGraph:
            self.__frGraph.delete()
            self.__frGraph = None
            self.__graphs = self.__graphs -1
            if(self.__graphs == 1 ):
                self.__memGraph.setYpos(10)
        else:
            self.__frGraph = graph.FrameRateGraph("FrameTime Graph",
                    getValue = g_player.getFrameTime)
            self.__graphs = self.__graphs +1
            if(self.__graphs >1):
                self.__frGraph.setYpos(190)
        self.__showingFrGraph = not(self.__showingFrGraph)

    def __switchClickTest(self):
        if self._clickTest:
            if self.__runningClickTest:
                g_log.trace(g_log.APP, 'Stopping clicktest')
                self._clickTest.stop()
            else:
                g_log.trace(g_log.APP, 'Starting clicktest')
                self._clickTest.start()

            self.__runningClickTest = not self.__runningClickTest

    def __switchMtemu(self):
        if self._mtEmu is None:
            self._mtEmu = MTemu()
            self._keyManager.bindKey('left ctrl',
                    self._mtEmu.changeMode, 'switch event mode')
            self._keyManager.bindKey('right ctrl',
                    self._mtEmu.changeMode, 'switch event mode')
            self._keyManager.bindKey('left shift',
                    self._mtEmu.multiTouch, 'create 2nd event')
            self._keyManager.bindKey('right shift',
                    self._mtEmu.multiTouch, 'create 2nd event')
            self._keyManager.bindKey('left shift',
                    self._mtEmu.multiTouch, 'create 2nd event', 'up')
            self._keyManager.bindKey('right shift',
                    self._mtEmu.multiTouch, 'create 2nd event', 'up')

        else:
            self.unbindKey('left ctrl')
            self.unbindKey('right ctrl')
            self.unbindKey('left shift')
            self.unbindKey('right shift')
            self._mtEmu.delete()
            self._mtEmu = None

    def __switchShowMTEvents(self):
        self.__showMTEvents = not(self.__showMTEvents)
        if self.__showMTEvents:
            self.__oldEventHook = g_player.getEventHook()
            g_player.setEventHook(self.__showMTEventHook)
        else:
            g_player.setEventHook(self.__oldEventHook)
            for id, touchVis in self.__touchViss.items():
                touchVis.unlink(True)
            self.__touchViss = {}

    def __showMTEventHook(self, event):
        if (isinstance(event, avg.TouchEvent) and event.source == avg.TOUCH and
                (event.type == avg.CURSORDOWN or event.type == avg.CURSORMOTION or
                 event.type == avg.CURSORUP)):
            try:
                touchVis = self.__touchViss[event.cursorid]
            except KeyError:
                touchVis = apphelpers.TouchVisualization(event,
                        parent=self.__touchVisOverlay)
                self.__touchViss[event.cursorid] = touchVis
            if event.type == avg.CURSORDOWN:
                pass
            elif event.type == avg.CURSORMOTION:
                touchVis.move(event)
            elif event.type == avg.CURSORUP:
                touchVis.unlink(True)
                del self.__touchViss[event.cursorid]
        if self.__oldEventHook:
            return self.__oldEventHook()
        else:
            return False

    def __killNotifyNode(self):
        if self.__notifyNode:
            self.__notifyNode.unlink()
            self.__notifyNode = None

    def __screenshot(self):
        fnum = 0
        fnameTemplate = 'screenshot-%03d.png'
        while os.path.exists(fnameTemplate % fnum):
            fnum += 1

        try:
            g_player.screenshot().save('screenshot-%03d.png' % fnum)
        except RuntimeError:
            text = 'Cannot save snapshot file'
        else:
            text='Screenshot saved as ' + fnameTemplate % fnum

        self.__killNotifyNode()

        self.__notifyNode = avg.WordsNode(
            text=text, x=g_player.getRootNode().width - 50,
            y=g_player.getRootNode().height - 50, alignment='right', fontsize=20,
            sensitive=False, parent=g_player.getRootNode())

        g_player.setTimeout(2000, self.__killNotifyNode)
<commit_msg>libavg_avgapp: more AppStarter refactoring, all tests pass now<commit_after># libavg - Media Playback Engine.
# Copyright (C) 2003-2008 Ulrich von Zadow
#
# This library is free software; you can redistribute it and/or
# modify it under the terms of the GNU Lesser General Public
# License as published by the Free Software Foundation; either
# version 2 of the License, or (at your option) any later version.
#
# This library 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
# Lesser General Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public
# License along with this library; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
#
# Current versions can be found at www.libavg.de
#
# Original author of this file is Martin Heistermann <mh at sponc dot de>
#

import os
import gc

from libavg import avg, Point2D
import graph
from mtemu import MTemu
import apphelpers

try:
    from alib.clicktest import ClickTest
except ImportError:
    ClickTest = None

DEFAULT_RESOLUTION = (640, 480)

g_player = avg.Player.get()
g_log = avg.Logger.get()

class AppStarter(object):
    """Starts an AVGApp"""
    def __init__(self, appClass, resolution=DEFAULT_RESOLUTION,
            debugWindowSize=None, fakeFullscreen=False):
        if fakeFullscreen:
            if os.name != 'nt':
                raise RuntimeError('fakeFullscreen works only under windows')

            g_player.setTimeout(0, self.__fakeFullscreen)

        resolution = Point2D(resolution)
        testMode = not 'AVG_DEPLOY' in os.environ

        if testMode and debugWindowSize is not None:
            debugWindowSize = Point2D(debugWindowSize)
        else:
            debugWindowSize = Point2D(0, 0)

        self._setupBaseDivs(resolution)

        g_player.showCursor(testMode)

        if fakeFullscreen:
            fullscreen = False
        else:
            fullscreen = not testMode

        g_player.setResolution(
                fullscreen,
                int(debugWindowSize.x), int(debugWindowSize.y),
                0 # color depth
                )

        self._startApp(appClass)

    def _startApp(self, appClass):
        self._onBeforePlay()
        g_player.setTimeout(0, self._onStart)
        self._appInstance = appClass(self._appNode)
        self._keyManager = apphelpers.KeyManager(
                self._appInstance.onKeyDown,
                self._appInstance.onKeyUp)

        self._setupDefaultKeys()

        self._appInstance.setStarter(self)
        g_player.play()
        self._appInstance.exit()

    def _setupBaseDivs(self, resolution):
        g_player.loadString("""
<?xml version="1.0"?>
<!DOCTYPE avg SYSTEM "../../libavg/doc/avg.dtd">
<avg width="%s" height="%s">
</avg>""" % (resolution.x, resolution.y))

        rootNode = g_player.getRootNode()
        self._appNode = avg.DivNode(opacity=0, sensitive=False,
                size=rootNode.size, parent=rootNode)

    def _setupDefaultKeys(self):
        pass

    def _onBeforePlay(self):
        pass

    def _onStart(self):
        self._appInstance.init()
        self._appNode.opacity = 1
        self._appNode.sensitive = True
        self._activeApp = self._appInstance
        self._appInstance.enter()

    def __fakeFullscreen(self):
        import win32gui
        import win32con
        import win32api

        def findWindow(title):
            def enumWinProc(h, lparams):
                lparams.append(h)
            winList=[]
            win32gui.EnumWindows(enumWinProc, winList)
            for hwnd in winList:
                curTitle = win32gui.GetWindowText(hwnd)
                if win32gui.IsWindowVisible(hwnd) and title == curTitle:
                    return hwnd
            return None

        hDesk = win32gui.GetDesktopWindow()
        (desktopLeft, desktopTop, desktopRight,
                desktopBottom) = win32gui.GetWindowRect(hDesk)
        w = findWindow("AVG Renderer")
        offSetX = 2
        offSetY = 3
        win32gui.SetWindowPos(w, win32con.HWND_TOP,
                -(win32api.GetSystemMetrics(win32con.SM_CYBORDER) + offSetX),
                -(win32api.GetSystemMetrics(win32con.SM_CYCAPTION) + offSetY),
                desktopRight, desktopBottom + 30, 0)

class AVGAppStarter(AppStarter):
    def __init__(self, *args, **kwargs):
        self.__graphs = 0
        self._mtEmu = None
        self.__showingMemGraph = False
        self.__showingFrGraph = False
        self.__runningClickTest = False
        self.__notifyNode = None
        self.__showMTEvents = False

        super(AVGAppStarter, self).__init__(*args, **kwargs)

    def _setupDefaultKeys(self):
        super(AVGAppStarter, self)._setupDefaultKeys()
        self._keyManager.bindKey('o', self.__dumpObjects, 'Dump objects')
        self._keyManager.bindKey('m', self.__showMemoryUsage, 'Show memory usage')
        self._keyManager.bindKey('f', self.__showFrameRateUsage, 'Show frameTime usage')
        self._keyManager.bindKey('.', self.__switchClickTest, 'Start clicktest')
        self._keyManager.bindKey('t', self.__switchMtemu, 'Activate multitouch emulation')
        self._keyManager.bindKey('e', self.__switchShowMTEvents, 'Show multitouch events')
        self._keyManager.bindKey('s', self.__screenshot, 'Take screenshot')

    def _onBeforePlay(self):
        super(AVGAppStarter, self)._onBeforePlay()
        rootNode = g_player.getRootNode()
        self.__touchViss = {}
        self.__touchVisOverlay = avg.DivNode(sensitive=False,
                size=rootNode.size,
                parent=rootNode)
        self.__initClickTest()

    def __initClickTest(self):
        if ClickTest:
            self._clickTest = ClickTest(self._appNode, multiClick=False)
        else:
            self._clickTest = None

    def __dumpObjects(self):
        gc.collect()
        testHelper = g_player.getTestHelper()
        testHelper.dumpObjects()
        print "Num anims: ", avg.getNumRunningAnims()
        print "Num python objects: ", len(gc.get_objects())

    def __showMemoryUsage(self):
        if self.__showingMemGraph:
            self.__memGraph.delete()
            self.__memGraph = None
            self.__graphs = self.__graphs -1
            if(self.__graphs == 1 ):
                self.__frGraph.setYpos(10)
        else:
            self.__memGraph = graph.MemGraph("Memory Graph",
                    getValue = avg.getMemoryUsage)
            self.__graphs = self.__graphs +1
            if(self.__graphs > 1 ):
                self.__memGraph.setYpos(190)
        self.__showingMemGraph = not(self.__showingMemGraph)

    def __showFrameRateUsage(self):
        if self.__showingFrGraph:
            self.__frGraph.delete()
            self.__frGraph = None
            self.__graphs = self.__graphs -1
            if(self.__graphs == 1 ):
                self.__memGraph.setYpos(10)
        else:
            self.__frGraph = graph.FrameRateGraph("FrameTime Graph",
                    getValue = g_player.getFrameTime)
            self.__graphs = self.__graphs +1
            if(self.__graphs >1):
                self.__frGraph.setYpos(190)
        self.__showingFrGraph = not(self.__showingFrGraph)

    def __switchClickTest(self):
        if self._clickTest:
            if self.__runningClickTest:
                g_log.trace(g_log.APP, 'Stopping clicktest')
                self._clickTest.stop()
            else:
                g_log.trace(g_log.APP, 'Starting clicktest')
                self._clickTest.start()

            self.__runningClickTest = not self.__runningClickTest

    def __switchMtemu(self):
        if self._mtEmu is None:
            self._mtEmu = MTemu()
            self._keyManager.bindKey('left ctrl',
                    self._mtEmu.changeMode, 'switch event mode')
            self._keyManager.bindKey('right ctrl',
                    self._mtEmu.changeMode, 'switch event mode')
            self._keyManager.bindKey('left shift',
                    self._mtEmu.multiTouch, 'create 2nd event')
            self._keyManager.bindKey('right shift',
                    self._mtEmu.multiTouch, 'create 2nd event')
            self._keyManager.bindKey('left shift',
                    self._mtEmu.multiTouch, 'create 2nd event', 'up')
            self._keyManager.bindKey('right shift',
                    self._mtEmu.multiTouch, 'create 2nd event', 'up')

        else:
            self.unbindKey('left ctrl')
            self.unbindKey('right ctrl')
            self.unbindKey('left shift')
            self.unbindKey('right shift')
            self._mtEmu.delete()
            self._mtEmu = None

    def __switchShowMTEvents(self):
        self.__showMTEvents = not(self.__showMTEvents)
        if self.__showMTEvents:
            self.__oldEventHook = g_player.getEventHook()
            g_player.setEventHook(self.__showMTEventHook)
        else:
            g_player.setEventHook(self.__oldEventHook)
            for id, touchVis in self.__touchViss.items():
                touchVis.unlink(True)
            self.__touchViss = {}

    def __showMTEventHook(self, event):
        if (isinstance(event, avg.TouchEvent) and event.source == avg.TOUCH and
                (event.type == avg.CURSORDOWN or event.type == avg.CURSORMOTION or
                 event.type == avg.CURSORUP)):
            try:
                touchVis = self.__touchViss[event.cursorid]
            except KeyError:
                touchVis = apphelpers.TouchVisualization(event,
                        parent=self.__touchVisOverlay)
                self.__touchViss[event.cursorid] = touchVis
            if event.type == avg.CURSORDOWN:
                pass
            elif event.type == avg.CURSORMOTION:
                touchVis.move(event)
            elif event.type == avg.CURSORUP:
                touchVis.unlink(True)
                del self.__touchViss[event.cursorid]
        if self.__oldEventHook:
            return self.__oldEventHook()
        else:
            return False

    def __killNotifyNode(self):
        if self.__notifyNode:
            self.__notifyNode.unlink()
            self.__notifyNode = None

    def __screenshot(self):
        fnum = 0
        fnameTemplate = 'screenshot-%03d.png'
        while os.path.exists(fnameTemplate % fnum):
            fnum += 1

        try:
            g_player.screenshot().save('screenshot-%03d.png' % fnum)
        except RuntimeError:
            text = 'Cannot save snapshot file'
        else:
            text='Screenshot saved as ' + fnameTemplate % fnum

        self.__killNotifyNode()

        self.__notifyNode = avg.WordsNode(
            text=text, x=g_player.getRootNode().width - 50,
            y=g_player.getRootNode().height - 50, alignment='right', fontsize=20,
            sensitive=False, parent=g_player.getRootNode())

        g_player.setTimeout(2000, self.__killNotifyNode)
<|endoftext|>