odoo/addons/document_webdav/dav_fs.py

631 lines
20 KiB
Python

# -*- coding: utf-8 -*-
##############################################################################
#
# OpenERP, Open Source Management Solution
# Copyright (C) 2004-2010 Tiny SPRL (<http://tiny.be>).
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as
# published by the Free Software Foundation, either version 3 of the
# License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
##############################################################################
import pooler
import base64
import sys
import os
import time
from string import joinfields, split, lower
from service import security
import netsvc
import urlparse
from DAV.constants import COLLECTION, OBJECT
from DAV.errors import *
from DAV.iface import *
import urllib
from DAV.davcmd import copyone, copytree, moveone, movetree, delone, deltree
from document.nodes import node_res_dir, node_res_obj
from cache import memoize
from tools import misc
CACHE_SIZE=20000
#hack for urlparse: add webdav in the net protocols
urlparse.uses_netloc.append('webdav')
urlparse.uses_netloc.append('webdavs')
class openerp_dav_handler(dav_interface):
"""
This class models a OpenERP interface for the DAV server
"""
PROPS={'DAV:': dav_interface.PROPS['DAV:'],}
M_NS={ "DAV:" : dav_interface.M_NS['DAV:'],}
def __init__(self, parent, verbose=False):
self.db_name = False
self.directory_id=False
self.db_name_list=[]
self.parent = parent
self.baseuri = parent.baseuri
self.verbose = verbose
def get_propnames(self,uri):
props = self.PROPS
self.parent.log_message('get propnames: %s' % uri)
if uri[-1]=='/':uri=uri[:-1]
cr, uid, pool, dbname, uri2 = self.get_cr(uri)
if not dbname:
if cr: cr.close()
return props
node = self.uri2object(cr, uid, pool, uri2)
if node:
props.update(node.get_dav_props(cr))
cr.close()
return props
def _get_dav_lockdiscovery(self, uri):
raise DAV_NotFound
def _get_dav_supportedlock(self, uri):
raise DAV_NotFound
def get_prop(self, uri, ns, propname):
""" return the value of a given property
uri -- uri of the object to get the property of
ns -- namespace of the property
pname -- name of the property
"""
if self.M_NS.has_key(ns):
return dav_interface.get_prop(self,uri,ns,propname)
if uri[-1]=='/':uri=uri[:-1]
cr, uid, pool, dbname, uri2 = self.get_cr(uri)
if not dbname:
if cr: cr.close()
raise DAV_NotFound
node = self.uri2object(cr, uid, pool, uri2)
if not node:
cr.close()
raise DAV_NotFound
res = node.get_dav_eprop(cr,ns,propname)
cr.close()
return res
def get_db(self,uri):
names=self.uri2local(uri).split('/')
self.db_name=False
if len(names) > 1:
self.db_name=self.uri2local(uri).split('/')[1]
if self.db_name=='':
raise Exception,'Plese specify Database name in folder'
return self.db_name
def urijoin(self,*ajoin):
""" Return the base URI of this request, or even join it with the
ajoin path elements
"""
return self.baseuri+ '/'.join(ajoin)
@memoize(4)
def db_list(self):
s = netsvc.ExportService.getService('db')
result = s.exp_list()
self.db_name_list=[]
for db_name in result:
db = pooler.get_db_only(db_name)
cr = db.cursor()
cr.execute("select id from ir_module_module where name = 'document' and state='installed' ")
res=cr.fetchone()
if res and len(res):
self.db_name_list.append(db_name)
cr.close()
return self.db_name_list
def get_childs(self,uri, filters=None):
""" return the child objects as self.baseuris for the given URI """
self.parent.log_message('get childs: %s' % uri)
if uri[-1]=='/':uri=uri[:-1]
cr, uid, pool, dbname, uri2 = self.get_cr(uri)
if not dbname:
if cr: cr.close()
res = map(lambda x: self.urijoin(x), self.db_list())
return res
result = []
node = self.uri2object(cr,uid,pool, uri2[:])
if not node:
if cr: cr.close()
raise DAV_NotFound(uri2)
else:
fp = node.full_path()
if fp and len(fp):
self.parent.log_message('childs: @%s' % fp)
fp = '/'.join(fp)
else:
fp = None
domain = None
if filters:
domain = node.get_domain(cr, filters)
for d in node.children(cr, domain):
self.parent.log_message('child: %s' % d.path)
if fp:
result.append( self.urijoin(dbname,fp,d.path) )
else:
result.append( self.urijoin(dbname,d.path) )
if cr: cr.close()
return result
def uri2local(self, uri):
uparts=urlparse.urlparse(uri)
reluri=uparts[2]
if reluri and reluri[-1]=="/":
reluri=reluri[:-1]
return reluri
#
# pos: -1 to get the parent of the uri
#
def get_cr(self, uri):
pdb = self.parent.auth_proxy.last_auth
reluri = self.uri2local(uri)
try:
dbname = reluri.split('/')[1]
except:
dbname = False
if not dbname:
return None, None, None, False, None
if not pdb and dbname:
# if dbname was in our uri, we should have authenticated
# against that.
raise Exception("Programming error")
#assert pdb == dbname, " %s != %s" %(pdb, dbname)
res = self.parent.auth_proxy.auth_creds.get(dbname, False)
if not res:
self.parent.auth_proxy.checkRequest(self.parent, uri, dbname)
res = self.parent.auth_proxy.auth_creds[dbname]
user, passwd, dbn2, uid = res
db,pool = pooler.get_db_and_pool(dbname)
cr = db.cursor()
uri2 = reluri.split('/')[2:]
return cr, uid, pool, dbname, uri2
def uri2object(self, cr,uid, pool,uri):
if not uid:
return None
return pool.get('document.directory').get_object(cr, uid, uri)
def get_data(self,uri):
self.parent.log_message('GET: %s' % uri)
if uri[-1]=='/':uri=uri[:-1]
cr, uid, pool, dbname, uri2 = self.get_cr(uri)
try:
if not dbname:
raise DAV_Error, 409
node = self.uri2object(cr,uid,pool, uri2)
if not node:
raise DAV_NotFound(uri2)
try:
datas = node.get_data(cr)
except TypeError,e:
import traceback
self.parent.log_error("GET typeError: %s", str(e))
self.parent.log_message("Exc: %s",traceback.format_exc())
raise DAV_Forbidden
except IndexError,e :
self.parent.log_error("GET IndexError: %s", str(e))
raise DAV_NotFound(uri2)
except Exception,e:
import traceback
self.parent.log_error("GET exception: %s",str(e))
self.parent.log_message("Exc: %s", traceback.format_exc())
raise DAV_Error, 409
return datas
finally:
if cr: cr.close()
@memoize(CACHE_SIZE)
def _get_dav_resourcetype(self,uri):
""" return type of object """
self.parent.log_message('get RT: %s' % uri)
if uri[-1]=='/':uri=uri[:-1]
cr, uid, pool, dbname, uri2 = self.get_cr(uri)
try:
if not dbname:
return COLLECTION
node = self.uri2object(cr,uid,pool, uri2)
if not node:
raise DAV_NotFound(uri2)
if node.type in ('collection','database'):
return COLLECTION
return OBJECT
finally:
if cr: cr.close()
def _get_dav_displayname(self,uri):
self.parent.log_message('get DN: %s' % uri)
if uri[-1]=='/':uri=uri[:-1]
cr, uid, pool, dbname, uri2 = self.get_cr(uri)
if not dbname:
cr.close()
return COLLECTION
node = self.uri2object(cr,uid,pool, uri2)
if not node:
cr.close()
raise DAV_NotFound(uri2)
cr.close()
return node.displayname
@memoize(CACHE_SIZE)
def _get_dav_getcontentlength(self,uri):
""" return the content length of an object """
self.parent.log_message('get length: %s' % uri)
if uri[-1]=='/':uri=uri[:-1]
result = 0
cr, uid, pool, dbname, uri2 = self.get_cr(uri)
if not dbname:
if cr: cr.close()
return str(result)
node = self.uri2object(cr, uid, pool, uri2)
if not node:
cr.close()
raise DAV_NotFound(uri2)
result = node.content_length or 0
cr.close()
return str(result)
@memoize(CACHE_SIZE)
def _get_dav_getetag(self,uri):
""" return the ETag of an object """
self.parent.log_message('get etag: %s' % uri)
if uri[-1]=='/':uri=uri[:-1]
result = 0
cr, uid, pool, dbname, uri2 = self.get_cr(uri)
if not dbname:
cr.close()
return '0'
node = self.uri2object(cr, uid, pool, uri2)
if not node:
cr.close()
raise DAV_NotFound(uri2)
result = node.get_etag(cr)
cr.close()
return str(result)
@memoize(CACHE_SIZE)
def get_lastmodified(self,uri):
""" return the last modified date of the object """
if uri[-1]=='/':uri=uri[:-1]
today = time.time()
cr, uid, pool, dbname, uri2 = self.get_cr(uri)
if not dbname:
return today
try:
node = self.uri2object(cr,uid,pool, uri2)
if not node:
raise DAV_NotFound(uri2)
if node.write_date:
return time.mktime(time.strptime(node.write_date,'%Y-%m-%d %H:%M:%S'))
else:
return today
finally:
if cr: cr.close()
@memoize(CACHE_SIZE)
def get_creationdate(self,uri):
""" return the last modified date of the object """
if uri[-1]=='/':uri=uri[:-1]
cr, uid, pool, dbname, uri2 = self.get_cr(uri)
if not dbname:
raise DAV_Error, 409
try:
node = self.uri2object(cr,uid,pool, uri2)
if not node:
raise DAV_NotFound(uri2)
if node.create_date:
result = time.mktime(time.strptime(node.create_date,'%Y-%m-%d %H:%M:%S'))
else:
result = time.time()
return result
finally:
if cr: cr.close()
@memoize(CACHE_SIZE)
def _get_dav_getcontenttype(self,uri):
self.parent.log_message('get contenttype: %s' % uri)
if uri[-1]=='/':uri=uri[:-1]
cr, uid, pool, dbname, uri2 = self.get_cr(uri)
if not dbname:
return 'httpd/unix-directory'
try:
node = self.uri2object(cr,uid,pool, uri2)
if not node:
raise DAV_NotFound(uri2)
result = node.mimetype
return result
#raise DAV_NotFound, 'Could not find %s' % path
finally:
if cr: cr.close()
def mkcol(self,uri):
""" create a new collection """
self.parent.log_message('MKCOL: %s' % uri)
uri = self.uri2local(uri)[1:]
if uri[-1]=='/':uri=uri[:-1]
parent = '/'.join(uri.split('/')[:-1])
parent = self.baseuri + parent
uri = self.baseuri + uri
cr, uid, pool,dbname, uri2 = self.get_cr(uri)
if not dbname:
raise DAV_Error, 409
node = self.uri2object(cr,uid,pool, uri2[:-1])
if node:
node.create_child_collection(cr, uri2[-1])
cr.commit()
cr.close()
return True
def put(self,uri,data,content_type=None):
""" put the object into the filesystem """
self.parent.log_message('Putting %s (%d), %s'%( misc.ustr(uri), data and len(data) or 0, content_type))
parent='/'.join(uri.split('/')[:-1])
cr, uid, pool,dbname, uri2 = self.get_cr(uri)
if not dbname:
raise DAV_Forbidden
try:
node = self.uri2object(cr,uid,pool, uri2[:])
except:
node = False
objname = uri2[-1]
ext = objname.find('.') >0 and objname.split('.')[1] or False
if not node:
dir_node = self.uri2object(cr,uid,pool, uri2[:-1])
if not dir_node:
raise DAV_NotFound('Parent folder not found')
try:
dir_node.create_child(cr,objname,data)
except Exception,e:
import traceback
self.parent.log_error("Cannot create %s: %s", objname, str(e))
self.parent.log_message("Exc: %s",traceback.format_exc())
raise DAV_Forbidden
else:
try:
node.set_data(cr,data)
except Exception,e:
import traceback
self.parent.log_error("Cannot save %s: %s", objname, str(e))
self.parent.log_message("Exc: %s",traceback.format_exc())
raise DAV_Forbidden
cr.commit()
cr.close()
return 201
def rmcol(self,uri):
""" delete a collection """
if uri[-1]=='/':uri=uri[:-1]
cr, uid, pool, dbname, uri2 = self.get_cr(uri)
if not dbname: # *-*
raise DAV_Error, 409
node = self.uri2object(cr, uid, pool, uri2)
object = node.context._dirobj.browse(cr, uid, node.dir_id)
if not object:
raise OSError(2, 'Not such file or directory.')
if object._table_name=='document.directory':
if node.children(cr):
raise OSError(39, 'Directory not empty.')
res = pool.get('document.directory').unlink(cr, uid, [object.id])
else:
raise OSError(1, 'Operation not permited.')
cr.commit()
cr.close()
return 204
def rm(self,uri):
if uri[-1]=='/':uri=uri[:-1]
object=False
cr, uid, pool,dbname, uri2 = self.get_cr(uri)
if not dbname:
cr.close()
raise DAV_Error, 409
node = self.uri2object(cr,uid,pool, uri2)
object = pool.get('ir.attachment').browse(cr, uid, node.file_id)
self.parent.log_message(' rm %s "%s"'%(object._table_name,uri))
if not object:
raise OSError(2, 'Not such file or directory.')
if object._table_name == 'ir.attachment':
res = pool.get('ir.attachment').unlink(cr, uid, [object.id])
else:
raise OSError(1, 'Operation not permited.')
cr.commit()
cr.close()
return 204
### DELETE handlers (examples)
### (we use the predefined methods in davcmd instead of doing
### a rm directly
###
def delone(self,uri):
""" delete a single resource
You have to return a result dict of the form
uri:error_code
or None if everything's ok
"""
if uri[-1]=='/':uri=uri[:-1]
res=delone(self,uri)
parent='/'.join(uri.split('/')[:-1])
return res
def deltree(self,uri):
""" delete a collection
You have to return a result dict of the form
uri:error_code
or None if everything's ok
"""
if uri[-1]=='/':uri=uri[:-1]
res=deltree(self,uri)
parent='/'.join(uri.split('/')[:-1])
return res
###
### MOVE handlers (examples)
###
def moveone(self,src,dst,overwrite):
""" move one resource with Depth=0
an alternative implementation would be
result_code=201
if overwrite:
result_code=204
r=os.system("rm -f '%s'" %dst)
if r: return 412
r=os.system("mv '%s' '%s'" %(src,dst))
if r: return 412
return result_code
(untested!). This would not use the davcmd functions
and thus can only detect errors directly on the root node.
"""
res=moveone(self,src,dst,overwrite)
return res
def movetree(self,src,dst,overwrite):
""" move a collection with Depth=infinity
an alternative implementation would be
result_code=201
if overwrite:
result_code=204
r=os.system("rm -rf '%s'" %dst)
if r: return 412
r=os.system("mv '%s' '%s'" %(src,dst))
if r: return 412
return result_code
(untested!). This would not use the davcmd functions
and thus can only detect errors directly on the root node"""
res=movetree(self,src,dst,overwrite)
return res
###
### COPY handlers
###
def copyone(self,src,dst,overwrite):
""" copy one resource with Depth=0
an alternative implementation would be
result_code=201
if overwrite:
result_code=204
r=os.system("rm -f '%s'" %dst)
if r: return 412
r=os.system("cp '%s' '%s'" %(src,dst))
if r: return 412
return result_code
(untested!). This would not use the davcmd functions
and thus can only detect errors directly on the root node.
"""
res=copyone(self,src,dst,overwrite)
return res
def copytree(self,src,dst,overwrite):
""" copy a collection with Depth=infinity
an alternative implementation would be
result_code=201
if overwrite:
result_code=204
r=os.system("rm -rf '%s'" %dst)
if r: return 412
r=os.system("cp -r '%s' '%s'" %(src,dst))
if r: return 412
return result_code
(untested!). This would not use the davcmd functions
and thus can only detect errors directly on the root node"""
res=copytree(self,src,dst,overwrite)
return res
###
### copy methods.
### This methods actually copy something. low-level
### They are called by the davcmd utility functions
### copytree and copyone (not the above!)
### Look in davcmd.py for further details.
###
def copy(self,src,dst):
src=urllib.unquote(src)
dst=urllib.unquote(dst)
ct = self._get_dav_getcontenttype(src)
data = self.get_data(src)
self.put(dst,data,ct)
return 201
def copycol(self,src,dst):
""" copy a collection.
As this is not recursive (the davserver recurses itself)
we will only create a new directory here. For some more
advanced systems we might also have to copy properties from
the source to the destination.
"""
print " copy a collection."
return self.mkcol(dst)
def exists(self,uri):
""" test if a resource exists """
result = False
cr, uid, pool,dbname, uri2 = self.get_cr(uri)
if not dbname:
if cr: cr.close()
return True
try:
node = self.uri2object(cr,uid,pool, uri2)
if node:
result = True
except:
pass
cr.close()
return result
@memoize(CACHE_SIZE)
def is_collection(self,uri):
""" test if the given uri is a collection """
return self._get_dav_resourcetype(uri)==COLLECTION