EventAggregator

Annotated EventAggregatorSupport/Resources.py

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