diff options
Diffstat (limited to 'addons/document_ftp/ftpserver/abstracted_fs.py')
| -rw-r--r-- | addons/document_ftp/ftpserver/abstracted_fs.py | 665 |
1 files changed, 0 insertions, 665 deletions
diff --git a/addons/document_ftp/ftpserver/abstracted_fs.py b/addons/document_ftp/ftpserver/abstracted_fs.py deleted file mode 100644 index 514ba10f2f6..00000000000 --- a/addons/document_ftp/ftpserver/abstracted_fs.py +++ /dev/null @@ -1,665 +0,0 @@ -# -*- encoding: utf-8 -*- - -import os -import time -from tarfile import filemode -import logging -import errno - -import glob -import fnmatch - -import openerp -from openerp import sql_db -import openerp.service -from openerp.service import security -from openerp.osv import osv - -from openerp.addons.document.document import get_node_context - -def _get_month_name(month): - month=int(month) - if month==1:return 'Jan' - elif month==2:return 'Feb' - elif month==3:return 'Mar' - elif month==4:return 'Apr' - elif month==5:return 'May' - elif month==6:return 'Jun' - elif month==7:return 'Jul' - elif month==8:return 'Aug' - elif month==9:return 'Sep' - elif month==10:return 'Oct' - elif month==11:return 'Nov' - elif month==12:return 'Dec' - -from ftpserver import _to_decode, _to_unicode - - -class abstracted_fs(object): - """A class used to interact with the file system, providing a high - level, cross-platform interface compatible with both Windows and - UNIX style filesystems. - - It provides some utility methods and some wraps around operations - involved in file creation and file system operations like moving - files or removing directories. - - Instance attributes: - - (str) root: the user home directory. - - (str) cwd: the current working directory. - - (str) rnfr: source file to be renamed. - - """ - - def __init__(self): - self.root = None - self.cwd = '/' - self.cwd_node = None - self.rnfr = None - self._log = logging.getLogger(__name__) - - # Ok - def db_list(self): - """Get the list of available databases, with FTPd support - """ - s = openerp.service.db - result = s.exp_list(document=True) - self.db_name_list = [] - for db_name in result: - db, cr = None, None - try: - try: - db = sql_db.db_connect(db_name) - cr = db.cursor() - 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 IN ('installed', 'to install', 'to upgrade') ") - res = cr.fetchone() - if res and len(res): - self.db_name_list.append(db_name) - cr.commit() - except Exception: - self._log.warning('Cannot use db "%s".', db_name) - finally: - if cr is not None: - cr.close() - return self.db_name_list - - def ftpnorm(self, ftppath): - """Normalize a "virtual" ftp pathname (tipically the raw string - coming from client). - - Pathname returned is relative!. - """ - p = os.path.normpath(ftppath) - # normalize string in a standard web-path notation having '/' - # as separator. xrg: is that really in the spec? - p = p.replace("\\", "/") - # os.path.normpath supports UNC paths (e.g. "//a/b/c") but we - # don't need them. In case we get an UNC path we collapse - # redundant separators appearing at the beginning of the string - while p[:2] == '//': - p = p[1:] - if p == '.': - return '' - return p - - def get_cwd(self): - """ return the cwd, decoded in utf""" - return _to_decode(self.cwd) - - def ftp2fs(self, path_orig, data): - raise DeprecationWarning() - - def fs2ftp(self, node): - """ Return the string path of a node, in ftp form - """ - res='/' - if node: - paths = node.full_path() - res = '/' + node.context.dbname + '/' + \ - _to_decode(os.path.join(*paths)) - - return res - - def validpath(self, path): - """Check whether the path belongs to user's home directory. - Expected argument is a datacr tuple - """ - # TODO: are we called for "/" ? - return isinstance(path, tuple) and path[1] and True or False - - # --- Wrapper methods around open() and tempfile.mkstemp - - def create(self, datacr, objname, mode): - """ Create a children file-node under node, open it - @return open node_descriptor of the created node - """ - objname = _to_unicode(objname) - cr , node, rem = datacr - try: - child = node.child(cr, objname) - if child: - if child.type not in ('file','content'): - raise OSError(1, 'Operation is not permitted.') - - 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: - self._log.exception('Cannot locate item %s at node %s.', objname, repr(node)) - pass - - try: - child = node.create_child(cr, objname, data=None) - 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 is not permitted.') - - def open(self, datacr, mode): - if not (datacr and datacr[1]): - raise OSError(1, 'Operation is not permitted.') - # Reading operation - cr, node, rem = datacr - try: - res = node.open_data(cr, mode) - cr.commit() - except TypeError: - raise IOError(errno.EINVAL, "No data.") - return res - - # ok, but need test more - - def mkstemp(self, suffix='', prefix='', dir=None, mode='wb'): - """A wrap around tempfile.mkstemp creating a file with a unique - name. Unlike mkstemp it returns an object with a file-like - interface. - """ - raise NotImplementedError # TODO - - text = not 'b' in mode - # for unique file , maintain version if duplicate file - if dir: - cr = dir.cr - uid = dir.uid - pool = openerp.registry(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)]) - if len(res): - pre = prefix.split('.') - prefix=pre[0] + '.v'+str(len(res))+'.'+pre[1] - return self.create(dir,suffix+prefix,text) - - - - # Ok - def chdir(self, datacr): - if (not datacr) or datacr == (None, None, None): - self.cwd = '/' - self.cwd_node = None - return None - if not datacr[1]: - raise OSError(1, 'Operation is not permitted.') - if datacr[1].type not in ('collection','database'): - raise OSError(2, 'Path is not a directory.') - self.cwd = '/'+datacr[1].context.dbname + '/' - self.cwd += '/'.join(datacr[1].full_path()) - self.cwd_node = datacr[1] - - # Ok - def mkdir(self, datacr, basename): - """Create the specified directory.""" - cr, node, rem = datacr or (None, None, None) - if not node: - raise OSError(1, 'Operation is not permitted.') - - try: - basename =_to_unicode(basename) - cdir = node.create_child_collection(cr, basename) - self._log.debug("Created child dir: %r", cdir) - cr.commit() - except Exception: - self._log.exception('Cannot create dir "%s" at node %s.', basename, repr(node)) - raise OSError(1, 'Operation is not permitted.') - - def close_cr(self, data): - if data and data[0]: - data[0].close() - return True - - 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. - """ - path = self.ftpnorm(line) - if self.cwd_node is None: - 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 == '..': path = self.cwd + '/..' - 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 \ - and path.startswith(self.cwd): - # make relative, so that cwd_node is used again - path = path[len(self.cwd):] - if path.startswith('/'): - path = path[1:] - - p_parts = path.split(os.sep) - - assert '..' not in p_parts - - rem_path = None - if mode in ('create',): - rem_path = p_parts[-1] - p_parts = p_parts[:-1] - - if os.path.isabs(path): - # we have to start from root, again - 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 directory.') - dbname = p_parts[0] - if dbname not in self.db_list(): - raise IOError(errno.ENOENT,'Invalid database path: %s.' % dbname) - try: - db = openerp.registry(dbname).db - except Exception: - raise OSError(1, 'Database cannot be used.') - cr = db.cursor() - try: - uid = security.login(dbname, self.username, self.password) - except Exception: - cr.close() - raise - if not uid: - cr.close() - raise OSError(2, 'Authentification required.') - n = get_node_context(cr, uid, {}) - node = n.get_uri(cr, p_parts[1:]) - return (cr, node, rem_path) - else: - # we never reach here if cwd_node is not set - if p_parts and p_parts[-1] == '': - p_parts = p_parts[:-1] - cr, uid = self.get_node_cr_uid(self.cwd_node) - if p_parts: - node = self.cwd_node.get_uri(cr, p_parts) - else: - node = self.cwd_node - if node is False and mode not in ('???'): - cr.close() - raise IOError(errno.ENOENT, 'Path does not exist.') - return (cr, node, rem_path) - - def get_node_cr_uid(self, node): - """ Get cr, uid, pool from a node - """ - assert node - db = openerp.registry(node.context.dbname).db - 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 - 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): - write_date = 0.0 - create_date = 0.0 - unixperms = 040550 - content_length = 0L - uuser = 'root' - ugroup = 'root' - type = 'database' - - def __init__(self, db): - self.path = db - - if datacr[1] is None: - result = [] - for db in self.db_list(): - try: - result.append(false_node(db)) - except osv.except_osv: - pass - return result - cr, node, rem = datacr - res = node.children(cr) - return res - - def rmdir(self, datacr): - """Remove the specified directory.""" - cr, node, rem = datacr - assert node - node.rmcol(cr) - cr.commit() - - def remove(self, datacr): - assert datacr[1] - if datacr[1].type == 'collection': - return self.rmdir(datacr) - elif datacr[1].type == 'file': - return self.rmfile(datacr) - raise OSError(1, 'Operation is not permitted.') - - def rmfile(self, datacr): - """Remove the specified file.""" - assert datacr[1] - cr = datacr[0] - datacr[1].rm(cr) - cr.commit() - - 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 children to ressource - """ - cr = datacr[0] - try: - nname = _to_unicode(datacr[2]) - ret = src.move_to(cr, datacr[1], new_name=nname) - # API shouldn't wait for us to write the object - assert (ret is True) or (ret is False) - cr.commit() - except EnvironmentError: - raise - except Exception: - self._log.exception('Cannot rename "%s" to "%s" at "%s".', src, datacr[2], datacr[1]) - raise OSError(1,'Operation is not permitted.') - - def stat(self, node): - raise NotImplementedError() - - # --- Wrapper methods around os.path.* - - # Ok - def isfile(self, node): - if node and (node.type in ('file','content')): - return True - return False - - # Ok - def islink(self, path): - """Return True if path is a symbolic link.""" - return False - - def isdir(self, node): - """Return True if path is a directory.""" - if node is None: - return True - if node and (node.type in ('collection','database')): - return True - return False - - def getsize(self, datacr): - """Return the size of the specified file in bytes.""" - if not (datacr and datacr[1]): - raise IOError(errno.ENOENT, "No such file or directory.") - if datacr[1].type in ('file', 'content'): - 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] - result = time.mktime(time.strptime(dt, '%Y-%m-%d %H:%M:%S')) - else: - result = time.mktime(time.localtime()) - return result - - # Ok - def realpath(self, path): - """Return the canonical version of path eliminating any - symbolic links encountered in the path (if they are - supported by the operating system). - """ - return path - - # Ok - def lexists(self, path): - """Return True if path refers to an existing path, including - a broken or circular symbolic link. - """ - raise DeprecationWarning() - return path and True or False - - exists = lexists - - # Ok, can be improved - 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.path[0] != '.', names) - return fnmatch.filter(names, pattern) - - # --- Listing utilities - - # note: the following operations are no more blocking - - 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) - 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]): - par = datacr[1].parent - return self.format_list(datacr[0], par, [datacr[1]]) - - def get_stat_dir(self, rawline, datacr): - """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, datacr)) - 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, datacr) - listing = self.glob1(basedir, basename) - if listing: - listing.sort() - return self.format_list(basedir, listing) - - def format_list(self, cr, parent_node, listing, ignore_err=True): - """Return an iterator object that yields the entries of given - directory emulating the "/bin/ls -lA" UNIX command output. - - - (str) basedir: the parent directory node. Can be None - - (list) listing: a list of nodes - - (bool) ignore_err: when False raise exception if os.lstat() - call fails. - - On platforms which do not support the pwd and grp modules (such - as Windows), ownership is printed as "owner" and "group" as a - default, and number of hard links is always "1". On UNIX - systems, the actual owner, group, and number of links are - printed. - - This is how output appears to client: - - -rw-rw-rw- 1 owner group 7045120 Sep 02 3:47 music.mp3 - drwxrwxrwx 1 owner group 0 Aug 31 18:50 e-books - -rw-rw-rw- 1 owner group 380 Sep 02 3:40 module.py - """ - for node in listing: - perms = filemode(node.unixperms) # permissions - nlinks = 1 - size = node.content_length or 0L - 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: - st_mtime = node.write_date or 0.0 - if isinstance(st_mtime, basestring): - st_mtime = time.strptime(st_mtime, '%Y-%m-%d %H:%M:%S') - elif isinstance(st_mtime, float): - st_mtime = time.localtime(st_mtime) - mname=_get_month_name(time.strftime("%m", st_mtime )) - 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") - fpath = node.path - if isinstance(fpath, (list, tuple)): - fpath = fpath[-1] - # 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) - - # Ok - def format_mlsx(self, cr, basedir, listing, perms, facts, ignore_err=True): - """Return an iterator object that yields the entries of a given - directory or of a single file in a form suitable with MLSD and - MLST commands. - - Every entry includes a list of "facts" referring the listed - element. See RFC-3659, chapter 7, to see what every single - fact stands for. - - - (str) basedir: the absolute dirname. - - (list) listing: the names of the entries in basedir - - (str) perms: the string referencing the user permissions. - - (str) facts: the list of "facts" to be returned. - - (bool) ignore_err: when False raise exception if os.stat() - call fails. - - Note that "facts" returned may change depending on the platform - and on what user specified by using the OPTS command. - - This is how output could appear to the client issuing - a MLSD request: - - type=file;size=156;perm=r;modify=20071029155301;unique=801cd2; music.mp3 - type=dir;size=0;perm=el;modify=20071127230206;unique=801e33; ebooks - type=file;size=211;perm=r;modify=20071103093626;unique=801e32; module.py - """ - permdir = ''.join([x for x in perms if x not in 'arw']) - permfile = ''.join([x for x in perms if x not in 'celmp']) - if ('w' in perms) or ('a' in perms) or ('f' in perms): - permdir += 'c' - if 'd' in perms: - permdir += 'p' - type = size = perm = modify = create = unique = mode = uid = gid = "" - for node in listing: - # type + perm - if self.isdir(node): - if 'type' in facts: - type = 'type=dir;' - if 'perm' in facts: - perm = 'perm=%s;' %permdir - else: - if 'type' in facts: - type = 'type=file;' - if 'perm' in facts: - perm = 'perm=%s;' %permfile - if 'size' in facts: - size = 'size=%s;' % (node.content_length or 0L) - # last modification time - if 'modify' in facts: - try: - st_mtime = node.write_date or 0.0 - if isinstance(st_mtime, basestring): - st_mtime = time.strptime(st_mtime, '%Y-%m-%d %H:%M:%S') - elif isinstance(st_mtime, float): - st_mtime = time.localtime(st_mtime) - modify = 'modify=%s;' %time.strftime("%Y%m%d%H%M%S", st_mtime) - 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 - try: - st_ctime = node.create_date or 0.0 - if isinstance(st_ctime, basestring): - st_ctime = time.strptime(st_ctime, '%Y-%m-%d %H:%M:%S') - elif isinstance(st_mtime, float): - st_ctime = time.localtime(st_ctime) - create = 'create=%s;' %time.strftime("%Y%m%d%H%M%S",st_ctime) - except ValueError: - create = "" - # UNIX only - if 'unix.mode' in facts: - mode = 'unix.mode=%s;' %oct(node.unixperms & 0777) - if 'unix.uid' in facts: - uid = 'unix.uid=%s;' % _to_decode(node.uuser) - if 'unix.gid' in facts: - 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 - # uniqueness for the file listed. - # The same approach is used by pure-ftpd. - # Implementors who want to provide unique fact on other - # platforms should use some platform-specific method (e.g. - # on Windows NTFS filesystems MTF records could be used). - # if 'unique' in facts: todo - # unique = "unique=%x%x;" %(st.st_dev, st.st_ino) - path = node.path - if isinstance (path, (list, tuple)): - path = path[-1] - path=_to_decode(path) - yield "%s%s%s%s%s%s%s%s%s %s\r\n" %(type, size, perm, modify, create, - mode, uid, gid, unique, path) - -# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: - |
