8c606e4cca0a9b0aae20da49ecbafda325d1d842
[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 uparts[3]:
418                                         turi += ';' + uparts[3]
419                                 if turi.startswith(ul):
420                                     result.append( turi[len(self.parent.davpath):])
421                                 else:
422                                     self.parent.log_error("ignore href %s because it is not under request path %s", turi, ul)
423                             return result
424                             # We don't want to continue with the children found below
425                             # Note the exceptions and that 'finally' will close the
426                             # cursor
427                 for d in node.children(cr, domain):
428                     self.parent.log_message('child: %s' % d.path)
429                     if fp:
430                         result.append( self.urijoin(dbname,fp,d.path) )
431                     else:
432                         result.append( self.urijoin(dbname,d.path) )
433         except DAV_Error:
434             raise
435         except Exception, e:
436             self.parent.log_error("cannot get_children: "+ str(e))
437             raise
438         finally:
439             if cr: cr.close()
440         return result
441
442     def uri2local(self, uri):
443         uparts=urlparse.urlparse(uri)
444         reluri=uparts[2]
445         if uparts[3]:
446             reluri += ';'+uparts[3]
447         if reluri and reluri[-1]=="/":
448             reluri=reluri[:-1]
449         return reluri
450
451     #
452     # pos: -1 to get the parent of the uri
453     #
454     def get_cr(self, uri, allow_last=False):
455         """ Split the uri, grab a cursor for that db
456         """
457         pdb = self.parent.auth_proxy.last_auth
458         dbname, uri2 = self.get_db(uri, rest_ret=True, allow_last=allow_last)
459         uri2 = (uri2 and uri2.split('/')) or []
460         if not dbname:
461             return None, None, None, False, uri2
462         # if dbname was in our uri, we should have authenticated
463         # against that.
464         assert pdb == dbname, " %s != %s" %(pdb, dbname)
465         res = self.parent.auth_proxy.auth_creds.get(dbname, False)
466         if not res:
467             self.parent.auth_proxy.checkRequest(self.parent, uri, dbname)
468             res = self.parent.auth_proxy.auth_creds[dbname]
469         user, passwd, dbn2, uid = res
470         db,pool = pooler.get_db_and_pool(dbname)
471         cr = db.cursor()
472         return cr, uid, pool, dbname, uri2
473
474     def uri2object(self, cr, uid, pool, uri):
475         if not uid:
476             return None
477         context = self.reduce_useragent()
478         return pool.get('document.directory').get_object(cr, uid, uri, context=context)
479
480     def get_data(self,uri, rrange=None):
481         self.parent.log_message('GET: %s' % uri)
482         cr, uid, pool, dbname, uri2 = self.get_cr(uri)
483         try:
484             if not dbname:
485                 raise DAV_Error, 409
486             node = self.uri2object(cr, uid, pool, uri2)
487             if not node:
488                 raise DAV_NotFound2(uri2)
489             # TODO: if node is a collection, for some specific set of
490             # clients ( web browsers; available in node context), 
491             # we may return a pseydo-html page with the directory listing.
492             try:
493                 res = node.open_data(cr,'r')
494                 if rrange:
495                     assert isinstance(rrange, (tuple,list))
496                     start, end = map(long, rrange)
497                     if not start:
498                         start = 0
499                     assert start >= 0
500                     if end and end < start:
501                         self.parent.log_error("Invalid range for data: %s-%s" %(start, end))
502                         raise DAV_Error(416, "Invalid range for data")
503                     if end:
504                         if end >= res.size():
505                             raise DAV_Error(416, "Requested data exceeds available size")
506                         length = (end + 1) - start
507                     else:
508                         length = res.size() - start
509                     res = BoundStream2(res, offset=start, length=length)
510                 
511             except TypeError,e:
512                 # for the collections that return this error, the DAV standard
513                 # says we'd better just return 200 OK with empty data
514                 return ''
515             except IndexError,e :
516                 self.parent.log_error("GET IndexError: %s", str(e))
517                 raise DAV_NotFound2(uri2)
518             except Exception,e:
519                 import traceback
520                 self.parent.log_error("GET exception: %s",str(e))
521                 self.parent.log_message("Exc: %s", traceback.format_exc())
522                 raise DAV_Error, 409
523             return res
524         finally:
525             if cr: cr.close()
526
527     @memoize(CACHE_SIZE)
528     def _get_dav_resourcetype(self, uri):
529         """ return type of object """
530         self.parent.log_message('get RT: %s' % uri)
531         cr, uid, pool, dbname, uri2 = self.get_cr(uri)
532         try:
533             if not dbname:
534                 return COLLECTION
535             node = self.uri2object(cr, uid, pool, uri2)
536             if not node:
537                 raise DAV_NotFound2(uri2)
538             try:
539                 return node.get_dav_resourcetype(cr)
540             except NotImplementedError:
541                 if node.type in ('collection','database'):
542                     return ('collection', 'DAV:')
543                 return ''
544         finally:
545             if cr: cr.close()
546
547     def _get_dav_displayname(self,uri):
548         self.parent.log_message('get DN: %s' % uri)
549         cr, uid, pool, dbname, uri2 = self.get_cr(uri)
550         if not dbname:
551             if cr: cr.close()
552             # at root, dbname, just return the last component
553             # of the path.
554             if uri2 and len(uri2) < 2:
555                 return uri2[-1]
556             return ''
557         node = self.uri2object(cr, uid, pool, uri2)
558         if not node:
559             if cr: cr.close()
560             raise DAV_NotFound2(uri2)
561         cr.close()
562         return node.displayname
563
564     @memoize(CACHE_SIZE)
565     def _get_dav_getcontentlength(self, uri):
566         """ return the content length of an object """        
567         self.parent.log_message('get length: %s' % uri)
568         result = 0
569         cr, uid, pool, dbname, uri2 = self.get_cr(uri)        
570         if not dbname:
571             if cr: cr.close()
572             return str(result)
573         node = self.uri2object(cr, uid, pool, uri2)
574         if not node:
575             if cr: cr.close()
576             raise DAV_NotFound2(uri2)
577         result = node.content_length or 0
578         cr.close()
579         return str(result)
580
581     @memoize(CACHE_SIZE)
582     def _get_dav_getetag(self,uri):
583         """ return the ETag of an object """
584         self.parent.log_message('get etag: %s' % uri)
585         result = 0
586         cr, uid, pool, dbname, uri2 = self.get_cr(uri)
587         if not dbname:
588             if cr: cr.close()
589             return '0'
590         node = self.uri2object(cr, uid, pool, uri2)
591         if not node:
592             cr.close()
593             raise DAV_NotFound2(uri2)
594         result = self._try_function(node.get_etag ,(cr,), "etag %s" %uri, cr=cr)
595         cr.close()
596         return str(result)
597
598     @memoize(CACHE_SIZE)
599     def get_lastmodified(self, uri):
600         """ return the last modified date of the object """
601         cr, uid, pool, dbname, uri2 = self.get_cr(uri)
602         if not dbname:
603             return time.time()
604         try:            
605             node = self.uri2object(cr, uid, pool, uri2)
606             if not node:
607                 raise DAV_NotFound2(uri2)
608             return _str2time(node.write_date)
609         finally:
610             if cr: cr.close()
611
612     def _get_dav_getlastmodified(self,uri):
613         """ return the last modified date of a resource
614         """
615         d=self.get_lastmodified(uri)
616         # format it. Note that we explicitly set the day, month names from
617         # an array, so that strftime() doesn't use its own locale-aware
618         # strings.
619         gmt = time.gmtime(d)
620         return time.strftime("%%s, %d %%s %Y %H:%M:%S GMT", gmt ) % \
621                     (day_names[gmt.tm_wday], month_names[gmt.tm_mon])
622
623     @memoize(CACHE_SIZE)
624     def get_creationdate(self, uri):
625         """ return the last modified date of the object """        
626         cr, uid, pool, dbname, uri2 = self.get_cr(uri)
627         if not dbname:
628             raise DAV_Error, 409
629         try:            
630             node = self.uri2object(cr, uid, pool, uri2)
631             if not node:
632                 raise DAV_NotFound2(uri2)
633
634             return _str2time(node.create_date)
635         finally:
636             if cr: cr.close()
637
638     @memoize(CACHE_SIZE)
639     def _get_dav_getcontenttype(self,uri):
640         self.parent.log_message('get contenttype: %s' % uri)
641         cr, uid, pool, dbname, uri2 = self.get_cr(uri)
642         if not dbname:
643             if cr: cr.close()
644             return 'httpd/unix-directory'
645         try:            
646             node = self.uri2object(cr, uid, pool, uri2)
647             if not node:
648                 raise DAV_NotFound2(uri2)
649             result = str(node.mimetype)
650             return result
651             #raise DAV_NotFound, 'Could not find %s' % path
652         finally:
653             if cr: cr.close()    
654     
655     def mkcol(self,uri):
656         """ create a new collection
657             see par. 9.3 of rfc4918
658         """
659         self.parent.log_message('MKCOL: %s' % uri)
660         cr, uid, pool, dbname, uri2 = self.get_cr(uri)
661         if not uri2[-1]:
662             if cr: cr.close()
663             raise DAV_Error(409, "Cannot create nameless collection")
664         if not dbname:
665             if cr: cr.close()
666             raise DAV_Error, 409
667         node = self.uri2object(cr,uid,pool, uri2[:-1])
668         if not node:
669             cr.close()
670             raise DAV_Error(409, "Parent path %s does not exist" % uri2[:-1])
671         nc = node.child(cr, uri2[-1])
672         if nc:
673             cr.close()
674             raise DAV_Error(405, "Path already exists")
675         self._try_function(node.create_child_collection, (cr, uri2[-1]),
676                     "create col %s" % uri2[-1], cr=cr)
677         cr.commit()
678         cr.close()
679         return True
680
681     def put(self, uri, data, content_type=None):
682         """ put the object into the filesystem """
683         self.parent.log_message('Putting %s (%d), %s'%( misc.ustr(uri), data and len(data) or 0, content_type))
684         cr, uid, pool,dbname, uri2 = self.get_cr(uri)
685         if not dbname:
686             if cr: cr.close()
687             raise DAV_Forbidden
688         try:
689             node = self.uri2object(cr, uid, pool, uri2[:])
690         except Exception:
691             node = False
692         
693         objname = misc.ustr(uri2[-1])
694         
695         ret = None
696         if not node:
697             dir_node = self.uri2object(cr, uid, pool, uri2[:-1])
698             if not dir_node:
699                 cr.close()
700                 raise DAV_NotFound('Parent folder not found')
701
702             newchild = self._try_function(dir_node.create_child, (cr, objname, data),
703                     "create %s" % objname, cr=cr)
704             if not newchild:
705                 cr.commit()
706                 cr.close()
707                 raise DAV_Error(400, "Failed to create resource")
708             
709             uparts=urlparse.urlparse(uri)
710             fileloc = '/'.join(newchild.full_path())
711             if isinstance(fileloc, unicode):
712                 fileloc = fileloc.encode('utf-8')
713             # the uri we get is a mangled one, where the davpath has been removed
714             davpath = self.parent.get_davpath()
715             
716             surl = '%s://%s' % (uparts[0], uparts[1])
717             uloc = urllib.quote(fileloc)
718             hurl = False
719             if uri != ('/'+uloc) and uri != (surl + '/' + uloc):
720                 hurl = '%s%s/%s/%s' %(surl, davpath, dbname, uloc)
721             etag = False
722             try:
723                 etag = str(newchild.get_etag(cr))
724             except Exception, e:
725                 self.parent.log_error("Cannot get etag for node: %s" % e)
726             ret = (str(hurl), etag)
727         else:
728             self._try_function(node.set_data, (cr, data), "save %s" % objname, cr=cr)
729             
730         cr.commit()
731         cr.close()
732         return ret
733
734     def rmcol(self,uri):
735         """ delete a collection """
736         cr, uid, pool, dbname, uri2 = self.get_cr(uri)        
737         if not dbname:
738             if cr: cr.close()
739             raise DAV_Error, 409
740
741         node = self.uri2object(cr, uid, pool, uri2)             
742         self._try_function(node.rmcol, (cr,), "rmcol %s" % uri, cr=cr)
743
744         cr.commit()
745         cr.close()
746         return 204
747
748     def rm(self,uri):
749         cr, uid, pool,dbname, uri2 = self.get_cr(uri)
750         if not dbname:        
751             if cr: cr.close()
752             raise DAV_Error, 409
753         node = self.uri2object(cr, uid, pool, uri2)
754         res = self._try_function(node.rm, (cr,), "rm %s"  % uri, cr=cr)
755         if not res:
756             if cr: cr.close()
757             raise OSError(1, 'Operation not permited.')        
758         cr.commit()
759         cr.close()
760         return 204
761
762     ### DELETE handlers (examples)
763     ### (we use the predefined methods in davcmd instead of doing
764     ### a rm directly
765     ###
766
767     def delone(self, uri):
768         """ delete a single resource
769
770         You have to return a result dict of the form
771         uri:error_code
772         or None if everything's ok
773
774         """
775         if uri[-1]=='/':uri=uri[:-1]
776         res=delone(self,uri)
777         # parent='/'.join(uri.split('/')[:-1])
778         return res
779
780     def deltree(self, uri):
781         """ delete a collection
782
783         You have to return a result dict of the form
784         uri:error_code
785         or None if everything's ok
786         """
787         if uri[-1]=='/':uri=uri[:-1]
788         res=deltree(self, uri)
789         # parent='/'.join(uri.split('/')[:-1])
790         return res
791
792
793     ###
794     ### MOVE handlers (examples)
795     ###
796
797     def moveone(self, src, dst, overwrite):
798         """ move one resource with Depth=0
799
800         an alternative implementation would be
801
802         result_code=201
803         if overwrite:
804             result_code=204
805             r=os.system("rm -f '%s'" %dst)
806             if r: return 412
807         r=os.system("mv '%s' '%s'" %(src,dst))
808         if r: return 412
809         return result_code
810
811         (untested!). This would not use the davcmd functions
812         and thus can only detect errors directly on the root node.
813         """
814         res=moveone(self, src, dst, overwrite)
815         return res
816
817     def movetree(self, src, dst, overwrite):
818         """ move a collection with Depth=infinity
819
820         an alternative implementation would be
821
822         result_code=201
823         if overwrite:
824             result_code=204
825             r=os.system("rm -rf '%s'" %dst)
826             if r: return 412
827         r=os.system("mv '%s' '%s'" %(src,dst))
828         if r: return 412
829         return result_code
830
831         (untested!). This would not use the davcmd functions
832         and thus can only detect errors directly on the root node"""
833
834         res=movetree(self, src, dst, overwrite)
835         return res
836
837     ###
838     ### COPY handlers
839     ###
840
841     def copyone(self, src, dst, overwrite):
842         """ copy one resource with Depth=0
843
844         an alternative implementation would be
845
846         result_code=201
847         if overwrite:
848             result_code=204
849             r=os.system("rm -f '%s'" %dst)
850             if r: return 412
851         r=os.system("cp '%s' '%s'" %(src,dst))
852         if r: return 412
853         return result_code
854
855         (untested!). This would not use the davcmd functions
856         and thus can only detect errors directly on the root node.
857         """
858         res=copyone(self, src, dst, overwrite)
859         return res
860
861     def copytree(self, src, dst, overwrite):
862         """ copy a collection with Depth=infinity
863
864         an alternative implementation would be
865
866         result_code=201
867         if overwrite:
868             result_code=204
869             r=os.system("rm -rf '%s'" %dst)
870             if r: return 412
871         r=os.system("cp -r '%s' '%s'" %(src,dst))
872         if r: return 412
873         return result_code
874
875         (untested!). This would not use the davcmd functions
876         and thus can only detect errors directly on the root node"""
877         res=copytree(self, src, dst, overwrite)
878         return res
879
880     ###
881     ### copy methods.
882     ### This methods actually copy something. low-level
883     ### They are called by the davcmd utility functions
884     ### copytree and copyone (not the above!)
885     ### Look in davcmd.py for further details.
886     ###
887
888     def copy(self, src, dst):
889         src=urllib.unquote(src)
890         dst=urllib.unquote(dst)
891         ct = self._get_dav_getcontenttype(src)
892         data = self.get_data(src)
893         self.put(dst, data, ct)
894         return 201
895
896     def copycol(self, src, dst):
897         """ copy a collection.
898
899         As this is not recursive (the davserver recurses itself)
900         we will only create a new directory here. For some more
901         advanced systems we might also have to copy properties from
902         the source to the destination.
903         """
904         return self.mkcol(dst)
905
906
907     def exists(self, uri):
908         """ test if a resource exists """
909         result = False
910         cr, uid, pool,dbname, uri2 = self.get_cr(uri)
911         if not dbname:
912             if cr: cr.close()
913             return True
914         try:
915             node = self.uri2object(cr, uid, pool, uri2)
916             if node:
917                 result = True
918         except Exception:
919             pass
920         cr.close()
921         return result
922
923     def unlock(self, uri, token):
924         """ Unlock a resource from that token 
925         
926         @return True if unlocked, False if no lock existed, Exceptions
927         """
928         cr, uid, pool, dbname, uri2 = self.get_cr(uri)
929         if not dbname:
930             if cr: cr.close()
931             raise DAV_Error, 409
932
933         node = self.uri2object(cr, uid, pool, uri2)
934         try:
935             node_fn = node.dav_unlock
936         except AttributeError:
937             # perhaps the node doesn't support locks
938             cr.close()
939             raise DAV_Error(400, 'No locks for this resource')
940
941         res = self._try_function(node_fn, (cr, token), "unlock %s" % uri, cr=cr)
942         cr.commit()
943         cr.close()
944         return res
945
946     def lock(self, uri, lock_data):
947         """ Lock (may create) resource.
948             Data is a dict, may contain:
949                 depth, token, refresh, lockscope, locktype, owner
950         """
951         cr, uid, pool, dbname, uri2 = self.get_cr(uri)
952         created = False
953         if not dbname:
954             if cr: cr.close()
955             raise DAV_Error, 409
956
957         try:
958             node = self.uri2object(cr, uid, pool, uri2[:])
959         except Exception:
960             node = False
961         
962         objname = misc.ustr(uri2[-1])
963         
964         if not node:
965             dir_node = self.uri2object(cr, uid, pool, uri2[:-1])
966             if not dir_node:
967                 cr.close()
968                 raise DAV_NotFound('Parent folder not found')
969
970             # We create a new node (file) but with empty data=None,
971             # as in RFC4918 p. 9.10.4
972             node = self._try_function(dir_node.create_child, (cr, objname, None),
973                     "create %s" % objname, cr=cr)
974             if not node:
975                 cr.commit()
976                 cr.close()
977                 raise DAV_Error(400, "Failed to create resource")
978             
979             created = True
980
981         try:
982             node_fn = node.dav_lock
983         except AttributeError:
984             # perhaps the node doesn't support locks
985             cr.close()
986             raise DAV_Error(400, 'No locks for this resource')
987
988         # Obtain the lock on the node
989         lres, pid, token = self._try_function(node_fn, (cr, lock_data), "lock %s" % objname, cr=cr)
990
991         if not lres:
992             cr.commit()
993             cr.close()
994             raise DAV_Error(423, "Resource already locked")
995         
996         assert isinstance(lres, list), 'lres: %s' % repr(lres)
997         
998         try:
999             data = mk_lock_response(self, uri, lres)
1000             cr.commit()
1001         except Exception:
1002             cr.close()
1003             raise
1004
1005         cr.close()
1006         return created, data, token
1007
1008     @memoize(CACHE_SIZE)
1009     def is_collection(self, uri):
1010         """ test if the given uri is a collection """
1011         cr, uid, pool, dbname, uri2 = self.get_cr(uri)
1012         try:
1013             if not dbname:
1014                 return True
1015             node = self.uri2object(cr,uid,pool, uri2)
1016             if not node:
1017                 raise DAV_NotFound2(uri2)
1018             if node.type in ('collection','database'):
1019                 return True
1020             return False
1021         finally:
1022             if cr: cr.close()
1023
1024 #eof