"""Module containing classes for client-side WebDAV access.
DAVResource is the class to use; it points to a location (URL) and offers
some methods to retrieve informations about the refered to resource.
"""
import urllib
from urlparse import urlparse, urlunparse, urljoin
import httplib
from pprint import pprint
import libxml2
import davbase
from davxml import xml_from_string
###
try:
False
True
except NameError:
True = 1
False = not True
pass
_DEFAULT_OWNER = u'pydav-client'
_DEFAULT_OWNER2 = u'pydav-client-2'
###
def xml_escape( s ):
"""Escape (quote) special chars for use with xml.
"""
s = s.replace("&", "&")
s = s.replace('"', """)
s = s.replace("<", "<")
return s.replace(">", ">",)
#
def _mk_nsdict ( names ):
preflist = list('ABCDEFGHIJKLMOPQRSTUVWXYZ')
nsdict = {}
for n in names:
nn, ns = n
if not nsdict.has_key(ns):
nsdict[ns] = preflist[0]
preflist = preflist[1:]
return nsdict
#
def _mk_if_data ( url, locktoken ):
s = '<%(url)s>(<%(locktoken)s>)' % locals()
return s
#
def _get_nsuri ( node ):
try:
ns = node.ns()
nsu = ns.get_content()
except libxml2.treeError:
nsu = None
pass
return nsu
#
def _get_nsprefix ( node ):
try:
ns = node.ns()
nsp = ns.get_name()
except libxml2.treeError:
nsp = None
pass
return nsp
#
def _find_child ( node, name ):
## chlds = node.get_children()
chlds = node.children
ret = []
if type(chlds) != type([]):
# XXX introduced this with Py2.1 support
while chlds:
if chlds.get_name() == name:
ret.append(chlds)
chlds = chlds.next
return ret
for n in chlds:
if n.get_name() == name:
ret.append(n)
return ret
#
###
class DAVError ( Exception ):
"""Generic DAV exception
"""
pass
#
class DAVNoFileError ( DAVError ):
"""Exception raised if a DAVFile specific method is invoked on a collection.
"""
pass
#
class DAVNoCollectionError ( DAVError ):
"""Exception raised if a collection specific method is invoked on a non-collection.
"""
pass
#
class DAVNotFoundError ( DAVError ):
"""Exception raised if a resource or a property was not found.
"""
pass
#
class DAVNotConnectedError ( DAVError ):
"""Exception raised if there is no connection to the server.
"""
pass
#
class DAVLockFailedError ( DAVError ):
"""Exception raised if an attempt to lock a resource failed.
"""
pass
#
class DAVUnlockFailedError ( DAVError ):
"""Exception raised if an attempt to lock a resource failed.
"""
pass
#
class DAVLockedError ( DAVError ):
"""Exception raised if an atempt to modify or lock a locked resource was made.
"""
pass
#
class DAVNotLockedError ( DAVError ):
"""Exception raised if an atempt to unlock a not locked resource was made.
"""
pass
#
class DAVNotOwnerError ( DAVError ):
"""Exception raised if an atempt to unlock a resource not owned was made.
"""
pass
#
class DAVInvalidLocktokenError ( DAVError ):
"""Exception raised if an atempt to unlock a not locked resource was made.
"""
pass
#
class DAVCreationFailedError ( DAVError ):
"""Exception raised if an atempt to create a resource failed.
"""
pass
#
class DAVUploadFailedError ( DAVError ):
"""Exception raised if an atempt to create a resource failed.
"""
pass
#
class DAVDeleteFailedError ( DAVError ):
"""Exception raised if an atempt to create a resource failed.
"""
pass
#
###
class DAVPropstat:
def __init__ ( self, doc, ps_node ):
self.status = None
self.reason = ''
self.properties = {}
self.locking_info = {}
self.description = ''
self._parse_ps(doc, ps_node)
return
def _parse_ps( self, doc, ps_node):
xpe = ps_node.nodePath() + '/'
st = doc.xpathEval(xpe + 'D:status')
if st:
st = st[0]
stmsg = st.get_content()
t = stmsg.split(' ', 3)
self.status = int(t[1])
self.reason = t[2]
desc = doc.xpathEval(xpe + 'D:responsedescription')
if desc:
self.description = desc[0].get_content().strip()
props = doc.xpathEval(xpe + 'D:prop/*')
for p in props:
pname = p.get_name()
pnsuri = _get_nsuri(p)
pkey = (pname, pnsuri)
pvalue = p.get_content().strip()
## pprint({'name':pkey, 'value':pvalue})
self.properties[pkey] = pvalue
# special cases
# resourcetype
path = xpe + 'D:prop/D:resourcetype/*'
re = doc.xpathEval(path)
if not re:
# resourcetype not filled
self.properties[('resourcetype','DAV:')] = ''
else:
self.properties[('resourcetype','DAV:')] = re[0].serialize().strip()
# locking info
linfo = {}
path = xpe + 'D:prop/D:lockdiscovery/D:activelock'
try:
ldelement = doc.xpathEval(path)[0]
path = ldelement.nodePath() + '/'
linfo['locktype'] = doc.xpathEval(path + 'D:locktype/*')[0].get_name().strip()
linfo['lockscope'] = doc.xpathEval(path + 'D:lockscope/*')[0].get_name().strip()
linfo['depth'] = doc.xpathEval(path + 'D:depth')[0].get_content().strip()
try:
linfo['owner'] = doc.xpathEval(path + 'D:owner')[0].serialize().strip()
except IndexError:
linfo['owner'] = None
pass
linfo['timeout'] = doc.xpathEval(path + 'D:timeout')[0].get_content().strip()
linfo['locktoken'] = doc.xpathEval(path + 'D:locktoken/D:href')[0].get_content().strip()
except IndexError:
pass
self.locking_info = linfo
return
def has_errors ( self ):
s = self.status
if s is not None and s >= 300:
return True
return False
def dump ( self ):
print "\t\tDAVPropstat Status:", self.status
print "\t\tDAVPropstat Reason:", self.reason
print "\t\tDAVPropstat Desc:", self.description
print "\t\tDAVPropstat Properties:",
pprint(self.properties)
print "\t\tDAVPropstat Locking info:",
pprint(self.locking_info)
print
#
class DAVResponse:
def __init__ ( self, doc, res_node ):
self.propstats = []
self.url = None
self.status = None
self.reason = None
self._parse_res(doc, res_node)
return
def _parse_res ( self, doc, res_node ):
href_nodes = _find_child(res_node, 'href')
if not href_nodes:
raise DAVNotFoundError, ('No href found in node %s!' % res_node.nodePath())
url_node = href_nodes[0]
self.url = urllib.unquote(url_node.get_content().strip())
status_nodes = _find_child(res_node, 'status')
if status_nodes:
st = status_nodes[0].get_content()
proto, status, reason = st.split(' ', 2)
self.status = status
self.reason = reason
pslist = _find_child(res_node, 'propstat')
for node in pslist:
ps = DAVPropstat(doc, node)
self.propstats.append(ps)
return
def has_errors ( self ):
s = self.status
if s is not None and s >= 300:
return True
for p in self.propstats:
if p.has_errors():
return True
return False
def propstat_count ( self ):
return len(self.propstats)
def get_propstat ( self, idx=0 ):
return self.propstats[idx]
def get_all_properties ( self ):
ret = {}
psl = [ ps.properties for ps in self.propstats if ps.status < 300 ]
for p in psl:
ret.update(p)
return ret
def get_locking_info ( self ):
ret = {}
iil = [ ps.locking_info for ps in self.propstats if ps.status < 300 ]
for i in iil:
ret.update(i)
return ret
def dump ( self ):
print "\tDAVResponse for", self.url
print "\tDAVResponse status:", self.status, self.reason
for p in self.propstats:
p.dump()
#
class DAVResult:
def __init__ ( self, http_response=None ):
"""Initialize a DAVResult instance.
If http_response is given (and a httplib.HTTPResponse instance)
the status code and the reason are copied.
If the status code equals 207 (Multi-Status), the body of
the response is read and parsed.
"""
self.responses = {}
self.etag = self.status = self.reason = None
if http_response is None:
return
data = http_response.read()
self.status = int(http_response.status)
self.reason = http_response.reason
self.etag = http_response.getheader('ETag', None)
self.lock_token = http_response.getheader('Lock-Token', None)
if self.lock_token and self.lock_token[0] == '<':
self.lock_token = self.lock_token[1:-1]
if self.status != 207:
return
self.parse_data(data)
return
def parse_data ( self, data ):
try:
doc = xml_from_string(data)
self._parse_response(doc)
doc.free()
except Exception, ex:
raise Exception, (data,)
return
def _parse_response ( self, doc ):
self.responses = {}
responses = doc.get_response_nodes()
for node in responses:
r = DAVResponse(doc, node)
self.responses[r.url] = r
return
def has_errors ( self ):
if self.status >= 300:
return True
for r in self.responses.values():
if r.has_errors():
return True
return False
def response_count ( self ):
return len(self.responses)
def get_response ( self, uri ):
try:
return self.responses[uri]
except KeyError as exc:
if uri[-1] == '/':
return self.responses[uri[:-1]]
else:
raise
def get_locktoken ( self, url ):
r = self.responses[url]
li = r.get_locking_info()
if li.has_key('locktoken'):
return li['locktoken']
return None
def get_etag ( self, url ):
r = self.responses[url]
pd = r.get_all_properties()
etag = pd.get(('getetag','DAV:'), None)
return etag
def dump ( self ):
print '='*60
print "DAVResult Dump"
print "HTTP Status:", self.status
print "HTTP Reason:", self.reason
for r in self.responses.values():
r.dump()
#
###
class DAVResource:
"""Basic class describing an arbitrary DAV resource (file or collection)
"""
def __init__ ( self, url, conn=None, auto_request=False ):
"""Setup a fresh instance.
"""
self._set_url(url)
self.auto_request = auto_request
self.collection = None
self.size = None
self.locktoken = None
self._conn = conn
self._result = None
return
def _set_url ( self, url ):
# extract server/port from url, needed for DAV
self.url = url
url_tuple = urlparse(url, 'http', 0)
self.scheme = url_tuple[0]
self.host = url_tuple[1]
self.path = url_tuple[2]
return
def _make_url_for ( self, path ):
t = (self.scheme, self.host, path) + ('','','')
return urlunparse(t)
def get_server ( self ):
return self._make_url_for('/')
def invalidate ( self ):
"""Invalidate the internal cache, so that the next method call will
invoke a request to the server.
"""
self.size = None
self.etag = None
self.collection = None
self._result = None
return
def update ( self, conn=None, depth=0 ):
"""Update all local data for this resource.
Returns a DAVResult instance as result.
This result is also stored internally, so don't mess with it.
Issues a propfind request with depth 'depth' to the server.
If the conn parameter is not None, it sets the connection to the
given one.
"""
if conn is not None:
self.set_connection(conn)
else:
self.invalidate() # just to be sure
try:
self._result = self._propfind(depth=depth)
## # XXX debug XXX
## self._result.dump()
v = self.get_property_value( ('resourcetype', 'DAV:') )
self.collection = v.find('collection') >= 0
except DAVError, ex:
if ex.args[0] == 404:
raise DAVNotFoundError, ex.args
else:
raise
return self._result
def is_connected ( self ):
"""Return True if there is a connection established.
"""
return self._conn is not None
def set_connection ( self, conn ):
"""Set the connection.
conn has to be a DAV instance from the davlib module.
"""
self._conn = conn
self.invalidate()
return
def connect ( self ):
"""Create a connection for this resource.
"""
h = self.host
ht = h.split(':')
try:
port = int(ht[1])
except (ValueError, IndexError):
port = None
pass
con = davbase.DAVConnection(ht[0], port)
self.set_connection(con)
return
def get_property_namespaces ( self ):
"""Return a list of tuples with all used namespace uris and their prefixes used for properties.
"""
if self.auto_request or not self._result:
self.update()
result = self._result
response = result.get_response(self.path)
names = response.get_all_properties().keys()
d = {}
for name, ns in names:
d[ns] = None
return d.keys()
def get_property_names ( self, nsuri=None ):
"""Return the names of all properties within the given namespace uri
If nsuri is empty (or None), return all property names from all
namespaces.
The result is a list of tuples (name, nsuri).
"""
props = []
if self.auto_request or not self._result:
self.update()
result = self._result
response = result.get_response(self.path)
# get all properties
if not nsuri:
res = response.get_all_properties().keys()
else:
res = [ t for t in response.get_all_properties().keys() if t[1] == nsuri ]
return res
def get_property_value ( self, propname ):
"""Return the value of the given property.
propname is a tuple of (name, nsuri).
"""
if self.auto_request or not self._result:
self.update()
result = self._result
response = result.get_response(self.path)
props = response.get_all_properties()
ret = props.get(propname, None)
return ret
def get_all_properties ( self ):
r = self._result.get_response(self.path)
ret = r.get_all_properties()
return ret
def get_etag ( self ):
"""Return the etag for this resource or None.
"""
etag = self._result.get_etag(self.path)
return etag
## def get_src_link ( self ):
## """If there is a source property return the src link otherwise return None.
## """
## if self.auto_request or not self._result:
## self.update()
## result = self._result
## res = result.res_doc.xpathEval('//D:source/D:link/D:src')
## if not res:
## return None
## return res[0].get_content()
## def get_dst_link ( self ):
## """If there is a source property, return the dst link otherwise return None.
## """
## if self.auto_request or not self._result:
## self.update()
## result = self._result
## res = result.res_doc.xpathEval('//D:source/D:link/D:dst')
## if not res:
## return None
## return res[0].get_content()
## def set_dst_link ( self, url ):
## """Set the link/dst element of the source property to the given url.
## Returns a DAVResult instance as result.
## The link/src element is set to the url this resource refers to.
## The urls are xml escaped before they're stored.
## """
## xml_head = '\n\n'
## xml_head += ''
## xml_tail = ''
## body = '' + xml_escape(url) + '' + xml_escape(self.url) + ''
## xml = xml_head + body + xml_tail
## res = self._proppatch(xml)
## return res
def set_properties ( self, pdict ):
"""Set or update the properties in pdict on this resource.
Returns a DAVResult instance as result.
The property names (keys of pdict) *have* to be tuples of (name, nsuri).
The property values will be xml escaped before they're stored and should be strings.
"""
# generate xml body for request
# make xml header incl. namspace declarations
nsdict = _mk_nsdict(pdict.keys())
xml_head = u'\n\n'
xml_head += u''
xml_tail = u''
xset = u''
# create body
for k, v in pdict.items():
name, nsuri = k
if type(v) == type(''):
v = v.decode('utf-8')
prefix = nsdict[nsuri].decode('utf-8')
pn = prefix + u':' + name.decode('utf-8')
if not v:
v = u''
## v = v.encode('utf-8')
v = xml_escape(v)
xset += u'<%s>%s%s>\n' % (pn, v, pn)
xml_body = xml_head + xset + xml_tail
xml_body = xml_body.encode('utf-8')
res = self._proppatch(xml_body)
return res
def del_properties ( self, plist ):
"""Delete the properties in plist on this resource.
Returns a DAVResult instance as result.
plist has to be a list of (name, nsuri) tuples.
"""
# generate xml body for request
nsdict = _mk_nsdict(plist)
xml_head = '\n\n'
xml_head += '\n'
xml_tail = ''
xset = ''
for k in plist:
name, nsuri = k
prefix = nsdict[nsuri]
pn = prefix + u':' + name
xset += '<%s />\n' % pn
xml_body = xml_head + xset + xml_tail
res = self._proppatch(xml_body)
return res
def is_collection ( self ):
"""Returns true if self refers to a collection.
"""
if self.auto_request or not self._result:
self.update()
return self.collection
def get ( self, xhdrs=None ):
"""Issue a get request to the url of this instance and return
a httplib.HTTPResponse instance.
"""
if xhdrs is None:
xhdrs = {}
res = self._conn.get(self.url, xhdrs)
return res
def head ( self, xhdrs=None ):
"""Issue a head request to the url of this instance and return
a httplib.HTTPResponse instance.
"""
if xhdrs is None:
xhdrs = {}
res = self._conn.head(self.url, xhdrs)
return res
def options ( self, xhdrs=None ):
"""Issue an options request to the url of this instance and return
a dict containing the result.
The resulting keys are: server, allow, dav
"""
if xhdrs is None:
xhdrs = {}
res = self._conn.options(self.url, xhdrs)
d = {}
for k, v in res.msg.items():
if k.lower() in ('dav','server','allow','ms-author-via'):
d[k] = v
return d
def is_locked ( self ):
"""Return True if this resource is locked.
"""
if self.auto_request or not self._result:
self.update()
result = self._result
lt = result.get_locktoken(self.path)
return bool(lt)
def get_locking_info ( self ):
"""Query the server for locking information.
The information is returned in a dict, which might be empty if no
lock is on the resource.
This method *always* issues a propfind request to the server!
"""
ret = {}
self.invalidate()
if not self.is_locked():
# short cut
return ret
response = self._result.get_response(self.path)
li = response.get_locking_info()
return li.copy()
def _lock_path ( self, path, owner=None, depth=0, header=None ):
if header is None:
header = {}
r = self._conn.lock(path, owner=owner, depth=depth, extra_hdrs=header)
davres = DAVResult(r)
return davres
def _unlock_path ( self, path, locktoken, header=None ):
if header is None:
header = {}
r = self._conn.unlock(path, locktoken, extra_hdrs=header)
davres = DAVResult(r)
return davres
def lock ( self, owner=None, depth='0' ):
"""Lock the resource this instance refers to and return the locktoken.
owner is a plain string or a xml fragment which is stored with the lock
information.
depth specifies the locking depth. Valid values are: 0 (default),
1 and 'infinite' which might not be supported by the server.
If the resource is already locked, DAVLockedError is raised.
If the lock fails, DAVLockFailedError is raised.
This method *always* issues a propfind request before the lock request!
This implies that it's not possible to lock a non-existing resource
with this method.
"""
self.invalidate()
if self.is_locked():
raise DAVLockedError
davres = self._lock_path(self.path, owner, depth)
if not davres.has_errors():
self.locktoken = davres.lock_token
return self.locktoken
raise DAVLockFailedError, (davres,)
def unlock ( self, locktoken=None, owner=None ):
"""Unlock this resource, if it is locked and the right locktoken is passed.
The method does not return anything.
If locktoken is None the method tries to use self.locktoken.
If there is no locktoken or the locktoken doesn't mactch the required
token, DAVInvalidLocktokenError is raised.
If the given owner data does not match the owner data stored with the resource,
DAVNotOwnerError is raised.
If the resource is not locked, DAVNotLockedError is raised.
This method *always* issues a propfind request before the unlock request!
"""
self.invalidate()
linfo = self.get_locking_info()
if not linfo:
raise DAVNotLockedError
# check if onwer matches
if owner is not None:
try:
if linfo['owner'] != owner:
raise DAVNotOwnerError
except KeyError:
# no owner set on lock, raise
raise DAVNotOwnerError
if locktoken is None:
# no locktoken provided, try to use our token
locktoken = self.locktoken
if not locktoken:
# no locktoken available
raise DAVInvalidLocktokenError
# check if our locktoken matches the required one
if locktoken != linfo['locktoken']:
raise DAVInvalidLocktokenError
davres = self._unlock_path(self.url, locktoken)
if not davres.has_errors():
self.update()
if self.locktoken == locktoken:
self.locktoken = None
return
raise DAVUnlockFailedError, (davres,)
def _propfind ( self, depth=0 ):
"""Query all properties for this resource and return a DAVResult instance.
"""
if not self.is_connected():
self.connect()
hdrs = {}
# if we have a locktoken, supply it
if self.locktoken is not None:
if self.locktoken[0] != '<':
lt = '<' + self.locktoken + '>'
hdrs['Lock-Token'] = self.locktoken
hdrs['If'] = '<%s>(%s)' % (self.url, lt)
xml = '\n'
xml += '\n'
try:
self._conn._con._http_vsn_str = 'HTTP/1.0'
self._conn._con._http_vsn = 10
try:
response = self._conn.propfind(self.url, body=xml,
depth=depth, extra_hdrs=hdrs)
except davbase.RedirectError, err:
new_url = err.args[0]
self._set_url(new_url)
# re-issue request
response = self._conn.propfind(self.url, body=xml,
depth=depth, extra_hdrs=hdrs)
pass
davres = DAVResult(response)
finally:
self._conn._con._http_vsn_str = 'HTTP/1.1'
self._conn._con._http_vsn = 11
if davres.status >= 300: # or davres.status in (404,200):
raise DAVError, (davres.status, davres.reason, davres)
return davres
def _proppatch ( self, body ):
"""Issue a PROPPATCH request with the given xml body.
Returns a DAVResult instance as result.
"""
if not self.is_connected():
self.connect()
hdrs = {}
# if we have a locktoken, supply it
if self.locktoken is not None:
if self.locktoken[0] != '<':
lt = '<' + self.locktoken + '>'
hdrs['Lock-Token'] = self.locktoken
hdrs['If'] = '<%s>(%s)' % (self.url, lt)
try:
self._conn._con._http_vsn_str = 'HTTP/1.0'
self._conn._con._http_vsn = 10
response = self._conn.proppatch(self.url, body=body, extra_hdrs=hdrs)
davres = DAVResult(response)
finally:
self._conn._con._http_vsn_str = 'HTTP/1.1'
self._conn._con._http_vsn = 11
if davres.status in (200,404) or davres.status >= 300:
raise DAVError, (davres.status, davres.reason, davres)
return davres
#
class DAVFile ( DAVResource ):
def __init__ ( self, url, conn=None, auto_request=False ):
DAVResource.__init__(self, url, conn, auto_request)
self.update()
if self.is_collection():
raise DAVNoFileError
return
def file_size ( self ):
"""Return the size of this DAVFile in bytes.
The size is taken out of the getcontentlength property.
If the getcontentlength property is not found, -1 is returned.
"""
if self.auto_request or not self._result:
self.update()
try:
fs = self.get_property_value( ('getcontentlength', 'DAV:') )
if not fs:
fs = 0
except DAVNotFoundError:
fs = -1
pass
self.size = int(fs)
return self.size
def upload ( self, data, mime_type=None, encoding=None ):
"""Upload data to this file via a PUT request.
"""
if mime_type is None:
mime_type = 'application/octet-stream'
self.update()
if self.is_locked():
linfo = self.get_locking_info()
if not (self.locktoken and self.locktoken == linfo['locktoken']):
return DAVLockedError
hdrs = {}
if self.locktoken:
hdrs['Lock-Token'] = '<' + self.locktoken + '>'
hdrs['If'] = '<%s>(<%s>)' % (self.url, self.locktoken)
etag = self.get_etag()
## print 'upload: ETAG:', etag
if etag:
try:
ifclause = hdrs['If']
ifclause += '([%s])' % etag
except KeyError:
ifclause = '<%s>([%s])' % (self.url, etag)
pass
hdrs['If'] = ifclause
res = self._conn.put(self.url, data,
content_type=mime_type,
content_enc=encoding,
extra_hdrs=hdrs)
res = DAVResult(res)
if res.status not in (200, 201, 204):
raise DAVUploadFailedError, (res.status, res.reason)
self.update()
return
#
class DAVCollection ( DAVResource ):
def __init__ ( self, url, conn=None, auto_request=False ):
"""Initialize a fresh DAVCollection instance.
Call DAVResource.__init__ and checks if the url points to
a collection. If the url does not point to a collection,
DAVNoCollectionError is raised.
"""
if url[-1] != '/':
url += '/'
DAVResource.__init__(self, url, conn, auto_request)
if not self.is_collection():
raise DAVNoCollectionError
return
def update ( self, conn=None, depth=1 ):
return DAVResource.update(self, conn=conn, depth=depth)
def get_child_names ( self ):
"""Return all children of this collection as absolute path names
on this server.
"""
ret = []
if self.auto_request or not self._result:
self.update()
result = self._result
uq = urllib.unquote
mypath = uq(self.path)
for url, e in result.responses.items():
href = uq(url)
if href == mypath:
continue
ret.append(href)
return ret
def get_child_objects ( self ):
"""Return all children of this collection as DAVResources.
"""
ret = []
if self.auto_request or not self._result:
self.update()
# for all (except ourself) responses get the href element
# and create the appropiate instance for it
result = self._result
uq = urllib.unquote
for url, e in result.responses.items():
if url == self.path:
continue
href = uq(url)
if href == self.path:
continue
furl = self._make_url_for(href)
try:
fo = DAVFile(furl, self._conn, self.auto_request)
except DAVNoFileError:
fo = DAVCollection(furl, self._conn, self.auto_request)
pass
except DAVError, ex:
if ex.args[0] in (403, 404, 405): # forbidden, not found, mehtod not allowed
# ignore files one does not have access to
continue
raise
ret.append(fo)
return ret
def _do_create_collection ( self, name ):
conn = self._conn
# construct path
while name and name[0] == '/':
name = name[1:]
if name[-1] != '/':
name += '/'
url = urljoin(self.url, name )
path = urlparse(url, 'http', 0)[2]
res = conn.mkcol(path)
if res:
res = DAVResult(res)
return (res, url)
def _do_create_file ( self, name, data='', content_type=None, encoding=None ):
conn = self._conn
# construct path
while name and name[0] == '/':
name = name[1:]
url = urljoin(self.url, name)
path = urlparse(url, 'http', 0)[2]
# lock resource, should be ok even if the resource does not exist
res = self._lock_path(path, owner=_DEFAULT_OWNER2, depth='0')
if res.status not in (200, 201):
raise DAVLockedError, (res.status, res.reason, url)
locktoken = res.lock_token
try:
# check if there is a resource with that name
r = conn.head(url)
r.read()
if r.status != 404:
# resource exists!
raise DAVCreationFailedError, (r.status, r.reason, url)
# resource does not exist, create file
# headers needed to honor the lock
hdr = { 'If': '<%s>(<%s>)' % (url, locktoken),
'Lock-Token': '<%s>' % locktoken }
res = None
res = conn.put(path, data,
content_type=content_type,
content_enc=encoding,
extra_hdrs=hdr)
if res:
res = DAVResult(res)
finally:
# unlock resource, even in case of exception
self._unlock_path(path, locktoken)
return (res, url)
def create_collection ( self, name ):
"""Create a new sub-collection as direct child of this collection.
Path names like 'xxx/aaa' are not allowed and will result in an error.
Returns a DAVCollection instance refering to the newly create collection.
"""
res, url = self._do_create_collection(name)
if res.status in (200, 201):
# created, return collection
return DAVCollection(url, self._conn)
raise DAVCreationFailedError(res.status, res.reason, url)
def create_file ( self, name, data='', content_type=None ):
"""Create a new file as direct child of this collection.
Path names like 'xxx/aaa' are not allowed and will result in an error.
Returns a DAVFile instance refering to the newly created file.
"""
res, url = self._do_create_file(name, data=data, content_type=content_type)
if res.status in (200, 201):
# created, return file
self.update()
return DAVFile(url, self._conn)
raise DAVCreationFailedError, (res.status, res.reason, url)
def _do_del ( self, url, path ):
locktoken = self.lock(owner=_DEFAULT_OWNER, depth='infinity')
# issue del request and hold result
hdr = { 'If': _mk_if_data(url, locktoken) }
try:
res = self._conn.delete(path, hdr)
finally:
# unlock resource asap
self.unlock(locktoken)
return res
def delete ( self, name ):
"""Delete a resource from this collection
"""
if self.is_locked():
raise DAVLockedError
# construct path
while name and name[0] == '/':
name = name[1:]
url = urljoin(self.url, name)
path = urlparse(url, 'http', 0)[2]
# do delete
res = self._do_del(url, path)
if res.status >= 300:
raise DAVDeleteFailedError, (res.status, res.reason, url)
# deleted and done
self.update()
return
#
###