Rename calendar into ical to avoid name collisions.
This commit is contained in:
236
radicale/ical.py
Normal file
236
radicale/ical.py
Normal file
@ -0,0 +1,236 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
# This file is part of Radicale Server - Calendar Server
|
||||
# Copyright © 2008-2010 Guillaume Ayoub
|
||||
# Copyright © 2008 Nicolas Kandel
|
||||
# Copyright © 2008 Pascal Halter
|
||||
#
|
||||
# This library is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 3 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 General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with Radicale. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
"""
|
||||
Radicale calendar classes.
|
||||
|
||||
Define the main classes of a calendar as seen from the server.
|
||||
|
||||
"""
|
||||
|
||||
import os
|
||||
import codecs
|
||||
|
||||
from radicale import config
|
||||
|
||||
|
||||
FOLDER = os.path.expanduser(config.get("storage", "folder"))
|
||||
|
||||
|
||||
# This function overrides the builtin ``open`` function for this module
|
||||
# pylint: disable-msg=W0622
|
||||
def open(path, mode="r"):
|
||||
"""Open file at ``path`` with ``mode``, automagically managing encoding."""
|
||||
return codecs.open(path, mode, config.get("encoding", "stock"))
|
||||
# pylint: enable-msg=W0622
|
||||
|
||||
|
||||
def serialize(headers=(), timezones=(), events=(), todos=()):
|
||||
items = ["BEGIN:VCALENDAR"]
|
||||
for part in (headers, timezones, todos, events):
|
||||
if part:
|
||||
items.append("\n".join(item.text for item in part))
|
||||
items.append("END:VCALENDAR")
|
||||
return "\n".join(items)
|
||||
|
||||
|
||||
class Header(object):
|
||||
"""Internal header class."""
|
||||
def __init__(self, text):
|
||||
"""Initialize header from ``text``."""
|
||||
self.text = text
|
||||
|
||||
|
||||
class Event(object):
|
||||
"""Internal event class."""
|
||||
tag = "VEVENT"
|
||||
|
||||
def __init__(self, text):
|
||||
"""Initialize event from ``text``."""
|
||||
self.text = text
|
||||
|
||||
@property
|
||||
def etag(self):
|
||||
"""Etag from event."""
|
||||
return '"%s"' % hash(self.text)
|
||||
|
||||
|
||||
class Todo(object):
|
||||
"""Internal todo class."""
|
||||
# This is not a TODO!
|
||||
# pylint: disable-msg=W0511
|
||||
tag = "VTODO"
|
||||
# pylint: enable-msg=W0511
|
||||
|
||||
def __init__(self, text):
|
||||
"""Initialize todo from ``text``."""
|
||||
self.text = text
|
||||
|
||||
@property
|
||||
def etag(self):
|
||||
"""Etag from todo."""
|
||||
return '"%s"' % hash(self.text)
|
||||
|
||||
|
||||
class Timezone(object):
|
||||
"""Internal timezone class."""
|
||||
tag = "VTIMEZONE"
|
||||
|
||||
def __init__(self, text):
|
||||
"""Initialize timezone from ``text``."""
|
||||
lines = text.splitlines()
|
||||
for line in lines:
|
||||
if line.startswith("TZID:"):
|
||||
self.name = line.replace("TZID:", "")
|
||||
break
|
||||
|
||||
self.text = text
|
||||
|
||||
|
||||
class Calendar(object):
|
||||
"""Internal calendar class."""
|
||||
def __init__(self, path):
|
||||
"""Initialize the calendar with ``cal`` and ``user`` parameters."""
|
||||
# TODO: Use properties from the calendar configuration
|
||||
self.encoding = "utf-8"
|
||||
self.owner = path.split("/")[0]
|
||||
self.path = os.path.join(FOLDER, path.replace("/", os.path.sep))
|
||||
self.ctag = self.etag
|
||||
|
||||
@staticmethod
|
||||
def _parse(text, obj):
|
||||
"""Find ``obj.tag`` items in ``text`` text.
|
||||
|
||||
Return a list of items of type ``obj``.
|
||||
|
||||
"""
|
||||
items = []
|
||||
|
||||
lines = text.splitlines()
|
||||
in_item = False
|
||||
item_lines = []
|
||||
|
||||
for line in lines:
|
||||
if line.startswith("BEGIN:%s" % obj.tag):
|
||||
in_item = True
|
||||
item_lines = []
|
||||
|
||||
if in_item:
|
||||
item_lines.append(line)
|
||||
if line.startswith("END:%s" % obj.tag):
|
||||
items.append(obj("\n".join(item_lines)))
|
||||
|
||||
return items
|
||||
|
||||
def append(self, text):
|
||||
"""Append ``text`` to calendar."""
|
||||
self.ctag = self.etag
|
||||
|
||||
timezones = self.timezones
|
||||
events = self.events
|
||||
todos = self.todos
|
||||
|
||||
for new_timezone in self._parse(text, Timezone):
|
||||
if new_timezone.name not in [timezone.name
|
||||
for timezone in timezones]:
|
||||
timezones.append(new_timezone)
|
||||
|
||||
for new_event in self._parse(text, Event):
|
||||
if new_event.etag not in [event.etag for event in events]:
|
||||
events.append(new_event)
|
||||
|
||||
for new_todo in self._parse(text, Todo):
|
||||
if new_todo.etag not in [todo.etag for todo in todos]:
|
||||
todos.append(new_todo)
|
||||
|
||||
self.write(timezones=timezones, events=events, todos=todos)
|
||||
|
||||
def remove(self, etag):
|
||||
"""Remove object named ``etag`` from the calendar."""
|
||||
self.ctag = self.etag
|
||||
todos = [todo for todo in self.todos if todo.etag != etag]
|
||||
events = [event for event in self.events if event.etag != etag]
|
||||
|
||||
self.write(todos=todos, events=events)
|
||||
|
||||
def replace(self, etag, text):
|
||||
"""Replace objet named ``etag`` by ``text`` in the calendar."""
|
||||
self.ctag = self.etag
|
||||
self.remove(etag)
|
||||
self.append(text)
|
||||
|
||||
def write(self, headers=None, timezones=None, events=None, todos=None):
|
||||
"""Write calendar with given parameters."""
|
||||
headers = headers or self.headers or (
|
||||
Header("PRODID:-//Radicale//NONSGML Radicale Server//EN"),
|
||||
Header("VERSION:2.0"))
|
||||
timezones = timezones or self.timezones
|
||||
events = events or self.events
|
||||
todos = todos or self.todos
|
||||
|
||||
# Create folder if absent
|
||||
if not os.path.exists(os.path.dirname(self.path)):
|
||||
os.makedirs(os.path.dirname(self.path))
|
||||
|
||||
text = serialize(headers, timezones, events, todos)
|
||||
return open(self.path, "w").write(text)
|
||||
|
||||
@property
|
||||
def etag(self):
|
||||
"""Etag from calendar."""
|
||||
return '"%s"' % hash(self.text)
|
||||
|
||||
@property
|
||||
def text(self):
|
||||
"""Calendar as plain text."""
|
||||
try:
|
||||
return open(self.path).read()
|
||||
except IOError:
|
||||
return ""
|
||||
|
||||
@property
|
||||
def headers(self):
|
||||
"""Find headers items in calendar."""
|
||||
header_lines = []
|
||||
|
||||
lines = self.text.splitlines()
|
||||
for line in lines:
|
||||
if line.startswith("PRODID:"):
|
||||
header_lines.append(Header(line))
|
||||
for line in lines:
|
||||
if line.startswith("VERSION:"):
|
||||
header_lines.append(Header(line))
|
||||
|
||||
return header_lines
|
||||
|
||||
@property
|
||||
def events(self):
|
||||
"""Get list of ``Event`` items in calendar."""
|
||||
return self._parse(self.text, Event)
|
||||
|
||||
@property
|
||||
def todos(self):
|
||||
"""Get list of ``Todo`` items in calendar."""
|
||||
return self._parse(self.text, Todo)
|
||||
|
||||
@property
|
||||
def timezones(self):
|
||||
"""Get list of ``Timezome`` items in calendar."""
|
||||
return self._parse(self.text, Timezone)
|
Reference in New Issue
Block a user