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