1 # -*- encoding: utf-8 -*-
2 ##############################################################################
4 # Copyright (c) 2004 TINY SPRL. (http://tiny.be) All Rights Reserved.
5 # Fabien Pinckaers <fp@tiny.Be>
7 # WARNING: This program as such is intended to be used by professional
8 # programmers who take the whole responsability of assessing all potential
9 # consequences resulting from its eventual inadequacies and bugs
10 # End users who are looking for a ready-to-use solution with commercial
11 # garantees and support are strongly adviced to contract a Free Software
14 # This program is Free Software; you can redistribute it and/or
15 # modify it under the terms of the GNU General Public License
16 # as published by the Free Software Foundation; either version 2
17 # of the License, or (at your option) any later version.
19 # This program is distributed in the hope that it will be useful,
20 # but WITHOUT ANY WARRANTY; without even the implied warranty of
21 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
22 # GNU General Public License for more details.
24 # You should have received a copy of the GNU General Public License
25 # along with this program; if not, write to the Free Software
26 # Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
28 ##############################################################################
36 from string import joinfields, split, lower
38 from service import security
43 from DAV.constants import COLLECTION, OBJECT
44 from DAV.errors import *
45 from DAV.iface import *
48 from DAV.davcmd import copyone, copytree, moveone, movetree, delone, deltree
49 from document.nodes import node_res_dir, node_res_obj
50 from cache import memoize
51 from tools import misc
54 #hack for urlparse: add webdav in the net protocols
55 urlparse.uses_netloc.append('webdav')
56 urlparse.uses_netloc.append('webdavs')
58 class tinydav_handler(dav_interface):
60 This class models a Tiny ERP interface for the DAV server
62 PROPS={'DAV:': dav_interface.PROPS['DAV:'], }
64 M_NS={ "DAV:" : dav_interface.M_NS['DAV:'], }
66 def __init__(self, parent, verbose=False):
68 self.directory_id=False
71 self.baseuri = parent.baseuri
73 def get_propnames(self,uri):
75 self.parent.log_message('get propnames: %s' % uri)
76 if uri[-1]=='/':uri=uri[:-1]
77 cr, uid, pool, dbname, uri2 = self.get_cr(uri)
81 node = self.uri2object(cr,uid,pool, uri2)
83 props.update(node.get_dav_props(cr))
87 def get_prop(self,uri,ns,propname):
88 """ return the value of a given property
90 uri -- uri of the object to get the property of
91 ns -- namespace of the property
92 pname -- name of the property
94 if self.M_NS.has_key(ns):
95 return dav_interface.get_prop(self,uri,ns,propname)
97 if uri[-1]=='/':uri=uri[:-1]
98 cr, uid, pool, dbname, uri2 = self.get_cr(uri)
102 node = self.uri2object(cr,uid,pool, uri2)
106 res = node.get_dav_eprop(cr,ns,propname)
111 # def get_db(self,uri):
112 # names=self.uri2local(uri).split('/')
115 # self.db_name=self.uri2local(uri).split('/')[1]
116 # if self.db_name=='':
117 # raise Exception,'Plese specify Database name in folder'
118 # return self.db_name
121 def later_get_db_from_path(self,path):
124 def urijoin(self,*ajoin):
125 """ Return the base URI of this request, or even join it with the
128 return self.baseuri+ '/'.join(ajoin)
132 s = netsvc.ExportService.getService('db')
135 for db_name in result:
136 db = pooler.get_db_only(db_name)
138 cr.execute("select id from ir_module_module where name = 'document' and state='installed' ")
141 self.db_name_list.append(db_name)
143 return self.db_name_list
145 def get_childs(self,uri):
146 """ return the child objects as self.baseuris for the given URI """
147 self.parent.log_message('get childs: %s' % uri)
148 if uri[-1]=='/':uri=uri[:-1]
149 cr, uid, pool, dbname, uri2 = self.get_cr(uri)
152 s = netsvc.ExportService.getService('db')
154 return map(lambda x: self.urijoin(x), self.db_list())
156 node = self.uri2object(cr,uid,pool, uri2[:])
159 raise DAV_NotFound(uri2)
161 fp = node.full_path()
163 self.parent.log_message('childs: @%s' % fp)
167 for d in node.children(cr):
168 self.parent.log_message('child: %s' % d.path)
170 result.append( self.urijoin(dbname,fp,d.path) )
172 result.append( self.urijoin(dbname,d.path) )
176 def uri2local(self, uri):
177 uparts=urlparse.urlparse(uri)
179 if reluri and reluri[-1]=="/":
184 # pos: -1 to get the parent of the uri
186 def get_cr(self, uri):
187 pdb = self.parent.auth_proxy.last_auth
188 reluri = self.uri2local(uri)
190 dbname = reluri.split('/')[2]
194 return None, None, None, False, None
195 if not pdb and dbname:
196 # if dbname was in our uri, we should have authenticated
198 raise Exception("Programming error")
199 assert pdb == dbname, " %s != %s" %(pdb, dbname)
200 user, passwd, dbn2, uid = self.parent.auth_proxy.auth_creds[pdb]
201 db,pool = pooler.get_db_and_pool(dbname)
203 uri2 = reluri.split('/')[3:]
204 return cr, uid, pool, dbname, uri2
206 def uri2object(self, cr,uid, pool,uri):
209 return pool.get('document.directory').get_object(cr, uid, uri)
211 def get_data(self,uri):
212 self.parent.log_message('GET: %s' % uri)
213 if uri[-1]=='/':uri=uri[:-1]
214 cr, uid, pool, dbname, uri2 = self.get_cr(uri)
218 node = self.uri2object(cr,uid,pool, uri2)
220 raise DAV_NotFound(uri2)
222 datas = node.get_data(cr)
225 self.parent.log_error("GET typeError: %s", str(e))
226 self.parent.log_message("Exc: %s",traceback.format_exc())
228 except IndexError,e :
229 self.parent.log_error("GET IndexError: %s", str(e))
230 raise DAV_NotFound(uri2)
233 self.parent.log_error("GET exception: %s",str(e))
234 self.parent.log_message("Exc: %s", traceback.format_exc())
241 def _get_dav_resourcetype(self,uri):
242 """ return type of object """
243 self.parent.log_message('get RT: %s' % uri)
244 if uri[-1]=='/':uri=uri[:-1]
245 cr, uid, pool, dbname, uri2 = self.get_cr(uri)
249 node = self.uri2object(cr,uid,pool, uri2)
251 raise DAV_NotFound(uri2)
252 if node.type in ('collection','database'):
258 def _get_dav_displayname(self,uri):
259 self.parent.log_message('get DN: %s' % uri)
260 if uri[-1]=='/':uri=uri[:-1]
261 cr, uid, pool, dbname, uri2 = self.get_cr(uri)
265 node = self.uri2object(cr,uid,pool, uri2)
268 raise DAV_NotFound(uri2)
270 return node.displayname
273 def _get_dav_getcontentlength(self,uri):
274 """ return the content length of an object """
275 self.parent.log_message('get length: %s' % uri)
276 if uri[-1]=='/':uri=uri[:-1]
278 cr, uid, pool, dbname, uri2 = self.get_cr(uri)
282 node = self.uri2object(cr, uid, pool, uri2)
285 raise DAV_NotFound(uri2)
286 result = node.content_length or 0
291 def _get_dav_getetag(self,uri):
292 """ return the ETag of an object """
293 self.parent.log_message('get etag: %s' % uri)
294 if uri[-1]=='/':uri=uri[:-1]
296 cr, uid, pool, dbname, uri2 = self.get_cr(uri)
300 node = self.uri2object(cr, uid, pool, uri2)
303 raise DAV_NotFound(uri2)
304 result = node.get_etag(cr)
309 def get_lastmodified(self,uri):
310 """ return the last modified date of the object """
311 if uri[-1]=='/':uri=uri[:-1]
313 cr, uid, pool, dbname, uri2 = self.get_cr(uri)
317 node = self.uri2object(cr,uid,pool, uri2)
319 raise DAV_NotFound(uri2)
321 return time.mktime(time.strptime(node.write_date,'%Y-%m-%d %H:%M:%S'))
328 def get_creationdate(self,uri):
329 """ return the last modified date of the object """
331 if uri[-1]=='/':uri=uri[:-1]
332 cr, uid, pool, dbname, uri2 = self.get_cr(uri)
336 node = self.uri2object(cr,uid,pool, uri2)
338 raise DAV_NotFound(uri2)
340 result = time.strptime(node.create_date,'%Y-%m-%d %H:%M:%S')
342 result = time.gmtime()
348 def _get_dav_getcontenttype(self,uri):
349 self.parent.log_message('get contenttype: %s' % uri)
350 if uri[-1]=='/':uri=uri[:-1]
351 cr, uid, pool, dbname, uri2 = self.get_cr(uri)
354 return 'httpd/unix-directory'
355 node = self.uri2object(cr,uid,pool, uri2)
357 raise DAV_NotFound(uri2)
358 result = node.mimetype
360 #raise DAV_NotFound, 'Could not find %s' % path
365 """ create a new collection """
366 self.parent.log_message('MKCOL: %s' % uri)
367 if uri[-1]=='/':uri=uri[:-1]
368 parent='/'.join(uri.split('/')[:-1])
369 if not parent.startswith(self.baseuri):
370 parent=self.baseuri + ''.join(parent[1:])
371 if not uri.startswith(self.baseuri):
372 uri=self.baseuri + ''.join(uri[1:])
375 cr, uid, pool,dbname, uri2 = self.get_cr(uri)
378 node = self.uri2object(cr,uid,pool, uri2[:-1])
380 if isinstance(node, node_res_obj):
381 object2 = node and pool.get(node.context.context['res_model']).browse(cr, uid, node.context.context['res_id']) or False
383 obj = node.context._dirobj.browse(cr, uid, node.dir_id)
384 if obj and (obj.type == 'ressource') and not object2:
385 raise OSError(1, 'Operation not permited.')
390 'ressource_parent_type_id': obj and obj.ressource_type_id.id or False,
391 'ressource_id': object2 and object2.id or False,
394 if (obj and (obj.type in ('directory'))) or not object2:
395 val['parent_id'] = obj and obj.id or False
397 pool.get('document.directory').create(cr, uid, val)
402 def put(self,uri,data,content_type=None):
403 """ put the object into the filesystem """
404 self.parent.log_message('Putting %s (%d), %s'%( misc.ustr(uri), len(data), content_type))
405 parent='/'.join(uri.split('/')[:-1])
406 cr, uid, pool,dbname, uri2 = self.get_cr(uri)
410 node = self.uri2object(cr,uid,pool, uri2[:])
414 ext = objname.find('.') >0 and objname.split('.')[1] or False
417 dir_node = self.uri2object(cr,uid,pool, uri2[:-1])
419 raise DAV_NotFound('Parent folder not found')
421 dir_node.create_child(cr,objname,data)
424 self.parent.log_error("Cannot create %s: %s", objname, str(e))
425 self.parent.log_message("Exc: %s",traceback.format_exc())
429 node.set_data(cr,data)
432 self.parent.log_error("Cannot save %s: %s", objname, str(e))
433 self.parent.log_message("Exc: %s",traceback.format_exc())
441 """ delete a collection """
442 if uri[-1]=='/':uri=uri[:-1]
444 cr, uid, pool, dbname, uri2 = self.get_cr(uri)
447 node = self.uri2object(cr, uid, pool, uri2)
448 object = node.context._dirobj.browse(cr, uid, node.dir_id)
451 raise OSError(2, 'Not such file or directory.')
452 if object._table_name=='document.directory':
453 if node.children(cr):
454 raise OSError(39, 'Directory not empty.')
455 res = pool.get('document.directory').unlink(cr, uid, [object.id])
457 raise OSError(1, 'Operation not permited.')
464 if uri[-1]=='/':uri=uri[:-1]
467 cr, uid, pool,dbname, uri2 = self.get_cr(uri)
471 node = self.uri2object(cr,uid,pool, uri2)
472 object = pool.get('ir.attachment').browse(cr, uid, node.file_id)
473 self.parent.log_message(' rm %s "%s"'%(object._table_name,uri))
475 raise OSError(2, 'Not such file or directory.')
476 if object._table_name == 'ir.attachment':
477 res = pool.get('ir.attachment').unlink(cr, uid, [object.id])
479 raise OSError(1, 'Operation not permited.')
485 ### DELETE handlers (examples)
486 ### (we use the predefined methods in davcmd instead of doing
490 def delone(self,uri):
491 """ delete a single resource
493 You have to return a result dict of the form
495 or None if everything's ok
498 if uri[-1]=='/':uri=uri[:-1]
500 parent='/'.join(uri.split('/')[:-1])
503 def deltree(self,uri):
504 """ delete a collection
506 You have to return a result dict of the form
508 or None if everything's ok
510 if uri[-1]=='/':uri=uri[:-1]
511 res=deltree(self,uri)
512 parent='/'.join(uri.split('/')[:-1])
517 ### MOVE handlers (examples)
520 def moveone(self,src,dst,overwrite):
521 """ move one resource with Depth=0
523 an alternative implementation would be
528 r=os.system("rm -f '%s'" %dst)
530 r=os.system("mv '%s' '%s'" %(src,dst))
534 (untested!). This would not use the davcmd functions
535 and thus can only detect errors directly on the root node.
537 res=moveone(self,src,dst,overwrite)
540 def movetree(self,src,dst,overwrite):
541 """ move a collection with Depth=infinity
543 an alternative implementation would be
548 r=os.system("rm -rf '%s'" %dst)
550 r=os.system("mv '%s' '%s'" %(src,dst))
554 (untested!). This would not use the davcmd functions
555 and thus can only detect errors directly on the root node"""
557 res=movetree(self,src,dst,overwrite)
564 def copyone(self,src,dst,overwrite):
565 """ copy one resource with Depth=0
567 an alternative implementation would be
572 r=os.system("rm -f '%s'" %dst)
574 r=os.system("cp '%s' '%s'" %(src,dst))
578 (untested!). This would not use the davcmd functions
579 and thus can only detect errors directly on the root node.
581 res=copyone(self,src,dst,overwrite)
584 def copytree(self,src,dst,overwrite):
585 """ copy a collection with Depth=infinity
587 an alternative implementation would be
592 r=os.system("rm -rf '%s'" %dst)
594 r=os.system("cp -r '%s' '%s'" %(src,dst))
598 (untested!). This would not use the davcmd functions
599 and thus can only detect errors directly on the root node"""
600 res=copytree(self,src,dst,overwrite)
605 ### This methods actually copy something. low-level
606 ### They are called by the davcmd utility functions
607 ### copytree and copyone (not the above!)
608 ### Look in davcmd.py for further details.
611 def copy(self,src,dst):
612 src=urllib.unquote(src)
613 dst=urllib.unquote(dst)
614 ct = self._get_dav_getcontenttype(src)
615 data = self.get_data(src)
616 self.put(dst,data,ct)
619 def copycol(self,src,dst):
620 """ copy a collection.
622 As this is not recursive (the davserver recurses itself)
623 we will only create a new directory here. For some more
624 advanced systems we might also have to copy properties from
625 the source to the destination.
627 print " copy a collection."
628 return self.mkcol(dst)
631 def exists(self,uri):
632 """ test if a resource exists """
634 cr, uid, pool,dbname, uri2 = self.get_cr(uri)
639 node = self.uri2object(cr,uid,pool, uri2)
648 def is_collection(self,uri):
649 """ test if the given uri is a collection """
650 return self._get_dav_resourcetype(uri)==COLLECTION