2
# Copyright (c) 2005 Apple Computer, Inc. All rights reserved.
4
# Permission is hereby granted, free of charge, to any person obtaining a copy
5
# of this software and associated documentation files (the "Software"), to deal
6
# in the Software without restriction, including without limitation the rights
7
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
8
# copies of the Software, and to permit persons to whom the Software is
9
# furnished to do so, subject to the following conditions:
11
# The above copyright notice and this permission notice shall be included in all
12
# copies or substantial portions of the Software.
14
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
15
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
16
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
17
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
18
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
19
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
22
# DRI: Wilfredo Sanchez, wsanchez@apple.com
26
DAV Property store using file system extended attributes.
28
This API is considered private to static.py and is therefore subject to
32
__all__ = ["xattrPropertyStore"]
39
if getattr(xattr, 'xattr', None) is None:
40
raise ImportError("wrong xattr package imported")
42
from twisted.python import log
43
from twisted.web2 import responsecode
44
from twisted.web2.http import HTTPError, StatusResponse
45
from twisted.web2.dav import davxml
47
class xattrPropertyStore (object):
50
This implementation uses Bob Ippolito's xattr package, available from:
52
http://undefined.org/python/#xattr
54
Note that the Bob's xattr package is specific to Linux and Darwin, at least
58
# Dead properties are stored as extended attributes on disk. In order to
59
# avoid conflicts with other attributes, prefix dead property names.
61
deadPropertyXattrPrefix = "WebDAV:"
63
# Linux seems to require that attribute names use a "user." prefix.
64
# FIXME: Is is a system-wide thing, or a per-filesystem thing?
65
# If the latter, how to we detect the file system?
66
if sys.platform == "linux2":
67
deadPropertyXattrPrefix = "user."
69
def _encode(clazz, name):
71
# FIXME: The xattr API in Mac OS 10.4.2 breaks if you have "/" in an
72
# attribute name (radar://4202440). We'll quote the strings to get rid
73
# of "/" characters for now.
75
result = list("{%s}%s" % name)
76
for i in range(len(result)):
78
if c in "%/": result[i] = "%%%02X" % (ord(c),)
79
r = clazz.deadPropertyXattrPrefix + ''.join(result)
82
def _decode(clazz, name):
83
name = urllib.unquote(name[len(clazz.deadPropertyXattrPrefix):])
85
index = name.find("}")
87
if (index is -1 or not len(name) > index or not name[0] == "{"):
88
raise ValueError("Invalid encoded name: %r" % (name,))
90
return (name[1:index], name[index+1:])
92
_encode = classmethod(_encode)
93
_decode = classmethod(_decode)
95
def __init__(self, resource):
96
self.resource = resource
97
self.attrs = xattr.xattr(self.resource.fp.path)
101
value = self.attrs[self._encode(qname)]
103
raise HTTPError(StatusResponse(
104
responsecode.NOT_FOUND,
105
"No such property: {%s}%s" % qname
108
doc = davxml.WebDAVDocument.fromString(value)
110
return doc.root_element
112
def set(self, property):
113
#log.msg("Writing property %s on file %s"
114
# % (property.sname(), self.resource.fp.path))
116
self.attrs[self._encode(property.qname())] = property.toxml()
118
# Update the resource because we've modified it
119
self.resource.fp.restat()
121
def delete(self, qname):
122
#log.msg("Deleting property {%s}%s on file %s"
123
# % (qname[0], qname[1], self.resource.fp.path))
126
del(self.attrs[self._encode(qname)])
128
# RFC 2518 Section 12.13.1 says that removal of
129
# non-existing property is not an error.
132
def contains(self, qname):
134
return self._encode(qname) in self.attrs
139
prefix = self.deadPropertyXattrPrefix
140
prefix_len = len(prefix)
142
return [ self._decode(name) for name in self.attrs if name.startswith(prefix) ]