[MERGE] lp:~xrg/openobject-addons/trunk-patch18
[odoo/odoo.git] / addons / document_webdav / dav_fs.py
1 # -*- coding: utf-8 -*-
2 ##############################################################################
3 #
4 #    OpenERP, Open Source Management Solution
5 #    Copyright (C) 2004-2010 Tiny SPRL (<http://tiny.be>).
6 #
7 #    This program is free software: you can redistribute it and/or modify
8 #    it under the terms of the GNU Affero General Public License as
9 #    published by the Free Software Foundation, either version 3 of the
10 #    License, or (at your option) any later version.
11 #
12 #    This program is distributed in the hope that it will be useful,
13 #    but WITHOUT ANY WARRANTY; without even the implied warranty of
14 #    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15 #    GNU Affero General Public License for more details.
16 #
17 #    You should have received a copy of the GNU Affero General Public License
18 #    along with this program.  If not, see <http://www.gnu.org/licenses/>.
19 #
20 ##############################################################################
21 import pooler
22
23 import os
24 import time
25 import errno
26
27 import netsvc
28 import urlparse
29
30 from DAV.constants import COLLECTION  #, OBJECT
31 from DAV.errors import DAV_Error, DAV_Forbidden, DAV_NotFound
32 from DAV.iface import dav_interface
33 import urllib
34
35 from DAV.davcmd import copyone, copytree, moveone, movetree, delone, deltree
36 from cache import memoize
37 from tools import misc
38
39 from webdav import mk_lock_response
40
41 try:
42     from tools.dict_tools import dict_merge2
43 except ImportError:
44     from document.dict_tools import dict_merge2
45
46 CACHE_SIZE=20000
47
48 #hack for urlparse: add webdav in the net protocols
49 urlparse.uses_netloc.append('webdav')
50 urlparse.uses_netloc.append('webdavs')
51
52 day_names = { 0: 'Mon', 1: 'Tue' , 2: 'Wed', 3: 'Thu', 4: 'Fri', 5: 'Sat', 6: 'Sun' }
53 month_names = { 1: 'Jan', 2: 'Feb', 3: 'Mar', 4: 'Apr', 5: 'May', 6: 'Jun',
54         7: 'Jul', 8: 'Aug', 9: 'Sep', 10: 'Oct', 11: 'Nov', 12: 'Dec' }
55
56 class DAV_NotFound2(DAV_NotFound):
57     """404 exception, that accepts our list uris
58     """
59     def __init__(self, *args):
60         if len(args) and isinstance(args[0], (tuple, list)):
61             path = ''.join([ '/' + x for x in args[0]])
62             args = (path, )
63         DAV_NotFound.__init__(self, *args)
64
65
66 def _str2time(cre):
67     """ Convert a string with time representation (from db) into time (float)
68     """
69     if not cre:
70         return time.time()
71     frac = 0.0
72     if isinstance(cre, basestring) and '.' in cre:
73         fdot = cre.find('.')
74         frac = float(cre[fdot:])
75         cre = cre[:fdot]
76     return time.mktime(time.strptime(cre,'%Y-%m-%d %H:%M:%S')) + frac
77
78 class BoundStream2(object):
79     """Wraps around a seekable buffer, reads a determined range of data
80     
81         Note that the supplied stream object MUST support a size() which
82         should return its data length (in bytes).
83     
84         A variation of the class in websrv_lib.py
85     """
86     
87     def __init__(self, stream, offset=None, length=None, chunk_size=None):
88         self._stream = stream
89         self._offset = offset or 0
90         self._length = length or self._stream.size()
91         self._rem_length = length
92         assert length and isinstance(length, (int, long))
93         assert length and length >= 0, length
94         self._chunk_size = chunk_size
95         if offset is not None:
96             self._stream.seek(offset)
97
98     def read(self, size=-1):
99         if not self._stream:
100             raise IOError(errno.EBADF, "read() without stream")
101         
102         if self._rem_length == 0:
103             return ''
104         elif self._rem_length < 0:
105             raise EOFError()
106
107         rsize = self._rem_length
108         if size > 0 and size < rsize:
109             rsize = size
110         if self._chunk_size and self._chunk_size < rsize:
111             rsize = self._chunk_size
112         
113         data = self._stream.read(rsize)
114         self._rem_length -= len(data)
115
116         return data
117
118     def __len__(self):
119         return self._length
120
121     def tell(self):
122         res = self._stream.tell()
123         if self._offset:
124             res -= self._offset
125         return res
126
127     def __iter__(self):
128         return self
129
130     def next(self):
131         return self.read(65536)
132
133     def seek(self, pos, whence=os.SEEK_SET):
134         """ Seek, computing our limited range
135         """
136         if whence == os.SEEK_SET:
137             if pos < 0 or pos > self._length:
138                 raise IOError(errno.EINVAL,"Cannot seek")
139             self._stream.seek(pos - self._offset)
140             self._rem_length = self._length - pos
141         elif whence == os.SEEK_CUR:
142             if pos > 0:
143                 if pos > self._rem_length:
144                     raise IOError(errno.EINVAL,"Cannot seek past end")
145                 elif pos < 0:
146                     oldpos = self.tell()
147                     if oldpos + pos < 0:
148                         raise IOError(errno.EINVAL,"Cannot seek before start")
149                 self._stream.seek(pos, os.SEEK_CUR)
150                 self._rem_length -= pos
151         elif whence == os.SEEK_END:
152             if pos > 0:
153                 raise IOError(errno.EINVAL,"Cannot seek past end")
154             else:
155                 if self._length + pos < 0:
156                     raise IOError(errno.EINVAL,"Cannot seek before start")
157             newpos = self._offset + self._length + pos
158             self._stream.seek(newpos, os.SEEK_SET)
159             self._rem_length = 0 - pos
160
161 class openerp_dav_handler(dav_interface):
162     """
163     This class models a OpenERP interface for the DAV server
164     """
165     PROPS={'DAV:': dav_interface.PROPS['DAV:'],}
166
167     M_NS={ "DAV:" : dav_interface.M_NS['DAV:'],}
168
169     def __init__(self,  parent, verbose=False):
170         self.db_name_list=[]
171         self.parent = parent
172         self.baseuri = parent.baseuri
173         self.verbose = verbose
174
175     def get_propnames(self, uri):
176         props = self.PROPS
177         self.parent.log_message('get propnames: %s' % uri)
178         cr, uid, pool, dbname, uri2 = self.get_cr(uri)
179         if not dbname:
180             if cr: cr.close()
181             # TODO: maybe limit props for databases..?
182             return props
183         node = self.uri2object(cr, uid, pool, uri2)
184         if node:
185             props = dict_merge2(props, node.get_dav_props(cr))
186         cr.close()
187         return props
188
189     def _try_function(self, funct, args, opname='run function', cr=None,
190             default_exc=DAV_Forbidden):
191         """ Try to run a function, and properly convert exceptions to DAV ones.
192
193             @objname the name of the operation being performed
194             @param cr if given, the cursor to close at exceptions
195         """
196
197         try:
198             return funct(*args)
199         except DAV_Error:
200             if cr: cr.close()
201             raise
202         except NotImplementedError, e:
203             if cr: cr.close()
204             import traceback
205             self.parent.log_error("Cannot %s: %s", opname, str(e))
206             self.parent.log_message("Exc: %s",traceback.format_exc())
207             # see par 9.3.1 of rfc
208             raise DAV_Error(403, str(e) or 'Not supported at this path')
209         except EnvironmentError, err:
210             if cr: cr.close()
211             import traceback
212             self.parent.log_error("Cannot %s: %s", opname, err.strerror)
213             self.parent.log_message("Exc: %s",traceback.format_exc())
214             raise default_exc(err.strerror)
215         except Exception, e:
216             import traceback
217             if cr: cr.close()
218             self.parent.log_error("Cannot %s: %s", opname, str(e))
219             self.parent.log_message("Exc: %s",traceback.format_exc())
220             raise default_exc("Operation failed")
221
222     def _get_dav_lockdiscovery(self, uri):
223         """ We raise that so that the node API is used """
224         raise DAV_NotFound
225
226     def _get_dav_supportedlock(self, uri):
227         """ We raise that so that the node API is used """
228         raise DAV_NotFound
229
230     def match_prop(self, uri, match, ns, propname):
231         if self.M_NS.has_key(ns):
232             return match == dav_interface.get_prop(self, uri, ns, propname)
233         cr, uid, pool, dbname, uri2 = self.get_cr(uri)
234         if not dbname:
235             if cr: cr.close()
236             raise DAV_NotFound
237         node = self.uri2object(cr, uid, pool, uri2)
238         if not node:
239             cr.close()
240             raise DAV_NotFound
241         res = node.match_dav_eprop(cr, match, ns, propname)
242         cr.close()
243         return res
244
245     def prep_http_options(self, uri, opts):
246         """see HttpOptions._prep_OPTIONS """
247         self.parent.log_message('get options: %s' % uri)
248         cr, uid, pool, dbname, uri2 = self.get_cr(uri, allow_last=True)
249
250         if not dbname:
251             if cr: cr.close()
252             return opts
253         node = self.uri2object(cr, uid, pool, uri2[:])
254
255         if not node:
256             if cr: cr.close()
257             return opts
258         else:
259             if hasattr(node, 'http_options'):
260                 ret = opts.copy()
261                 for key, val in node.http_options.items():
262                     if isinstance(val, basestring):
263                         val = [val, ]
264                     if key in ret:
265                         ret[key] = ret[key][:]  # copy the orig. array
266                     else:
267                         ret[key] = []
268                     ret[key].extend(val)
269
270                 self.parent.log_message('options: %s' % ret)
271             else:
272                 ret = opts
273             cr.close()
274             return ret
275
276     def reduce_useragent(self):
277         ua = self.parent.headers.get('User-Agent', False)
278         ctx = {}
279         if ua:
280             if 'iPhone' in ua:
281                 ctx['DAV-client'] = 'iPhone'
282             elif 'Konqueror' in ua:
283                 ctx['DAV-client'] = 'GroupDAV'
284         return ctx
285
286     def get_prop(self, uri, ns, propname):
287         """ return the value of a given property
288
289             uri        -- uri of the object to get the property of
290             ns        -- namespace of the property
291             pname        -- name of the property
292          """
293         if self.M_NS.has_key(ns):
294             try:
295                 # if it's not in the interface class, a "DAV:" property
296                 # may be at the node class. So shouldn't give up early.
297                 return dav_interface.get_prop(self, uri, ns, propname)
298             except DAV_NotFound:
299                 pass
300         cr, uid, pool, dbname, uri2 = self.get_cr(uri)
301         if not dbname:
302             if cr: cr.close()
303             raise DAV_NotFound
304         try:
305             node = self.uri2object(cr, uid, pool, uri2)
306             if not node:
307                 raise DAV_NotFound
308             res = node.get_dav_eprop(cr, ns, propname)
309         finally:
310             cr.close()
311         return res
312
313     def get_db(self, uri, rest_ret=False, allow_last=False):
314         """Parse the uri and get the dbname and the rest.
315            Db name should be the first component in the unix-like
316            path supplied in uri.
317
318            @param rest_ret Instead of the db_name, return (db_name, rest),
319                 where rest is the remaining path
320            @param allow_last If the dbname is the last component in the
321                 path, allow it to be resolved. The default False value means
322                 we will not attempt to use the db, unless there is more
323                 path.
324
325            @return db_name or (dbname, rest) depending on rest_ret,
326                 will return dbname=False when component is not found.
327         """
328
329         uri2 = self.uri2local(uri)
330         if uri2.startswith('/'):
331             uri2 = uri2[1:]
332         names=uri2.split('/',1)
333         db_name=False
334         rest = None
335         if allow_last:
336             ll = 0
337         else:
338             ll = 1
339         if len(names) > ll and names[0]:
340             db_name = names[0]
341             names = names[1:]
342
343         if rest_ret:
344             if len(names):
345                 rest = names[0]
346             return db_name, rest
347         return db_name
348
349
350     def urijoin(self,*ajoin):
351         """ Return the base URI of this request, or even join it with the
352             ajoin path elements
353         """
354         return self.parent.get_baseuri(self) + '/'.join(ajoin)
355
356     @memoize(4)
357     def db_list(self):
358         s = netsvc.ExportService.getService('db')
359         result = s.exp_list()
360         self.db_name_list=[]
361         for db_name in result:
362             cr = None
363             try:
364                 db = pooler.get_db_only(db_name)
365                 cr = db.cursor()
366                 cr.execute("SELECT id FROM ir_module_module WHERE name = 'document' AND state='installed' ")
367                 res=cr.fetchone()
368                 if res and len(res):
369                     self.db_name_list.append(db_name)
370             except Exception, e:
371                 self.parent.log_error("Exception in db list: %s" % e)
372             finally:
373                 if cr:
374                     cr.close()
375         return self.db_name_list
376
377     def get_childs(self,uri, filters=None):
378         """ return the child objects as self.baseuris for the given URI """
379         self.parent.log_message('get children: %s' % uri)
380         cr, uid, pool, dbname, uri2 = self.get_cr(uri, allow_last=True)
381
382         if not dbname:
383             if cr: cr.close()
384             res = map(lambda x: self.urijoin(x), self.db_list())
385             return res
386         result = []
387         node = self.uri2object(cr, uid, pool, uri2[:])
388
389         try:
390             if not node:
391                 raise DAV_NotFound2(uri2)
392             else:
393                 fp = node.full_path()
394                 if fp and len(fp):
395                     fp = '/'.join(fp)
396                     self.parent.log_message('children for: %s' % fp)
397                 else:
398                     fp = None
399                 domain = None
400                 if filters:
401                     domain = node.get_domain(cr, filters)
402                     
403                     if hasattr(filters, 'getElementsByTagNameNS'):
404                         hrefs = filters.getElementsByTagNameNS('DAV:', 'href')
405                         if hrefs:
406                             ul = self.parent.davpath + self.uri2local(uri)
407                             for hr in hrefs:
408                                 turi = ''
409                                 for tx in hr.childNodes:
410                                     if tx.nodeType == hr.TEXT_NODE:
411                                         turi += tx.data
412                                 if not turi.startswith('/'):
413                                     # it may be an absolute URL, decode to the
414                                     # relative part, because ul is relative, anyway
415                                     uparts=urlparse.urlparse(turi)
416                                     turi=uparts[2]
417                                 if turi.startswith(ul):
418                                     result.append( turi[len(self.parent.davpath):])
419                                 else:
420                                     self.parent.log_error("ignore href %s because it is not under request path %s", turi, ul)
421                             return result
422                             # We don't want to continue with the children found below
423                             # Note the exceptions and that 'finally' will close the
424                             # cursor
425                 for d in node.children(cr, domain):
426                     self.parent.log_message('child: %s' % d.path)
427                     if fp:
428                         result.append( self.urijoin(dbname,fp,d.path) )
429                     else:
430                         result.append( self.urijoin(dbname,d.path) )
431         except DAV_Error:
432             raise
433         except Exception, e:
434             self.parent.log_error("cannot get_children: "+ str(e))
435             raise
436         finally:
437             if cr: cr.close()
438         return result
439
440     def uri2local(self, uri):
441         uparts=urlparse.urlparse(uri)
442         reluri=uparts[2]
443         if reluri and reluri[-1]=="/":
444             reluri=reluri[:-1]
445         return reluri
446
447     #
448     # pos: -1 to get the parent of the uri
449     #
450     def get_cr(self, uri, allow_last=False):
451         """ Split the uri, grab a cursor for that db
452         """
453         pdb = self.parent.auth_proxy.last_auth
454         dbname, uri2 = self.get_db(uri, rest_ret=True, allow_last=allow_last)
455         uri2 = (uri2 and uri2.split('/')) or []
456         if not dbname:
457             return None, None, None, False, uri2
458         # if dbname was in our uri, we should have authenticated
459         # against that.
460         assert pdb == dbname, " %s != %s" %(pdb, dbname)
461         res = self.parent.auth_proxy.auth_creds.get(dbname, False)
462         if not res:
463             self.parent.auth_proxy.checkRequest(self.parent, uri, dbname)
464             res = self.parent.auth_proxy.auth_creds[dbname]
465         user, passwd, dbn2, uid = res
466         db,pool = pooler.get_db_and_pool(dbname)
467         cr = db.cursor()
468         return cr, uid, pool, dbname, uri2
469
470     def uri2object(self, cr, uid, pool, uri):
471         if not uid:
472             return None
473         context = self.reduce_useragent()
474         return pool.get('document.directory').get_object(cr, uid, uri, context=context)
475
476     def get_data(self,uri, rrange=None):
477         self.parent.log_message('GET: %s' % uri)
478         cr, uid, pool, dbname, uri2 = self.get_cr(uri)
479         try:
480             if not dbname:
481                 raise DAV_Error, 409
482             node = self.uri2object(cr, uid, pool, uri2)
483             if not node:
484                 raise DAV_NotFound2(uri2)
485             # TODO: if node is a collection, for some specific set of
486             # clients ( web browsers; available in node context), 
487             # we may return a pseydo-html page with the directory listing.
488             try:
489                 res = node.open_data(cr,'r')
490                 if rrange:
491                     assert isinstance(rrange, (tuple,list))
492                     start, end = map(long, rrange)
493                     if not start:
494                         start = 0
495                     assert start >= 0
496                     if end and end < start:
497                         self.parent.log_error("Invalid range for data: %s-%s" %(start, end))
498                         raise DAV_Error(416, "Invalid range for data")
499                     if end:
500                         if end >= res.size():
501                             raise DAV_Error(416, "Requested data exceeds available size")
502                         length = (end + 1) - start
503                     else:
504                         length = res.size() - start
505                     res = BoundStream2(res, offset=start, length=length)
506                 
507             except TypeError,e:
508                 # for the collections that return this error, the DAV standard
509                 # says we'd better just return 200 OK with empty data
510                 return ''
511             except IndexError,e :
512                 self.parent.log_error("GET IndexError: %s", str(e))
513                 raise DAV_NotFound2(uri2)
514             except Exception,e:
515                 import traceback
516                 self.parent.log_error("GET exception: %s",str(e))
517                 self.parent.log_message("Exc: %s", traceback.format_exc())
518                 raise DAV_Error, 409
519             return res
520         finally:
521             if cr: cr.close()
522
523     @memoize(CACHE_SIZE)
524     def _get_dav_resourcetype(self, uri):
525         """ return type of object """
526         self.parent.log_message('get RT: %s' % uri)
527         cr, uid, pool, dbname, uri2 = self.get_cr(uri)
528         try:
529             if not dbname:
530                 return COLLECTION
531             node = self.uri2object(cr, uid, pool, uri2)
532             if not node:
533                 raise DAV_NotFound2(uri2)
534             try:
535                 return node.get_dav_resourcetype(cr)
536             except NotImplementedError:
537                 if node.type in ('collection','database'):
538                     return ('collection', 'DAV:')
539                 return ''
540         finally:
541             if cr: cr.close()
542
543     def _get_dav_displayname(self,uri):
544         self.parent.log_message('get DN: %s' % uri)
545         cr, uid, pool, dbname, uri2 = self.get_cr(uri)
546         if not dbname:
547             if cr: cr.close()
548             # at root, dbname, just return the last component
549             # of the path.
550             if uri2 and len(uri2) < 2:
551                 return uri2[-1]
552             return ''
553         node = self.uri2object(cr, uid, pool, uri2)
554         if not node:
555             if cr: cr.close()
556             raise DAV_NotFound2(uri2)
557         cr.close()
558         return node.displayname
559
560     @memoize(CACHE_SIZE)
561     def _get_dav_getcontentlength(self, uri):
562         """ return the content length of an object """        
563         self.parent.log_message('get length: %s' % uri)
564         result = 0
565         cr, uid, pool, dbname, uri2 = self.get_cr(uri)        
566         if not dbname:
567             if cr: cr.close()
568             return str(result)
569         node = self.uri2object(cr, uid, pool, uri2)
570         if not node:
571             if cr: cr.close()
572             raise DAV_NotFound2(uri2)
573         result = node.content_length or 0
574         cr.close()
575         return str(result)
576
577     @memoize(CACHE_SIZE)
578     def _get_dav_getetag(self,uri):
579         """ return the ETag of an object """
580         self.parent.log_message('get etag: %s' % uri)
581         result = 0
582         cr, uid, pool, dbname, uri2 = self.get_cr(uri)
583         if not dbname:
584             if cr: cr.close()
585             return '0'
586         node = self.uri2object(cr, uid, pool, uri2)
587         if not node:
588             cr.close()
589             raise DAV_NotFound2(uri2)
590         result = self._try_function(node.get_etag ,(cr,), "etag %s" %uri, cr=cr)
591         cr.close()
592         return str(result)
593
594     @memoize(CACHE_SIZE)
595     def get_lastmodified(self, uri):
596         """ return the last modified date of the object """
597         cr, uid, pool, dbname, uri2 = self.get_cr(uri)
598         if not dbname:
599             return time.time()
600         try:            
601             node = self.uri2object(cr, uid, pool, uri2)
602             if not node:
603                 raise DAV_NotFound2(uri2)
604             return _str2time(node.write_date)
605         finally:
606             if cr: cr.close()
607
608     def _get_dav_getlastmodified(self,uri):
609         """ return the last modified date of a resource
610         """
611         d=self.get_lastmodified(uri)
612         # format it. Note that we explicitly set the day, month names from
613         # an array, so that strftime() doesn't use its own locale-aware
614         # strings.
615         gmt = time.gmtime(d)
616         return time.strftime("%%s, %d %%s %Y %H:%M:%S GMT", gmt ) % \
617                     (day_names[gmt.tm_wday], month_names[gmt.tm_mon])
618
619     @memoize(CACHE_SIZE)
620     def get_creationdate(self, uri):
621         """ return the last modified date of the object """        
622         cr, uid, pool, dbname, uri2 = self.get_cr(uri)
623         if not dbname:
624             raise DAV_Error, 409
625         try:            
626             node = self.uri2object(cr, uid, pool, uri2)
627             if not node:
628                 raise DAV_NotFound2(uri2)
629
630             return _str2time(node.create_date)
631         finally:
632             if cr: cr.close()
633
634     @memoize(CACHE_SIZE)
635     def _get_dav_getcontenttype(self,uri):
636         self.parent.log_message('get contenttype: %s' % uri)
637         cr, uid, pool, dbname, uri2 = self.get_cr(uri)
638         if not dbname:
639             if cr: cr.close()
640             return 'httpd/unix-directory'
641         try:            
642             node = self.uri2object(cr, uid, pool, uri2)
643             if not node:
644                 raise DAV_NotFound2(uri2)
645             result = str(node.mimetype)
646             return result
647             #raise DAV_NotFound, 'Could not find %s' % path
648         finally:
649             if cr: cr.close()    
650     
651     def mkcol(self,uri):
652         """ create a new collection
653             see par. 9.3 of rfc4918
654         """
655         self.parent.log_message('MKCOL: %s' % uri)
656         cr, uid, pool, dbname, uri2 = self.get_cr(uri)
657         if not uri2[-1]:
658             if cr: cr.close()
659             raise DAV_Error(409, "Cannot create nameless collection")
660         if not dbname:
661             if cr: cr.close()
662             raise DAV_Error, 409
663         node = self.uri2object(cr,uid,pool, uri2[:-1])
664         if not node:
665             cr.close()
666             raise DAV_Error(409, "Parent path %s does not exist" % uri2[:-1])
667         nc = node.child(cr, uri2[-1])
668         if nc:
669             cr.close()
670             raise DAV_Error(405, "Path already exists")
671         self._try_function(node.create_child_collection, (cr, uri2[-1]),
672                     "create col %s" % uri2[-1], cr=cr)
673         cr.commit()
674         cr.close()
675         return True
676
677     def put(self, uri, data, content_type=None):
678         """ put the object into the filesystem """
679         self.parent.log_message('Putting %s (%d), %s'%( misc.ustr(uri), data and len(data) or 0, content_type))
680         cr, uid, pool,dbname, uri2 = self.get_cr(uri)
681         if not dbname:
682             if cr: cr.close()
683             raise DAV_Forbidden
684         try:
685             node = self.uri2object(cr, uid, pool, uri2[:])
686         except Exception:
687             node = False
688         
689         objname = misc.ustr(uri2[-1])
690         
691         ret = None
692         if not node:
693             dir_node = self.uri2object(cr, uid, pool, uri2[:-1])
694             if not dir_node:
695                 cr.close()
696                 raise DAV_NotFound('Parent folder not found')
697
698             newchild = self._try_function(dir_node.create_child, (cr, objname, data),
699                     "create %s" % objname, cr=cr)
700             if not newchild:
701                 cr.commit()
702                 cr.close()
703                 raise DAV_Error(400, "Failed to create resource")
704             
705             uparts=urlparse.urlparse(uri)
706             fileloc = '/'.join(newchild.full_path())
707             if isinstance(fileloc, unicode):
708                 fileloc = fileloc.encode('utf-8')
709             # the uri we get is a mangled one, where the davpath has been removed
710             davpath = self.parent.get_davpath()
711             
712             surl = '%s://%s' % (uparts[0], uparts[1])
713             uloc = urllib.quote(fileloc)
714             hurl = False
715             if uri != ('/'+uloc) and uri != (surl + '/' + uloc):
716                 hurl = '%s%s/%s/%s' %(surl, davpath, dbname, uloc)
717             etag = False
718             try:
719                 etag = str(newchild.get_etag(cr))
720             except Exception, e:
721                 self.parent.log_error("Cannot get etag for node: %s" % e)
722             ret = (str(hurl), etag)
723         else:
724             self._try_function(node.set_data, (cr, data), "save %s" % objname, cr=cr)
725             
726         cr.commit()
727         cr.close()
728         return ret
729
730     def rmcol(self,uri):
731         """ delete a collection """
732         cr, uid, pool, dbname, uri2 = self.get_cr(uri)        
733         if not dbname:
734             if cr: cr.close()
735             raise DAV_Error, 409
736
737         node = self.uri2object(cr, uid, pool, uri2)             
738         self._try_function(node.rmcol, (cr,), "rmcol %s" % uri, cr=cr)
739
740         cr.commit()
741         cr.close()
742         return 204
743
744     def rm(self,uri):
745         cr, uid, pool,dbname, uri2 = self.get_cr(uri)
746         if not dbname:        
747             if cr: cr.close()
748             raise DAV_Error, 409
749         node = self.uri2object(cr, uid, pool, uri2)
750         res = self._try_function(node.rm, (cr,), "rm %s"  % uri, cr=cr)
751         if not res:
752             if cr: cr.close()
753             raise OSError(1, 'Operation not permited.')        
754         cr.commit()
755         cr.close()
756         return 204
757
758     ### DELETE handlers (examples)
759     ### (we use the predefined methods in davcmd instead of doing
760     ### a rm directly
761     ###
762
763     def delone(self, uri):
764         """ delete a single resource
765
766         You have to return a result dict of the form
767         uri:error_code
768         or None if everything's ok
769
770         """
771         if uri[-1]=='/':uri=uri[:-1]
772         res=delone(self,uri)
773         # parent='/'.join(uri.split('/')[:-1])
774         return res
775
776     def deltree(self, uri):
777         """ delete a collection
778
779         You have to return a result dict of the form
780         uri:error_code
781         or None if everything's ok
782         """
783         if uri[-1]=='/':uri=uri[:-1]
784         res=deltree(self, uri)
785         # parent='/'.join(uri.split('/')[:-1])
786         return res
787
788
789     ###
790     ### MOVE handlers (examples)
791     ###
792
793     def moveone(self, src, dst, overwrite):
794         """ move one resource with Depth=0
795
796         an alternative implementation would be
797
798         result_code=201
799         if overwrite:
800             result_code=204
801             r=os.system("rm -f '%s'" %dst)
802             if r: return 412
803         r=os.system("mv '%s' '%s'" %(src,dst))
804         if r: return 412
805         return result_code
806
807         (untested!). This would not use the davcmd functions
808         and thus can only detect errors directly on the root node.
809         """
810         res=moveone(self, src, dst, overwrite)
811         return res
812
813     def movetree(self, src, dst, overwrite):
814         """ move a collection with Depth=infinity
815
816         an alternative implementation would be
817
818         result_code=201
819         if overwrite:
820             result_code=204
821             r=os.system("rm -rf '%s'" %dst)
822             if r: return 412
823         r=os.system("mv '%s' '%s'" %(src,dst))
824         if r: return 412
825         return result_code
826
827         (untested!). This would not use the davcmd functions
828         and thus can only detect errors directly on the root node"""
829
830         res=movetree(self, src, dst, overwrite)
831         return res
832
833     ###
834     ### COPY handlers
835     ###
836
837     def copyone(self, src, dst, overwrite):
838         """ copy one resource with Depth=0
839
840         an alternative implementation would be
841
842         result_code=201
843         if overwrite:
844             result_code=204
845             r=os.system("rm -f '%s'" %dst)
846             if r: return 412
847         r=os.system("cp '%s' '%s'" %(src,dst))
848         if r: return 412
849         return result_code
850
851         (untested!). This would not use the davcmd functions
852         and thus can only detect errors directly on the root node.
853         """
854         res=copyone(self, src, dst, overwrite)
855         return res
856
857     def copytree(self, src, dst, overwrite):
858         """ copy a collection with Depth=infinity
859
860         an alternative implementation would be
861
862         result_code=201
863         if overwrite:
864             result_code=204
865             r=os.system("rm -rf '%s'" %dst)
866             if r: return 412
867         r=os.system("cp -r '%s' '%s'" %(src,dst))
868         if r: return 412
869         return result_code
870
871         (untested!). This would not use the davcmd functions
872         and thus can only detect errors directly on the root node"""
873         res=copytree(self, src, dst, overwrite)
874         return res
875
876     ###
877     ### copy methods.
878     ### This methods actually copy something. low-level
879     ### They are called by the davcmd utility functions
880     ### copytree and copyone (not the above!)
881     ### Look in davcmd.py for further details.
882     ###
883
884     def copy(self, src, dst):
885         src=urllib.unquote(src)
886         dst=urllib.unquote(dst)
887         ct = self._get_dav_getcontenttype(src)
888         data = self.get_data(src)
889         self.put(dst, data, ct)
890         return 201
891
892     def copycol(self, src, dst):
893         """ copy a collection.
894
895         As this is not recursive (the davserver recurses itself)
896         we will only create a new directory here. For some more
897         advanced systems we might also have to copy properties from
898         the source to the destination.
899         """
900         return self.mkcol(dst)
901
902
903     def exists(self, uri):
904         """ test if a resource exists """
905         result = False
906         cr, uid, pool,dbname, uri2 = self.get_cr(uri)
907         if not dbname:
908             if cr: cr.close()
909             return True
910         try:
911             node = self.uri2object(cr, uid, pool, uri2)
912             if node:
913                 result = True
914         except Exception:
915             pass
916         cr.close()
917         return result
918
919     def unlock(self, uri, token):
920         """ Unlock a resource from that token 
921         
922         @return True if unlocked, False if no lock existed, Exceptions
923         """
924         cr, uid, pool, dbname, uri2 = self.get_cr(uri)
925         if not dbname:
926             if cr: cr.close()
927             raise DAV_Error, 409
928
929         node = self.uri2object(cr, uid, pool, uri2)
930         try:
931             node_fn = node.dav_unlock
932         except AttributeError:
933             # perhaps the node doesn't support locks
934             cr.close()
935             raise DAV_Error(400, 'No locks for this resource')
936
937         res = self._try_function(node_fn, (cr, token), "unlock %s" % uri, cr=cr)
938         cr.commit()
939         cr.close()
940         return res
941
942     def lock(self, uri, lock_data):
943         """ Lock (may create) resource.
944             Data is a dict, may contain:
945                 depth, token, refresh, lockscope, locktype, owner
946         """
947         cr, uid, pool, dbname, uri2 = self.get_cr(uri)
948         created = False
949         if not dbname:
950             if cr: cr.close()
951             raise DAV_Error, 409
952
953         try:
954             node = self.uri2object(cr, uid, pool, uri2[:])
955         except Exception:
956             node = False
957         
958         objname = misc.ustr(uri2[-1])
959         
960         if not node:
961             dir_node = self.uri2object(cr, uid, pool, uri2[:-1])
962             if not dir_node:
963                 cr.close()
964                 raise DAV_NotFound('Parent folder not found')
965
966             # We create a new node (file) but with empty data=None,
967             # as in RFC4918 p. 9.10.4
968             node = self._try_function(dir_node.create_child, (cr, objname, None),
969                     "create %s" % objname, cr=cr)
970             if not node:
971                 cr.commit()
972                 cr.close()
973                 raise DAV_Error(400, "Failed to create resource")
974             
975             created = True
976
977         try:
978             node_fn = node.dav_lock
979         except AttributeError:
980             # perhaps the node doesn't support locks
981             cr.close()
982             raise DAV_Error(400, 'No locks for this resource')
983
984         # Obtain the lock on the node
985         lres, pid, token = self._try_function(node_fn, (cr, lock_data), "lock %s" % objname, cr=cr)
986
987         if not lres:
988             cr.commit()
989             cr.close()
990             raise DAV_Error(423, "Resource already locked")
991         
992         assert isinstance(lres, list), 'lres: %s' % repr(lres)
993         
994         try:
995             data = mk_lock_response(self, uri, lres)
996             cr.commit()
997         except Exception:
998             cr.close()
999             raise
1000
1001         cr.close()
1002         return created, data, token
1003
1004     @memoize(CACHE_SIZE)
1005     def is_collection(self, uri):
1006         """ test if the given uri is a collection """
1007         cr, uid, pool, dbname, uri2 = self.get_cr(uri)
1008         try:
1009             if not dbname:
1010                 return True
1011             node = self.uri2object(cr,uid,pool, uri2)
1012             if not node:
1013                 raise DAV_NotFound2(uri2)
1014             if node.type in ('collection','database'):
1015                 return True
1016             return False
1017         finally:
1018             if cr: cr.close()
1019
1020 #eof