Split the storage filesystem backend into another file
This commit is contained in:
		
							
								
								
									
										8
									
								
								config
									
									
									
									
									
								
							
							
						
						
									
										8
									
								
								config
									
									
									
									
									
								
							| @@ -72,8 +72,14 @@ courier_socket = | ||||
|  | ||||
|  | ||||
| [storage] | ||||
| # Storage backend | ||||
| type = filesystem | ||||
|  | ||||
| # Folder for storing local calendars, created if not present | ||||
| folder = ~/.config/radicale/calendars | ||||
| filesystem_folder = ~/.config/radicale/calendars | ||||
|  | ||||
| # Git repository for storing local calendars, created if not present | ||||
| filesystem_folder = ~/.config/radicale/calendars | ||||
|  | ||||
|  | ||||
| [logging] | ||||
|   | ||||
| @@ -46,7 +46,7 @@ except ImportError: | ||||
|     from urlparse import urlparse | ||||
| # pylint: enable=F0401,E0611 | ||||
|  | ||||
| from radicale import acl, config, ical, log, xmlutils | ||||
| from radicale import acl, config, ical, log, storage, xmlutils | ||||
|  | ||||
|  | ||||
| VERSION = "git" | ||||
| @@ -112,6 +112,7 @@ class Application(object): | ||||
|         """Initialize application.""" | ||||
|         super(Application, self).__init__() | ||||
|         self.acl = acl.load() | ||||
|         storage.load() | ||||
|         self.encoding = config.get("encoding", "request") | ||||
|         if config.getboolean('logging', 'full_environment'): | ||||
|             self.headers_log = lambda environ: environ | ||||
|   | ||||
| @@ -63,7 +63,11 @@ INITIAL_CONFIG = { | ||||
|         "pam_group_membership": "", | ||||
|         "courier_socket": ""}, | ||||
|     "storage": { | ||||
|         "folder": os.path.expanduser("~/.config/radicale/calendars")}, | ||||
|         "type": "filesystem", | ||||
|         "filesystem_folder": | ||||
|             os.path.expanduser("~/.config/radicale/calendars"), | ||||
|         "git_folder": | ||||
|             os.path.expanduser("~/.config/radicale/calendars")}, | ||||
|     "logging": { | ||||
|         "config": "/etc/radicale/logging", | ||||
|         "debug": "False", | ||||
|   | ||||
							
								
								
									
										106
									
								
								radicale/ical.py
									
									
									
									
									
								
							
							
						
						
									
										106
									
								
								radicale/ical.py
									
									
									
									
									
								
							| @@ -25,26 +25,10 @@ Define the main classes of a calendar as seen from the server. | ||||
|  | ||||
| """ | ||||
|  | ||||
| import codecs | ||||
| from contextlib import contextmanager | ||||
| import json | ||||
| import os | ||||
| import posixpath | ||||
| import time | ||||
| import uuid | ||||
|  | ||||
| from radicale import config | ||||
|  | ||||
|  | ||||
| FOLDER = os.path.expanduser(config.get("storage", "folder")) | ||||
|  | ||||
|  | ||||
| # This function overrides the builtin ``open`` function for this module | ||||
| # pylint: disable=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=W0622 | ||||
| from contextlib import contextmanager | ||||
|  | ||||
|  | ||||
| def serialize(headers=(), items=()): | ||||
| @@ -171,9 +155,8 @@ class Calendar(object): | ||||
|         """ | ||||
|         self.encoding = "utf-8" | ||||
|         split_path = path.split("/") | ||||
|         self.path = os.path.join(FOLDER, path.replace("/", os.sep)) | ||||
|         self.props_path = self.path + '.props' | ||||
|         if principal and split_path and os.path.isdir(self.path): | ||||
|         self.path = path | ||||
|         if principal and split_path and self.is_collection(self.path): | ||||
|             # Already existing principal calendar | ||||
|             self.owner = split_path[0] | ||||
|         elif len(split_path) > 1: | ||||
| @@ -193,7 +176,7 @@ class Calendar(object): | ||||
|         ``include_container`` is ``True`` (the default), the containing object | ||||
|         is included in the result. | ||||
|  | ||||
|         The ``path`` is relative to the storage folder. | ||||
|         The ``path`` is relative. | ||||
|  | ||||
|         """ | ||||
|         # First do normpath and then strip, to prevent access to FOLDER/../ | ||||
| @@ -201,28 +184,21 @@ class Calendar(object): | ||||
|         attributes = sane_path.split("/") | ||||
|         if not attributes: | ||||
|             return None | ||||
|         if not (os.path.isfile(os.path.join(FOLDER, *attributes)) or | ||||
|                 path.endswith("/")): | ||||
|         if not (cls.is_item("/".join(attributes)) or path.endswith("/")): | ||||
|             attributes.pop() | ||||
|  | ||||
|         result = [] | ||||
|  | ||||
|         path = "/".join(attributes) | ||||
|         abs_path = os.path.join(FOLDER, path.replace("/", os.sep)) | ||||
|  | ||||
|         principal = len(attributes) <= 1 | ||||
|         if os.path.isdir(abs_path): | ||||
|         if cls.is_collection(path): | ||||
|             if depth == "0": | ||||
|                 result.append(cls(path, principal)) | ||||
|             else: | ||||
|                 if include_container: | ||||
|                     result.append(cls(path, principal)) | ||||
|                 try: | ||||
|                     for filename in next(os.walk(abs_path))[2]: | ||||
|                         if cls.is_vcalendar(os.path.join(abs_path, filename)): | ||||
|                             result.append(cls(os.path.join(path, filename))) | ||||
|                 except StopIteration: | ||||
|                     # Directory does not exist yet | ||||
|                     pass | ||||
|                     for child in cls.children(path): | ||||
|                         result.append(child) | ||||
|         else: | ||||
|             if depth == "0": | ||||
|                 result.append(cls(path)) | ||||
| @@ -233,12 +209,30 @@ class Calendar(object): | ||||
|                 result.extend(calendar.components) | ||||
|         return result | ||||
|  | ||||
|     @staticmethod | ||||
|     def is_vcalendar(path): | ||||
|         """Return ``True`` if there is a VCALENDAR file under ``path``.""" | ||||
|         with open(path) as stream: | ||||
|     def open(self, path): | ||||
|         """Return the content of the calendar under ``path``.""" | ||||
|         raise NotImplemented | ||||
|  | ||||
|     @classmethod | ||||
|     def is_collection(cls, path): | ||||
|         """Return ``True`` if relative ``path`` is a collection.""" | ||||
|         raise NotImplemented | ||||
|  | ||||
|     @classmethod | ||||
|     def is_item(cls, path): | ||||
|         """Return ``True`` if relative ``path`` is a collection item.""" | ||||
|         raise NotImplemented | ||||
|  | ||||
|     def is_vcalendar(self, path): | ||||
|         """Return ``True`` if there is a VCALENDAR under relative ``path``.""" | ||||
|         with self.open(path) as stream: | ||||
|             return 'BEGIN:VCALENDAR' == stream.read(15) | ||||
|  | ||||
|     @classmethod | ||||
|     def children(cls, path): | ||||
|         """Yield the children of the collection at local ``path``.""" | ||||
|         raise NotImplemented | ||||
|  | ||||
|     @staticmethod | ||||
|     def _parse(text, item_types, name=None): | ||||
|         """Find items with type in ``item_types`` in ``text``. | ||||
| @@ -303,8 +297,7 @@ class Calendar(object): | ||||
|  | ||||
|     def delete(self): | ||||
|         """Delete the calendar.""" | ||||
|         os.remove(self.path) | ||||
|         os.remove(self.props_path) | ||||
|         raise NotImplemented | ||||
|  | ||||
|     def remove(self, name): | ||||
|         """Remove object named ``name`` from calendar.""" | ||||
| @@ -327,16 +320,12 @@ class Calendar(object): | ||||
|             Header("VERSION:2.0")) | ||||
|         items = items if items is not None else self.items | ||||
|  | ||||
|         self._create_dirs(self.path) | ||||
|  | ||||
|         text = serialize(headers, items) | ||||
|         return open(self.path, "w").write(text) | ||||
|         self.save(text) | ||||
|  | ||||
|     @staticmethod | ||||
|     def _create_dirs(path): | ||||
|         """Create folder if absent.""" | ||||
|         if not os.path.exists(os.path.dirname(path)): | ||||
|             os.makedirs(os.path.dirname(path)) | ||||
|     def save(self, text): | ||||
|         """Save the text into the calendar.""" | ||||
|         raise NotImplemented | ||||
|  | ||||
|     @property | ||||
|     def etag(self): | ||||
| @@ -353,10 +342,7 @@ class Calendar(object): | ||||
|     @property | ||||
|     def text(self): | ||||
|         """Calendar as plain text.""" | ||||
|         try: | ||||
|             return open(self.path).read() | ||||
|         except IOError: | ||||
|             return "" | ||||
|         raise NotImplemented | ||||
|  | ||||
|     @property | ||||
|     def headers(self): | ||||
| @@ -410,27 +396,13 @@ class Calendar(object): | ||||
|         The date is formatted according to rfc1123-5.2.14. | ||||
|  | ||||
|         """ | ||||
|         # Create calendar if needed | ||||
|         if not os.path.exists(self.path): | ||||
|             self.write() | ||||
|  | ||||
|         modification_time = time.gmtime(os.path.getmtime(self.path)) | ||||
|         return time.strftime("%a, %d %b %Y %H:%M:%S +0000", modification_time) | ||||
|         raise NotImplemented | ||||
|  | ||||
|     @property | ||||
|     @contextmanager | ||||
|     def props(self): | ||||
|         """Get the calendar properties.""" | ||||
|         # On enter | ||||
|         properties = {} | ||||
|         if os.path.exists(self.props_path): | ||||
|             with open(self.props_path) as prop_file: | ||||
|                 properties.update(json.load(prop_file)) | ||||
|         yield properties | ||||
|         # On exit | ||||
|         self._create_dirs(self.props_path) | ||||
|         with open(self.props_path, 'w') as prop_file: | ||||
|             json.dump(properties, prop_file) | ||||
|         raise NotImplemented | ||||
|  | ||||
|     @property | ||||
|     def owner_url(self): | ||||
|   | ||||
							
								
								
									
										34
									
								
								radicale/storage/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										34
									
								
								radicale/storage/__init__.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,34 @@ | ||||
| # -*- coding: utf-8 -*- | ||||
| # | ||||
| # This file is part of Radicale Server - Calendar Server | ||||
| # Copyright © 2012 Guillaume Ayoub | ||||
| # | ||||
| # 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/>. | ||||
|  | ||||
| """ | ||||
| Storage backends. | ||||
|  | ||||
| This module loads the storage backend, according to the storage | ||||
| configuration. | ||||
|  | ||||
| """ | ||||
|  | ||||
| from radicale import config | ||||
|  | ||||
|  | ||||
| def load(): | ||||
|     """Load list of available storage managers.""" | ||||
|     storage_type = config.get("storage", "type") | ||||
|     module = __import__("radicale.storage", fromlist=[storage_type]) | ||||
|     return getattr(module, storage_type) | ||||
							
								
								
									
										113
									
								
								radicale/storage/filesystem.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										113
									
								
								radicale/storage/filesystem.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,113 @@ | ||||
| # -*- coding: utf-8 -*- | ||||
| # | ||||
| # This file is part of Radicale Server - Calendar Server | ||||
| # Copyright © 2012 Guillaume Ayoub | ||||
| # | ||||
| # 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/>. | ||||
|  | ||||
| """ | ||||
| Filesystem storage backend. | ||||
|  | ||||
| """ | ||||
|  | ||||
| import codecs | ||||
| import os | ||||
| import json | ||||
| import time | ||||
| from contextlib import contextmanager | ||||
|  | ||||
| from radicale import config, ical | ||||
|  | ||||
|  | ||||
| FOLDER = os.path.expanduser(config.get("storage", "filesystem_folder")) | ||||
|  | ||||
|  | ||||
| class Calendar(ical.Calendar): | ||||
|     @staticmethod | ||||
|     def open(path, mode="r"): | ||||
|         abs_path = os.path.join(FOLDER, path.replace("/", os.sep)) | ||||
|         return codecs.open(abs_path, mode, config.get("encoding", "stock")) | ||||
|  | ||||
|     @classmethod | ||||
|     def is_collection(cls, path): | ||||
|         abs_path = os.path.join(FOLDER, path.replace("/", os.sep)) | ||||
|         return os.path.isdir(abs_path) | ||||
|  | ||||
|     @classmethod | ||||
|     def is_item(cls, path): | ||||
|         abs_path = os.path.join(FOLDER, path.replace("/", os.sep)) | ||||
|         return os.path.isfile(abs_path) | ||||
|  | ||||
|     @classmethod | ||||
|     def children(cls, path): | ||||
|         abs_path = os.path.join(FOLDER, path.replace("/", os.sep)) | ||||
|         for filename in next(os.walk(abs_path))[2]: | ||||
|             if cls.is_collection(path): | ||||
|                 yield cls(path) | ||||
|  | ||||
|     def delete(self): | ||||
|         os.remove(self._path) | ||||
|         os.remove(self._props_path) | ||||
|  | ||||
|     @property | ||||
|     def _path(self): | ||||
|         """Absolute path of the file at local ``path``.""" | ||||
|         return os.path.join(FOLDER, self.path.replace("/", os.sep)) | ||||
|  | ||||
|     @property | ||||
|     def _props_path(self): | ||||
|         """Absolute path of the file storing the calendar properties.""" | ||||
|         return self._path + ".props" | ||||
|  | ||||
|     def _create_dirs(self): | ||||
|         """Create folder storing the calendar if absent.""" | ||||
|         if not os.path.exists(os.path.dirname(self._path)): | ||||
|             os.makedirs(os.path.dirname(self._path)) | ||||
|  | ||||
|     @property | ||||
|     @contextmanager | ||||
|     def props(self): | ||||
|         # On enter | ||||
|         properties = {} | ||||
|         if os.path.exists(self._props_path): | ||||
|             with open(self._props_path) as prop_file: | ||||
|                 properties.update(json.load(prop_file)) | ||||
|         yield properties | ||||
|         # On exit | ||||
|         self._create_dirs() | ||||
|         with open(self._props_path, 'w') as prop_file: | ||||
|             json.dump(properties, prop_file) | ||||
|  | ||||
|     @property | ||||
|     def last_modified(self): | ||||
|         # Create calendar if needed | ||||
|         if not os.path.exists(self._path): | ||||
|             self.write() | ||||
|  | ||||
|         modification_time = time.gmtime(os.path.getmtime(self._path)) | ||||
|         return time.strftime("%a, %d %b %Y %H:%M:%S +0000", modification_time) | ||||
|  | ||||
|     @property | ||||
|     def text(self): | ||||
|         try: | ||||
|             return open(self._path).read() | ||||
|         except IOError: | ||||
|             return "" | ||||
|  | ||||
|     def save(self, text): | ||||
|         self._create_dirs() | ||||
|         self.open(self._path, "w").write(text) | ||||
|  | ||||
|  | ||||
| ical.Calendar = Calendar | ||||
							
								
								
									
										2
									
								
								setup.py
									
									
									
									
									
								
							
							
						
						
									
										2
									
								
								setup.py
									
									
									
									
									
								
							| @@ -55,7 +55,7 @@ setup( | ||||
|         "Radicale-%s.tar.gz" % radicale.VERSION, | ||||
|     license="GNU GPL v3", | ||||
|     platforms="Any", | ||||
|     packages=["radicale", "radicale.acl"], | ||||
|     packages=["radicale", "radicale.acl", "radicale.storage"], | ||||
|     provides=["radicale"], | ||||
|     scripts=["bin/radicale"], | ||||
|     keywords=["calendar", "CalDAV"], | ||||
|   | ||||
		Reference in New Issue
	
	Block a user
	 Guillaume Ayoub
					Guillaume Ayoub