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