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