From d5a68a7b692a6fa568c5fcebc580109afb5bb6d4 Mon Sep 17 00:00:00 2001
From: p_christeas <>
Date: Wed, 2 Dec 2009 11:06:57 +0530
Subject: [PATCH 01/19] [IMP]document : get improvement of document from
xrg-addons branch who made nice improvement in document
bzr revid: hmo@tinyerp.com-20091202053657-4oflezyhbolu3xvo
---
addons/document/__init__.py | 7 +
addons/document/__terp__.py | 8 +-
addons/document/content_index.py | 228 +++-
addons/document/directory_content.py | 113 ++
addons/document/directory_report.py | 64 +
addons/document/doc_conf_wizard.py | 163 +++
addons/document/document.py | 776 +----------
addons/document/document_data.xml | 104 +-
addons/document/document_demo.xml | 84 --
addons/document/document_directory.py | 312 +++++
addons/document/document_storage.py | 281 ++++
addons/document/document_view.xml | 178 +--
addons/document/ftpserver/__init__.py | 5 +-
addons/document/ftpserver/abstracted_fs.py | 37 +-
addons/document/ftpserver/authorizer.py | 26 +-
addons/document/ftpserver/ftpserver.py | 1375 +++++++++-----------
addons/document/nodes.py | 776 +++++++++++
addons/document/odt2txt.py | 1 +
addons/document/std_index.py | 111 ++
addons/document/test_cindex.py | 46 +
20 files changed, 2909 insertions(+), 1786 deletions(-)
create mode 100644 addons/document/directory_content.py
create mode 100644 addons/document/directory_report.py
create mode 100644 addons/document/doc_conf_wizard.py
create mode 100644 addons/document/document_directory.py
create mode 100644 addons/document/document_storage.py
create mode 100644 addons/document/nodes.py
create mode 100644 addons/document/std_index.py
create mode 100755 addons/document/test_cindex.py
diff --git a/addons/document/__init__.py b/addons/document/__init__.py
index 5c97428f05d..a9810a8b7b8 100644
--- a/addons/document/__init__.py
+++ b/addons/document/__init__.py
@@ -19,6 +19,13 @@
#
##############################################################################
+import content_index
+import std_index
+import document_storage
+import document_directory
+import directory_content
+import directory_report
+import doc_conf_wizard
import document
import ftpserver
diff --git a/addons/document/__terp__.py b/addons/document/__terp__.py
index d2246d270bf..be9eb6a9d3f 100644
--- a/addons/document/__terp__.py
+++ b/addons/document/__terp__.py
@@ -22,7 +22,7 @@
{
'name': 'Integrated Document Management System',
- 'version': '1.1',
+ 'version': '1.99',
'category': 'Generic Modules/Others',
'description': """This is a complete document management system:
* FTP Interface
@@ -32,17 +32,17 @@
'author': 'Tiny',
'website': 'http://www.openerp.com',
'depends': ['base', 'process'],
- 'init_xml': ['document_data.xml', 'document_demo.xml'],
+ 'init_xml': [],
'update_xml': [
'document_view.xml',
'document_data.xml',
'security/document_security.xml',
'security/ir.model.access.csv'
],
- 'demo_xml': [],
+ 'demo_xml': [ 'document_demo.xml',],
'installable': True,
'active': False,
- 'certificate': '0070515416461',
+ 'certificate': None,
}
# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:
diff --git a/addons/document/content_index.py b/addons/document/content_index.py
index 6c0f083b8ac..a063251c698 100644
--- a/addons/document/content_index.py
+++ b/addons/document/content_index.py
@@ -18,53 +18,195 @@
# along with this program. If not, see .
#
##############################################################################
-
-import time
import os
-import StringIO
-import odt2txt
import tempfile
-#
-# This should be the indexer
-#
-def _to_unicode(s):
- try:
- return s.decode('utf-8')
- except UnicodeError:
- try:
- return s.decode('latin')
- except UnicodeError:
- try:
- return s.encode('ascii')
- except UnicodeError:
- return s
+# A quick hack: if netsvc is not there, emulate it. Thus, work offline, too
+try:
+ import netsvc
+ def log(lvl,msg):
+ netsvc.Logger().notifyChannel("index",lvl,msg)
+except:
+ class netsvc:
+ LOG_NOTSET = 'notset'
+ LOG_DEBUG_RPC = 'debug_rpc'
+ LOG_DEBUG = 'debug'
+ LOG_DEBUG2 = 'debug2'
+ LOG_INFO = 'info'
+ LOG_WARNING = 'warn'
+ LOG_ERROR = 'error'
+ LOG_CRITICAL = 'critical'
+
+ def log(lvl,msg):
+ print msg
-def content_index(content, filename=None, content_type=None):
- fname,ext = os.path.splitext(filename)
- result = ''
- if ext in ('.doc'): #or content_type ?
- (stdin,stdout) = os.popen2('antiword -', 'b')
- stdin.write(content)
- stdin.close()
- result = _to_unicode(stdout.read())
- elif ext == '.pdf':
- file_descriptor, file_name = tempfile.mkstemp(suffix=ext)
- os.write(file_descriptor, content)
- os.close(file_descriptor)
- fp = os.popen('pdftotext -enc UTF-8 -nopgbrk '+file_name+' -', 'r')
- result = fp.read()
- fp.close()
- elif ext in ('.xls','.ods','.odt','.odp'):
- s = StringIO.StringIO(content)
- o = odt2txt.OpenDocumentTextFile(s)
- result = _to_unicode(o.toString())
- s.close()
- elif ext in ('.txt','.py','.patch','.html','.csv','.xml'):
- result = content
- #else:
- # result = content
- return result
+class NhException(Exception):
+ pass
+
+from subprocess import Popen, PIPE
+
+class indexer():
+ """ An indexer knows how to parse the content of some file.
+
+ Typically, one indexer should be instantiated per file
+ type.
+ Override this class to add more functionality. Note that
+ you should only override the Content or the File methods
+ that give an optimal result. """
+
+ def _getMimeTypes(self):
+ """ Return supported mimetypes """
+ return []
+
+ def _getExtensions(self):
+ return []
+
+ def _getDefMime(self,ext):
+ """ Return a mimetype for this document type, ideally the
+ closest to the extension ext. """
+ mts = self._getMimeTypes();
+ if len (mts):
+ return mts[0]
+ return None
+
+ def indexContent(self,content,filename=None, realfile = None):
+ """ Use either content or the real file, to index.
+ Some parsers will work better with the actual
+ content, others parse a file easier. Try the
+ optimal.
+ """
+ res = ''
+ try:
+ if content != None:
+ return self._doIndexContent(content)
+ except NhException:
+ pass
+
+ if realfile != None:
+ try:
+ return self._doIndexFile(realfile)
+ except NhException:
+ pass
+
+ fp = open(realfile,'rb')
+ content2 = fp.read()
+ fp.close()
+
+ # The not-handled exception may be raised here
+ return self._doIndexContent(content2)
+
+
+ # last try, with a tmp file
+ if content:
+ try:
+ fname,ext = filename and os.path.splitext(filename) or ('','')
+ fd, rfname = tempfile.mkstemp(suffix=ext)
+ os.write(fd, content)
+ os.close(fd)
+ res = self._doIndexFile(rfname)
+ os.unlink(rfname)
+ return res
+ except NhException:
+ pass
+
+ raise NhException('No appropriate method to index file')
+
+ def _doIndexContent(self,content):
+ raise NhException("Content not handled here")
+
+ def _doIndexFile(self,fpath):
+ raise NhException("Content not handled here")
+
+
+
+def mime_match(mime, mdict):
+ if mdict.has_key(mime):
+ return (mime, mdict[mime])
+ if '/' in mime:
+ mpat = mime.split('/')[0]+'/*'
+ if mdict.has_key(mpat):
+ return (mime, mdict[mpat])
+
+ return (None, None)
+
+class contentIndex() :
+ def __init__(self):
+ self.mimes = {}
+ self.exts = {}
+
+ def register(self, obj):
+ f = False
+ for mime in obj._getMimeTypes():
+ self.mimes[mime] = obj
+ f = True
+
+ for ext in obj._getExtensions():
+ self.exts[ext] = obj
+ f = True
+
+ if f:
+ log(netsvc.LOG_DEBUG, "Register content indexer: %r" % obj)
+ if not f:
+ raise Exception("Your indexer should at least suport a mimetype or extension")
+
+ def doIndex(self,content, filename=None, content_type=None, realfname = None, debug=False):
+ fobj = None
+ fname = None
+ mime = None
+ if content_type and self.mimes.has_key(content_type):
+ mime = content_type
+ fobj = self.mimes[content_type]
+ elif filename:
+ bname,ext = os.path.splitext(filename)
+ if self.exts.has_key(ext):
+ fobj = self.exts[ext]
+ mime = fobj._getDefMime(ext)
+
+ if content_type and not fobj:
+ mime,fobj = mime_match(content_type, self.mimes)
+
+ if not fobj:
+ try:
+ if realfname :
+ fname = realfname
+ else:
+ bname,ext = os.path.splitext(filename)
+ fd, fname = tempfile.mkstemp(suffix=ext)
+ os.write(fd, content)
+ os.close(fd)
+
+ fp = Popen(['file','-b','--mime-type',fname], shell=False, stdout=PIPE).stdout
+ result = fp.read()
+ fp.close()
+ mime2 = result.strip()
+ log(netsvc.LOG_DEBUG,"File gave us: %s" % mime2)
+ # Note that the temporary file still exists now.
+ mime,fobj = mime_match(mime2, self.mimes)
+ if not mime:
+ mime = mime2
+ except Exception, e:
+ log(netsvc.LOG_WARNING,"Cannot determine mime type: %s" % str(e))
+
+ try:
+ if fobj:
+ res = (mime, fobj.indexContent(content,filename,fname or realfname) )
+ else:
+ log(netsvc.LOG_DEBUG,"Have no object, return (%s, None)" % mime)
+ res = (mime, None )
+ except Exception, e:
+ log(netsvc.LOG_WARNING,"Could not index file, %s" % e)
+ res = None
+
+ # If we created a tmp file, unlink it now
+ if not realfname and fname:
+ try:
+ os.unlink(fname)
+ except Exception, e:
+ log(netsvc.LOG_WARNING,"Could not unlink %s, %s" %(fname, e))
+
+ return res
+
+cntIndex = contentIndex()
# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:
diff --git a/addons/document/directory_content.py b/addons/document/directory_content.py
new file mode 100644
index 00000000000..4ccad653711
--- /dev/null
+++ b/addons/document/directory_content.py
@@ -0,0 +1,113 @@
+# -*- coding: utf-8 -*-
+##############################################################################
+#
+# OpenERP, Open Source Management Solution
+# Copyright (C) 2004-2009 Tiny SPRL ().
+#
+# 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 .
+#
+##############################################################################
+import base64
+
+from osv import osv, fields
+from osv.orm import except_orm
+import urlparse
+
+import os
+import nodes
+
+class document_directory_content_type(osv.osv):
+ _name = 'document.directory.content.type'
+ _description = 'Directory Content Type'
+ _columns = {
+ 'name': fields.char('Content Type', size=64, required=True),
+ 'code': fields.char('Extension', size=4),
+ 'active': fields.boolean('Active'),
+ 'mimetype': fields.char('Mime Type',size=32)
+ }
+ _defaults = {
+ 'active': lambda *args: 1
+ }
+document_directory_content_type()
+
+class document_directory_content(osv.osv):
+ _name = 'document.directory.content'
+ _description = 'Directory Content'
+ _order = "sequence"
+ def _extension_get(self, cr, uid, context={}):
+ cr.execute('select code,name from document_directory_content_type where active')
+ res = cr.fetchall()
+ return res
+ _columns = {
+ 'name': fields.char('Content Name', size=64, required=True),
+ 'sequence': fields.integer('Sequence', size=16),
+ 'prefix': fields.char('Prefix', size=16),
+ 'suffix': fields.char('Suffix', size=16),
+ 'report_id': fields.many2one('ir.actions.report.xml', 'Report'),
+ 'extension': fields.selection(_extension_get, 'Document Type', required=True, size=4),
+ 'include_name': fields.boolean('Include Record Name', help="Check this field if you want that the name of the file start by the record name."),
+ 'directory_id': fields.many2one('document.directory', 'Directory'),
+ }
+ _defaults = {
+ 'extension': lambda *args: '.pdf',
+ 'sequence': lambda *args: 1,
+ 'include_name': lambda *args: 1,
+ }
+
+ def _file_get(self,cr,node, nodename, content, context=None):
+ """ return the nodes of a parent having a content
+ The return value MUST be false or a list of node_class objects.
+ """
+
+ # TODO: respect the context!
+ if content.include_name and not node.object2:
+ return False
+
+ res2 = []
+ tname = ''
+ if content.include_name:
+ content_name = node.object2.name
+ obj = pool.get(node.object.ressource_type_id.model)
+ name_for = obj._name.split('.')[-1]
+ if content_name and content_name.find(name_for) == 0 :
+ content_name = content_name.replace(name_for,'')
+ tname = (content.prefix or '') + content_name + (content.suffix or '') + (content.extension or '')
+ else:
+ tname = (content.prefix or '') + (content.suffix or '') + (content.extension or '')
+ if tname.find('/'):
+ tname=tname.replace('/', '_')
+ if not nodename:
+ n = nodes.node_content(tname, node, node.context,content)
+ res2.append( n)
+ else:
+ if nodename == tname:
+ n = nodes.node_content(tname, node, node.context,content)
+ n.fill_fields(cr)
+ res2.append(n)
+ return res2
+
+ def process_write(self, cr, uid, node, data):
+ if node.extension != '.pdf':
+ raise Exception("Invalid content: %s" % node.extension)
+ return True
+ def process_read(self, cr, uid, node):
+ if node.extension != '.pdf':
+ raise Exception("Invalid content: %s" % node.extension)
+ report = self.pool.get('ir.actions.report.xml').browse(cr, uid, node.content.report_id.id)
+ srv = netsvc.LocalService('report.'+report.report_name)
+ pdf,pdftype = srv.create(cr, uid, [node.object.id], {}, {})
+ s = StringIO.StringIO(pdf)
+ s.name = node
+ return s
+document_directory_content()
diff --git a/addons/document/directory_report.py b/addons/document/directory_report.py
new file mode 100644
index 00000000000..b5a80adc7ec
--- /dev/null
+++ b/addons/document/directory_report.py
@@ -0,0 +1,64 @@
+# -*- encoding: utf-8 -*-
+##############################################################################
+#
+# OpenERP, Open Source Management Solution
+# Copyright (C) 2004-2009 Tiny SPRL (). All Rights Reserved
+# $Id$
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU 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 General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see .
+#
+##############################################################################
+
+import base64
+
+from osv import osv, fields
+from osv.orm import except_orm
+import urlparse
+
+import os
+
+class ir_action_report_xml(osv.osv):
+ _name="ir.actions.report.xml"
+ _inherit ="ir.actions.report.xml"
+
+ def _model_get(self, cr, uid, ids, name, arg, context):
+ res = {}
+ model_pool = self.pool.get('ir.model')
+ for data in self.read(cr,uid,ids,['model']):
+ model = data.get('model',False)
+ if model:
+ model_id =model_pool.search(cr,uid,[('model','=',model)])
+ if model_id:
+ res[data.get('id')] = model_id[0]
+ else:
+ res[data.get('id')] = False
+ return res
+
+ def _model_search(self, cr, uid, obj, name, args):
+ if not len(args):
+ return []
+ model_id= args[0][2]
+ if not model_id:
+ return []
+ model = self.pool.get('ir.model').read(cr,uid,[model_id])[0]['model']
+ report_id = self.search(cr,uid,[('model','=',model)])
+ if not report_id:
+ return [('id','=','0')]
+ return [('id','in',report_id)]
+
+ _columns={
+ 'model_id' : fields.function(_model_get,fnct_search=_model_search,method=True,string='Model Id'),
+ }
+
+ir_action_report_xml()
diff --git a/addons/document/doc_conf_wizard.py b/addons/document/doc_conf_wizard.py
new file mode 100644
index 00000000000..66354c8876b
--- /dev/null
+++ b/addons/document/doc_conf_wizard.py
@@ -0,0 +1,163 @@
+# -*- coding: utf-8 -*-
+##############################################################################
+#
+# OpenERP, Open Source Management Solution
+# Copyright (C) 2004-2009 Tiny SPRL ().
+#
+# 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 .
+#
+##############################################################################
+
+import base64
+
+from osv import osv, fields
+from osv.orm import except_orm
+import urlparse
+
+import os
+
+class document_configuration_wizard(osv.osv_memory):
+ _name='document.configuration.wizard'
+ _rec_name = 'Auto Directory configuration'
+ _columns = {
+ 'host': fields.char('Server Address', size=64, help="Put here the server address or IP. " \
+ "Keep localhost if you don't know what to write.", required=True)
+ }
+
+ def detect_ip_addr(self, cr, uid, context=None):
+ def _detect_ip_addr(self, cr, uid, context=None):
+ from array import array
+ import socket
+ from struct import pack, unpack
+
+ try:
+ import fcntl
+ except ImportError:
+ fcntl = None
+
+ if not fcntl: # not UNIX:
+ host = socket.gethostname()
+ ip_addr = socket.gethostbyname(host)
+ else: # UNIX:
+ # get all interfaces:
+ nbytes = 128 * 32
+ s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
+ names = array('B', '\0' * nbytes)
+ outbytes = unpack('iL', fcntl.ioctl( s.fileno(), 0x8912, pack('iL', nbytes, names.buffer_info()[0])))[0]
+ namestr = names.tostring()
+ ifaces = [namestr[i:i+32].split('\0', 1)[0] for i in range(0, outbytes, 32)]
+
+ for ifname in [iface for iface in ifaces if iface != 'lo']:
+ ip_addr = socket.inet_ntoa(fcntl.ioctl(s.fileno(), 0x8915, pack('256s', ifname[:15]))[20:24])
+ break
+ return ip_addr
+
+ try:
+ ip_addr = _detect_ip_addr(self, cr, uid, context)
+ except:
+ ip_addr = 'localhost'
+ return ip_addr
+
+ _defaults = {
+ 'host': detect_ip_addr,
+ }
+
+ def action_cancel(self,cr,uid,ids,conect=None):
+ return {
+ 'view_type': 'form',
+ "view_mode": 'form',
+ 'res_model': 'ir.actions.configuration.wizard',
+ 'type': 'ir.actions.act_window',
+ 'target':'new',
+ }
+
+ def action_config(self, cr, uid, ids, context=None):
+ conf = self.browse(cr, uid, ids[0], context)
+ obj=self.pool.get('document.directory')
+ objid=self.pool.get('ir.model.data')
+
+ if self.pool.get('sale.order'):
+ id = objid._get_id(cr, uid, 'document', 'dir_sale_order_all')
+ id = objid.browse(cr, uid, id, context=context).res_id
+ mid = self.pool.get('ir.model').search(cr, uid, [('model','=','sale.order')])
+ obj.write(cr, uid, [id], {
+ 'type':'ressource',
+ 'ressource_type_id': mid[0],
+ 'domain': '[]',
+ })
+ aid = objid._get_id(cr, uid, 'sale', 'report_sale_order')
+ aid = objid.browse(cr, uid, aid, context=context).res_id
+
+ self.pool.get('document.directory.content').create(cr, uid, {
+ 'name': "Print Order",
+ 'suffix': "_print",
+ 'report_id': aid,
+ 'extension': '.pdf',
+ 'include_name': 1,
+ 'directory_id': id,
+ })
+ id = objid._get_id(cr, uid, 'document', 'dir_sale_order_quote')
+ id = objid.browse(cr, uid, id, context=context).res_id
+ obj.write(cr, uid, [id], {
+ 'type':'ressource',
+ 'ressource_type_id': mid[0],
+ 'domain': "[('state','=','draft')]",
+ })
+
+ if self.pool.get('product.product'):
+ id = objid._get_id(cr, uid, 'document', 'dir_product')
+ id = objid.browse(cr, uid, id, context=context).res_id
+ mid = self.pool.get('ir.model').search(cr, uid, [('model','=','product.product')])
+ obj.write(cr, uid, [id], {
+ 'type':'ressource',
+ 'ressource_type_id': mid[0],
+ })
+
+ if self.pool.get('stock.location'):
+ aid = objid._get_id(cr, uid, 'stock', 'report_product_history')
+ aid = objid.browse(cr, uid, aid, context=context).res_id
+
+ self.pool.get('document.directory.content').create(cr, uid, {
+ 'name': "Product Stock",
+ 'suffix': "_stock_forecast",
+ 'report_id': aid,
+ 'extension': '.pdf',
+ 'include_name': 1,
+ 'directory_id': id,
+ })
+
+ if self.pool.get('account.analytic.account'):
+ id = objid._get_id(cr, uid, 'document', 'dir_project')
+ id = objid.browse(cr, uid, id, context=context).res_id
+ mid = self.pool.get('ir.model').search(cr, uid, [('model','=','account.analytic.account')])
+ obj.write(cr, uid, [id], {
+ 'type':'ressource',
+ 'ressource_type_id': mid[0],
+ 'domain': '[]',
+ 'ressource_tree': 1
+ })
+
+ # Update the action for FTP browse.
+ aid = objid._get_id(cr, uid, 'document', 'action_document_browse')
+ aid = objid.browse(cr, uid, aid, context=context).res_id
+ self.pool.get('ir.actions.url').write(cr, uid, [aid], {'url': 'ftp://'+(conf.host or 'localhost')+':8021/'})
+
+ return {
+ 'view_type': 'form',
+ "view_mode": 'form',
+ 'res_model': 'ir.actions.configuration.wizard',
+ 'type': 'ir.actions.act_window',
+ 'target': 'new',
+ }
+document_configuration_wizard()
diff --git a/addons/document/document.py b/addons/document/document.py
index cd3d667f8bf..1dffba0453d 100644
--- a/addons/document/document.py
+++ b/addons/document/document.py
@@ -22,498 +22,19 @@
import base64
from osv import osv, fields
-from osv.orm import except_orm
import urlparse
import os
import pooler
-from content_index import content_index
import netsvc
-import StringIO
+#import StringIO
-import random
-import string
from psycopg2 import Binary
-from tools import config
+#from tools import config
import tools
from tools.translate import _
-
-def random_name():
- random.seed()
- d = [random.choice(string.ascii_letters) for x in xrange(10) ]
- name = "".join(d)
- return name
-
-
-# Unsupported WebDAV Commands:
-# label
-# search
-# checkin
-# checkout
-# propget
-# propset
-
-#
-# An object that represent an uri
-# path: the uri of the object
-# content: the Content it belongs to (_print.pdf)
-# type: content or collection
-# content: objct = res.partner
-# collection: object = directory, object2 = res.partner
-# file: objct = ir.attachement
-# root: if we are at the first directory of a ressource
-#
-INVALID_CHARS={'*':str(hash('*')), '|':str(hash('|')) , "\\":str(hash("\\")), '/':'__', ':':str(hash(':')), '"':str(hash('"')), '<':str(hash('<')) , '>':str(hash('>')) , '?':str(hash('?'))}
-
-
-class node_class(object):
- def __init__(self, cr, uid, path, object, object2=False, context={}, content=False, type='collection', root=False):
- self.cr = cr
- self.uid = uid
- self.path = path
- self.object = object
- self.object2 = object2
- self.context = context
- self.content = content
- self.type=type
- self.root=root
-
- def _file_get(self, nodename=False):
- if not self.object:
- return []
- pool = pooler.get_pool(self.cr.dbname)
- fobj = pool.get('ir.attachment')
- res2 = []
- where = []
- if self.object2:
- where.append( ('res_model','=',self.object2._name) )
- where.append( ('res_id','=',self.object2.id) )
- else:
- where.append( ('parent_id','=',self.object.id) )
- where.append( ('res_id','=',False) )
- if nodename:
- where.append( (fobj._rec_name,'=',nodename) )
- for content in self.object.content_ids:
- if self.object2 or not content.include_name:
- if content.include_name:
- content_name = self.object2.name
- obj = pool.get(self.object.ressource_type_id.model)
- name_for = obj._name.split('.')[-1]
- if content_name and content_name.find(name_for) == 0 :
- content_name = content_name.replace(name_for,'')
- test_nodename = content_name + (content.suffix or '') + (content.extension or '')
- else:
- test_nodename = (content.suffix or '') + (content.extension or '')
- if test_nodename.find('/'):
- test_nodename=test_nodename.replace('/', '_')
- path = self.path+'/'+test_nodename
- if not nodename:
- n = node_class(self.cr, self.uid,path, self.object2, False, context=self.context, content=content, type='content', root=False)
- res2.append( n)
- else:
- if nodename == test_nodename:
- n = node_class(self.cr, self.uid, path, self.object2, False, context=self.context, content=content, type='content', root=False)
- res2.append(n)
-
- ids = fobj.search(self.cr, self.uid, where+[ ('parent_id','=',self.object and self.object.id or False) ])
- if self.object and self.root and (self.object.type=='ressource'):
- ids += fobj.search(self.cr, self.uid, where+[ ('parent_id','=',False) ])
- res = fobj.browse(self.cr, self.uid, ids, context=self.context)
- return map(lambda x: node_class(self.cr, self.uid, self.path+'/'+eval('x.'+fobj._rec_name), x, False, context=self.context, type='file', root=False), res) + res2
-
- def get_translation(self,value,lang):
- result = value
- #TODO : to get translation term
- return result
-
- def directory_list_for_child(self,nodename,parent=False):
- pool = pooler.get_pool(self.cr.dbname)
- where = []
- if nodename:
- nodename = self.get_translation(nodename, self.context['lang'])
- where.append(('name','=',nodename))
- if (self.object and self.object.type=='directory') or not self.object2:
- where.append(('parent_id','=',self.object and self.object.id or False))
- else:
- where.append(('parent_id','=',False))
- if self.object:
- where.append(('ressource_parent_type_id','=',self.object.ressource_type_id.id))
- else:
- where.append(('ressource_parent_type_id','=',False))
-
- ids = pool.get('document.directory').search(self.cr, self.uid, where+[('ressource_id','=',0)])
- if self.object2:
- ids += pool.get('document.directory').search(self.cr, self.uid, where+[('ressource_id','=',self.object2.id)])
- res = pool.get('document.directory').browse(self.cr, self.uid, ids, self.context)
- return res
-
- def _child_get(self, nodename=False):
- if self.type not in ('collection','database'):
- return []
- res = self.directory_list_for_child(nodename)
- result= map(lambda x: node_class(self.cr, self.uid, self.path+'/'+x.name, x, x.type=='directory' and self.object2 or False, context=self.context, root=self.root), res)
- if self.type=='database':
- pool = pooler.get_pool(self.cr.dbname)
- fobj = pool.get('ir.attachment')
- vargs = [('parent_id','=',False),('res_id','=',False)]
- if nodename:
- vargs.append((fobj._rec_name,'=',nodename))
- file_ids=fobj.search(self.cr,self.uid,vargs)
-
- res = fobj.browse(self.cr, self.uid, file_ids, context=self.context)
- result +=map(lambda x: node_class(self.cr, self.uid, self.path+'/'+eval('x.'+fobj._rec_name), x, False, context=self.context, type='file', root=self.root), res)
- if self.type=='collection' and self.object.type=="ressource":
- where = self.object.domain and eval(self.object.domain, {'active_id':self.root, 'uid':self.uid}) or []
- pool = pooler.get_pool(self.cr.dbname)
- obj = pool.get(self.object.ressource_type_id.model)
- _dirname_field = obj._rec_name
- if len(obj.fields_get(self.cr, self.uid, ['dirname'])):
- _dirname_field = 'dirname'
-
- name_for = obj._name.split('.')[-1]
- if nodename and nodename.find(name_for) == 0 :
- id = int(nodename.replace(name_for,''))
- where.append(('id','=',id))
- elif nodename:
- if nodename.find('__') :
- nodename=nodename.replace('__','/')
- for invalid in INVALID_CHARS:
- if nodename.find(INVALID_CHARS[invalid]) :
- nodename=nodename.replace(INVALID_CHARS[invalid],invalid)
- nodename = self.get_translation(nodename, self.context['lang'])
- where.append((_dirname_field,'=',nodename))
-
- if self.object.ressource_tree:
- if obj._parent_name in obj.fields_get(self.cr,self.uid):
- where.append((obj._parent_name,'=',self.object2 and self.object2.id or False))
- ids = obj.search(self.cr, self.uid, where)
- res = obj.browse(self.cr, self.uid, ids,self.context)
- result+= map(lambda x: node_class(self.cr, self.uid, self.path+'/'+x.name.replace('/','__'), self.object, x, context=self.context, root=x.id), res)
- return result
- else :
- if self.object2:
- return result
- else:
- if self.object2:
- return result
-
-
- ids = obj.search(self.cr, self.uid, where)
- res = obj.browse(self.cr, self.uid, ids,self.context)
- for r in res:
- if len(obj.fields_get(self.cr, self.uid, [_dirname_field])):
- r.name = eval('r.'+_dirname_field)
- else:
- r.name = False
- if not r.name:
- r.name = name_for + '%d'%r.id
- for invalid in INVALID_CHARS:
- if r.name.find(invalid) :
- r.name = r.name.replace(invalid,INVALID_CHARS[invalid])
- result2 = map(lambda x: node_class(self.cr, self.uid, self.path+'/'+x.name.replace('/','__'), self.object, x, context=self.context, root=x.id), res)
- if result2:
- if self.object.ressource_tree:
- result += result2
- else:
- result = result2
- return result
-
- def children(self):
- return self._child_get() + self._file_get()
-
- def child(self, name):
- res = self._child_get(name)
- if res:
- return res[0]
- res = self._file_get(name)
- if res:
- return res[0]
- return None
-
- def path_get(self):
- path = self.path
- if self.path[0]=='/':
- path = self.path[1:]
- return path
-
-class document_directory(osv.osv):
- _name = 'document.directory'
- _description = 'Document directory'
- _columns = {
- 'name': fields.char('Name', size=64, required=True, select=1),
- 'write_date': fields.datetime('Date Modified', readonly=True),
- 'write_uid': fields.many2one('res.users', 'Last Modification User', readonly=True),
- 'create_date': fields.datetime('Date Created', readonly=True),
- 'create_uid': fields.many2one('res.users', 'Creator', readonly=True),
- 'file_type': fields.char('Content Type', size=32),
- 'domain': fields.char('Domain', size=128, help="Use a domain if you want to apply an automatic filter on visible resources."),
- 'user_id': fields.many2one('res.users', 'Owner'),
- 'group_ids': fields.many2many('res.groups', 'document_directory_group_rel', 'item_id', 'group_id', 'Groups'),
- 'parent_id': fields.many2one('document.directory', 'Parent Item'),
- 'child_ids': fields.one2many('document.directory', 'parent_id', 'Children'),
- 'file_ids': fields.one2many('ir.attachment', 'parent_id', 'Files'),
- 'content_ids': fields.one2many('document.directory.content', 'directory_id', 'Virtual Files'),
- 'type': fields.selection([('directory','Static Directory'),('ressource','Other Resources')], 'Type', required=True),
- 'ressource_type_id': fields.many2one('ir.model', 'Directories Mapped to Objects',
- help="Select an object here and Open ERP will create a mapping for each of these " \
- "objects, using the given domain, when browsing through FTP."),
- 'ressource_parent_type_id': fields.many2one('ir.model', 'Parent Model',
- help="If you put an object here, this directory template will appear bellow all of these objects. " \
- "Don't put a parent directory if you select a parent model."),
- 'ressource_id': fields.integer('Resource ID'),
- 'ressource_tree': fields.boolean('Tree Structure',
- help="Check this if you want to use the same tree structure as the object selected in the system."),
- }
- _defaults = {
- 'user_id': lambda self,cr,uid,ctx: uid,
- 'domain': lambda self,cr,uid,ctx: '[]',
- 'type': lambda *args: 'directory',
- 'ressource_id': lambda *a: 0
- }
- _sql_constraints = [
- ('dirname_uniq', 'unique (name,parent_id,ressource_id,ressource_parent_type_id)', 'The directory name must be unique !')
- ]
-
- def get_resource_path(self,cr,uid,dir_id,res_model,res_id):
- # this method will be used in process module
- # to be need test and Improvement if resource dir has parent resource (link resource)
- path=[]
- def _parent(dir_id,path):
- parent=self.browse(cr,uid,dir_id)
- if parent.parent_id and not parent.ressource_parent_type_id:
- _parent(parent.parent_id.id,path)
- path.append(parent.name)
- else:
- path.append(parent.name)
- return path
-
- directory=self.browse(cr,uid,dir_id)
- model_ids=self.pool.get('ir.model').search(cr,uid,[('model','=',res_model)])
- if directory:
- _parent(dir_id,path)
- if res_id:
- path.append(self.pool.get(directory.ressource_type_id.model).browse(cr,uid,res_id).name)
- user=self.pool.get('res.users').browse(cr,uid,uid)
- return "ftp://%s:%s@localhost:%s/%s/%s"%(user.login,user.password,config.get('ftp_server_port',8021),cr.dbname,'/'.join(path))
- return False
-
- def _check_recursion(self, cr, uid, ids):
- level = 100
- while len(ids):
- cr.execute('select distinct parent_id from document_directory where id in ('+','.join(map(str, ids))+')')
- ids = filter(None, map(lambda x:x[0], cr.fetchall()))
- if not level:
- return False
- level -= 1
- return True
-
- _constraints = [
- (_check_recursion, 'Error! You can not create recursive Directories.', ['parent_id'])
- ]
- def __init__(self, *args, **kwargs):
- res = super(document_directory, self).__init__(*args, **kwargs)
- self._cache = {}
-
- def onchange_content_id(self, cr, uid, ids, ressource_type_id):
- return {}
-
- def _get_childs(self, cr, uid, node, nodename=False, context={}):
- where = []
- if nodename:
- nodename = self.get_translation(nodename, self.context['lang'])
- where.append(('name','=',nodename))
- if object:
- where.append(('parent_id','=',object.id))
- ids = self.search(cr, uid, where, context)
- return self.browse(cr, uid, ids, context), False
-
- """
- PRE:
- uri: of the form "Sales Order/SO001"
- PORT:
- uri
- object: the object.directory or object.directory.content
- object2: the other object linked (if object.directory.content)
- """
- def get_object(self, cr, uid, uri, context={}):
- #TODO : set user's context_lang in context
- context.update({'lang':False})
- if not uri:
- return node_class(cr, uid, '', False, context=context, type='database')
- turi = tuple(uri)
- if False and (turi in self._cache):
- (path, oo, oo2, context, content,type,root) = self._cache[turi]
- if oo:
- object = self.pool.get(oo[0]).browse(cr, uid, oo[1], context)
- else:
- object = False
- if oo2:
- object2 = self.pool.get(oo2[0]).browse(cr, uid, oo2[1], context)
- else:
- object2 = False
- node = node_class(cr, uid, '/', False, context=context, type='database')
- return node
-
- node = node_class(cr, uid, '/', False, context=context, type='database')
- for path in uri[:]:
- if path:
- node = node.child(path)
- if not node:
- return False
- oo = node.object and (node.object._name, node.object.id) or False
- oo2 = node.object2 and (node.object2._name, node.object2.id) or False
- self._cache[turi] = (node.path, oo, oo2, node.context, node.content,node.type,node.root)
- return node
-
- def get_childs(self, cr, uid, uri, context={}):
- node = self.get_object(cr, uid, uri, context)
- if uri:
- children = node.children()
- else:
- children= [node]
- result = map(lambda node: node.path_get(), children)
- #childs,object2 = self._get_childs(cr, uid, object, False, context)
- #result = map(lambda x: urlparse.urljoin(path+'/',x.name), childs)
- return result
-
- def copy(self, cr, uid, id, default=None, context=None):
- if not default:
- default ={}
- name = self.read(cr, uid, [id])[0]['name']
- default.update({'name': name+ " (copy)"})
- return super(document_directory,self).copy(cr,uid,id,default,context)
-
- def _check_duplication(self, cr, uid,vals,ids=[],op='create'):
- name=vals.get('name',False)
- parent_id=vals.get('parent_id',False)
- ressource_parent_type_id=vals.get('ressource_parent_type_id',False)
- ressource_id=vals.get('ressource_id',0)
- if op=='write':
- for directory in self.browse(cr,uid,ids):
- if not name:
- name=directory.name
- if not parent_id:
- parent_id=directory.parent_id and directory.parent_id.id or False
- if not ressource_parent_type_id:
- ressource_parent_type_id=directory.ressource_parent_type_id and directory.ressource_parent_type_id.id or False
- if not ressource_id:
- ressource_id=directory.ressource_id and directory.ressource_id or 0
- res=self.search(cr,uid,[('id','<>',directory.id),('name','=',name),('parent_id','=',parent_id),('ressource_parent_type_id','=',ressource_parent_type_id),('ressource_id','=',ressource_id)])
- if len(res):
- return False
- if op=='create':
- res=self.search(cr,uid,[('name','=',name),('parent_id','=',parent_id),('ressource_parent_type_id','=',ressource_parent_type_id),('ressource_id','=',ressource_id)])
- if len(res):
- return False
- return True
- def write(self, cr, uid, ids, vals, context=None):
- if not self._check_duplication(cr,uid,vals,ids,op='write'):
- raise osv.except_osv(_('ValidateError'), _('Directory name must be unique!'))
- return super(document_directory,self).write(cr,uid,ids,vals,context=context)
-
- def create(self, cr, uid, vals, context=None):
- if not self._check_duplication(cr,uid,vals):
- raise osv.except_osv(_('ValidateError'), _('Directory name must be unique!'))
- if vals.get('name',False) and (vals.get('name').find('/')+1 or vals.get('name').find('@')+1 or vals.get('name').find('$')+1 or vals.get('name').find('#')+1) :
- raise osv.except_osv(_('ValidateError'), _('Directory name contains special characters!'))
- return super(document_directory,self).create(cr, uid, vals, context)
-
-document_directory()
-
-class document_directory_node(osv.osv):
- _inherit = 'process.node'
- _columns = {
- 'directory_id': fields.many2one('document.directory', 'Document directory', ondelete="set null"),
- }
-document_directory_node()
-
-class document_directory_content_type(osv.osv):
- _name = 'document.directory.content.type'
- _description = 'Directory Content Type'
- _columns = {
- 'name': fields.char('Content Type', size=64, required=True),
- 'code': fields.char('Extension', size=4),
- 'active': fields.boolean('Active'),
- }
- _defaults = {
- 'active': lambda *args: 1
- }
-document_directory_content_type()
-
-class document_directory_content(osv.osv):
- _name = 'document.directory.content'
- _description = 'Directory Content'
- _order = "sequence"
- def _extension_get(self, cr, uid, context={}):
- cr.execute('select code,name from document_directory_content_type where active')
- res = cr.fetchall()
- return res
- _columns = {
- 'name': fields.char('Content Name', size=64, required=True),
- 'sequence': fields.integer('Sequence', size=16),
- 'suffix': fields.char('Suffix', size=16),
- 'report_id': fields.many2one('ir.actions.report.xml', 'Report'),
- 'extension': fields.selection(_extension_get, 'Document Type', required=True, size=4),
- 'include_name': fields.boolean('Include Record Name', help="Check this field if you want that the name of the file start by the record name."),
- 'directory_id': fields.many2one('document.directory', 'Directory'),
- }
- _defaults = {
- 'extension': lambda *args: '.pdf',
- 'sequence': lambda *args: 1,
- 'include_name': lambda *args: 1,
- }
- def process_write_pdf(self, cr, uid, node, context={}):
- return True
- def process_read_pdf(self, cr, uid, node, context={}):
- report = self.pool.get('ir.actions.report.xml').browse(cr, uid, node.content.report_id.id)
- srv = netsvc.LocalService('report.'+report.report_name)
- pdf,pdftype = srv.create(cr, uid, [node.object.id], {}, {})
- s = StringIO.StringIO(pdf)
- s.name = node
- return s
-document_directory_content()
-
-class ir_action_report_xml(osv.osv):
- _name="ir.actions.report.xml"
- _inherit ="ir.actions.report.xml"
-
- def _model_get(self, cr, uid, ids, name, arg, context):
- res = {}
- model_pool = self.pool.get('ir.model')
- for data in self.read(cr,uid,ids,['model']):
- model = data.get('model',False)
- if model:
- model_id =model_pool.search(cr,uid,[('model','=',model)])
- if model_id:
- res[data.get('id')] = model_id[0]
- else:
- res[data.get('id')] = False
- return res
-
- def _model_search(self, cr, uid, obj, name, args):
- if not len(args):
- return []
- model_id= args[0][2]
- if not model_id:
- return []
- model = self.pool.get('ir.model').read(cr,uid,[model_id])[0]['model']
- report_id = self.search(cr,uid,[('model','=',model)])
- if not report_id:
- return [('id','=','0')]
- return [('id','in',report_id)]
-
- _columns={
- 'model_id' : fields.function(_model_get,fnct_search=_model_search,method=True,string='Model Id'),
- }
-
-ir_action_report_xml()
-
-def create_directory(path):
- dir_name = random_name()
- path = os.path.join(path,dir_name)
- os.makedirs(path)
- return dir_name
+import nodes
class document_file(osv.osv):
_inherit = 'ir.attachment'
@@ -522,70 +43,49 @@ class document_file(osv.osv):
return os.path.join(tools.config['root_path'], 'filestore', cr.dbname)
def _data_get(self, cr, uid, ids, name, arg, context):
+ fbrl = self.browse(cr,uid,ids,context=context)
+ nctx = nodes.get_node_context(cr,uid,context)
result = {}
- cr.execute('select id,store_fname,link from ir_attachment where id in ('+','.join(map(str, ids))+')')
- for id,r,l in cr.fetchall():
- try:
- value = file(os.path.join(self._get_filestore(cr), r), 'rb').read()
- result[id] = base64.encodestring(value)
- except:
- result[id]=''
-# if context.get('bin_size', False):
-# result[id] = tools.human_size(result[id])
+ bin_size = context.get('bin_size', False)
+ for fbro in fbrl:
+ fnode = nodes.node_file(None,None,nctx,fbro)
+ if not bin_size:
+ data = fnode.get_data(cr,fbro)
+ result[fbro.id] = base64.encodestring(data or '')
+ else:
+ result[fbro.id] = fnode.get_data_len(cr,fbro)
+
return result
#
# This code can be improved
#
- def _data_set(self, cr, uid, id, name, value, args=None, context={}):
+ def _data_set(self, cr, uid, id, name, value, arg, context):
if not value:
- filename = self.browse(cr, uid, id, context).store_fname
- try:
- os.unlink(os.path.join(self._get_filestore(cr), filename))
- except:
- pass
- cr.execute('update ir_attachment set store_fname=NULL WHERE id=%s', (id,) )
return True
- #if (not context) or context.get('store_method','fs')=='fs':
- try:
- path = self._get_filestore(cr)
- if not os.path.isdir(path):
- try:
- os.makedirs(path)
- except:
- raise except_orm(_('Permission Denied !'), _('You do not permissions to write on the server side.'))
-
- flag = None
- # This can be improved
- for dirs in os.listdir(path):
- if os.path.isdir(os.path.join(path,dirs)) and len(os.listdir(os.path.join(path,dirs)))<4000:
- flag = dirs
- break
- flag = flag or create_directory(path)
- filename = random_name()
- fname = os.path.join(path, flag, filename)
- fp = file(fname,'wb')
- v = base64.decodestring(value)
- fp.write(v)
- filesize = os.stat(fname).st_size
- cr.execute('update ir_attachment set store_fname=%s,store_method=%s,file_size=%s where id=%s', (os.path.join(flag,filename),'fs',len(v),id))
- return True
- except Exception,e :
- raise except_orm(_('Error!'), str(e))
+ fbro = self.browse(cr,uid,id,context=context)
+ nctx = nodes.get_node_context(cr,uid,context)
+ fnode = nodes.node_file(None,None,nctx,fbro)
+ res = fnode.set_data(cr,base64.decodestring(value),fbro)
+ return res
_columns = {
'user_id': fields.many2one('res.users', 'Owner', select=1),
'group_ids': fields.many2many('res.groups', 'document_directory_group_rel', 'item_id', 'group_id', 'Groups'),
- 'parent_id': fields.many2one('document.directory', 'Directory', select=1),
+ # the directory id now is mandatory. It can still be computed automatically.
+ 'parent_id': fields.many2one('document.directory', 'Directory', select=1, required=True),
'file_size': fields.integer('File Size', required=True),
'file_type': fields.char('Content Type', size=32),
+ # If ir.attachment contained any data before document is installed, preserve
+ # the data, don't drop the column!
+ 'db_datas': fields.binary('Data',oldname='datas'),
'index_content': fields.text('Indexed Content'),
'write_date': fields.datetime('Date Modified', readonly=True),
'write_uid': fields.many2one('res.users', 'Last Modification User', readonly=True),
'create_date': fields.datetime('Date Created', readonly=True),
'create_uid': fields.many2one('res.users', 'Creator', readonly=True),
'store_method': fields.selection([('db','Database'),('fs','Filesystem'),('link','Link')], "Storing Method"),
- 'datas': fields.function(_data_get,method=True,fnct_inv=_data_set,string='File Content',type="binary"),
+ 'datas': fields.function(_data_get,method=True,fnct_inv=_data_set,string='File Content',type="binary", nodrop=True),
'store_fname': fields.char('Stored Filename', size=200),
'res_model': fields.char('Attached Model', size=64), #res_model
'res_id': fields.integer('Attached ID'), #res_id
@@ -593,10 +93,15 @@ class document_file(osv.osv):
'title': fields.char('Resource Title',size=64),
}
+ def __get_def_directory(self,cr,uid, context = None):
+ dirobj = self.pool.get('document.directory')
+ return dirobj._get_root_directory(cr,uid,context)
+
_defaults = {
'user_id': lambda self,cr,uid,ctx:uid,
'file_size': lambda self,cr,uid,ctx:0,
- 'store_method': lambda *args: 'db'
+ 'store_method': lambda *args: 'db',
+ 'parent_id': __get_def_directory
}
_sql_constraints = [
('filename_uniq', 'unique (name,parent_id,res_id,res_model)', 'The file name must be unique !')
@@ -638,20 +143,11 @@ class document_file(osv.osv):
raise except_orm(_('ValidateError'), _('File name must be unique!'))
result = super(document_file,self).write(cr,uid,ids,vals,context=context)
cr.commit()
- try:
- for f in self.browse(cr, uid, ids, context=context):
- #if 'datas' not in vals:
- # vals['datas']=f.datas
- res = content_index(base64.decodestring(vals['datas']), f.datas_fname, f.file_type or None)
- super(document_file,self).write(cr, uid, ids, {
- 'index_content': res
- })
- cr.commit()
- except:
- pass
return result
- def create(self, cr, uid, vals, context={}):
+ def create(self, cr, uid, vals, context=None):
+ if not context:
+ context = {}
vals['title']=vals['name']
vals['parent_id'] = context.get('parent_id',False) or vals.get('parent_id',False)
if not vals.get('res_id', False) and context.get('default_res_id',False):
@@ -686,200 +182,30 @@ class document_file(osv.osv):
import urllib
datas=base64.encodestring(urllib.urlopen(vals['link']).read())
else:
- datas = vals.get('datas',False)
-
+ datas=vals.get('datas',False)
vals['file_size']= datas and len(datas) or 0
if not self._check_duplication(cr,uid,vals):
raise except_orm(_('ValidateError'), _('File name must be unique!'))
result = super(document_file,self).create(cr, uid, vals, context)
cr.commit()
- try:
- res = content_index(base64.decodestring(datas), vals['datas_fname'], vals.get('content_type', None))
- super(document_file,self).write(cr, uid, [result], {
- 'index_content': res,
- })
- cr.commit()
- except:
- pass
return result
def unlink(self,cr, uid, ids, context={}):
+ stor = self.pool.get('document.storage')
+ unres= []
+ # We have to do the unlink in 2 stages: prepare a list of actual
+ # files to be unlinked, update the db (safer to do first, can be
+ # rolled back) and then unlink the files. The list wouldn't exist
+ # after we discard the objects
+
for f in self.browse(cr, uid, ids, context):
- #if f.store_method=='fs':
- try:
- os.unlink(os.path.join(self._get_filestore(cr), f.store_fname))
- except:
- pass
- return super(document_file, self).unlink(cr, uid, ids, context)
+ # TODO: update the node cache
+ r = stor.prepare_unlink(cr,uid,f.parent_id.storage_id, f)
+ if r:
+ unres.append(r)
+ res = super(document_file, self).unlink(cr, uid, ids, context)
+ stor.do_unlink(cr,uid,unres)
+ return res
+
document_file()
-class document_configuration_wizard(osv.osv_memory):
- _name='document.configuration.wizard'
- _rec_name = 'Auto Directory configuration'
- _columns = {
- 'host': fields.char('Server Address', size=64, help="Put here the server address or IP. " \
- "Keep localhost if you don't know what to write.", required=True),
- 'suggested_host': fields.char('Suggested Server Address', size=64, readonly=True, help="This is the guessed server address"),
- 'port': fields.char('Server Port', size=5, help="Put here the server port. " \
- "Keep 8021 if you don't know what to write.", required=True)
- }
-
- def get_ftp_server_address(self, cr, uid, context=None):
- default_address = config.get('ftp_server_address', None)
- if default_address is None:
- default_address = tools.misc.detect_ip_addr()
-
- return default_address
-
- def get_ftp_server_port(self, cr, uid, context=None):
- return config.get('ftp_server_port', 8021)
-
- def get_suggested_ftp_server_address(self, cr, uid, context=None):
- return tools.misc.detect_ip_addr()
-
- _defaults = {
- 'host': get_ftp_server_address,
- 'suggested_host': get_suggested_ftp_server_address,
- 'port': get_ftp_server_port,
- }
-
- def action_cancel(self,cr,uid,ids,conect=None):
- return {
- 'view_type': 'form',
- "view_mode": 'form',
- 'res_model': 'ir.actions.configuration.wizard',
- 'type': 'ir.actions.act_window',
- 'target':'new',
- }
-
- def action_config(self, cr, uid, ids, context=None):
- conf = self.browse(cr, uid, ids[0], context)
- obj=self.pool.get('document.directory')
- objid=self.pool.get('ir.model.data')
-
- if self.pool.get('sale.order'):
- id = objid._get_id(cr, uid, 'document', 'dir_sale_order_all')
- id = objid.browse(cr, uid, id, context=context).res_id
- mid = self.pool.get('ir.model').search(cr, uid, [('model','=','sale.order')])
- obj.write(cr, uid, [id], {
- 'type':'ressource',
- 'ressource_type_id': mid[0],
- 'domain': '[]',
- })
- aid = objid._get_id(cr, uid, 'sale', 'report_sale_order')
- aid = objid.browse(cr, uid, aid, context=context).res_id
-
- self.pool.get('document.directory.content').create(cr, uid, {
- 'name': "Print Order",
- 'suffix': "_print",
- 'report_id': aid,
- 'extension': '.pdf',
- 'include_name': 1,
- 'directory_id': id,
- })
- id = objid._get_id(cr, uid, 'document', 'dir_sale_order_quote')
- id = objid.browse(cr, uid, id, context=context).res_id
- obj.write(cr, uid, [id], {
- 'type':'ressource',
- 'ressource_type_id': mid[0],
- 'domain': "[('state','=','draft')]",
- })
-
- if self.pool.get('product.product'):
- id = objid._get_id(cr, uid, 'document', 'dir_product')
- id = objid.browse(cr, uid, id, context=context).res_id
- mid = self.pool.get('ir.model').search(cr, uid, [('model','=','product.product')])
- obj.write(cr, uid, [id], {
- 'type':'ressource',
- 'ressource_type_id': mid[0],
- })
-
- if self.pool.get('stock.location'):
- aid = objid._get_id(cr, uid, 'stock', 'report_product_history')
- aid = objid.browse(cr, uid, aid, context=context).res_id
-
- self.pool.get('document.directory.content').create(cr, uid, {
- 'name': "Product Stock",
- 'suffix': "_stock_forecast",
- 'report_id': aid,
- 'extension': '.pdf',
- 'include_name': 1,
- 'directory_id': id,
- })
-
- if self.pool.get('account.analytic.account'):
- id = objid._get_id(cr, uid, 'document', 'dir_project')
- id = objid.browse(cr, uid, id, context=context).res_id
- mid = self.pool.get('ir.model').search(cr, uid, [('model','=','account.analytic.account')])
- obj.write(cr, uid, [id], {
- 'type':'ressource',
- 'ressource_type_id': mid[0],
- 'domain': '[]',
- 'ressource_tree': 1
- })
-
- aid = objid._get_id(cr, uid, 'document', 'action_document_browse')
- aid = objid.browse(cr, uid, aid, context=context).res_id
- self.pool.get('ir.actions.url').write(cr, uid, [aid], {'url': 'ftp://'+(conf.host or 'localhost')+':'+conf.port+'/'})
-
- config['ftp_server_address'] = conf.host
- config['ftp_server_port'] = conf.port
- config.save()
-
- return {
- 'view_type': 'form',
- "view_mode": 'form',
- 'res_model': 'ir.actions.configuration.wizard',
- 'type': 'ir.actions.act_window',
- 'target': 'new',
- }
-
-document_configuration_wizard()
-
-
-class document_configuration_ftpserver_wizard(osv.osv_memory):
- _name='document.configuration.ftp_server.wizard'
- _rec_name = 'Configure FTP server address'
- _columns = {
- 'host': fields.char('Server Address', size=64, help="Put here the server address or IP. " \
- "Keep localhost if you don't know what to write.", required=True),
- 'suggested_host': fields.char('Suggested Server Address', size=64, readonly=True, help="This is the guessed server address"),
- 'port': fields.char('Server Port', size=5, help="Put here the server port. " \
- "Keep 8021 if you don't know what to write.", required=True)
- }
-
- def get_ftp_server_address(self, cr, uid, context=None):
- default_address = tools.misc.detect_ip_addr()
- return config.get('ftp_server_address', default_address)
-
- def get_ftp_server_port(self, cr, uid, context=None):
- return config.get('ftp_server_port', '8021')
-
- def get_suggested_ftp_server_address(self, cr, uid, context=None):
- return tools.misc.detect_ip_addr()
-
- _defaults = {
- 'host': get_ftp_server_address,
- 'suggested_host': get_suggested_ftp_server_address,
- 'port': get_ftp_server_port,
- }
-
- def action_cancel(self,cr,uid,ids,conect=None):
- return {}
-
- def action_config(self, cr, uid, ids, context=None):
- conf = self.browse(cr, uid, ids[0], context)
- obj = self.pool.get('ir.model.data')
-
- aid = obj._get_id(cr, uid, 'document', 'action_document_browse')
- aid = obj.browse(cr, uid, aid, context=context).res_id
- self.pool.get('ir.actions.url').write(cr, uid, [aid], {'url': 'ftp://'+(conf.host or 'localhost')+':'+conf.port+'/'})
-
- config['ftp_server_address'] = conf.host
- config['ftp_server_port'] = conf.port
- config.save()
-
- return {}
-
-document_configuration_ftpserver_wizard()
-
diff --git a/addons/document/document_data.xml b/addons/document/document_data.xml
index 511de005b6f..77c80b078d1 100644
--- a/addons/document/document_data.xml
+++ b/addons/document/document_data.xml
@@ -1,10 +1,112 @@
-
+
.pdf
PDF Report
+ application/pdf
+
+
+
+ Default File storage
+
+
+
+
+ DB storage
+
+
+
+
+
+
+ Documents
+
+
+ 0
+
+
+
+ My Folder
+
+
+
+ 0
+
+
+
+
+ Partners by Category
+
+ ressource
+ 1
+
+ 0
+
+
+
+
+
+
+ Partners
+ ressource
+ [('category_id','in',[active_id])]
+
+
+
+ 0
+
+
+
+
+ Personnal Folders
+
+ ressource
+
+
+ 0
+
+
+
+
+ Products
+
+
+ 0
+
+
+
+
+ Sales Order
+
+
+ 0
+
+
+
+
+ All Sales Order
+
+
+ 0
+
+
+
+
+ Quotations
+
+
+ 0
+
+
+
+
+ Projects
+
+
+ 0
+
diff --git a/addons/document/document_demo.xml b/addons/document/document_demo.xml
index 95910e6031b..1c757a73909 100644
--- a/addons/document/document_demo.xml
+++ b/addons/document/document_demo.xml
@@ -2,90 +2,6 @@
-
- Documents
-
- 0
-
-
-
- My Folder
-
-
- 0
-
-
-
-
- Partners by Category
-
- ressource
- 1
- 0
-
-
-
-
-
-
- Partners
- ressource
- [('category_id','in',[active_id])]
-
-
-
- 0
-
-
-
-
- Personnal Folders
-
- ressource
-
- 0
-
-
-
-
- Products
-
-
- 0
-
-
-
-
- Sales Order
-
-
- 0
-
-
-
-
- All Sales Order
-
-
- 0
-
-
-
-
- Quotations
-
-
- 0
-
-
-
-
- Projects
-
-
- 0
-
-
diff --git a/addons/document/document_directory.py b/addons/document/document_directory.py
new file mode 100644
index 00000000000..133927f9664
--- /dev/null
+++ b/addons/document/document_directory.py
@@ -0,0 +1,312 @@
+# -*- coding: utf-8 -*-
+##############################################################################
+#
+# OpenERP, Open Source Management Solution
+# Copyright (C) 2004-2009 Tiny SPRL ().
+#
+# 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 .
+#
+##############################################################################
+
+import base64
+
+from osv import osv, fields
+from osv.orm import except_orm
+import urlparse
+
+import os
+import nodes
+
+class document_directory(osv.osv):
+ _name = 'document.directory'
+ _description = 'Document directory'
+ _columns = {
+ 'name': fields.char('Name', size=64, required=True, select=1),
+ 'write_date': fields.datetime('Date Modified', readonly=True),
+ 'write_uid': fields.many2one('res.users', 'Last Modification User', readonly=True),
+ 'create_date': fields.datetime('Date Created', readonly=True),
+ 'create_uid': fields.many2one('res.users', 'Creator', readonly=True),
+ 'file_type': fields.char('Content Type', size=32),
+ 'domain': fields.char('Domain', size=128, help="Use a domain if you want to apply an automatic filter on visible resources."),
+ 'user_id': fields.many2one('res.users', 'Owner'),
+ 'storage_id': fields.many2one('document.storage', 'Storage'),
+ 'group_ids': fields.many2many('res.groups', 'document_directory_group_rel', 'item_id', 'group_id', 'Groups'),
+ 'parent_id': fields.many2one('document.directory', 'Parent Item'),
+ 'child_ids': fields.one2many('document.directory', 'parent_id', 'Children'),
+ 'file_ids': fields.one2many('ir.attachment', 'parent_id', 'Files'),
+ 'content_ids': fields.one2many('document.directory.content', 'directory_id', 'Virtual Files'),
+ 'type': fields.selection([('directory','Static Directory'),('ressource','Other Resources')], 'Type', required=True),
+ 'ressource_type_id': fields.many2one('ir.model', 'Directories Mapped to Objects',
+ help="Select an object here and Open ERP will create a mapping for each of these " \
+ "objects, using the given domain, when browsing through FTP."),
+ 'resource_field': fields.char('Name field',size=32,help='Field to be used as name on resource directories. If empty, the "name" will be used.'),
+ 'ressource_parent_type_id': fields.many2one('ir.model', 'Parent Model',
+ help="If you put an object here, this directory template will appear bellow all of these objects. " \
+ "Don't put a parent directory if you select a parent model."),
+ 'ressource_id': fields.integer('Resource ID'),
+ 'ressource_tree': fields.boolean('Tree Structure',
+ help="Check this if you want to use the same tree structure as the object selected in the system."),
+ 'dctx_ids': fields.one2many('document.directory.dctx', 'dir_id', 'Context fields'),
+ }
+ def _get_root_directory(self, cr,uid, context=None):
+ objid=self.pool.get('ir.model.data')
+ try:
+ mid = objid._get_id(cr, uid, 'document', 'dir_root')
+ if not mid:
+ return None
+ except Exception, e:
+ import netsvc
+ logger = netsvc.Logger()
+ logger.notifyChannel("document", netsvc.LOG_WARNING, 'Cannot set directory root:'+ str(e))
+ return None
+ return objid.browse(cr, uid, mid, context=context).res_id
+
+ def _get_def_storage(self,cr,uid,context=None):
+ if context and context.has_key('default_parent_id'):
+ # Use the same storage as the parent..
+ diro = self.browse(cr,uid,context['default_parent_id'])
+ if diro.storage_id:
+ return diro.storage_id.id
+ objid=self.pool.get('ir.model.data')
+ try:
+ mid = objid._get_id(cr, uid, 'document', 'storage_default')
+ return objid.browse(cr, uid, mid, context=context).res_id
+ except Exception:
+ return None
+
+ _defaults = {
+ 'user_id': lambda self,cr,uid,ctx: uid,
+ 'domain': lambda self,cr,uid,ctx: '[]',
+ 'type': lambda *args: 'directory',
+ 'ressource_id': lambda *a: 0,
+ 'parent_id': _get_root_directory,
+ 'storage_id': _get_def_storage,
+ }
+ _sql_constraints = [
+ ('dirname_uniq', 'unique (name,parent_id,ressource_id,ressource_parent_type_id)', 'The directory name must be unique !'),
+ ('no_selfparent', 'check(parent_id <> id)', 'Directory cannot be parent of itself!')
+ ]
+
+ def ol_get_resource_path(self,cr,uid,dir_id,res_model,res_id):
+ # this method will be used in process module
+ # to be need test and Improvement if resource dir has parent resource (link resource)
+ path=[]
+ def _parent(dir_id,path):
+ parent=self.browse(cr,uid,dir_id)
+ if parent.parent_id and not parent.ressource_parent_type_id:
+ _parent(parent.parent_id.id,path)
+ path.append(parent.name)
+ else:
+ path.append(parent.name)
+ return path
+
+ directory=self.browse(cr,uid,dir_id)
+ model_ids=self.pool.get('ir.model').search(cr,uid,[('model','=',res_model)])
+ if directory:
+ _parent(dir_id,path)
+ path.append(self.pool.get(directory.ressource_type_id.model).browse(cr,uid,res_id).name)
+ #user=self.pool.get('res.users').browse(cr,uid,uid)
+ #return "ftp://%s:%s@localhost:%s/%s/%s"%(user.login,user.password,config.get('ftp_server_port',8021),cr.dbname,'/'.join(path))
+ # No way we will return the password!
+ return "ftp://user:pass@host:port/test/this"
+ return False
+
+ def _check_recursion(self, cr, uid, ids):
+ level = 100
+ while len(ids):
+ cr.execute('select distinct parent_id from document_directory where id in ('+','.join(map(str,ids))+')')
+ ids = filter(None, map(lambda x:x[0], cr.fetchall()))
+ if not level:
+ return False
+ level -= 1
+ return True
+
+ _constraints = [
+ (_check_recursion, 'Error! You can not create recursive Directories.', ['parent_id'])
+ ]
+ def __init__(self, *args, **kwargs):
+ res = super(document_directory, self).__init__(*args, **kwargs)
+ #self._cache = {}
+
+ def onchange_content_id(self, cr, uid, ids, ressource_type_id):
+ return {}
+
+ """
+ PRE:
+ uri: of the form "Sales Order/SO001"
+ PORT:
+ uri
+ object: the object.directory or object.directory.content
+ object2: the other object linked (if object.directory.content)
+ """
+ def get_object(self, cr, uid, uri, context=None):
+ """ Return a node object for the given uri.
+ This fn merely passes the call to node_context
+ """
+ if not context:
+ context = {}
+ lang = context.get('lang',False)
+ if not lang:
+ user = self.pool.get('res.users').browse(cr, uid, uid)
+ lang = user.context_lang
+ context['lang'] = lang
+
+ try: #just instrumentation
+ return nodes.get_node_context(cr, uid, context).get_uri(cr,uri)
+ except Exception,e:
+ print "exception: ",e
+ raise
+
+
+ def _locate_child(self, cr,uid, root_id, uri,nparent, ncontext):
+ """ try to locate the node in uri,
+ Return a tuple (node_dir, remaining_path)
+ """
+ did = root_id
+ duri = uri
+ path = []
+ context = ncontext.context
+ while len(duri):
+ nid = self.search(cr,uid,[('parent_id','=',did),('name','=',duri[0]),('type','=','directory')], context=context)
+ if not nid:
+ break
+ if len(nid)>1:
+ print "Duplicate dir? p= %d, n=\"%s\"" %(did,duri[0])
+ path.append(duri[0])
+ duri = duri[1:]
+ did = nid[0]
+
+ return (nodes.node_dir(path, nparent,ncontext,self.browse(cr,uid,did, context)), duri)
+
+
+ nid = self.search(cr,uid,[('parent_id','=',did),('name','=',duri[0]),('type','=','ressource')], context=context)
+ if nid:
+ if len(nid)>1:
+ print "Duplicate dir? p= %d, n=\"%s\"" %(did,duri[0])
+ path.append(duri[0])
+ duri = duri[1:]
+ did = nid[0]
+ return nodes.node_res_dir(path, nparent,ncontext,self.browse(cr,uid,did, context))
+
+ # Here, we must find the appropriate non-dir child..
+ # Chech for files:
+ fil_obj = self.pool.get('ir.attachment')
+ nid = fil_obj.search(cr,uid,[('parent_id','=',did),('name','=',duri[0])],context=context)
+ if nid:
+ if len(duri)>1:
+ # cannot treat child as a dir
+ return None
+ if len(nid)>1:
+ print "Duplicate file?",did,duri[0]
+ path.append(duri[0])
+ return nodes.node_file(path,nparent,ncontext,fil_obj.browse(cr,uid,nid[0],context))
+
+ print "nothing found:",did, duri
+ #still, nothing found
+ return None
+
+ def old_code():
+ if not uri:
+ return node_database(cr, uid, context=context)
+ turi = tuple(uri)
+ node = node_class(cr, uid, '/', False, context=context, type='database')
+ for path in uri[:]:
+ if path:
+ node = node.child(path)
+ if not node:
+ return False
+ oo = node.object and (node.object._name, node.object.id) or False
+ oo2 = node.object2 and (node.object2._name, node.object2.id) or False
+ return node
+
+ def ol_get_childs(self, cr, uid, uri, context={}):
+ node = self.get_object(cr, uid, uri, context)
+ if uri:
+ children = node.children()
+ else:
+ children= [node]
+ result = map(lambda node: node.path_get(), children)
+ return result
+
+ def copy(self, cr, uid, id, default=None, context=None):
+ if not default:
+ default ={}
+ name = self.read(cr, uid, [id])[0]['name']
+ default.update({'name': name+ " (copy)"})
+ return super(document_directory,self).copy(cr,uid,id,default,context)
+
+ def _check_duplication(self, cr, uid,vals,ids=[],op='create'):
+ name=vals.get('name',False)
+ parent_id=vals.get('parent_id',False)
+ ressource_parent_type_id=vals.get('ressource_parent_type_id',False)
+ ressource_id=vals.get('ressource_id',0)
+ if op=='write':
+ for directory in self.browse(cr,uid,ids):
+ if not name:
+ name=directory.name
+ if not parent_id:
+ parent_id=directory.parent_id and directory.parent_id.id or False
+ if not ressource_parent_type_id:
+ ressource_parent_type_id=directory.ressource_parent_type_id and directory.ressource_parent_type_id.id or False
+ if not ressource_id:
+ ressource_id=directory.ressource_id and directory.ressource_id or 0
+ res=self.search(cr,uid,[('id','<>',directory.id),('name','=',name),('parent_id','=',parent_id),('ressource_parent_type_id','=',ressource_parent_type_id),('ressource_id','=',ressource_id)])
+ if len(res):
+ return False
+ if op=='create':
+ res=self.search(cr,uid,[('name','=',name),('parent_id','=',parent_id),('ressource_parent_type_id','=',ressource_parent_type_id),('ressource_id','=',ressource_id)])
+ if len(res):
+ return False
+ return True
+ def write(self, cr, uid, ids, vals, context=None):
+ if not self._check_duplication(cr,uid,vals,ids,op='write'):
+ raise osv.except_osv(_('ValidateError'), _('Directory name must be unique!'))
+ return super(document_directory,self).write(cr,uid,ids,vals,context=context)
+
+ def create(self, cr, uid, vals, context=None):
+ if not self._check_duplication(cr,uid,vals):
+ raise osv.except_osv(_('ValidateError'), _('Directory name must be unique!'))
+ if vals.get('name',False) and (vals.get('name').find('/')+1 or vals.get('name').find('@')+1 or vals.get('name').find('$')+1 or vals.get('name').find('#')+1) :
+ raise osv.except_osv(_('ValidateError'), _('Directory name contains special characters!'))
+ return super(document_directory,self).create(cr, uid, vals, context)
+
+document_directory()
+
+class document_directory_dctx(osv.osv):
+ """ In order to evaluate dynamic folders, child items could have a limiting
+ domain expression. For that, their parents will export a context where useful
+ information will be passed on.
+ If you define sth like "s_id" = "this.id" at a folder iterating over sales, its
+ children could have a domain like [('sale_id', = ,dctx_s_id )]
+ This system should be used recursively, that is, parent dynamic context will be
+ appended to all children down the tree.
+ """
+ _name = 'document.directory.dctx'
+ _description = 'Directory dynamic context'
+ _columns = {
+ 'dir_id': fields.many2one('document.directory', 'Directory', required=True),
+ 'field': fields.char('Field', size=20, required=True, select=1, help="The name of the field. Note that the prefix \"dctx_\" will be prepended to what is typed here."),
+ 'expr': fields.char('Expression', size=64, required=True, help="A python expression used to evaluate the field.\n" + \
+ "You can use 'dir_id' for current dir, 'res_id', 'res_model' as a reference to the current record, in dynamic folders"),
+ }
+
+document_directory_dctx()
+
+
+class document_directory_node(osv.osv):
+ _inherit = 'process.node'
+ _columns = {
+ 'directory_id': fields.many2one('document.directory', 'Document directory', ondelete="set null"),
+ }
+document_directory_node()
diff --git a/addons/document/document_storage.py b/addons/document/document_storage.py
new file mode 100644
index 00000000000..d4096138800
--- /dev/null
+++ b/addons/document/document_storage.py
@@ -0,0 +1,281 @@
+# -*- encoding: utf-8 -*-
+##############################################################################
+#
+# OpenERP, Open Source Management Solution
+#
+# Copyright (C) P. Christeas, 2009, all rights reserved
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU 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 General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see .
+#
+##############################################################################
+
+from osv import osv, fields
+import os
+import tools
+import base64
+from tools.misc import ustr
+
+from osv.orm import except_orm
+
+import random
+import string
+import netsvc
+from content_index import cntIndex
+
+
+""" The algorithm of data storage
+
+We have to consider 3 cases of data /retrieval/:
+ Given (context,path) we need to access the file (aka. node).
+ given (directory, context), we need one of its children (for listings, views)
+ given (ir.attachment, context), we needs its data and metadata (node).
+
+For data /storage/ we have the cases:
+ Have (ir.attachment, context), we modify the file (save, update, rename etc).
+ Have (directory, context), we create a file.
+ Have (path, context), we create or modify a file.
+
+Note that in all above cases, we don't explicitly choose the storage media,
+but always require a context to be present.
+
+Note that a node will not always have a corresponding ir.attachment. Dynamic
+nodes, for once, won't. Their metadata will be computed by the parent storage
+media + directory.
+
+The algorithm says that in any of the above cases, our first goal is to locate
+the node for any combination of search criteria. It would be wise NOT to
+represent each node in the path (like node[/] + node[/dir1] + node[/dir1/dir2])
+but directly jump to the end node (like node[/dir1/dir2]) whenever possible.
+
+We also contain all the parenting loop code in one function. This is intentional,
+because one day this will be optimized in the db (Pg 8.4).
+
+
+"""
+
+def random_name():
+ random.seed()
+ d = [random.choice(string.ascii_letters) for x in xrange(10) ]
+ name = "".join(d)
+ return name
+
+INVALID_CHARS={'*':str(hash('*')), '|':str(hash('|')) , "\\":str(hash("\\")), '/':'__', ':':str(hash(':')), '"':str(hash('"')), '<':str(hash('<')) , '>':str(hash('>')) , '?':str(hash('?'))}
+
+
+def create_directory(path):
+ dir_name = random_name()
+ path = os.path.join(path,dir_name)
+ os.makedirs(path)
+ return dir_name
+
+
+class document_storage(osv.osv):
+ """ The primary object for data storage.
+ Each instance of this object is a storage media, in which our application
+ can store contents. The object here controls the behaviour of the storage
+ media.
+ The referring document.directory-ies will control the placement of data
+ into the storage.
+
+ It is a bad idea to have multiple document.storage objects pointing to
+ the same tree of filesystem storage.
+ """
+ _name = 'document.storage'
+ _description = 'Document storage media'
+ _columns = {
+ 'name': fields.char('Name', size=64, required=True, select=1),
+ 'write_date': fields.datetime('Date Modified', readonly=True),
+ 'write_uid': fields.many2one('res.users', 'Last Modification User', readonly=True),
+ 'create_date': fields.datetime('Date Created', readonly=True),
+ 'create_uid': fields.many2one('res.users', 'Creator', readonly=True),
+ 'user_id': fields.many2one('res.users', 'Owner'),
+ 'group_ids': fields.many2many('res.groups', 'document_directory_group_rel', 'item_id', 'group_id', 'Groups'),
+ 'dir_ids': fields.one2many('document.directory', 'parent_id', 'Directories'),
+ 'type': fields.selection([('db','Database'),('filestore','Internal File storage'),
+ ('realstore','External file storage'), ('virtual','Virtual storage')], 'Type', required=True),
+ 'path': fields.char('Path',size=250,select=1,help="For file storage, the root path of the storage"),
+ 'online': fields.boolean('Online',help="If not checked, media is currently offline and its contents not available", required=True),
+ 'readonly': fields.boolean('Read Only', help="If set, media is for reading only"),
+ }
+
+ def _get_rootpath(self,cr,uid,context=None):
+ from tools import config
+ return os.path.join(tools.config['root_path'], 'filestore', cr.dbname)
+
+ _defaults = {
+ 'user_id': lambda self,cr,uid,ctx: uid,
+ 'online': lambda *args: True,
+ 'readonly': lambda *args: False,
+ # Note: the defaults below should only be used ONCE for the default
+ # storage media. All other times, we should create different paths at least.
+ 'type': lambda *args: 'filestore',
+ 'path': _get_rootpath,
+ }
+ _sql_constraints = [
+ # SQL note: a path = NULL doesn't have to be unique.
+ ('path_uniq', 'UNIQUE(type,path)', "The storage path must be unique!")
+ ]
+
+ def get_data(self, cr,uid, id, file_node,context = None, fil_obj = None):
+ """ retrieve the contents of some file_node having storage_id = id
+ optionally, fil_obj could point to the browse object of the file
+ (ir.attachment)
+ """
+ if not context:
+ context = {}
+ boo = self.browse(cr,uid,id,context)
+ if fil_obj:
+ ira = fil_obj
+ else:
+ ira = self.pool.get('ir.attachment').browse(cr,uid, file_node.file_id, context=context)
+ return self.__get_data_3(cr,uid,boo, ira, context)
+
+ def __get_data_3(self,cr,uid, boo, ira, context):
+ if not boo.online:
+ raise RuntimeError('media offline')
+ if boo.type == 'filestore':
+ if not ira.store_fname:
+ # On a migrated db, some files may have the wrong storage type
+ # try to fix their directory.
+ if ira.file_size:
+ netsvc.Logger().notifyChannel('document',netsvc.LOG_WARNING,"ir.attachment #%d does not have a filename, but is at filestore, fix it!" %ira.id)
+ return None
+ fpath = os.path.join(boo.path,ira.store_fname)
+ print "Trying to read \"%s\".."% fpath
+ return file(fpath,'rb').read()
+ elif boo.type == 'db':
+ # TODO: we need a better api for large files
+ if ira.db_datas:
+ out=base64.decodestring(ira.db_datas)
+ else:
+ out=''
+ return out
+ elif boo.type == 'realstore':
+ # fpath = os.path.join(boo.path,
+ return None
+ else:
+ raise TypeError("No %s storage" % boo.type)
+
+ def set_data(self, cr,uid, id, file_node, data, context = None, fil_obj = None):
+ """ store the data.
+ This function MUST be used from an ir.attachment. It wouldn't make sense
+ to store things persistently for other types (dynamic).
+ """
+ if not context:
+ context = {}
+ boo = self.browse(cr,uid,id,context)
+ logger = netsvc.Logger()
+ if fil_obj:
+ ira = fil_obj
+ else:
+ ira = self.pool.get('ir.attachment').browse(cr,uid, file_node.file_id, context=context)
+
+ if not boo.online:
+ raise RuntimeError('media offline')
+ logger.notifyChannel('document',netsvc.LOG_DEBUG,"Store data for ir.attachment #%d" %ira.id)
+ store_fname = None
+ fname = None
+ if boo.type == 'filestore':
+ path = boo.path
+ try:
+ flag = None
+ # This can be improved
+ for dirs in os.listdir(path):
+ if os.path.isdir(os.path.join(path,dirs)) and len(os.listdir(os.path.join(path,dirs)))<4000:
+ flag = dirs
+ break
+ flag = flag or create_directory(path)
+ filename = random_name()
+ fname = os.path.join(path, flag, filename)
+ fp = file(fname,'wb')
+ fp.write(data)
+ fp.close()
+ logger.notifyChannel('document',netsvc.LOG_DEBUG,"Saved data to %s" % fname)
+ filesize = len(data) # os.stat(fname).st_size
+ store_fname = os.path.join(flag,filename)
+
+ # TODO Here, an old file would be left hanging.
+
+ except Exception,e :
+ netsvc.Logger().notifyChannel('document',netsvc.LOG_WARNING,"Couldn't save data: %s" %str(e))
+ raise except_orm(_('Error!'), str(e))
+ elif boo.type == 'db':
+ filesize = len(data)
+ # will that work for huge data? TODO
+ out=base64.encodestring(data)
+ cr.execute('UPDATE ir_attachment SET db_datas = %s WHERE id = %s',
+ ( out, file_node.file_id ))
+ else:
+ raise TypeError("No %s storage" % boo.type)
+
+ # 2nd phase: store the metadata
+ try:
+ icont = ''
+ mime = ira.file_type
+ try:
+ mime,icont = cntIndex.doIndex(data, ira.datas_fname,
+ ira.file_type or None,fname)
+ except Exception,e:
+ logger.notifyChannel('document', netsvc.LOG_DEBUG, 'Cannot index file: %s' % str(e))
+ pass
+
+ # a hack: /assume/ that the calling write operation will not try
+ # to write the fname and size, and update them in the db concurrently.
+ # We cannot use a write() here, because we are already in one.
+ cr.execute('UPDATE ir_attachment SET store_fname = %s, file_size = %s, index_content = %s, file_type = %s WHERE id = %s',
+ (store_fname, filesize, ustr(icont), mime, file_node.file_id ))
+ file_node.content_length = filesize
+ file_node.content_type = mime
+ return True
+ except Exception,e :
+ netsvc.Logger().notifyChannel('document',netsvc.LOG_WARNING,"Couldn't save data: %s" %str(e))
+ # should we really rollback once we have written the actual data?
+ # at the db case (only), that rollback would be safe
+ raise except_orm(_('Error at doc write!'), str(e))
+
+ def prepare_unlink(self,cr,uid,storage_bo, fil_bo):
+ """ Before we unlink a file (fil_boo), prepare the list of real
+ files that have to be removed, too. """
+
+ if not storage_bo.online:
+ raise RuntimeError('media offline')
+
+ if storage_bo.type == 'filestore':
+ fname = fil_bo.store_fname
+ if not fname:
+ return None
+ path = storage_bo.path
+ return ( storage_bo.id, 'file', os.path.join(path,fname))
+ elif storage_bo.type == 'db':
+ return None
+ else:
+ raise TypeError("No %s storage" % boo.type)
+
+ def do_unlink(self, cr,uid,unres):
+ for id, ktype, fname in unres:
+ if ktype == 'file':
+ try:
+ os.unlink(fname)
+ except Exception,e:
+ netsvc.Logger().notifyChannel('document',netsvc.LOG_WARNING,"Could not remove file %s, please remove manually." % fname)
+ else:
+ netsvc.Logger().notifyChannel('document',netsvc.LOG_WARNING,"Unknown unlink key %s" % ktype)
+
+ return True
+
+
+document_storage()
+
+
+#eof
diff --git a/addons/document/document_view.xml b/addons/document/document_view.xml
index 6e570bb406a..babc5af33b3 100644
--- a/addons/document/document_view.xml
+++ b/addons/document/document_view.xml
@@ -3,6 +3,48 @@
+
+ document.storage
+ document.storage
+ form
+
+
+
+
+
+
+ document.storage
+ document.storage
+ tree
+
+
+
+
+
+
+
+
+
+
+
+ ir.actions.act_window
+ document.storage
+ form
+ tree,form
+
+
+
document.directory
document.directory
@@ -12,6 +54,7 @@
+
@@ -20,14 +63,17 @@
+
-
-
+
+
+
-
+
+
+
+
+
+
+
+
+
+
+
@@ -151,7 +210,7 @@
-
+
@@ -243,86 +302,45 @@
-
-
- Auto Configure Directory
- document.configuration.wizard
- form
-
-
-
-
+
+ Auto Configure Directory
+ document.configuration.wizard
+ form
+
+
+
+
-
- Auto Configure Directory
- ir.actions.act_window
- document.configuration.wizard
- form
- form
- new
-
+
+ Auto Configure Directory
+ ir.actions.act_window
+ document.configuration.wizard
+ form
+ form
+ new
+
-
- Auto Configure Directory
- This wizard will configure the URL of the server of the document management system. These ftp server address and port will be stored in the configuration file.
-
-
+
+ Auto Configure Directory
+ This wizard will configure the URL of the server of the document management system.
+
+
-
-
-
- Configure FTP server address
- document.configuration.ftp_server.wizard
- form
-
-
-
-
-
-
- Configure FTP server address
- ir.actions.act_window
- document.configuration.ftp_server.wizard
- form
- form
- new
-
-
-
+
diff --git a/addons/document/ftpserver/__init__.py b/addons/document/ftpserver/__init__.py
index 47615a442a6..3dc1f1b1202 100644
--- a/addons/document/ftpserver/__init__.py
+++ b/addons/document/ftpserver/__init__.py
@@ -28,7 +28,7 @@ import netsvc
from tools import config
from tools.misc import detect_ip_addr
HOST = ''
-PORT = int(config.get('ftp_server_port', 8021))
+PORT = 8021
PASSIVE_PORTS = None
pps = config.get('ftp_server_passive_ports', '').split(':')
if len(pps) == 2:
@@ -53,8 +53,7 @@ class ftp_server(threading.Thread):
ftpserver.logerror = lambda msg: self.log(netsvc.LOG_ERROR, msg)
HOST = config.get('ftp_server_address', detect_ip_addr())
- PORT = int(config.get('ftp_server_port', '8021'))
-
+ PORT = int(config.get('ftp_server_port', '8021'))
address = (HOST, PORT)
ftpd = ftpserver.FTPServer(address, ftpserver.FTPHandler)
ftpd.serve_forever()
diff --git a/addons/document/ftpserver/abstracted_fs.py b/addons/document/ftpserver/abstracted_fs.py
index 21b38d7dc4f..d795110d4ea 100644
--- a/addons/document/ftpserver/abstracted_fs.py
+++ b/addons/document/ftpserver/abstracted_fs.py
@@ -1,24 +1,4 @@
-# -*- coding: utf-8 -*-
-##############################################################################
-#
-# OpenERP, Open Source Management Solution
-# Copyright (C) 2004-2009 Tiny SPRL ().
-#
-# 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 .
-#
-##############################################################################
-
+# -*- encoding: utf-8 -*-
import os
import time
from tarfile import filemode
@@ -141,8 +121,8 @@ class abstracted_fs:
# Ok
def db_list(self):
#return pooler.pool_dic.keys()
- s = netsvc.LocalService('db')
- result = s.list()
+ s = netsvc.ExportService.getService('db')
+ result = s.exp_list()
self.db_name_list = []
for db_name in result:
db, cr = None, None
@@ -222,13 +202,18 @@ class abstracted_fs:
# Ok
def fs2ftp(self, node):
- res='/'
+ res = '/'
if node:
- res=os.path.normpath(node.path)
+ path = node.path
+ print '**********', path
+ if isinstance(path,list):
+ path = '/'.join(path)
+ res = os.path.normpath(path)
res = res.replace("\\", "/")
while res[:2] == '//':
res = res[1:]
- res='/' + node.cr.dbname + '/' + _to_decode(res)
+ if len(node.path):
+ res = '/' + node.path[0] + '/' + _to_decode(res)
#res = node and ('/' + node.cr.dbname + '/' + _to_decode(self.ftpnorm(node.path))) or '/'
return res
diff --git a/addons/document/ftpserver/authorizer.py b/addons/document/ftpserver/authorizer.py
index afa36680009..5a64ca8f7a0 100644
--- a/addons/document/ftpserver/authorizer.py
+++ b/addons/document/ftpserver/authorizer.py
@@ -1,26 +1,4 @@
-# -*- coding: utf-8 -*-
-##############################################################################
-#
-# OpenERP, Open Source Management Solution
-# Copyright (C) 2004-2009 Tiny SPRL ().
-#
-# 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 .
-#
-##############################################################################
-
-import pooler
-from service import security
+# -*- encoding: utf-8 -*-
class authorizer:
read_perms = "elr"
@@ -69,7 +47,7 @@ class authorizer:
paths = path.split('/')
if not len(paths)>2:
return True
- db_name = paths[1]
+ db_name = paths[1]
db,pool = pooler.get_db_and_pool(db_name)
res = security.login(db_name, username, self.password)
return bool(res)
diff --git a/addons/document/ftpserver/ftpserver.py b/addons/document/ftpserver/ftpserver.py
index 3414fa78bbd..13c44287303 100644
--- a/addons/document/ftpserver/ftpserver.py
+++ b/addons/document/ftpserver/ftpserver.py
@@ -1,10 +1,12 @@
-# -*- coding: utf-8 -*-
+#!/usr/bin/env python
+# -*- encoding: utf-8 -*-
# ftpserver.py
#
# pyftpdlib is released under the MIT license, reproduced below:
# ======================================================================
# Copyright (C) 2007 Giampaolo Rodola'
# Hacked by Fabien Pinckaers (C) 2008
+#
# All Rights Reserved
#
# Permission to use, copy, modify, and distribute this software and
@@ -56,9 +58,6 @@ the backend functionality for the FTPd:
providing a high level, cross-platform interface compatible
with both Windows and UNIX style filesystems.
- [CallLater] - calls a function at a later time whithin the polling
- loop asynchronously.
-
[AuthorizerError] - base class for authorizers exceptions.
@@ -117,11 +116,11 @@ import traceback
import errno
import time
import glob
+import fnmatch
import tempfile
import warnings
import random
import stat
-import heapq
from tarfile import filemode
try:
@@ -131,80 +130,67 @@ except ImportError:
pwd = grp = None
+LOG_ACTIVE = True
+
__all__ = ['proto_cmds', 'Error', 'log', 'logline', 'logerror', 'DummyAuthorizer',
- 'AuthorizerError', 'FTPHandler', 'FTPServer', 'PassiveDTP',
- 'ActiveDTP', 'DTPHandler', 'FileProducer', 'BufferedIteratorProducer',
- 'AbstractedFS', 'CallLater']
+ 'FTPHandler', 'FTPServer', 'PassiveDTP', 'ActiveDTP', 'DTPHandler',
+ 'FileProducer', 'IteratorProducer', 'BufferedIteratorProducer',
+ 'AbstractedFS',]
__pname__ = 'Python FTP server library (pyftpdlib)'
-__ver__ = '0.5.1'
-__date__ = '2009-01-21'
+__ver__ = '0.4.0'
+__date__ = '2008-05-16'
__author__ = "Giampaolo Rodola' "
__web__ = 'http://code.google.com/p/pyftpdlib/'
proto_cmds = {
- # cmd : (perm, auth, arg, path, help)
- 'ABOR': (None, True, False, False, 'Syntax: ABOR (abort transfer).'),
- 'ALLO': (None, True, True, False, 'Syntax: ALLO bytes (noop; allocate storage).'),
- 'APPE': ('a', True, True, True, 'Syntax: APPE file-name (append data to an existent file).'),
- 'CDUP': ('e', True, False, True, 'Syntax: CDUP (go to parent directory).'),
- 'CWD' : ('e', True, None, True, 'Syntax: CWD [ dir-name] (change current working directory).'),
- 'DELE': ('d', True, True, True, 'Syntax: DELE file-name (delete file).'),
- 'EPRT': (None, True, True, False, 'Syntax: EPRT |proto|ip|port| (set server in extended active mode).'),
- 'EPSV': (None, True, None, False, 'Syntax: EPSV [ proto/"ALL"] (set server in extended passive mode).'),
- 'FEAT': (None, False, False, False, 'Syntax: FEAT (list all new features supported).'),
- 'HELP': (None, False, None, False, 'Syntax: HELP [ cmd] (show help).'),
- 'LIST': ('l', True, None, True, 'Syntax: LIST [ path-name] (list files).'),
- 'MDTM': (None, True, True, True, 'Syntax: MDTM [ file-name] (get last modification time).'),
- 'MLSD': ('l', True, None, True, 'Syntax: MLSD [ dir-name] (list files in a machine-processable form)'),
- 'MLST': (None, True, None, True, 'Syntax: MLST [ path-name] (show a path in a machine-processable form)'),
- 'MODE': (None, True, True, False, 'Syntax: MODE mode (noop; set data transfer mode).'),
- 'MKD' : ('m', True, True, True, 'Syntax: MDK dir-name (create directory).'),
- 'NLST': ('l', True, None, True, 'Syntax: NLST [ path-name] (list files in a compact form).'),
- 'NOOP': (None, False, False, False, 'Syntax: NOOP (just do nothing).'),
- 'OPTS': (None, True, True, False, 'Syntax: OPTS ftp-command [ option] (specify options for FTP commands)'),
- 'PASS': (None, False, True, False, 'Syntax: PASS user-name (set user password).'),
- 'PASV': (None, True, False, False, 'Syntax: PASV (set server in passive mode).'),
- 'PORT': (None, True, True, False, 'Syntax: PORT h1,h2,h3,h4,p1,p2 (set server in active mode).'),
- 'PWD' : (None, True, False, False, 'Syntax: PWD (get current working directory).'),
- 'QUIT': (None, False, False, False, 'Syntax: QUIT (quit current session).'),
- 'REIN': (None, True, False, False, 'Syntax: REIN (reinitialize / flush account).'),
- 'REST': (None, True, True, False, 'Syntax: REST marker (restart file position).'),
- 'RETR': ('r', True, True, True, 'Syntax: RETR file-name (retrieve a file).'),
- 'RMD' : ('d', True, True, True, 'Syntax: RMD dir-name (remove directory).'),
- 'RNFR': ('f', True, True, True, 'Syntax: RNFR file-name (file renaming (source name)).'),
- 'RNTO': (None, True, True, True, 'Syntax: RNTO file-name (file renaming (destination name)).'),
- 'SITE': (None, False, True, False, 'Syntax: SITE site-command (execute the specified SITE command).'),
- 'SITE HELP' : (None, False, None, False, 'Syntax: SITE HELP [ site-command] (show SITE command help).'),
- 'SIZE': (None, True, True, True, 'Syntax: HELP file-name (get file size).'),
- 'STAT': ('l', False, None, True, 'Syntax: STAT [ path name] (status information [list files]).'),
- 'STOR': ('w', True, True, True, 'Syntax: STOR file-name (store a file).'),
- 'STOU': ('w', True, None, True, 'Syntax: STOU [ file-name] (store a file with a unique name).'),
- 'STRU': (None, True, True, False, 'Syntax: STRU type (noop; set file structure).'),
- 'SYST': (None, False, False, False, 'Syntax: SYST (get operating system type).'),
- 'TYPE': (None, True, True, False, 'Syntax: TYPE [A | I] (set transfer type).'),
- 'USER': (None, False, True, False, 'Syntax: USER user-name (set username).'),
- 'XCUP': ('e', True, False, True, 'Syntax: XCUP (obsolete; go to parent directory).'),
- 'XCWD': ('e', True, None, True, 'Syntax: XCWD [ dir-name] (obsolete; change current directory).'),
- 'XMKD': ('m', True, True, True, 'Syntax: XMDK dir-name (obsolete; create directory).'),
- 'XPWD': (None, True, False, False, 'Syntax: XPWD (obsolete; get current dir).'),
- 'XRMD': ('d', True, True, True, 'Syntax: XRMD dir-name (obsolete; remove directory).'),
+ 'ABOR': 'Syntax: ABOR (abort transfer).',
+ 'ALLO': 'Syntax: ALLO bytes (obsolete; allocate storage).',
+ 'APPE': 'Syntax: APPE file-name (append data to an existent file).',
+ 'CDUP': 'Syntax: CDUP (go to parent directory).',
+ 'CWD' : 'Syntax: CWD dir-name (change current working directory).',
+ 'DELE': 'Syntax: DELE file-name (delete file).',
+ 'EPRT': 'Syntax: EPRT |proto|ip|port| (set server in extended active mode).',
+ 'EPSV': 'Syntax: EPSV [ proto/"ALL"] (set server in extended passive mode).',
+ 'FEAT': 'Syntax: FEAT (list all new features supported).',
+ 'HELP': 'Syntax: HELP [ cmd] (show help).',
+ 'LIST': 'Syntax: LIST [ path-name] (list files).',
+ 'MDTM': 'Syntax: MDTM file-name (get last modification time).',
+ 'MLSD': 'Syntax: MLSD [ dir-name] (list files in a machine-processable form)',
+ 'MLST': 'Syntax: MLST [ path-name] (show a path in a machine-processable form)',
+ 'MODE': 'Syntax: MODE mode (obsolete; set data transfer mode).',
+ 'MKD' : 'Syntax: MDK dir-name (create directory).',
+ 'NLST': 'Syntax: NLST [ path-name] (list files in a compact form).',
+ 'NOOP': 'Syntax: NOOP (just do nothing).',
+ 'OPTS': 'Syntax: OPTS ftp-command [ option] (specify options for FTP commands)',
+ 'PASS': 'Syntax: PASS user-name (set user password).',
+ 'PASV': 'Syntax: PASV (set server in passive mode).',
+ 'PORT': 'Syntax: PORT h1,h2,h3,h4,p1,p2 (set server in active mode).',
+ 'PWD' : 'Syntax: PWD (get current working directory).',
+ 'QUIT': 'Syntax: QUIT (quit current session).',
+ 'REIN': 'Syntax: REIN (reinitialize / flush account).',
+ 'REST': 'Syntax: REST marker (restart file position).',
+ 'RETR': 'Syntax: RETR file-name (retrieve a file).',
+ 'RMD' : 'Syntax: RMD dir-name (remove directory).',
+ 'RNFR': 'Syntax: RNFR file-name (file renaming (source name)).',
+ 'RNTO': 'Syntax: RNTO file-name (file renaming (destination name)).',
+ 'SIZE': 'Syntax: HELP file-name (get file size).',
+ 'STAT': 'Syntax: STAT [ path name] (status information [list files]).',
+ 'STOR': 'Syntax: STOR file-name (store a file).',
+ 'STOU': 'Syntax: STOU [ file-name] (store a file with a unique name).',
+ 'STRU': 'Syntax: STRU type (obsolete; set file structure).',
+ 'SYST': 'Syntax: SYST (get operating system type).',
+ 'TYPE': 'Syntax: TYPE [A | I] (set transfer type).',
+ 'USER': 'Syntax: USER user-name (set username).',
+ 'XCUP': 'Syntax: XCUP (obsolete; go to parent directory).',
+ 'XCWD': 'Syntax: XCWD dir-name (obsolete; change current directory).',
+ 'XMKD': 'Syntax: XMDK dir-name (obsolete; create directory).',
+ 'XPWD': 'Syntax: XPWD (obsolete; get current dir).',
+ 'XRMD': 'Syntax: XRMD dir-name (obsolete; remove directory).',
}
-class _CommandProperty:
- def __init__(self, perm, auth_needed, arg_needed, check_path, help):
- self.perm = perm
- self.auth_needed = auth_needed
- self.arg_needed = arg_needed
- self.check_path = check_path
- self.help = help
-
-for cmd, properties in proto_cmds.iteritems():
- proto_cmds[cmd] = _CommandProperty(*properties)
-del cmd, properties
-
# hack around format_exc function of traceback module to grant
# backward compatibility with python < 2.4
@@ -235,97 +221,6 @@ def _strerror(err):
else:
return err.strerror
-# the heap used for the scheduled tasks
-_tasks = []
-
-def _scheduler():
- """Run the scheduled functions due to expire soonest (if any)."""
- now = time.time()
- while _tasks and now >= _tasks[0].timeout:
- call = heapq.heappop(_tasks)
- if call.repush:
- heapq.heappush(_tasks, call)
- call.repush = False
- continue
- try:
- call.call()
- finally:
- if not call.cancelled:
- call.cancel()
-
-
-class CallLater:
- """Calls a function at a later time.
-
- It can be used to asynchronously schedule a call within the polling
- loop without blocking it. The instance returned is an object that
- can be used to cancel or reschedule the call.
- """
-
- def __init__(self, seconds, target, *args, **kwargs):
- """
- - (int) seconds: the number of seconds to wait
- - (obj) target: the callable object to call later
- - args: the arguments to call it with
- - kwargs: the keyword arguments to call it with
- """
- assert callable(target), "%s is not callable" %target
- assert sys.maxint >= seconds >= 0, "%s is not greater than or equal " \
- "to 0 seconds" % (seconds)
- self.__delay = seconds
- self.__target = target
- self.__args = args
- self.__kwargs = kwargs
- # seconds from the epoch at which to call the function
- self.timeout = time.time() + self.__delay
- self.repush = False
- self.cancelled = False
- heapq.heappush(_tasks, self)
-
- def __le__(self, other):
- return self.timeout <= other.timeout
-
- def call(self):
- """Call this scheduled function."""
- assert not self.cancelled, "Already cancelled"
- self.__target(*self.__args, **self.__kwargs)
-
- def reset(self):
- """Reschedule this call resetting the current countdown."""
- assert not self.cancelled, "Already cancelled"
- self.timeout = time.time() + self.__delay
- self.repush = True
-
- def delay(self, seconds):
- """Reschedule this call for a later time."""
- assert not self.cancelled, "Already cancelled."
- assert sys.maxint >= seconds >= 0, "%s is not greater than or equal " \
- "to 0 seconds" %(seconds)
- self.__delay = seconds
- newtime = time.time() + self.__delay
- if newtime > self.timeout:
- self.timeout = newtime
- self.repush = True
- else:
- # XXX - slow, can be improved
- self.timeout = newtime
- heapq.heapify(_tasks)
-
- def cancel(self):
- """Unschedule this call."""
- assert not self.cancelled, "Already cancelled"
- self.cancelled = True
- del self.__target, self.__args, self.__kwargs
- if self in _tasks:
- pos = _tasks.index(self)
- if pos == 0:
- heapq.heappop(_tasks)
- elif pos == len(_tasks) - 1:
- _tasks.pop(pos)
- else:
- _tasks[pos] = _tasks.pop()
- heapq._siftup(_tasks, pos)
-
# --- library defined exceptions
@@ -340,11 +235,13 @@ class AuthorizerError(Error):
def log(msg):
"""Log messages intended for the end user."""
- print msg
+ if LOG_ACTIVE:
+ print msg
def logline(msg):
"""Log commands and responses passing through the command channel."""
- print msg
+ if LOG_ACTIVE:
+ print msg
def logerror(msg):
"""Log traceback outputs occurring in case of errors."""
@@ -403,14 +300,20 @@ class DummyAuthorizer:
"""
if self.has_user(username):
raise AuthorizerError('User "%s" already exists' %username)
+ homedir = os.path.realpath(homedir)
if not os.path.isdir(homedir):
raise AuthorizerError('No such directory: "%s"' %homedir)
- homedir = os.path.realpath(homedir)
- self._check_permissions(username, perm)
+ for p in perm:
+ if p not in 'elradfmw':
+ raise AuthorizerError('No such permission "%s"' %p)
+ for p in perm:
+ if (p in self.write_perms) and (username == 'anonymous'):
+ warnings.warn("write permissions assigned to anonymous user.",
+ RuntimeWarning)
+ break
dic = {'pwd': str(password),
'home': homedir,
'perm': perm,
- 'operms': {},
'msg_login': str(msg_login),
'msg_quit': str(msg_quit)
}
@@ -438,26 +341,9 @@ class DummyAuthorizer:
"""Remove a user from the virtual users table."""
del self.user_table[username]
- def override_perm(self, username, directory, perm, recursive=False):
- """Override permissions for a given directory."""
- self._check_permissions(username, perm)
- if not os.path.isdir(directory):
- raise AuthorizerError('No such directory: "%s"' %directory)
- directory = os.path.normcase(os.path.realpath(directory))
- home = os.path.normcase(self.get_home_dir(username))
- if directory == home:
- raise AuthorizerError("Can't override home directory permissions")
- if not self._issubpath(directory, home):
- raise AuthorizerError("Path escapes user home directory")
- self.user_table[username]['operms'][directory] = perm, recursive
-
def validate_authentication(self, username, password):
"""Return True if the supplied username and password match the
stored credentials."""
- if not self.has_user(username):
- return False
- if username == 'anonymous':
- return True
return self.user_table[username]['pwd'] == password
def impersonate_user(self, username, password):
@@ -489,19 +375,6 @@ class DummyAuthorizer:
Expected perm argument is one of the following letters:
"elradfmw".
"""
- if path is None:
- return perm in self.user_table[username]['perm']
-
- path = os.path.normcase(path)
- for dir in self.user_table[username]['operms'].keys():
- operm, recursive = self.user_table[username]['operms'][dir]
- if self._issubpath(path, dir):
- if recursive:
- return perm in operm
- if (path == dir) or (os.path.dirname(path) == dir \
- and not os.path.isdir(path)):
- return perm in operm
-
return perm in self.user_table[username]['perm']
def get_perms(self, username):
@@ -520,23 +393,6 @@ class DummyAuthorizer:
"""Return the user's quitting message."""
return self.user_table[username]['msg_quit']
- def _check_permissions(self, username, perm):
- warned = 0
- for p in perm:
- if p not in self.read_perms + self.write_perms:
- raise AuthorizerError('No such permission "%s"' %p)
- if (username == 'anonymous') and (p in self.write_perms) and not warned:
- warnings.warn("Write permissions assigned to anonymous user.",
- RuntimeWarning)
- warned = 1
-
- def _issubpath(self, a, b):
- """Return True if a is a sub-path of b or if the paths are equal."""
- p1 = a.rstrip(os.sep).split(os.sep)
- p2 = b.rstrip(os.sep).split(os.sep)
- return p1[:len(p2)] == p2
-
-
# --- DTP classes
@@ -544,11 +400,7 @@ class PassiveDTP(asyncore.dispatcher):
"""This class is an asyncore.disptacher subclass. It creates a
socket listening on a local port, dispatching the resultant
connection to DTPHandler.
-
- - (int) timeout: the timeout for a remote client to establish
- connection with the listening socket. Defaults to 30 seconds.
"""
- timeout = 30
def __init__(self, cmd_channel, extmode=False):
"""Initialize the passive data server.
@@ -558,17 +410,13 @@ class PassiveDTP(asyncore.dispatcher):
"""
asyncore.dispatcher.__init__(self)
self.cmd_channel = cmd_channel
- if self.timeout:
- self.idler = CallLater(self.timeout, self.handle_timeout)
- else:
- self.idler = None
ip = self.cmd_channel.getsockname()[0]
self.create_socket(self.cmd_channel.af, socket.SOCK_STREAM)
- if self.cmd_channel.passive_ports is None:
- # By using 0 as port number value we let kernel choose a
- # free unprivileged random port.
+ if not self.cmd_channel.passive_ports:
+ # By using 0 as port number value we let kernel choose a free
+ # unprivileged random port.
self.bind((ip, 0))
else:
ports = list(self.cmd_channel.passive_ports)
@@ -613,12 +461,8 @@ class PassiveDTP(asyncore.dispatcher):
def handle_accept(self):
"""Called when remote client initiates a connection."""
- try:
- sock, addr = self.accept()
- except TypeError:
- # for some reason sometimes accept() returns None instead
- # of a socket
- return
+ sock, addr = self.accept()
+
# Check the origin of data connection. If not expressively
# configured we drop the incoming data connection if remote
# IP address does not match the client's IP address.
@@ -648,10 +492,6 @@ class PassiveDTP(asyncore.dispatcher):
self.cmd_channel.data_channel = handler
self.cmd_channel.on_dtp_connection()
- def handle_timeout(self):
- self.cmd_channel.respond("421 Passive data channel timed out.")
- self.close()
-
def writable(self):
return 0
@@ -664,21 +504,16 @@ class PassiveDTP(asyncore.dispatcher):
logerror(traceback.format_exc())
self.close()
- def close(self):
- if self.idler is not None and not self.idler.cancelled:
- self.idler.cancel()
- asyncore.dispatcher.close(self)
+ def handle_close(self):
+ """Called on closing the data connection."""
+ self.close()
class ActiveDTP(asyncore.dispatcher):
"""This class is an asyncore.disptacher subclass. It creates a
socket resulting from the connection to a remote user-port,
dispatching it to DTPHandler.
-
- - (int) timeout: the timeout for us to establish connection with
- the client's listening data socket.
"""
- timeout = 30
def __init__(self, ip, port, cmd_channel):
"""Initialize the active data channel attemping to connect
@@ -690,10 +525,6 @@ class ActiveDTP(asyncore.dispatcher):
"""
asyncore.dispatcher.__init__(self)
self.cmd_channel = cmd_channel
- if self.timeout:
- self.idler = CallLater(self.timeout, self.handle_timeout)
- else:
- self.idler = None
self.create_socket(self.cmd_channel.af, socket.SOCK_STREAM)
try:
self.connect((ip, port))
@@ -703,19 +534,11 @@ class ActiveDTP(asyncore.dispatcher):
# --- connection / overridden
- # NOOP, overridden to prevent unhandled read/write event
- # messages to be printed on Python < 2.6
-
def handle_write(self):
- pass
-
- def handle_read(self):
- pass
+ """NOOP, must be overridden to prevent unhandled write event."""
def handle_connect(self):
"""Called when connection is established."""
- if self.idler is not None and not self.idler.cancelled:
- self.idler.cancel()
self.cmd_channel.respond('200 Active data connection established.')
# delegate such connection to DTP handler
handler = self.cmd_channel.dtp_handler(self.socket, self.cmd_channel)
@@ -723,10 +546,6 @@ class ActiveDTP(asyncore.dispatcher):
self.cmd_channel.on_dtp_connection()
#self.close() # <-- (done automatically)
- def handle_timeout(self):
- self.cmd_channel.respond("421 Active data channel timed out.")
- self.close()
-
def handle_expt(self):
self.cmd_channel.respond("425 Can't connect to specified address.")
self.close()
@@ -744,11 +563,6 @@ class ActiveDTP(asyncore.dispatcher):
self.cmd_channel.respond("425 Can't connect to specified address.")
self.close()
- def close(self):
- if self.idler is not None and not self.idler.cancelled:
- self.idler.cancel()
- asyncore.dispatcher.close(self)
-
try:
from collections import deque
@@ -764,16 +578,15 @@ class DTPHandler(asyncore.dispatcher):
RFC-959) managing data-transfer operations involving sending
and receiving data.
- Class attributes:
+ Instance attributes defined in this class, initialized when
+ channel is opened:
- - (int) timeout: the timeout which roughly is the maximum time we
- permit data transfers to stall for with no progress. If the
- timeout triggers, the remote client will be kicked off
- (defaults 300).
-
- - (int) ac_in_buffer_size: incoming data buffer size (defaults 65536)
-
- - (int) ac_out_buffer_size: outgoing data buffer size (defaults 65536)
+ - (instance) cmd_channel: the command channel class instance.
+ - (file) file_obj: the file transferred (if any).
+ - (bool) receive: True if channel is used for receiving data.
+ - (bool) transfer_finished: True if transfer completed successfully.
+ - (int) tot_bytes_sent: the total bytes sent.
+ - (int) tot_bytes_received: the total bytes received.
DTPHandler implementation note:
@@ -795,9 +608,8 @@ class DTPHandler(asyncore.dispatcher):
available we use a list instead.
"""
- timeout = 300
- ac_in_buffer_size = 65536
- ac_out_buffer_size = 65536
+ ac_in_buffer_size = 8192
+ ac_out_buffer_size = 8192
def __init__(self, sock_obj, cmd_channel):
"""Initialize the command channel.
@@ -819,12 +631,6 @@ class DTPHandler(asyncore.dispatcher):
self.tot_bytes_sent = 0
self.tot_bytes_received = 0
self.data_wrapper = lambda x: x
- self._lastdata = 0
- self._closed = False
- if self.timeout:
- self.idler = CallLater(self.timeout, self.handle_timeout)
- else:
- self.idler = None
# --- utility methods
@@ -836,10 +642,7 @@ class DTPHandler(asyncore.dispatcher):
- (str) type: current transfer type, 'a' (ASCII) or 'i' (binary).
"""
if type == 'a':
- if os.linesep == '\r\n':
- self.data_wrapper = lambda x: x
- else:
- self.data_wrapper = lambda x: x.replace('\r\n', os.linesep)
+ self.data_wrapper = lambda x: x.replace('\r\n', os.linesep)
elif type == 'i':
self.data_wrapper = lambda x: x
else:
@@ -945,20 +748,6 @@ class DTPHandler(asyncore.dispatcher):
# we tried to send some actual data
return
- def handle_timeout(self):
- """Called cyclically to check if data trasfer is stalling with
- no progress in which case the client is kicked off.
- """
- if self.get_transmitted_bytes() > self._lastdata:
- self._lastdata = self.get_transmitted_bytes()
- self.idler = CallLater(self.timeout, self.handle_timeout)
- else:
- msg = "Data connection timed out."
- self.cmd_channel.log(msg)
- self.cmd_channel.respond("421 " + msg)
- self.cmd_channel.close_when_done()
- self.close()
-
def handle_expt(self):
"""Called on "exceptional" data events."""
self.cmd_channel.respond("426 Connection error; transfer aborted.")
@@ -1016,19 +805,10 @@ class DTPHandler(asyncore.dispatcher):
def close(self):
"""Close the data channel, first attempting to close any remaining
file handles."""
- if not self._closed:
- self._closed = True
- if self.file_obj is not None and not self.file_obj.closed:
- self.file_obj.close()
- if self.idler is not None and not self.idler.cancelled:
- self.idler.cancel()
- asyncore.dispatcher.close(self)
- if self.file_obj is not None and self.transfer_finished:
- if self.receive:
- self.cmd_channel.on_file_received(self.file_obj.name)
- else:
- self.cmd_channel.on_file_sent(self.file_obj.name)
- self.cmd_channel.on_dtp_close()
+ if self.file_obj and not self.file_obj.closed:
+ self.file_obj.close()
+ asyncore.dispatcher.close(self)
+ self.cmd_channel.on_dtp_close()
# --- producers
@@ -1047,10 +827,7 @@ class FileProducer:
self.done = False
self.file = file
if type == 'a':
- if os.linesep == '\r\n':
- self.data_wrapper = lambda x: x
- else:
- self.data_wrapper = lambda x: x.replace(os.linesep, '\r\n')
+ self.data_wrapper = lambda x: x.replace(os.linesep, '\r\n')
elif type == 'i':
self.data_wrapper = lambda x: x
else:
@@ -1068,6 +845,22 @@ class FileProducer:
return data
+class IteratorProducer:
+ """Producer for iterator objects."""
+
+ def __init__(self, iterator):
+ self.iterator = iterator
+
+ def more(self):
+ """Attempt a chunk of data from iterator by calling its next()
+ method.
+ """
+ try:
+ return self.iterator.next()
+ except StopIteration:
+ return ''
+
+
class BufferedIteratorProducer:
"""Producer for iterator objects with buffer capabilities."""
# how many times iterator.next() will be called before
@@ -1250,9 +1043,9 @@ class AbstractedFS:
os.chdir(basedir)
self.cwd = self.fs2ftp(path)
- def mkdir(self, path):
+ def mkdir(self, path, basename):
"""Create the specified directory."""
- os.mkdir(path)
+ os.mkdir(os.path.join(path, basename))
def listdir(self, path):
"""List the content of a directory."""
@@ -1330,6 +1123,17 @@ class AbstractedFS:
exists = lexists # alias for backward compatibility with 0.2.0
+ def glob1(self, dirname, pattern):
+ """Return a list of files matching a dirname pattern
+ non-recursively.
+
+ Unlike glob.glob1 raises exception if os.listdir() fails.
+ """
+ names = self.listdir(dirname)
+ if pattern[0] != '.':
+ names = filter(lambda x: x[0] != '.', names)
+ return fnmatch.filter(names, pattern)
+
# --- Listing utilities
# note: the following operations are no more blocking
@@ -1348,6 +1152,28 @@ class AbstractedFS:
self.lstat(path) # raise exc in case of problems
return self.format_list(basedir, [filename])
+ def get_stat_dir(self, rawline):
+ """Return an iterator object that yields a list of files
+ matching a dirname pattern non-recursively in a form
+ suitable for STAT command.
+
+ - (str) rawline: the raw string passed by client as command
+ argument.
+ """
+ ftppath = self.ftpnorm(rawline)
+ if not glob.has_magic(ftppath):
+ return self.get_list_dir(self.ftp2fs(rawline))
+ else:
+ basedir, basename = os.path.split(ftppath)
+ if glob.has_magic(basedir):
+ return iter(['Directory recursion not supported.\r\n'])
+ else:
+ basedir = self.ftp2fs(basedir)
+ listing = self.glob1(basedir, basename)
+ if listing:
+ listing.sort()
+ return self.format_list(basedir, listing)
+
def format_list(self, basedir, listing, ignore_err=True):
"""Return an iterator object that yields the entries of given
directory emulating the "/bin/ls -lA" UNIX command output.
@@ -1397,15 +1223,14 @@ class AbstractedFS:
# bogus values for owner and group
uname = "owner"
gname = "group"
+ # stat.st_mtime could fail (-1) if last mtime is too old
+ # in which case we return the local time as last mtime
try:
mtime = time.strftime("%b %d %H:%M", time.localtime(st.st_mtime))
except ValueError:
- # It could be raised if last mtime happens to be too
- # old (prior to year 1900) in which case we return
- # the current time as last mtime.
mtime = time.strftime("%b %d %H:%M")
# if the file is a symlink, resolve it, e.g. "symlink -> realfile"
- if stat.S_ISLNK(st.st_mode) and hasattr(os, 'readlink'):
+ if stat.S_ISLNK(st.st_mode):
basename = basename + " -> " + os.readlink(file)
# formatting is matched with proftpd ls output
@@ -1476,9 +1301,8 @@ class AbstractedFS:
try:
modify = 'modify=%s;' %time.strftime("%Y%m%d%H%M%S",
time.localtime(st.st_mtime))
- # it could be raised if last mtime happens to be too old
- # (prior to year 1900)
except ValueError:
+ # stat.st_mtime could fail (-1) if last mtime is too old
modify = ""
if 'create' in facts:
# on Windows we can provide also the creation time
@@ -1519,11 +1343,6 @@ class FTPHandler(asynchat.async_chat):
reproduced below and can be modified before instantiating this
class.
- - (int) timeout:
- The timeout which is the maximum time a remote client may spend
- between FTP commands. If the timeout triggers, the remote client
- will be kicked off. Defaults to 300 seconds.
-
- (str) banner: the string sent when client connects.
- (int) max_login_attempts:
@@ -1579,7 +1398,6 @@ class FTPHandler(asynchat.async_chat):
abstracted_fs = AbstractedFS
# session attributes (explained in the docstring)
- timeout = 300
banner = "pyftpdlib %s ready." %__ver__
max_login_attempts = 3
permit_foreign_addresses = False
@@ -1594,17 +1412,17 @@ class FTPHandler(asynchat.async_chat):
established connection.
- (instance) server: the ftp server class instance.
"""
- asynchat.async_chat.__init__(self, conn)
- self.set_terminator("\r\n")
- # try to handle urgent data inline
try:
- self.socket.setsockopt(socket.SOL_SOCKET, socket.SO_OOBINLINE, 1)
- except socket.error:
- pass
-
- # public session attributes
+ asynchat.async_chat.__init__(self, conn=conn) # python2.5
+ except TypeError, e:
+ asynchat.async_chat.__init__(self, sock=conn) # python2.6
self.server = server
self.remote_ip, self.remote_port = self.socket.getpeername()[:2]
+ self.in_buffer = []
+ self.in_buffer_len = 0
+ self.set_terminator("\r\n")
+
+ # session attributes
self.fs = self.abstracted_fs()
self.authenticated = False
self.username = ""
@@ -1613,13 +1431,24 @@ class FTPHandler(asynchat.async_chat):
self.current_type = 'a'
self.restart_position = 0
self.quit_pending = False
- self.sleeping = False
+ self._epsvall = False
+ self.__in_dtp_queue = None
+ self.__out_dtp_queue = None
+
+ # mlsx facts attributes
+ self.current_facts = ['type', 'perm', 'size', 'modify']
+ if os.name == 'posix':
+ self.current_facts.append('unique')
+ self.available_facts = self.current_facts[:]
+ if pwd and grp:
+ self.available_facts += ['unix.mode', 'unix.uid', 'unix.gid']
+ if os.name == 'nt':
+ self.available_facts.append('create')
+
+ # dtp attributes
self.data_server = None
self.data_channel = None
- if self.timeout:
- self.idler = CallLater(self.timeout, self.handle_timeout)
- else:
- self.idler = None
+
if hasattr(self.socket, 'family'):
self.af = self.socket.family
else: # python < 2.5
@@ -1627,23 +1456,6 @@ class FTPHandler(asynchat.async_chat):
self.af = socket.getaddrinfo(ip, port, socket.AF_UNSPEC,
socket.SOCK_STREAM)[0][0]
- # private session attributes
- self._in_buffer = []
- self._in_buffer_len = 0
- self._epsvall = False
- self._in_dtp_queue = None
- self._out_dtp_queue = None
- self._closed = False
- self._extra_feats = []
- self._current_facts = ['type', 'perm', 'size', 'modify']
- if os.name == 'posix':
- self._current_facts.append('unique')
- self._available_facts = self._current_facts[:]
- if pwd and grp:
- self._available_facts += ['unix.mode', 'unix.uid', 'unix.gid']
- if os.name == 'nt':
- self._available_facts.append('create')
-
def handle(self):
"""Return a 220 'Ready' response to the client over the command
channel.
@@ -1675,33 +1487,37 @@ class FTPHandler(asynchat.async_chat):
self.log(msg)
self.close_when_done()
- def handle_timeout(self):
- """Called when client does not send any command within the time
- specified in attribute."""
- msg = "Control connection timed out."
- self.log(msg)
- self.respond("421 " + msg)
- self.close_when_done()
-
# --- asyncore / asynchat overridden methods
def readable(self):
# if there's a quit pending we stop reading data from socket
- return not self.sleeping
+ return not self.quit_pending
def collect_incoming_data(self, data):
"""Read incoming data and append to the input buffer."""
- self._in_buffer.append(data)
- self._in_buffer_len += len(data)
+ self.in_buffer.append(data)
+ self.in_buffer_len += len(data)
# Flush buffer if it gets too long (possible DoS attacks).
# RFC-959 specifies that a 500 response could be given in
# such cases
buflimit = 2048
- if self._in_buffer_len > buflimit:
+ if self.in_buffer_len > buflimit:
self.respond('500 Command too long.')
self.log('Command received exceeded buffer limit of %s.' %(buflimit))
- self._in_buffer = []
- self._in_buffer_len = 0
+ self.in_buffer = []
+ self.in_buffer_len = 0
+
+ # commands accepted before authentication
+ unauth_cmds = ('FEAT','HELP','NOOP','PASS','QUIT','STAT','SYST','USER')
+
+ # commands needing an argument
+ arg_cmds = ('ALLO','APPE','DELE','EPRT','MDTM','MODE','MKD','OPTS','PORT',
+ 'REST','RETR','RMD','RNFR','RNTO','SIZE', 'STOR','STRU',
+ 'TYPE','USER','XMKD','XRMD')
+
+ # commands needing no argument
+ unarg_cmds = ('ABOR','CDUP','FEAT','NOOP','PASV','PWD','QUIT','REIN',
+ 'SYST','XCUP','XPWD')
def found_terminator(self):
r"""Called when the incoming data stream matches the \r\n
@@ -1711,109 +1527,130 @@ class FTPHandler(asynchat.async_chat):
corresponding method (e.g. for received command "MKD pathname",
ftp_MKD() method is called with "pathname" as the argument).
"""
- if self.idler is not None and not self.idler.cancelled:
- self.idler.reset()
-
- line = ''.join(self._in_buffer)
- self._in_buffer = []
- self._in_buffer_len = 0
+ line = ''.join(self.in_buffer)
+ self.in_buffer = []
+ self.in_buffer_len = 0
cmd = line.split(' ')[0].upper()
- arg = line[len(cmd)+1:]
- if cmd == "SITE" and arg:
- cmd = "SITE %s" %arg.split(' ')[0].upper()
- arg = line[len(cmd)+1:]
+ space = line.find(' ')
+ if space != -1:
+ arg = line[space + 1:]
+ else:
+ arg = ""
if cmd != 'PASS':
self.logline("<== %s" %line)
else:
self.logline("<== %s %s" %(line.split(' ')[0], '*' * 6))
- # Recognize those commands having a "special semantic". They
- # should be sent by following the RFC-959 procedure of sending
- # Telnet IP/Synch sequence (chr 242 and 255) as OOB data but
- # since many ftp clients don't do it correctly we check the
- # last 4 characters only.
- if not cmd in proto_cmds:
- if cmd[-4:] in ('ABOR', 'STAT', 'QUIT'):
- cmd = cmd[-4:]
- else:
- self.respond('500 Command "%s" not understood.' %cmd)
- return
-
- if not arg and proto_cmds[cmd].arg_needed is True:
+ # let's check if user provided an argument for those commands
+ # needing one
+ if not arg and cmd in self.arg_cmds:
self.respond("501 Syntax error: command needs an argument.")
return
- if arg and proto_cmds[cmd].arg_needed is False:
+
+ # let's do the same for those commands requiring no argument.
+ elif arg and cmd in self.unarg_cmds:
self.respond("501 Syntax error: command does not accept arguments.")
return
- if not self.authenticated:
- if proto_cmds[cmd].auth_needed or (cmd == 'STAT' and arg):
+ # provide a limited set of commands if user isn't
+ # authenticated yet
+ if (not self.authenticated):
+ if cmd in self.unauth_cmds:
+ # we permit STAT during this phase but we don't want
+ # STAT to return a directory LISTing if the user is
+ # not authenticated yet (this could happen if STAT
+ # is used with an argument)
+ if (cmd == 'STAT') and arg:
+ self.respond("530 Log in with USER and PASS first.")
+ else:
+ method = getattr(self, 'ftp_' + cmd)
+ method(arg) # call the proper ftp_* method
+ elif cmd in proto_cmds:
self.respond("530 Log in with USER and PASS first.")
else:
- method = getattr(self, 'ftp_' + cmd.replace(' ', '_'))
- method(arg) # call the proper ftp_* method
- else:
- if cmd == 'STAT' and not arg:
- self.ftp_STAT('')
- return
+ self.respond('500 Command "%s" not understood.' %line)
- # for file-system related commands check whether real path
- # destination is valid
- if proto_cmds[cmd].check_path and cmd != 'STOU':
- if cmd in ('CWD', 'XCWD'):
- arg = arg or '/'
- elif cmd in ('CDUP', 'XCUP'):
- arg = '..'
- elif cmd == 'LIST':
- if arg.lower() in ('-a', '-l', '-al', '-la'):
- arg = self.fs.cwd
- else:
- arg = arg or self.fs.cwd
- elif cmd == 'STAT':
- if glob.has_magic(arg):
- self.respond('550 Globbing not supported.')
- return
- arg = arg or self.fs.cwd
- else: # LIST, NLST, MLSD, MLST
- arg = arg or self.fs.cwd
- if not self.fs.validpath(arg or '/'):
- line = self.fs.ftpnorm(arg)
+ # provide full command set
+ elif (self.authenticated) and (cmd in proto_cmds):
+ if not (self.__check_path(arg, arg)): # and self.__check_perm(cmd, arg)):
+ return
+ method = getattr(self, 'ftp_' + cmd)
+ method(arg) # call the proper ftp_* method
+
+ else:
+ # recognize those commands having "special semantics"
+ if 'ABOR' in cmd:
+ self.ftp_ABOR("")
+ elif 'STAT' in cmd:
+ self.ftp_STAT("")
+ # unknown command
+ else:
+ self.respond('500 Command "%s" not understood.' %line)
+
+ def __check_path(self, cmd, line):
+ """Check whether a path is valid."""
+ # For the following commands we have to make sure that the real
+ # path destination belongs to the user's root directory.
+ # If provided path is a symlink we follow its final destination
+ # to do so.
+ if cmd in ('APPE','CWD','DELE','MDTM','NLST','MLSD','MLST','RETR',
+ 'RMD','SIZE','STOR','XCWD','XRMD'):
+ datacr = None
+ datacr = self.fs.get_cr(line)
+ try:
+ if not self.fs.validpath(self.fs.ftp2fs(line, datacr)):
+ line = self.fs.ftpnorm(line)
err = '"%s" points to a path which is outside ' \
"the user's root directory" %line
self.respond("550 %s." %err)
- self.log('FAIL %s "%s". %s.' %(cmd, line, err))
- return
- # check permission
- perm = proto_cmds[cmd].perm
- if perm is not None and cmd != 'STOU':
- if not self.authorizer.has_perm(self.username, perm, arg or '/'):
- self.log('FAIL %s "%s". Not enough privileges.' \
- %(cmd, self.fs.fs2ftp(line)))
- self.respond("550 Can't %s. Not enough privileges." %cmd)
- return
- # call the proper ftp_* method
- method = getattr(self, 'ftp_' + cmd.replace(' ', '_'))
- method(arg)
+ self.log('FAIL %s "%s". %s.' %(cmd, line, err))
+ self.fs.close_cr(datacr)
+ return False
+ except:
+ pass
+ self.fs.close_cr(datacr)
+ return True
+
+ def __check_perm(self, cmd, line, datacr):
+ """Check permissions depending on issued command."""
+ map = {'CWD':'e', 'XCWD':'e', 'CDUP':'e', 'XCUP':'e',
+ 'LIST':'l', 'NLST':'l', 'MLSD':'l', 'STAT':'l',
+ 'RETR':'r',
+ 'APPE':'a',
+ 'DELE':'d', 'RMD':'d', 'XRMD':'d',
+ 'RNFR':'f',
+ 'MKD':'m', 'XMKD':'m',
+ 'STOR':'w'}
+ if cmd in map:
+ if cmd == 'STAT' and not line:
+ return True
+ perm = map[cmd]
+ if not line and (cmd in ('LIST','NLST','MLSD')):
+ path = self.fs.ftp2fs(self.fs.cwd, datacr)
+ else:
+ path = self.fs.ftp2fs(line, datacr)
+ if not self.authorizer.has_perm(self.username, perm, path):
+ self.log('FAIL %s "%s". Not enough privileges.' \
+ %(cmd, self.fs.ftpnorm(line)))
+ self.respond("550 Can't %s. Not enough privileges." %cmd)
+ return False
+ return True
def handle_expt(self):
- """Called when there is out of band (OOB) data to be read.
- This could happen in case of such clients strictly following
- the RFC-959 directives of sending Telnet IP and Synch as OOB
- data before issuing ABOR, STAT and QUIT commands.
- It should never be called since the SO_OOBINLINE option is
- enabled except on some systems like FreeBSD where it doesn't
- seem to have effect.
+ """Called when there is out of band (OOB) data for the socket
+ connection. This could happen in case of such commands needing
+ "special action" (typically STAT and ABOR) in which case we
+ append OOB data to incoming buffer.
"""
if hasattr(socket, 'MSG_OOB'):
try:
data = self.socket.recv(1024, socket.MSG_OOB)
- except socket.error, why:
- if why[0] == errno.EINVAL:
- return
+ except socket.error:
+ pass
else:
- self._in_buffer.append(data)
+ self.in_buffer.append(data)
return
self.log("Can't handle OOB data.")
self.close()
@@ -1838,23 +1675,21 @@ class FTPHandler(asynchat.async_chat):
def handle_close(self):
self.close()
+ _closed = False
def close(self):
"""Close the current channel disconnecting the client."""
if not self._closed:
self._closed = True
- if self.data_server is not None:
+ if self.data_server:
self.data_server.close()
del self.data_server
- if self.data_channel is not None:
+ if self.data_channel:
self.data_channel.close()
del self.data_channel
- del self._out_dtp_queue
- del self._in_dtp_queue
-
- if self.idler is not None and not self.idler.cancelled:
- self.idler.cancel()
+ del self.__out_dtp_queue
+ del self.__in_dtp_queue
# remove client IP address from ip map
self.server.ip_map.remove(self.remote_ip)
@@ -1863,16 +1698,6 @@ class FTPHandler(asynchat.async_chat):
# --- callbacks
- def on_file_sent(self, file):
- """Called every time a file has been succesfully sent.
- 'file' is the complete filename of the file being sent.
- """
-
- def on_file_received(self, file):
- """Called every time a file has been succesfully received.
- 'file' is the complete filename of the file being received.
- """
-
def on_dtp_connection(self):
"""Called every time data channel connects (either active or
passive).
@@ -1882,45 +1707,34 @@ class FTPHandler(asynchat.async_chat):
If awaiting inbound data, the data channel is enabled for
receiving.
"""
- if self.data_server is not None:
+ if self.data_server:
self.data_server.close()
self.data_server = None
- # stop the idle timer as long as the data transfer is not finished
- if self.idler is not None and not self.idler.cancelled:
- self.idler.cancel()
-
# check for data to send
- if self._out_dtp_queue is not None:
- data, isproducer, file = self._out_dtp_queue
- self._out_dtp_queue = None
+ if self.__out_dtp_queue:
+ data, isproducer, file = self.__out_dtp_queue
if file:
self.data_channel.file_obj = file
- try:
- if not isproducer:
- self.data_channel.push(data)
- else:
- self.data_channel.push_with_producer(data)
- if self.data_channel is not None:
- self.data_channel.close_when_done()
- except:
- # dealing with this exception is up to DTP (see bug #84)
- self.data_channel.handle_error()
+ if not isproducer:
+ self.data_channel.push(data)
+ else:
+ self.data_channel.push_with_producer(data)
+ if self.data_channel:
+ self.data_channel.close_when_done()
+ self.__out_dtp_queue = None
# check for data to receive
- elif self._in_dtp_queue is not None:
- self.data_channel.file_obj = self._in_dtp_queue
+ elif self.__in_dtp_queue:
+ self.data_channel.file_obj = self.__in_dtp_queue
self.data_channel.enable_receiving(self.current_type)
- self._in_dtp_queue = None
+ self.__in_dtp_queue = None
def on_dtp_close(self):
"""Called every time the data channel is closed."""
self.data_channel = None
if self.quit_pending:
self.close_when_done()
- elif self.timeout:
- # data transfer finished, restart the idle timer
- self.idler = CallLater(self.timeout, self.handle_timeout)
# --- utility
@@ -1943,26 +1757,22 @@ class FTPHandler(asynchat.async_chat):
- (bool) isproducer: whether treat data as a producer.
- (file) file: the file[-like] object to send (if any).
"""
- if self.data_channel is not None:
+ if self.data_channel:
self.respond("125 Data connection already open. Transfer starting.")
if file:
self.data_channel.file_obj = file
- try:
- if not isproducer:
- self.data_channel.push(data)
- else:
- self.data_channel.push_with_producer(data)
- if self.data_channel is not None:
- self.data_channel.close_when_done()
- except:
- # dealing with this exception is up to DTP (see bug #84)
- self.data_channel.handle_error()
+ if not isproducer:
+ self.data_channel.push(data)
+ else:
+ self.data_channel.push_with_producer(data)
+ if self.data_channel:
+ self.data_channel.close_when_done()
else:
self.respond("150 File status okay. About to open data connection.")
- self._out_dtp_queue = (data, isproducer, file)
+ self.__out_dtp_queue = (data, isproducer, file)
def log(self, msg):
- """Log a message, including additional identifying session data."""
+ """Log a message, including additional identifying session data."""
log("[%s]@%s:%s %s" %(self.username, self.remote_ip,
self.remote_port, msg))
@@ -1974,11 +1784,11 @@ class FTPHandler(asynchat.async_chat):
"""Flush account information by clearing attributes that need
to be reset on a REIN or new USER command.
"""
- if self.data_channel is not None:
+ if self.data_channel:
if not self.data_channel.transfer_in_progress():
self.data_channel.close()
self.data_channel = None
- if self.data_server is not None:
+ if self.data_server:
self.data_server.close()
self.data_server = None
@@ -1990,9 +1800,8 @@ class FTPHandler(asynchat.async_chat):
self.current_type = 'a'
self.restart_position = 0
self.quit_pending = False
- self.sleeping = False
- self._in_dtp_queue = None
- self._out_dtp_queue = None
+ self.__in_dtp_queue = None
+ self.__out_dtp_queue = None
def run_as_current_user(self, function, *args, **kwargs):
"""Execute a function impersonating the current logged-in user."""
@@ -2011,33 +1820,36 @@ class FTPHandler(asynchat.async_chat):
# FTP bounce attacks protection: according to RFC-2577 it's
# recommended to reject PORT if IP address specified in it
# does not match client IP address.
- if not self.permit_foreign_addresses and ip != self.remote_ip:
- self.log("Rejected data connection to foreign address %s:%s."
- %(ip, port))
- self.respond("501 Can't connect to a foreign address.")
- return
+ if not self.permit_foreign_addresses:
+ if ip != self.remote_ip:
+ self.log("Rejected data connection to foreign address %s:%s."
+ %(ip, port))
+ self.respond("501 Can't connect to a foreign address.")
+ return
# ...another RFC-2577 recommendation is rejecting connections
# to privileged ports (< 1024) for security reasons.
- if not self.permit_privileged_ports and port < 1024:
- self.log('PORT against the privileged port "%s" refused.' %port)
- self.respond("501 Can't connect over a privileged port.")
- return
+ if not self.permit_privileged_ports:
+ if port < 1024:
+ self.log('PORT against the privileged port "%s" refused.' %port)
+ self.respond("501 Can't connect over a privileged port.")
+ return
# close existent DTP-server instance, if any.
- if self.data_server is not None:
+ if self.data_server:
self.data_server.close()
self.data_server = None
- if self.data_channel is not None:
+ if self.data_channel:
self.data_channel.close()
self.data_channel = None
# make sure we are not hitting the max connections limit
- if self.server.max_cons and len(self._map) >= self.server.max_cons:
- msg = "Too many connections. Can't open data channel."
- self.respond("425 %s" %msg)
- self.log(msg)
- return
+ if self.server.max_cons:
+ if len(self._map) >= self.server.max_cons:
+ msg = "Too many connections. Can't open data channel."
+ self.respond("425 %s" %msg)
+ self.log(msg)
+ return
# open data channel
self.active_dtp(ip, port, self)
@@ -2049,20 +1861,21 @@ class FTPHandler(asynchat.async_chat):
which case extended passive mode will be used (see RFC-2428).
"""
# close existing DTP-server instance, if any
- if self.data_server is not None:
+ if self.data_server:
self.data_server.close()
self.data_server = None
- if self.data_channel is not None:
+ if self.data_channel:
self.data_channel.close()
self.data_channel = None
# make sure we are not hitting the max connections limit
- if self.server.max_cons and len(self._map) >= self.server.max_cons:
- msg = "Too many connections. Can't open data channel."
- self.respond("425 %s" %msg)
- self.log(msg)
- return
+ if self.server.max_cons:
+ if len(self._map) >= self.server.max_cons:
+ msg = "Too many connections. Can't open data channel."
+ self.respond("425 %s" %msg)
+ self.log(msg)
+ return
# open data channel
self.data_server = self.passive_dtp(self, extmode)
@@ -2123,7 +1936,7 @@ class FTPHandler(asynchat.async_chat):
assert len(octs) == 4
for x in octs:
assert 0 <= x <= 255
- except (AssertionError, ValueError, OverflowError):
+ except (AssertionError, ValueError, OverflowError), err:
self.respond("501 Invalid EPRT format.")
else:
self._make_eport(ip, port)
@@ -2186,7 +1999,12 @@ class FTPHandler(asynchat.async_chat):
self.respond('501 Unknown network protocol (use 2).')
def ftp_QUIT(self, line):
- """Quit the current session disconnecting the client."""
+ """Quit the current session."""
+ # From RFC-959:
+ # This command terminates a USER and if file transfer is not
+ # in progress, the server closes the control connection.
+ # If file transfer is in progress, the connection will remain
+ # open for result response and the server will then close it.
if self.authenticated:
msg_quit = self.authorizer.get_msg_quit(self.username)
else:
@@ -2197,15 +2015,12 @@ class FTPHandler(asynchat.async_chat):
self.push("221-%s\r\n" %msg_quit)
self.respond("221 ")
- # From RFC-959:
- # If file transfer is in progress, the connection will remain
- # open for result response and the server will then close it.
- # We also stop responding to any further command.
- if self.data_channel:
- self.quit_pending = True
- self.sleeping = True
- else:
+ if not self.data_channel:
self.close_when_done()
+ else:
+ # tell the cmd channel to stop responding to commands.
+ self.quit_pending = True
+
# --- data transferring
@@ -2216,30 +2031,35 @@ class FTPHandler(asynchat.async_chat):
# - If no argument, fall back on cwd as default.
# - Some older FTP clients erroneously issue /bin/ls-like LIST
# formats in which case we fall back on cwd as default.
- datacr = None
- try:
- datacr = self.fs.get_cr(line)
- path = self.fs.ftp2fs(line, datacr)
+ if not line or line.lower() in ('-a', '-l', '-al', '-la'):
+ line = self.fs.cwd
+ try:
+ data = None
+ data = self.fs.get_cr(line)
+ path = self.fs.ftp2fs(line, data)
line = self.fs.ftpnorm(line)
- iterator = self.run_as_current_user(self.fs.get_list_dir, path)
+ iterator = self.run_as_current_user(self.fs.get_list_dir, path)
except OSError, err:
+ self.fs.close_cr(data)
why = _strerror(err)
self.log('FAIL LIST "%s". %s.' %(line, why))
self.respond('550 %s.' %why)
else:
+ self.fs.close_cr(data)
self.log('OK LIST "%s". Transfer starting.' %line)
producer = BufferedIteratorProducer(iterator)
self.push_dtp_data(producer, isproducer=True)
- self.fs.close_cr(datacr)
def ftp_NLST(self, line):
"""Return a list of files in the specified directory in a
compact form to the client.
"""
- datacr = None
- try:
- datacr = self.fs.get_cr(line)
- path = self.fs.ftp2fs(line, datacr)
+ if not line:
+ line = self.fs.cwd
+ try:
+ data = None
+ data = self.fs.get_cr(line)
+ path = self.fs.ftp2fs(line, data)
line = self.fs.ftpnorm(line)
if self.fs.isdir(path):
listing = self.run_as_current_user(self.fs.listdir, path)
@@ -2248,19 +2068,21 @@ class FTPHandler(asynchat.async_chat):
# if path is a file we just list its name
self.fs.lstat(path) # raise exc in case of problems
basedir, filename = os.path.split(line)
- listing = [filename]
+ listing = [filename]
except OSError, err:
+ self.fs.close_cr(data)
why = _strerror(err)
self.log('FAIL NLST "%s". %s.' %(line, why))
self.respond('550 %s.' %why)
else:
+ self.fs.close_cr(data)
data = ''
if listing:
listing.sort()
data = '\r\n'.join(listing) + '\r\n'
self.log('OK NLST "%s". Transfer starting.' %line)
self.push_dtp_data(data)
- self.fs.close_cr(datacr)
+
# --- MLST and MLSD commands
# The MLST and MLSD commands are intended to standardize the file and
@@ -2271,9 +2093,12 @@ class FTPHandler(asynchat.async_chat):
def ftp_MLST(self, line):
"""Return information about a pathname in a machine-processable
form as defined in RFC-3659.
- """
- datacr = None
- try:
+ """
+ # if no argument, fall back on cwd as default
+ if not line:
+ line = self.fs.cwd
+ try:
+ datacr = None
datacr = self.fs.get_cr(line)
path = self.fs.ftp2fs(line, datacr)
line = self.fs.ftpnorm(line)
@@ -2281,7 +2106,7 @@ class FTPHandler(asynchat.async_chat):
perms = self.authorizer.get_perms(self.username)
iterator = self.run_as_current_user(self.fs.format_mlsx, basedir,
[basename], perms, self.current_facts, ignore_err=False)
- data = ''.join(iterator)
+ data = ''.join(iterator)
except OSError, err:
self.fs.close_cr(datacr)
why = _strerror(err)
@@ -2302,8 +2127,11 @@ class FTPHandler(asynchat.async_chat):
"""Return contents of a directory in a machine-processable form
as defined in RFC-3659.
"""
- datacr = None
- try:
+ # if no argument, fall back on cwd as default
+ if not line:
+ line = self.fs.cwd
+ try:
+ datacr = None
datacr = self.fs.get_cr(line)
path = self.fs.ftp2fs(line, datacr)
line = self.fs.ftpnorm(line)
@@ -2313,7 +2141,7 @@ class FTPHandler(asynchat.async_chat):
self.log('FAIL MLSD "%s". %s.' %(line, err))
self.respond("501 %s." %err)
return
- listing = self.run_as_current_user(self.fs.listdir, path)
+ listing = self.run_as_current_user(self.fs.listdir, path)
except OSError, err:
self.fs.close_cr(datacr)
why = _strerror(err)
@@ -2323,7 +2151,7 @@ class FTPHandler(asynchat.async_chat):
self.fs.close_cr(datacr)
perms = self.authorizer.get_perms(self.username)
iterator = self.fs.format_mlsx(path, listing, perms,
- self._current_facts)
+ self.current_facts)
producer = BufferedIteratorProducer(iterator)
self.log('OK MLSD "%s". Transfer starting.' %line)
self.push_dtp_data(producer, isproducer=True)
@@ -2332,14 +2160,18 @@ class FTPHandler(asynchat.async_chat):
"""Retrieve the specified file (transfer from the server to the
client)
"""
- datacr = None
- try:
+ try:
+ datacr = None
datacr = self.fs.get_cr(line)
file = self.fs.ftp2fs(line, datacr)
line = self.fs.ftpnorm(line)
- rest_pos = self.restart_position
- self.restart_position = 0
- fd = self.run_as_current_user(self.fs.open, file, 'rb')
+ fd = self.run_as_current_user(self.fs.open, file, 'rb')
+ except OSError, err:
+ self.fs.close_cr(datacr)
+ why = _strerror(err)
+ self.log('FAIL RETR "%s". %s.' %(line, why))
+ self.respond('550 %s.' %why)
+ return
except IOError, err:
self.fs.close_cr(datacr)
why = _strerror(err)
@@ -2347,7 +2179,7 @@ class FTPHandler(asynchat.async_chat):
self.respond('550 %s.' %why)
return
- if rest_pos:
+ if self.restart_position:
# Make sure that the requested offset is valid (within the
# size of the file being resumed).
# According to RFC-1123 a 554 reply may result in case that
@@ -2355,18 +2187,19 @@ class FTPHandler(asynchat.async_chat):
# the REST.
ok = 0
try:
- assert not rest_pos > self.fs.getsize(file)
- fd.seek(rest_pos)
+ assert not self.restart_position > self.fs.getsize(file)
+ fd.seek(self.restart_position)
ok = 1
except AssertionError:
why = "Invalid REST parameter"
except IOError, err:
why = _strerror(err)
- if not ok:
+ self.restart_position = 0
+ if not ok:
self.respond('554 %s' %why)
self.log('FAIL RETR "%s". %s.' %(line, why))
self.fs.close_cr(datacr)
- return
+ return
self.log('OK RETR "%s". Download starting.' %line)
producer = FileProducer(fd, self.current_type)
self.push_dtp_data(producer, isproducer=True, file=fd)
@@ -2383,7 +2216,7 @@ class FTPHandler(asynchat.async_chat):
cmd = 'APPE'
else:
cmd = 'STOR'
-
+
line = self.fs.ftpnorm(line)
basedir,basename = os.path.split(line)
@@ -2394,14 +2227,12 @@ class FTPHandler(asynchat.async_chat):
except OSError, err:
self.fs.close_cr(datacr)
- why = ftpserver._strerror(err)
+ why = _strerror(err)
self.log('FAIL %s "%s". %s.' %(cmd, line, why))
self.respond('550 %s.' %why)
return
-
- rest_pos = self.restart_position
- self.restart_position = 0
- if rest_pos:
+
+ if self.restart_position:
mode = 'r+'
try:
fd = self.run_as_current_user(self.fs.create, file, basename, mode + 'b')
@@ -2412,7 +2243,7 @@ class FTPHandler(asynchat.async_chat):
self.respond('550 %s.' %why)
return
- if rest_pos:
+ if self.restart_position:
# Make sure that the requested offset is valid (within the
# size of the file being resumed).
# According to RFC-1123 a 554 reply may result in case
@@ -2420,29 +2251,31 @@ class FTPHandler(asynchat.async_chat):
# specified in the REST.
ok = 0
try:
- assert not rest_pos > self.fs.getsize(file)
- fd.seek(rest_pos)
+ assert not self.restart_position > self.fs.getsize(self.fs.ftp2fs(line, datacr))
+ fd.seek(self.restart_position)
ok = 1
except AssertionError:
why = "Invalid REST parameter"
except IOError, err:
why = _strerror(err)
+ self.restart_position = 0
if not ok:
+ self.fs.close_cr(datacr)
self.respond('554 %s' %why)
self.log('FAIL %s "%s". %s.' %(cmd, line, why))
- self.fs.close_cr(datacr)
return
self.log('OK %s "%s". Upload starting.' %(cmd, line))
- if self.data_channel is not None:
+ if self.data_channel:
self.respond("125 Data connection already open. Transfer starting.")
self.data_channel.file_obj = fd
self.data_channel.enable_receiving(self.current_type)
else:
self.respond("150 File status okay. About to open data connection.")
- self._in_dtp_queue = fd
+ self.__in_dtp_queue = fd
self.fs.close_cr(datacr)
+
def ftp_STOU(self, line):
"""Store a file on the server with a unique name."""
# Note 1: RFC-959 prohibited STOU parameters, but this
@@ -2459,7 +2292,7 @@ class FTPHandler(asynchat.async_chat):
if self.restart_position:
self.respond("450 Can't STOU while REST request is pending.")
return
-
+
datacr = None
datacr = self.fs.get_cr(line)
@@ -2469,7 +2302,7 @@ class FTPHandler(asynchat.async_chat):
basedir = self.fs.ftp2fs(basedir, datacr)
#prefix = prefix + '.'
else:
- basedir = self.fs.ftp2fs(self.fs.cwd)
+ basedir = self.fs.ftp2fs(self.fs.cwd, datacr)
prefix = 'ftpd.'
try:
fd = self.run_as_current_user(self.fs.mkstemp, prefix=prefix,
@@ -2487,12 +2320,8 @@ class FTPHandler(asynchat.async_chat):
self.fs.close_cr(datacr)
return
- if not self.authorizer.has_perm(self.username, 'w', fd.name):
- try:
- fd.close()
- self.run_as_current_user(self.fs.remove, fd.name)
- except os.error:
- pass
+ filename = line
+ if not self.authorizer.has_perm(self.username, 'w', filename):
self.log('FAIL STOU "%s". Not enough privileges'
%self.fs.ftpnorm(line))
self.respond("550 Can't STOU: not enough privileges.")
@@ -2500,30 +2329,27 @@ class FTPHandler(asynchat.async_chat):
return
# now just acts like STOR except that restarting isn't allowed
- filename = os.path.basename(fd.name)
self.log('OK STOU "%s". Upload starting.' %filename)
- if self.data_channel is not None:
+ if self.data_channel:
self.respond("125 FILE: %s" %filename)
self.data_channel.file_obj = fd
self.data_channel.enable_receiving(self.current_type)
else:
self.respond("150 FILE: %s" %filename)
- self._in_dtp_queue = fd
+ self.__in_dtp_queue = fd
self.fs.close_cr(datacr)
- def ftp_APPE(self, file):
+
+ def ftp_APPE(self, line):
"""Append data to an existing file on the server."""
# watch for APPE preceded by REST, which makes no sense.
if self.restart_position:
- self.respond("450 Can't APPE while REST request is pending.")
+ self.respond("550 Can't APPE while REST request is pending.")
else:
- self.ftp_STOR(file, mode='a')
+ self.ftp_STOR(line, mode='a')
def ftp_REST(self, line):
"""Restart a file transfer from a previous mark."""
- if self.current_type == 'a':
- self.respond('501 Resuming transfers not allowed in ASCII mode.')
- return
try:
marker = int(line)
if marker < 0:
@@ -2531,7 +2357,9 @@ class FTPHandler(asynchat.async_chat):
except (ValueError, OverflowError):
self.respond("501 Invalid parameter.")
else:
- self.respond("350 Restarting at position %s." %marker)
+ self.respond("350 Restarting at position %s. " \
+ "Now use RETR/STOR for resuming." %marker)
+ self.log("OK REST %s." %marker)
self.restart_position = marker
def ftp_ABOR(self, line):
@@ -2542,7 +2370,7 @@ class FTPHandler(asynchat.async_chat):
resp = "225 No transfer to abort."
else:
# a PASV was received but connection wasn't made yet
- if self.data_server is not None:
+ if self.data_server:
self.data_server.close()
self.data_server = None
resp = "225 ABOR command successful; data channel closed."
@@ -2554,7 +2382,7 @@ class FTPHandler(asynchat.async_chat):
# was successfully processed.
# If no data has been transmitted we just respond with 225
# indicating that no transfer was in progress.
- if self.data_channel is not None:
+ if self.data_channel:
if self.data_channel.transfer_in_progress():
self.data_channel.close()
self.data_channel = None
@@ -2573,6 +2401,10 @@ class FTPHandler(asynchat.async_chat):
def ftp_USER(self, line):
"""Set the username for the current session."""
+ # we always treat anonymous user as lower-case string.
+ if line.lower() == "anonymous":
+ line = "anonymous"
+
# RFC-959 specifies a 530 response to the USER command if the
# username is not valid. If the username is valid is required
# ftpd returns a 331 response instead. In order to prevent a
@@ -2593,10 +2425,9 @@ class FTPHandler(asynchat.async_chat):
self.respond('331 %s, send password.' %msg)
self.username = line
- _auth_failed_timeout = 5
-
def ftp_PASS(self, line):
"""Check username's password against the authorizer."""
+
if self.authenticated:
self.respond("503 User already authenticated.")
return
@@ -2604,42 +2435,50 @@ class FTPHandler(asynchat.async_chat):
self.respond("503 Login with USER first.")
return
- def auth_failed(msg="Authentication failed."):
- if not self._closed:
+ # username ok
+ if self.authorizer.has_user(self.username):
+ if self.username == 'anonymous' \
+ or self.authorizer.validate_authentication(self.username, line):
+ msg_login = self.authorizer.get_msg_login(self.username)
+ if len(msg_login) <= 75:
+ self.respond('230 %s' %msg_login)
+ else:
+ self.push("230-%s\r\n" %msg_login)
+ self.respond("230 ")
+
+ self.authenticated = True
+ self.password = line
+ self.attempted_logins = 0
+ self.fs.root = self.authorizer.get_home_dir(self.username)
+ self.fs.username=self.username
+ self.fs.password=line
+ self.log("User %s logged in." %self.username)
+ else:
self.attempted_logins += 1
if self.attempted_logins >= self.max_login_attempts:
- msg = "530 " + msg + " Disconnecting."
- self.respond(msg)
- self.log(msg)
- self.close_when_done()
+ self.respond("530 Maximum login attempts. Disconnecting.")
+ self.close()
else:
- self.respond("530 " + msg)
- self.log(msg)
- self.sleeping = False
+ self.respond("530 Authentication failed.")
+ self.log('Authentication failed (user: "%s").' %self.username)
+ self.username = ""
- if self.authorizer.validate_authentication(self.username, line):
- msg_login = self.authorizer.get_msg_login(self.username)
- if len(msg_login) <= 75:
- self.respond('230 %s' %msg_login)
- else:
- self.push("230-%s\r\n" %msg_login)
- self.respond("230 ")
- self.authenticated = True
- self.password = line
- self.fs.username = self.username
- self.fs.password = line
- self.attempted_logins = 0
- self.fs.root = self.authorizer.get_home_dir(self.username)
- self.log("User %s logged in." %self.username)
+ # wrong username
else:
- self.username = ""
- self.fs.username = ""
- self.sleeping = True
- if self.username == 'anonymous':
- CallLater(self._auth_failed_timeout, auth_failed,
- "Anonymous access not allowed.")
+ self.attempted_logins += 1
+ if self.attempted_logins >= self.max_login_attempts:
+ self.log('Authentication failed: unknown username "%s".'
+ %self.username)
+ self.respond("530 Maximum login attempts. Disconnecting.")
+ self.close()
+ elif self.username.lower() == 'anonymous':
+ self.respond("530 Anonymous access not allowed.")
+ self.log('Authentication failed: anonymous access not allowed.')
else:
- CallLater(self._auth_failed_timeout, auth_failed)
+ self.respond("530 Authentication failed.")
+ self.log('Authentication failed: unknown username "%s".'
+ %self.username)
+ self.username = ""
def ftp_REIN(self, line):
"""Reinitialize user's current session."""
@@ -2665,6 +2504,9 @@ class FTPHandler(asynchat.async_chat):
def ftp_CWD(self, line):
"""Change the current working directory."""
+ # TODO: a lot of FTP servers go back to root directory if no
+ # arg is provided but this is not specified in RFC-959.
+ # Search for official references about this behaviour.
if not line:
line = '/'
datacr = None
@@ -2694,38 +2536,33 @@ class FTPHandler(asynchat.async_chat):
def ftp_SIZE(self, line):
"""Return size of file in a format suitable for using with
- RESTart as defined in RFC-3659."""
+ RESTart as defined in RFC-3659.
- # Implementation note: properly handling the SIZE command when
- # TYPE ASCII is used would require to scan the entire file to
- # perform the ASCII translation logic
- # (file.read().replace(os.linesep, '\r\n')) and then calculating
- # the len of such data which may be different than the actual
- # size of the file on the server. Considering that calculating
- # such result could be very resource-intensive and also dangerous
- # (DoS) we reject SIZE when the current TYPE is ASCII.
- # However, clients in general should not be resuming downloads
- # in ASCII mode. Resuming downloads in binary mode is the
- # recommended way as specified in RFC-3659.
-
- if self.current_type == 'a':
- why = "SIZE not allowed in ASCII mode"
- self.log('FAIL SIZE "%s". %s.' %(line, why))
- self.respond("550 %s." %why)
- return
- datacr = False
+ Implementation note:
+ properly handling the SIZE command when TYPE ASCII is used would
+ require to scan the entire file to perform the ASCII translation
+ logic (file.read().replace(os.linesep, '\r\n')) and then
+ calculating the len of such data which may be different than
+ the actual size of the file on the server. Considering that
+ calculating such result could be very resource-intensive it
+ could be easy for a malicious client to try a DoS attack, thus
+ we do not perform the ASCII translation.
+
+ However, clients in general should not be resuming downloads in
+ ASCII mode. Resuming downloads in binary mode is the recommended
+ way as specified in RFC-3659.
+ """
+ datacr = None
try:
datacr = self.fs.get_cr(line)
path = self.fs.ftp2fs(line, datacr)
line = self.fs.ftpnorm(line)
- if not self.fs.isfile(self.fs.realpath(path)):
+ if self.fs.isdir(path):
why = "%s is not retrievable" %line
self.log('FAIL SIZE "%s". %s.' %(line, why))
self.respond("550 %s." %why)
- self.fs.close_cr(datacr)
return
size = self.run_as_current_user(self.fs.getsize, path)
- size = self.run_as_current_user(self.fs.getsize, path)
except OSError, err:
why = _strerror(err)
self.log('FAIL SIZE "%s". %s.' %(line, why))
@@ -2739,7 +2576,7 @@ class FTPHandler(asynchat.async_chat):
"""Return last modification time of file to the client as an ISO
3307 style timestamp (YYYYMMDDHHMMSS) as defined in RFC-3659.
"""
- datacr = None
+ datacr = None
try:
datacr = self.fs.get_cr(line)
path = self.fs.ftp2fs(line, datacr)
@@ -2750,23 +2587,17 @@ class FTPHandler(asynchat.async_chat):
self.respond("550 %s." %why)
self.fs.close_cr(datacr)
return
- secs = self.run_as_current_user(self.fs.getmtime, path)
- lmt = self.run_as_current_user(self.fs.getmtime, path)
-
- except (OSError, ValueError), err:
- if isinstance(err, OSError):
- why = _strerror(err)
- else:
- # It could happen if file's last modification time
- # happens to be too old (prior to year 1900)
- why = "Can't determine file's last modification time"
+ lmt = self.run_as_current_user(self.fs.getmtime, path)
+ except OSError, err:
+ why = _strerror(err)
self.log('FAIL MDTM "%s". %s.' %(line, why))
self.respond('550 %s.' %why)
else:
+ lmt = time.strftime("%Y%m%d%H%M%S", time.localtime(lmt))
self.respond("213 %s" %lmt)
self.log('OK MDTM "%s".' %line)
self.fs.close_cr(datacr)
-
+
def ftp_MKD(self, line):
"""Create the specified directory."""
datacr = None
@@ -2782,15 +2613,12 @@ class FTPHandler(asynchat.async_chat):
self.respond('550 %s.' %why)
else:
self.log('OK MKD "%s".' %line)
- # The 257 response is supposed to include the directory
- # name and in case it contains embedded double-quotes
- # they must be doubled (see RFC-959, chapter 7, appendix 2).
- self.respond('257 "%s" directory created.' %line.replace('"', '""'))
+ self.respond("257 Directory created.")
self.fs.close_cr(datacr)
def ftp_RMD(self, line):
"""Remove the specified directory."""
- datacr = False
+ datacr = None
try:
datacr = self.fs.get_cr(line)
path = self.fs.ftp2fs(line, datacr)
@@ -2801,7 +2629,7 @@ class FTPHandler(asynchat.async_chat):
self.log('FAIL MKD "/". %s' %msg)
self.fs.close_cr(datacr)
return
- self.run_as_current_user(self.fs.rmdir, path)
+ self.run_as_current_user(self.fs.rmdir, path)
except OSError, err:
why = _strerror(err)
self.log('FAIL RMD "%s". %s.' %(line, why))
@@ -2810,7 +2638,7 @@ class FTPHandler(asynchat.async_chat):
self.log('OK RMD "%s".' %line)
self.respond("250 Directory removed.")
self.fs.close_cr(datacr)
-
+
def ftp_DELE(self, line):
"""Delete the specified file."""
datacr = None
@@ -2827,11 +2655,10 @@ class FTPHandler(asynchat.async_chat):
self.log('OK DELE "%s".' %line)
self.respond("250 File removed.")
self.fs.close_cr(datacr)
-
+
def ftp_RNFR(self, line):
"""Rename the specified (only the source name is specified
here, see RNTO command)"""
-
datacr = None
try:
datacr = self.fs.get_cr(line)
@@ -2855,7 +2682,7 @@ class FTPHandler(asynchat.async_chat):
if not self.fs.rnfr:
self.respond("503 Bad sequence of commands: use RNFR first.")
return
- datacr = None
+ datacr = None
try:
try:
datacr = self.fs.get_cr(line)
@@ -2878,50 +2705,35 @@ class FTPHandler(asynchat.async_chat):
self.fs.close_cr(datacr)
-
# --- others
def ftp_TYPE(self, line):
"""Set current type data type to binary/ascii"""
- type = line.upper().replace(' ', '')
- if type in ("A", "L7"):
+ line = line.upper()
+ if line in ("A", "AN", "A N"):
self.respond("200 Type set to: ASCII.")
self.current_type = 'a'
- elif type in ("I", "L8"):
+ elif line in ("I", "L8", "L 8"):
self.respond("200 Type set to: Binary.")
self.current_type = 'i'
else:
self.respond('504 Unsupported type "%s".' %line)
def ftp_STRU(self, line):
- """Set file structure ("F" is the only one supported (noop))."""
- stru = line.upper()
- if stru == 'F':
+ """Set file structure (obsolete)."""
+ # obsolete (backward compatibility with older ftp clients)
+ if line in ('f','F'):
self.respond('200 File transfer structure set to: F.')
- elif stru in ('P', 'R'):
- # R is required in minimum implementations by RFC-959, 5.1.
- # RFC-1123, 4.1.2.13, amends this to only apply to servers
- # whose file systems support record structures, but also
- # suggests that such a server "may still accept files with
- # STRU R, recording the byte stream literally".
- # Should we accept R but with no operational difference from
- # F? proftpd and wu-ftpd don't accept STRU R. We just do
- # the same.
- #
- # RFC-1123 recommends against implementing P.
- self.respond('504 Unimplemented STRU type.')
else:
- self.respond('501 Unrecognized STRU type.')
+ self.respond('504 Unimplemented STRU type.')
def ftp_MODE(self, line):
- """Set data transfer mode ("S" is the only one supported (noop))."""
- mode = line.upper()
- if mode == 'S':
+ """Set data transfer mode (obsolete)"""
+ # obsolete (backward compatibility with older ftp clients)
+ if line in ('s', 'S'):
self.respond('200 Transfer mode set to: S')
- elif mode in ('B', 'C'):
- self.respond('504 Unimplemented MODE type.')
else:
- self.respond('501 Unrecognized MODE type.')
+ self.respond('504 Unimplemented MODE type.')
def ftp_STAT(self, line):
"""Return statistics about current ftp session. If an argument
@@ -2929,15 +2741,17 @@ class FTPHandler(asynchat.async_chat):
Implementation note:
- RFC-959 does not explicitly mention globbing but many FTP
- servers do support it as a measure of convenience for FTP
- clients and users.
+ RFC-959 do not explicitly mention globbing; this means that FTP
+ servers are not required to support globbing in order to be
+ compliant. However, many FTP servers do support globbing as a
+ measure of convenience for FTP clients and users.
In order to search for and match the given globbing expression,
the code has to search (possibly) many directories, examine
each contained filename, and build a list of matching files in
memory. Since this operation can be quite intensive, both CPU-
- and memory-wise, we do not support globbing.
+ and memory-wise, we limit the search to only one directory
+ non-recursively, as LIST does.
"""
# return STATus information about ftpd
if not line:
@@ -2955,9 +2769,9 @@ class FTPHandler(asynchat.async_chat):
else:
type = 'Binary'
s.append("TYPE: %s; STRUcture: File; MODE: Stream" %type)
- if self.data_server is not None:
+ if self.data_server:
s.append('Passive data channel waiting for connection.')
- elif self.data_channel is not None:
+ elif self.data_channel:
bytes_sent = self.data_channel.tot_bytes_sent
bytes_recv = self.data_channel.tot_bytes_received
s.append('Data connection open:')
@@ -2976,22 +2790,19 @@ class FTPHandler(asynchat.async_chat):
datacr = self.fs.get_cr(line)
iterator = self.run_as_current_user(self.fs.get_stat_dir, line, datacr)
except OSError, err:
- why = _strerror(err)
- self.log('FAIL STAT "%s". %s.' %(line, why))
- self.respond('550 %s.' %why)
+ self.respond('550 %s.' %_strerror(err))
else:
- self.push('213-Status of "%s":\r\n' %line)
+ self.push('213-Status of "%s":\r\n' %self.fs.ftpnorm(line))
self.push_with_producer(BufferedIteratorProducer(iterator))
self.respond('213 End of status.')
self.fs.close_cr(datacr)
-
+
def ftp_FEAT(self, line):
"""List all new features supported as defined in RFC-2398."""
features = ['EPRT','EPSV','MDTM','MLSD','REST STREAM','SIZE','TVFS']
- features.extend(self._extra_feats)
s = ''
- for fact in self._available_facts:
- if fact in self._current_facts:
+ for fact in self.available_facts:
+ if fact in self.current_facts:
s += fact + '*;'
else:
s += fact + ';'
@@ -3016,8 +2827,8 @@ class FTPHandler(asynchat.async_chat):
self.respond('501 %s.' %err)
else:
facts = [x.lower() for x in arg.split(';')]
- self._current_facts = [x for x in facts if x in self._available_facts]
- f = ''.join([x + ';' for x in self._current_facts])
+ self.current_facts = [x for x in facts if x in self.available_facts]
+ f = ''.join([x + ';' for x in self.current_facts])
self.respond('200 MLST OPTS ' + f)
def ftp_NOOP(self, line):
@@ -3034,23 +2845,22 @@ class FTPHandler(asynchat.async_chat):
self.respond("215 UNIX Type: L8")
def ftp_ALLO(self, line):
- """Allocate bytes for storage (noop)."""
- # not necessary (always respond with 202)
+ """Allocate bytes for storage (obsolete)."""
+ # obsolete (always respond with 202)
self.respond("202 No storage allocation necessary.")
def ftp_HELP(self, line):
"""Return help text to the client."""
if line:
- line = line.upper()
- if line in proto_cmds:
- self.respond("214 %s" %proto_cmds[line].help)
+ if line.upper() in proto_cmds:
+ self.respond("214 %s" %proto_cmds[line.upper()])
else:
self.respond("501 Unrecognized command.")
else:
# provide a compact list of recognized commands
def formatted_help():
cmds = []
- keys = [x for x in proto_cmds.keys() if not x.startswith('SITE ')]
+ keys = proto_cmds.keys()
keys.sort()
while keys:
elems = tuple((keys[0:8]))
@@ -3062,30 +2872,6 @@ class FTPHandler(asynchat.async_chat):
self.push(formatted_help())
self.respond("214 Help command successful.")
- # --- site commands
-
- # No SITE commands aside from SITE HELP are implemented by default.
- # The user willing to add support for a specific SITE command has
- # to define a new ftp_SITE_%CMD% method in the subclass.
-
- def ftp_SITE_HELP(self, line):
- """Return help text to the client for a given SITE command."""
- if line:
- line = line.upper()
- if line in proto_cmds:
- self.respond("214 %s" %proto_cmds[line].help)
- else:
- self.respond("501 Unrecognized SITE command.")
- else:
- self.push("214-The following SITE commands are recognized:\r\n")
- site_cmds = []
- keys = proto_cmds.keys()
- keys.sort()
- for cmd in keys:
- if cmd.startswith('SITE '):
- site_cmds.append(' %s\r\n' %cmd[5:])
- self.push(''.join(site_cmds))
- self.respond("214 Help SITE command successful.")
# --- support for deprecated cmds
@@ -3186,50 +2972,43 @@ class FTPServer(asyncore.dispatcher):
return
asyncore.dispatcher.set_reuse_addr(self)
- def serve_forever(self, timeout=1, use_poll=False, map=None, count=None):
+ def serve_forever(self, **kwargs):
"""A wrap around asyncore.loop(); starts the asyncore polling
- loop including running the scheduler.
- The arguments are the same expected by original asyncore.loop()
- function.
+ loop.
+
+ The keyword arguments in kwargs are the same expected by
+ asyncore.loop() function: timeout, use_poll, map and count.
"""
- if map is None:
- map = asyncore.socket_map
- # backward compatibility for python versions < 2.4
+ if not 'count' in kwargs:
+ log("Serving FTP on %s:%s" %self.socket.getsockname()[:2])
+
+ # backward compatibility for python < 2.4
if not hasattr(self, '_map'):
+ if not 'map' in kwargs:
+ map = asyncore.socket_map
+ else:
+ map = kwargs['map']
self._map = self.handler._map = map
- if use_poll and hasattr(asyncore.select, 'poll'):
- poll_fun = asyncore.poll2
- else:
- poll_fun = asyncore.poll
-
- if count is None:
- log("Serving FTP on %s:%s" %self.socket.getsockname()[:2])
- try:
- while map or _tasks:
- if map:
- poll_fun(timeout, map)
- if _tasks:
- _scheduler()
- except (KeyboardInterrupt, SystemExit, asyncore.ExitNow):
- log("Shutting down FTP server.")
- self.close_all()
- else:
- while (map or _tasks) and count > 0:
- if map:
- poll_fun(timeout, map)
- if _tasks:
- _scheduler()
- count = count - 1
+ try:
+ # FIX #16, #26
+ # use_poll specifies whether to use select module's poll()
+ # with asyncore or whether to use asyncore's own poll()
+ # method Python versions < 2.4 need use_poll set to False
+ # This breaks on OS X systems if use_poll is set to True.
+ # All systems seem to work fine with it set to False
+ # (tested on Linux, Windows, and OS X platforms)
+ if kwargs:
+ asyncore.loop(**kwargs)
+ else:
+ asyncore.loop(timeout=1.0, use_poll=False)
+ except (KeyboardInterrupt, SystemExit, asyncore.ExitNow):
+ log("Shutting down FTPd.")
+ self.close_all()
def handle_accept(self):
"""Called when remote client initiates a connection."""
- try:
- sock_obj, addr = self.accept()
- except TypeError:
- # for some reason sometimes accept() returns None instead
- # of a socket
- return
+ sock_obj, addr = self.accept()
log("[]%s:%s Connected." %addr[:2])
handler = self.handler(sock_obj, self)
@@ -3268,7 +3047,7 @@ class FTPServer(asyncore.dispatcher):
self.close()
def close_all(self, map=None, ignore_all=False):
- """Stop serving and also disconnects all currently connected
+ """Stop serving; close all existent connections disconnecting
clients.
- (dict) map:
@@ -3281,11 +3060,13 @@ class FTPServer(asyncore.dispatcher):
Implementation note:
- This is how asyncore.close_all() is implemented starting from
+ Instead of using the current asyncore.close_all() function
+ which only close sockets, we iterate over all existent channels
+ calling close() method for each one of them, avoiding memory
+ leaks.
+
+ This is how asyncore.close_all() function should work in
Python 2.6.
- The previous versions of close_all() instead of iteratating over
- all opened channels and calling close() method for each one
- of them only closed sockets generating memory leaks.
"""
if map is None:
map = self._map
@@ -3307,13 +3088,15 @@ class FTPServer(asyncore.dispatcher):
def test():
# cmd line usage (provide a read-only anonymous ftp server):
- # python -m pyftpdlib.ftpserver
+ # python -m pyftpdlib.FTPServer
authorizer = DummyAuthorizer()
- authorizer.add_anonymous(os.getcwd())
+ authorizer.add_anonymous(os.getcwd(), perm='elradfmw')
FTPHandler.authorizer = authorizer
- address = ('', 21)
+ address = ('', 8021)
ftpd = FTPServer(address, FTPHandler)
ftpd.serve_forever()
if __name__ == '__main__':
test()
+
+# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:
diff --git a/addons/document/nodes.py b/addons/document/nodes.py
new file mode 100644
index 00000000000..19efe97c7c1
--- /dev/null
+++ b/addons/document/nodes.py
@@ -0,0 +1,776 @@
+# -*- coding: utf-8 -*-
+##############################################################################
+#
+# OpenERP, Open Source Management Solution
+# Copyright (C) 2004-2009 Tiny SPRL ().
+#
+# 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 .
+#
+##############################################################################
+
+import base64
+
+from osv import osv, fields
+from osv.orm import except_orm
+import urlparse
+import pooler
+from tools.safe_eval import safe_eval
+
+import os
+import time
+
+#
+# An object that represent an uri
+# path: the uri of the object
+# content: the Content it belongs to (_print.pdf)
+# type: content or collection
+# content: objct = res.partner
+# collection: object = directory, object2 = res.partner
+# file: objct = ir.attachement
+# root: if we are at the first directory of a ressource
+#
+
+def get_node_context(cr, uid, context):
+ return node_context(cr,uid,context)
+
+class node_context(object):
+ """ This is the root node, representing access to some particular
+ context """
+ cached_roots = {}
+
+ def __init__(self, cr, uid, context=None):
+ # we don't cache the cr!
+ #self.cr = cr
+ self.uid = uid
+ self.context = context
+ self._dirobj = pooler.get_pool(cr.dbname).get('document.directory')
+ assert self._dirobj
+ self.rootdir = self._dirobj._get_root_directory(cr,uid,context)
+
+ def get_uri(self, cr, uri):
+ """ Although this fn passes back to doc.dir, it is needed since
+ it is a potential caching point """
+
+ (ndir, duri) = self._dirobj._locate_child(cr,self.uid, self.rootdir,uri, None, self)
+
+ while duri:
+ ndir = ndir.child(cr, duri[0])
+ if not ndir:
+ return False
+ duri = duri[1:]
+ return ndir
+
+
+class node_database():
+ """ A node representing the database directory
+ Useless?
+ """
+ def __init__(self,ncontext):
+ self.nctx = ncontext
+
+
+
+
+class node_class(object):
+ """ this is a superclass for our inodes
+ It is an API for all code that wants to access the document files.
+ Nodes have attributes which contain usual file properties
+ """
+ our_type = 'baseclass'
+ def __init__(self, path, parent, context):
+ assert isinstance(context,node_context)
+ assert (not parent ) or isinstance(parent,node_class)
+ self.path = path
+ self.context = context
+ self.type=self.our_type
+ self.parent = parent
+ self.mimetype = 'application/octet-stream'
+ self.create_date = None
+ self.write_date = None
+ self.content_length = 0
+ # dynamic context:
+ self.dctx = {}
+ if parent:
+ self.dctx = parent.dctx.copy()
+ self.displayname = 'Object'
+
+ def full_path(self):
+ if self.parent:
+ s = self.parent.full_path()
+ else:
+ s = []
+ if isinstance(self.path,list):
+ s+=self.path
+ else:
+ s.append(self.path)
+ return s
+
+ def children(self, cr):
+ print "node_class.children()"
+ return [] #stub
+
+ def child(self,cr, name):
+ print "node_class.child()"
+ return None
+
+ def path_get(self):
+ print "node_class.path_get()"
+ return False
+
+ def get_data(self,cr):
+ raise TypeError('no data for %s'% self.type)
+
+ def _get_storage(self,cr):
+ raise RuntimeError("no storage for base class")
+
+ def get_etag(self,cr):
+ """ Get a tag, unique per object + modification.
+
+ see. http://tools.ietf.org/html/rfc2616#section-13.3.3 """
+ return self._get_ttag(cr) + ':' + self._get_wtag(cr)
+
+ def _get_wtag(self,cr):
+ """ Return the modification time as a unique, compact string """
+ if self.write_date:
+ wtime = time.mktime(time.strptime(self.write_date,'%Y-%m-%d %H:%M:%S'))
+ else: wtime = time.time()
+ return str(wtime)
+
+ def _get_ttag(self,cr):
+ """ Get a unique tag for this type/id of object.
+ Must be overriden, so that each node is uniquely identified.
+ """
+ print "node_class.get_ttag()",self
+ raise RuntimeError("get_etag stub()")
+
+ def get_dav_props(self, cr):
+ """ If this class has special behaviour for GroupDAV etc, export
+ its capabilities """
+ return {}
+
+ def get_dav_eprop(self,cr,ns,prop):
+ return None
+
+class node_dir(node_class):
+ our_type = 'collection'
+ def __init__(self,path, parent, context, dirr, dctx=None):
+ super(node_dir,self).__init__(path, parent,context)
+ self.dir_id = dirr.id
+ #todo: more info from dirr
+ self.mimetype = 'application/x-directory'
+ # 'httpd/unix-directory'
+ self.create_date = dirr.create_date
+ # TODO: the write date should be MAX(file.write)..
+ self.write_date = dirr.write_date or dirr.create_date
+ self.content_length = 0
+ if dctx:
+ self.dctx.update(dctx)
+ dc2 = self.context.context
+ dc2.update(self.dctx)
+ dc2['dir_id'] = self.dir_id
+ self.displayname = dirr.name
+ for dfld in dirr.dctx_ids:
+ try:
+ self.dctx['dctx_' + dfld.field] = safe_eval(dfld.expr,dc2)
+ except Exception,e:
+ print "Cannot eval %s" % dfld.expr
+ print e
+ pass
+
+ def children(self,cr):
+ return self._child_get(cr) + self._file_get(cr)
+
+ def child(self,cr, name):
+ res = self._child_get(cr,name)
+ if res:
+ return res[0]
+ res = self._file_get(cr,name)
+ if res:
+ return res[0]
+ return None
+
+ def _file_get(self,cr, nodename=False):
+ res = []
+ cntobj = self.context._dirobj.pool.get('document.directory.content')
+ uid = self.context.uid
+ ctx = self.context.context.copy()
+ ctx.update(self.dctx)
+ where = [('directory_id','=',self.dir_id) ]
+ ids = cntobj.search(cr,uid,where,context=ctx)
+ for content in cntobj.browse(cr,uid,ids,context=ctx):
+ res3 = cntobj._file_get(cr,self,nodename,content)
+ if res3:
+ res.extend(res3)
+
+ return res
+
+ def get_dav_props(self, cr):
+ res = {}
+ cntobj = self.context._dirobj.pool.get('document.directory.content')
+ uid = self.context.uid
+ ctx = self.context.context.copy()
+ ctx.update(self.dctx)
+ where = [('directory_id','=',self.dir_id) ]
+ ids = cntobj.search(cr,uid,where,context=ctx)
+ for content in cntobj.browse(cr,uid,ids,context=ctx):
+ if content.extension == '.ics': # FIXME: call the content class!
+ res['http://groupdav.org/'] = ('resourcetype',)
+ break
+ return res
+
+ def get_dav_eprop(self,cr,ns,prop):
+ if ns != 'http://groupdav.org/' or prop != 'resourcetype':
+ print "Who asked for %s:%s?" % (ns,prop)
+ return None
+ res = {}
+ cntobj = self.context._dirobj.pool.get('document.directory.content')
+ uid = self.context.uid
+ ctx = self.context.context.copy()
+ ctx.update(self.dctx)
+ where = [('directory_id','=',self.dir_id) ]
+ ids = cntobj.search(cr,uid,where,context=ctx)
+ for content in cntobj.browse(cr,uid,ids,context=ctx):
+ if content.extension == '.ics': # FIXME: call the content class!
+ return ('vevent-collection','http://groupdav.org/')
+ return None
+
+ def _child_get(self,cr,name = None):
+ dirobj = self.context._dirobj
+ uid = self.context.uid
+ ctx = self.context.context.copy()
+ ctx.update(self.dctx)
+ where = [('parent_id','=',self.dir_id) ]
+ if name:
+ where.append(('name','=',name))
+ ids = dirobj.search(cr, uid, where,context=ctx)
+ res = []
+ if ids:
+ for dirr in dirobj.browse(cr,uid,ids,context=ctx):
+ if dirr.type == 'directory':
+ res.append(node_dir(dirr.name,self,self.context,dirr))
+ elif dirr.type == 'ressource':
+ res.append(node_res_dir(dirr.name,self,self.context,dirr))
+
+ fil_obj=dirobj.pool.get('ir.attachment')
+ #where2 = where # + [('res_model', '=', None)]
+ ids = fil_obj.search(cr,uid,where,context=ctx)
+ if ids:
+ for fil in fil_obj.browse(cr,uid,ids,context=ctx):
+ res.append(node_file(fil.name,self,self.context,fil))
+
+ return res
+
+ def create_child(self,cr,path,data):
+ """ API function to create a child file object and node
+ Return the node_* created
+ """
+ dirobj = self.context._dirobj
+ uid = self.context.uid
+ ctx = self.context.context.copy()
+ ctx.update(self.dctx)
+ fil_obj=dirobj.pool.get('ir.attachment')
+ val = {
+ 'name': path,
+ 'datas_fname': path,
+ 'parent_id': self.dir_id,
+ # Datas are not set here
+ }
+
+ fil_id = fil_obj.create(cr,uid, val, context=ctx)
+ fil = fil_obj.browse(cr,uid,fil_id,context=ctx)
+ fnode = node_file(path,self,self.context,fil)
+ fnode.set_data(cr,data,fil)
+ return fnode
+
+ def _get_ttag(self,cr):
+ return 'dir-%d' % self.dir_id
+
+class node_res_dir(node_class):
+ """ A special sibling to node_dir, which does only contain dynamically
+ created folders foreach resource in the foreign model.
+ All folders should be of type node_res_obj and merely behave like
+ node_dirs (with limited domain).
+ """
+ our_type = 'collection'
+ def __init__(self,path, parent, context, dirr, dctx=None ):
+ super(node_res_dir,self).__init__(path, parent,context)
+ self.dir_id = dirr.id
+ #todo: more info from dirr
+ self.mimetype = 'application/x-directory'
+ # 'httpd/unix-directory'
+ self.create_date = dirr.create_date
+ # TODO: the write date should be MAX(file.write)..
+ self.write_date = dirr.write_date or dirr.create_date
+ self.content_length = 0
+ self.res_model = dirr.ressource_type_id.model
+ self.resm_id = dirr.ressource_id
+ self.namefield = dirr.resource_field or 'name'
+ self.displayname = dirr.name
+ # Important: the domain is evaluated using the *parent* dctx!
+ self.domain = safe_eval(dirr.domain,self.dctx)
+ # and then, we add our own vars in the dctx:
+ if dctx:
+ self.dctx.update(dctx)
+
+ # and then, we prepare a dctx dict, for deferred evaluation:
+ self.dctx_dict = {}
+ for dfld in dirr.dctx_ids:
+ self.dctx_dict['dctx_' + dfld.field] = dfld.expr
+
+ def children(self,cr):
+ return self._child_get(cr)
+
+ def child(self,cr, name):
+ res = self._child_get(cr,name)
+ if res:
+ return res[0]
+ return None
+
+ def _child_get(self,cr,name = None):
+ """ return virtual children of resource, based on the
+ foreign object.
+
+ Note that many objects use NULL for a name, so we should
+ better call the name_search(),name_get() set of methods
+ """
+ obj = self.context._dirobj.pool.get(self.res_model)
+ if not obj:
+ print "couldn't find model", self.res_model
+ return []
+ uid = self.context.uid
+ ctx = self.context.context.copy()
+ ctx.update(self.dctx)
+ where = []
+ if self.domain:
+ where.append(self.domain)
+ if self.resm_id:
+ where.append(('id','=',self.resm_id))
+
+ if name:
+ where.append((self.namefield,'=',name))
+ # print "Where clause for %s" % self.res_model, where
+
+ resids = obj.search(cr,uid, where, context=ctx)
+ res = []
+ for bo in obj.browse(cr,uid,resids,context=ctx):
+ if not bo:
+ continue
+ name = getattr(bo,self.namefield)
+ if not name:
+ continue
+ # Yes! we can't do better but skip nameless records.
+ res.append(node_res_obj(name,self,self.context,self.res_model, bo))
+ return res
+
+ def _get_ttag(self,cr):
+ return 'rdir-%d' % self.dir_id
+
+class node_res_obj(node_class):
+ """ A special sibling to node_dir, which does only contain dynamically
+ created folders foreach resource in the foreign model.
+ All folders should be of type node_res_obj and merely behave like
+ node_dirs (with limited domain).
+ """
+ our_type = 'collection'
+ def __init__(self,path, parent, context, res_model, res_bo, res_id = None):
+ super(node_res_obj,self).__init__(path, parent,context)
+ assert parent
+ #todo: more info from dirr
+ self.dir_id = parent.dir_id
+ self.mimetype = 'application/x-directory'
+ # 'httpd/unix-directory'
+ self.create_date = parent.create_date
+ # TODO: the write date should be MAX(file.write)..
+ self.write_date = parent.write_date
+ self.content_length = 0
+ self.res_model = res_model
+ self.domain = parent.domain
+ self.displayname = path
+
+ if res_bo:
+ self.res_id = res_bo.id
+ dc2 = self.context.context
+ dc2.update(self.dctx)
+ dc2['res_model'] = res_model
+ dc2['res_id'] = res_bo.id
+ dc2['this'] = res_bo
+ for fld,expr in parent.dctx_dict.items():
+ try:
+ self.dctx[fld] = safe_eval(expr,dc2)
+ except Exception,e:
+ print "Cannot eval %s for %s" % (expr, fld)
+ print e
+ pass
+ else:
+ self.res_id = res_id
+
+ def children(self,cr):
+ return self._child_get(cr) + self._file_get(cr)
+
+ def child(self,cr, name):
+ res = self._child_get(cr,name)
+ if res:
+ return res[0]
+ res = self._file_get(cr,name)
+ if res:
+ return res[0]
+ return None
+
+ def _file_get(self,cr, nodename=False):
+ res = []
+ cntobj = self.context._dirobj.pool.get('document.directory.content')
+ uid = self.context.uid
+ ctx = self.context.context.copy()
+ ctx.update(self.dctx)
+ where = [('directory_id','=',self.dir_id) ]
+ #if self.domain:
+ # where.extend(self.domain)
+ # print "res_obj file_get clause", where
+ ids = cntobj.search(cr,uid,where,context=ctx)
+ for content in cntobj.browse(cr,uid,ids,context=ctx):
+ res3 = cntobj._file_get(cr,self,nodename,content, context=ctx)
+ if res3:
+ res.extend(res3)
+
+ return res
+
+ def get_dav_props(self, cr):
+ res = {}
+ cntobj = self.context._dirobj.pool.get('document.directory.content')
+ uid = self.context.uid
+ ctx = self.context.context.copy()
+ ctx.update(self.dctx)
+ where = [('directory_id','=',self.dir_id) ]
+ ids = cntobj.search(cr,uid,where,context=ctx)
+ for content in cntobj.browse(cr,uid,ids,context=ctx):
+ if content.extension == '.ics': # FIXME: call the content class!
+ res['http://groupdav.org/'] = ('resourcetype',)
+ return res
+
+ def get_dav_eprop(self,cr,ns,prop):
+ if ns != 'http://groupdav.org/' or prop != 'resourcetype':
+ print "Who asked for %s:%s?" % (ns,prop)
+ return None
+ res = {}
+ cntobj = self.context._dirobj.pool.get('document.directory.content')
+ uid = self.context.uid
+ ctx = self.context.context.copy()
+ ctx.update(self.dctx)
+ where = [('directory_id','=',self.dir_id) ]
+ ids = cntobj.search(cr,uid,where,context=ctx)
+ for content in cntobj.browse(cr,uid,ids,context=ctx):
+ if content.extension == '.ics': # FIXME: call the content class!
+ return ('vevent-collection','http://groupdav.org/')
+ return None
+
+ def _child_get(self,cr,name = None):
+ dirobj = self.context._dirobj
+ uid = self.context.uid
+ ctx = self.context.context.copy()
+ ctx.update(self.dctx)
+ where = [('parent_id','=',self.dir_id) ]
+ if name:
+ where.append(('name','=',name))
+ ids = dirobj.search(cr, uid, where,context=ctx)
+ res = []
+ if ids:
+ for dirr in dirobj.browse(cr,uid,ids,context=ctx):
+ if dirr.type == 'directory':
+ res.append(node_res_obj(dirr.name,self,self.context,self.res_model,res_bo = None, res_id = self.res_id))
+ elif dirr.type == 'ressource':
+ # child resources can be controlled by properly set dctx
+ res.append(node_res_dir(dirr.name,self,self.context,dirr))
+
+ fil_obj=dirobj.pool.get('ir.attachment')
+ where2 = where + [('res_model', '=', self.res_model), ('res_id','=',self.res_id)]
+ # print "where clause for dir_obj", where2
+ ids = fil_obj.search(cr,uid,where2,context=ctx)
+ if ids:
+ for fil in fil_obj.browse(cr,uid,ids,context=ctx):
+ res.append(node_file(fil.name,self,self.context,fil))
+
+ return res
+
+ def create_child(self,cr,path,data):
+ """ API function to create a child file object and node
+ Return the node_* created
+ """
+ dirobj = self.context._dirobj
+ uid = self.context.uid
+ ctx = self.context.context.copy()
+ ctx.update(self.dctx)
+ fil_obj=dirobj.pool.get('ir.attachment')
+ val = {
+ 'name': path,
+ 'datas_fname': path,
+ 'parent_id': self.dir_id,
+ 'res_model': self.res_model,
+ 'res_id': self.res_id,
+ # Datas are not set here
+ }
+
+ fil_id = fil_obj.create(cr,uid, val, context=ctx)
+ fil = fil_obj.browse(cr,uid,fil_id,context=ctx)
+ fnode = node_file(path,self,self.context,fil)
+ fnode.set_data(cr,data,fil)
+ return fnode
+
+ def _get_ttag(self,cr):
+ return 'rodir-%d-%d' % (self.dir_id,self.res_id)
+
+class node_file(node_class):
+ our_type = 'file'
+ def __init__(self,path, parent, context, fil):
+ super(node_file,self).__init__(path, parent,context)
+ self.file_id = fil.id
+ #todo: more info from ir_attachment
+ if fil.file_type and '/' in fil.file_type:
+ self.mimetype = fil.file_type
+ self.create_date = fil.create_date
+ self.write_date = fil.write_date or fil.create_date
+ self.content_length = fil.file_size
+ self.displayname = fil.name
+
+ # This only propagates the problem to get_data. Better
+ # fix those files to point to the root dir.
+ if fil.parent_id:
+ self.storage_id = fil.parent_id.storage_id.id
+ else:
+ self.storage_id = None
+
+ def get_data(self, cr, fil_obj = None):
+ """ Retrieve the data for some file.
+ fil_obj may optionally be specified, and should be a browse object
+ for the file. This is useful when the caller has already initiated
+ the browse object. """
+ # this is where storage kicks in..
+ stor = self.storage_id
+ assert stor
+ stobj = self.context._dirobj.pool.get('document.storage')
+ return stobj.get_data(cr,self.context.uid,stor, self,self.context.context, fil_obj)
+
+ def get_data_len(self, cr, fil_obj = None):
+ # TODO: verify with the storage object!
+ return self.content_length
+
+ def set_data(self, cr, data, fil_obj = None):
+ """ Store data at some file.
+ fil_obj may optionally be specified, and should be a browse object
+ for the file. This is useful when the caller has already initiated
+ the browse object. """
+ # this is where storage kicks in..
+ stor = self.storage_id
+ assert stor
+ stobj = self.context._dirobj.pool.get('document.storage')
+ return stobj.set_data(cr,self.context.uid,stor, self, data, self.context.context, fil_obj)
+
+ def _get_ttag(self,cr):
+ return 'file-%d' % self.file_id
+
+class node_content(node_class):
+ our_type = 'content'
+ def __init__(self,path, parent, context, cnt, dctx = None, act_id=None):
+ super(node_content,self).__init__(path, parent,context)
+ self.cnt_id = cnt.id
+ self.create_date = False
+ self.write_date = False
+ self.content_length = False
+ self.extension = cnt.extension
+ self.report_id = cnt.report_id and cnt.report_id.id
+ #self.mimetype = cnt.extension.
+ self.displayname = path
+ if dctx:
+ self.dctx.update(dctx)
+ self.act_id = act_id
+
+ def fill_fields(self,cr,dctx = None):
+ """ Try to read the object and fill missing fields, like mimetype,
+ dates etc.
+ This function must be different from the constructor, because
+ it uses the db cursor.
+ """
+
+ cr.execute('SELECT DISTINCT mimetype FROM document_directory_content_type WHERE active AND code = %s;',
+ (self.extension,))
+ res = cr.fetchall()
+ if res and res[0][0]:
+ self.mimetype = res[0][0]
+
+
+ def get_data(self, cr, fil_obj = None):
+ cntobj = self.context._dirobj.pool.get('document.directory.content')
+ ctx = self.context.context.copy()
+ ctx.update(self.dctx)
+ data = cntobj.process_read(cr,self.context.uid,self,ctx)
+ if data:
+ self.content_length = len(data)
+ return data
+
+ def get_data_len(self, cr, fil_obj = None):
+ if not self.content_length:
+ self.get_data(cr,fil_obj)
+ return self.content_length
+
+ def set_data(self, cr, data, fil_obj = None):
+ cntobj = self.context._dirobj.pool.get('document.directory.content')
+ ctx = self.context.context.copy()
+ ctx.update(self.dctx)
+ return cntobj.process_write(cr,self.context.uid,self, data,ctx)
+
+ def _get_ttag(self,cr):
+ return 'cnt-%d%s' % (self.cnt_id,(self.act_id and ('-' + str(self.act_id))) or '')
+
+class old_class():
+ # the old code, remove..
+ def __init__(self, cr, uid, path, object, object2=False, context={}, content=False, type='collection', root=False):
+ self.cr = cr
+ def _file_get(self, nodename=False):
+ if not self.object:
+ return []
+ pool = pooler.get_pool(self.cr.dbname)
+ fobj = pool.get('ir.attachment')
+ res2 = []
+ where = []
+ if self.object2:
+ where.append( ('res_model','=',self.object2._name) )
+ where.append( ('res_id','=',self.object2.id) )
+ else:
+ where.append( ('parent_id','=',self.object.id) )
+ where.append( ('res_id','=',False) )
+ if nodename:
+ where.append( (fobj._rec_name,'=',nodename) )
+ for content in self.object.content_ids:
+ res3 = content._table._file_get(self,nodename,content)
+ if res3:
+ res2.extend(res3)
+
+ ids = fobj.search(self.cr, self.uid, where+[ ('parent_id','=',self.object and self.object.id or False) ])
+ if self.object and self.root and (self.object.type=='ressource'):
+ ids += fobj.search(self.cr, self.uid, where+[ ('parent_id','=',False) ])
+ res = fobj.browse(self.cr, self.uid, ids, context=self.context)
+ return map(lambda x: node_class(self.cr, self.uid, self.path+'/'+eval('x.'+fobj._rec_name), x, False, context=self.context, type='file', root=False), res) + res2
+
+ def get_translation(self,value,lang):
+ # Must go, it works on arbitrary models and could be ambiguous.
+ result = value
+ pool = pooler.get_pool(self.cr.dbname)
+ translation_ids = pool.get('ir.translation').search(self.cr, self.uid, [('value','=',value),('lang','=',lang),('type','=','model')])
+ if len(translation_ids):
+ tran_id = translation_ids[0]
+ translation = pool.get('ir.translation').read(self.cr, self.uid, tran_id, ['res_id','name'])
+ res_model,field_name = tuple(translation['name'].split(','))
+ res_id = translation['res_id']
+ res = pool.get(res_model).read(self.cr, self.uid, res_id, [field_name])
+ if res:
+ result = res[field_name]
+ return result
+
+ def directory_list_for_child(self,nodename,parent=False):
+ pool = pooler.get_pool(self.cr.dbname)
+ where = []
+ if nodename:
+ nodename = self.get_translation(nodename, self.context['lang'])
+ where.append(('name','=',nodename))
+ if (self.object and self.object.type=='directory') or not self.object2:
+ where.append(('parent_id','=',self.object and self.object.id or False))
+ else:
+ where.append(('parent_id','=',False))
+ if self.object:
+ where.append(('ressource_parent_type_id','=',self.object.ressource_type_id.id))
+ else:
+ where.append(('ressource_parent_type_id','=',False))
+
+ ids = pool.get('document.directory').search(self.cr, self.uid, where+[('ressource_id','=',0)])
+ if self.object2:
+ ids += pool.get('document.directory').search(self.cr, self.uid, where+[('ressource_id','=',self.object2.id)])
+ res = pool.get('document.directory').browse(self.cr, self.uid, ids, self.context)
+ return res
+
+ def _child_get(self, nodename=False):
+ if self.type not in ('collection','database'):
+ return []
+ res = self.directory_list_for_child(nodename)
+ result= map(lambda x: node_class(self.cr, self.uid, self.path+'/'+x.name, x, x.type=='directory' and self.object2 or False, context=self.context, root=self.root), res)
+ if self.type=='database':
+ pool = pooler.get_pool(self.cr.dbname)
+ fobj = pool.get('ir.attachment')
+ vargs = [('parent_id','=',False),('res_id','=',False)]
+ if nodename:
+ vargs.append((fobj._rec_name,'=',nodename))
+ file_ids=fobj.search(self.cr,self.uid,vargs)
+
+ res = fobj.browse(self.cr, self.uid, file_ids, context=self.context)
+ result +=map(lambda x: node_class(self.cr, self.uid, self.path+'/'+eval('x.'+fobj._rec_name), x, False, context=self.context, type='file', root=self.root), res)
+ if self.type=='collection' and self.object.type=="ressource":
+ where = self.object.domain and eval(self.object.domain, {'active_id':self.root, 'uid':self.uid}) or []
+ pool = pooler.get_pool(self.cr.dbname)
+ obj = pool.get(self.object.ressource_type_id.model)
+ _dirname_field = obj._rec_name
+ if len(obj.fields_get(self.cr, self.uid, ['dirname'])):
+ _dirname_field = 'dirname'
+
+ name_for = obj._name.split('.')[-1]
+ if nodename and nodename.find(name_for) == 0 :
+ id = int(nodename.replace(name_for,''))
+ where.append(('id','=',id))
+ elif nodename:
+ if nodename.find('__') :
+ nodename=nodename.replace('__','/')
+ for invalid in INVALID_CHARS:
+ if nodename.find(INVALID_CHARS[invalid]) :
+ nodename=nodename.replace(INVALID_CHARS[invalid],invalid)
+ nodename = self.get_translation(nodename, self.context['lang'])
+ where.append((_dirname_field,'=',nodename))
+
+ if self.object.ressource_tree:
+ if obj._parent_name in obj.fields_get(self.cr,self.uid):
+ where.append((obj._parent_name,'=',self.object2 and self.object2.id or False))
+ ids = obj.search(self.cr, self.uid, where)
+ res = obj.browse(self.cr, self.uid, ids,self.context)
+ result+= map(lambda x: node_class(self.cr, self.uid, self.path+'/'+x.name.replace('/','__'), self.object, x, context=self.context, root=x.id), res)
+ return result
+ else :
+ if self.object2:
+ return result
+ else:
+ if self.object2:
+ return result
+
+
+ ids = obj.search(self.cr, self.uid, where)
+ res = obj.browse(self.cr, self.uid, ids,self.context)
+ for r in res:
+ if len(obj.fields_get(self.cr, self.uid, [_dirname_field])):
+ r.name = eval('r.'+_dirname_field)
+ else:
+ r.name = False
+ if not r.name:
+ r.name = name_for + '%d'%r.id
+ for invalid in INVALID_CHARS:
+ if r.name.find(invalid) :
+ r.name = r.name.replace(invalid,INVALID_CHARS[invalid])
+ result2 = map(lambda x: node_class(self.cr, self.uid, self.path+'/'+x.name.replace('/','__'), self.object, x, context=self.context, root=x.id), res)
+ if result2:
+ if self.object.ressource_tree:
+ result += result2
+ else:
+ result = result2
+ return result
+
+
+ def path_get(self):
+ path = self.path
+ if self.path[0]=='/':
+ path = self.path[1:]
+ return path
diff --git a/addons/document/odt2txt.py b/addons/document/odt2txt.py
index 9ee9d088b0a..96acc6f7bd1 100644
--- a/addons/document/odt2txt.py
+++ b/addons/document/odt2txt.py
@@ -1,3 +1,4 @@
+#!/usr/bin/python
# -*- coding: utf-8 -*-
##############################################################################
#
diff --git a/addons/document/std_index.py b/addons/document/std_index.py
new file mode 100644
index 00000000000..730e5082a1b
--- /dev/null
+++ b/addons/document/std_index.py
@@ -0,0 +1,111 @@
+# -*- coding: utf-8 -*-
+##############################################################################
+#
+# OpenERP, Open Source Management Solution
+# Copyright (C) 2004-2009 Tiny SPRL ().
+#
+# 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 .
+#
+##############################################################################
+
+import StringIO
+import odt2txt
+
+from content_index import indexer, cntIndex
+
+
+def _to_unicode(s):
+ try:
+ return s.decode('utf-8')
+ except UnicodeError:
+ try:
+ return s.decode('latin')
+ except UnicodeError:
+ try:
+ return s.encode('ascii')
+ except UnicodeError:
+ return s
+
+class TxtIndex(indexer):
+ def _getMimeTypes(self):
+ return ['text/plain','text/html','text/diff','text/xml', 'text/*',
+ 'application/xml']
+
+ def _getExtensions(self):
+ return ['.txt', '.py']
+
+ def _doIndexContent(self,content):
+ return content
+
+cntIndex.register(TxtIndex())
+
+class DocIndex(indexer):
+ def _getMimeTypes(self):
+ return [ 'application/ms-word']
+
+ def _getExtensions(self):
+ return ['.doc']
+
+ def _doIndexFile(self,fname):
+ fp = Popen(['antiword',fname], shell=False, stdout=PIPE).stdout
+ return _to_unicode( fp.read())
+
+cntIndex.register(DocIndex())
+
+class PdfIndex(indexer):
+ def _getMimeTypes(self):
+ return [ 'application/pdf']
+
+ def _getExtensions(self):
+ return ['.pdf']
+
+ def _doIndexFile(self,fname):
+ fp = Popen(['pdftotext', '-enc', 'UTF-8', '-nopgbrk', fname, '-'], shell=False, stdout=PIPE).stdout
+ return _to_unicode( fp.read())
+
+cntIndex.register(PdfIndex())
+
+class ImageNoIndex(indexer):
+ def _getMimeTypes(self):
+ return [ 'image/*']
+
+ def _getExtensions(self):
+ #better return no extension, and let 'file' do its magic
+ return []
+ #return ['.png','.jpg','.gif','.jpeg','.bmp','.tiff']
+
+ def _doIndexContent(self,content):
+ return 'image'
+
+
+cntIndex.register(ImageNoIndex())
+
+#class Doc(indexer):
+ #def _getDefMime(self,ext):
+
+#def content_index(content, filename=None, content_type=None):
+ #fname,ext = os.path.splitext(filename)
+ #result = ''
+ #elif ext in ('.xls','.ods','.odt','.odp'):
+ #s = StringIO.StringIO(content)
+ #o = odt2txt.OpenDocumentTextFile(s)
+ #result = _to_unicode(o.toString())
+ #s.close()
+ #elif ext in ('.txt','.py','.patch','.html','.csv','.xml'):
+ #result = content
+ #else:
+ #result = content
+ #return result
+
+#eof
diff --git a/addons/document/test_cindex.py b/addons/document/test_cindex.py
new file mode 100755
index 00000000000..9993ffad425
--- /dev/null
+++ b/addons/document/test_cindex.py
@@ -0,0 +1,46 @@
+#!/usr/bin/python
+
+import sys
+import os
+import glob
+
+from optparse import OptionParser
+
+parser = OptionParser()
+parser.add_option("-q", "--quiet",
+ action="store_false", dest="verbose", default=True,
+ help="don't print status messages to stdout")
+
+parser.add_option("-C", "--content",
+ action="store_true", dest="docontent", default=False,
+ help="Disect content, rather than the file.")
+
+(options, args) = parser.parse_args()
+
+import content_index, std_index
+
+from content_index import cntIndex
+
+for fname in args:
+ try:
+ if options.docontent:
+ fp = open(fname,'rb')
+ content = fp.read()
+ fp.close()
+ res = cntIndex.doIndex(content,fname,None,None,True)
+ else:
+ res = cntIndex.doIndex(None, fname, None, fname,True)
+
+ if res:
+ print "Result: ", res[0]
+ print res[1]
+ else:
+ print "No result"
+ except Exception,e:
+ import traceback,sys
+ tb_s = reduce(lambda x, y: x+y, traceback.format_exception( sys.exc_type, sys.exc_value, sys.exc_traceback))
+ print "Traceback:",tb_s
+ print "Exception: ",e
+
+
+#eof
From 36c6824e5c14c3766f194ecc4eed6f1324313cfc Mon Sep 17 00:00:00 2001
From: p_christeas <>
Date: Wed, 2 Dec 2009 11:15:55 +0530
Subject: [PATCH 02/19] [IMP] document_ics : taken improveemnt from xrg-addons
bzr revid: hmo@tinyerp.com-20091202054555-tm16uo4xwc45kgj0
---
addons/document_ics/__init__.py | 2 +-
addons/document_ics/document_data.xml | 1 +
addons/document_ics/document_ics.py | 335 ++++++++++++++++++++++++++
addons/document_ics/document_view.xml | 21 +-
4 files changed, 355 insertions(+), 4 deletions(-)
create mode 100644 addons/document_ics/document_ics.py
diff --git a/addons/document_ics/__init__.py b/addons/document_ics/__init__.py
index 2be140f3f0f..4c5cacb0f74 100644
--- a/addons/document_ics/__init__.py
+++ b/addons/document_ics/__init__.py
@@ -19,6 +19,6 @@
#
##############################################################################
-import document
+import document_ics
import document_ics_config_wizard
# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:
diff --git a/addons/document_ics/document_data.xml b/addons/document_ics/document_data.xml
index d2e83a35f40..55522621e17 100644
--- a/addons/document_ics/document_data.xml
+++ b/addons/document_ics/document_data.xml
@@ -5,6 +5,7 @@
.ics
ICS Calendar
+ text/calendar
diff --git a/addons/document_ics/document_ics.py b/addons/document_ics/document_ics.py
new file mode 100644
index 00000000000..61bddb2c306
--- /dev/null
+++ b/addons/document_ics/document_ics.py
@@ -0,0 +1,335 @@
+# -*- coding: utf-8 -*-
+##############################################################################
+#
+# OpenERP, Open Source Management Solution
+# Copyright (C) 2004-2009 Tiny SPRL ().
+#
+# 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 .
+#
+##############################################################################
+
+from osv import osv, fields
+from osv.orm import except_orm
+import os
+import StringIO
+import base64
+import datetime
+import time
+import random
+import tools
+import re
+
+from document import nodes
+
+from tools.safe_eval import safe_eval
+
+ICS_TAGS = {
+ 'summary':'normal',
+ 'uid':'normal' ,
+ 'dtstart':'date' ,
+ 'dtend':'date' ,
+ 'created':'date' ,
+ 'dtstamp':'date' ,
+ 'last-modified':'normal' ,
+ 'url':'normal' ,
+ 'attendee':'multiple',
+ 'location':'normal',
+ 'categories': 'normal',
+ 'description':'normal',
+
+ # TODO: handle the 'duration' property
+}
+
+ICS_FUNCTIONS = [
+ ('field', 'Use the field'),
+ ('const', 'Expression as constant'),
+ ('hours', 'Interval in hours'),
+ ]
+
+class document_directory_ics_fields(osv.osv):
+ _name = 'document.directory.ics.fields'
+ _columns = {
+ 'field_id': fields.many2one('ir.model.fields', 'Open ERP Field'),
+ 'name': fields.selection(map(lambda x: (x, x), ICS_TAGS.keys()), 'ICS Value', required=True),
+ 'content_id': fields.many2one('document.directory.content', 'Content', required=True, ondelete='cascade'),
+ 'expr': fields.char("Expression", size=64),
+ 'fn': fields.selection(ICS_FUNCTIONS,'Function',help="Alternate method of calculating the value", required=True)
+ }
+ _defaults = {
+ 'fn': lambda *a: 'field',
+ }
+
+document_directory_ics_fields()
+
+class document_directory_content(osv.osv):
+ _inherit = 'document.directory.content'
+ __rege = re.compile(r'OpenERP-([\w|\.]+)_([0-9]+)@(\w+)$')
+ _columns = {
+ 'object_id': fields.many2one('ir.model', 'Object', oldname= 'ics_object_id'),
+ 'obj_iterate': fields.boolean('Iterate object',help="If set, a separate instance will be created for each record of Object"),
+ 'fname_field': fields.char("Filename field",size=16,help="The field of the object used in the filename. Has to be a unique identifier."),
+ 'ics_domain': fields.char('Domain', size=64),
+ 'ics_field_ids': fields.one2many('document.directory.ics.fields', 'content_id', 'Fields Mapping')
+ }
+ _defaults = {
+ 'ics_domain': lambda *args: '[]'
+ }
+ def _file_get(self, cr, node, nodename, content, context=None):
+ if not content.obj_iterate:
+ return super(document_directory_content, self)._file_get(cr,node,nodename,content)
+ else:
+ # print "iterate over ", content.object_id.model
+ mod = self.pool.get(content.object_id.model)
+ uid = node.context.uid
+ fname_fld = content.fname_field or 'id'
+ where = []
+ if node.domain:
+ where.append(node.domain)
+ if nodename:
+ # Reverse-parse the nodename to deduce the clause:
+ prefix = (content.prefix or '')
+ suffix = (content.suffix or '') + (content.extension or '')
+ if not nodename.startswith(prefix):
+ return False
+ if not nodename.endswith(suffix):
+ return False
+ tval = nodename[len(prefix):0 - len(suffix)]
+ where.append((fname_fld,'=',tval))
+ # print "ics iterate clause:", where
+ resids = mod.search(cr,uid,where,context=context)
+ if not resids:
+ return False
+
+ res2 = []
+ for ro in mod.read(cr,uid,resids,['id', fname_fld]):
+ tname = (content.prefix or '') + str(ro[fname_fld])
+ tname += (content.suffix or '') + (content.extension or '')
+ dctx2 = { 'active_id': ro['id'] }
+ if fname_fld:
+ dctx2['active_'+fname_fld] = ro[fname_fld]
+ n = nodes.node_content(tname, node, node.context,content,dctx=dctx2, act_id = ro['id'])
+ n.fill_fields(cr, dctx2)
+ res2.append(n)
+ return res2
+
+ def process_write(self, cr, uid, node, data, context=None):
+ if node.extension != '.ics':
+ return super(document_directory_content).process_write(cr, uid, node, data, context)
+ import vobject
+ parsedCal = vobject.readOne(data)
+ fields = {}
+ funcs = {}
+ fexprs = {}
+ content = self.browse(cr, uid, node.cnt_id, context)
+
+ idomain = {}
+ ctx = (context or {})
+ ctx.update(node.context.context.copy())
+ ctx.update(node.dctx)
+ # print "ICS domain: ", type(content.ics_domain), content.ics_domain
+ if content.ics_domain:
+ for d in safe_eval(content.ics_domain,ctx):
+ # TODO: operator?
+ idomain[d[0]]=d[2]
+ for n in content.ics_field_ids:
+ fields[n.name] = n.field_id.name and str(n.field_id.name)
+ funcs[n.name] = n.fn
+ fexprs[n.name] = n.expr
+
+ if 'uid' not in fields:
+ print "uid not in ", fields
+ # FIXME: should pass
+ return True
+ for child in parsedCal.getChildren():
+ result = {}
+ uuid = None
+
+ for event in child.getChildren():
+ enl = event.name.lower()
+ if enl =='uid':
+ uuid = event.value
+ if not enl in fields:
+ # print "skip", enl
+ continue
+ if fields[enl] and funcs[enl] == 'field':
+ if ICS_TAGS[enl]=='normal':
+ result[fields[enl]] = event.value.encode('utf8')
+ elif ICS_TAGS[enl]=='date':
+ result[fields[enl]] = event.value.strftime('%Y-%m-%d %H:%M:%S')
+
+ # print "Field ",enl, result[fields[enl]]
+ elif fields[enl] and funcs[enl] == 'hours':
+ ntag = fexprs[enl] or 'dtstart'
+ ts_start = child.getChildValue(ntag, default=False)
+ if not ts_start:
+ raise Exception("Cannot parse hours (for %s) without %s" % (enl, ntag))
+ ts_end = event.value
+ assert isinstance(ts_start, datetime.datetime)
+ assert isinstance(ts_end, datetime.datetime)
+ td = ts_end - ts_start
+ result[fields[enl]] = td.days * 24.0 + ( td.seconds / 3600.0)
+
+ # put other functions here..
+ else:
+ # print "Unhandled tag in ICS:", enl
+ pass
+ # end for
+
+ if not uuid:
+ print "Skipping cal", child
+ # FIXME: should pass
+ continue
+
+ cmodel = content.object_id.model
+ wexpr = False
+ if fields['uid']:
+ wexpr = [(fields['uid'], '=', uuid.encode('utf8'))]
+ else:
+ # Parse back the uid from 'OpenERP-%s_%s@%s'
+ wematch = self.__rege.match(uuid.encode('utf8'))
+ # TODO: perhaps also add the domain to wexpr, restrict.
+ if not wematch:
+ raise Exception("Cannot locate UID in %s" % uuid)
+ if wematch.group(3) != cr.dbname:
+ raise Exception("Object is not for our db!")
+ if content.object_id:
+ if wematch.group(1) != cmodel:
+ raise Exception("ICS must be at the wrong folder, this one is for %s" % cmodel)
+ else:
+ # TODO: perhaps guess the model from the iCal, is it safe?
+ pass
+
+ wexpr = [ ( 'id', '=', wematch.group(2) ) ]
+
+ # print "Looking at ", cmodel, " for ", wexpr
+ # print "domain=", idomain
+
+ fobj = self.pool.get(content.object_id.model)
+
+ if not wexpr:
+ id = False
+ else:
+ id = fobj.search(cr, uid, wexpr, context=context)
+
+ if isinstance(id, list):
+ if len(id) > 1:
+ raise Exception("Multiple matches found for ICS")
+ if id:
+ print "writting at %s#%d:" %(cmodel, id[0]), result
+ fobj.write(cr, uid, id, result, context=context)
+ else:
+ r = idomain.copy()
+ r.update(result)
+ print "creating at %s#%d:" %(cmodel, id), result
+ fobj.create(cr, uid, r, context=context)
+
+ return True
+
+ def process_read(self, cr, uid, node, context=None):
+ def ics_datetime(idate, short=False):
+ if short:
+ return datetime.date.fromtimestamp(time.mktime(time.strptime(idate, '%Y-%m-%d')))
+ else:
+ return datetime.datetime.strptime(idate, '%Y-%m-%d %H:%M:%S')
+
+ if node.extension != '.ics':
+ return super(document_directory_content).process_read(cr, uid, node, context)
+
+ import vobject
+ ctx = (context or {})
+ ctx.update(node.context.context.copy())
+ ctx.update(node.dctx)
+ content = self.browse(cr, uid, node.cnt_id, ctx)
+ obj_class = self.pool.get(content.object_id.model)
+
+ if content.ics_domain:
+ domain = safe_eval(content.ics_domain,ctx)
+ else:
+ domain = []
+ if node.act_id:
+ domain.append(('id','=',node.act_id))
+ # print "process read clause:",domain
+ ids = obj_class.search(cr, uid, domain, context=ctx)
+ cal = vobject.iCalendar()
+ for obj in obj_class.browse(cr, uid, ids):
+ event = cal.add('vevent')
+ # Fix dtstamp et last-modified with create and write date on the object line
+ perm = obj_class.perm_read(cr, uid, [obj.id], context)
+ event.add('created').value = ics_datetime(time.strftime('%Y-%m-%d %H:%M:%S'))
+ event.add('dtstamp').value = ics_datetime(perm[0]['create_date'][:19])
+ if perm[0]['write_date']:
+ event.add('last-modified').value = ics_datetime(perm[0]['write_date'][:19])
+ for field in content.ics_field_ids:
+ if field.field_id.name:
+ value = getattr(obj, field.field_id.name)
+ else: value = None
+ if (not value) and field.name=='uid':
+ value = 'OpenERP-%s_%s@%s' % (content.object_id.model, str(obj.id), cr.dbname,)
+ # Why? obj_class.write(cr, uid, [obj.id], {field.field_id.name: value})
+ if ICS_TAGS[field.name]=='normal':
+ if type(value)==type(obj):
+ value=value.name
+ event.add(field.name).value = tools.ustr(value) or ''
+ elif ICS_TAGS[field.name]=='date' and value:
+ if field.name == 'dtstart':
+ date_start = start_date = datetime.datetime.fromtimestamp(time.mktime(time.strptime(value , "%Y-%m-%d %H:%M:%S")))
+ if field.name == 'dtend' and ( isinstance(value, float) or field.fn == 'hours'):
+ value = (start_date + datetime.timedelta(hours=value)).strftime('%Y-%m-%d %H:%M:%S')
+ if len(value)==10:
+ value = ics_datetime(value, True)
+ else:
+ value = ics_datetime(value)
+ event.add(field.name).value = value
+ s= cal.serialize()
+ cr.commit()
+ return s
+document_directory_content()
+
+class crm_case(osv.osv):
+ _inherit = 'crm.case'
+ _columns = {
+ 'code': fields.char('Calendar Code', size=64),
+ 'date_deadline': fields.datetime('Deadline', help="Deadline Date is automatically computed from Start Date + Duration"),
+ }
+
+ _defaults = {
+ 'code': lambda obj, cr, uid, context: obj.pool.get('ir.sequence').get(cr, uid, 'crm.case'),
+ }
+
+ def copy(self, cr, uid, id, default=None, context=None):
+ """
+ code field must be unique in ICS file
+ """
+ if not default: default = {}
+ if not context: context = {}
+ default.update({'code': self.pool.get('ir.sequence').get(cr, uid, 'crm.case'), 'id': False})
+ return super(crm_case, self).copy(cr, uid, id, default, context)
+
+ def on_change_duration(self, cr, uid, id, date, duration):
+ if not date:
+ return {}
+ start_date = datetime.datetime.fromtimestamp(time.mktime(time.strptime(date, "%Y-%m-%d %H:%M:%S")))
+ if duration >= 0 :
+ end = start_date + datetime.timedelta(hours=duration)
+ if duration < 0:
+ raise osv.except_osv(_('Warning !'),
+ _('You can not set negative Duration.'))
+
+ res = {'value' : {'date_deadline' : end.strftime('%Y-%m-%d %H:%M:%S')}}
+ return res
+
+crm_case()
+
+# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:
+
diff --git a/addons/document_ics/document_view.xml b/addons/document_ics/document_view.xml
index 1623be30af6..e5212fc7f66 100644
--- a/addons/document_ics/document_view.xml
+++ b/addons/document_ics/document_view.xml
@@ -1,20 +1,35 @@
-
+
document.directory
document.directory
form
+
+
+
+
+
+
+
+
+
+
+ document.directory
+ document.directory
+ form
+
-
-
+
+
+
From 84898328030e9172ce759067fe28a91d7210a49f Mon Sep 17 00:00:00 2001
From: "Harry (Open ERP)"
Date: Mon, 7 Dec 2009 18:41:11 +0530
Subject: [PATCH 03/19] [IMP] document: remove with four
bzr revid: hmo@tinyerp.com-20091207131111-x11rq6p0ixsgenq4
---
addons/document/content_index.py | 336 +++++------
addons/document/directory_content.py | 68 +--
addons/document/doc_conf_wizard.py | 2 +-
addons/document/document_directory.py | 6 +-
addons/document/document_storage.py | 284 ++++-----
addons/document/nodes.py | 830 +++++++++++++-------------
addons/document/std_index.py | 70 +--
addons/document/test_cindex.py | 38 +-
8 files changed, 817 insertions(+), 817 deletions(-)
diff --git a/addons/document/content_index.py b/addons/document/content_index.py
index a063251c698..b296c623465 100644
--- a/addons/document/content_index.py
+++ b/addons/document/content_index.py
@@ -23,189 +23,189 @@ import tempfile
# A quick hack: if netsvc is not there, emulate it. Thus, work offline, too
try:
- import netsvc
- def log(lvl,msg):
- netsvc.Logger().notifyChannel("index",lvl,msg)
+ import netsvc
+ def log(lvl,msg):
+ netsvc.Logger().notifyChannel("index",lvl,msg)
except:
- class netsvc:
- LOG_NOTSET = 'notset'
- LOG_DEBUG_RPC = 'debug_rpc'
- LOG_DEBUG = 'debug'
- LOG_DEBUG2 = 'debug2'
- LOG_INFO = 'info'
- LOG_WARNING = 'warn'
- LOG_ERROR = 'error'
- LOG_CRITICAL = 'critical'
-
- def log(lvl,msg):
- print msg
+ class netsvc:
+ LOG_NOTSET = 'notset'
+ LOG_DEBUG_RPC = 'debug_rpc'
+ LOG_DEBUG = 'debug'
+ LOG_DEBUG2 = 'debug2'
+ LOG_INFO = 'info'
+ LOG_WARNING = 'warn'
+ LOG_ERROR = 'error'
+ LOG_CRITICAL = 'critical'
+
+ def log(lvl,msg):
+ print msg
class NhException(Exception):
- pass
+ pass
from subprocess import Popen, PIPE
class indexer():
- """ An indexer knows how to parse the content of some file.
-
- Typically, one indexer should be instantiated per file
- type.
- Override this class to add more functionality. Note that
- you should only override the Content or the File methods
- that give an optimal result. """
-
- def _getMimeTypes(self):
- """ Return supported mimetypes """
- return []
-
- def _getExtensions(self):
- return []
-
- def _getDefMime(self,ext):
- """ Return a mimetype for this document type, ideally the
- closest to the extension ext. """
- mts = self._getMimeTypes();
- if len (mts):
- return mts[0]
- return None
+ """ An indexer knows how to parse the content of some file.
+
+ Typically, one indexer should be instantiated per file
+ type.
+ Override this class to add more functionality. Note that
+ you should only override the Content or the File methods
+ that give an optimal result. """
+
+ def _getMimeTypes(self):
+ """ Return supported mimetypes """
+ return []
+
+ def _getExtensions(self):
+ return []
+
+ def _getDefMime(self,ext):
+ """ Return a mimetype for this document type, ideally the
+ closest to the extension ext. """
+ mts = self._getMimeTypes();
+ if len (mts):
+ return mts[0]
+ return None
- def indexContent(self,content,filename=None, realfile = None):
- """ Use either content or the real file, to index.
- Some parsers will work better with the actual
- content, others parse a file easier. Try the
- optimal.
- """
- res = ''
- try:
- if content != None:
- return self._doIndexContent(content)
- except NhException:
- pass
-
- if realfile != None:
- try:
- return self._doIndexFile(realfile)
- except NhException:
- pass
-
- fp = open(realfile,'rb')
- content2 = fp.read()
- fp.close()
-
- # The not-handled exception may be raised here
- return self._doIndexContent(content2)
-
-
- # last try, with a tmp file
- if content:
- try:
- fname,ext = filename and os.path.splitext(filename) or ('','')
- fd, rfname = tempfile.mkstemp(suffix=ext)
- os.write(fd, content)
- os.close(fd)
- res = self._doIndexFile(rfname)
- os.unlink(rfname)
- return res
- except NhException:
- pass
+ def indexContent(self,content,filename=None, realfile = None):
+ """ Use either content or the real file, to index.
+ Some parsers will work better with the actual
+ content, others parse a file easier. Try the
+ optimal.
+ """
+ res = ''
+ try:
+ if content != None:
+ return self._doIndexContent(content)
+ except NhException:
+ pass
+
+ if realfile != None:
+ try:
+ return self._doIndexFile(realfile)
+ except NhException:
+ pass
+
+ fp = open(realfile,'rb')
+ content2 = fp.read()
+ fp.close()
+
+ # The not-handled exception may be raised here
+ return self._doIndexContent(content2)
+
+
+ # last try, with a tmp file
+ if content:
+ try:
+ fname,ext = filename and os.path.splitext(filename) or ('','')
+ fd, rfname = tempfile.mkstemp(suffix=ext)
+ os.write(fd, content)
+ os.close(fd)
+ res = self._doIndexFile(rfname)
+ os.unlink(rfname)
+ return res
+ except NhException:
+ pass
- raise NhException('No appropriate method to index file')
-
- def _doIndexContent(self,content):
- raise NhException("Content not handled here")
+ raise NhException('No appropriate method to index file')
+
+ def _doIndexContent(self,content):
+ raise NhException("Content not handled here")
- def _doIndexFile(self,fpath):
- raise NhException("Content not handled here")
-
-
+ def _doIndexFile(self,fpath):
+ raise NhException("Content not handled here")
+
+
def mime_match(mime, mdict):
- if mdict.has_key(mime):
- return (mime, mdict[mime])
- if '/' in mime:
- mpat = mime.split('/')[0]+'/*'
- if mdict.has_key(mpat):
- return (mime, mdict[mpat])
-
- return (None, None)
+ if mdict.has_key(mime):
+ return (mime, mdict[mime])
+ if '/' in mime:
+ mpat = mime.split('/')[0]+'/*'
+ if mdict.has_key(mpat):
+ return (mime, mdict[mpat])
+
+ return (None, None)
class contentIndex() :
- def __init__(self):
- self.mimes = {}
- self.exts = {}
-
- def register(self, obj):
- f = False
- for mime in obj._getMimeTypes():
- self.mimes[mime] = obj
- f = True
-
- for ext in obj._getExtensions():
- self.exts[ext] = obj
- f = True
-
- if f:
- log(netsvc.LOG_DEBUG, "Register content indexer: %r" % obj)
- if not f:
- raise Exception("Your indexer should at least suport a mimetype or extension")
-
- def doIndex(self,content, filename=None, content_type=None, realfname = None, debug=False):
- fobj = None
- fname = None
- mime = None
- if content_type and self.mimes.has_key(content_type):
- mime = content_type
- fobj = self.mimes[content_type]
- elif filename:
- bname,ext = os.path.splitext(filename)
- if self.exts.has_key(ext):
- fobj = self.exts[ext]
- mime = fobj._getDefMime(ext)
-
- if content_type and not fobj:
- mime,fobj = mime_match(content_type, self.mimes)
-
- if not fobj:
- try:
- if realfname :
- fname = realfname
- else:
- bname,ext = os.path.splitext(filename)
- fd, fname = tempfile.mkstemp(suffix=ext)
- os.write(fd, content)
- os.close(fd)
-
- fp = Popen(['file','-b','--mime-type',fname], shell=False, stdout=PIPE).stdout
- result = fp.read()
- fp.close()
- mime2 = result.strip()
- log(netsvc.LOG_DEBUG,"File gave us: %s" % mime2)
- # Note that the temporary file still exists now.
- mime,fobj = mime_match(mime2, self.mimes)
- if not mime:
- mime = mime2
- except Exception, e:
- log(netsvc.LOG_WARNING,"Cannot determine mime type: %s" % str(e))
-
- try:
- if fobj:
- res = (mime, fobj.indexContent(content,filename,fname or realfname) )
- else:
- log(netsvc.LOG_DEBUG,"Have no object, return (%s, None)" % mime)
- res = (mime, None )
- except Exception, e:
- log(netsvc.LOG_WARNING,"Could not index file, %s" % e)
- res = None
-
- # If we created a tmp file, unlink it now
- if not realfname and fname:
- try:
- os.unlink(fname)
- except Exception, e:
- log(netsvc.LOG_WARNING,"Could not unlink %s, %s" %(fname, e))
-
- return res
+ def __init__(self):
+ self.mimes = {}
+ self.exts = {}
+
+ def register(self, obj):
+ f = False
+ for mime in obj._getMimeTypes():
+ self.mimes[mime] = obj
+ f = True
+
+ for ext in obj._getExtensions():
+ self.exts[ext] = obj
+ f = True
+
+ if f:
+ log(netsvc.LOG_DEBUG, "Register content indexer: %r" % obj)
+ if not f:
+ raise Exception("Your indexer should at least suport a mimetype or extension")
+
+ def doIndex(self,content, filename=None, content_type=None, realfname = None, debug=False):
+ fobj = None
+ fname = None
+ mime = None
+ if content_type and self.mimes.has_key(content_type):
+ mime = content_type
+ fobj = self.mimes[content_type]
+ elif filename:
+ bname,ext = os.path.splitext(filename)
+ if self.exts.has_key(ext):
+ fobj = self.exts[ext]
+ mime = fobj._getDefMime(ext)
+
+ if content_type and not fobj:
+ mime,fobj = mime_match(content_type, self.mimes)
+
+ if not fobj:
+ try:
+ if realfname :
+ fname = realfname
+ else:
+ bname,ext = os.path.splitext(filename)
+ fd, fname = tempfile.mkstemp(suffix=ext)
+ os.write(fd, content)
+ os.close(fd)
+
+ fp = Popen(['file','-b','--mime-type',fname], shell=False, stdout=PIPE).stdout
+ result = fp.read()
+ fp.close()
+ mime2 = result.strip()
+ log(netsvc.LOG_DEBUG,"File gave us: %s" % mime2)
+ # Note that the temporary file still exists now.
+ mime,fobj = mime_match(mime2, self.mimes)
+ if not mime:
+ mime = mime2
+ except Exception, e:
+ log(netsvc.LOG_WARNING,"Cannot determine mime type: %s" % str(e))
+
+ try:
+ if fobj:
+ res = (mime, fobj.indexContent(content,filename,fname or realfname) )
+ else:
+ log(netsvc.LOG_DEBUG,"Have no object, return (%s, None)" % mime)
+ res = (mime, None )
+ except Exception, e:
+ log(netsvc.LOG_WARNING,"Could not index file, %s" % e)
+ res = None
+
+ # If we created a tmp file, unlink it now
+ if not realfname and fname:
+ try:
+ os.unlink(fname)
+ except Exception, e:
+ log(netsvc.LOG_WARNING,"Could not unlink %s, %s" %(fname, e))
+
+ return res
cntIndex = contentIndex()
diff --git a/addons/document/directory_content.py b/addons/document/directory_content.py
index 4ccad653711..dbd801d6be0 100644
--- a/addons/document/directory_content.py
+++ b/addons/document/directory_content.py
@@ -66,44 +66,44 @@ class document_directory_content(osv.osv):
}
def _file_get(self,cr,node, nodename, content, context=None):
- """ return the nodes of a parent having a content
- The return value MUST be false or a list of node_class objects.
- """
-
- # TODO: respect the context!
- if content.include_name and not node.object2:
- return False
-
- res2 = []
- tname = ''
- if content.include_name:
- content_name = node.object2.name
- obj = pool.get(node.object.ressource_type_id.model)
- name_for = obj._name.split('.')[-1]
- if content_name and content_name.find(name_for) == 0 :
- content_name = content_name.replace(name_for,'')
- tname = (content.prefix or '') + content_name + (content.suffix or '') + (content.extension or '')
- else:
- tname = (content.prefix or '') + (content.suffix or '') + (content.extension or '')
- if tname.find('/'):
- tname=tname.replace('/', '_')
- if not nodename:
- n = nodes.node_content(tname, node, node.context,content)
- res2.append( n)
- else:
- if nodename == tname:
- n = nodes.node_content(tname, node, node.context,content)
- n.fill_fields(cr)
- res2.append(n)
- return res2
+ """ return the nodes of a parent having a content
+ The return value MUST be false or a list of node_class objects.
+ """
+
+ # TODO: respect the context!
+ if content.include_name and not node.object2:
+ return False
+
+ res2 = []
+ tname = ''
+ if content.include_name:
+ content_name = node.object2.name
+ obj = pool.get(node.object.ressource_type_id.model)
+ name_for = obj._name.split('.')[-1]
+ if content_name and content_name.find(name_for) == 0 :
+ content_name = content_name.replace(name_for,'')
+ tname = (content.prefix or '') + content_name + (content.suffix or '') + (content.extension or '')
+ else:
+ tname = (content.prefix or '') + (content.suffix or '') + (content.extension or '')
+ if tname.find('/'):
+ tname=tname.replace('/', '_')
+ if not nodename:
+ n = nodes.node_content(tname, node, node.context,content)
+ res2.append( n)
+ else:
+ if nodename == tname:
+ n = nodes.node_content(tname, node, node.context,content)
+ n.fill_fields(cr)
+ res2.append(n)
+ return res2
def process_write(self, cr, uid, node, data):
- if node.extension != '.pdf':
- raise Exception("Invalid content: %s" % node.extension)
+ if node.extension != '.pdf':
+ raise Exception("Invalid content: %s" % node.extension)
return True
def process_read(self, cr, uid, node):
- if node.extension != '.pdf':
- raise Exception("Invalid content: %s" % node.extension)
+ if node.extension != '.pdf':
+ raise Exception("Invalid content: %s" % node.extension)
report = self.pool.get('ir.actions.report.xml').browse(cr, uid, node.content.report_id.id)
srv = netsvc.LocalService('report.'+report.report_name)
pdf,pdftype = srv.create(cr, uid, [node.object.id], {}, {})
diff --git a/addons/document/doc_conf_wizard.py b/addons/document/doc_conf_wizard.py
index 66354c8876b..ea0d0ab1487 100644
--- a/addons/document/doc_conf_wizard.py
+++ b/addons/document/doc_conf_wizard.py
@@ -148,7 +148,7 @@ class document_configuration_wizard(osv.osv_memory):
'ressource_tree': 1
})
- # Update the action for FTP browse.
+ # Update the action for FTP browse.
aid = objid._get_id(cr, uid, 'document', 'action_document_browse')
aid = objid.browse(cr, uid, aid, context=context).res_id
self.pool.get('ir.actions.url').write(cr, uid, [aid], {'url': 'ftp://'+(conf.host or 'localhost')+':8021/'})
diff --git a/addons/document/document_directory.py b/addons/document/document_directory.py
index 133927f9664..95e4d438b76 100644
--- a/addons/document/document_directory.py
+++ b/addons/document/document_directory.py
@@ -40,7 +40,7 @@ class document_directory(osv.osv):
'file_type': fields.char('Content Type', size=32),
'domain': fields.char('Domain', size=128, help="Use a domain if you want to apply an automatic filter on visible resources."),
'user_id': fields.many2one('res.users', 'Owner'),
- 'storage_id': fields.many2one('document.storage', 'Storage'),
+ 'storage_id': fields.many2one('document.storage', 'Storage'),
'group_ids': fields.many2many('res.groups', 'document_directory_group_rel', 'item_id', 'group_id', 'Groups'),
'parent_id': fields.many2one('document.directory', 'Parent Item'),
'child_ids': fields.one2many('document.directory', 'parent_id', 'Children'),
@@ -118,8 +118,8 @@ class document_directory(osv.osv):
path.append(self.pool.get(directory.ressource_type_id.model).browse(cr,uid,res_id).name)
#user=self.pool.get('res.users').browse(cr,uid,uid)
#return "ftp://%s:%s@localhost:%s/%s/%s"%(user.login,user.password,config.get('ftp_server_port',8021),cr.dbname,'/'.join(path))
- # No way we will return the password!
- return "ftp://user:pass@host:port/test/this"
+ # No way we will return the password!
+ return "ftp://user:pass@host:port/test/this"
return False
def _check_recursion(self, cr, uid, ids):
diff --git a/addons/document/document_storage.py b/addons/document/document_storage.py
index d4096138800..488a5ee7e6c 100644
--- a/addons/document/document_storage.py
+++ b/addons/document/document_storage.py
@@ -103,177 +103,177 @@ class document_storage(osv.osv):
'group_ids': fields.many2many('res.groups', 'document_directory_group_rel', 'item_id', 'group_id', 'Groups'),
'dir_ids': fields.one2many('document.directory', 'parent_id', 'Directories'),
'type': fields.selection([('db','Database'),('filestore','Internal File storage'),
- ('realstore','External file storage'), ('virtual','Virtual storage')], 'Type', required=True),
- 'path': fields.char('Path',size=250,select=1,help="For file storage, the root path of the storage"),
- 'online': fields.boolean('Online',help="If not checked, media is currently offline and its contents not available", required=True),
- 'readonly': fields.boolean('Read Only', help="If set, media is for reading only"),
+ ('realstore','External file storage'), ('virtual','Virtual storage')], 'Type', required=True),
+ 'path': fields.char('Path',size=250,select=1,help="For file storage, the root path of the storage"),
+ 'online': fields.boolean('Online',help="If not checked, media is currently offline and its contents not available", required=True),
+ 'readonly': fields.boolean('Read Only', help="If set, media is for reading only"),
}
def _get_rootpath(self,cr,uid,context=None):
- from tools import config
+ from tools import config
return os.path.join(tools.config['root_path'], 'filestore', cr.dbname)
_defaults = {
'user_id': lambda self,cr,uid,ctx: uid,
- 'online': lambda *args: True,
- 'readonly': lambda *args: False,
+ 'online': lambda *args: True,
+ 'readonly': lambda *args: False,
# Note: the defaults below should only be used ONCE for the default
# storage media. All other times, we should create different paths at least.
'type': lambda *args: 'filestore',
'path': _get_rootpath,
}
_sql_constraints = [
- # SQL note: a path = NULL doesn't have to be unique.
- ('path_uniq', 'UNIQUE(type,path)', "The storage path must be unique!")
- ]
-
+ # SQL note: a path = NULL doesn't have to be unique.
+ ('path_uniq', 'UNIQUE(type,path)', "The storage path must be unique!")
+ ]
+
def get_data(self, cr,uid, id, file_node,context = None, fil_obj = None):
- """ retrieve the contents of some file_node having storage_id = id
- optionally, fil_obj could point to the browse object of the file
- (ir.attachment)
- """
- if not context:
- context = {}
+ """ retrieve the contents of some file_node having storage_id = id
+ optionally, fil_obj could point to the browse object of the file
+ (ir.attachment)
+ """
+ if not context:
+ context = {}
boo = self.browse(cr,uid,id,context)
- if fil_obj:
- ira = fil_obj
- else:
- ira = self.pool.get('ir.attachment').browse(cr,uid, file_node.file_id, context=context)
- return self.__get_data_3(cr,uid,boo, ira, context)
-
+ if fil_obj:
+ ira = fil_obj
+ else:
+ ira = self.pool.get('ir.attachment').browse(cr,uid, file_node.file_id, context=context)
+ return self.__get_data_3(cr,uid,boo, ira, context)
+
def __get_data_3(self,cr,uid, boo, ira, context):
- if not boo.online:
- raise RuntimeError('media offline')
- if boo.type == 'filestore':
- if not ira.store_fname:
- # On a migrated db, some files may have the wrong storage type
- # try to fix their directory.
- if ira.file_size:
- netsvc.Logger().notifyChannel('document',netsvc.LOG_WARNING,"ir.attachment #%d does not have a filename, but is at filestore, fix it!" %ira.id)
- return None
- fpath = os.path.join(boo.path,ira.store_fname)
- print "Trying to read \"%s\".."% fpath
- return file(fpath,'rb').read()
- elif boo.type == 'db':
- # TODO: we need a better api for large files
- if ira.db_datas:
- out=base64.decodestring(ira.db_datas)
- else:
- out=''
- return out
- elif boo.type == 'realstore':
- # fpath = os.path.join(boo.path,
- return None
- else:
- raise TypeError("No %s storage" % boo.type)
+ if not boo.online:
+ raise RuntimeError('media offline')
+ if boo.type == 'filestore':
+ if not ira.store_fname:
+ # On a migrated db, some files may have the wrong storage type
+ # try to fix their directory.
+ if ira.file_size:
+ netsvc.Logger().notifyChannel('document',netsvc.LOG_WARNING,"ir.attachment #%d does not have a filename, but is at filestore, fix it!" %ira.id)
+ return None
+ fpath = os.path.join(boo.path,ira.store_fname)
+ print "Trying to read \"%s\".."% fpath
+ return file(fpath,'rb').read()
+ elif boo.type == 'db':
+ # TODO: we need a better api for large files
+ if ira.db_datas:
+ out=base64.decodestring(ira.db_datas)
+ else:
+ out=''
+ return out
+ elif boo.type == 'realstore':
+ # fpath = os.path.join(boo.path,
+ return None
+ else:
+ raise TypeError("No %s storage" % boo.type)
def set_data(self, cr,uid, id, file_node, data, context = None, fil_obj = None):
- """ store the data.
- This function MUST be used from an ir.attachment. It wouldn't make sense
- to store things persistently for other types (dynamic).
- """
- if not context:
- context = {}
+ """ store the data.
+ This function MUST be used from an ir.attachment. It wouldn't make sense
+ to store things persistently for other types (dynamic).
+ """
+ if not context:
+ context = {}
boo = self.browse(cr,uid,id,context)
- logger = netsvc.Logger()
- if fil_obj:
- ira = fil_obj
- else:
- ira = self.pool.get('ir.attachment').browse(cr,uid, file_node.file_id, context=context)
-
- if not boo.online:
- raise RuntimeError('media offline')
- logger.notifyChannel('document',netsvc.LOG_DEBUG,"Store data for ir.attachment #%d" %ira.id)
- store_fname = None
- fname = None
- if boo.type == 'filestore':
- path = boo.path
- try:
- flag = None
- # This can be improved
- for dirs in os.listdir(path):
- if os.path.isdir(os.path.join(path,dirs)) and len(os.listdir(os.path.join(path,dirs)))<4000:
- flag = dirs
+ logger = netsvc.Logger()
+ if fil_obj:
+ ira = fil_obj
+ else:
+ ira = self.pool.get('ir.attachment').browse(cr,uid, file_node.file_id, context=context)
+
+ if not boo.online:
+ raise RuntimeError('media offline')
+ logger.notifyChannel('document',netsvc.LOG_DEBUG,"Store data for ir.attachment #%d" %ira.id)
+ store_fname = None
+ fname = None
+ if boo.type == 'filestore':
+ path = boo.path
+ try:
+ flag = None
+ # This can be improved
+ for dirs in os.listdir(path):
+ if os.path.isdir(os.path.join(path,dirs)) and len(os.listdir(os.path.join(path,dirs)))<4000:
+ flag = dirs
break
- flag = flag or create_directory(path)
- filename = random_name()
- fname = os.path.join(path, flag, filename)
- fp = file(fname,'wb')
- fp.write(data)
- fp.close()
- logger.notifyChannel('document',netsvc.LOG_DEBUG,"Saved data to %s" % fname)
- filesize = len(data) # os.stat(fname).st_size
- store_fname = os.path.join(flag,filename)
-
- # TODO Here, an old file would be left hanging.
+ flag = flag or create_directory(path)
+ filename = random_name()
+ fname = os.path.join(path, flag, filename)
+ fp = file(fname,'wb')
+ fp.write(data)
+ fp.close()
+ logger.notifyChannel('document',netsvc.LOG_DEBUG,"Saved data to %s" % fname)
+ filesize = len(data) # os.stat(fname).st_size
+ store_fname = os.path.join(flag,filename)
+
+ # TODO Here, an old file would be left hanging.
except Exception,e :
- netsvc.Logger().notifyChannel('document',netsvc.LOG_WARNING,"Couldn't save data: %s" %str(e))
- raise except_orm(_('Error!'), str(e))
- elif boo.type == 'db':
- filesize = len(data)
- # will that work for huge data? TODO
- out=base64.encodestring(data)
- cr.execute('UPDATE ir_attachment SET db_datas = %s WHERE id = %s',
- ( out, file_node.file_id ))
- else:
- raise TypeError("No %s storage" % boo.type)
+ netsvc.Logger().notifyChannel('document',netsvc.LOG_WARNING,"Couldn't save data: %s" %str(e))
+ raise except_orm(_('Error!'), str(e))
+ elif boo.type == 'db':
+ filesize = len(data)
+ # will that work for huge data? TODO
+ out=base64.encodestring(data)
+ cr.execute('UPDATE ir_attachment SET db_datas = %s WHERE id = %s',
+ ( out, file_node.file_id ))
+ else:
+ raise TypeError("No %s storage" % boo.type)
- # 2nd phase: store the metadata
- try:
- icont = ''
- mime = ira.file_type
- try:
- mime,icont = cntIndex.doIndex(data, ira.datas_fname,
- ira.file_type or None,fname)
- except Exception,e:
- logger.notifyChannel('document', netsvc.LOG_DEBUG, 'Cannot index file: %s' % str(e))
- pass
+ # 2nd phase: store the metadata
+ try:
+ icont = ''
+ mime = ira.file_type
+ try:
+ mime,icont = cntIndex.doIndex(data, ira.datas_fname,
+ ira.file_type or None,fname)
+ except Exception,e:
+ logger.notifyChannel('document', netsvc.LOG_DEBUG, 'Cannot index file: %s' % str(e))
+ pass
- # a hack: /assume/ that the calling write operation will not try
- # to write the fname and size, and update them in the db concurrently.
- # We cannot use a write() here, because we are already in one.
- cr.execute('UPDATE ir_attachment SET store_fname = %s, file_size = %s, index_content = %s, file_type = %s WHERE id = %s',
- (store_fname, filesize, ustr(icont), mime, file_node.file_id ))
- file_node.content_length = filesize
- file_node.content_type = mime
- return True
+ # a hack: /assume/ that the calling write operation will not try
+ # to write the fname and size, and update them in the db concurrently.
+ # We cannot use a write() here, because we are already in one.
+ cr.execute('UPDATE ir_attachment SET store_fname = %s, file_size = %s, index_content = %s, file_type = %s WHERE id = %s',
+ (store_fname, filesize, ustr(icont), mime, file_node.file_id ))
+ file_node.content_length = filesize
+ file_node.content_type = mime
+ return True
except Exception,e :
- netsvc.Logger().notifyChannel('document',netsvc.LOG_WARNING,"Couldn't save data: %s" %str(e))
- # should we really rollback once we have written the actual data?
- # at the db case (only), that rollback would be safe
- raise except_orm(_('Error at doc write!'), str(e))
+ netsvc.Logger().notifyChannel('document',netsvc.LOG_WARNING,"Couldn't save data: %s" %str(e))
+ # should we really rollback once we have written the actual data?
+ # at the db case (only), that rollback would be safe
+ raise except_orm(_('Error at doc write!'), str(e))
def prepare_unlink(self,cr,uid,storage_bo, fil_bo):
- """ Before we unlink a file (fil_boo), prepare the list of real
- files that have to be removed, too. """
-
- if not storage_bo.online:
- raise RuntimeError('media offline')
-
- if storage_bo.type == 'filestore':
- fname = fil_bo.store_fname
- if not fname:
- return None
- path = storage_bo.path
- return ( storage_bo.id, 'file', os.path.join(path,fname))
- elif storage_bo.type == 'db':
- return None
- else:
- raise TypeError("No %s storage" % boo.type)
+ """ Before we unlink a file (fil_boo), prepare the list of real
+ files that have to be removed, too. """
+
+ if not storage_bo.online:
+ raise RuntimeError('media offline')
+
+ if storage_bo.type == 'filestore':
+ fname = fil_bo.store_fname
+ if not fname:
+ return None
+ path = storage_bo.path
+ return ( storage_bo.id, 'file', os.path.join(path,fname))
+ elif storage_bo.type == 'db':
+ return None
+ else:
+ raise TypeError("No %s storage" % boo.type)
def do_unlink(self, cr,uid,unres):
- for id, ktype, fname in unres:
- if ktype == 'file':
- try:
- os.unlink(fname)
- except Exception,e:
- netsvc.Logger().notifyChannel('document',netsvc.LOG_WARNING,"Could not remove file %s, please remove manually." % fname)
- else:
- netsvc.Logger().notifyChannel('document',netsvc.LOG_WARNING,"Unknown unlink key %s" % ktype)
-
- return True
-
+ for id, ktype, fname in unres:
+ if ktype == 'file':
+ try:
+ os.unlink(fname)
+ except Exception,e:
+ netsvc.Logger().notifyChannel('document',netsvc.LOG_WARNING,"Could not remove file %s, please remove manually." % fname)
+ else:
+ netsvc.Logger().notifyChannel('document',netsvc.LOG_WARNING,"Unknown unlink key %s" % ktype)
+
+ return True
+
document_storage()
diff --git a/addons/document/nodes.py b/addons/document/nodes.py
index 19efe97c7c1..845ba4e3ace 100644
--- a/addons/document/nodes.py
+++ b/addons/document/nodes.py
@@ -42,42 +42,42 @@ import time
#
def get_node_context(cr, uid, context):
- return node_context(cr,uid,context)
+ return node_context(cr,uid,context)
class node_context(object):
""" This is the root node, representing access to some particular
- context """
+ context """
cached_roots = {}
def __init__(self, cr, uid, context=None):
- # we don't cache the cr!
- #self.cr = cr
- self.uid = uid
- self.context = context
- self._dirobj = pooler.get_pool(cr.dbname).get('document.directory')
- assert self._dirobj
- self.rootdir = self._dirobj._get_root_directory(cr,uid,context)
+ # we don't cache the cr!
+ #self.cr = cr
+ self.uid = uid
+ self.context = context
+ self._dirobj = pooler.get_pool(cr.dbname).get('document.directory')
+ assert self._dirobj
+ self.rootdir = self._dirobj._get_root_directory(cr,uid,context)
def get_uri(self, cr, uri):
- """ Although this fn passes back to doc.dir, it is needed since
- it is a potential caching point """
-
- (ndir, duri) = self._dirobj._locate_child(cr,self.uid, self.rootdir,uri, None, self)
-
- while duri:
- ndir = ndir.child(cr, duri[0])
- if not ndir:
- return False
- duri = duri[1:]
- return ndir
+ """ Although this fn passes back to doc.dir, it is needed since
+ it is a potential caching point """
+
+ (ndir, duri) = self._dirobj._locate_child(cr,self.uid, self.rootdir,uri, None, self)
+
+ while duri:
+ ndir = ndir.child(cr, duri[0])
+ if not ndir:
+ return False
+ duri = duri[1:]
+ return ndir
class node_database():
""" A node representing the database directory
- Useless?
- """
+ Useless?
+ """
def __init__(self,ncontext):
- self.nctx = ncontext
+ self.nctx = ncontext
@@ -85,108 +85,108 @@ class node_database():
class node_class(object):
""" this is a superclass for our inodes
It is an API for all code that wants to access the document files.
- Nodes have attributes which contain usual file properties
- """
+ Nodes have attributes which contain usual file properties
+ """
our_type = 'baseclass'
def __init__(self, path, parent, context):
- assert isinstance(context,node_context)
- assert (not parent ) or isinstance(parent,node_class)
+ assert isinstance(context,node_context)
+ assert (not parent ) or isinstance(parent,node_class)
self.path = path
self.context = context
self.type=self.our_type
- self.parent = parent
- self.mimetype = 'application/octet-stream'
- self.create_date = None
- self.write_date = None
- self.content_length = 0
- # dynamic context:
- self.dctx = {}
- if parent:
- self.dctx = parent.dctx.copy()
- self.displayname = 'Object'
-
+ self.parent = parent
+ self.mimetype = 'application/octet-stream'
+ self.create_date = None
+ self.write_date = None
+ self.content_length = 0
+ # dynamic context:
+ self.dctx = {}
+ if parent:
+ self.dctx = parent.dctx.copy()
+ self.displayname = 'Object'
+
def full_path(self):
- if self.parent:
- s = self.parent.full_path()
- else:
- s = []
- if isinstance(self.path,list):
- s+=self.path
- else:
- s.append(self.path)
- return s
+ if self.parent:
+ s = self.parent.full_path()
+ else:
+ s = []
+ if isinstance(self.path,list):
+ s+=self.path
+ else:
+ s.append(self.path)
+ return s
def children(self, cr):
print "node_class.children()"
- return [] #stub
+ return [] #stub
def child(self,cr, name):
- print "node_class.child()"
+ print "node_class.child()"
return None
def path_get(self):
- print "node_class.path_get()"
- return False
-
+ print "node_class.path_get()"
+ return False
+
def get_data(self,cr):
- raise TypeError('no data for %s'% self.type)
+ raise TypeError('no data for %s'% self.type)
def _get_storage(self,cr):
- raise RuntimeError("no storage for base class")
+ raise RuntimeError("no storage for base class")
def get_etag(self,cr):
""" Get a tag, unique per object + modification.
-
- see. http://tools.ietf.org/html/rfc2616#section-13.3.3 """
- return self._get_ttag(cr) + ':' + self._get_wtag(cr)
+
+ see. http://tools.ietf.org/html/rfc2616#section-13.3.3 """
+ return self._get_ttag(cr) + ':' + self._get_wtag(cr)
def _get_wtag(self,cr):
- """ Return the modification time as a unique, compact string """
- if self.write_date:
- wtime = time.mktime(time.strptime(self.write_date,'%Y-%m-%d %H:%M:%S'))
- else: wtime = time.time()
- return str(wtime)
+ """ Return the modification time as a unique, compact string """
+ if self.write_date:
+ wtime = time.mktime(time.strptime(self.write_date,'%Y-%m-%d %H:%M:%S'))
+ else: wtime = time.time()
+ return str(wtime)
def _get_ttag(self,cr):
- """ Get a unique tag for this type/id of object.
- Must be overriden, so that each node is uniquely identified.
- """
- print "node_class.get_ttag()",self
- raise RuntimeError("get_etag stub()")
-
+ """ Get a unique tag for this type/id of object.
+ Must be overriden, so that each node is uniquely identified.
+ """
+ print "node_class.get_ttag()",self
+ raise RuntimeError("get_etag stub()")
+
def get_dav_props(self, cr):
""" If this class has special behaviour for GroupDAV etc, export
- its capabilities """
- return {}
+ its capabilities """
+ return {}
def get_dav_eprop(self,cr,ns,prop):
- return None
+ return None
class node_dir(node_class):
our_type = 'collection'
def __init__(self,path, parent, context, dirr, dctx=None):
- super(node_dir,self).__init__(path, parent,context)
- self.dir_id = dirr.id
- #todo: more info from dirr
- self.mimetype = 'application/x-directory'
- # 'httpd/unix-directory'
- self.create_date = dirr.create_date
- # TODO: the write date should be MAX(file.write)..
- self.write_date = dirr.write_date or dirr.create_date
- self.content_length = 0
- if dctx:
- self.dctx.update(dctx)
- dc2 = self.context.context
- dc2.update(self.dctx)
- dc2['dir_id'] = self.dir_id
- self.displayname = dirr.name
- for dfld in dirr.dctx_ids:
- try:
- self.dctx['dctx_' + dfld.field] = safe_eval(dfld.expr,dc2)
- except Exception,e:
- print "Cannot eval %s" % dfld.expr
- print e
- pass
+ super(node_dir,self).__init__(path, parent,context)
+ self.dir_id = dirr.id
+ #todo: more info from dirr
+ self.mimetype = 'application/x-directory'
+ # 'httpd/unix-directory'
+ self.create_date = dirr.create_date
+ # TODO: the write date should be MAX(file.write)..
+ self.write_date = dirr.write_date or dirr.create_date
+ self.content_length = 0
+ if dctx:
+ self.dctx.update(dctx)
+ dc2 = self.context.context
+ dc2.update(self.dctx)
+ dc2['dir_id'] = self.dir_id
+ self.displayname = dirr.name
+ for dfld in dirr.dctx_ids:
+ try:
+ self.dctx['dctx_' + dfld.field] = safe_eval(dfld.expr,dc2)
+ except Exception,e:
+ print "Cannot eval %s" % dfld.expr
+ print e
+ pass
def children(self,cr):
return self._child_get(cr) + self._file_get(cr)
@@ -201,132 +201,132 @@ class node_dir(node_class):
return None
def _file_get(self,cr, nodename=False):
- res = []
- cntobj = self.context._dirobj.pool.get('document.directory.content')
- uid = self.context.uid
- ctx = self.context.context.copy()
- ctx.update(self.dctx)
- where = [('directory_id','=',self.dir_id) ]
- ids = cntobj.search(cr,uid,where,context=ctx)
+ res = []
+ cntobj = self.context._dirobj.pool.get('document.directory.content')
+ uid = self.context.uid
+ ctx = self.context.context.copy()
+ ctx.update(self.dctx)
+ where = [('directory_id','=',self.dir_id) ]
+ ids = cntobj.search(cr,uid,where,context=ctx)
for content in cntobj.browse(cr,uid,ids,context=ctx):
- res3 = cntobj._file_get(cr,self,nodename,content)
- if res3:
- res.extend(res3)
+ res3 = cntobj._file_get(cr,self,nodename,content)
+ if res3:
+ res.extend(res3)
- return res
+ return res
def get_dav_props(self, cr):
- res = {}
- cntobj = self.context._dirobj.pool.get('document.directory.content')
- uid = self.context.uid
- ctx = self.context.context.copy()
- ctx.update(self.dctx)
- where = [('directory_id','=',self.dir_id) ]
- ids = cntobj.search(cr,uid,where,context=ctx)
+ res = {}
+ cntobj = self.context._dirobj.pool.get('document.directory.content')
+ uid = self.context.uid
+ ctx = self.context.context.copy()
+ ctx.update(self.dctx)
+ where = [('directory_id','=',self.dir_id) ]
+ ids = cntobj.search(cr,uid,where,context=ctx)
for content in cntobj.browse(cr,uid,ids,context=ctx):
- if content.extension == '.ics': # FIXME: call the content class!
- res['http://groupdav.org/'] = ('resourcetype',)
- break
- return res
+ if content.extension == '.ics': # FIXME: call the content class!
+ res['http://groupdav.org/'] = ('resourcetype',)
+ break
+ return res
def get_dav_eprop(self,cr,ns,prop):
- if ns != 'http://groupdav.org/' or prop != 'resourcetype':
- print "Who asked for %s:%s?" % (ns,prop)
- return None
- res = {}
- cntobj = self.context._dirobj.pool.get('document.directory.content')
- uid = self.context.uid
- ctx = self.context.context.copy()
- ctx.update(self.dctx)
- where = [('directory_id','=',self.dir_id) ]
- ids = cntobj.search(cr,uid,where,context=ctx)
+ if ns != 'http://groupdav.org/' or prop != 'resourcetype':
+ print "Who asked for %s:%s?" % (ns,prop)
+ return None
+ res = {}
+ cntobj = self.context._dirobj.pool.get('document.directory.content')
+ uid = self.context.uid
+ ctx = self.context.context.copy()
+ ctx.update(self.dctx)
+ where = [('directory_id','=',self.dir_id) ]
+ ids = cntobj.search(cr,uid,where,context=ctx)
for content in cntobj.browse(cr,uid,ids,context=ctx):
- if content.extension == '.ics': # FIXME: call the content class!
- return ('vevent-collection','http://groupdav.org/')
- return None
+ if content.extension == '.ics': # FIXME: call the content class!
+ return ('vevent-collection','http://groupdav.org/')
+ return None
def _child_get(self,cr,name = None):
- dirobj = self.context._dirobj
- uid = self.context.uid
- ctx = self.context.context.copy()
- ctx.update(self.dctx)
- where = [('parent_id','=',self.dir_id) ]
- if name:
- where.append(('name','=',name))
- ids = dirobj.search(cr, uid, where,context=ctx)
- res = []
- if ids:
- for dirr in dirobj.browse(cr,uid,ids,context=ctx):
- if dirr.type == 'directory':
- res.append(node_dir(dirr.name,self,self.context,dirr))
- elif dirr.type == 'ressource':
- res.append(node_res_dir(dirr.name,self,self.context,dirr))
-
- fil_obj=dirobj.pool.get('ir.attachment')
- #where2 = where # + [('res_model', '=', None)]
- ids = fil_obj.search(cr,uid,where,context=ctx)
- if ids:
- for fil in fil_obj.browse(cr,uid,ids,context=ctx):
- res.append(node_file(fil.name,self,self.context,fil))
-
- return res
-
+ dirobj = self.context._dirobj
+ uid = self.context.uid
+ ctx = self.context.context.copy()
+ ctx.update(self.dctx)
+ where = [('parent_id','=',self.dir_id) ]
+ if name:
+ where.append(('name','=',name))
+ ids = dirobj.search(cr, uid, where,context=ctx)
+ res = []
+ if ids:
+ for dirr in dirobj.browse(cr,uid,ids,context=ctx):
+ if dirr.type == 'directory':
+ res.append(node_dir(dirr.name,self,self.context,dirr))
+ elif dirr.type == 'ressource':
+ res.append(node_res_dir(dirr.name,self,self.context,dirr))
+
+ fil_obj=dirobj.pool.get('ir.attachment')
+ #where2 = where # + [('res_model', '=', None)]
+ ids = fil_obj.search(cr,uid,where,context=ctx)
+ if ids:
+ for fil in fil_obj.browse(cr,uid,ids,context=ctx):
+ res.append(node_file(fil.name,self,self.context,fil))
+
+ return res
+
def create_child(self,cr,path,data):
- """ API function to create a child file object and node
- Return the node_* created
- """
- dirobj = self.context._dirobj
- uid = self.context.uid
- ctx = self.context.context.copy()
- ctx.update(self.dctx)
- fil_obj=dirobj.pool.get('ir.attachment')
- val = {
- 'name': path,
- 'datas_fname': path,
- 'parent_id': self.dir_id,
- # Datas are not set here
- }
+ """ API function to create a child file object and node
+ Return the node_* created
+ """
+ dirobj = self.context._dirobj
+ uid = self.context.uid
+ ctx = self.context.context.copy()
+ ctx.update(self.dctx)
+ fil_obj=dirobj.pool.get('ir.attachment')
+ val = {
+ 'name': path,
+ 'datas_fname': path,
+ 'parent_id': self.dir_id,
+ # Datas are not set here
+ }
- fil_id = fil_obj.create(cr,uid, val, context=ctx)
- fil = fil_obj.browse(cr,uid,fil_id,context=ctx)
- fnode = node_file(path,self,self.context,fil)
- fnode.set_data(cr,data,fil)
- return fnode
-
+ fil_id = fil_obj.create(cr,uid, val, context=ctx)
+ fil = fil_obj.browse(cr,uid,fil_id,context=ctx)
+ fnode = node_file(path,self,self.context,fil)
+ fnode.set_data(cr,data,fil)
+ return fnode
+
def _get_ttag(self,cr):
- return 'dir-%d' % self.dir_id
+ return 'dir-%d' % self.dir_id
class node_res_dir(node_class):
""" A special sibling to node_dir, which does only contain dynamically
created folders foreach resource in the foreign model.
- All folders should be of type node_res_obj and merely behave like
- node_dirs (with limited domain).
- """
+ All folders should be of type node_res_obj and merely behave like
+ node_dirs (with limited domain).
+ """
our_type = 'collection'
def __init__(self,path, parent, context, dirr, dctx=None ):
- super(node_res_dir,self).__init__(path, parent,context)
- self.dir_id = dirr.id
- #todo: more info from dirr
- self.mimetype = 'application/x-directory'
- # 'httpd/unix-directory'
- self.create_date = dirr.create_date
- # TODO: the write date should be MAX(file.write)..
- self.write_date = dirr.write_date or dirr.create_date
- self.content_length = 0
- self.res_model = dirr.ressource_type_id.model
- self.resm_id = dirr.ressource_id
- self.namefield = dirr.resource_field or 'name'
- self.displayname = dirr.name
- # Important: the domain is evaluated using the *parent* dctx!
- self.domain = safe_eval(dirr.domain,self.dctx)
- # and then, we add our own vars in the dctx:
- if dctx:
- self.dctx.update(dctx)
-
- # and then, we prepare a dctx dict, for deferred evaluation:
- self.dctx_dict = {}
- for dfld in dirr.dctx_ids:
- self.dctx_dict['dctx_' + dfld.field] = dfld.expr
+ super(node_res_dir,self).__init__(path, parent,context)
+ self.dir_id = dirr.id
+ #todo: more info from dirr
+ self.mimetype = 'application/x-directory'
+ # 'httpd/unix-directory'
+ self.create_date = dirr.create_date
+ # TODO: the write date should be MAX(file.write)..
+ self.write_date = dirr.write_date or dirr.create_date
+ self.content_length = 0
+ self.res_model = dirr.ressource_type_id.model
+ self.resm_id = dirr.ressource_id
+ self.namefield = dirr.resource_field or 'name'
+ self.displayname = dirr.name
+ # Important: the domain is evaluated using the *parent* dctx!
+ self.domain = safe_eval(dirr.domain,self.dctx)
+ # and then, we add our own vars in the dctx:
+ if dctx:
+ self.dctx.update(dctx)
+
+ # and then, we prepare a dctx dict, for deferred evaluation:
+ self.dctx_dict = {}
+ for dfld in dirr.dctx_ids:
+ self.dctx_dict['dctx_' + dfld.field] = dfld.expr
def children(self,cr):
return self._child_get(cr)
@@ -339,81 +339,81 @@ class node_res_dir(node_class):
def _child_get(self,cr,name = None):
""" return virtual children of resource, based on the
- foreign object.
-
- Note that many objects use NULL for a name, so we should
- better call the name_search(),name_get() set of methods
- """
- obj = self.context._dirobj.pool.get(self.res_model)
- if not obj:
- print "couldn't find model", self.res_model
- return []
- uid = self.context.uid
- ctx = self.context.context.copy()
- ctx.update(self.dctx)
- where = []
- if self.domain:
- where.append(self.domain)
- if self.resm_id:
- where.append(('id','=',self.resm_id))
-
- if name:
- where.append((self.namefield,'=',name))
- # print "Where clause for %s" % self.res_model, where
-
- resids = obj.search(cr,uid, where, context=ctx)
- res = []
- for bo in obj.browse(cr,uid,resids,context=ctx):
- if not bo:
- continue
- name = getattr(bo,self.namefield)
- if not name:
- continue
- # Yes! we can't do better but skip nameless records.
- res.append(node_res_obj(name,self,self.context,self.res_model, bo))
- return res
+ foreign object.
+
+ Note that many objects use NULL for a name, so we should
+ better call the name_search(),name_get() set of methods
+ """
+ obj = self.context._dirobj.pool.get(self.res_model)
+ if not obj:
+ print "couldn't find model", self.res_model
+ return []
+ uid = self.context.uid
+ ctx = self.context.context.copy()
+ ctx.update(self.dctx)
+ where = []
+ if self.domain:
+ where.append(self.domain)
+ if self.resm_id:
+ where.append(('id','=',self.resm_id))
+
+ if name:
+ where.append((self.namefield,'=',name))
+ # print "Where clause for %s" % self.res_model, where
+
+ resids = obj.search(cr,uid, where, context=ctx)
+ res = []
+ for bo in obj.browse(cr,uid,resids,context=ctx):
+ if not bo:
+ continue
+ name = getattr(bo,self.namefield)
+ if not name:
+ continue
+ # Yes! we can't do better but skip nameless records.
+ res.append(node_res_obj(name,self,self.context,self.res_model, bo))
+ return res
def _get_ttag(self,cr):
- return 'rdir-%d' % self.dir_id
+ return 'rdir-%d' % self.dir_id
class node_res_obj(node_class):
""" A special sibling to node_dir, which does only contain dynamically
created folders foreach resource in the foreign model.
- All folders should be of type node_res_obj and merely behave like
- node_dirs (with limited domain).
- """
+ All folders should be of type node_res_obj and merely behave like
+ node_dirs (with limited domain).
+ """
our_type = 'collection'
def __init__(self,path, parent, context, res_model, res_bo, res_id = None):
- super(node_res_obj,self).__init__(path, parent,context)
- assert parent
- #todo: more info from dirr
- self.dir_id = parent.dir_id
- self.mimetype = 'application/x-directory'
- # 'httpd/unix-directory'
- self.create_date = parent.create_date
- # TODO: the write date should be MAX(file.write)..
- self.write_date = parent.write_date
- self.content_length = 0
- self.res_model = res_model
- self.domain = parent.domain
- self.displayname = path
+ super(node_res_obj,self).__init__(path, parent,context)
+ assert parent
+ #todo: more info from dirr
+ self.dir_id = parent.dir_id
+ self.mimetype = 'application/x-directory'
+ # 'httpd/unix-directory'
+ self.create_date = parent.create_date
+ # TODO: the write date should be MAX(file.write)..
+ self.write_date = parent.write_date
+ self.content_length = 0
+ self.res_model = res_model
+ self.domain = parent.domain
+ self.displayname = path
- if res_bo:
- self.res_id = res_bo.id
- dc2 = self.context.context
- dc2.update(self.dctx)
- dc2['res_model'] = res_model
- dc2['res_id'] = res_bo.id
- dc2['this'] = res_bo
- for fld,expr in parent.dctx_dict.items():
- try:
- self.dctx[fld] = safe_eval(expr,dc2)
- except Exception,e:
- print "Cannot eval %s for %s" % (expr, fld)
- print e
- pass
- else:
- self.res_id = res_id
+ if res_bo:
+ self.res_id = res_bo.id
+ dc2 = self.context.context
+ dc2.update(self.dctx)
+ dc2['res_model'] = res_model
+ dc2['res_id'] = res_bo.id
+ dc2['this'] = res_bo
+ for fld,expr in parent.dctx_dict.items():
+ try:
+ self.dctx[fld] = safe_eval(expr,dc2)
+ except Exception,e:
+ print "Cannot eval %s for %s" % (expr, fld)
+ print e
+ pass
+ else:
+ self.res_id = res_id
def children(self,cr):
return self._child_get(cr) + self._file_get(cr)
@@ -428,172 +428,172 @@ class node_res_obj(node_class):
return None
def _file_get(self,cr, nodename=False):
- res = []
- cntobj = self.context._dirobj.pool.get('document.directory.content')
- uid = self.context.uid
- ctx = self.context.context.copy()
- ctx.update(self.dctx)
- where = [('directory_id','=',self.dir_id) ]
- #if self.domain:
- # where.extend(self.domain)
- # print "res_obj file_get clause", where
- ids = cntobj.search(cr,uid,where,context=ctx)
+ res = []
+ cntobj = self.context._dirobj.pool.get('document.directory.content')
+ uid = self.context.uid
+ ctx = self.context.context.copy()
+ ctx.update(self.dctx)
+ where = [('directory_id','=',self.dir_id) ]
+ #if self.domain:
+ # where.extend(self.domain)
+ # print "res_obj file_get clause", where
+ ids = cntobj.search(cr,uid,where,context=ctx)
for content in cntobj.browse(cr,uid,ids,context=ctx):
- res3 = cntobj._file_get(cr,self,nodename,content, context=ctx)
- if res3:
- res.extend(res3)
+ res3 = cntobj._file_get(cr,self,nodename,content, context=ctx)
+ if res3:
+ res.extend(res3)
- return res
+ return res
def get_dav_props(self, cr):
- res = {}
- cntobj = self.context._dirobj.pool.get('document.directory.content')
- uid = self.context.uid
- ctx = self.context.context.copy()
- ctx.update(self.dctx)
- where = [('directory_id','=',self.dir_id) ]
- ids = cntobj.search(cr,uid,where,context=ctx)
+ res = {}
+ cntobj = self.context._dirobj.pool.get('document.directory.content')
+ uid = self.context.uid
+ ctx = self.context.context.copy()
+ ctx.update(self.dctx)
+ where = [('directory_id','=',self.dir_id) ]
+ ids = cntobj.search(cr,uid,where,context=ctx)
for content in cntobj.browse(cr,uid,ids,context=ctx):
- if content.extension == '.ics': # FIXME: call the content class!
- res['http://groupdav.org/'] = ('resourcetype',)
- return res
+ if content.extension == '.ics': # FIXME: call the content class!
+ res['http://groupdav.org/'] = ('resourcetype',)
+ return res
def get_dav_eprop(self,cr,ns,prop):
- if ns != 'http://groupdav.org/' or prop != 'resourcetype':
- print "Who asked for %s:%s?" % (ns,prop)
- return None
- res = {}
- cntobj = self.context._dirobj.pool.get('document.directory.content')
- uid = self.context.uid
- ctx = self.context.context.copy()
- ctx.update(self.dctx)
- where = [('directory_id','=',self.dir_id) ]
- ids = cntobj.search(cr,uid,where,context=ctx)
+ if ns != 'http://groupdav.org/' or prop != 'resourcetype':
+ print "Who asked for %s:%s?" % (ns,prop)
+ return None
+ res = {}
+ cntobj = self.context._dirobj.pool.get('document.directory.content')
+ uid = self.context.uid
+ ctx = self.context.context.copy()
+ ctx.update(self.dctx)
+ where = [('directory_id','=',self.dir_id) ]
+ ids = cntobj.search(cr,uid,where,context=ctx)
for content in cntobj.browse(cr,uid,ids,context=ctx):
- if content.extension == '.ics': # FIXME: call the content class!
- return ('vevent-collection','http://groupdav.org/')
- return None
+ if content.extension == '.ics': # FIXME: call the content class!
+ return ('vevent-collection','http://groupdav.org/')
+ return None
def _child_get(self,cr,name = None):
- dirobj = self.context._dirobj
- uid = self.context.uid
- ctx = self.context.context.copy()
- ctx.update(self.dctx)
- where = [('parent_id','=',self.dir_id) ]
- if name:
- where.append(('name','=',name))
- ids = dirobj.search(cr, uid, where,context=ctx)
- res = []
- if ids:
- for dirr in dirobj.browse(cr,uid,ids,context=ctx):
- if dirr.type == 'directory':
- res.append(node_res_obj(dirr.name,self,self.context,self.res_model,res_bo = None, res_id = self.res_id))
- elif dirr.type == 'ressource':
- # child resources can be controlled by properly set dctx
- res.append(node_res_dir(dirr.name,self,self.context,dirr))
-
- fil_obj=dirobj.pool.get('ir.attachment')
- where2 = where + [('res_model', '=', self.res_model), ('res_id','=',self.res_id)]
- # print "where clause for dir_obj", where2
- ids = fil_obj.search(cr,uid,where2,context=ctx)
- if ids:
- for fil in fil_obj.browse(cr,uid,ids,context=ctx):
- res.append(node_file(fil.name,self,self.context,fil))
-
- return res
-
+ dirobj = self.context._dirobj
+ uid = self.context.uid
+ ctx = self.context.context.copy()
+ ctx.update(self.dctx)
+ where = [('parent_id','=',self.dir_id) ]
+ if name:
+ where.append(('name','=',name))
+ ids = dirobj.search(cr, uid, where,context=ctx)
+ res = []
+ if ids:
+ for dirr in dirobj.browse(cr,uid,ids,context=ctx):
+ if dirr.type == 'directory':
+ res.append(node_res_obj(dirr.name,self,self.context,self.res_model,res_bo = None, res_id = self.res_id))
+ elif dirr.type == 'ressource':
+ # child resources can be controlled by properly set dctx
+ res.append(node_res_dir(dirr.name,self,self.context,dirr))
+
+ fil_obj=dirobj.pool.get('ir.attachment')
+ where2 = where + [('res_model', '=', self.res_model), ('res_id','=',self.res_id)]
+ # print "where clause for dir_obj", where2
+ ids = fil_obj.search(cr,uid,where2,context=ctx)
+ if ids:
+ for fil in fil_obj.browse(cr,uid,ids,context=ctx):
+ res.append(node_file(fil.name,self,self.context,fil))
+
+ return res
+
def create_child(self,cr,path,data):
- """ API function to create a child file object and node
- Return the node_* created
- """
- dirobj = self.context._dirobj
- uid = self.context.uid
- ctx = self.context.context.copy()
- ctx.update(self.dctx)
- fil_obj=dirobj.pool.get('ir.attachment')
- val = {
- 'name': path,
- 'datas_fname': path,
- 'parent_id': self.dir_id,
- 'res_model': self.res_model,
- 'res_id': self.res_id,
- # Datas are not set here
- }
+ """ API function to create a child file object and node
+ Return the node_* created
+ """
+ dirobj = self.context._dirobj
+ uid = self.context.uid
+ ctx = self.context.context.copy()
+ ctx.update(self.dctx)
+ fil_obj=dirobj.pool.get('ir.attachment')
+ val = {
+ 'name': path,
+ 'datas_fname': path,
+ 'parent_id': self.dir_id,
+ 'res_model': self.res_model,
+ 'res_id': self.res_id,
+ # Datas are not set here
+ }
- fil_id = fil_obj.create(cr,uid, val, context=ctx)
- fil = fil_obj.browse(cr,uid,fil_id,context=ctx)
- fnode = node_file(path,self,self.context,fil)
- fnode.set_data(cr,data,fil)
- return fnode
+ fil_id = fil_obj.create(cr,uid, val, context=ctx)
+ fil = fil_obj.browse(cr,uid,fil_id,context=ctx)
+ fnode = node_file(path,self,self.context,fil)
+ fnode.set_data(cr,data,fil)
+ return fnode
def _get_ttag(self,cr):
- return 'rodir-%d-%d' % (self.dir_id,self.res_id)
+ return 'rodir-%d-%d' % (self.dir_id,self.res_id)
class node_file(node_class):
our_type = 'file'
def __init__(self,path, parent, context, fil):
- super(node_file,self).__init__(path, parent,context)
- self.file_id = fil.id
- #todo: more info from ir_attachment
- if fil.file_type and '/' in fil.file_type:
- self.mimetype = fil.file_type
- self.create_date = fil.create_date
- self.write_date = fil.write_date or fil.create_date
- self.content_length = fil.file_size
- self.displayname = fil.name
-
- # This only propagates the problem to get_data. Better
- # fix those files to point to the root dir.
- if fil.parent_id:
- self.storage_id = fil.parent_id.storage_id.id
- else:
- self.storage_id = None
-
+ super(node_file,self).__init__(path, parent,context)
+ self.file_id = fil.id
+ #todo: more info from ir_attachment
+ if fil.file_type and '/' in fil.file_type:
+ self.mimetype = fil.file_type
+ self.create_date = fil.create_date
+ self.write_date = fil.write_date or fil.create_date
+ self.content_length = fil.file_size
+ self.displayname = fil.name
+
+ # This only propagates the problem to get_data. Better
+ # fix those files to point to the root dir.
+ if fil.parent_id:
+ self.storage_id = fil.parent_id.storage_id.id
+ else:
+ self.storage_id = None
+
def get_data(self, cr, fil_obj = None):
- """ Retrieve the data for some file.
- fil_obj may optionally be specified, and should be a browse object
- for the file. This is useful when the caller has already initiated
- the browse object. """
- # this is where storage kicks in..
- stor = self.storage_id
- assert stor
- stobj = self.context._dirobj.pool.get('document.storage')
- return stobj.get_data(cr,self.context.uid,stor, self,self.context.context, fil_obj)
+ """ Retrieve the data for some file.
+ fil_obj may optionally be specified, and should be a browse object
+ for the file. This is useful when the caller has already initiated
+ the browse object. """
+ # this is where storage kicks in..
+ stor = self.storage_id
+ assert stor
+ stobj = self.context._dirobj.pool.get('document.storage')
+ return stobj.get_data(cr,self.context.uid,stor, self,self.context.context, fil_obj)
def get_data_len(self, cr, fil_obj = None):
- # TODO: verify with the storage object!
- return self.content_length
+ # TODO: verify with the storage object!
+ return self.content_length
def set_data(self, cr, data, fil_obj = None):
- """ Store data at some file.
- fil_obj may optionally be specified, and should be a browse object
- for the file. This is useful when the caller has already initiated
- the browse object. """
- # this is where storage kicks in..
- stor = self.storage_id
- assert stor
- stobj = self.context._dirobj.pool.get('document.storage')
- return stobj.set_data(cr,self.context.uid,stor, self, data, self.context.context, fil_obj)
+ """ Store data at some file.
+ fil_obj may optionally be specified, and should be a browse object
+ for the file. This is useful when the caller has already initiated
+ the browse object. """
+ # this is where storage kicks in..
+ stor = self.storage_id
+ assert stor
+ stobj = self.context._dirobj.pool.get('document.storage')
+ return stobj.set_data(cr,self.context.uid,stor, self, data, self.context.context, fil_obj)
def _get_ttag(self,cr):
- return 'file-%d' % self.file_id
+ return 'file-%d' % self.file_id
class node_content(node_class):
our_type = 'content'
def __init__(self,path, parent, context, cnt, dctx = None, act_id=None):
- super(node_content,self).__init__(path, parent,context)
- self.cnt_id = cnt.id
- self.create_date = False
- self.write_date = False
- self.content_length = False
- self.extension = cnt.extension
- self.report_id = cnt.report_id and cnt.report_id.id
- #self.mimetype = cnt.extension.
- self.displayname = path
- if dctx:
- self.dctx.update(dctx)
- self.act_id = act_id
-
+ super(node_content,self).__init__(path, parent,context)
+ self.cnt_id = cnt.id
+ self.create_date = False
+ self.write_date = False
+ self.content_length = False
+ self.extension = cnt.extension
+ self.report_id = cnt.report_id and cnt.report_id.id
+ #self.mimetype = cnt.extension.
+ self.displayname = path
+ if dctx:
+ self.dctx.update(dctx)
+ self.act_id = act_id
+
def fill_fields(self,cr,dctx = None):
""" Try to read the object and fill missing fields, like mimetype,
dates etc.
@@ -610,17 +610,17 @@ class node_content(node_class):
def get_data(self, cr, fil_obj = None):
cntobj = self.context._dirobj.pool.get('document.directory.content')
- ctx = self.context.context.copy()
- ctx.update(self.dctx)
- data = cntobj.process_read(cr,self.context.uid,self,ctx)
- if data:
- self.content_length = len(data)
- return data
+ ctx = self.context.context.copy()
+ ctx.update(self.dctx)
+ data = cntobj.process_read(cr,self.context.uid,self,ctx)
+ if data:
+ self.content_length = len(data)
+ return data
def get_data_len(self, cr, fil_obj = None):
- if not self.content_length:
- self.get_data(cr,fil_obj)
- return self.content_length
+ if not self.content_length:
+ self.get_data(cr,fil_obj)
+ return self.content_length
def set_data(self, cr, data, fil_obj = None):
cntobj = self.context._dirobj.pool.get('document.directory.content')
@@ -651,9 +651,9 @@ class old_class():
if nodename:
where.append( (fobj._rec_name,'=',nodename) )
for content in self.object.content_ids:
- res3 = content._table._file_get(self,nodename,content)
- if res3:
- res2.extend(res3)
+ res3 = content._table._file_get(self,nodename,content)
+ if res3:
+ res2.extend(res3)
ids = fobj.search(self.cr, self.uid, where+[ ('parent_id','=',self.object and self.object.id or False) ])
if self.object and self.root and (self.object.type=='ressource'):
diff --git a/addons/document/std_index.py b/addons/document/std_index.py
index 730e5082a1b..a35f1ab615d 100644
--- a/addons/document/std_index.py
+++ b/addons/document/std_index.py
@@ -38,61 +38,61 @@ def _to_unicode(s):
return s
class TxtIndex(indexer):
- def _getMimeTypes(self):
- return ['text/plain','text/html','text/diff','text/xml', 'text/*',
- 'application/xml']
-
- def _getExtensions(self):
- return ['.txt', '.py']
+ def _getMimeTypes(self):
+ return ['text/plain','text/html','text/diff','text/xml', 'text/*',
+ 'application/xml']
+
+ def _getExtensions(self):
+ return ['.txt', '.py']
- def _doIndexContent(self,content):
- return content
-
+ def _doIndexContent(self,content):
+ return content
+
cntIndex.register(TxtIndex())
class DocIndex(indexer):
- def _getMimeTypes(self):
- return [ 'application/ms-word']
-
- def _getExtensions(self):
- return ['.doc']
+ def _getMimeTypes(self):
+ return [ 'application/ms-word']
+
+ def _getExtensions(self):
+ return ['.doc']
- def _doIndexFile(self,fname):
- fp = Popen(['antiword',fname], shell=False, stdout=PIPE).stdout
- return _to_unicode( fp.read())
+ def _doIndexFile(self,fname):
+ fp = Popen(['antiword',fname], shell=False, stdout=PIPE).stdout
+ return _to_unicode( fp.read())
cntIndex.register(DocIndex())
class PdfIndex(indexer):
- def _getMimeTypes(self):
- return [ 'application/pdf']
-
- def _getExtensions(self):
- return ['.pdf']
+ def _getMimeTypes(self):
+ return [ 'application/pdf']
+
+ def _getExtensions(self):
+ return ['.pdf']
- def _doIndexFile(self,fname):
- fp = Popen(['pdftotext', '-enc', 'UTF-8', '-nopgbrk', fname, '-'], shell=False, stdout=PIPE).stdout
- return _to_unicode( fp.read())
+ def _doIndexFile(self,fname):
+ fp = Popen(['pdftotext', '-enc', 'UTF-8', '-nopgbrk', fname, '-'], shell=False, stdout=PIPE).stdout
+ return _to_unicode( fp.read())
cntIndex.register(PdfIndex())
class ImageNoIndex(indexer):
- def _getMimeTypes(self):
- return [ 'image/*']
-
- def _getExtensions(self):
- #better return no extension, and let 'file' do its magic
- return []
- #return ['.png','.jpg','.gif','.jpeg','.bmp','.tiff']
+ def _getMimeTypes(self):
+ return [ 'image/*']
+
+ def _getExtensions(self):
+ #better return no extension, and let 'file' do its magic
+ return []
+ #return ['.png','.jpg','.gif','.jpeg','.bmp','.tiff']
- def _doIndexContent(self,content):
- return 'image'
+ def _doIndexContent(self,content):
+ return 'image'
cntIndex.register(ImageNoIndex())
#class Doc(indexer):
- #def _getDefMime(self,ext):
+ #def _getDefMime(self,ext):
#def content_index(content, filename=None, content_type=None):
#fname,ext = os.path.splitext(filename)
diff --git a/addons/document/test_cindex.py b/addons/document/test_cindex.py
index 9993ffad425..dc5fcf7ebeb 100755
--- a/addons/document/test_cindex.py
+++ b/addons/document/test_cindex.py
@@ -22,25 +22,25 @@ import content_index, std_index
from content_index import cntIndex
for fname in args:
- try:
- if options.docontent:
- fp = open(fname,'rb')
- content = fp.read()
- fp.close()
- res = cntIndex.doIndex(content,fname,None,None,True)
- else:
- res = cntIndex.doIndex(None, fname, None, fname,True)
+ try:
+ if options.docontent:
+ fp = open(fname,'rb')
+ content = fp.read()
+ fp.close()
+ res = cntIndex.doIndex(content,fname,None,None,True)
+ else:
+ res = cntIndex.doIndex(None, fname, None, fname,True)
- if res:
- print "Result: ", res[0]
- print res[1]
- else:
- print "No result"
- except Exception,e:
- import traceback,sys
- tb_s = reduce(lambda x, y: x+y, traceback.format_exception( sys.exc_type, sys.exc_value, sys.exc_traceback))
- print "Traceback:",tb_s
- print "Exception: ",e
-
+ if res:
+ print "Result: ", res[0]
+ print res[1]
+ else:
+ print "No result"
+ except Exception,e:
+ import traceback,sys
+ tb_s = reduce(lambda x, y: x+y, traceback.format_exception( sys.exc_type, sys.exc_value, sys.exc_traceback))
+ print "Traceback:",tb_s
+ print "Exception: ",e
+
#eof
From 307898c2a8152de4dabdce9a90a37f1412d74d80 Mon Sep 17 00:00:00 2001
From: "Harry (Open ERP)"
Date: Tue, 8 Dec 2009 19:00:41 +0530
Subject: [PATCH 04/19] [FIX] document: fixed problem on mkdir, remove, create
file, rename
bzr revid: hmo@tinyerp.com-20091208133041-ikpo3by8r8xbrthb
---
addons/document/directory_content.py | 20 +-
addons/document/document_directory.py | 6 +-
addons/document/document_storage.py | 11 +-
addons/document/ftpserver/abstracted_fs.py | 282 ++++++++++++---------
addons/document/nodes.py | 19 +-
5 files changed, 193 insertions(+), 145 deletions(-)
diff --git a/addons/document/directory_content.py b/addons/document/directory_content.py
index dbd801d6be0..a43b5730627 100644
--- a/addons/document/directory_content.py
+++ b/addons/document/directory_content.py
@@ -24,8 +24,10 @@ from osv import osv, fields
from osv.orm import except_orm
import urlparse
+import netsvc
import os
import nodes
+import StringIO
class document_directory_content_type(osv.osv):
_name = 'document.directory.content.type'
@@ -71,17 +73,16 @@ class document_directory_content(osv.osv):
"""
# TODO: respect the context!
- if content.include_name and not node.object2:
+ model = node.res_model
+ if content.include_name and not model:
return False
res2 = []
tname = ''
if content.include_name:
- content_name = node.object2.name
- obj = pool.get(node.object.ressource_type_id.model)
- name_for = obj._name.split('.')[-1]
- if content_name and content_name.find(name_for) == 0 :
- content_name = content_name.replace(name_for,'')
+ content_name = node.displayname or ''
+ obj = node.context._dirobj.pool.get(model)
+ if content_name:
tname = (content.prefix or '') + content_name + (content.suffix or '') + (content.extension or '')
else:
tname = (content.prefix or '') + (content.suffix or '') + (content.extension or '')
@@ -101,12 +102,13 @@ class document_directory_content(osv.osv):
if node.extension != '.pdf':
raise Exception("Invalid content: %s" % node.extension)
return True
+
def process_read(self, cr, uid, node):
if node.extension != '.pdf':
raise Exception("Invalid content: %s" % node.extension)
- report = self.pool.get('ir.actions.report.xml').browse(cr, uid, node.content.report_id.id)
- srv = netsvc.LocalService('report.'+report.report_name)
- pdf,pdftype = srv.create(cr, uid, [node.object.id], {}, {})
+ report = self.pool.get('ir.actions.report.xml').browse(cr, uid, node.report_id)
+ srv = netsvc.Service._services['report.'+report.report_name]
+ pdf,pdftype = srv.create(cr, uid, [node.context.context['res_id']], {}, {})
s = StringIO.StringIO(pdf)
s.name = node
return s
diff --git a/addons/document/document_directory.py b/addons/document/document_directory.py
index 95e4d438b76..1f95fd6a61a 100644
--- a/addons/document/document_directory.py
+++ b/addons/document/document_directory.py
@@ -176,9 +176,9 @@ class document_directory(osv.osv):
did = root_id
duri = uri
path = []
- context = ncontext.context
- while len(duri):
- nid = self.search(cr,uid,[('parent_id','=',did),('name','=',duri[0]),('type','=','directory')], context=context)
+ context = ncontext.context
+ while len(duri):
+ nid = self.search(cr,uid,[('parent_id','=',did),('name','=',duri[0]),('type','=','directory')], context=context)
if not nid:
break
if len(nid)>1:
diff --git a/addons/document/document_storage.py b/addons/document/document_storage.py
index 488a5ee7e6c..54e4ad3f9cf 100644
--- a/addons/document/document_storage.py
+++ b/addons/document/document_storage.py
@@ -190,11 +190,12 @@ class document_storage(osv.osv):
path = boo.path
try:
flag = None
- # This can be improved
- for dirs in os.listdir(path):
- if os.path.isdir(os.path.join(path,dirs)) and len(os.listdir(os.path.join(path,dirs)))<4000:
- flag = dirs
- break
+ # This can be improved
+ if os.path.isdir(path):
+ for dirs in os.listdir(path):
+ if os.path.isdir(os.path.join(path,dirs)) and len(os.listdir(os.path.join(path,dirs)))<4000:
+ flag = dirs
+ break
flag = flag or create_directory(path)
filename = random_name()
fname = os.path.join(path, flag, filename)
diff --git a/addons/document/ftpserver/abstracted_fs.py b/addons/document/ftpserver/abstracted_fs.py
index d795110d4ea..11fd56f976e 100644
--- a/addons/document/ftpserver/abstracted_fs.py
+++ b/addons/document/ftpserver/abstracted_fs.py
@@ -13,6 +13,7 @@ import netsvc
import os
from service import security
from osv import osv
+from document.nodes import node_res_dir, node_res_obj
import stat
def log(message):
@@ -202,18 +203,13 @@ class abstracted_fs:
# Ok
def fs2ftp(self, node):
- res = '/'
+ res='/'
if node:
- path = node.path
- print '**********', path
- if isinstance(path,list):
- path = '/'.join(path)
- res = os.path.normpath(path)
+ res=os.path.normpath(''.join(node.full_path()))
res = res.replace("\\", "/")
while res[:2] == '//':
res = res[1:]
- if len(node.path):
- res = '/' + node.path[0] + '/' + _to_decode(res)
+ res='/' + node.context.dbname + '/' + _to_decode(res)
#res = node and ('/' + node.cr.dbname + '/' + _to_decode(self.ftpnorm(node.path))) or '/'
return res
@@ -235,16 +231,16 @@ class abstracted_fs:
# Ok
def create(self, node, objname, mode):
- objname=_to_unicode(objname)
- cr = node.cr
- uid = node.uid
- pool = pooler.get_pool(cr.dbname)
- child = node.child(objname)
+ objname = _to_unicode(objname)
+ uid = node.context.uid
+ pool = pooler.get_pool(node.context.dbname)
+ cr = pooler.get_db(node.context.dbname).cursor()
+ child = node.child(cr, objname)
if child:
if child.type in ('collection','database'):
raise OSError(1, 'Operation not permited.')
- if child.type=='content':
- s = content_wrapper(cr.dbname, uid, pool, child)
+ if child.type == 'content':
+ s = content_wrapper(node.context.dbname, uid, pool, child)
return s
try:
fobj = pool.get('ir.attachment')
@@ -252,29 +248,34 @@ class abstracted_fs:
# TODO: test if already exist and modify in this case if node.type=file
### checked already exits
- object2=node and node.object2 or False
- object=node and node.object or False
- cid=False
-
- where=[('name','=',objname)]
+ object2 = False
+ if isinstance(node, node_res_obj):
+ object2 = node and pool.get(node.context.context['res_model']).browse(cr, uid, node.context.context['res_id']) or False
+
+ cid = False
+ object = node.context._dirobj.browse(cr, uid, node.dir_id)
+ where = [('name','=',objname)]
if object and (object.type in ('directory')) or object2:
where.append(('parent_id','=',object.id))
else:
where.append(('parent_id','=',False))
if object2:
- where +=[('res_id','=',object2.id),('res_model','=',object2._name)]
+ where += [('res_id','=',object2.id),('res_model','=',object2._name)]
cids = fobj.search(cr, uid,where)
if len(cids):
- cid=cids[0]
+ cid = cids[0]
if not cid:
val = {
'name': objname,
'datas_fname': objname,
+ 'parent_id' : node.dir_id,
'datas': '',
'file_size': 0L,
'file_type': ext,
+ 'store_method' : (object.storage_id.type == 'filestore' and 'fs')\
+ or (object.storage_id.type == 'db' and 'db')
}
if object and (object.type in ('directory')) or not object2:
val['parent_id']= object and object.id or False
@@ -292,9 +293,9 @@ class abstracted_fs:
cid = fobj.create(cr, uid, val, context={})
cr.commit()
- s = file_wrapper('', cid, cr.dbname, uid, )
+ s = file_wrapper('', cid, node.context.dbname, uid, )
return s
- except Exception,e:
+ except Exception,e:
log(e)
raise OSError(1, 'Operation not permited.')
@@ -304,16 +305,23 @@ class abstracted_fs:
raise OSError(1, 'Operation not permited.')
# Reading operation
if node.type=='file':
+ cr = pooler.get_db(node.context.dbname).cursor()
+ uid = node.context.uid
if not self.isfile(node):
raise OSError(1, 'Operation not permited.')
- s = StringIO.StringIO(base64.decodestring(node.object.datas or ''))
+ fobj = node.context._dirobj.pool.get('ir.attachment').browse(cr, uid, node.file_id, context=node.context.context)
+ if fobj.store_method and fobj.store_method== 'fs' :
+ s = StringIO.StringIO(node.get_data(cr, fobj))
+ else:
+ s = StringIO.StringIO(base64.decodestring(fobj.db_datas or ''))
s.name = node
return s
elif node.type=='content':
- cr = node.cr
- uid = node.uid
- pool = pooler.get_pool(cr.dbname)
- return getattr(pool.get('document.directory.content'), 'process_read_'+node.content.extension[1:])(cr, uid, node)
+# cr = node.cr
+ uid = node.context.uid
+ cr = pooler.get_db(node.context.dbname).cursor()
+ pool = pooler.get_pool(node.context.dbname)
+ return getattr(pool.get('document.directory.content'), 'process_read')(cr, uid, node)
else:
raise OSError(1, 'Operation not permited.')
@@ -344,7 +352,7 @@ class abstracted_fs:
if dir:
cr = dir.cr
uid = dir.uid
- pool = pooler.get_pool(cr.dbname)
+ pool = pooler.get_pool(node.context.dbname)
object=dir and dir.object or False
object2=dir and dir.object2 or False
res=pool.get('ir.attachment').search(cr,uid,[('name','like',prefix),('parent_id','=',object and object.type in ('directory','ressource') and object.id or False),('res_id','=',object2 and object2.id or False),('res_model','=',object2 and object2._name or False)])
@@ -359,8 +367,8 @@ class abstracted_fs:
# Ok
def chdir(self, path):
if not path:
- self.cwd='/'
- return None
+ self.cwd = '/'
+ return None
if path.type in ('collection','database'):
self.cwd = self.fs2ftp(path)
else:
@@ -369,31 +377,36 @@ class abstracted_fs:
# Ok
def mkdir(self, node, basename):
"""Create the specified directory."""
+ cr = False
if not node:
raise OSError(1, 'Operation not permited.')
try:
- basename=_to_unicode(basename)
- object2=node and node.object2 or False
- object=node and node.object or False
- cr = node.cr
- uid = node.uid
- pool = pooler.get_pool(cr.dbname)
- if node.object and (node.object.type=='ressource') and not node.object2:
+ basename =_to_unicode(basename)
+ cr = pooler.get_db(node.context.dbname).cursor()
+ uid = node.context.uid
+ pool = pooler.get_pool(node.context.dbname)
+ object2 = False
+ if isinstance(node, node_res_obj):
+ object2 = node and pool.get(node.context.context['res_model']).browse(cr, uid, node.context.context['res_id']) or False
+ obj = node.context._dirobj.browse(cr, uid, node.dir_id)
+ if obj and (obj.type == 'ressource') and not object2:
raise OSError(1, 'Operation not permited.')
val = {
'name': basename,
- 'ressource_parent_type_id': object and object.ressource_type_id.id or False,
+ 'ressource_parent_type_id': obj and obj.ressource_type_id.id or False,
'ressource_id': object2 and object2.id or False
}
- if (object and (object.type in ('directory'))) or not object2:
- val['parent_id'] = object and object.id or False
+ #if (obj and (obj.type in ('directory'))) or not object2:
+ # val['parent_id'] = obj and obj.id or False
+ val['parent_id'] = node.dir_id
# Check if it alreayd exists !
pool.get('document.directory').create(cr, uid, val)
- cr.commit()
+ cr.commit()
except Exception,e:
log(e)
raise OSError(1, 'Operation not permited.')
-
+ finally:
+ if cr: cr.close()
# Ok
def close_cr(self, data):
@@ -421,8 +434,9 @@ class abstracted_fs:
# Ok
def listdir(self, path):
"""List the content of a directory."""
- class false_node:
- object = None
+ class false_node(object):
+ write_date = None
+ create_date = None
type = 'database'
def __init__(self, db):
self.path = '/'+db
@@ -434,55 +448,83 @@ class abstracted_fs:
uid = security.login(db, self.username, self.password)
if uid:
result.append(false_node(db))
- except osv.except_osv:
+ except osv.except_osv:
pass
return result
- return path.children()
+ cr = pooler.get_db(path.context.dbname).cursor()
+ return path.children(cr)
# Ok
def rmdir(self, node):
"""Remove the specified directory."""
- cr = node.cr
- uid = node.uid
- pool = pooler.get_pool(cr.dbname)
- object2=node and node.object2 or False
- object=node and node.object or False
- if object._table_name=='document.directory':
- if node.children():
+ assert node
+ cr = pooler.get_db(node.context.dbname).cursor()
+ uid = node.context.uid
+ pool = pooler.get_pool(node.context.dbname)
+ 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(39, 'Directory not empty.')
+ raise OSError(1, 'Operation not permited.')
cr.commit()
+ cr.close()
# Ok
def remove(self, node):
+ assert node
+ if node.type == 'collection':
+ return self.rmdir(node)
+ elif node.type == 'file':
+ return self.rmfile(node)
+ raise OSError(1, 'Operation not permited.')
+
+ def rmfile(self, node):
"""Remove the specified file."""
- cr = node.cr
- uid = node.uid
- pool = pooler.get_pool(cr.dbname)
- object2=node and node.object2 or False
- object=node and node.object or False
+ assert node
+ if node.type == 'collection':
+ return self.rmdir(node)
+ uid = node.context.uid
+ pool = pooler.get_pool(node.context.dbname)
+ cr = pooler.get_db(node.context.dbname).cursor()
+ object = pool.get('ir.attachment').browse(cr, uid, node.file_id)
if not object:
raise OSError(2, 'Not such file or directory.')
- if object._table_name=='ir.attachment':
+ 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()
# Ok
- def rename(self, src, dst_basedir,dst_basename):
+ def rename(self, src, dst_basedir, dst_basename):
"""
Renaming operation, the effect depends on the src:
* A file: read, create and remove
* A directory: change the parent and reassign childs to ressource
"""
+ cr = False
try:
- dst_basename=_to_unicode(dst_basename)
- if src.type=='collection':
- if src.object._table_name <> 'document.directory':
+ dst_basename = _to_unicode(dst_basename)
+ cr = pooler.get_db(src.context.dbname).cursor()
+ uid = src.context.uid
+
+ if src.type == 'collection':
+ obj2 = False
+ dst_obj2 = False
+ pool = pooler.get_pool(src.context.dbname)
+ if isinstance(src, node_res_obj):
+ obj2 = src and pool.get(src.context.context['res_model']).browse(cr, uid, src.context.context['res_id']) or False
+ obj = src.context._dirobj.browse(cr, uid, src.dir_id)
+ if isinstance(dst_basedir, node_res_obj):
+ dst_obj2 = dst_basedir and pool.get(dst_basedir.context.context['res_model']).browse(cr, uid, dst_basedir.context.context['res_id']) or False
+ dst_obj = dst_basedir.context._dirobj.browse(cr, uid, dst_basedir.dir_id)
+ if obj._table_name <> 'document.directory':
raise OSError(1, 'Operation not permited.')
result = {
'directory': [],
@@ -491,44 +533,41 @@ class abstracted_fs:
# Compute all childs to set the new ressource ID
child_ids = [src]
while len(child_ids):
- node = child_ids.pop(0)
- child_ids += node.children()
- if node.type =='collection':
- result['directory'].append(node.object.id)
- if (not node.object.ressource_id) and node.object2:
+ node = child_ids.pop(0)
+ child_ids += node.children(cr)
+ object2 = node and hasattr(node, "res_model") and node.res_model or False
+ object = node.context._dirobj.browse(cr, uid, node.dir_id)
+ if node.type == 'collection':
+ result['directory'].append(object.id)
+ if (not object.ressource_id) and object2:
raise OSError(1, 'Operation not permited.')
- elif node.type =='file':
- result['attachment'].append(node.object.id)
+ elif node.type == 'file':
+ result['attachment'].append(object.id)
- cr = src.cr
- uid = src.uid
- pool = pooler.get_pool(cr.dbname)
- object2=src and src.object2 or False
- object=src and src.object or False
- if object2 and not object.ressource_id:
+
+ if obj2 and not obj.ressource_id:
raise OSError(1, 'Operation not permited.')
val = {
'name':dst_basename,
}
- if (dst_basedir.object and (dst_basedir.object.type in ('directory'))) or not dst_basedir.object2:
- val['parent_id'] = dst_basedir.object and dst_basedir.object.id or False
+ if (dst_obj and (dst_obj.type in ('directory'))) or not dst_obj2:
+ val['parent_id'] = dst_obj and dst_obj.id or False
else:
val['parent_id'] = False
- res = pool.get('document.directory').write(cr, uid, [object.id],val)
+ res = pool.get('document.directory').write(cr, uid, [obj.id], val)
- if dst_basedir.object2:
- ressource_type_id = pool.get('ir.model').search(cr,uid,[('model','=',dst_basedir.object2._name)])[0]
- ressource_id = dst_basedir.object2.id
- title = dst_basedir.object2.name
- ressource_model = dst_basedir.object2._name
- if dst_basedir.object2._name=='res.partner':
- partner_id=dst_basedir.object2.id
- else:
- obj2=pool.get(dst_basedir.object2._name)
- partner_id= obj2.fields_get(cr,uid,['partner_id']) and dst_basedir.object2.partner_id.id or False
+ if dst_obj2:
+ ressource_type_id = pool.get('ir.model').search(cr, uid, [('model','=',dst_obj2._name)])[0]
+ ressource_id = dst_obj2.id
+ title = dst_obj2.name
+ ressource_model = dst_obj2._name
+ if dst_obj2._name == 'res.partner':
+ partner_id = dst_obj2.id
+ else:
+ partner_id= pool.get(dst_obj2._name).fields_get(cr, uid, ['partner_id']) and dst_obj2.partner_id.id or False
else:
ressource_type_id = False
- ressource_id=False
+ ressource_id = False
ressource_model = False
partner_id = False
title = False
@@ -545,11 +584,17 @@ class abstracted_fs:
}
pool.get('ir.attachment').write(cr, uid, result['attachment'], val)
if (not val['res_id']) and result['attachment']:
- dst_basedir.cr.execute('update ir_attachment set res_id=NULL where id in ('+','.join(map(str,result['attachment']))+')')
+ cr.execute('update ir_attachment set res_id=NULL where id in ('+','.join(map(str,result['attachment']))+')')
cr.commit()
- elif src.type=='file':
- pool = pooler.get_pool(src.cr.dbname)
+ elif src.type == 'file':
+ pool = pooler.get_pool(src.context.dbname)
+ obj = pool.get('ir.attachment').browse(cr, uid, src.file_id)
+ dst_obj2 = False
+ if isinstance(dst_basedir, node_res_dir):
+ dst_obj2 = dst_basedir and dst_basedir.res_model or False
+ dst_obj = dst_basedir.context._dirobj.browse(cr, uid, dst_basedir.dir_id)
+
val = {
'partner_id':False,
#'res_id': False,
@@ -559,40 +604,40 @@ class abstracted_fs:
'title': dst_basename,
}
- if (dst_basedir.object and (dst_basedir.object.type in ('directory','ressource'))) or not dst_basedir.object2:
- val['parent_id'] = dst_basedir.object and dst_basedir.object.id or False
+ if (dst_obj and (dst_obj.type in ('directory','ressource'))) or not dst_obj2:
+ val['parent_id'] = dst_obj and dst_obj.id or False
else:
val['parent_id'] = False
- if dst_basedir.object2:
- val['res_model'] = dst_basedir.object2._name
- val['res_id'] = dst_basedir.object2.id
- val['title'] = dst_basedir.object2.name
- if dst_basedir.object2._name=='res.partner':
- val['partner_id']=dst_basedir.object2.id
- else:
- obj2=pool.get(dst_basedir.object2._name)
- val['partner_id']= obj2.fields_get(src.cr, src.uid,['partner_id']) and dst_basedir.object2.partner_id.id or False
- elif src.object.res_id:
+ if dst_obj2:
+ val['res_model'] = dst_obj2._name
+ val['res_id'] = dst_obj2.id
+ val['title'] = dst_obj2.name
+ if dst_obj2._name == 'res.partner':
+ val['partner_id'] = dst_obj2.id
+ else:
+ val['partner_id'] = pool.get(dst_obj2._name).fields_get(src.cr, src.uid, ['partner_id']) and dst_obj2.partner_id.id or False
+ elif obj.res_id:
# I had to do that because writing False to an integer writes 0 instead of NULL
# change if one day we decide to improve osv/fields.py
- dst_basedir.cr.execute('update ir_attachment set res_id=NULL where id=%s', (src.object.id,))
+ cr.execute('update ir_attachment set res_id=NULL where id=%s', (obj.id,))
- pool.get('ir.attachment').write(src.cr, src.uid, [src.object.id], val)
- src.cr.commit()
+ pool.get('ir.attachment').write(cr, uid, [obj.id], val)
+ cr.commit()
elif src.type=='content':
- src_file=self.open(src,'r')
- dst_file=self.create(dst_basedir,dst_basename,'w')
+ src_file = self.open(src,'r')
+ dst_file = self.create(dst_basedir, dst_basename, 'w')
dst_file.write(src_file.getvalue())
dst_file.close()
src_file.close()
- src.cr.commit()
+ cr.commit()
else:
raise OSError(1, 'Operation not permited.')
except Exception,err:
log(err)
raise OSError(1,'Operation not permited.')
-
+ finally:
+ if cr: cr.close()
@@ -635,15 +680,16 @@ class abstracted_fs:
"""Return the size of the specified file in bytes."""
result = 0L
if node.type=='file':
- result = node.object.file_size or 0L
+ result = node.content_length or 0L
return result
# Ok
def getmtime(self, node):
"""Return the last modified time as a number of seconds since
the epoch."""
- if node.object and node.type<>'content':
- dt = (node.object.write_date or node.object.create_date)[:19]
+
+ if node.write_date or node.create_date:
+ dt = (node.write_date or node.create_date)[:19]
result = time.mktime(time.strptime(dt, '%Y-%m-%d %H:%M:%S'))
else:
result = time.mktime(time.localtime())
diff --git a/addons/document/nodes.py b/addons/document/nodes.py
index 845ba4e3ace..42a0e21f2f7 100644
--- a/addons/document/nodes.py
+++ b/addons/document/nodes.py
@@ -51,7 +51,7 @@ class node_context(object):
def __init__(self, cr, uid, context=None):
# we don't cache the cr!
- #self.cr = cr
+ self.dbname = cr.dbname
self.uid = uid
self.context = context
self._dirobj = pooler.get_pool(cr.dbname).get('document.directory')
@@ -114,7 +114,7 @@ class node_class(object):
s+=self.path
else:
s.append(self.path)
- return s
+ return map(lambda x: '/' +x, s)
def children(self, cr):
print "node_class.children()"
@@ -318,6 +318,7 @@ class node_res_dir(node_class):
self.namefield = dirr.resource_field or 'name'
self.displayname = dirr.name
# Important: the domain is evaluated using the *parent* dctx!
+ self.dctx.update({'active_id': self.context.context['dir_id']})
self.domain = safe_eval(dirr.domain,self.dctx)
# and then, we add our own vars in the dctx:
if dctx:
@@ -353,14 +354,13 @@ class node_res_dir(node_class):
ctx.update(self.dctx)
where = []
if self.domain:
- where.append(self.domain)
+ where += self.domain
if self.resm_id:
where.append(('id','=',self.resm_id))
if name:
where.append((self.namefield,'=',name))
- # print "Where clause for %s" % self.res_model, where
-
+ # print "Where clause for %s" % self.res_model, where
resids = obj.search(cr,uid, where, context=ctx)
res = []
for bo in obj.browse(cr,uid,resids,context=ctx):
@@ -396,8 +396,7 @@ class node_res_obj(node_class):
self.content_length = 0
self.res_model = res_model
self.domain = parent.domain
- self.displayname = path
-
+ self.displayname = path
if res_bo:
self.res_id = res_bo.id
dc2 = self.context.context
@@ -415,10 +414,10 @@ class node_res_obj(node_class):
else:
self.res_id = res_id
- def children(self,cr):
+ def children(self,cr):
return self._child_get(cr) + self._file_get(cr)
- def child(self,cr, name):
+ def child(self,cr, name):
res = self._child_get(cr,name)
if res:
return res[0]
@@ -481,7 +480,7 @@ class node_res_obj(node_class):
ctx.update(self.dctx)
where = [('parent_id','=',self.dir_id) ]
if name:
- where.append(('name','=',name))
+ where.append(('name','=',name))
ids = dirobj.search(cr, uid, where,context=ctx)
res = []
if ids:
From 8777bb210a334f27f64e380131d7f050f2a35f8c Mon Sep 17 00:00:00 2001
From: "Harry (Open ERP)"
Date: Wed, 9 Dec 2009 17:10:28 +0530
Subject: [PATCH 05/19] [FIX] document :directory tree structure is work fine
now
bzr revid: hmo@tinyerp.com-20091209114028-wulu72g9qfd9e2rj
---
addons/document/ftpserver/abstracted_fs.py | 2 +-
addons/document/nodes.py | 54 ++++++++++++++++------
2 files changed, 42 insertions(+), 14 deletions(-)
diff --git a/addons/document/ftpserver/abstracted_fs.py b/addons/document/ftpserver/abstracted_fs.py
index 11fd56f976e..8fcbac6734c 100644
--- a/addons/document/ftpserver/abstracted_fs.py
+++ b/addons/document/ftpserver/abstracted_fs.py
@@ -451,7 +451,7 @@ class abstracted_fs:
except osv.except_osv:
pass
return result
- cr = pooler.get_db(path.context.dbname).cursor()
+ cr = pooler.get_db(path.context.dbname).cursor()
return path.children(cr)
# Ok
diff --git a/addons/document/nodes.py b/addons/document/nodes.py
index 42a0e21f2f7..fb377fed03c 100644
--- a/addons/document/nodes.py
+++ b/addons/document/nodes.py
@@ -64,7 +64,7 @@ class node_context(object):
(ndir, duri) = self._dirobj._locate_child(cr,self.uid, self.rootdir,uri, None, self)
- while duri:
+ while duri:
ndir = ndir.child(cr, duri[0])
if not ndir:
return False
@@ -320,6 +320,7 @@ class node_res_dir(node_class):
# Important: the domain is evaluated using the *parent* dctx!
self.dctx.update({'active_id': self.context.context['dir_id']})
self.domain = safe_eval(dirr.domain,self.dctx)
+ self.ressource_tree = dirr.ressource_tree
# and then, we add our own vars in the dctx:
if dctx:
self.dctx.update(dctx)
@@ -349,6 +350,7 @@ class node_res_dir(node_class):
if not obj:
print "couldn't find model", self.res_model
return []
+ dirobj = self.context._dirobj
uid = self.context.uid
ctx = self.context.context.copy()
ctx.update(self.dctx)
@@ -360,16 +362,24 @@ class node_res_dir(node_class):
if name:
where.append((self.namefield,'=',name))
- # print "Where clause for %s" % self.res_model, where
+ # print "Where clause for %s" % self.res_model, where
+ if self.ressource_tree:
+ object2 = False
+ if self.resm_id:
+ object2 = dirobj.pool.get(self.res_model).browse(cr, uid, self.resm_id) or False
+ if obj._parent_name in obj.fields_get(cr, uid):
+ where.append((obj._parent_name,'=',object2 and object2.id or False))
+
resids = obj.search(cr,uid, where, context=ctx)
- res = []
+ res = []
for bo in obj.browse(cr,uid,resids,context=ctx):
if not bo:
continue
name = getattr(bo,self.namefield)
if not name:
continue
- # Yes! we can't do better but skip nameless records.
+ # Yes! we can't do better but skip nameless records.
+
res.append(node_res_obj(name,self,self.context,self.res_model, bo))
return res
@@ -386,7 +396,7 @@ class node_res_obj(node_class):
def __init__(self,path, parent, context, res_model, res_bo, res_id = None):
super(node_res_obj,self).__init__(path, parent,context)
assert parent
- #todo: more info from dirr
+ #todo: more info from dirr
self.dir_id = parent.dir_id
self.mimetype = 'application/x-directory'
# 'httpd/unix-directory'
@@ -396,15 +406,16 @@ class node_res_obj(node_class):
self.content_length = 0
self.res_model = res_model
self.domain = parent.domain
- self.displayname = path
- if res_bo:
- self.res_id = res_bo.id
+ self.displayname = path
+ self.dctx_dict = parent.dctx_dict
+ if res_bo:
+ self.res_id = res_bo.id
dc2 = self.context.context
dc2.update(self.dctx)
dc2['res_model'] = res_model
dc2['res_id'] = res_bo.id
- dc2['this'] = res_bo
- for fld,expr in parent.dctx_dict.items():
+ dc2['this'] = res_bo
+ for fld,expr in self.dctx_dict.items():
try:
self.dctx[fld] = safe_eval(expr,dc2)
except Exception,e:
@@ -478,10 +489,27 @@ class node_res_obj(node_class):
uid = self.context.uid
ctx = self.context.context.copy()
ctx.update(self.dctx)
- where = [('parent_id','=',self.dir_id) ]
+ directory = dirobj.browse(cr, uid, self.dir_id)
+ obj = dirobj.pool.get(self.res_model)
+ where = []
if name:
- where.append(('name','=',name))
- ids = dirobj.search(cr, uid, where,context=ctx)
+ where.append(('name','=',name))
+ if self.res_id and directory.ressource_tree:
+ if obj._parent_name in obj.fields_get(cr, uid):
+ where.append((obj._parent_name, '=', self.res_id))
+ resids = obj.search(cr,uid, where, context=ctx)
+ res = []
+ for bo in obj.browse(cr,uid,resids,context=ctx):
+ namefield = directory.resource_field or 'name'
+ if not bo:
+ continue
+ name = getattr(bo, namefield)
+ if not name:
+ continue
+ res.append(node_res_obj(name, self, self.context, self.res_model, res_bo = bo))
+ return res
+ where = [('parent_id','=',self.dir_id) ]
+ ids = dirobj.search(cr, uid, where,context=ctx)
res = []
if ids:
for dirr in dirobj.browse(cr,uid,ids,context=ctx):
From 21b45c94bca3e962c668bf7fd5574cdf695e20ea Mon Sep 17 00:00:00 2001
From: "Harry (Open ERP)"
Date: Wed, 9 Dec 2009 18:48:51 +0530
Subject: [PATCH 06/19] [FIX] document : fix problem to get files of res obj
node
bzr revid: hmo@tinyerp.com-20091209131851-07u1bp2gqwxv1ady
---
addons/document/nodes.py | 15 +++++++--------
1 file changed, 7 insertions(+), 8 deletions(-)
diff --git a/addons/document/nodes.py b/addons/document/nodes.py
index fb377fed03c..ac15b0a359c 100644
--- a/addons/document/nodes.py
+++ b/addons/document/nodes.py
@@ -62,13 +62,12 @@ class node_context(object):
""" Although this fn passes back to doc.dir, it is needed since
it is a potential caching point """
- (ndir, duri) = self._dirobj._locate_child(cr,self.uid, self.rootdir,uri, None, self)
-
+ (ndir, duri) = self._dirobj._locate_child(cr,self.uid, self.rootdir,uri, None, self)
while duri:
ndir = ndir.child(cr, duri[0])
if not ndir:
- return False
- duri = duri[1:]
+ return False
+ duri = duri[1:]
return ndir
@@ -506,11 +505,11 @@ class node_res_obj(node_class):
name = getattr(bo, namefield)
if not name:
continue
- res.append(node_res_obj(name, self, self.context, self.res_model, res_bo = bo))
- return res
+ res.append(node_res_obj(name, self, self.context, self.res_model, res_bo = bo))
+ if name:
+ where.append(('name','=',name))
where = [('parent_id','=',self.dir_id) ]
- ids = dirobj.search(cr, uid, where,context=ctx)
- res = []
+ ids = dirobj.search(cr, uid, where,context=ctx)
if ids:
for dirr in dirobj.browse(cr,uid,ids,context=ctx):
if dirr.type == 'directory':
From ee05dd4cf652d34f0f9084b890044b30c188b52e Mon Sep 17 00:00:00 2001
From: "Harry (Open ERP)"
Date: Thu, 10 Dec 2009 12:00:00 +0530
Subject: [PATCH 07/19] [FIX] document: close cursor if not need
bzr revid: hmo@tinyerp.com-20091210063000-pu3q9dhpgnurw3eo
---
addons/document/ftpserver/abstracted_fs.py | 41 ++++++++++++++--------
1 file changed, 26 insertions(+), 15 deletions(-)
diff --git a/addons/document/ftpserver/abstracted_fs.py b/addons/document/ftpserver/abstracted_fs.py
index 8fcbac6734c..017ef584d51 100644
--- a/addons/document/ftpserver/abstracted_fs.py
+++ b/addons/document/ftpserver/abstracted_fs.py
@@ -205,7 +205,7 @@ class abstracted_fs:
def fs2ftp(self, node):
res='/'
if node:
- res=os.path.normpath(''.join(node.full_path()))
+ res = os.path.normpath(''.join(node.full_path()))
res = res.replace("\\", "/")
while res[:2] == '//':
res = res[1:]
@@ -231,18 +231,19 @@ class abstracted_fs:
# Ok
def create(self, node, objname, mode):
- objname = _to_unicode(objname)
- uid = node.context.uid
- pool = pooler.get_pool(node.context.dbname)
- cr = pooler.get_db(node.context.dbname).cursor()
- child = node.child(cr, objname)
- if child:
- if child.type in ('collection','database'):
- raise OSError(1, 'Operation not permited.')
- if child.type == 'content':
- s = content_wrapper(node.context.dbname, uid, pool, child)
- return s
+ objname = _to_unicode(objname)
+ cr = None
try:
+ uid = node.context.uid
+ pool = pooler.get_pool(node.context.dbname)
+ cr = pooler.get_db(node.context.dbname).cursor()
+ child = node.child(cr, objname)
+ if child:
+ if child.type in ('collection','database'):
+ raise OSError(1, 'Operation not permited.')
+ if child.type == 'content':
+ s = content_wrapper(node.context.dbname, uid, pool, child)
+ return s
fobj = pool.get('ir.attachment')
ext = objname.find('.') >0 and objname.split('.')[1] or False
@@ -298,6 +299,9 @@ class abstracted_fs:
except Exception,e:
log(e)
raise OSError(1, 'Operation not permited.')
+ finally:
+ if cr:
+ cr.close()
# Ok
def open(self, node, mode):
@@ -315,13 +319,15 @@ class abstracted_fs:
else:
s = StringIO.StringIO(base64.decodestring(fobj.db_datas or ''))
s.name = node
+ cr.close()
return s
elif node.type=='content':
-# cr = node.cr
uid = node.context.uid
cr = pooler.get_db(node.context.dbname).cursor()
pool = pooler.get_pool(node.context.dbname)
- return getattr(pool.get('document.directory.content'), 'process_read')(cr, uid, node)
+ res = getattr(pool.get('document.directory.content'), 'process_read')(cr, uid, node)
+ cr.close()
+ return res
else:
raise OSError(1, 'Operation not permited.')
@@ -371,6 +377,9 @@ class abstracted_fs:
return None
if path.type in ('collection','database'):
self.cwd = self.fs2ftp(path)
+ elif path.type in ('file'):
+ parent_path = path.full_path()[:-1]
+ self.cwd = os.path.normpath(''.join(parent_path))
else:
raise OSError(1, 'Operation not permited.')
@@ -452,7 +461,9 @@ class abstracted_fs:
pass
return result
cr = pooler.get_db(path.context.dbname).cursor()
- return path.children(cr)
+ res = path.children(cr)
+ cr.close()
+ return res
# Ok
def rmdir(self, node):
From 51d35e7dd61b9491b32bd397db548d014d21322f Mon Sep 17 00:00:00 2001
From: "Harry (Open ERP)"
Date: Thu, 10 Dec 2009 12:02:55 +0530
Subject: [PATCH 08/19] [FIX] document : fixed small problem
bzr revid: hmo@tinyerp.com-20091210063255-492nlwzb80lwb3tc
---
addons/document/nodes.py | 6 +++---
1 file changed, 3 insertions(+), 3 deletions(-)
diff --git a/addons/document/nodes.py b/addons/document/nodes.py
index ac15b0a359c..4458e00ad4d 100644
--- a/addons/document/nodes.py
+++ b/addons/document/nodes.py
@@ -491,13 +491,13 @@ class node_res_obj(node_class):
directory = dirobj.browse(cr, uid, self.dir_id)
obj = dirobj.pool.get(self.res_model)
where = []
+ res = []
if name:
- where.append(('name','=',name))
+ where.append(('name','=',name))
if self.res_id and directory.ressource_tree:
if obj._parent_name in obj.fields_get(cr, uid):
where.append((obj._parent_name, '=', self.res_id))
- resids = obj.search(cr,uid, where, context=ctx)
- res = []
+ resids = obj.search(cr,uid, where, context=ctx)
for bo in obj.browse(cr,uid,resids,context=ctx):
namefield = directory.resource_field or 'name'
if not bo:
From 4a6d0a4a5112de7c89e8fe468f5ea0470ab01acf Mon Sep 17 00:00:00 2001
From: "Harry (Open ERP)"
Date: Thu, 10 Dec 2009 14:33:05 +0530
Subject: [PATCH 09/19] [FIX] document : child resources display now
bzr revid: hmo@tinyerp.com-20091210090305-nqg64404mn367nz5
---
addons/document/nodes.py | 40 +++++++++++++++++++++++++---------------
1 file changed, 25 insertions(+), 15 deletions(-)
diff --git a/addons/document/nodes.py b/addons/document/nodes.py
index 4458e00ad4d..9bc3de05bfa 100644
--- a/addons/document/nodes.py
+++ b/addons/document/nodes.py
@@ -251,8 +251,9 @@ class node_dir(node_class):
ctx.update(self.dctx)
where = [('parent_id','=',self.dir_id) ]
if name:
- where.append(('name','=',name))
- ids = dirobj.search(cr, uid, where,context=ctx)
+ where.append(('name','=',name))
+
+ ids = dirobj.search(cr, uid, where + [('ressource_parent_type_id','=',False)],context=ctx)
res = []
if ids:
for dirr in dirobj.browse(cr,uid,ids,context=ctx):
@@ -316,9 +317,8 @@ class node_res_dir(node_class):
self.resm_id = dirr.ressource_id
self.namefield = dirr.resource_field or 'name'
self.displayname = dirr.name
- # Important: the domain is evaluated using the *parent* dctx!
- self.dctx.update({'active_id': self.context.context['dir_id']})
- self.domain = safe_eval(dirr.domain,self.dctx)
+ # Important: the domain is evaluated using the *parent* dctx!
+ self.domain = dirr.domain
self.ressource_tree = dirr.ressource_tree
# and then, we add our own vars in the dctx:
if dctx:
@@ -355,12 +355,13 @@ class node_res_dir(node_class):
ctx.update(self.dctx)
where = []
if self.domain:
- where += self.domain
+ where += safe_eval(self.domain, self.dctx)
if self.resm_id:
where.append(('id','=',self.resm_id))
if name:
where.append((self.namefield,'=',name))
+
# print "Where clause for %s" % self.res_model, where
if self.ressource_tree:
object2 = False
@@ -491,13 +492,16 @@ class node_res_obj(node_class):
directory = dirobj.browse(cr, uid, self.dir_id)
obj = dirobj.pool.get(self.res_model)
where = []
- res = []
+ res = []
if name:
- where.append(('name','=',name))
- if self.res_id and directory.ressource_tree:
+ where.append(('name','=',name))
+
+ # Directory Structure display in tree structure
+ if self.res_id and directory.ressource_tree:
+ where1 = where
if obj._parent_name in obj.fields_get(cr, uid):
- where.append((obj._parent_name, '=', self.res_id))
- resids = obj.search(cr,uid, where, context=ctx)
+ where1 += [(obj._parent_name, '=', self.res_id)]
+ resids = obj.search(cr,uid, where1, context=ctx)
for bo in obj.browse(cr,uid,resids,context=ctx):
namefield = directory.resource_field or 'name'
if not bo:
@@ -505,9 +509,15 @@ class node_res_obj(node_class):
name = getattr(bo, namefield)
if not name:
continue
- res.append(node_res_obj(name, self, self.context, self.res_model, res_bo = bo))
- if name:
- where.append(('name','=',name))
+ res.append(node_res_obj(name, self, self.context, self.res_model, res_bo = bo))
+
+ # Get Child Ressource Directories
+ where12 = [('ressource_parent_type_id','=',directory.ressource_type_id.id)]
+ dirids = dirobj.search(cr,uid, where12)
+ for dirr in dirobj.browse(cr, uid, dirids, context=ctx):
+ if dirr.type == 'ressource':
+ res.append(node_res_dir(dirr.name, self, self.context, dirr, {'active_id': self.res_id}))
+
where = [('parent_id','=',self.dir_id) ]
ids = dirobj.search(cr, uid, where,context=ctx)
if ids:
@@ -516,7 +526,7 @@ class node_res_obj(node_class):
res.append(node_res_obj(dirr.name,self,self.context,self.res_model,res_bo = None, res_id = self.res_id))
elif dirr.type == 'ressource':
# child resources can be controlled by properly set dctx
- res.append(node_res_dir(dirr.name,self,self.context,dirr))
+ res.append(node_res_dir(dirr.name,self,self.context, dirr, {'active_id': self.res_id}))
fil_obj=dirobj.pool.get('ir.attachment')
where2 = where + [('res_model', '=', self.res_model), ('res_id','=',self.res_id)]
From 7c11b0e8fed2ff9ac716557eb352f661faa4fa09 Mon Sep 17 00:00:00 2001
From: "Harry (Open ERP)"
Date: Tue, 15 Dec 2009 16:18:10 +0530
Subject: [PATCH 10/19] [FIX+IMP] document: fixed problem on rename file and
directory of sub-directory of ressource directory
bzr revid: hmo@tinyerp.com-20091215104810-vi711k1ny28a55uc
---
addons/document/document_directory.py | 1 +
addons/document/ftpserver/abstracted_fs.py | 62 +++++++--------
addons/document/nodes.py | 90 ++++++++++++----------
3 files changed, 81 insertions(+), 72 deletions(-)
diff --git a/addons/document/document_directory.py b/addons/document/document_directory.py
index 1f95fd6a61a..d4232caadb2 100644
--- a/addons/document/document_directory.py
+++ b/addons/document/document_directory.py
@@ -27,6 +27,7 @@ import urlparse
import os
import nodes
+from tools.translate import _
class document_directory(osv.osv):
_name = 'document.directory'
diff --git a/addons/document/ftpserver/abstracted_fs.py b/addons/document/ftpserver/abstracted_fs.py
index 017ef584d51..0073b26b9f2 100644
--- a/addons/document/ftpserver/abstracted_fs.py
+++ b/addons/document/ftpserver/abstracted_fs.py
@@ -189,13 +189,13 @@ class abstracted_fs:
# Ok
def ftp2fs(self, path_orig, data):
- path = self.ftpnorm(path_orig)
+ path = self.ftpnorm(path_orig)
if not data or (path and path=='/'):
return None
path2 = filter(None,path.split('/'))[1:]
(cr, uid, pool) = data
if len(path2):
- path2[-1]=_to_unicode(path2[-1])
+ path2[-1]=_to_unicode(path2[-1])
res = pool.get('document.directory').get_object(cr, uid, path2[:])
if not res:
raise OSError(2, 'Not such file or directory.')
@@ -209,7 +209,7 @@ class abstracted_fs:
res = res.replace("\\", "/")
while res[:2] == '//':
res = res[1:]
- res='/' + node.context.dbname + '/' + _to_decode(res)
+ res = '/' + node.context.dbname + '/' + _to_decode(res)
#res = node and ('/' + node.cr.dbname + '/' + _to_decode(self.ftpnorm(node.path))) or '/'
return res
@@ -403,11 +403,11 @@ class abstracted_fs:
val = {
'name': basename,
'ressource_parent_type_id': obj and obj.ressource_type_id.id or False,
- 'ressource_id': object2 and object2.id or False
+ 'ressource_id': object2 and object2.id or False,
+ 'parent_id' : False
}
- #if (obj and (obj.type in ('directory'))) or not object2:
- # val['parent_id'] = obj and obj.id or False
- val['parent_id'] = node.dir_id
+ if (obj and (obj.type in ('directory'))) or not object2:
+ val['parent_id'] = obj and obj.id or False
# Check if it alreayd exists !
pool.get('document.directory').create(cr, uid, val)
cr.commit()
@@ -523,8 +523,7 @@ class abstracted_fs:
try:
dst_basename = _to_unicode(dst_basename)
cr = pooler.get_db(src.context.dbname).cursor()
- uid = src.context.uid
-
+ uid = src.context.uid
if src.type == 'collection':
obj2 = False
dst_obj2 = False
@@ -534,40 +533,39 @@ class abstracted_fs:
obj = src.context._dirobj.browse(cr, uid, src.dir_id)
if isinstance(dst_basedir, node_res_obj):
dst_obj2 = dst_basedir and pool.get(dst_basedir.context.context['res_model']).browse(cr, uid, dst_basedir.context.context['res_id']) or False
- dst_obj = dst_basedir.context._dirobj.browse(cr, uid, dst_basedir.dir_id)
+ dst_obj = dst_basedir.context._dirobj.browse(cr, uid, dst_basedir.dir_id)
if obj._table_name <> 'document.directory':
raise OSError(1, 'Operation not permited.')
result = {
'directory': [],
'attachment': []
}
- # Compute all childs to set the new ressource ID
+ # Compute all childs to set the new ressource ID
child_ids = [src]
while len(child_ids):
node = child_ids.pop(0)
- child_ids += node.children(cr)
- object2 = node and hasattr(node, "res_model") and node.res_model or False
- object = node.context._dirobj.browse(cr, uid, node.dir_id)
+ child_ids += node.children(cr)
if node.type == 'collection':
+ object2 = False
+ if isinstance(node, node_res_obj):
+ object2 = node and pool.get(node.context.context['res_model']).browse(cr, uid, node.context.context['res_id']) or False
+ object = node.context._dirobj.browse(cr, uid, node.dir_id)
result['directory'].append(object.id)
if (not object.ressource_id) and object2:
raise OSError(1, 'Operation not permited.')
elif node.type == 'file':
result['attachment'].append(object.id)
-
if obj2 and not obj.ressource_id:
raise OSError(1, 'Operation not permited.')
- val = {
- 'name':dst_basename,
- }
+
if (dst_obj and (dst_obj.type in ('directory'))) or not dst_obj2:
- val['parent_id'] = dst_obj and dst_obj.id or False
+ parent_id = dst_obj and dst_obj.id or False
else:
- val['parent_id'] = False
- res = pool.get('document.directory').write(cr, uid, [obj.id], val)
-
- if dst_obj2:
+ parent_id = False
+
+
+ if dst_obj2:
ressource_type_id = pool.get('ir.model').search(cr, uid, [('model','=',dst_obj2._name)])[0]
ressource_id = dst_obj2.id
title = dst_obj2.name
@@ -575,17 +573,18 @@ class abstracted_fs:
if dst_obj2._name == 'res.partner':
partner_id = dst_obj2.id
else:
- partner_id= pool.get(dst_obj2._name).fields_get(cr, uid, ['partner_id']) and dst_obj2.partner_id.id or False
+ partner_id = pool.get(dst_obj2._name).fields_get(cr, uid, ['partner_id']) and dst_obj2.partner_id.id or False
else:
ressource_type_id = False
ressource_id = False
ressource_model = False
partner_id = False
- title = False
-
+ title = False
pool.get('document.directory').write(cr, uid, result['directory'], {
+ 'name' : dst_basename,
'ressource_id': ressource_id,
- 'ressource_type_id': ressource_type_id
+ 'ressource_parent_type_id': ressource_type_id,
+ 'parent_id' : parent_id
})
val = {
'res_id': ressource_id,
@@ -598,12 +597,13 @@ class abstracted_fs:
cr.execute('update ir_attachment set res_id=NULL where id in ('+','.join(map(str,result['attachment']))+')')
cr.commit()
+
elif src.type == 'file':
pool = pooler.get_pool(src.context.dbname)
obj = pool.get('ir.attachment').browse(cr, uid, src.file_id)
- dst_obj2 = False
- if isinstance(dst_basedir, node_res_dir):
- dst_obj2 = dst_basedir and dst_basedir.res_model or False
+ dst_obj2 = False
+ if isinstance(dst_basedir, node_res_obj):
+ dst_obj2 = dst_basedir and pool.get(dst_basedir.context.context['res_model']).browse(cr, uid, dst_basedir.context.context['res_id']) or False
dst_obj = dst_basedir.context._dirobj.browse(cr, uid, dst_basedir.dir_id)
val = {
@@ -627,7 +627,7 @@ class abstracted_fs:
if dst_obj2._name == 'res.partner':
val['partner_id'] = dst_obj2.id
else:
- val['partner_id'] = pool.get(dst_obj2._name).fields_get(src.cr, src.uid, ['partner_id']) and dst_obj2.partner_id.id or False
+ val['partner_id'] = pool.get(dst_obj2._name).fields_get(cr, uid, ['partner_id']) and dst_obj2.partner_id.id or False
elif obj.res_id:
# I had to do that because writing False to an integer writes 0 instead of NULL
# change if one day we decide to improve osv/fields.py
diff --git a/addons/document/nodes.py b/addons/document/nodes.py
index 9bc3de05bfa..e6a614bf6aa 100644
--- a/addons/document/nodes.py
+++ b/addons/document/nodes.py
@@ -60,14 +60,13 @@ class node_context(object):
def get_uri(self, cr, uri):
""" Although this fn passes back to doc.dir, it is needed since
- it is a potential caching point """
-
- (ndir, duri) = self._dirobj._locate_child(cr,self.uid, self.rootdir,uri, None, self)
- while duri:
- ndir = ndir.child(cr, duri[0])
+ it is a potential caching point """
+ (ndir, duri) = self._dirobj._locate_child(cr,self.uid, self.rootdir,uri, None, self)
+ while duri:
+ ndir = ndir.child(cr, duri[0])
if not ndir:
return False
- duri = duri[1:]
+ duri = duri[1:]
return ndir
@@ -190,7 +189,7 @@ class node_dir(node_class):
def children(self,cr):
return self._child_get(cr) + self._file_get(cr)
- def child(self,cr, name):
+ def child(self,cr, name):
res = self._child_get(cr,name)
if res:
return res[0]
@@ -332,7 +331,7 @@ class node_res_dir(node_class):
def children(self,cr):
return self._child_get(cr)
- def child(self,cr, name):
+ def child(self,cr, name):
res = self._child_get(cr,name)
if res:
return res[0]
@@ -380,7 +379,7 @@ class node_res_dir(node_class):
continue
# Yes! we can't do better but skip nameless records.
- res.append(node_res_obj(name,self,self.context,self.res_model, bo))
+ res.append(node_res_obj(name, self.dir_id, self, self.context, self.res_model, bo))
return res
def _get_ttag(self,cr):
@@ -393,11 +392,11 @@ class node_res_obj(node_class):
node_dirs (with limited domain).
"""
our_type = 'collection'
- def __init__(self,path, parent, context, res_model, res_bo, res_id = None):
+ def __init__(self, path, dir_id, parent, context, res_model, res_bo, res_id = None):
super(node_res_obj,self).__init__(path, parent,context)
assert parent
- #todo: more info from dirr
- self.dir_id = parent.dir_id
+ #todo: more info from dirr
+ self.dir_id = dir_id
self.mimetype = 'application/x-directory'
# 'httpd/unix-directory'
self.create_date = parent.create_date
@@ -428,8 +427,8 @@ class node_res_obj(node_class):
def children(self,cr):
return self._child_get(cr) + self._file_get(cr)
- def child(self,cr, name):
- res = self._child_get(cr,name)
+ def child(self,cr, name):
+ res = self._child_get(cr,name)
if res:
return res[0]
res = self._file_get(cr,name)
@@ -484,7 +483,7 @@ class node_res_obj(node_class):
return ('vevent-collection','http://groupdav.org/')
return None
- def _child_get(self,cr,name = None):
+ def _child_get(self,cr,name = None):
dirobj = self.context._dirobj
uid = self.context.uid
ctx = self.context.context.copy()
@@ -498,44 +497,53 @@ class node_res_obj(node_class):
# Directory Structure display in tree structure
if self.res_id and directory.ressource_tree:
- where1 = where
+ where1 = []
if obj._parent_name in obj.fields_get(cr, uid):
- where1 += [(obj._parent_name, '=', self.res_id)]
+ where1 = where + [(obj._parent_name, '=', self.res_id)]
resids = obj.search(cr,uid, where1, context=ctx)
for bo in obj.browse(cr,uid,resids,context=ctx):
namefield = directory.resource_field or 'name'
if not bo:
continue
- name = getattr(bo, namefield)
- if not name:
+ res_name = getattr(bo, namefield)
+ if not res_name:
continue
- res.append(node_res_obj(name, self, self.context, self.res_model, res_bo = bo))
+ res.append(node_res_obj(res_name, self.dir_id, self, self.context, self.res_model, res_bo = bo))
+
+
+ where2 = where + [('parent_id','=',self.dir_id) ]
+ ids = dirobj.search(cr, uid, where2, context=ctx)
+ for dirr in dirobj.browse(cr, uid, ids, context=ctx):
+ if dirr.type == 'directory':
+ res.append(node_res_obj(dirr.name, dirr.id, self, self.context, self.res_model, res_bo = None, res_id = self.res_id))
+ elif dirr.type == 'ressource':
+ # child resources can be controlled by properly set dctx
+ res.append(node_res_dir(dirr.name,self,self.context, dirr, {'active_id': self.res_id}))
+
- # Get Child Ressource Directories
- where12 = [('ressource_parent_type_id','=',directory.ressource_type_id.id)]
- dirids = dirobj.search(cr,uid, where12)
- for dirr in dirobj.browse(cr, uid, dirids, context=ctx):
- if dirr.type == 'ressource':
- res.append(node_res_dir(dirr.name, self, self.context, dirr, {'active_id': self.res_id}))
+
- where = [('parent_id','=',self.dir_id) ]
- ids = dirobj.search(cr, uid, where,context=ctx)
- if ids:
- for dirr in dirobj.browse(cr,uid,ids,context=ctx):
- if dirr.type == 'directory':
- res.append(node_res_obj(dirr.name,self,self.context,self.res_model,res_bo = None, res_id = self.res_id))
- elif dirr.type == 'ressource':
- # child resources can be controlled by properly set dctx
- res.append(node_res_dir(dirr.name,self,self.context, dirr, {'active_id': self.res_id}))
-
- fil_obj=dirobj.pool.get('ir.attachment')
- where2 = where + [('res_model', '=', self.res_model), ('res_id','=',self.res_id)]
+ fil_obj = dirobj.pool.get('ir.attachment')
+ where3 = where2 + [('res_model', '=', self.res_model), ('res_id','=',self.res_id)]
# print "where clause for dir_obj", where2
- ids = fil_obj.search(cr,uid,where2,context=ctx)
+ ids = fil_obj.search(cr, uid, where3, context=ctx)
if ids:
- for fil in fil_obj.browse(cr,uid,ids,context=ctx):
- res.append(node_file(fil.name,self,self.context,fil))
+ for fil in fil_obj.browse(cr, uid, ids, context=ctx):
+ res.append(node_file(fil.name, self, self.context, fil))
+
+ # Get Child Ressource Directories
+ if directory.ressource_type_id and directory.ressource_type_id.id:
+ where4 = where + [('ressource_parent_type_id','=',directory.ressource_type_id.id)]
+ where5 = where4 + [('ressource_id','=',0)]
+ dirids = dirobj.search(cr,uid, where5)
+ where5 = where4 + [('ressource_id','=',self.res_id)]
+ dirids = dirids + dirobj.search(cr,uid, where5)
+ for dirr in dirobj.browse(cr, uid, dirids, context=ctx):
+ if dirr.type == 'directory' and not dirr.parent_id:
+ res.append(node_res_obj(dirr.name, dirr.id, self, self.context, self.res_model, res_bo = None, res_id = self.res_id))
+ if dirr.type == 'ressource':
+ res.append(node_res_dir(dirr.name, self, self.context, dirr, {'active_id': self.res_id}))
return res
def create_child(self,cr,path,data):
From 8507374543ed88c0a170459fe8cc3ab763dea8ff Mon Sep 17 00:00:00 2001
From: "Harry (Open ERP)"
Date: Tue, 15 Dec 2009 16:42:09 +0530
Subject: [PATCH 11/19] [FIX] document_ics: Replace with four
bzr revid: hmo@tinyerp.com-20091215111209-m2djy0ku9oe6nsk8
---
addons/document_ics/document_ics.py | 82 ++++++++++++++---------------
1 file changed, 41 insertions(+), 41 deletions(-)
diff --git a/addons/document_ics/document_ics.py b/addons/document_ics/document_ics.py
index 61bddb2c306..caccf25e3ce 100644
--- a/addons/document_ics/document_ics.py
+++ b/addons/document_ics/document_ics.py
@@ -30,7 +30,7 @@ import random
import tools
import re
-from document import nodes
+from document.nodes import node_content
from tools.safe_eval import safe_eval
@@ -86,42 +86,42 @@ class document_directory_content(osv.osv):
'ics_domain': lambda *args: '[]'
}
def _file_get(self, cr, node, nodename, content, context=None):
- if not content.obj_iterate:
- return super(document_directory_content, self)._file_get(cr,node,nodename,content)
- else:
- # print "iterate over ", content.object_id.model
- mod = self.pool.get(content.object_id.model)
- uid = node.context.uid
- fname_fld = content.fname_field or 'id'
- where = []
- if node.domain:
- where.append(node.domain)
- if nodename:
- # Reverse-parse the nodename to deduce the clause:
- prefix = (content.prefix or '')
- suffix = (content.suffix or '') + (content.extension or '')
- if not nodename.startswith(prefix):
- return False
- if not nodename.endswith(suffix):
- return False
- tval = nodename[len(prefix):0 - len(suffix)]
- where.append((fname_fld,'=',tval))
- # print "ics iterate clause:", where
- resids = mod.search(cr,uid,where,context=context)
- if not resids:
- return False
-
- res2 = []
- for ro in mod.read(cr,uid,resids,['id', fname_fld]):
- tname = (content.prefix or '') + str(ro[fname_fld])
- tname += (content.suffix or '') + (content.extension or '')
- dctx2 = { 'active_id': ro['id'] }
- if fname_fld:
- dctx2['active_'+fname_fld] = ro[fname_fld]
- n = nodes.node_content(tname, node, node.context,content,dctx=dctx2, act_id = ro['id'])
- n.fill_fields(cr, dctx2)
- res2.append(n)
- return res2
+ if not content.obj_iterate:
+ return super(document_directory_content, self)._file_get(cr,node,nodename,content)
+ else:
+ # print "iterate over ", content.object_id.model
+ mod = self.pool.get(content.object_id.model)
+ uid = node.context.uid
+ fname_fld = content.fname_field or 'id'
+ where = []
+ if node.domain:
+ where.append(node.domain)
+ if nodename:
+ # Reverse-parse the nodename to deduce the clause:
+ prefix = (content.prefix or '')
+ suffix = (content.suffix or '') + (content.extension or '')
+ if not nodename.startswith(prefix):
+ return False
+ if not nodename.endswith(suffix):
+ return False
+ tval = nodename[len(prefix):0 - len(suffix)]
+ where.append((fname_fld,'=',tval))
+ # print "ics iterate clause:", where
+ resids = mod.search(cr,uid,where,context=context)
+ if not resids:
+ return False
+
+ res2 = []
+ for ro in mod.read(cr,uid,resids,['id', fname_fld]):
+ tname = (content.prefix or '') + str(ro[fname_fld])
+ tname += (content.suffix or '') + (content.extension or '')
+ dctx2 = { 'active_id': ro['id'] }
+ if fname_fld:
+ dctx2['active_'+fname_fld] = ro[fname_fld]
+ n = node_content(tname, node, node.context,content,dctx=dctx2, act_id = ro['id'])
+ n.fill_fields(cr, dctx2)
+ res2.append(n)
+ return res2
def process_write(self, cr, uid, node, data, context=None):
if node.extension != '.ics':
@@ -148,8 +148,8 @@ class document_directory_content(osv.osv):
fexprs[n.name] = n.expr
if 'uid' not in fields:
- print "uid not in ", fields
- # FIXME: should pass
+ print "uid not in ", fields
+ # FIXME: should pass
return True
for child in parsedCal.getChildren():
result = {}
@@ -188,11 +188,11 @@ class document_directory_content(osv.osv):
if not uuid:
print "Skipping cal", child
- # FIXME: should pass
+ # FIXME: should pass
continue
cmodel = content.object_id.model
- wexpr = False
+ wexpr = False
if fields['uid']:
wexpr = [(fields['uid'], '=', uuid.encode('utf8'))]
else:
From 67dbab9d33991bf6c1e0aa8f413657c39c266986 Mon Sep 17 00:00:00 2001
From: "Harry (Open ERP)"
Date: Tue, 15 Dec 2009 19:32:50 +0530
Subject: [PATCH 12/19] [IMP+FIX] document_ics: fixed regression on open, read
ics files
bzr revid: hmo@tinyerp.com-20091215140250-aw7z7jsd90fxy85b
---
addons/document/directory_content.py | 2 +-
addons/document/ftpserver/abstracted_fs.py | 12 +-
addons/document/ftpserver/ftpserver.py | 2 +-
addons/document/nodes.py | 12 +-
addons/document_ics/document.py | 191 ---------------------
addons/document_ics/document_ics.py | 17 +-
6 files changed, 26 insertions(+), 210 deletions(-)
delete mode 100644 addons/document_ics/document.py
diff --git a/addons/document/directory_content.py b/addons/document/directory_content.py
index a43b5730627..b82da1bce8a 100644
--- a/addons/document/directory_content.py
+++ b/addons/document/directory_content.py
@@ -67,7 +67,7 @@ class document_directory_content(osv.osv):
'include_name': lambda *args: 1,
}
- def _file_get(self,cr,node, nodename, content, context=None):
+ def _file_get(self, cr, node, nodename, content, context=None):
""" return the nodes of a parent having a content
The return value MUST be false or a list of node_class objects.
"""
diff --git a/addons/document/ftpserver/abstracted_fs.py b/addons/document/ftpserver/abstracted_fs.py
index 0073b26b9f2..46ddba18128 100644
--- a/addons/document/ftpserver/abstracted_fs.py
+++ b/addons/document/ftpserver/abstracted_fs.py
@@ -307,8 +307,8 @@ class abstracted_fs:
def open(self, node, mode):
if not node:
raise OSError(1, 'Operation not permited.')
- # Reading operation
- if node.type=='file':
+ # Reading operation
+ if node.type == 'file':
cr = pooler.get_db(node.context.dbname).cursor()
uid = node.context.uid
if not self.isfile(node):
@@ -321,13 +321,15 @@ class abstracted_fs:
s.name = node
cr.close()
return s
- elif node.type=='content':
+ elif node.type == 'content':
uid = node.context.uid
cr = pooler.get_db(node.context.dbname).cursor()
pool = pooler.get_pool(node.context.dbname)
res = getattr(pool.get('document.directory.content'), 'process_read')(cr, uid, node)
- cr.close()
- return res
+ cr.close()
+ s = StringIO.StringIO(res)
+ s.name = node
+ return s
else:
raise OSError(1, 'Operation not permited.')
diff --git a/addons/document/ftpserver/ftpserver.py b/addons/document/ftpserver/ftpserver.py
index 13c44287303..e265987ae32 100644
--- a/addons/document/ftpserver/ftpserver.py
+++ b/addons/document/ftpserver/ftpserver.py
@@ -2165,7 +2165,7 @@ class FTPHandler(asynchat.async_chat):
datacr = self.fs.get_cr(line)
file = self.fs.ftp2fs(line, datacr)
line = self.fs.ftpnorm(line)
- fd = self.run_as_current_user(self.fs.open, file, 'rb')
+ fd = self.run_as_current_user(self.fs.open, file, 'rb')
except OSError, err:
self.fs.close_cr(datacr)
why = _strerror(err)
diff --git a/addons/document/nodes.py b/addons/document/nodes.py
index e6a614bf6aa..6216d4c3428 100644
--- a/addons/document/nodes.py
+++ b/addons/document/nodes.py
@@ -168,7 +168,9 @@ class node_dir(node_class):
#todo: more info from dirr
self.mimetype = 'application/x-directory'
# 'httpd/unix-directory'
- self.create_date = dirr.create_date
+ self.create_date = dirr.create_date
+ self.domain = dirr.domain
+ self.res_model = dirr.ressource_type_id and dirr.ressource_type_id.model or False
# TODO: the write date should be MAX(file.write)..
self.write_date = dirr.write_date or dirr.create_date
self.content_length = 0
@@ -205,9 +207,9 @@ class node_dir(node_class):
ctx = self.context.context.copy()
ctx.update(self.dctx)
where = [('directory_id','=',self.dir_id) ]
- ids = cntobj.search(cr,uid,where,context=ctx)
- for content in cntobj.browse(cr,uid,ids,context=ctx):
- res3 = cntobj._file_get(cr,self,nodename,content)
+ ids = cntobj.search(cr, uid, where, context=ctx)
+ for content in cntobj.browse(cr, uid, ids, context=ctx):
+ res3 = cntobj._file_get(cr, self, nodename, content)
if res3:
res.extend(res3)
@@ -312,7 +314,7 @@ class node_res_dir(node_class):
# TODO: the write date should be MAX(file.write)..
self.write_date = dirr.write_date or dirr.create_date
self.content_length = 0
- self.res_model = dirr.ressource_type_id.model
+ self.res_model = dirr.ressource_type_id and dirr.ressource_type_id.model or False
self.resm_id = dirr.ressource_id
self.namefield = dirr.resource_field or 'name'
self.displayname = dirr.name
diff --git a/addons/document_ics/document.py b/addons/document_ics/document.py
deleted file mode 100644
index bf32009f404..00000000000
--- a/addons/document_ics/document.py
+++ /dev/null
@@ -1,191 +0,0 @@
-# -*- coding: utf-8 -*-
-##############################################################################
-#
-# OpenERP, Open Source Management Solution
-# Copyright (C) 2004-2009 Tiny SPRL ().
-#
-# 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 .
-#
-##############################################################################
-
-from osv import osv, fields
-from osv.orm import except_orm
-import os
-import StringIO
-import base64
-import datetime
-import time
-import random
-import tools
-
-ICS_TAGS = {
- 'summary':'normal',
- 'uid':'normal' ,
- 'dtstart':'date' ,
- 'dtend':'date' ,
- 'created':'date' ,
- 'dtstamp':'date' ,
- 'last-modified':'normal' ,
- 'url':'normal' ,
- 'attendee':'multiple',
- 'location':'normal',
- 'categories': 'normal',
- 'description':'normal',
-
- # TODO: handle the 'duration' property
-}
-
-class document_directory_ics_fields(osv.osv):
- _name = 'document.directory.ics.fields'
- _columns = {
- 'field_id': fields.many2one('ir.model.fields', 'Open ERP Field', required=True),
- 'name': fields.selection(map(lambda x: (x, x), ICS_TAGS.keys()), 'ICS Value', required=True),
- 'content_id': fields.many2one('document.directory.content', 'Content', required=True, ondelete='cascade')
- }
-document_directory_ics_fields()
-
-class document_directory_content(osv.osv):
- _inherit = 'document.directory.content'
- _columns = {
- 'ics_object_id': fields.many2one('ir.model', 'Object'),
- 'ics_domain': fields.char('Domain', size=64),
- 'ics_field_ids': fields.one2many('document.directory.ics.fields', 'content_id', 'Fields Mapping')
- }
- _defaults = {
- 'ics_domain': lambda *args: '[]'
- }
- def process_write_ics(self, cr, uid, node, data, context={}):
- import vobject
- parsedCal = vobject.readOne(data)
- fields = {}
- fobj = self.pool.get('document.directory.content')
- content = fobj.browse(cr, uid, node.content.id, context)
-
- idomain = {}
- for d in eval(content.ics_domain):
- idomain[d[0]]=d[2]
- for n in content.ics_field_ids:
- fields[n.name] = n.field_id.name
- if 'uid' not in fields:
- return True
- for child in parsedCal.getChildren():
- result = {}
- uuid = None
- for event in child.getChildren():
- if event.name.lower()=='uid':
- uuid = event.value
- if event.name.lower() in fields:
- if ICS_TAGS[event.name.lower()]=='normal':
- result[fields[event.name.lower()]] = event.value.encode('utf8')
- elif ICS_TAGS[event.name.lower()]=='date':
- result[fields[event.name.lower()]] = event.value.strftime('%Y-%m-%d %H:%M:%S')
- if not uuid:
- continue
-
- fobj = self.pool.get(content.ics_object_id.model)
- id = fobj.search(cr, uid, [(fields['uid'], '=', uuid.encode('utf8'))], context=context)
- if id:
- fobj.write(cr, uid, id, result, context=context)
- else:
- r = idomain.copy()
- r.update(result)
- fobj.create(cr, uid, r, context=context)
-
- return True
-
- def process_read_ics(self, cr, uid, node, context={}):
- def ics_datetime(idate, short=False):
- if short:
- return datetime.date.fromtimestamp(time.mktime(time.strptime(idate, '%Y-%m-%d')))
- else:
- return datetime.datetime.strptime(idate, '%Y-%m-%d %H:%M:%S')
-
- import vobject
- obj_class = self.pool.get(node.content.ics_object_id.model)
- # Can be improved to use context and active_id !
- domain = eval(node.content.ics_domain)
- ids = obj_class.search(cr, uid, domain, context)
- cal = vobject.iCalendar()
- for obj in obj_class.browse(cr, uid, ids, context):
- event = cal.add('vevent')
- # Fix dtstamp et last-modified with create and write date on the object line
- perm = obj_class.perm_read(cr, uid, [obj.id], context)
- event.add('created').value = ics_datetime(time.strftime('%Y-%m-%d %H:%M:%S'))
- event.add('dtstamp').value = ics_datetime(perm[0]['create_date'][:19])
- if perm[0]['write_date']:
- event.add('last-modified').value = ics_datetime(perm[0]['write_date'][:19])
- for field in node.content.ics_field_ids:
- value = getattr(obj, field.field_id.name)
- value = value and tools.ustr(value)
- if (not value) and field.name=='uid':
- value = 'OpenERP-%s_%s@%s' % (node.content.ics_object_id.model, str(obj.id), cr.dbname,)
- obj_class.write(cr, uid, [obj.id], {field.field_id.name: value})
- if ICS_TAGS[field.name]=='normal':
- if type(value)==type(obj):
- value=value.name
- value = value or ''
- event.add(field.name).value = value or ''
- elif ICS_TAGS[field.name]=='date' and value:
- if field.name == 'dtstart':
- date_start = start_date = datetime.datetime.fromtimestamp(time.mktime(time.strptime(value , "%Y-%m-%d %H:%M:%S")))
- if field.name == 'dtend' and isinstance(value, float):
- value = (start_date + datetime.timedelta(hours=value)).strftime('%Y-%m-%d %H:%M:%S')
- if len(value)==10:
- value = ics_datetime(value, True)
- else:
- value = ics_datetime(value)
- event.add(field.name).value = value
- s= StringIO.StringIO(cal.serialize())
- s.name = node
- cr.commit()
- return s
-document_directory_content()
-
-class crm_case(osv.osv):
- _inherit = 'crm.case'
- _columns = {
- 'code': fields.char('Calendar Code', size=64),
- 'date_deadline': fields.datetime('Deadline', help="Deadline Date is automatically computed from Start Date + Duration"),
- }
-
- _defaults = {
- 'code': lambda obj, cr, uid, context: obj.pool.get('ir.sequence').get(cr, uid, 'crm.case'),
- }
-
- def copy(self, cr, uid, id, default=None, context=None):
- """
- code field must be unique in ICS file
- """
- if not default: default = {}
- if not context: context = {}
- default.update({'code': self.pool.get('ir.sequence').get(cr, uid, 'crm.case'), 'id': False})
- return super(crm_case, self).copy(cr, uid, id, default, context)
-
- def on_change_duration(self, cr, uid, id, date, duration):
- if not date:
- return {}
- start_date = datetime.datetime.fromtimestamp(time.mktime(time.strptime(date, "%Y-%m-%d %H:%M:%S")))
- if duration >= 0 :
- end = start_date + datetime.timedelta(hours=duration)
- if duration < 0:
- raise osv.except_osv(_('Warning !'),
- _('You can not set negative Duration.'))
-
- res = {'value' : {'date_deadline' : end.strftime('%Y-%m-%d %H:%M:%S')}}
- return res
-
-crm_case()
-
-# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:
-
diff --git a/addons/document_ics/document_ics.py b/addons/document_ics/document_ics.py
index caccf25e3ce..1fb8518050b 100644
--- a/addons/document_ics/document_ics.py
+++ b/addons/document_ics/document_ics.py
@@ -87,15 +87,16 @@ class document_directory_content(osv.osv):
}
def _file_get(self, cr, node, nodename, content, context=None):
if not content.obj_iterate:
- return super(document_directory_content, self)._file_get(cr,node,nodename,content)
+ return super(document_directory_content, self)._file_get(cr, node, nodename, content)
else:
- # print "iterate over ", content.object_id.model
+ if not content.object_id:
+ return False
mod = self.pool.get(content.object_id.model)
uid = node.context.uid
fname_fld = content.fname_field or 'id'
- where = []
+ where = []
if node.domain:
- where.append(node.domain)
+ where += eval(node.domain)
if nodename:
# Reverse-parse the nodename to deduce the clause:
prefix = (content.prefix or '')
@@ -107,7 +108,7 @@ class document_directory_content(osv.osv):
tval = nodename[len(prefix):0 - len(suffix)]
where.append((fname_fld,'=',tval))
# print "ics iterate clause:", where
- resids = mod.search(cr,uid,where,context=context)
+ resids = mod.search(cr, uid, where, context=context)
if not resids:
return False
@@ -244,13 +245,15 @@ class document_directory_content(osv.osv):
return datetime.datetime.strptime(idate, '%Y-%m-%d %H:%M:%S')
if node.extension != '.ics':
- return super(document_directory_content).process_read(cr, uid, node, context)
+ return super(document_directory_content).process_read(cr, uid, node, context)
import vobject
ctx = (context or {})
ctx.update(node.context.context.copy())
ctx.update(node.dctx)
content = self.browse(cr, uid, node.cnt_id, ctx)
+ if not content.object_id:
+ return super(document_directory_content).process_read(cr, uid, node, context)
obj_class = self.pool.get(content.object_id.model)
if content.ics_domain:
@@ -261,7 +264,7 @@ class document_directory_content(osv.osv):
domain.append(('id','=',node.act_id))
# print "process read clause:",domain
ids = obj_class.search(cr, uid, domain, context=ctx)
- cal = vobject.iCalendar()
+ cal = vobject.iCalendar()
for obj in obj_class.browse(cr, uid, ids):
event = cal.add('vevent')
# Fix dtstamp et last-modified with create and write date on the object line
From 6082127d5da37e2a2314fe96e68ad413fa5af3bf Mon Sep 17 00:00:00 2001
From: "Harry (Open ERP)"
Date: Tue, 15 Dec 2009 20:01:05 +0530
Subject: [PATCH 13/19] [FIX] document_webdav : Replace with four
bzr revid: hmo@tinyerp.com-20091215143105-3m1zuyf0fas65opf
---
addons/document_webdav/cache.py | 68 +-
addons/document_webdav/dav_fs.py | 1008 +++++++++++------------
addons/document_webdav/webdav_server.py | 44 +-
3 files changed, 560 insertions(+), 560 deletions(-)
diff --git a/addons/document_webdav/cache.py b/addons/document_webdav/cache.py
index bc75114c4ae..7f03aa6da13 100644
--- a/addons/document_webdav/cache.py
+++ b/addons/document_webdav/cache.py
@@ -2,38 +2,38 @@ import time
import heapq
def memoize(maxsize):
- """decorator to 'memoize' a function - caching its results"""
- def decorating_function(f):
- cache = {} # map from key to value
- heap = [] # list of keys, in LRU heap
- cursize = 0 # because len() is slow
- def wrapper(*args):
- key = repr(args)
- # performance crap
- _cache=cache
- _heap=heap
- _heappop = heapq.heappop
- _heappush = heapq.heappush
- _time = time.time
- _cursize = cursize
- _maxsize = maxsize
- if not _cache.has_key(key):
- if _cursize == _maxsize:
- # pop oldest element
- (_,oldkey) = _heappop(_heap)
- _cache.pop(oldkey)
- else:
- _cursize += 1
- # insert this element
- _cache[key] = f(*args)
- _heappush(_heap,(_time(),key))
- wrapper.misses += 1
- else:
- wrapper.hits += 1
- return cache[key]
- wrapper.__doc__ = f.__doc__
- wrapper.__name__ = f.__name__
- wrapper.hits = wrapper.misses = 0
- return wrapper
- return decorating_function
+ """decorator to 'memoize' a function - caching its results"""
+ def decorating_function(f):
+ cache = {} # map from key to value
+ heap = [] # list of keys, in LRU heap
+ cursize = 0 # because len() is slow
+ def wrapper(*args):
+ key = repr(args)
+ # performance crap
+ _cache=cache
+ _heap=heap
+ _heappop = heapq.heappop
+ _heappush = heapq.heappush
+ _time = time.time
+ _cursize = cursize
+ _maxsize = maxsize
+ if not _cache.has_key(key):
+ if _cursize == _maxsize:
+ # pop oldest element
+ (_,oldkey) = _heappop(_heap)
+ _cache.pop(oldkey)
+ else:
+ _cursize += 1
+ # insert this element
+ _cache[key] = f(*args)
+ _heappush(_heap,(_time(),key))
+ wrapper.misses += 1
+ else:
+ wrapper.hits += 1
+ return cache[key]
+ wrapper.__doc__ = f.__doc__
+ wrapper.__name__ = f.__name__
+ wrapper.hits = wrapper.misses = 0
+ return wrapper
+ return decorating_function
diff --git a/addons/document_webdav/dav_fs.py b/addons/document_webdav/dav_fs.py
index 4cd4b2d6405..0d8331b8959 100644
--- a/addons/document_webdav/dav_fs.py
+++ b/addons/document_webdav/dav_fs.py
@@ -56,33 +56,33 @@ urlparse.uses_netloc.append('webdav')
urlparse.uses_netloc.append('webdavs')
class tinydav_handler(dav_interface):
- """
- This class models a Tiny ERP interface for the DAV server
- """
- PROPS={'DAV:': dav_interface.PROPS['DAV:'], }
+ """
+ This class models a Tiny ERP interface for the DAV server
+ """
+ PROPS={'DAV:': dav_interface.PROPS['DAV:'], }
- M_NS={ "DAV:" : dav_interface.M_NS['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
+ 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
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:
- cr.close()
- return props
- node = self.uri2object(cr,uid,pool, uri2)
- if node:
- props.update(node.get_dav_props(cr))
- cr.close()
- return props
+ 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:
+ 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_prop(self,uri,ns,propname):
""" return the value of a given property
@@ -97,557 +97,557 @@ class tinydav_handler(dav_interface):
if uri[-1]=='/':uri=uri[:-1]
cr, uid, pool, dbname, uri2 = self.get_cr(uri)
if not dbname:
- cr.close()
+ cr.close()
raise DAV_NotFound
node = self.uri2object(cr,uid,pool, uri2)
if not node:
- cr.close()
+ 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 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 later_get_db_from_path(self,path):
- return "aaa"
+ def later_get_db_from_path(self,path):
+ return "aaa"
- 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)
+ 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.LocalService('db')
- result = s.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
+ @memoize(4)
+ def db_list(self):
+ s = netsvc.LocalService('db')
+ result = s.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):
- """ 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:
- s = netsvc.LocalService('db')
- return map(lambda x: self.urijoin(x), self.db_list())
- result = []
- node = self.uri2object(cr,uid,pool, uri2[:])
- if not node:
- 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
- for d in node.children(cr):
- 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) )
- cr.close()
- return result
+ def get_childs(self,uri):
+ """ 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:
+ s = netsvc.LocalService('db')
+ return map(lambda x: self.urijoin(x), self.db_list())
+ result = []
+ node = self.uri2object(cr,uid,pool, uri2[:])
+ if not node:
+ 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
+ for d in node.children(cr):
+ 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) )
+ 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
+ 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('/')[2]
- 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)
- user, passwd, dbn2, uid = self.parent.auth_proxy.auth_creds[pdb]
- db,pool = pooler.get_db_and_pool(dbname)
- cr = db.cursor()
- uri2 = reluri.split('/')[3:]
- return cr, uid, pool, dbname, uri2
+ #
+ # 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('/')[2]
+ 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)
+ user, passwd, dbn2, uid = self.parent.auth_proxy.auth_creds[pdb]
+ db,pool = pooler.get_db_and_pool(dbname)
+ cr = db.cursor()
+ uri2 = reluri.split('/')[3:]
+ 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 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:
- cr.close()
+ 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:
+ 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:
- 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:
+ 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
+ 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:
- cr.close()
- return '0'
- 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_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:
+ cr.close()
+ return '0'
+ 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_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)
- try:
- if not dbname:
- return today
- 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:
- cr.close()
+ @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)
+ try:
+ if not dbname:
+ return today
+ 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:
+ cr.close()
- @memoize(CACHE_SIZE)
- def get_creationdate(self,uri):
- """ return the last modified date of the object """
+ @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)
- try:
- if not dbname:
- raise DAV_Error, 409
- node = self.uri2object(cr,uid,pool, uri2)
- if not node:
- raise DAV_NotFound(uri2)
- if node.create_date:
- result = time.strptime(node.create_date,'%Y-%m-%d %H:%M:%S')
- else:
- result = time.gmtime()
- return result
- finally:
- cr.close()
+ 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)
+ if node.create_date:
+ result = time.strptime(node.create_date,'%Y-%m-%d %H:%M:%S')
+ else:
+ result = time.gmtime()
+ return result
+ finally:
+ 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)
- try:
- if not dbname:
- return 'httpd/unix-directory'
- node = self.uri2object(cr,uid,pool, uri2)
- if not node:
- raise DAV_NotFound(uri2)
-
- result = 'application/octet-stream'
- #if node.type=='collection':
- #result ='httpd/unix-directory'
- #else:
- result = node.mimetype
- return result
- #raise DAV_NotFound, 'Could not find %s' % path
- finally:
- 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)
+ try:
+ if not dbname:
+ return 'httpd/unix-directory'
+ node = self.uri2object(cr,uid,pool, uri2)
+ if not node:
+ raise DAV_NotFound(uri2)
+
+ result = 'application/octet-stream'
+ #if node.type=='collection':
+ #result ='httpd/unix-directory'
+ #else:
+ result = node.mimetype
+ return result
+ #raise DAV_NotFound, 'Could not find %s' % path
+ finally:
+ cr.close()
- def mkcol(self,uri):
- """ create a new collection """
- self.parent.log_message('MKCOL: %s' % uri)
- if uri[-1]=='/':uri=uri[:-1]
- parent='/'.join(uri.split('/')[:-1])
- if not parent.startswith(self.baseuri):
- parent=self.baseuri + ''.join(parent[1:])
- if not uri.startswith(self.baseuri):
- uri=self.baseuri + ''.join(uri[1:])
+ def mkcol(self,uri):
+ """ create a new collection """
+ self.parent.log_message('MKCOL: %s' % uri)
+ if uri[-1]=='/':uri=uri[:-1]
+ parent='/'.join(uri.split('/')[:-1])
+ if not parent.startswith(self.baseuri):
+ parent=self.baseuri + ''.join(parent[1:])
+ if not uri.startswith(self.baseuri):
+ uri=self.baseuri + ''.join(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[:-1])
- object2=node and node.object2 or False
- object=node and node.object or False
+ cr, uid, pool,dbname, uri2 = self.get_cr(uri)
+ if not dbname:
+ raise DAV_Error, 409
+ node = self.uri2object(cr,uid,pool, uri2[:-1])
+ object2=node and node.object2 or False
+ object=node and node.object or False
- objname = uri2[-1]
- if not object:
- pool.get('document.directory').create(cr, uid, {
- 'name': objname,
- 'parent_id': False,
- 'ressource_type_id': False,
- 'ressource_id': False
- })
- else:
- pool.get('document.directory').create(cr, uid, {
- 'name': objname,
- 'parent_id': object.id,
- 'ressource_type_id': object.ressource_type_id.id,
- 'ressource_id': object2 and object2.id or False
- })
+ objname = uri2[-1]
+ if not object:
+ pool.get('document.directory').create(cr, uid, {
+ 'name': objname,
+ 'parent_id': False,
+ 'ressource_type_id': False,
+ 'ressource_id': False
+ })
+ else:
+ pool.get('document.directory').create(cr, uid, {
+ 'name': objname,
+ 'parent_id': object.id,
+ 'ressource_type_id': object.ressource_type_id.id,
+ 'ressource_id': object2 and object2.id or False
+ })
- cr.commit()
- cr.close()
- return True
+ 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'%( unicode(uri,'utf8'), len(data), 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
+ def put(self,uri,data,content_type=None):
+ """ put the object into the filesystem """
+ self.parent.log_message('Putting %s (%d), %s'%( unicode(uri,'utf8'), len(data), 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()
+ 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()
- return 201
+ return 201
- def rmcol(self,uri):
- """ delete a collection """
- if uri[-1]=='/':uri=uri[:-1]
+ def rmcol(self,uri):
+ """ delete a collection """
+ if uri[-1]=='/':uri=uri[:-1]
- cr, uid, pool, dbname, uri2 = self.get_cr(uri)
- if True or not dbname: # *-*
- raise DAV_Error, 409
- node = self.uri2object(cr,uid,pool, uri2)
- object2=node and node.object2 or False
- object=node and node.object or False
- if object._table_name=='document.directory':
- if object.child_ids:
- raise DAV_Forbidden # forbidden
- if object.file_ids:
- raise DAV_Forbidden # forbidden
- res = pool.get('document.directory').unlink(cr, uid, [object.id])
+ cr, uid, pool, dbname, uri2 = self.get_cr(uri)
+ if True or not dbname: # *-*
+ raise DAV_Error, 409
+ node = self.uri2object(cr,uid,pool, uri2)
+ object2=node and node.object2 or False
+ object=node and node.object or False
+ if object._table_name=='document.directory':
+ if object.child_ids:
+ raise DAV_Forbidden # forbidden
+ if object.file_ids:
+ raise DAV_Forbidden # forbidden
+ res = pool.get('document.directory').unlink(cr, uid, [object.id])
- cr.commit()
- cr.close()
- return 204
+ cr.commit()
+ cr.close()
+ return 204
- def rm(self,uri):
- if uri[-1]=='/':uri=uri[:-1]
+ def rm(self,uri):
+ if uri[-1]=='/':uri=uri[:-1]
- object=False
- cr, uid, pool,dbname, uri2 = self.get_cr(uri)
- #if not dbname:
- if True:
- raise DAV_Error, 409
- node = self.uri2object(cr,uid,pool, uri2)
- object2=node and node.object2 or False
- object=node and node.object or False
- if not object:
- raise DAV_NotFound
+ object=False
+ cr, uid, pool,dbname, uri2 = self.get_cr(uri)
+ #if not dbname:
+ if True:
+ raise DAV_Error, 409
+ node = self.uri2object(cr,uid,pool, uri2)
+ object2=node and node.object2 or False
+ object=node and node.object or False
+ if not object:
+ raise DAV_NotFound
- self.parent.log_message(' rm %s "%s"'%(object._table_name,uri))
- if object._table_name=='ir.attachment':
- res = pool.get('ir.attachment').unlink(cr, uid, [object.id])
- else:
- raise DAV_Forbidden # forbidden
- parent='/'.join(uri.split('/')[:-1])
- cr.commit()
- cr.close()
- return 204
+ self.parent.log_message(' rm %s "%s"'%(object._table_name,uri))
+ if object._table_name=='ir.attachment':
+ res = pool.get('ir.attachment').unlink(cr, uid, [object.id])
+ else:
+ raise DAV_Forbidden # forbidden
+ parent='/'.join(uri.split('/')[:-1])
+ cr.commit()
+ cr.close()
+ return 204
- ### DELETE handlers (examples)
- ### (we use the predefined methods in davcmd instead of doing
- ### a rm directly
- ###
+ ### DELETE handlers (examples)
+ ### (we use the predefined methods in davcmd instead of doing
+ ### a rm directly
+ ###
- def delone(self,uri):
- """ delete a single resource
+ 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
+ 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
+ """
+ if uri[-1]=='/':uri=uri[:-1]
+ res=delone(self,uri)
+ parent='/'.join(uri.split('/')[:-1])
+ return res
- def deltree(self,uri):
- """ delete a collection
+ 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
+ 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)
- ###
+ ###
+ ### MOVE handlers (examples)
+ ###
- def moveone(self,src,dst,overwrite):
- """ move one resource with Depth=0
+ def moveone(self,src,dst,overwrite):
+ """ move one resource with Depth=0
- an alternative implementation would be
+ 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
+ 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
+ (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
+ def movetree(self,src,dst,overwrite):
+ """ move a collection with Depth=infinity
- an alternative implementation would be
+ 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
+ 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"""
+ (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
+ res=movetree(self,src,dst,overwrite)
+ return res
- ###
- ### COPY handlers
- ###
+ ###
+ ### COPY handlers
+ ###
- def copyone(self,src,dst,overwrite):
- """ copy one resource with Depth=0
+ def copyone(self,src,dst,overwrite):
+ """ copy one resource with Depth=0
- an alternative implementation would be
+ 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
+ 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
+ (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
+ def copytree(self,src,dst,overwrite):
+ """ copy a collection with Depth=infinity
- an alternative implementation would be
+ 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
+ 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
+ (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.
- ###
+ ###
+ ### 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 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.
+ 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)
+ 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:
- return True
- try:
- node = self.uri2object(cr,uid,pool, uri2)
- if node:
- result = True
- except:
- pass
- cr.close()
- return result
+ def exists(self,uri):
+ """ test if a resource exists """
+ result = False
+ cr, uid, pool,dbname, uri2 = self.get_cr(uri)
+ if not dbname:
+ 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
+ @memoize(CACHE_SIZE)
+ def is_collection(self,uri):
+ """ test if the given uri is a collection """
+ return self._get_dav_resourcetype(uri)==COLLECTION
diff --git a/addons/document_webdav/webdav_server.py b/addons/document_webdav/webdav_server.py
index f04315e13c9..37ce38e6709 100644
--- a/addons/document_webdav/webdav_server.py
+++ b/addons/document_webdav/webdav_server.py
@@ -35,13 +35,13 @@ from service.websrv_lib import HTTPDir,FixSendError
class DAVHandler(FixSendError,DAVRequestHandler):
verbose = False
-
+
def get_userinfo(self,user,pw):
- print "get_userinfo"
- return False
+ print "get_userinfo"
+ return False
def _log(self, message):
- netsvc.Logger().notifyChannel("webdav",netsvc.LOG_DEBUG,message)
-
+ netsvc.Logger().notifyChannel("webdav",netsvc.LOG_DEBUG,message)
+
def handle(self):
pass
@@ -49,30 +49,30 @@ class DAVHandler(FixSendError,DAVRequestHandler):
pass
def setup(self):
- davpath = '/'+config.get_misc('webdav','vdir','webdav')+'/'
- self.baseuri = "http://%s:%d%s"% (self.server.server_name,self.server.server_port,davpath)
- self.IFACE_CLASS = tinydav_handler(self)
- pass
-
+ davpath = '/'+config.get_misc('webdav','vdir','webdav')+'/'
+ self.baseuri = "http://%s:%d%s"% (self.server.server_name,self.server.server_port,davpath)
+ self.IFACE_CLASS = tinydav_handler(self)
+ pass
+
def log_message(self, format, *args):
- netsvc.Logger().notifyChannel('webdav',netsvc.LOG_DEBUG_RPC,format % args)
+ netsvc.Logger().notifyChannel('webdav',netsvc.LOG_DEBUG_RPC,format % args)
def log_error(self, format, *args):
- netsvc.Logger().notifyChannel('xmlrpc',netsvc.LOG_WARNING,format % args)
+ netsvc.Logger().notifyChannel('xmlrpc',netsvc.LOG_WARNING,format % args)
try:
- from service.http_server import reg_http_service,OpenERPAuthProvider
- if (config.get_misc('webdav','enable',False)):
- davpath = '/'+config.get_misc('webdav','vdir','webdav')+'/'
- handler = DAVHandler
- handler.verbose = config.get_misc('webdav','verbose',True)
- handler.debug = config.get_misc('webdav','debug',True)
- reg_http_service(HTTPDir(davpath,DAVHandler,OpenERPAuthProvider()))
- netsvc.Logger().notifyChannel('webdav',netsvc.LOG_INFO,"WebDAV service registered at path: %s/ "% davpath)
+ from service.http_server import reg_http_service,OpenERPAuthProvider
+ if (config.get_misc('webdav','enable',False)):
+ davpath = '/'+config.get_misc('webdav','vdir','webdav')+'/'
+ handler = DAVHandler
+ handler.verbose = config.get_misc('webdav','verbose',True)
+ handler.debug = config.get_misc('webdav','debug',True)
+ reg_http_service(HTTPDir(davpath,DAVHandler,OpenERPAuthProvider()))
+ netsvc.Logger().notifyChannel('webdav',netsvc.LOG_INFO,"WebDAV service registered at path: %s/ "% davpath)
except Exception, e:
- logger = netsvc.Logger()
- logger.notifyChannel('webdav', netsvc.LOG_ERROR, 'Cannot launch webdav: %s' % e)
+ logger = netsvc.Logger()
+ logger.notifyChannel('webdav', netsvc.LOG_ERROR, 'Cannot launch webdav: %s' % e)
#eof
From 575035a2050fd5328fe0dad3cdab3d453abaf430 Mon Sep 17 00:00:00 2001
From: "Harry (Open ERP)"
Date: Wed, 16 Dec 2009 11:39:18 +0530
Subject: [PATCH 14/19] [FIX] document_webdav : Replace char with four
bzr revid: hmo@tinyerp.com-20091216060918-vadie86sxpakwp8u
---
addons/document_webdav/DAV/WebDAVServer.py | 48 +++++++++++-----------
1 file changed, 24 insertions(+), 24 deletions(-)
diff --git a/addons/document_webdav/DAV/WebDAVServer.py b/addons/document_webdav/DAV/WebDAVServer.py
index cb4db2c0204..5f409773c18 100644
--- a/addons/document_webdav/DAV/WebDAVServer.py
+++ b/addons/document_webdav/DAV/WebDAVServer.py
@@ -81,8 +81,8 @@ class DAVRequestHandler(BaseHTTPServer.BaseHTTPRequestHandler):
def send_body(self,DATA,code,msg,desc,ctype='application/octet-stream',headers=None):
""" send a body in one part """
- if not headers:
- headers = {}
+ if not headers:
+ headers = {}
self.send_response(code,message=msg)
self.send_header("Connection", "keep-alive")
self.send_header("Accept-Ranges", "bytes")
@@ -122,7 +122,7 @@ class DAVRequestHandler(BaseHTTPServer.BaseHTTPRequestHandler):
self.send_response(200)
self.send_header("Allow", "GET, HEAD, COPY, MOVE, POST, PUT, PROPFIND, PROPPATCH, OPTIONS, MKCOL, DELETE, TRACE")
self.send_header("Content-Type", "text/plain")
- self.send_header("Connection", "keep-alive")
+ self.send_header("Connection", "keep-alive")
self.send_header("DAV", "1")
self.end_headers()
@@ -162,9 +162,9 @@ class DAVRequestHandler(BaseHTTPServer.BaseHTTPRequestHandler):
try:
DATA=pf.createResponse()
DATA=DATA+"\n"
- # print "Data:", DATA
- except DAV_NotFound,(ec,dd):
- return self.send_notFound(dd, uri)
+ # print "Data:", DATA
+ except DAV_NotFound,(ec,dd):
+ return self.send_notFound(dd, uri)
except DAV_Error, (ec,dd):
return self.send_error(ec,dd)
@@ -172,10 +172,10 @@ class DAVRequestHandler(BaseHTTPServer.BaseHTTPRequestHandler):
def geturi(self):
buri = self.IFACE_CLASS.baseuri
- if buri[-1] == '/':
- return urllib.unquote(buri[:-1]+self.path)
- else:
- return urllib.unquote(buri+self.path)
+ if buri[-1] == '/':
+ return urllib.unquote(buri[:-1]+self.path)
+ else:
+ return urllib.unquote(buri+self.path)
def do_GET(self):
"""Serve a GET request."""
@@ -209,7 +209,7 @@ class DAVRequestHandler(BaseHTTPServer.BaseHTTPRequestHandler):
""" Send a HEAD response """
dc=self.IFACE_CLASS
uri=self.geturi()
-
+
# get the last modified date
try:
lm=dc.get_prop(uri,"DAV:","getlastmodified")
@@ -243,12 +243,12 @@ class DAVRequestHandler(BaseHTTPServer.BaseHTTPRequestHandler):
uri=self.geturi()
try:
res = dc.mkcol(uri)
- if res:
- self.send_body(None,201,"Created",'')
- else:
- self.send_body(None,415,"Cannot create",'')
- #self.send_header("Connection", "keep-alive")
- # Todo: some content, too
+ if res:
+ self.send_body(None,201,"Created",'')
+ else:
+ self.send_body(None,415,"Cannot create",'')
+ #self.send_header("Connection", "keep-alive")
+ # Todo: some content, too
except DAV_Error, (ec,dd):
self.send_body(None,int(ec),dd,dd)
@@ -370,10 +370,10 @@ class DAVRequestHandler(BaseHTTPServer.BaseHTTPRequestHandler):
def send_notFound(self,descr,uri):
body = """
-
- %s
-
- %s
-
- """
- return self.send_status(404,descr, body=body % (uri,descr))
+
+ %s
+
+ %s
+
+ """
+ return self.send_status(404,descr, body=body % (uri,descr))
From ad5882ec42eb4f923f38cb73c9e23559bff1594b Mon Sep 17 00:00:00 2001
From: "Harry (Open ERP)"
Date: Thu, 17 Dec 2009 14:12:14 +0530
Subject: [PATCH 15/19] [IMP] document: remove ftp Integration from document
and make seperate module for FTP Integration
bzr revid: hmo@tinyerp.com-20091217084214-6mve0dt68u8jdded
---
addons/document/__init__.py | 2 -
addons/document/__terp__.py | 3 +-
addons/document/document_view.xml | 51 +----------------
addons/document/nodes.py | 6 +-
addons/document/security/ir.model.access.csv | 2 -
addons/document_ftp/__init__.py | 24 ++++++++
addons/document_ftp/__terp__.py | 43 ++++++++++++++
.../doc_conf_wizard.py | 0
addons/document_ftp/document_ftp_view.xml | 56 +++++++++++++++++++
.../ftpserver/__init__.py | 0
.../ftpserver/abstracted_fs.py | 4 +-
.../ftpserver/authorizer.py | 0
.../ftpserver/ftpserver.py | 0
.../document_ftp/security/ir.model.access.csv | 3 +
14 files changed, 137 insertions(+), 57 deletions(-)
create mode 100644 addons/document_ftp/__init__.py
create mode 100644 addons/document_ftp/__terp__.py
rename addons/{document => document_ftp}/doc_conf_wizard.py (100%)
create mode 100644 addons/document_ftp/document_ftp_view.xml
rename addons/{document => document_ftp}/ftpserver/__init__.py (100%)
rename addons/{document => document_ftp}/ftpserver/abstracted_fs.py (99%)
rename addons/{document => document_ftp}/ftpserver/authorizer.py (100%)
rename addons/{document => document_ftp}/ftpserver/ftpserver.py (100%)
create mode 100644 addons/document_ftp/security/ir.model.access.csv
diff --git a/addons/document/__init__.py b/addons/document/__init__.py
index a9810a8b7b8..f624cfa850b 100644
--- a/addons/document/__init__.py
+++ b/addons/document/__init__.py
@@ -25,8 +25,6 @@ import document_storage
import document_directory
import directory_content
import directory_report
-import doc_conf_wizard
import document
-import ftpserver
# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:
diff --git a/addons/document/__terp__.py b/addons/document/__terp__.py
index be9eb6a9d3f..edd5ebe83af 100644
--- a/addons/document/__terp__.py
+++ b/addons/document/__terp__.py
@@ -24,8 +24,7 @@
'name': 'Integrated Document Management System',
'version': '1.99',
'category': 'Generic Modules/Others',
- 'description': """This is a complete document management system:
- * FTP Interface
+ 'description': """This is a complete document management system:
* User Authentication
* Document Indexation
""",
diff --git a/addons/document/document_view.xml b/addons/document/document_view.xml
index babc5af33b3..e23b3ac5025 100644
--- a/addons/document/document_view.xml
+++ b/addons/document/document_view.xml
@@ -146,19 +146,7 @@
-
-
- Browse Files
- ftp://localhost:8021/
-
-
+ parent="menu_document_configuration"/>
ir.attachment
@@ -301,42 +289,7 @@
-
-
-
- Auto Configure Directory
- document.configuration.wizard
- form
-
-
-
-
-
-
- Auto Configure Directory
- ir.actions.act_window
- document.configuration.wizard
- form
- form
- new
-
-
-
- Auto Configure Directory
- This wizard will configure the URL of the server of the document management system.
-
-
-
+
).
+#
+# 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 .
+#
+##############################################################################
+import doc_conf_wizard
+import ftpserver
+
+# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:
diff --git a/addons/document_ftp/__terp__.py b/addons/document_ftp/__terp__.py
new file mode 100644
index 00000000000..13da099768c
--- /dev/null
+++ b/addons/document_ftp/__terp__.py
@@ -0,0 +1,43 @@
+# -*- coding: utf-8 -*-
+##############################################################################
+#
+# OpenERP, Open Source Management Solution
+# Copyright (C) 2004-2009 Tiny SPRL ().
+#
+# 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 .
+#
+##############################################################################
+
+
+{
+ 'name': 'Integrated FTP Server with Document Management System',
+ 'version': '1.99',
+ 'category': 'Generic Modules/Others',
+ 'description': """This is a support FTP Interface with document management system:
+""",
+ 'author': 'Tiny',
+ 'website': 'http://www.openerp.com',
+ 'depends': ['base', 'document'],
+ 'init_xml': [],
+ 'update_xml': [
+ 'document_ftp_view.xml',
+ 'security/ir.model.access.csv'
+ ],
+ 'demo_xml': [],
+ 'installable': True,
+ 'active': False,
+ 'certificate': None,
+}
+
+# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:
diff --git a/addons/document/doc_conf_wizard.py b/addons/document_ftp/doc_conf_wizard.py
similarity index 100%
rename from addons/document/doc_conf_wizard.py
rename to addons/document_ftp/doc_conf_wizard.py
diff --git a/addons/document_ftp/document_ftp_view.xml b/addons/document_ftp/document_ftp_view.xml
new file mode 100644
index 00000000000..793dbfb7dea
--- /dev/null
+++ b/addons/document_ftp/document_ftp_view.xml
@@ -0,0 +1,56 @@
+
+
+
+ Browse Files
+ ftp://localhost:8021/
+
+
+
+
+
+
+
+ Auto Configure Directory
+ document.configuration.wizard
+ form
+
+
+
+
+
+
+ Auto Configure Directory
+ ir.actions.act_window
+ document.configuration.wizard
+ form
+ form
+ new
+
+
+
+ Auto Configure Directory
+ This wizard will configure the URL of the server of the document management system.
+
+
+
+
+
+
+
+
diff --git a/addons/document/ftpserver/__init__.py b/addons/document_ftp/ftpserver/__init__.py
similarity index 100%
rename from addons/document/ftpserver/__init__.py
rename to addons/document_ftp/ftpserver/__init__.py
diff --git a/addons/document/ftpserver/abstracted_fs.py b/addons/document_ftp/ftpserver/abstracted_fs.py
similarity index 99%
rename from addons/document/ftpserver/abstracted_fs.py
rename to addons/document_ftp/ftpserver/abstracted_fs.py
index 46ddba18128..34a62e8683a 100644
--- a/addons/document/ftpserver/abstracted_fs.py
+++ b/addons/document_ftp/ftpserver/abstracted_fs.py
@@ -205,7 +205,9 @@ class abstracted_fs:
def fs2ftp(self, node):
res='/'
if node:
- res = os.path.normpath(''.join(node.full_path()))
+ paths = node.full_path()
+ paths = map(lambda x: '/' +x, paths)
+ res = os.path.normpath(''.join(paths))
res = res.replace("\\", "/")
while res[:2] == '//':
res = res[1:]
diff --git a/addons/document/ftpserver/authorizer.py b/addons/document_ftp/ftpserver/authorizer.py
similarity index 100%
rename from addons/document/ftpserver/authorizer.py
rename to addons/document_ftp/ftpserver/authorizer.py
diff --git a/addons/document/ftpserver/ftpserver.py b/addons/document_ftp/ftpserver/ftpserver.py
similarity index 100%
rename from addons/document/ftpserver/ftpserver.py
rename to addons/document_ftp/ftpserver/ftpserver.py
diff --git a/addons/document_ftp/security/ir.model.access.csv b/addons/document_ftp/security/ir.model.access.csv
new file mode 100644
index 00000000000..c9c082f4397
--- /dev/null
+++ b/addons/document_ftp/security/ir.model.access.csv
@@ -0,0 +1,3 @@
+"id","name","model_id:id","group_id:id","perm_read","perm_write","perm_create","perm_unlink"
+"access_document_configuation_wizard","document.configuration.wizard document manager","model_document_configuration_wizard","document.group_document_manager",1,1,1,1
+"access_document_configuation_wizard_sytem","document.configuration.wizard group system","model_document_configuration_wizard","base.group_system",1,1,1,1
From 33f9180c0cc11b1fbb4a5fee8caeb7faffd2de68 Mon Sep 17 00:00:00 2001
From: "P. Christeas"
Date: Thu, 17 Dec 2009 14:32:52 +0530
Subject: [PATCH 16/19] [FIX] document_webdav: close another 2 cursors, avoid
deadlock. taken from xrg git branch:fc70e930c1e89c868c613fdc83534e9f853dcfa2
bzr revid: hmo@tinyerp.com-20091217090252-f9thyune5j2dcxa6
---
addons/document_webdav/dav_fs.py | 8 +++-----
1 file changed, 3 insertions(+), 5 deletions(-)
diff --git a/addons/document_webdav/dav_fs.py b/addons/document_webdav/dav_fs.py
index 0d8331b8959..68f0bd71c40 100644
--- a/addons/document_webdav/dav_fs.py
+++ b/addons/document_webdav/dav_fs.py
@@ -150,6 +150,7 @@ class tinydav_handler(dav_interface):
if not dbname:
s = netsvc.LocalService('db')
+ cr.close()
return map(lambda x: self.urijoin(x), self.db_list())
result = []
node = self.uri2object(cr,uid,pool, uri2[:])
@@ -354,11 +355,6 @@ class tinydav_handler(dav_interface):
node = self.uri2object(cr,uid,pool, uri2)
if not node:
raise DAV_NotFound(uri2)
-
- result = 'application/octet-stream'
- #if node.type=='collection':
- #result ='httpd/unix-directory'
- #else:
result = node.mimetype
return result
#raise DAV_NotFound, 'Could not find %s' % path
@@ -469,6 +465,7 @@ class tinydav_handler(dav_interface):
cr, uid, pool,dbname, uri2 = self.get_cr(uri)
#if not dbname:
if True:
+ cr.close()
raise DAV_Error, 409
node = self.uri2object(cr,uid,pool, uri2)
object2=node and node.object2 or False
@@ -637,6 +634,7 @@ class tinydav_handler(dav_interface):
result = False
cr, uid, pool,dbname, uri2 = self.get_cr(uri)
if not dbname:
+ cr.close()
return True
try:
node = self.uri2object(cr,uid,pool, uri2)
From 23dd94d600e40a07435a688da6ce16fd456d474b Mon Sep 17 00:00:00 2001
From: "Harry (Open ERP)"
Date: Thu, 17 Dec 2009 14:34:37 +0530
Subject: [PATCH 17/19] [IMP] document_webdav : Replace with four
bzr revid: hmo@tinyerp.com-20091217090437-56n5zq91e1fzy8rf
---
addons/document_webdav/DAV/iface.py | 18 +++++++++---------
1 file changed, 9 insertions(+), 9 deletions(-)
diff --git a/addons/document_webdav/DAV/iface.py b/addons/document_webdav/DAV/iface.py
index 8f6daa5eaa8..c8f5fe2594d 100644
--- a/addons/document_webdav/DAV/iface.py
+++ b/addons/document_webdav/DAV/iface.py
@@ -67,19 +67,19 @@ class dav_interface:
if self.M_NS.has_key(ns):
prefix=self.M_NS[ns]
else:
- print "No namespace:",ns
+ print "No namespace:",ns, "( for prop:", propname,")"
raise DAV_NotFound
mname=prefix+"_"+propname
- if not hasattr(self,mname):
- raise DAV_NotFound
+ if not hasattr(self,mname):
+ raise DAV_NotFound
try:
m=getattr(self,mname)
r=m(uri)
return r
except AttributeError, e:
- print 'Property %s not supported' % propname
- print "Exception:", e
+ print 'Property %s not supported' % propname
+ print "Exception:", e
raise DAV_NotFound
###
@@ -110,15 +110,15 @@ class dav_interface:
""" return the creationdate of a resource """
d=self.get_creationdate(uri)
# format it
- if isinstance(d, int) or isinstance(d, float):
- d = time.localtimetime(d)
+ if isinstance(d, int) or isinstance(d, float):
+ d = time.localtimetime(d)
return time.strftime("%Y-%m-%dT%H:%M:%S%Z",d)
def _get_dav_getlastmodified(self,uri):
""" return the last modified date of a resource """
d=self.get_lastmodified(uri)
- if isinstance(d, int) or isinstance(d, float):
- d = time.localtime(d)
+ if isinstance(d, int) or isinstance(d, float):
+ d = time.localtime(d)
# format it
return time.asctime(d)
From 1b8c310a0a009b9c031e6bb177673bdf65a84b90 Mon Sep 17 00:00:00 2001
From: "Harry (Open ERP)"
Date: Thu, 17 Dec 2009 17:26:04 +0530
Subject: [PATCH 18/19] [FIX] document_webdav: fixed regration in mkcol, rmcol,
rm
bzr revid: hmo@tinyerp.com-20091217115604-g5h8vr9zmcaam4dv
---
addons/document_webdav/DAV/utils.py | 2 +-
addons/document_webdav/dav_fs.py | 87 ++++++++++++++---------------
2 files changed, 44 insertions(+), 45 deletions(-)
diff --git a/addons/document_webdav/DAV/utils.py b/addons/document_webdav/DAV/utils.py
index a764a6d96c9..c4bb4d1a4c0 100755
--- a/addons/document_webdav/DAV/utils.py
+++ b/addons/document_webdav/DAV/utils.py
@@ -135,7 +135,7 @@ def get_parenturi(uri):
def make_xmlresponse(result):
""" construct a response from a dict of uri:error_code elements """
- doc = Document.Document(None)
+ doc = Document(None)
ms=doc.createElement("D:multistatus")
ms.setAttribute("xmlns:D","DAV:")
doc.appendChild(ms)
diff --git a/addons/document_webdav/dav_fs.py b/addons/document_webdav/dav_fs.py
index 68f0bd71c40..750b06a4b65 100644
--- a/addons/document_webdav/dav_fs.py
+++ b/addons/document_webdav/dav_fs.py
@@ -46,9 +46,9 @@ 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
@@ -183,9 +183,9 @@ class tinydav_handler(dav_interface):
#
# pos: -1 to get the parent of the uri
#
- def get_cr(self, uri):
+ def get_cr(self, uri):
pdb = self.parent.auth_proxy.last_auth
- reluri = self.uri2local(uri)
+ reluri = self.uri2local(uri)
try:
dbname = reluri.split('/')[2]
except:
@@ -195,7 +195,7 @@ class tinydav_handler(dav_interface):
if not pdb and dbname:
# if dbname was in our uri, we should have authenticated
# against that.
- raise Exception("Programming error")
+ raise Exception("Programming error")
assert pdb == dbname, " %s != %s" %(pdb, dbname)
user, passwd, dbn2, uid = self.parent.auth_proxy.auth_creds[pdb]
db,pool = pooler.get_db_and_pool(dbname)
@@ -376,32 +376,32 @@ class tinydav_handler(dav_interface):
if not dbname:
raise DAV_Error, 409
node = self.uri2object(cr,uid,pool, uri2[:-1])
- object2=node and node.object2 or False
- object=node and node.object or False
+ object2 = False
+ if isinstance(node, node_res_obj):
+ object2 = node and pool.get(node.context.context['res_model']).browse(cr, uid, node.context.context['res_id']) or False
+
+ obj = node.context._dirobj.browse(cr, uid, node.dir_id)
+ if obj and (obj.type == 'ressource') and not object2:
+ raise OSError(1, 'Operation not permited.')
objname = uri2[-1]
- if not object:
- pool.get('document.directory').create(cr, uid, {
+ val = {
'name': objname,
- 'parent_id': False,
- 'ressource_type_id': False,
- 'ressource_id': False
- })
- else:
- pool.get('document.directory').create(cr, uid, {
- 'name': objname,
- 'parent_id': object.id,
- 'ressource_type_id': object.ressource_type_id.id,
- 'ressource_id': object2 and object2.id or False
- })
-
- cr.commit()
+ 'ressource_parent_type_id': obj and obj.ressource_type_id.id or False,
+ 'ressource_id': object2 and object2.id or False,
+ 'parent_id' : False
+ }
+ if (obj and (obj.type in ('directory'))) or not object2:
+ val['parent_id'] = obj and obj.id or False
+
+ pool.get('document.directory').create(cr, uid, val)
+ 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'%( unicode(uri,'utf8'), len(data), content_type))
+ self.parent.log_message('Putting %s (%d), %s'%( misc.ustr(uri), len(data), content_type))
parent='/'.join(uri.split('/')[:-1])
cr, uid, pool,dbname, uri2 = self.get_cr(uri)
if not dbname:
@@ -434,25 +434,27 @@ class tinydav_handler(dav_interface):
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 True or not dbname: # *-*
+ cr, uid, pool, dbname, uri2 = self.get_cr(uri)
+ if not dbname: # *-*
raise DAV_Error, 409
- node = self.uri2object(cr,uid,pool, uri2)
- object2=node and node.object2 or False
- object=node and node.object or False
+ 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 object.child_ids:
- raise DAV_Forbidden # forbidden
- if object.file_ids:
- raise DAV_Forbidden # forbidden
+ 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()
@@ -463,22 +465,19 @@ class tinydav_handler(dav_interface):
object=False
cr, uid, pool,dbname, uri2 = self.get_cr(uri)
- #if not dbname:
- if True:
- cr.close()
+ if not dbname:
+ cr.close()
raise DAV_Error, 409
node = self.uri2object(cr,uid,pool, uri2)
- object2=node and node.object2 or False
- object=node and node.object or False
- if not object:
- raise DAV_NotFound
-
+ object = pool.get('ir.attachment').browse(cr, uid, node.file_id)
self.parent.log_message(' rm %s "%s"'%(object._table_name,uri))
- if object._table_name=='ir.attachment':
+ 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 DAV_Forbidden # forbidden
- parent='/'.join(uri.split('/')[:-1])
+ raise OSError(1, 'Operation not permited.')
+
cr.commit()
cr.close()
return 204
From 6d32891c048f51d1c678e9f343dfdf3210fdd2b6 Mon Sep 17 00:00:00 2001
From: "Harry (Open ERP)"
Date: Thu, 17 Dec 2009 18:52:31 +0530
Subject: [PATCH 19/19] [FIX] document, document_ics, document_ftp : solved
problem to read content data
bzr revid: hmo@tinyerp.com-20091217132231-a4zcin2lvd2qjjsn
---
addons/document/directory_content.py | 10 ++++------
addons/document_ftp/ftpserver/abstracted_fs.py | 8 ++++----
addons/document_ics/document_ics.py | 9 ++++-----
3 files changed, 12 insertions(+), 15 deletions(-)
diff --git a/addons/document/directory_content.py b/addons/document/directory_content.py
index b82da1bce8a..0b1b444e83a 100644
--- a/addons/document/directory_content.py
+++ b/addons/document/directory_content.py
@@ -98,18 +98,16 @@ class document_directory_content(osv.osv):
res2.append(n)
return res2
- def process_write(self, cr, uid, node, data):
+ def process_write(self, cr, uid, node, data, context=None):
if node.extension != '.pdf':
raise Exception("Invalid content: %s" % node.extension)
return True
- def process_read(self, cr, uid, node):
+ def process_read(self, cr, uid, node, context=None):
if node.extension != '.pdf':
raise Exception("Invalid content: %s" % node.extension)
report = self.pool.get('ir.actions.report.xml').browse(cr, uid, node.report_id)
srv = netsvc.Service._services['report.'+report.report_name]
- pdf,pdftype = srv.create(cr, uid, [node.context.context['res_id']], {}, {})
- s = StringIO.StringIO(pdf)
- s.name = node
- return s
+ pdf,pdftype = srv.create(cr, uid, [node.context.context['res_id']], {}, {})
+ return pdf
document_directory_content()
diff --git a/addons/document_ftp/ftpserver/abstracted_fs.py b/addons/document_ftp/ftpserver/abstracted_fs.py
index 34a62e8683a..82a860b95d1 100644
--- a/addons/document_ftp/ftpserver/abstracted_fs.py
+++ b/addons/document_ftp/ftpserver/abstracted_fs.py
@@ -328,10 +328,10 @@ class abstracted_fs:
cr = pooler.get_db(node.context.dbname).cursor()
pool = pooler.get_pool(node.context.dbname)
res = getattr(pool.get('document.directory.content'), 'process_read')(cr, uid, node)
- cr.close()
- s = StringIO.StringIO(res)
- s.name = node
- return s
+ res = StringIO.StringIO(res)
+ res.name = node
+ cr.close()
+ return res
else:
raise OSError(1, 'Operation not permited.')
diff --git a/addons/document_ics/document_ics.py b/addons/document_ics/document_ics.py
index 1fb8518050b..440d043d880 100644
--- a/addons/document_ics/document_ics.py
+++ b/addons/document_ics/document_ics.py
@@ -126,7 +126,7 @@ class document_directory_content(osv.osv):
def process_write(self, cr, uid, node, data, context=None):
if node.extension != '.ics':
- return super(document_directory_content).process_write(cr, uid, node, data, context)
+ return super(document_directory_content, self).process_write(cr, uid, node, data, context)
import vobject
parsedCal = vobject.readOne(data)
fields = {}
@@ -245,7 +245,7 @@ class document_directory_content(osv.osv):
return datetime.datetime.strptime(idate, '%Y-%m-%d %H:%M:%S')
if node.extension != '.ics':
- return super(document_directory_content).process_read(cr, uid, node, context)
+ return super(document_directory_content, self).process_read(cr, uid, node, context)
import vobject
ctx = (context or {})
@@ -253,7 +253,7 @@ class document_directory_content(osv.osv):
ctx.update(node.dctx)
content = self.browse(cr, uid, node.cnt_id, ctx)
if not content.object_id:
- return super(document_directory_content).process_read(cr, uid, node, context)
+ return super(document_directory_content, self).process_read(cr, uid, node, context)
obj_class = self.pool.get(content.object_id.model)
if content.ics_domain:
@@ -294,8 +294,7 @@ class document_directory_content(osv.osv):
else:
value = ics_datetime(value)
event.add(field.name).value = value
- s= cal.serialize()
- cr.commit()
+ s = cal.serialize()
return s
document_directory_content()