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 base64
|
||||||
import socket
|
import socket
|
||||||
|
# Manage Python2/3 different modules
|
||||||
|
# pylint: disable-msg=F0401
|
||||||
try:
|
try:
|
||||||
from http import client, server
|
from http import client, server
|
||||||
except ImportError:
|
except ImportError:
|
||||||
import httplib as client
|
import httplib as client
|
||||||
import BaseHTTPServer as server
|
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):
|
def _check(request, function):
|
||||||
@ -77,7 +81,9 @@ class HTTPSServer(HTTPServer):
|
|||||||
def __init__(self, address, handler):
|
def __init__(self, address, handler):
|
||||||
"""Create server by wrapping HTTP socket in an SSL socket."""
|
"""Create server by wrapping HTTP socket in an SSL socket."""
|
||||||
# Fails with Python 2.5, import if needed
|
# Fails with Python 2.5, import if needed
|
||||||
|
# pylint: disable-msg=F0401
|
||||||
import ssl
|
import ssl
|
||||||
|
# pylint: enable-msg=F0401
|
||||||
|
|
||||||
HTTPServer.__init__(self, address, handler)
|
HTTPServer.__init__(self, address, handler)
|
||||||
self.socket = ssl.wrap_socket(
|
self.socket = ssl.wrap_socket(
|
||||||
@ -98,14 +104,15 @@ class CalendarHTTPHandler(server.BaseHTTPRequestHandler):
|
|||||||
check_rights = lambda function: lambda request: _check(request, function)
|
check_rights = lambda function: lambda request: _check(request, function)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def calendar(self):
|
def _calendar(self):
|
||||||
"""The ``calendar.Calendar`` object corresponding to the given path."""
|
"""The ``calendar.Calendar`` object corresponding to the given path."""
|
||||||
path = self.path.strip("/").split("/")
|
# ``normpath`` should clean malformed and malicious request paths
|
||||||
if len(path) >= 2:
|
attributes = os.path.normpath(self.path.strip("/")).split("/")
|
||||||
cal = "%s/%s" % (path[0], path[1])
|
if len(attributes) >= 2:
|
||||||
return calendar.Calendar("radicale", cal)
|
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."""
|
"""Try to decode text according to various parameters."""
|
||||||
# List of charsets to try
|
# List of charsets to try
|
||||||
charsets = []
|
charsets = []
|
||||||
@ -134,7 +141,7 @@ class CalendarHTTPHandler(server.BaseHTTPRequestHandler):
|
|||||||
@check_rights
|
@check_rights
|
||||||
def do_GET(self):
|
def do_GET(self):
|
||||||
"""Manage GET request."""
|
"""Manage GET request."""
|
||||||
answer = self.calendar.vcalendar.encode(self._encoding)
|
answer = self._calendar.read().encode(self._encoding)
|
||||||
|
|
||||||
self.send_response(client.OK)
|
self.send_response(client.OK)
|
||||||
self.send_header("Content-Length", len(answer))
|
self.send_header("Content-Length", len(answer))
|
||||||
@ -145,7 +152,7 @@ class CalendarHTTPHandler(server.BaseHTTPRequestHandler):
|
|||||||
def do_DELETE(self):
|
def do_DELETE(self):
|
||||||
"""Manage DELETE request."""
|
"""Manage DELETE request."""
|
||||||
obj = self.headers.get("If-Match", None)
|
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_response(client.NO_CONTENT)
|
||||||
self.send_header("Content-Length", len(answer))
|
self.send_header("Content-Length", len(answer))
|
||||||
@ -162,7 +169,7 @@ class CalendarHTTPHandler(server.BaseHTTPRequestHandler):
|
|||||||
def do_PROPFIND(self):
|
def do_PROPFIND(self):
|
||||||
"""Manage PROPFIND request."""
|
"""Manage PROPFIND request."""
|
||||||
xml_request = self.rfile.read(int(self.headers["Content-Length"]))
|
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_response(client.MULTI_STATUS)
|
||||||
self.send_header("DAV", "1, calendar-access")
|
self.send_header("DAV", "1, calendar-access")
|
||||||
@ -173,10 +180,10 @@ class CalendarHTTPHandler(server.BaseHTTPRequestHandler):
|
|||||||
@check_rights
|
@check_rights
|
||||||
def do_PUT(self):
|
def do_PUT(self):
|
||||||
"""Manage PUT request."""
|
"""Manage PUT request."""
|
||||||
ical_request = self.decode(
|
ical_request = self._decode(
|
||||||
self.rfile.read(int(self.headers["Content-Length"])))
|
self.rfile.read(int(self.headers["Content-Length"])))
|
||||||
obj = self.headers.get("If-Match", None)
|
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)
|
self.send_response(client.CREATED)
|
||||||
|
|
||||||
@ -184,9 +191,11 @@ class CalendarHTTPHandler(server.BaseHTTPRequestHandler):
|
|||||||
def do_REPORT(self):
|
def do_REPORT(self):
|
||||||
"""Manage REPORT request."""
|
"""Manage REPORT request."""
|
||||||
xml_request = self.rfile.read(int(self.headers["Content-Length"]))
|
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_response(client.MULTI_STATUS)
|
||||||
self.send_header("Content-Length", len(answer))
|
self.send_header("Content-Length", len(answer))
|
||||||
self.end_headers()
|
self.end_headers()
|
||||||
self.wfile.write(answer)
|
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):
|
FOLDER = os.path.expanduser(config.get("storage", "folder"))
|
||||||
"""Hash an vcalendar string."""
|
|
||||||
return str(hash(vcalendar))
|
|
||||||
|
|
||||||
|
# This function overrides the builtin ``open`` function for this module
|
||||||
class Calendar(object):
|
# pylint: disable-msg=W0622
|
||||||
"""Internal calendar class."""
|
def open(path, mode="r"):
|
||||||
def __init__(self, user, cal):
|
"""Open file at ``path`` with ``mode``, automagically managing encoding."""
|
||||||
"""Initialize the calendar with ``cal`` and ``user`` parameters."""
|
return codecs.open(path, mode, config.get("encoding", "stock"))
|
||||||
# TODO: Use properties from the calendar configuration
|
# pylint: enable-msg=W0622
|
||||||
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)
|
|
||||||
|
|
||||||
|
|
||||||
class Header(object):
|
class Header(object):
|
||||||
"""Internal header class."""
|
"""Internal header class."""
|
||||||
def __init__(self, vcalendar):
|
def __init__(self, text):
|
||||||
"""Initialize header from ``vcalendar``."""
|
"""Initialize header from ``text``."""
|
||||||
self.text = vcalendar
|
self.text = text
|
||||||
|
|
||||||
|
|
||||||
class Timezone(object):
|
class Event(object):
|
||||||
"""Internal timezone class."""
|
"""Internal event class."""
|
||||||
def __init__(self, vcalendar):
|
tag = "VEVENT"
|
||||||
"""Initialize timezone from ``vcalendar``."""
|
|
||||||
lines = vcalendar.splitlines()
|
|
||||||
for line in lines:
|
|
||||||
if line.startswith("TZID:"):
|
|
||||||
self.id = line.lstrip("TZID:")
|
|
||||||
break
|
|
||||||
|
|
||||||
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):
|
class Todo(object):
|
||||||
"""Internal todo class."""
|
"""Internal todo class."""
|
||||||
def __init__(self, vcalendar):
|
# This is not a TODO!
|
||||||
"""Initialize todo from ``vcalendar``."""
|
# pylint: disable-msg=W0511
|
||||||
self.text = vcalendar
|
tag = "VTODO"
|
||||||
|
# pylint: enable-msg=W0511
|
||||||
|
|
||||||
|
def __init__(self, text):
|
||||||
|
"""Initialize todo from ``text``."""
|
||||||
|
self.text = text
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def etag(self):
|
def etag(self):
|
||||||
"""Etag from todo."""
|
"""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 os
|
||||||
import sys
|
import sys
|
||||||
|
# Manage Python2/3 different modules
|
||||||
|
# pylint: disable-msg=F0401
|
||||||
try:
|
try:
|
||||||
from configparser import RawConfigParser as ConfigParser
|
from configparser import RawConfigParser as ConfigParser
|
||||||
except ImportError:
|
except ImportError:
|
||||||
from ConfigParser import RawConfigParser as ConfigParser
|
from ConfigParser import RawConfigParser as ConfigParser
|
||||||
|
# pylint: enable-msg=F0401
|
||||||
|
|
||||||
|
|
||||||
# Default configuration
|
# Default configuration
|
||||||
@ -43,34 +46,27 @@ INITIAL_CONFIG = {
|
|||||||
"daemon": "False",
|
"daemon": "False",
|
||||||
"ssl": "False",
|
"ssl": "False",
|
||||||
"certificate": "/etc/apache2/ssl/server.crt",
|
"certificate": "/etc/apache2/ssl/server.crt",
|
||||||
"key": "/etc/apache2/ssl/server.key",
|
"key": "/etc/apache2/ssl/server.key"},
|
||||||
},
|
|
||||||
"encoding": {
|
"encoding": {
|
||||||
"request": "utf-8",
|
"request": "utf-8",
|
||||||
"stock": "utf-8",
|
"stock": "utf-8"},
|
||||||
},
|
|
||||||
"acl": {
|
"acl": {
|
||||||
"type": "fake",
|
"type": "fake",
|
||||||
"filename": "/etc/radicale/users",
|
"filename": "/etc/radicale/users",
|
||||||
"encryption": "crypt",
|
"encryption": "crypt"},
|
||||||
},
|
"storage": {
|
||||||
"support": {
|
"folder": os.path.expanduser("~/.config/radicale/calendars")}}
|
||||||
"type": "plain",
|
|
||||||
"folder": os.path.expanduser("~/.config/radicale"),
|
|
||||||
"calendar": "radicale/cal",
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
# Create a ConfigParser and configure it
|
# Create a ConfigParser and configure it
|
||||||
_CONFIG = ConfigParser()
|
_CONFIG_PARSER = ConfigParser()
|
||||||
|
|
||||||
for section, values in INITIAL_CONFIG.items():
|
for section, values in INITIAL_CONFIG.items():
|
||||||
_CONFIG.add_section(section)
|
_CONFIG_PARSER.add_section(section)
|
||||||
for key, value in values.items():
|
for key, value in values.items():
|
||||||
_CONFIG.set(section, key, value)
|
_CONFIG_PARSER.set(section, key, value)
|
||||||
|
|
||||||
_CONFIG.read("/etc/radicale/config")
|
_CONFIG_PARSER.read("/etc/radicale/config")
|
||||||
_CONFIG.read(os.path.expanduser("~/.config/radicale/config"))
|
_CONFIG_PARSER.read(os.path.expanduser("~/.config/radicale/config"))
|
||||||
|
|
||||||
# Wrap config module into ConfigParser instance
|
# 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):
|
def put(ical_request, calendar, url, obj):
|
||||||
"""Read PUT requests."""
|
"""Read PUT requests."""
|
||||||
|
# TODO: use url to set hreference
|
||||||
if obj:
|
if obj:
|
||||||
# PUT is modifying obj
|
# PUT is modifying obj
|
||||||
calendar.replace(obj, ical_request)
|
calendar.replace(obj, ical_request)
|
||||||
@ -174,11 +175,10 @@ def report(xml_request, calendar, url):
|
|||||||
# is that really what is needed?
|
# is that really what is needed?
|
||||||
# Read rfc4791-9.[6|10] for info
|
# Read rfc4791-9.[6|10] for info
|
||||||
for hreference in hreferences:
|
for hreference in hreferences:
|
||||||
headers = ical.headers(calendar.vcalendar)
|
headers = ical.headers(calendar.text)
|
||||||
timezones = ical.timezones(calendar.vcalendar)
|
timezones = ical.timezones(calendar.text)
|
||||||
|
|
||||||
objects = \
|
objects = ical.events(calendar.text) + ical.todos(calendar.text)
|
||||||
ical.events(calendar.vcalendar) + ical.todos(calendar.vcalendar)
|
|
||||||
|
|
||||||
if not objects:
|
if not objects:
|
||||||
# TODO: Read rfc4791-9.[6|10] to find a right answer
|
# 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",
|
author_email="guillaume.ayoub@kozea.fr",
|
||||||
url="http://www.radicale.org/",
|
url="http://www.radicale.org/",
|
||||||
license="GNU GPL v3",
|
license="GNU GPL v3",
|
||||||
packages=["radicale", "radicale.acl", "radicale.support"],
|
packages=["radicale", "radicale.acl"],
|
||||||
scripts=["radicale.py"],
|
scripts=["radicale.py"],
|
||||||
cmdclass={'clean': Clean,
|
cmdclass={'clean': Clean,
|
||||||
"build_scripts": BuildScripts})
|
"build_scripts": BuildScripts})
|
||||||
|
Loading…
Reference in New Issue
Block a user