Merge ical/support/calendar modules.
This commit is contained in:
parent
21a743fcde
commit
9a07ec71d3
@ -33,15 +33,19 @@ should have been included in this package.
|
||||
|
||||
"""
|
||||
|
||||
import os
|
||||
import base64
|
||||
import socket
|
||||
# Manage Python2/3 different modules
|
||||
# pylint: disable-msg=F0401
|
||||
try:
|
||||
from http import client, server
|
||||
except ImportError:
|
||||
import httplib as client
|
||||
import BaseHTTPServer as server
|
||||
# pylint: enable-msg=F0401
|
||||
|
||||
from radicale import acl, calendar, config, support, xmlutils
|
||||
from radicale import acl, calendar, config, xmlutils
|
||||
|
||||
|
||||
def _check(request, function):
|
||||
@ -77,7 +81,9 @@ class HTTPSServer(HTTPServer):
|
||||
def __init__(self, address, handler):
|
||||
"""Create server by wrapping HTTP socket in an SSL socket."""
|
||||
# Fails with Python 2.5, import if needed
|
||||
# pylint: disable-msg=F0401
|
||||
import ssl
|
||||
# pylint: enable-msg=F0401
|
||||
|
||||
HTTPServer.__init__(self, address, handler)
|
||||
self.socket = ssl.wrap_socket(
|
||||
@ -98,14 +104,15 @@ class CalendarHTTPHandler(server.BaseHTTPRequestHandler):
|
||||
check_rights = lambda function: lambda request: _check(request, function)
|
||||
|
||||
@property
|
||||
def calendar(self):
|
||||
def _calendar(self):
|
||||
"""The ``calendar.Calendar`` object corresponding to the given path."""
|
||||
path = self.path.strip("/").split("/")
|
||||
if len(path) >= 2:
|
||||
cal = "%s/%s" % (path[0], path[1])
|
||||
return calendar.Calendar("radicale", cal)
|
||||
# ``normpath`` should clean malformed and malicious request paths
|
||||
attributes = os.path.normpath(self.path.strip("/")).split("/")
|
||||
if len(attributes) >= 2:
|
||||
path = "%s/%s" % (attributes[0], attributes[1])
|
||||
return calendar.Calendar(path)
|
||||
|
||||
def decode(self, text):
|
||||
def _decode(self, text):
|
||||
"""Try to decode text according to various parameters."""
|
||||
# List of charsets to try
|
||||
charsets = []
|
||||
@ -134,7 +141,7 @@ class CalendarHTTPHandler(server.BaseHTTPRequestHandler):
|
||||
@check_rights
|
||||
def do_GET(self):
|
||||
"""Manage GET request."""
|
||||
answer = self.calendar.vcalendar.encode(self._encoding)
|
||||
answer = self._calendar.read().encode(self._encoding)
|
||||
|
||||
self.send_response(client.OK)
|
||||
self.send_header("Content-Length", len(answer))
|
||||
@ -145,7 +152,7 @@ class CalendarHTTPHandler(server.BaseHTTPRequestHandler):
|
||||
def do_DELETE(self):
|
||||
"""Manage DELETE request."""
|
||||
obj = self.headers.get("If-Match", None)
|
||||
answer = xmlutils.delete(obj, self.calendar, self.path)
|
||||
answer = xmlutils.delete(obj, self._calendar, self.path)
|
||||
|
||||
self.send_response(client.NO_CONTENT)
|
||||
self.send_header("Content-Length", len(answer))
|
||||
@ -162,7 +169,7 @@ class CalendarHTTPHandler(server.BaseHTTPRequestHandler):
|
||||
def do_PROPFIND(self):
|
||||
"""Manage PROPFIND request."""
|
||||
xml_request = self.rfile.read(int(self.headers["Content-Length"]))
|
||||
answer = xmlutils.propfind(xml_request, self.calendar, self.path)
|
||||
answer = xmlutils.propfind(xml_request, self._calendar, self.path)
|
||||
|
||||
self.send_response(client.MULTI_STATUS)
|
||||
self.send_header("DAV", "1, calendar-access")
|
||||
@ -173,10 +180,10 @@ class CalendarHTTPHandler(server.BaseHTTPRequestHandler):
|
||||
@check_rights
|
||||
def do_PUT(self):
|
||||
"""Manage PUT request."""
|
||||
ical_request = self.decode(
|
||||
ical_request = self._decode(
|
||||
self.rfile.read(int(self.headers["Content-Length"])))
|
||||
obj = self.headers.get("If-Match", None)
|
||||
xmlutils.put(ical_request, self.calendar, self.path, obj)
|
||||
xmlutils.put(ical_request, self._calendar, self.path, obj)
|
||||
|
||||
self.send_response(client.CREATED)
|
||||
|
||||
@ -184,9 +191,11 @@ class CalendarHTTPHandler(server.BaseHTTPRequestHandler):
|
||||
def do_REPORT(self):
|
||||
"""Manage REPORT request."""
|
||||
xml_request = self.rfile.read(int(self.headers["Content-Length"]))
|
||||
answer = xmlutils.report(xml_request, self.calendar, self.path)
|
||||
answer = xmlutils.report(xml_request, self._calendar, self.path)
|
||||
|
||||
self.send_response(client.MULTI_STATUS)
|
||||
self.send_header("Content-Length", len(answer))
|
||||
self.end_headers()
|
||||
self.wfile.write(answer)
|
||||
|
||||
# pylint: enable-msg=C0103
|
||||
|
@ -25,93 +25,209 @@ Define the main classes of a calendar as seen from the server.
|
||||
|
||||
"""
|
||||
|
||||
from radicale import support
|
||||
import os
|
||||
import codecs
|
||||
|
||||
from radicale import config
|
||||
|
||||
|
||||
def hash_tag(vcalendar):
|
||||
"""Hash an vcalendar string."""
|
||||
return str(hash(vcalendar))
|
||||
FOLDER = os.path.expanduser(config.get("storage", "folder"))
|
||||
|
||||
|
||||
class Calendar(object):
|
||||
"""Internal calendar class."""
|
||||
def __init__(self, user, cal):
|
||||
"""Initialize the calendar with ``cal`` and ``user`` parameters."""
|
||||
# TODO: Use properties from the calendar configuration
|
||||
self.support = support.load()
|
||||
self.encoding = "utf-8"
|
||||
self.owner = "radicale"
|
||||
self.user = user
|
||||
self.cal = cal
|
||||
self.version = "2.0"
|
||||
self.ctag = hash_tag(self.vcalendar)
|
||||
|
||||
def append(self, vcalendar):
|
||||
"""Append vcalendar to the calendar."""
|
||||
self.ctag = hash_tag(self.vcalendar)
|
||||
self.support.append(self.cal, vcalendar)
|
||||
|
||||
def remove(self, uid):
|
||||
"""Remove object named ``uid`` from the calendar."""
|
||||
self.ctag = hash_tag(self.vcalendar)
|
||||
self.support.remove(self.cal, uid)
|
||||
|
||||
def replace(self, uid, vcalendar):
|
||||
"""Replace objet named ``uid`` by ``vcalendar`` in the calendar."""
|
||||
self.ctag = hash_tag(self.vcalendar)
|
||||
self.support.remove(self.cal, uid)
|
||||
self.support.append(self.cal, vcalendar)
|
||||
|
||||
@property
|
||||
def vcalendar(self):
|
||||
"""Unicode calendar from the calendar."""
|
||||
return self.support.read(self.cal)
|
||||
|
||||
@property
|
||||
def etag(self):
|
||||
"""Etag from calendar."""
|
||||
return '"%s"' % hash_tag(self.vcalendar)
|
||||
|
||||
|
||||
class Event(object):
|
||||
"""Internal event class."""
|
||||
def __init__(self, vcalendar):
|
||||
"""Initialize event from ``vcalendar``."""
|
||||
self.text = vcalendar
|
||||
|
||||
@property
|
||||
def etag(self):
|
||||
"""Etag from event."""
|
||||
return '"%s"' % hash_tag(self.text)
|
||||
# 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
|
||||
|
||||
|
||||
class Header(object):
|
||||
"""Internal header class."""
|
||||
def __init__(self, vcalendar):
|
||||
"""Initialize header from ``vcalendar``."""
|
||||
self.text = vcalendar
|
||||
def __init__(self, text):
|
||||
"""Initialize header from ``text``."""
|
||||
self.text = text
|
||||
|
||||
|
||||
class Timezone(object):
|
||||
"""Internal timezone class."""
|
||||
def __init__(self, vcalendar):
|
||||
"""Initialize timezone from ``vcalendar``."""
|
||||
lines = vcalendar.splitlines()
|
||||
for line in lines:
|
||||
if line.startswith("TZID:"):
|
||||
self.id = line.lstrip("TZID:")
|
||||
break
|
||||
class Event(object):
|
||||
"""Internal event class."""
|
||||
tag = "VEVENT"
|
||||
|
||||
self.text = vcalendar
|
||||
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."""
|
||||
def __init__(self, vcalendar):
|
||||
"""Initialize todo from ``vcalendar``."""
|
||||
self.text = vcalendar
|
||||
# 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 hash_tag(self.text)
|
||||
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 = "\n".join((
|
||||
"BEGIN:VCALENDAR",
|
||||
"\n".join([header.text for header in headers]),
|
||||
"\n".join([timezone.text for timezone in timezones]),
|
||||
"\n".join([todo.text for todo in todos]),
|
||||
"\n".join([event.text for event in events]),
|
||||
"END:VCALENDAR"))
|
||||
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)
|
||||
|
@ -29,10 +29,13 @@ Give a configparser-like interface to read and write configuration.
|
||||
|
||||
import os
|
||||
import sys
|
||||
# Manage Python2/3 different modules
|
||||
# pylint: disable-msg=F0401
|
||||
try:
|
||||
from configparser import RawConfigParser as ConfigParser
|
||||
except ImportError:
|
||||
from ConfigParser import RawConfigParser as ConfigParser
|
||||
# pylint: enable-msg=F0401
|
||||
|
||||
|
||||
# Default configuration
|
||||
@ -43,34 +46,27 @@ INITIAL_CONFIG = {
|
||||
"daemon": "False",
|
||||
"ssl": "False",
|
||||
"certificate": "/etc/apache2/ssl/server.crt",
|
||||
"key": "/etc/apache2/ssl/server.key",
|
||||
},
|
||||
"key": "/etc/apache2/ssl/server.key"},
|
||||
"encoding": {
|
||||
"request": "utf-8",
|
||||
"stock": "utf-8",
|
||||
},
|
||||
"stock": "utf-8"},
|
||||
"acl": {
|
||||
"type": "fake",
|
||||
"filename": "/etc/radicale/users",
|
||||
"encryption": "crypt",
|
||||
},
|
||||
"support": {
|
||||
"type": "plain",
|
||||
"folder": os.path.expanduser("~/.config/radicale"),
|
||||
"calendar": "radicale/cal",
|
||||
},
|
||||
}
|
||||
"encryption": "crypt"},
|
||||
"storage": {
|
||||
"folder": os.path.expanduser("~/.config/radicale/calendars")}}
|
||||
|
||||
# Create a ConfigParser and configure it
|
||||
_CONFIG = ConfigParser()
|
||||
_CONFIG_PARSER = ConfigParser()
|
||||
|
||||
for section, values in INITIAL_CONFIG.items():
|
||||
_CONFIG.add_section(section)
|
||||
_CONFIG_PARSER.add_section(section)
|
||||
for key, value in values.items():
|
||||
_CONFIG.set(section, key, value)
|
||||
_CONFIG_PARSER.set(section, key, value)
|
||||
|
||||
_CONFIG.read("/etc/radicale/config")
|
||||
_CONFIG.read(os.path.expanduser("~/.config/radicale/config"))
|
||||
_CONFIG_PARSER.read("/etc/radicale/config")
|
||||
_CONFIG_PARSER.read(os.path.expanduser("~/.config/radicale/config"))
|
||||
|
||||
# Wrap config module into ConfigParser instance
|
||||
sys.modules[__name__] = _CONFIG
|
||||
sys.modules[__name__] = _CONFIG_PARSER
|
||||
|
@ -1,98 +0,0 @@
|
||||
# -*- 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/>.
|
||||
|
||||
"""
|
||||
iCal parsing functions.
|
||||
|
||||
"""
|
||||
|
||||
# TODO: Manage filters (see xmlutils)
|
||||
|
||||
from radicale import calendar
|
||||
|
||||
|
||||
def write_calendar(headers=(
|
||||
calendar.Header("PRODID:-//Radicale//NONSGML Radicale Server//EN"),
|
||||
calendar.Header("VERSION:2.0")),
|
||||
timezones=(), todos=(), events=()):
|
||||
"""Create calendar from given parameters."""
|
||||
cal = "\n".join((
|
||||
"BEGIN:VCALENDAR",
|
||||
"\n".join([header.text for header in headers]),
|
||||
"\n".join([timezone.text for timezone in timezones]),
|
||||
"\n".join([todo.text for todo in todos]),
|
||||
"\n".join([event.text for event in events]),
|
||||
"END:VCALENDAR"))
|
||||
return "\n".join([line for line in cal.splitlines() if line])
|
||||
|
||||
|
||||
def _parse(vcalendar, tag, obj):
|
||||
"""Find ``tag`` items in ``vcalendar``.
|
||||
|
||||
Return a list of items of type ``obj``.
|
||||
|
||||
"""
|
||||
items = []
|
||||
|
||||
lines = vcalendar.splitlines()
|
||||
in_item = False
|
||||
item_lines = []
|
||||
|
||||
for line in lines:
|
||||
if line.startswith("BEGIN:%s" % tag):
|
||||
in_item = True
|
||||
item_lines = []
|
||||
|
||||
if in_item:
|
||||
item_lines.append(line)
|
||||
if line.startswith("END:%s" % tag):
|
||||
items.append(obj("\n".join(item_lines)))
|
||||
|
||||
return items
|
||||
|
||||
|
||||
def headers(vcalendar):
|
||||
"""Find Headers items in ``vcalendar``."""
|
||||
header_lines = []
|
||||
|
||||
lines = vcalendar.splitlines()
|
||||
for line in lines:
|
||||
if line.startswith("PRODID:"):
|
||||
header_lines.append(calendar.Header(line))
|
||||
for line in lines:
|
||||
if line.startswith("VERSION:"):
|
||||
header_lines.append(calendar.Header(line))
|
||||
|
||||
return header_lines
|
||||
|
||||
|
||||
def events(vcalendar):
|
||||
"""Get list of ``Event`` from VEVENTS items in ``vcalendar``."""
|
||||
return _parse(vcalendar, "VEVENT", calendar.Event)
|
||||
|
||||
|
||||
def todos(vcalendar):
|
||||
"""Get list of ``Todo`` from VTODO items in ``vcalendar``."""
|
||||
return _parse(vcalendar, "VTODO", calendar.Todo)
|
||||
|
||||
|
||||
def timezones(vcalendar):
|
||||
"""Get list of ``Timezome`` from VTIMEZONE items in ``vcalendar``."""
|
||||
return _parse(vcalendar, "VTIMEZONE", calendar.Timezone)
|
@ -1,32 +0,0 @@
|
||||
# -*- 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/>.
|
||||
|
||||
"""
|
||||
Calendar storage support configuration.
|
||||
|
||||
"""
|
||||
|
||||
from radicale import config
|
||||
|
||||
def load():
|
||||
"""Load list of available storage support managers."""
|
||||
module = __import__("radicale.support", globals(), locals(),
|
||||
[config.get("support", "type")])
|
||||
return getattr(module, config.get("support", "type"))
|
@ -1,128 +0,0 @@
|
||||
# -*- 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/>.
|
||||
|
||||
"""
|
||||
Plain text storage.
|
||||
|
||||
"""
|
||||
|
||||
import os
|
||||
import posixpath
|
||||
import codecs
|
||||
|
||||
from radicale import config, ical
|
||||
|
||||
FOLDER = os.path.expanduser(config.get("support", "folder"))
|
||||
DEFAULT_CALENDAR = config.get("support", "calendar")
|
||||
|
||||
|
||||
def _open(path, mode="r"):
|
||||
"""Open file at ``path`` with ``mode``, automagically managing encoding."""
|
||||
return codecs.open(path, mode, config.get("encoding", "stock"))
|
||||
|
||||
|
||||
def calendars():
|
||||
"""List available calendars paths."""
|
||||
available_calendars = []
|
||||
|
||||
for filename in os.listdir(FOLDER):
|
||||
if os.path.isdir(os.path.join(FOLDER, filename)):
|
||||
for cal in os.listdir(os.path.join(FOLDER, filename)):
|
||||
available_calendars.append(posixpath.join(filename, cal))
|
||||
|
||||
return available_calendars
|
||||
|
||||
|
||||
def mkcalendar(name):
|
||||
"""Write a new calendar called ``name``."""
|
||||
user, cal = name.split(posixpath.sep)
|
||||
if not os.path.exists(os.path.join(FOLDER, user)):
|
||||
os.makedirs(os.path.join(FOLDER, user))
|
||||
descriptor = _open(os.path.join(FOLDER, user, cal), "w")
|
||||
descriptor.write(ical.write_calendar())
|
||||
|
||||
|
||||
def read(cal):
|
||||
"""Read calendar ``cal``."""
|
||||
path = os.path.join(FOLDER, cal.replace(posixpath.sep, os.path.sep))
|
||||
return _open(path).read()
|
||||
|
||||
|
||||
def append(cal, vcalendar):
|
||||
"""Append ``vcalendar`` to ``cal``."""
|
||||
old_calendar = read(cal)
|
||||
old_timezones = [timezone.id for timezone in ical.timezones(old_calendar)]
|
||||
path = os.path.join(FOLDER, cal.replace(posixpath.sep, os.path.sep))
|
||||
|
||||
old_objects = []
|
||||
old_objects.extend([event.etag for event in ical.events(old_calendar)])
|
||||
old_objects.extend([todo.etag for todo in ical.todos(old_calendar)])
|
||||
|
||||
objects = []
|
||||
objects.extend(ical.events(vcalendar))
|
||||
objects.extend(ical.todos(vcalendar))
|
||||
|
||||
for timezone in ical.timezones(vcalendar):
|
||||
if timezone.id not in old_timezones:
|
||||
descriptor = _open(path)
|
||||
lines = [line for line in descriptor.readlines() if line]
|
||||
descriptor.close()
|
||||
|
||||
for i, line in enumerate(timezone.text.splitlines()):
|
||||
lines.insert(2 + i, line + "\n")
|
||||
|
||||
descriptor = _open(path, "w")
|
||||
descriptor.writelines(lines)
|
||||
descriptor.close()
|
||||
|
||||
for obj in objects:
|
||||
if obj.etag not in old_objects:
|
||||
descriptor = _open(path)
|
||||
lines = [line for line in descriptor.readlines() if line]
|
||||
descriptor.close()
|
||||
|
||||
for line in obj.text.splitlines():
|
||||
lines.insert(-1, line + "\n")
|
||||
|
||||
descriptor = _open(path, "w")
|
||||
descriptor.writelines(lines)
|
||||
descriptor.close()
|
||||
|
||||
|
||||
def remove(cal, etag):
|
||||
"""Remove object named ``etag`` from ``cal``."""
|
||||
path = os.path.join(FOLDER, cal.replace(posixpath.sep, os.path.sep))
|
||||
|
||||
cal = read(cal)
|
||||
|
||||
headers = ical.headers(cal)
|
||||
timezones = ical.timezones(cal)
|
||||
todos = [todo for todo in ical.todos(cal) if todo.etag != etag]
|
||||
events = [event for event in ical.events(cal) if event.etag != etag]
|
||||
|
||||
descriptor = _open(path, "w")
|
||||
descriptor.write(ical.write_calendar(headers, timezones, todos, events))
|
||||
descriptor.close()
|
||||
|
||||
|
||||
# Create default calendar if not present
|
||||
if DEFAULT_CALENDAR:
|
||||
if DEFAULT_CALENDAR not in calendars():
|
||||
mkcalendar(DEFAULT_CALENDAR)
|
@ -140,6 +140,7 @@ def propfind(xml_request, calendar, url):
|
||||
|
||||
def put(ical_request, calendar, url, obj):
|
||||
"""Read PUT requests."""
|
||||
# TODO: use url to set hreference
|
||||
if obj:
|
||||
# PUT is modifying obj
|
||||
calendar.replace(obj, ical_request)
|
||||
@ -174,11 +175,10 @@ def report(xml_request, calendar, url):
|
||||
# is that really what is needed?
|
||||
# Read rfc4791-9.[6|10] for info
|
||||
for hreference in hreferences:
|
||||
headers = ical.headers(calendar.vcalendar)
|
||||
timezones = ical.timezones(calendar.vcalendar)
|
||||
headers = ical.headers(calendar.text)
|
||||
timezones = ical.timezones(calendar.text)
|
||||
|
||||
objects = \
|
||||
ical.events(calendar.vcalendar) + ical.todos(calendar.vcalendar)
|
||||
objects = ical.events(calendar.text) + ical.todos(calendar.text)
|
||||
|
||||
if not objects:
|
||||
# TODO: Read rfc4791-9.[6|10] to find a right answer
|
||||
|
2
setup.py
2
setup.py
@ -69,7 +69,7 @@ setup(
|
||||
author_email="guillaume.ayoub@kozea.fr",
|
||||
url="http://www.radicale.org/",
|
||||
license="GNU GPL v3",
|
||||
packages=["radicale", "radicale.acl", "radicale.support"],
|
||||
packages=["radicale", "radicale.acl"],
|
||||
scripts=["radicale.py"],
|
||||
cmdclass={'clean': Clean,
|
||||
"build_scripts": BuildScripts})
|
||||
|
Loading…
Reference in New Issue
Block a user