EventAggregator

Annotated EventAggregatorSupport/Resources.py

363:ae498018f49d
2013-05-07 Paul Boddie Moved the resource parsing into a separate function in anticipation of the handling of resource collections such as RSS or Atom feeds.
paul@347 1
# -*- coding: iso-8859-1 -*-
paul@347 2
"""
paul@347 3
    MoinMoin - EventAggregator resource acquisition and access
paul@347 4
paul@347 5
    @copyright: 2008, 2009, 2010, 2011, 2012, 2013 by Paul Boddie <paul@boddie.org.uk>
paul@347 6
    @license: GNU GPL (v2 or later), see COPYING.txt for details.
paul@347 7
"""
paul@347 8
paul@347 9
from EventAggregatorSupport.Filter import *
paul@347 10
from EventAggregatorSupport.Types import *
paul@347 11
paul@347 12
from DateSupport import Date, Month
paul@347 13
from MoinSupport import *
paul@359 14
from MoinRemoteSupport import getCachedResource, getCachedResourceMetadata
paul@347 15
paul@347 16
import codecs
paul@347 17
import urllib
paul@347 18
paul@347 19
try:
paul@347 20
    from cStringIO import StringIO
paul@347 21
except ImportError:
paul@347 22
    from StringIO import StringIO
paul@347 23
paul@347 24
try:
paul@347 25
    import vCalendar
paul@347 26
except ImportError:
paul@347 27
    vCalendar = None
paul@347 28
paul@347 29
# Obtaining event containers and events from such containers.
paul@347 30
paul@347 31
def getEventPages(pages):
paul@347 32
paul@347 33
    "Return a list of events found on the given 'pages'."
paul@347 34
paul@347 35
    # Get real pages instead of result pages.
paul@347 36
paul@347 37
    return map(EventPage, pages)
paul@347 38
paul@347 39
def getAllEventSources(request):
paul@347 40
paul@347 41
    "Return all event sources defined in the Wiki using the 'request'."
paul@347 42
paul@347 43
    sources_page = getattr(request.cfg, "event_aggregator_sources_page", "EventSourcesDict")
paul@347 44
paul@347 45
    # Remote sources are accessed via dictionary page definitions.
paul@347 46
paul@347 47
    return getWikiDict(sources_page, request)
paul@347 48
paul@347 49
def getEventResources(sources, calendar_start, calendar_end, request):
paul@347 50
paul@347 51
    """
paul@347 52
    Return resource objects for the given 'sources' using the given
paul@347 53
    'calendar_start' and 'calendar_end' to parameterise requests to the sources,
paul@347 54
    and the 'request' to access configuration settings in the Wiki.
paul@347 55
    """
paul@347 56
paul@347 57
    sources_dict = getAllEventSources(request)
paul@347 58
    if not sources_dict:
paul@347 59
        return []
paul@347 60
paul@347 61
    # Use dates for the calendar limits.
paul@347 62
paul@347 63
    if isinstance(calendar_start, Date):
paul@347 64
        pass
paul@347 65
    elif isinstance(calendar_start, Month):
paul@347 66
        calendar_start = calendar_start.as_date(1)
paul@347 67
paul@347 68
    if isinstance(calendar_end, Date):
paul@347 69
        pass
paul@347 70
    elif isinstance(calendar_end, Month):
paul@347 71
        calendar_end = calendar_end.as_date(-1)
paul@347 72
paul@347 73
    resources = []
paul@347 74
paul@347 75
    for source in sources:
paul@347 76
        try:
paul@347 77
            details = sources_dict[source].split()
paul@347 78
            url = details[0]
paul@347 79
            format = (details[1:] or ["ical"])[0]
paul@347 80
        except (KeyError, ValueError):
paul@347 81
            pass
paul@347 82
        else:
paul@363 83
            resource = getEventResourcesFromSource(url, format, calendar_start, calendar_end, request)
paul@363 84
            if resource:
paul@363 85
                resources.append(resource)
paul@347 86
paul@347 87
    return resources
paul@347 88
paul@363 89
def getEventResourcesFromSource(url, format, calendar_start, calendar_end, request):
paul@363 90
paul@363 91
    """
paul@363 92
    Return a resource object for the given 'url' providing content in the
paul@363 93
    specified 'format', using the given 'calendar_start' and 'calendar_end' to
paul@363 94
    parameterise requests to the sources and the 'request' to access
paul@363 95
    configuration settings in the Wiki.
paul@363 96
    """
paul@363 97
paul@363 98
    # Prevent local file access.
paul@363 99
paul@363 100
    if url.startswith("file:"):
paul@363 101
        return None
paul@363 102
paul@363 103
    # Parameterise the URL.
paul@363 104
    # Where other parameters are used, care must be taken to encode them
paul@363 105
    # properly.
paul@363 106
paul@363 107
    url = url.replace("{start}", urllib.quote_plus(calendar_start and str(calendar_start) or ""))
paul@363 108
    url = url.replace("{end}", urllib.quote_plus(calendar_end and str(calendar_end) or ""))
paul@363 109
paul@363 110
    # Get a parser.
paul@363 111
    # NOTE: This could be done reactively by choosing a parser based on
paul@363 112
    # NOTE: the content type provided by the URL.
paul@363 113
paul@363 114
    if format == "ical" and vCalendar is not None:
paul@363 115
        parser = vCalendar.parse
paul@363 116
        resource_cls = EventCalendar
paul@363 117
        required_content_type = "text/calendar"
paul@363 118
    else:
paul@363 119
        return None
paul@363 120
paul@363 121
    # Obtain the resource, using a cached version if appropriate.
paul@363 122
paul@363 123
    max_cache_age = int(getattr(request.cfg, "event_aggregator_max_cache_age", "300"))
paul@363 124
    data = getCachedResource(request, url, "EventAggregator", "wiki", max_cache_age)
paul@363 125
    if not data:
paul@363 126
        return None
paul@363 127
paul@363 128
    # Process the entry, parsing the content.
paul@363 129
paul@363 130
    f = StringIO(data)
paul@363 131
    try:
paul@363 132
        # Get the content type and encoding, making sure that the data
paul@363 133
        # can be parsed.
paul@363 134
paul@363 135
        url, content_type, encoding, metadata = getCachedResourceMetadata(f)
paul@363 136
paul@363 137
        if content_type != required_content_type:
paul@363 138
            return None
paul@363 139
paul@363 140
        # Send the data to the parser.
paul@363 141
paul@363 142
        uf = codecs.getreader(encoding or "utf-8")(f)
paul@363 143
        try:
paul@363 144
            return resource_cls(url, parser(uf), metadata)
paul@363 145
        finally:
paul@363 146
            uf.close()
paul@363 147
    finally:
paul@363 148
        f.close()
paul@363 149
paul@347 150
def getEventsFromResources(resources):
paul@347 151
paul@347 152
    "Return a list of events supplied by the given event 'resources'."
paul@347 153
paul@347 154
    events = []
paul@347 155
paul@347 156
    for resource in resources:
paul@347 157
paul@347 158
        # Get all events described by the resource.
paul@347 159
paul@347 160
        for event in resource.getEvents():
paul@347 161
paul@347 162
            # Remember the event.
paul@347 163
paul@347 164
            events.append(event)
paul@347 165
paul@347 166
    return events
paul@347 167
paul@347 168
# Page-related functions.
paul@347 169
paul@347 170
def fillEventPageFromTemplate(template_page, new_page, event_details, category_pagenames):
paul@347 171
paul@347 172
    """
paul@347 173
    Using the given 'template_page', complete the 'new_page' by copying the
paul@347 174
    template and adding the given 'event_details' (a dictionary of event
paul@347 175
    fields), setting also the 'category_pagenames' to define category
paul@347 176
    membership.
paul@347 177
    """
paul@347 178
paul@347 179
    event_page = EventPage(template_page)
paul@347 180
    new_event_page = EventPage(new_page)
paul@347 181
    new_event_page.copyPage(event_page)
paul@347 182
paul@347 183
    if new_event_page.getFormat() == "wiki":
paul@347 184
        new_event = Event(new_event_page, event_details)
paul@347 185
        new_event_page.setEvents([new_event])
paul@347 186
        new_event_page.setCategoryMembership(category_pagenames)
paul@347 187
        new_event_page.flushEventDetails()
paul@347 188
paul@347 189
    return new_event_page.getBody()
paul@347 190
paul@347 191
# Event selection from request parameters.
paul@347 192
paul@347 193
def getEventsUsingParameters(category_names, search_pattern, remote_sources,
paul@347 194
    calendar_start, calendar_end, resolution, request):
paul@347 195
paul@347 196
    "Get the events according to the resolution of the calendar."
paul@347 197
paul@347 198
    if search_pattern:
paul@347 199
        results         = getPagesForSearch(search_pattern, request)
paul@347 200
    else:
paul@347 201
        results         = []
paul@347 202
paul@347 203
    results            += getAllCategoryPages(category_names, request)
paul@347 204
    pages               = getPagesFromResults(results, request)
paul@347 205
    events              = getEventsFromResources(getEventPages(pages))
paul@347 206
    events             += getEventsFromResources(getEventResources(remote_sources, calendar_start, calendar_end, request))
paul@347 207
    all_shown_events    = getEventsInPeriod(events, getCalendarPeriod(calendar_start, calendar_end))
paul@347 208
    earliest, latest    = getEventLimits(all_shown_events)
paul@347 209
paul@347 210
    # Get a concrete period of time.
paul@347 211
paul@347 212
    first, last = getConcretePeriod(calendar_start, calendar_end, earliest, latest, resolution)
paul@347 213
paul@347 214
    return all_shown_events, first, last
paul@347 215
paul@347 216
# vim: tabstop=4 expandtab shiftwidth=4