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