merged with trunk
[odoo/odoo.git] / addons / document_ftp / ftpserver / abstracted_fs.py
old mode 100644 (file)
new mode 100755 (executable)
index ae6ca93..7580a88
@@ -1,9 +1,8 @@
 # -*- encoding: utf-8 -*-
+
 import os
 import time
 from tarfile import filemode
-import StringIO
-import base64
 import logging
 import errno
 
@@ -12,12 +11,10 @@ import fnmatch
 
 import pooler
 import netsvc
-import os
+
 from service import security
 from osv import osv
-#from document.nodes import node_res_dir, node_res_obj
 from document.nodes import get_node_context
-import stat
 
 def _get_month_name(month):
     month=int(month)
@@ -34,29 +31,8 @@ def _get_month_name(month):
     elif month==11:return 'Nov'
     elif month==12:return 'Dec'
 
-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
-
-def _to_decode(s):
-    try:
-        return s.encode('utf-8')
-    except UnicodeError:
-        try:
-            return s.encode('latin')
-        except UnicodeError:
-            try:
-                return s.decode('ascii')
-            except UnicodeError:
-                return s  
+from ftpserver import _to_decode, _to_unicode
+
 
 class abstracted_fs(object):
     """A class used to interact with the file system, providing a high
@@ -97,8 +73,8 @@ class abstracted_fs(object):
                     cr.execute("SELECT 1 FROM pg_class WHERE relkind = 'r' AND relname = 'ir_module_module'")
                     if not cr.fetchone():
                         continue
-    
-                    cr.execute("SELECT id FROM ir_module_module WHERE name = 'document_ftp' AND state='installed' ")
+
+                    cr.execute("SELECT id FROM ir_module_module WHERE name = 'document_ftp' AND state IN ('installed', 'to install', 'to upgrade') ")
                     res = cr.fetchone()
                     if res and len(res):
                         self.db_name_list.append(db_name)
@@ -108,8 +84,6 @@ class abstracted_fs(object):
             finally:
                 if cr is not None:
                     cr.close()
-                #if db is not None:
-                #    pooler.close_db(db_name)        
         return self.db_name_list
 
     def ftpnorm(self, ftppath):
@@ -138,7 +112,7 @@ class abstracted_fs(object):
     def ftp2fs(self, path_orig, data):
         raise DeprecationWarning()
 
-    def fs2ftp(self, node):        
+    def fs2ftp(self, node):
         """ Return the string path of a node, in ftp form
         """
         res='/'
@@ -146,7 +120,7 @@ class abstracted_fs(object):
             paths = node.full_path()
             res = '/' + node.context.dbname + '/' +  \
                 _to_decode(os.path.join(*paths))
-            
+
         return res
 
     def validpath(self, path):
@@ -162,7 +136,7 @@ class abstracted_fs(object):
         """ Create a children file-node under node, open it
             @return open node_descriptor of the created node
         """
-        objname = _to_unicode(objname) 
+        objname = _to_unicode(objname)
         cr , node, rem = datacr
         try:
             child = node.child(cr, objname)
@@ -171,17 +145,24 @@ class abstracted_fs(object):
                     raise OSError(1, 'Operation not permited.')
 
                 ret = child.open_data(cr, mode)
+                cr.commit()
+                assert ret, "Cannot create descriptor for %r: %r" % (child, ret)
                 return ret
         except EnvironmentError:
             raise
-        except Exception,e:
+        except Exception:
             self._log.exception('Cannot locate item %s at node %s', objname, repr(node))
             pass
 
         try:
             child = node.create_child(cr, objname, data=None)
-            return child.open_data(cr, mode)
-        except Exception,e:
+            ret = child.open_data(cr, mode)
+            assert ret, "cannot create descriptor for %r" % child
+            cr.commit()
+            return ret
+        except EnvironmentError:
+            raise
+        except Exception:
             self._log.exception('Cannot create item %s at node %s', objname, repr(node))
             raise OSError(1, 'Operation not permited.')
 
@@ -190,7 +171,11 @@ class abstracted_fs(object):
             raise OSError(1, 'Operation not permited.')
         # Reading operation
         cr, node, rem = datacr
-        res = node.open_data(cr, mode)
+        try:
+            res = node.open_data(cr, mode)
+            cr.commit()
+        except TypeError:
+            raise IOError(errno.EINVAL, "No data")
         return res
 
     # ok, but need test more
@@ -200,12 +185,11 @@ class abstracted_fs(object):
         name.  Unlike mkstemp it returns an object with a file-like
         interface.
         """
-        raise NotImplementedError
+        raise NotImplementedError # TODO
 
         text = not 'b' in mode
         # for unique file , maintain version if duplicate file
         if dir:
-           # TODO
             cr = dir.cr
             uid = dir.uid
             pool = pooler.get_pool(node.context.dbname)
@@ -215,7 +199,6 @@ class abstracted_fs(object):
             if len(res):
                 pre = prefix.split('.')
                 prefix=pre[0] + '.v'+str(len(res))+'.'+pre[1]
-            #prefix = prefix + '.'
         return self.create(dir,suffix+prefix,text)
 
 
@@ -225,7 +208,7 @@ class abstracted_fs(object):
         if (not datacr) or datacr == (None, None, None):
             self.cwd = '/'
             self.cwd_node = None
-            return None        
+            return None
         if not datacr[1]:
             raise OSError(1, 'Operation not permitted')
         if datacr[1].type not in  ('collection','database'):
@@ -240,13 +223,13 @@ class abstracted_fs(object):
         cr, node, rem = datacr or (None, None, None)
         if not node:
             raise OSError(1, 'Operation not permited.')
-        
+
         try:
             basename =_to_unicode(basename)
             cdir = node.create_child_collection(cr, basename)
             self._log.debug("Created child dir: %r", cdir)
             cr.commit()
-        except Exception,e:
+        except Exception:
             self._log.exception('Cannot create dir "%s" at node %s', basename, repr(node))
             raise OSError(1, 'Operation not permited.')
 
@@ -257,15 +240,15 @@ class abstracted_fs(object):
 
     def get_cr(self, pathname):
         raise DeprecationWarning()
-    
+
     def get_crdata(self, line, mode='file'):
         """ Get database cursor, node and remainder data, for commands
-        
+
         This is the helper function that will prepare the arguments for
         any of the subsequent commands.
         It returns a tuple in the form of:
         @code        ( cr, node, rem_path=None )
-        
+
         @param line An absolute or relative ftp path, as passed to the cmd.
         @param mode A word describing the mode of operation, so that this
                     function behaves properly in the different commands.
@@ -275,10 +258,10 @@ class abstracted_fs(object):
             if not os.path.isabs(path):
                 path = os.path.join(self.root, path)
 
-            if path == '/' and mode in ('list', 'cwd'):
-                return (None, None, None )
+        if path == '/' and mode in ('list', 'cwd'):
+            return (None, None, None )
 
-        path = os.path.normpath(path) # again, for '/db/../ss'
+        path = _to_unicode(os.path.normpath(path)) # again, for '/db/../ss'
         if path == '.': path = ''
 
         if os.path.isabs(path) and self.cwd_node is not None \
@@ -299,10 +282,14 @@ class abstracted_fs(object):
 
         if os.path.isabs(path):
             # we have to start from root, again
-            p_parts = p_parts[1:]
+            while p_parts and p_parts[0] == '':
+                p_parts = p_parts[1:]
+            # self._log.debug("Path parts: %r ", p_parts)
+            if not p_parts:
+                raise IOError(errno.EPERM, 'Cannot perform operation at root dir')
             dbname = p_parts[0]
             if dbname not in self.db_list():
-                return None
+                raise IOError(errno.ENOENT,'Invalid database path: %s' % dbname)
             try:
                 db = pooler.get_db(dbname)
             except Exception:
@@ -339,15 +326,15 @@ class abstracted_fs(object):
         assert node
         db = pooler.get_db(node.context.dbname)
         return db.cursor(), node.context.uid
-        
+
     def get_node_cr(self, node):
         """ Get the cursor for the database of a node
-        
-        The cursor is the only thing that a node will not store 
+
+        The cursor is the only thing that a node will not store
         persistenly, so we have to obtain a new one for each call.
         """
         return self.get_node_cr_uid(node)[0]
-        
+
     def listdir(self, datacr):
         """List the content of a directory."""
         class false_node(object):
@@ -358,7 +345,7 @@ class abstracted_fs(object):
             uuser = 'root'
             ugroup = 'root'
             type = 'database'
-            
+
             def __init__(self, db):
                 self.path = db
 
@@ -378,7 +365,6 @@ class abstracted_fs(object):
         """Remove the specified directory."""
         cr, node, rem = datacr
         assert node
-        cr = self.get_node_cr(node)
         node.rmcol(cr)
         cr.commit()
 
@@ -400,7 +386,7 @@ class abstracted_fs(object):
     def rename(self, src, datacr):
         """ Renaming operation, the effect depends on the src:
             * A file: read, create and remove
-            * A directory: change the parent and reassign childs to ressource
+            * A directory: change the parent and reassign children to ressource
         """
         cr = datacr[0]
         try:
@@ -409,8 +395,10 @@ class abstracted_fs(object):
             # API shouldn't wait for us to write the object
             assert (ret is True) or (ret is False)
             cr.commit()
-        except Exception,err:
-            self._log.exception('Cannot rename "%s" to "%s" at "%s"', src, dst_basename, dst_basedir)
+        except EnvironmentError:
+            raise
+        except Exception:
+            self._log.exception('Cannot rename "%s" to "%s" at "%s"', src, datacr[2], datacr[1])
             raise OSError(1,'Operation not permited.')
 
     def stat(self, node):
@@ -440,16 +428,16 @@ class abstracted_fs(object):
     def getsize(self, datacr):
         """Return the size of the specified file in bytes."""
         if not (datacr and datacr[1]):
-            return 0L
+            raise IOError(errno.ENOENT, "No such file or directory")
         if datacr[1].type in ('file', 'content'):
-            return datacr[1].content_length or 0L
+            return datacr[1].get_data_len(datacr[0]) or 0L
         return 0L
 
     # Ok
     def getmtime(self, datacr):
         """Return the last modified time as a number of seconds since
         the epoch."""
-        
+
         node = datacr[1]
         if node.write_date or node.create_date:
             dt = (node.write_date or node.create_date)[:19]
@@ -495,12 +483,11 @@ class abstracted_fs(object):
     def get_list_dir(self, datacr):
         """"Return an iterator object that yields a directory listing
         in a form suitable for LIST command.
-        """        
+        """
         if not datacr:
             return None
         elif self.isdir(datacr[1]):
             listing = self.listdir(datacr)
-            #listing.sort()
             return self.format_list(datacr[0], datacr[1], listing)
         # if path is a file or a symlink we return information about it
         elif self.isfile(datacr[1]):
@@ -554,8 +541,8 @@ class abstracted_fs(object):
             perms = filemode(node.unixperms)  # permissions
             nlinks = 1
             size = node.content_length or 0L
-            uname = node.uuser
-            gname = node.ugroup
+            uname = _to_decode(node.uuser)
+            gname = _to_decode(node.ugroup)
             # stat.st_mtime could fail (-1) if last mtime is too old
             # in which case we return the local time as last mtime
             try:
@@ -568,11 +555,11 @@ class abstracted_fs(object):
                 mtime = mname+' '+time.strftime("%d %H:%M", st_mtime)
             except ValueError:
                 mname=_get_month_name(time.strftime("%m"))
-                mtime = mname+' '+time.strftime("%d %H:%M")            
+                mtime = mname+' '+time.strftime("%d %H:%M")
             fpath = node.path
             if isinstance(fpath, (list, tuple)):
                 fpath = fpath[-1]
-            # formatting is matched with proftpd ls output            
+            # formatting is matched with proftpd ls output
             path=_to_decode(fpath)
             yield "%s %3s %-8s %-8s %8s %s %s\r\n" %(perms, nlinks, uname, gname,
                                                      size, mtime, path)
@@ -615,7 +602,7 @@ class abstracted_fs(object):
             # type + perm
             if self.isdir(node):
                 if 'type' in facts:
-                    type = 'type=dir;'                    
+                    type = 'type=dir;'
                 if 'perm' in facts:
                     perm = 'perm=%s;' %permdir
             else:
@@ -652,9 +639,9 @@ class abstracted_fs(object):
             if 'unix.mode' in facts:
                 mode = 'unix.mode=%s;' %oct(node.unixperms & 0777)
             if 'unix.uid' in facts:
-                uid = 'unix.uid=%s;' % node.uuser
+                uid = 'unix.uid=%s;' % _to_decode(node.uuser)
             if 'unix.gid' in facts:
-                gid = 'unix.gid=%s;' % node.ugroup
+                gid = 'unix.gid=%s;' % _to_decode(node.ugroup)
             # We provide unique fact (see RFC-3659, chapter 7.5.2) on
             # posix platforms only; we get it by mixing st_dev and
             # st_ino values which should be enough for granting an