[IMP] document_webdav : Replace <TAB> with four <White Space>
[odoo/odoo.git] / addons / document_webdav / dav_fs.py
1 # -*- encoding: utf-8 -*-
2 ##############################################################################
3 #
4 # Copyright (c) 2004 TINY SPRL. (http://tiny.be) All Rights Reserved.
5 #                    Fabien Pinckaers <fp@tiny.Be>
6 #
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
12 # Service Company
13 #
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.
18 #
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.
23 #
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.
27 #
28 ##############################################################################
29
30 import pooler
31
32 import base64
33 import sys
34 import os
35 import time
36 from string import joinfields, split, lower
37
38 from service import security
39
40 import netsvc
41 import urlparse
42
43 from DAV.constants import COLLECTION, OBJECT
44 from DAV.errors import *
45 from DAV.iface import *
46 import urllib
47
48 from DAV.davcmd import copyone, copytree, moveone, movetree, delone, deltree
49
50 from cache import memoize
51
52 CACHE_SIZE=20000
53
54 #hack for urlparse: add webdav in the net protocols
55 urlparse.uses_netloc.append('webdav')
56 urlparse.uses_netloc.append('webdavs')
57
58 class tinydav_handler(dav_interface):
59     """
60     This class models a Tiny ERP interface for the DAV server
61     """
62     PROPS={'DAV:': dav_interface.PROPS['DAV:'], }
63
64     M_NS={ "DAV:" : dav_interface.M_NS['DAV:'], }
65
66     def __init__(self,  parent, verbose=False):
67         self.db_name = False
68         self.directory_id=False
69         self.db_name_list=[]
70         self.parent = parent
71         self.baseuri = parent.baseuri
72
73         def get_propnames(self,uri):
74             props = self.PROPS
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)
78             if not dbname:
79                 cr.close()
80                 return props
81             node = self.uri2object(cr,uid,pool, uri2)
82             if node:
83                 props.update(node.get_dav_props(cr))
84             cr.close()
85             return props
86
87         def get_prop(self,uri,ns,propname):
88             """ return the value of a given property
89
90                 uri        -- uri of the object to get the property of
91                 ns        -- namespace of the property
92                 pname        -- name of the property
93              """
94             if self.M_NS.has_key(ns):
95                return dav_interface.get_prop(self,uri,ns,propname)
96
97             if uri[-1]=='/':uri=uri[:-1]
98             cr, uid, pool, dbname, uri2 = self.get_cr(uri)
99             if not dbname:
100                 cr.close()
101                 raise DAV_NotFound
102             node = self.uri2object(cr,uid,pool, uri2)
103             if not node:
104                 cr.close()
105                 raise DAV_NotFound
106             res = node.get_dav_eprop(cr,ns,propname)
107             cr.close()
108             return res
109
110 #
111 #    def get_db(self,uri):
112 #        names=self.uri2local(uri).split('/')
113 #        self.db_name=False
114 #        if len(names) > 1:
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
119 #
120
121     def later_get_db_from_path(self,path):
122         return "aaa"
123
124     def urijoin(self,*ajoin):
125         """ Return the base URI of this request, or even join it with the
126             ajoin path elements
127         """
128         return self.baseuri+ '/'.join(ajoin)
129
130     @memoize(4)
131     def db_list(self):
132         s = netsvc.LocalService('db')
133         result = s.list()
134         self.db_name_list=[]
135         for db_name in result:
136             db = pooler.get_db_only(db_name)
137             cr = db.cursor()
138             cr.execute("select id from ir_module_module where name = 'document' and state='installed' ")
139             res=cr.fetchone()
140             if res and len(res):
141                 self.db_name_list.append(db_name)
142             cr.close()
143         return self.db_name_list
144
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)
150         
151         if not dbname:
152             s = netsvc.LocalService('db')
153             cr.close()
154             return map(lambda x: self.urijoin(x), self.db_list())
155         result = []
156         node = self.uri2object(cr,uid,pool, uri2[:])
157         if not node:
158             cr.close()
159             raise DAV_NotFound(uri2)
160         else:
161             fp = node.full_path()
162             if fp and len(fp):
163                 self.parent.log_message('childs: @%s' % fp)
164                 fp = '/'.join(fp)
165             else:
166                 fp = None
167             for d in node.children(cr):
168                 self.parent.log_message('child: %s' % d.path)
169                 if fp:
170                     result.append( self.urijoin(dbname,fp,d.path) )
171                 else:
172                     result.append( self.urijoin(dbname,d.path) )
173         cr.close()
174         return result
175
176     def uri2local(self, uri):
177         uparts=urlparse.urlparse(uri)
178         reluri=uparts[2]
179         if reluri and reluri[-1]=="/":
180             reluri=reluri[:-1]
181         return reluri
182
183     #
184     # pos: -1 to get the parent of the uri
185     #
186     def get_cr(self, uri):
187         pdb = self.parent.auth_proxy.last_auth
188         reluri = self.uri2local(uri)
189         try:
190             dbname = reluri.split('/')[2]
191         except:
192             dbname = False
193         if not dbname:
194             return None, None, None, False, None
195         if not pdb and dbname:
196             # if dbname was in our uri, we should have authenticated
197             # against that.
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)
202         cr = db.cursor()
203         uri2 = reluri.split('/')[3:]
204         return cr, uid, pool, dbname, uri2
205
206     def uri2object(self, cr,uid, pool,uri):
207         if not uid:
208             return None
209         return pool.get('document.directory').get_object(cr, uid, uri)
210
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)
215         try:
216             if not dbname:
217                 raise DAV_Error, 409
218             node = self.uri2object(cr,uid,pool, uri2)
219             if not node:
220                 raise DAV_NotFound(uri2)
221             try:
222                 datas = node.get_data(cr)
223             except TypeError,e:
224                 import traceback
225                 self.parent.log_error("GET typeError: %s", str(e))
226                 self.parent.log_message("Exc: %s",traceback.format_exc())
227                 raise DAV_Forbidden
228             except IndexError,e :
229                 self.parent.log_error("GET IndexError: %s", str(e))
230                 raise DAV_NotFound(uri2)
231             except Exception,e:
232                 import traceback
233                 self.parent.log_error("GET exception: %s",str(e))
234                 self.parent.log_message("Exc: %s", traceback.format_exc())
235                 raise DAV_Error, 409
236             return datas
237         finally:
238             cr.close()
239
240     @memoize(CACHE_SIZE)
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)
246         try:
247             if not dbname:
248                 return COLLECTION
249             node = self.uri2object(cr,uid,pool, uri2)
250             if not node:
251                 raise DAV_NotFound(uri2)
252             if node.type in ('collection','database'):
253                 return COLLECTION
254             return OBJECT
255         finally:
256             cr.close()
257
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)
262         if not dbname:
263             cr.close()
264             return COLLECTION
265         node = self.uri2object(cr,uid,pool, uri2)
266         if not node:
267             cr.close()
268             raise DAV_NotFound(uri2)
269         cr.close()
270         return node.displayname
271
272     @memoize(CACHE_SIZE)
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]
277         result = 0
278         cr, uid, pool, dbname, uri2 = self.get_cr(uri)
279         if not dbname:
280             cr.close()
281             return '0'
282         node = self.uri2object(cr, uid, pool, uri2)
283         if not node:
284             cr.close()
285             raise DAV_NotFound(uri2)
286         result = node.content_length or 0
287         cr.close()
288         return str(result)
289
290     @memoize(CACHE_SIZE)
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]
295         result = 0
296         cr, uid, pool, dbname, uri2 = self.get_cr(uri)
297         if not dbname:
298             cr.close()
299             return '0'
300         node = self.uri2object(cr, uid, pool, uri2)
301         if not node:
302             cr.close()
303             raise DAV_NotFound(uri2)
304         result = node.get_etag(cr)
305         cr.close()
306         return str(result)
307
308     @memoize(CACHE_SIZE)
309     def get_lastmodified(self,uri):
310         """ return the last modified date of the object """
311         if uri[-1]=='/':uri=uri[:-1]
312         today = time.time()
313         cr, uid, pool, dbname, uri2 = self.get_cr(uri)
314         try:
315             if not dbname:
316                 return today
317             node = self.uri2object(cr,uid,pool, uri2)
318             if not node:
319                 raise DAV_NotFound(uri2)
320             if node.write_date:
321                 return time.mktime(time.strptime(node.write_date,'%Y-%m-%d %H:%M:%S'))
322             else:
323                 return today
324         finally:
325             cr.close()
326
327     @memoize(CACHE_SIZE)
328     def get_creationdate(self,uri):
329         """ return the last modified date of the object """
330
331         if uri[-1]=='/':uri=uri[:-1]
332         cr, uid, pool, dbname, uri2 = self.get_cr(uri)
333         try:
334             if not dbname:
335                 raise DAV_Error, 409
336             node = self.uri2object(cr,uid,pool, uri2)
337             if not node:
338                 raise DAV_NotFound(uri2)
339             if node.create_date:
340                 result = time.strptime(node.create_date,'%Y-%m-%d %H:%M:%S')
341             else:
342                 result = time.gmtime()
343             return result
344         finally:
345             cr.close()
346
347     @memoize(CACHE_SIZE)
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)
352         try:
353             if not dbname:
354                 return 'httpd/unix-directory'
355             node = self.uri2object(cr,uid,pool, uri2)
356             if not node:
357                 raise DAV_NotFound(uri2)
358             result = node.mimetype
359             return result
360             #raise DAV_NotFound, 'Could not find %s' % path
361         finally:
362             cr.close()
363
364     def mkcol(self,uri):
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:])
373
374
375         cr, uid, pool,dbname, uri2 = self.get_cr(uri)
376         if not dbname:
377             raise DAV_Error, 409
378         node = self.uri2object(cr,uid,pool, uri2[:-1])
379         object2=node and node.object2 or False
380         object=node and node.object or False
381
382         objname = uri2[-1]
383         if not object:
384             pool.get('document.directory').create(cr, uid, {
385                 'name': objname,
386                 'parent_id': False,
387                 'ressource_type_id': False,
388                 'ressource_id': False
389             })
390         else:
391             pool.get('document.directory').create(cr, uid, {
392                 'name': objname,
393                 'parent_id': object.id,
394                 'ressource_type_id': object.ressource_type_id.id,
395                 'ressource_id': object2 and object2.id or False
396             })
397
398         cr.commit()
399         cr.close()
400         return True
401
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'%( unicode(uri,'utf8'), len(data), content_type))
405         parent='/'.join(uri.split('/')[:-1])
406         cr, uid, pool,dbname, uri2 = self.get_cr(uri)
407         if not dbname:
408             raise DAV_Forbidden
409         try:
410             node = self.uri2object(cr,uid,pool, uri2[:])
411         except:
412             node = False
413         objname = uri2[-1]
414         ext = objname.find('.') >0 and objname.split('.')[1] or False
415
416         if not node:
417             dir_node = self.uri2object(cr,uid,pool, uri2[:-1])
418             if not dir_node:
419                 raise DAV_NotFound('Parent folder not found')
420             try:
421                 dir_node.create_child(cr,objname,data)
422             except Exception,e:
423                 import traceback
424                 self.parent.log_error("Cannot create %s: %s", objname, str(e))
425                 self.parent.log_message("Exc: %s",traceback.format_exc())
426                 raise DAV_Forbidden
427         else:
428             try:
429                 node.set_data(cr,data)
430             except Exception,e:
431                 import traceback
432                 self.parent.log_error("Cannot save %s: %s", objname, str(e))
433                 self.parent.log_message("Exc: %s",traceback.format_exc())
434                 raise DAV_Forbidden
435             
436         cr.commit()
437
438         return 201
439
440     def rmcol(self,uri):
441         """ delete a collection """
442         if uri[-1]=='/':uri=uri[:-1]
443
444         cr, uid, pool, dbname, uri2 = self.get_cr(uri)
445         if True or not dbname: # *-*
446             raise DAV_Error, 409
447         node = self.uri2object(cr,uid,pool, uri2)
448         object2=node and node.object2 or False
449         object=node and node.object or False
450         if object._table_name=='document.directory':
451             if object.child_ids:
452                 raise DAV_Forbidden # forbidden
453             if object.file_ids:
454                 raise DAV_Forbidden # forbidden
455             res = pool.get('document.directory').unlink(cr, uid, [object.id])
456
457         cr.commit()
458         cr.close()
459         return 204
460
461     def rm(self,uri):
462         if uri[-1]=='/':uri=uri[:-1]
463
464         object=False
465         cr, uid, pool,dbname, uri2 = self.get_cr(uri)
466         #if not dbname:
467         if True:
468             cr.close()
469             raise DAV_Error, 409
470         node = self.uri2object(cr,uid,pool, uri2)
471         object2=node and node.object2 or False
472         object=node and node.object or False
473         if not object:
474             raise DAV_NotFound
475
476         self.parent.log_message(' rm %s "%s"'%(object._table_name,uri))
477         if object._table_name=='ir.attachment':
478             res = pool.get('ir.attachment').unlink(cr, uid, [object.id])
479         else:
480             raise DAV_Forbidden # forbidden
481         parent='/'.join(uri.split('/')[:-1])
482         cr.commit()
483         cr.close()
484         return 204
485
486     ### DELETE handlers (examples)
487     ### (we use the predefined methods in davcmd instead of doing
488     ### a rm directly
489     ###
490
491     def delone(self,uri):
492         """ delete a single resource
493
494         You have to return a result dict of the form
495         uri:error_code
496         or None if everything's ok
497
498         """
499         if uri[-1]=='/':uri=uri[:-1]
500         res=delone(self,uri)
501         parent='/'.join(uri.split('/')[:-1])
502         return res
503
504     def deltree(self,uri):
505         """ delete a collection
506
507         You have to return a result dict of the form
508         uri:error_code
509         or None if everything's ok
510         """
511         if uri[-1]=='/':uri=uri[:-1]
512         res=deltree(self,uri)
513         parent='/'.join(uri.split('/')[:-1])
514         return res
515
516
517     ###
518     ### MOVE handlers (examples)
519     ###
520
521     def moveone(self,src,dst,overwrite):
522         """ move one resource with Depth=0
523
524         an alternative implementation would be
525
526         result_code=201
527         if overwrite:
528             result_code=204
529             r=os.system("rm -f '%s'" %dst)
530             if r: return 412
531         r=os.system("mv '%s' '%s'" %(src,dst))
532         if r: return 412
533         return result_code
534
535         (untested!). This would not use the davcmd functions
536         and thus can only detect errors directly on the root node.
537         """
538         res=moveone(self,src,dst,overwrite)
539         return res
540
541     def movetree(self,src,dst,overwrite):
542         """ move a collection with Depth=infinity
543
544         an alternative implementation would be
545
546         result_code=201
547         if overwrite:
548             result_code=204
549             r=os.system("rm -rf '%s'" %dst)
550             if r: return 412
551         r=os.system("mv '%s' '%s'" %(src,dst))
552         if r: return 412
553         return result_code
554
555         (untested!). This would not use the davcmd functions
556         and thus can only detect errors directly on the root node"""
557
558         res=movetree(self,src,dst,overwrite)
559         return res
560
561     ###
562     ### COPY handlers
563     ###
564
565     def copyone(self,src,dst,overwrite):
566         """ copy one resource with Depth=0
567
568         an alternative implementation would be
569
570         result_code=201
571         if overwrite:
572             result_code=204
573             r=os.system("rm -f '%s'" %dst)
574             if r: return 412
575         r=os.system("cp '%s' '%s'" %(src,dst))
576         if r: return 412
577         return result_code
578
579         (untested!). This would not use the davcmd functions
580         and thus can only detect errors directly on the root node.
581         """
582         res=copyone(self,src,dst,overwrite)
583         return res
584
585     def copytree(self,src,dst,overwrite):
586         """ copy a collection with Depth=infinity
587
588         an alternative implementation would be
589
590         result_code=201
591         if overwrite:
592             result_code=204
593             r=os.system("rm -rf '%s'" %dst)
594             if r: return 412
595         r=os.system("cp -r '%s' '%s'" %(src,dst))
596         if r: return 412
597         return result_code
598
599         (untested!). This would not use the davcmd functions
600         and thus can only detect errors directly on the root node"""
601         res=copytree(self,src,dst,overwrite)
602         return res
603
604     ###
605     ### copy methods.
606     ### This methods actually copy something. low-level
607     ### They are called by the davcmd utility functions
608     ### copytree and copyone (not the above!)
609     ### Look in davcmd.py for further details.
610     ###
611
612     def copy(self,src,dst):
613         src=urllib.unquote(src)
614         dst=urllib.unquote(dst)
615         ct = self._get_dav_getcontenttype(src)
616         data = self.get_data(src)
617         self.put(dst,data,ct)
618         return 201
619
620     def copycol(self,src,dst):
621         """ copy a collection.
622
623         As this is not recursive (the davserver recurses itself)
624         we will only create a new directory here. For some more
625         advanced systems we might also have to copy properties from
626         the source to the destination.
627         """
628         print " copy a collection."
629         return self.mkcol(dst)
630
631
632     def exists(self,uri):
633         """ test if a resource exists """
634         result = False
635         cr, uid, pool,dbname, uri2 = self.get_cr(uri)
636         if not dbname:
637             cr.close()
638             return True
639         try:
640             node = self.uri2object(cr,uid,pool, uri2)
641             if node:
642                 result = True
643         except:
644             pass
645         cr.close()
646         return result
647
648     @memoize(CACHE_SIZE)
649     def is_collection(self,uri):
650         """ test if the given uri is a collection """
651         return self._get_dav_resourcetype(uri)==COLLECTION