From 8dcb2a1fbfccb364fdc80890ae59976d09295cb6 Mon Sep 17 00:00:00 2001 From: Carlos Almeida Date: Sat, 17 Oct 2015 00:15:17 -0300 Subject: [PATCH 1/8] [MIG] Old OpenERP document_ftp module to 8.0 migration --- document_ftp/README.rst | 6 + document_ftp/__init__.py | 29 + document_ftp/__openerp__.py | 48 + document_ftp/ftpserver/__init__.py | 74 + document_ftp/ftpserver/abstracted_fs.py | 665 ++++ document_ftp/ftpserver/authorizer.py | 70 + document_ftp/ftpserver/ftpserver.py | 3046 +++++++++++++++++ document_ftp/i18n/ar.po | 130 + document_ftp/i18n/bg.po | 131 + document_ftp/i18n/ca.po | 131 + document_ftp/i18n/cs.po | 130 + document_ftp/i18n/da.po | 124 + document_ftp/i18n/de.po | 131 + document_ftp/i18n/document_ftp.pot | 117 + document_ftp/i18n/el.po | 124 + document_ftp/i18n/en_GB.po | 130 + document_ftp/i18n/es.po | 131 + document_ftp/i18n/es_CR.po | 131 + document_ftp/i18n/es_EC.po | 129 + document_ftp/i18n/es_MX.po | 148 + document_ftp/i18n/es_PY.po | 126 + document_ftp/i18n/es_VE.po | 148 + document_ftp/i18n/et.po | 124 + document_ftp/i18n/fi.po | 126 + document_ftp/i18n/fr.po | 132 + document_ftp/i18n/gl.po | 131 + document_ftp/i18n/hr.po | 131 + document_ftp/i18n/hu.po | 131 + document_ftp/i18n/it.po | 131 + document_ftp/i18n/ja.po | 127 + document_ftp/i18n/mk.po | 131 + document_ftp/i18n/mn.po | 129 + document_ftp/i18n/nb.po | 131 + document_ftp/i18n/nl.po | 131 + document_ftp/i18n/pl.po | 130 + document_ftp/i18n/pt.po | 131 + document_ftp/i18n/pt_BR.po | 130 + document_ftp/i18n/ro.po | 131 + document_ftp/i18n/ru.po | 130 + document_ftp/i18n/sk.po | 124 + document_ftp/i18n/sl.po | 130 + document_ftp/i18n/sr.po | 126 + document_ftp/i18n/sr@latin.po | 131 + document_ftp/i18n/sv.po | 131 + document_ftp/i18n/tr.po | 130 + document_ftp/i18n/vi.po | 124 + document_ftp/i18n/zh_CN.po | 126 + document_ftp/i18n/zh_TW.po | 126 + document_ftp/images/1_configure_ftp.jpeg | Bin 0 -> 79350 bytes document_ftp/images/2_document_browse.jpeg | Bin 0 -> 36766 bytes document_ftp/images/3_document_ftp.jpeg | Bin 0 -> 109474 bytes document_ftp/res_config.py | 40 + document_ftp/res_config_view.xml | 17 + document_ftp/security/ir.model.access.csv | 1 + document_ftp/test/document_ftp_test.yml | 87 + document_ftp/test/document_ftp_test2.yml | 289 ++ document_ftp/test/document_ftp_test3.yml | 92 + document_ftp/test/document_ftp_test4.yml | 187 + document_ftp/test/document_ftp_test5.yml | 30 + document_ftp/test_easyftp.py | 71 + document_ftp/wizard/__init__.py | 26 + document_ftp/wizard/ftp_browse.py | 65 + document_ftp/wizard/ftp_browse_view.xml | 39 + document_ftp/wizard/ftp_configuration.py | 53 + .../wizard/ftp_configuration_view.xml | 44 + 65 files changed, 10295 insertions(+) create mode 100644 document_ftp/README.rst create mode 100644 document_ftp/__init__.py create mode 100644 document_ftp/__openerp__.py create mode 100644 document_ftp/ftpserver/__init__.py create mode 100644 document_ftp/ftpserver/abstracted_fs.py create mode 100644 document_ftp/ftpserver/authorizer.py create mode 100755 document_ftp/ftpserver/ftpserver.py create mode 100644 document_ftp/i18n/ar.po create mode 100644 document_ftp/i18n/bg.po create mode 100644 document_ftp/i18n/ca.po create mode 100644 document_ftp/i18n/cs.po create mode 100644 document_ftp/i18n/da.po create mode 100644 document_ftp/i18n/de.po create mode 100644 document_ftp/i18n/document_ftp.pot create mode 100644 document_ftp/i18n/el.po create mode 100644 document_ftp/i18n/en_GB.po create mode 100644 document_ftp/i18n/es.po create mode 100644 document_ftp/i18n/es_CR.po create mode 100644 document_ftp/i18n/es_EC.po create mode 100644 document_ftp/i18n/es_MX.po create mode 100644 document_ftp/i18n/es_PY.po create mode 100644 document_ftp/i18n/es_VE.po create mode 100644 document_ftp/i18n/et.po create mode 100644 document_ftp/i18n/fi.po create mode 100644 document_ftp/i18n/fr.po create mode 100644 document_ftp/i18n/gl.po create mode 100644 document_ftp/i18n/hr.po create mode 100644 document_ftp/i18n/hu.po create mode 100644 document_ftp/i18n/it.po create mode 100644 document_ftp/i18n/ja.po create mode 100644 document_ftp/i18n/mk.po create mode 100644 document_ftp/i18n/mn.po create mode 100644 document_ftp/i18n/nb.po create mode 100644 document_ftp/i18n/nl.po create mode 100644 document_ftp/i18n/pl.po create mode 100644 document_ftp/i18n/pt.po create mode 100644 document_ftp/i18n/pt_BR.po create mode 100644 document_ftp/i18n/ro.po create mode 100644 document_ftp/i18n/ru.po create mode 100644 document_ftp/i18n/sk.po create mode 100644 document_ftp/i18n/sl.po create mode 100644 document_ftp/i18n/sr.po create mode 100644 document_ftp/i18n/sr@latin.po create mode 100644 document_ftp/i18n/sv.po create mode 100644 document_ftp/i18n/tr.po create mode 100644 document_ftp/i18n/vi.po create mode 100644 document_ftp/i18n/zh_CN.po create mode 100644 document_ftp/i18n/zh_TW.po create mode 100644 document_ftp/images/1_configure_ftp.jpeg create mode 100644 document_ftp/images/2_document_browse.jpeg create mode 100644 document_ftp/images/3_document_ftp.jpeg create mode 100644 document_ftp/res_config.py create mode 100644 document_ftp/res_config_view.xml create mode 100644 document_ftp/security/ir.model.access.csv create mode 100644 document_ftp/test/document_ftp_test.yml create mode 100644 document_ftp/test/document_ftp_test2.yml create mode 100644 document_ftp/test/document_ftp_test3.yml create mode 100644 document_ftp/test/document_ftp_test4.yml create mode 100644 document_ftp/test/document_ftp_test5.yml create mode 100644 document_ftp/test_easyftp.py create mode 100644 document_ftp/wizard/__init__.py create mode 100644 document_ftp/wizard/ftp_browse.py create mode 100644 document_ftp/wizard/ftp_browse_view.xml create mode 100644 document_ftp/wizard/ftp_configuration.py create mode 100644 document_ftp/wizard/ftp_configuration_view.xml diff --git a/document_ftp/README.rst b/document_ftp/README.rst new file mode 100644 index 00000000..da59afbf --- /dev/null +++ b/document_ftp/README.rst @@ -0,0 +1,6 @@ +This is a support FTP Interface with document management system. +================================================================ + +With this module you would not only be able to access documents through OpenERP +but you would also be able to connect with them through the file system using the +FTP client. diff --git a/document_ftp/__init__.py b/document_ftp/__init__.py new file mode 100644 index 00000000..098e6cb7 --- /dev/null +++ b/document_ftp/__init__.py @@ -0,0 +1,29 @@ +# -*- coding: utf-8 -*- +############################################################################## +# +# OpenERP, Open Source Management Solution +# Copyright (C) 2004-2010 Tiny SPRL (). +# +# ThinkOpen Solutions Brasil (). +# +# 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 . import ftpserver +from . import wizard +from . import res_config + +post_load = ftpserver.start_server + diff --git a/document_ftp/__openerp__.py b/document_ftp/__openerp__.py new file mode 100644 index 00000000..1bb322da --- /dev/null +++ b/document_ftp/__openerp__.py @@ -0,0 +1,48 @@ +# -*- coding: utf-8 -*- +############################################################################## +# +# OpenERP, Open Source Management Solution +# Copyright (C) 2004-2010 Tiny SPRL (). + +# ThinkOpen Solutions Brasil (). +# +# 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': 'Shared Repositories (FTP)', + 'version': '8.0.0.0.1', + 'category': 'Knowledge Management', + 'author': 'OpenERP SA', + 'website': 'http://www.openerp.com', + 'depends': ['base', 'document'], + 'data': [ + 'wizard/ftp_configuration_view.xml', + 'wizard/ftp_browse_view.xml', + 'security/ir.model.access.csv', + 'res_config_view.xml', + ], + 'demo': [], + 'test': [ + 'test/document_ftp_test2.yml', + 'test/document_ftp_test4.yml', + ], + 'installable': True, + 'auto_install': False, + 'images': ['images/1_configure_ftp.jpeg', 'images/2_document_browse.jpeg', 'images/3_document_ftp.jpeg'], + 'post_load': 'post_load', +} + diff --git a/document_ftp/ftpserver/__init__.py b/document_ftp/ftpserver/__init__.py new file mode 100644 index 00000000..2d55be57 --- /dev/null +++ b/document_ftp/ftpserver/__init__.py @@ -0,0 +1,74 @@ +# -*- coding: utf-8 -*- +############################################################################## +# +# OpenERP, Open Source Management Solution +# Copyright (C) 2004-2010 Tiny SPRL (). + +# ThinkOpen Solutions Brasil (). +# +# 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 threading +import logging + +import authorizer +import abstracted_fs +import ftpserver + +import openerp +from openerp.tools import config +_logger = logging.getLogger(__name__) +import socket + +def start_server(): + if openerp.multi_process: + _logger.info("FTP disabled in multiprocess mode") + return + ip_address = ([(s.connect(('8.8.8.8', 80)), s.getsockname()[0], s.close()) for s in [socket.socket(socket.AF_INET, socket.SOCK_DGRAM)]][0][1]) + if not ip_address: + ip_address = '127.0.0.1' + HOST = config.get('ftp_server_host', str(ip_address)) + PORT = int(config.get('ftp_server_port', '8021')) + PASSIVE_PORTS = None + pps = config.get('ftp_server_passive_ports', '').split(':') + if len(pps) == 2: + PASSIVE_PORTS = int(pps[0]), int(pps[1]) + + class ftp_server(threading.Thread): + + def run(self): + autho = authorizer.authorizer() + ftpserver.FTPHandler.authorizer = autho + ftpserver.max_cons = 300 + ftpserver.max_cons_per_ip = 50 + ftpserver.FTPHandler.abstracted_fs = abstracted_fs.abstracted_fs + if PASSIVE_PORTS: + ftpserver.FTPHandler.passive_ports = PASSIVE_PORTS + + ftpserver.log = lambda msg: _logger.info(msg) + ftpserver.logline = lambda msg: None + ftpserver.logerror = lambda msg: _logger.error(msg) + + ftpd = ftpserver.FTPServer((HOST, PORT), ftpserver.FTPHandler) + ftpd.serve_forever() + + if HOST.lower() == 'none': + _logger.info("\n Server FTP Not Started\n") + else: + _logger.info("\n Serving FTP on %s:%s\n" % (HOST, PORT)) + ds = ftp_server() + ds.daemon = True + ds.start() diff --git a/document_ftp/ftpserver/abstracted_fs.py b/document_ftp/ftpserver/abstracted_fs.py new file mode 100644 index 00000000..9d611f85 --- /dev/null +++ b/document_ftp/ftpserver/abstracted_fs.py @@ -0,0 +1,665 @@ +# -*- encoding: utf-8 -*- + +import os +import time +from tarfile import filemode +import logging +import errno +import openerp +import glob +import fnmatch + +from openerp import pooler, netsvc, sql_db +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 = netsvc.ExportService.getService('db') + # result = s.exp_list(document=True) + #======================================================================= + result = openerp.service.db.exp_list(True) + # yogesh + 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 = 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)]) + 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 = pooler.get_db(dbname) + 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 = 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 + 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) + diff --git a/document_ftp/ftpserver/authorizer.py b/document_ftp/ftpserver/authorizer.py new file mode 100644 index 00000000..5bcb21b6 --- /dev/null +++ b/document_ftp/ftpserver/authorizer.py @@ -0,0 +1,70 @@ +# -*- encoding: utf-8 -*- + +class authorizer: + read_perms = "elr" + write_perms = "adfmw" + + def __init__(self): + self.password = '' + + def validate_authentication(self, username, password): + """Return True if the supplied username and password match the + stored credentials.""" + self.password = password + return True + + def impersonate_user(self, username, password): + """Impersonate another user (noop). + + It is always called before accessing the filesystem. + By default it does nothing. The subclass overriding this + method is expected to provide a mechanism to change the + current user. + """ + + def terminate_impersonation(self): + """Terminate impersonation (noop). + + It is always called after having accessed the filesystem. + By default it does nothing. The subclass overriding this + method is expected to provide a mechanism to switch back + to the original user. + """ + + def has_user(self, username): + """Whether the username exists in the virtual users table.""" + if username == 'anonymous': + return False + return True + + def has_perm(self, username, perm, path=None): + """Whether the user has permission over path (an absolute + pathname of a file or a directory). + + Expected perm argument is one of the following letters: + "elradfmw". + """ + paths = path.split('/') + if not len(paths) > 2: + return True + db_name = paths[1] + db, pool = pooler.get_db_and_pool(db_name) + res = security.login(db_name, username, self.password) + return bool(res) + + def get_perms(self, username): + """Return current user permissions.""" + return 'elr' + + def get_home_dir(self, username): + """Return the user's home directory.""" + return '/' + + def get_msg_login(self, username): + """Return the user's login message.""" + return 'Welcome on OpenERP document management system.' + + def get_msg_quit(self, username): + """Return the user's quitting message.""" + return 'Bye.' + diff --git a/document_ftp/ftpserver/ftpserver.py b/document_ftp/ftpserver/ftpserver.py new file mode 100755 index 00000000..99d2e50a --- /dev/null +++ b/document_ftp/ftpserver/ftpserver.py @@ -0,0 +1,3046 @@ +#!/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 +# its documentation for any purpose and without fee is hereby +# granted, provided that the above copyright notice appear in all +# copies and that both that copyright notice and this permission +# notice appear in supporting documentation, and that the name of +# Giampaolo Rodola' not be used in advertising or publicity pertaining to +# distribution of the software without specific, written prior +# permission. +# +# Giampaolo Rodola' DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE, +# INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN +# NO EVENT Giampaolo Rodola' BE LIABLE FOR ANY SPECIAL, INDIRECT OR +# CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS +# OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, +# NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN +# CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +# ====================================================================== + + +"""pyftpdlib: RFC-959 asynchronous FTP server. + +pyftpdlib implements a fully functioning asynchronous FTP server as +defined in RFC-959. A hierarchy of classes outlined below implement +the backend functionality for the FTPd: + + [FTPServer] - the base class for the backend. + + [FTPHandler] - a class representing the server-protocol-interpreter + (server-PI, see RFC-959). Each time a new connection occurs + FTPServer will create a new FTPHandler instance to handle the + current PI session. + + [ActiveDTP], [PassiveDTP] - base classes for active/passive-DTP + backends. + + [DTPHandler] - this class handles processing of data transfer + operations (server-DTP, see RFC-959). + + [DummyAuthorizer] - an "authorizer" is a class handling FTPd + authentications and permissions. It is used inside FTPHandler class + to verify user passwords, to get user's home directory and to get + permissions when a filesystem read/write occurs. "DummyAuthorizer" + is the base authorizer class providing a platform independent + interface for managing virtual users. + + [AbstractedFS] - class used to interact with the file system, + providing a high level, cross-platform interface compatible + with both Windows and UNIX style filesystems. + + [AuthorizerError] - base class for authorizers exceptions. + + +pyftpdlib also provides 3 different logging streams through 3 functions +which can be overridden to allow for custom logging. + + [log] - the main logger that logs the most important messages for + the end user regarding the FTPd. + + [logline] - this function is used to log commands and responses + passing through the control FTP channel. + + [logerror] - log traceback outputs occurring in case of errors. + + +Usage example: + +>>> from pyftpdlib import ftpserver +>>> authorizer = ftpserver.DummyAuthorizer() +>>> authorizer.add_user('user', 'password', '/home/user', perm='elradfmw') +>>> authorizer.add_anonymous('/home/nobody') +>>> ftp_handler = ftpserver.FTPHandler +>>> ftp_handler.authorizer = authorizer +>>> address = ("127.0.0.1", 21) +>>> ftpd = ftpserver.FTPServer(address, ftp_handler) +>>> ftpd.serve_forever() +Serving FTP on 127.0.0.1:21 +[]127.0.0.1:2503 connected. +127.0.0.1:2503 ==> 220 Ready. +127.0.0.1:2503 <== USER anonymous +127.0.0.1:2503 ==> 331 Username ok, send password. +127.0.0.1:2503 <== PASS ****** +127.0.0.1:2503 ==> 230 Login successful. +[anonymous]@127.0.0.1:2503 User anonymous logged in. +127.0.0.1:2503 <== TYPE A +127.0.0.1:2503 ==> 200 Type set to: ASCII. +127.0.0.1:2503 <== PASV +127.0.0.1:2503 ==> 227 Entering passive mode (127,0,0,1,9,201). +127.0.0.1:2503 <== LIST +127.0.0.1:2503 ==> 150 File status okay. About to open data connection. +[anonymous]@127.0.0.1:2503 OK LIST "/". Transfer starting. +127.0.0.1:2503 ==> 226 Transfer complete. +[anonymous]@127.0.0.1:2503 Transfer complete. 706 bytes transmitted. +127.0.0.1:2503 <== QUIT +127.0.0.1:2503 ==> 221 Goodbye. +[anonymous]@127.0.0.1:2503 Disconnected. +""" + + +import asyncore +import asynchat +import socket +import os +import sys +import traceback +import errno +import time +import glob +import fnmatch +import tempfile +import warnings +import random +import stat +from collections import deque +from tarfile import filemode + +LOG_ACTIVE = True + +__all__ = ['proto_cmds', 'Error', 'log', 'logline', 'logerror', 'DummyAuthorizer', + 'FTPHandler', 'FTPServer', 'PassiveDTP', 'ActiveDTP', 'DTPHandler', + 'FileProducer', 'IteratorProducer', 'BufferedIteratorProducer', + 'AbstractedFS', ] + + +__pname__ = 'Python FTP server library (pyftpdlib)' +__ver__ = '0.4.0' +__date__ = '2008-05-16' +__author__ = "Giampaolo Rodola' " +__web__ = 'http://code.google.com/p/pyftpdlib/' + + +proto_cmds = { + '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).', + } + + +def _strerror(err): + """A wrap around os.strerror() which may be not available on all + platforms (e.g. pythonCE). + + - (instance) err: an EnvironmentError or derived class instance. + """ + if hasattr(os, 'strerror'): + return os.strerror(err.errno) + else: + return err.strerror + +def _to_unicode(s): + try: + return s.decode('utf-8') + except UnicodeError: + pass + try: + return s.decode('latin') + except UnicodeError: + pass + try: + return s.encode('ascii') + except UnicodeError: + return s + +def _to_decode(s): + try: + return s.encode('utf-8') + except UnicodeError: + pass + try: + return s.encode('latin') + except UnicodeError: + pass + try: + return s.decode('ascii') + except UnicodeError: + return s + +# --- library defined exceptions + +class Error(Exception): + """Base class for module exceptions.""" + +class AuthorizerError(Error): + """Base class for authorizer exceptions.""" + + +# --- loggers + +def log(msg): + """Log messages intended for the end user.""" + if LOG_ACTIVE: + print msg + +def logline(msg): + """Log commands and responses passing through the command channel.""" + if LOG_ACTIVE: + print msg + +def logerror(msg): + """Log traceback outputs occurring in case of errors.""" + sys.stderr.write(str(msg) + '\n') + sys.stderr.flush() + + +# --- authorizers + +class DummyAuthorizer: + """Basic "dummy" authorizer class, suitable for subclassing to + create your own custom authorizers. + + An "authorizer" is a class handling authentications and permissions + of the FTP server. It is used inside FTPHandler class for verifying + user's password, getting users home directory, checking user + permissions when a file read/write event occurs and changing user + before accessing the filesystem. + + DummyAuthorizer is the base authorizer, providing a platform + independent interface for managing "virtual" FTP users. System + dependent authorizers can by written by subclassing this base + class and overriding appropriate methods as necessary. + """ + + read_perms = "elr" + write_perms = "adfmw" + + def __init__(self): + self.user_table = {} + + def add_user(self, username, password, homedir, perm='elr', + msg_login="Login successful.", msg_quit="Goodbye."): + """Add a user to the virtual users table. + + AuthorizerError exceptions raised on error conditions such as + invalid permissions, missing home directory or duplicate usernames. + + Optional perm argument is a string referencing the user's + permissions explained below: + + Read permissions: + - "e" = change directory (CWD command) + - "l" = list files (LIST, NLST, MLSD commands) + - "r" = retrieve file from the server (RETR command) + + Write permissions: + - "a" = append data to an existing file (APPE command) + - "d" = delete file or directory (DELE, RMD commands) + - "f" = rename file or directory (RNFR, RNTO commands) + - "m" = create directory (MKD command) + - "w" = store a file to the server (STOR, STOU commands) + + Optional msg_login and msg_quit arguments can be specified to + provide customized response strings when user log-in and quit. + """ + 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) + 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 are assigned to anonymous user.", + RuntimeWarning) + break + dic = {'pwd': str(password), + 'home': homedir, + 'perm': perm, + 'msg_login': str(msg_login), + 'msg_quit': str(msg_quit) + } + self.user_table[username] = dic + + def add_anonymous(self, homedir, **kwargs): + """Add an anonymous user to the virtual users table. + + AuthorizerError exception raised on error conditions such as + invalid permissions, missing home directory, or duplicate + anonymous users. + + The keyword arguments in kwargs are the same expected by + add_user method: "perm", "msg_login" and "msg_quit". + + The optional "perm" keyword argument is a string defaulting to + "elr" referencing "read-only" anonymous user's permissions. + + Using write permission values ("adfmw") results in a + RuntimeWarning. + """ + DummyAuthorizer.add_user(self, 'anonymous', '', homedir, **kwargs) + + def remove_user(self, username): + """Remove a user from the virtual users table.""" + del self.user_table[username] + + def validate_authentication(self, username, password): + """Return True if the supplied username and password match the + stored credentials.""" + return self.user_table[username]['pwd'] == password + + def impersonate_user(self, username, password): + """Impersonate another user (noop). + + It is always called before accessing the filesystem. + By default it does nothing. The subclass overriding this + method is expected to provide a mechanism to change the + current user. + """ + + def terminate_impersonation(self): + """Terminate impersonation (noop). + + It is always called after having accessed the filesystem. + By default it does nothing. The subclass overriding this + method is expected to provide a mechanism to switch back + to the original user. + """ + + def has_user(self, username): + """Whether the username exists in the virtual users table.""" + return username in self.user_table + + def has_perm(self, username, perm, path=None): + """Whether the user has permission over path (an absolute + pathname of a file or a directory). + + Expected perm argument is one of the following letters: + "elradfmw". + """ + return perm in self.user_table[username]['perm'] + + def get_perms(self, username): + """Return current user permissions.""" + return self.user_table[username]['perm'] + + def get_home_dir(self, username): + """Return the user's home directory.""" + return self.user_table[username]['home'] + + def get_msg_login(self, username): + """Return the user's login message.""" + return self.user_table[username]['msg_login'] + + def get_msg_quit(self, username): + """Return the user's quitting message.""" + return self.user_table[username]['msg_quit'] + + +# --- DTP classes + +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. + """ + + def __init__(self, cmd_channel, extmode=False): + """Initialize the passive data server. + + - (instance) cmd_channel: the command channel class instance. + - (bool) extmode: wheter use extended passive mode response type. + """ + asyncore.dispatcher.__init__(self) + self.cmd_channel = cmd_channel + + ip = self.cmd_channel.getsockname()[0] + self.create_socket(self.cmd_channel.af, socket.SOCK_STREAM) + + 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) + while ports: + port = ports.pop(random.randint(0, len(ports) - 1)) + try: + self.bind((ip, port)) + except socket.error, why: + if why[0] == errno.EADDRINUSE: # port already in use + if ports: + continue + # If cannot use one of the ports in the configured + # range we'll use a kernel-assigned port, and log + # a message reporting the issue. + # By using 0 as port number value we let kernel + # choose a free unprivileged random port. + else: + self.bind((ip, 0)) + self.cmd_channel.log( + "Can't find a valid passive port in the " + "configured range. A random kernel-assigned " + "port will be used." + ) + else: + raise + else: + break + self.listen(5) + port = self.socket.getsockname()[1] + if not extmode: + if self.cmd_channel.masquerade_address: + ip = self.cmd_channel.masquerade_address + # The format of 227 response in not standardized. + # This is the most expected: + self.cmd_channel.respond('227 Entering passive mode (%s,%d,%d).' % ( + ip.replace('.', ','), port / 256, port % 256)) + else: + self.cmd_channel.respond('229 Entering extended passive mode ' + '(|||%d|).' % port) + + # --- connection / overridden + + def handle_accept(self): + """Called when remote client initiates a connection.""" + 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. + if (self.cmd_channel.remote_ip != addr[0]): + if not self.cmd_channel.permit_foreign_addresses: + try: + sock.close() + except socket.error: + pass + msg = 'Rejected data connection from foreign address %s:%s.' \ + % (addr[0], addr[1]) + self.cmd_channel.respond("425 %s" % msg) + self.cmd_channel.log(msg) + # do not close listening socket: it couldn't be client's blame + return + else: + # site-to-site FTP allowed + msg = 'Established data connection with foreign address %s:%s.'\ + % (addr[0], addr[1]) + self.cmd_channel.log(msg) + # Immediately close the current channel (we accept only one + # connection at time) and avoid running out of max connections + # limit. + self.close() + # delegate such connection to DTP handler + handler = self.cmd_channel.dtp_handler(sock, self.cmd_channel) + self.cmd_channel.data_channel = handler + self.cmd_channel.on_dtp_connection() + + def writable(self): + return 0 + + def handle_error(self): + """Called to handle any uncaught exceptions.""" + try: + raise + except (KeyboardInterrupt, SystemExit, asyncore.ExitNow): + raise + logerror(traceback.format_exc()) + self.close() + + 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. + """ + + def __init__(self, ip, port, cmd_channel): + """Initialize the active data channel attemping to connect + to remote data socket. + + - (str) ip: the remote IP address. + - (int) port: the remote port. + - (instance) cmd_channel: the command channel class instance. + """ + asyncore.dispatcher.__init__(self) + self.cmd_channel = cmd_channel + self.create_socket(self.cmd_channel.af, socket.SOCK_STREAM) + try: + self.connect((ip, port)) + except socket.gaierror: + self.cmd_channel.respond("425 Cannot connect to specified address.") + self.close() + + # --- connection / overridden + + def handle_write(self): + """NOOP, must be overridden to prevent unhandled write event.""" + + def handle_connect(self): + """Called when connection is established.""" + self.cmd_channel.respond('200 Active data connection has been established.') + # delegate such connection to DTP handler + handler = self.cmd_channel.dtp_handler(self.socket, self.cmd_channel) + self.cmd_channel.data_channel = handler + self.cmd_channel.on_dtp_connection() + + def handle_expt(self): + self.cmd_channel.respond("425 Cannot connect to specified address.") + self.close() + + def handle_error(self): + """Called to handle any uncaught exceptions.""" + try: + raise + except (KeyboardInterrupt, SystemExit, asyncore.ExitNow): + raise + except socket.error: + pass + except: + logerror(traceback.format_exc()) + self.cmd_channel.respond("425 Cannot connect to specified address.") + self.close() + +class DTPHandler(asyncore.dispatcher): + """Class handling server-data-transfer-process (server-DTP, see + RFC-959) managing data-transfer operations involving sending + and receiving data. + + Instance attributes defined in this class, initialized when + channel is opened: + + - (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: + + When a producer is consumed and close_when_done() has been called + previously, refill_buffer() erroneously calls close() instead of + handle_close() - (see: http://bugs.python.org/issue1740572) + + To avoid this problem DTPHandler is implemented as a subclass of + asyncore.dispatcher instead of asynchat.async_chat. + This implementation follows the same approach that asynchat module + should use in Python 2.6. + + The most important change in the implementation is related to + producer_fifo, which is a pure deque object instead of a + producer_fifo instance. + + Since we don't want to break backward compatibily with older python + versions (deque has been introduced in Python 2.4), if deque is not + available we use a list instead. + """ + + ac_in_buffer_size = 8192 + ac_out_buffer_size = 8192 + + def __init__(self, sock_obj, cmd_channel): + """Initialize the command channel. + + - (instance) sock_obj: the socket object instance of the newly + established connection. + - (instance) cmd_channel: the command channel class instance. + """ + asyncore.dispatcher.__init__(self, sock_obj) + # we toss the use of the asynchat's "simple producer" and + # replace it with a pure deque, which the original fifo + # was a wrapping of + self.producer_fifo = deque() + + self.cmd_channel = cmd_channel + self.file_obj = None + self.receive = False + self.transfer_finished = False + self.tot_bytes_sent = 0 + self.tot_bytes_received = 0 + self.data_wrapper = lambda x: x + + # --- utility methods + + def enable_receiving(self, type): + """Enable receiving of data over the channel. Depending on the + TYPE currently in use it creates an appropriate wrapper for the + incoming data. + + - (str) type: current transfer type, 'a' (ASCII) or 'i' (binary). + """ + if type == 'a': + self.data_wrapper = lambda x: x.replace('\r\n', os.linesep) + elif type == 'i': + self.data_wrapper = lambda x: x + else: + raise TypeError, "Unsupported type." + self.receive = True + + def get_transmitted_bytes(self): + "Return the number of transmitted bytes." + return self.tot_bytes_sent + self.tot_bytes_received + + def transfer_in_progress(self): + "Return True if a transfer is in progress, else False." + return self.get_transmitted_bytes() != 0 + + # --- connection + + def handle_read(self): + """Called when there is data waiting to be read.""" + try: + chunk = self.recv(self.ac_in_buffer_size) + except socket.error: + self.handle_error() + else: + self.tot_bytes_received += len(chunk) + if not chunk: + self.transfer_finished = True + # self.close() # <-- asyncore.recv() already do that... + return + # while we're writing on the file an exception could occur + # in case that filesystem gets full; if this happens we + # let handle_error() method handle this exception, providing + # a detailed error message. + self.file_obj.write(self.data_wrapper(chunk)) + + def handle_write(self): + """Called when data is ready to be written, initiates send.""" + self.initiate_send() + + def push(self, data): + """Push data onto the deque and initiate send.""" + sabs = self.ac_out_buffer_size + if len(data) > sabs: + for i in xrange(0, len(data), sabs): + self.producer_fifo.append(data[i:i + sabs]) + else: + self.producer_fifo.append(data) + self.initiate_send() + + def push_with_producer(self, producer): + """Push data using a producer and initiate send.""" + self.producer_fifo.append(producer) + self.initiate_send() + + def readable(self): + """Predicate for inclusion in the readable for select().""" + return self.receive + + def writable(self): + """Predicate for inclusion in the writable for select().""" + return self.producer_fifo or (not self.connected) + + def close_when_done(self): + """Automatically close this channel once the outgoing queue is empty.""" + self.producer_fifo.append(None) + + def initiate_send(self): + """Attempt to send data in fifo order.""" + while self.producer_fifo and self.connected: + first = self.producer_fifo[0] + # handle empty string/buffer or None entry + if not first: + del self.producer_fifo[0] + if first is None: + self.transfer_finished = True + self.handle_close() + return + + # handle classic producer behavior + obs = self.ac_out_buffer_size + try: + data = buffer(first, 0, obs) + except TypeError: + data = first.more() + if data: + self.producer_fifo.appendleft(data) + else: + del self.producer_fifo[0] + continue + + # send the data + try: + num_sent = self.send(data) + except socket.error: + self.handle_error() + return + + if num_sent: + self.tot_bytes_sent += num_sent + if num_sent < len(data) or obs < len(first): + self.producer_fifo[0] = first[num_sent:] + else: + del self.producer_fifo[0] + # we tried to send some actual data + return + + def handle_expt(self): + """Called on "exceptional" data events.""" + self.cmd_channel.respond("426 Connection error; transfer aborted.") + self.close() + + def handle_error(self): + """Called when an exception is raised and not otherwise handled.""" + try: + raise + except (KeyboardInterrupt, SystemExit, asyncore.ExitNow): + raise + except socket.error, err: + # fix around asyncore bug (http://bugs.python.org/issue1736101) + if err[0] in (errno.ECONNRESET, errno.ENOTCONN, errno.ESHUTDOWN, \ + errno.ECONNABORTED): + self.handle_close() + return + else: + error = str(err[1]) + # an error could occur in case we fail reading / writing + # from / to file (e.g. file system gets full) + except EnvironmentError, err: + error = _strerror(err) + except: + # some other exception occurred; we don't want to provide + # confidential error messages + logerror(traceback.format_exc()) + error = "Internal error." + self.cmd_channel.respond("426 %s; transfer aborted." % error) + self.close() + + def handle_close(self): + """Called when the socket is closed.""" + # If we used channel for receiving we assume that transfer is + # finished when client close connection , if we used channel + # for sending we have to check that all data has been sent + # (responding with 226) or not (responding with 426). + if self.receive: + self.transfer_finished = True + action = 'received' + else: + action = 'sent' + if self.transfer_finished: + self.cmd_channel.respond("226 Transfer complete.") + if self.file_obj: + fname = self.file_obj.name + self.cmd_channel.log('"%s" %s.' % (fname, action)) + else: + tot_bytes = self.get_transmitted_bytes() + msg = "Transfer aborted; %d bytes transmitted." % tot_bytes + self.cmd_channel.respond("426 " + msg) + self.cmd_channel.log(msg) + self.close() + + def close(self): + """Close the data channel, first attempting to close any remaining + file handles.""" + 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 + +class FileProducer: + """Producer wrapper for file[-like] objects.""" + + buffer_size = 65536 + + def __init__(self, file, type): + """Initialize the producer with a data_wrapper appropriate to TYPE. + + - (file) file: the file[-like] object. + - (str) type: the current TYPE, 'a' (ASCII) or 'i' (binary). + """ + self.done = False + self.file = file + if type == 'a': + self.data_wrapper = lambda x: x.replace(os.linesep, '\r\n') + elif type == 'i': + self.data_wrapper = lambda x: x + else: + raise TypeError, "Unsupported type." + + def more(self): + """Attempt a chunk of data of size self.buffer_size.""" + if self.done: + return '' + data = self.data_wrapper(self.file.read(self.buffer_size)) + if not data: + self.done = True + if not self.file.closed: + self.file.close() + 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 + # returning some data + loops = 20 + + def __init__(self, iterator): + self.iterator = iterator + + def more(self): + """Attempt a chunk of data from iterator by calling + its next() method different times. + """ + buffer = [] + for x in xrange(self.loops): + try: + buffer.append(self.iterator.next()) + except StopIteration: + break + return ''.join(buffer) + + +# --- filesystem + +class AbstractedFS: + """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.rnfr = None + + # --- Pathname / conversion utilities + + def ftpnorm(self, ftppath): + """Normalize a "virtual" ftp pathname (tipically the raw string + coming from client) depending on the current working directory. + + Example (having "/foo" as current working directory): + 'x' -> '/foo/x' + + Note: directory separators are system independent ("/"). + Pathname returned is always absolutized. + """ + if os.path.isabs(ftppath): + p = os.path.normpath(ftppath) + else: + p = os.path.normpath(os.path.join(self.cwd, ftppath)) + # normalize string in a standard web-path notation having '/' + # as separator. + 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:] + # Anti path traversal: don't trust user input, in the event + # that self.cwd is not absolute, return "/" as a safety measure. + # This is for extra protection, maybe not really necessary. + if not os.path.isabs(p): + p = "/" + return p + + def ftp2fs(self, ftppath): + """Translate a "virtual" ftp pathname (tipically the raw string + coming from client) into equivalent absolute "real" filesystem + pathname. + + Example (having "/home/user" as root directory): + 'x' -> '/home/user/x' + + Note: directory separators are system dependent. + """ + # as far as I know, it should always be path traversal safe... + if os.path.normpath(self.root) == os.sep: + return os.path.normpath(self.ftpnorm(ftppath)) + else: + p = self.ftpnorm(ftppath)[1:] + return os.path.normpath(os.path.join(self.root, p)) + + def fs2ftp(self, fspath): + """Translate a "real" filesystem pathname into equivalent + absolute "virtual" ftp pathname depending on the user's + root directory. + + Example (having "/home/user" as root directory): + '/home/user/x' -> '/x' + + As for ftpnorm, directory separators are system independent + ("/") and pathname returned is always absolutized. + + On invalid pathnames escaping from user's root directory + (e.g. "/home" when root is "/home/user") always return "/". + """ + if os.path.isabs(fspath): + p = os.path.normpath(fspath) + else: + p = os.path.normpath(os.path.join(self.root, fspath)) + if not self.validpath(p): + return '/' + p = p.replace(os.sep, "/") + p = p[len(self.root):] + if not p.startswith('/'): + p = '/' + p + return p + + # alias for backward compatibility with 0.2.0 + normalize = ftpnorm + translate = ftp2fs + + def validpath(self, path): + """Check whether the path belongs to user's home directory. + Expected argument is a "real" filesystem pathname. + + If path is a symbolic link it is resolved to check its real + destination. + + Pathnames escaping from user's root directory are considered + not valid. + """ + root = self.realpath(self.root) + path = self.realpath(path) + if not self.root.endswith(os.sep): + root = self.root + os.sep + if not path.endswith(os.sep): + path = path + os.sep + if path[0:len(root)] == root: + return True + return False + + # --- Wrapper methods around open() and tempfile.mkstemp + + def open(self, filename, mode): + """Open a file returning its handler.""" + return open(filename, mode) + + 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. + """ + class FileWrapper: + def __init__(self, fd, name): + self.file = fd + self.name = name + def __getattr__(self, attr): + return getattr(self.file, attr) + + text = not 'b' in mode + # max number of tries to find out a unique file name + tempfile.TMP_MAX = 50 + fd, name = tempfile.mkstemp(suffix, prefix, dir, text=text) + file = os.fdopen(fd, mode) + return FileWrapper(file, name) + + # --- Wrapper methods around os.* + + def chdir(self, path): + """Change the current directory.""" + # temporarily join the specified directory to see if we have + # permissions to do so + basedir = os.getcwd() + try: + os.chdir(path) + except os.error: + raise + else: + os.chdir(basedir) + self.cwd = self.fs2ftp(path) + + def mkdir(self, path, basename): + """Create the specified directory.""" + os.mkdir(os.path.join(path, basename)) + + def listdir(self, path): + """List the content of a directory.""" + return os.listdir(path) + + def rmdir(self, path): + """Remove the specified directory.""" + os.rmdir(path) + + def remove(self, path): + """Remove the specified file.""" + os.remove(path) + + def rename(self, src, dst): + """Rename the specified src file to the dst filename.""" + os.rename(src, dst) + + def stat(self, path): + """Perform a stat() system call on the given path.""" + return os.stat(path) + + def lstat(self, path): + """Like stat but does not follow symbolic links.""" + return os.lstat(path) + + if not hasattr(os, 'lstat'): + lstat = stat + + # --- Wrapper methods around os.path.* + + def isfile(self, path): + """Return True if path is a file.""" + return os.path.isfile(path) + + def islink(self, path): + """Return True if path is a symbolic link.""" + return os.path.islink(path) + + def isdir(self, path): + """Return True if path is a directory.""" + return os.path.isdir(path) + + def getsize(self, path): + """Return the size of the specified file in bytes.""" + return os.path.getsize(path) + + def getmtime(self, path): + """Return the last modified time as a number of seconds since + the epoch.""" + return os.path.getmtime(path) + + 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 os.path.realpath(path) + + def lexists(self, path): + """Return True if path refers to an existing path, including + a broken or circular symbolic link. + """ + if hasattr(os.path, 'lexists'): + return os.path.lexists(path) + # grant backward compatibility with python 2.3 + elif hasattr(os, 'lstat'): + try: + os.lstat(path) + except os.error: + return False + return True + # fallback + else: + return os.path.exists(path) + + 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 + + def get_list_dir(self, datacr): + """"Return an iterator object that yields a directory listing + in a form suitable for LIST command. + """ + raise DeprecationWarning() + + 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. + + - (str) basedir: the absolute dirname. + - (list) listing: the names of the entries in basedir + - (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 basename in listing: + file = os.path.join(basedir, basename) + try: + st = self.lstat(file) + except os.error: + if ignore_err: + continue + raise + perms = filemode(st.st_mode) # permissions + nlinks = st.st_nlink # number of links to inode + if not nlinks: # non-posix system, let's use a bogus value + nlinks = 1 + size = st.st_size # file size + uname = st.st_uid or "owner" + gname = st.st_gid or "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: + 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): + basename = basename + " -> " + os.readlink(file) + + # formatting is matched with proftpd ls output + yield "%s %3s %-8s %-8s %8s %s %s\r\n" % (perms, nlinks, uname, gname, + size, mtime, basename) + + def format_mlsx(self, 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 basename in listing: + file = os.path.join(basedir, basename) + try: + st = self.stat(file) + except OSError: + if ignore_err: + continue + raise + # type + perm + if stat.S_ISDIR(st.st_mode): + if 'type' in facts: + if basename == '.': + type = 'type=cdir;' + elif basename == '..': + type = 'type=pdir;' + else: + 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;' % st.st_size # file size + # last modification time + if 'modify' in facts: + try: + modify = 'modify=%s;' % time.strftime("%Y%m%d%H%M%S", + time.localtime(st.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: + create = 'create=%s;' % time.strftime("%Y%m%d%H%M%S", + time.localtime(st.st_ctime)) + except ValueError: + create = "" + # UNIX only + if 'unix.mode' in facts: + mode = 'unix.mode=%s;' % oct(st.st_mode & 0777) + if 'unix.uid' in facts: + uid = 'unix.uid=%s;' % st.st_uid + if 'unix.gid' in facts: + gid = 'unix.gid=%s;' % st.st_gid + # 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: + unique = "unique=%x%x;" % (st.st_dev, st.st_ino) + + yield "%s%s%s%s%s%s%s%s%s %s\r\n" % (type, size, perm, modify, create, + mode, uid, gid, unique, basename) + + +# --- FTP + +class FTPExceptionSent(Exception): + """An FTP exception that FTPHandler has processed + """ + pass + +class FTPHandler(asynchat.async_chat): + """Implements the FTP server Protocol Interpreter (see RFC-959), + handling commands received from the client on the control channel. + + All relevant session information is stored in class attributes + reproduced below and can be modified before instantiating this + class. + + - (str) banner: the string sent when client connects. + + - (int) max_login_attempts: + the maximum number of wrong authentications before disconnecting + the client (default 3). + + - (bool)permit_foreign_addresses: + FTP site-to-site transfer feature: also referenced as "FXP" it + permits for transferring a file between two remote FTP servers + without the transfer going through the client's host (not + recommended for security reasons as described in RFC-2577). + Having this attribute set to False means that all data + connections from/to remote IP addresses which do not match the + client's IP address will be dropped (defualt False). + + - (bool) permit_privileged_ports: + set to True if you want to permit active data connections (PORT) + over privileged ports (not recommended, defaulting to False). + + - (str) masquerade_address: + the "masqueraded" IP address to provide along PASV reply when + pyftpdlib is running behind a NAT or other types of gateways. + When configured pyftpdlib will hide its local address and + instead use the public address of your NAT (default None). + + - (list) passive_ports: + what ports ftpd will use for its passive data transfers. + Value expected is a list of integers (e.g. range(60000, 65535)). + When configured pyftpdlib will no longer use kernel-assigned + random ports (default None). + + + All relevant instance attributes initialized when client connects + are reproduced below. You may be interested in them in case you + want to subclass the original FTPHandler. + + - (bool) authenticated: True if client authenticated himself. + - (str) username: the name of the connected user (if any). + - (int) attempted_logins: number of currently attempted logins. + - (str) current_type: the current transfer type (default "a") + - (int) af: the address family (IPv4/IPv6) + - (instance) server: the FTPServer class instance. + - (instance) data_server: the data server instance (if any). + - (instance) data_channel: the data channel instance (if any). + """ + # these are overridable defaults + + # default classes + authorizer = DummyAuthorizer() + active_dtp = ActiveDTP + passive_dtp = PassiveDTP + dtp_handler = DTPHandler + abstracted_fs = AbstractedFS + + # session attributes (explained in the docstring) + banner = "pyftpdlib %s ready." % __ver__ + max_login_attempts = 3 + permit_foreign_addresses = False + permit_privileged_ports = False + masquerade_address = None + passive_ports = None + + def __init__(self, conn, server): + """Initialize the command channel. + + - (instance) conn: the socket object instance of the newly + established connection. + - (instance) server: the ftp server class instance. + """ + try: + asynchat.async_chat.__init__(self, conn=conn) # python2.5 + except TypeError: + 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 = "" + self.password = "" + self.attempted_logins = 0 + self.current_type = 'a' + self.restart_position = 0 + self.quit_pending = False + self._epsvall = False + self.__in_dtp_queue = None + self.__out_dtp_queue = None + + self.__errno_responses = { + errno.EPERM: 553, + errno.EINVAL: 504, + errno.ENOENT: 550, + errno.EREMOTE: 450, + errno.EEXIST: 521, + } + + # mlsx facts attributes + self.current_facts = ['type', 'perm', 'size', 'modify'] + self.current_facts.append('unique') + self.available_facts = self.current_facts[:] + self.available_facts += ['unix.mode', 'unix.uid', 'unix.gid'] + self.available_facts.append('create') + + # dtp attributes + self.data_server = None + self.data_channel = None + + if hasattr(self.socket, 'family'): + self.af = self.socket.family + else: # python < 2.5 + ip, port = self.socket.getsockname()[:2] + self.af = socket.getaddrinfo(ip, port, socket.AF_UNSPEC, + socket.SOCK_STREAM)[0][0] + + def handle(self): + """Return a 220 'Ready' response to the client over the command + channel. + """ + if len(self.banner) <= 75: + self.respond("220 %s" % str(self.banner)) + else: + self.push('220-%s\r\n' % str(self.banner)) + self.respond('220 ') + + def handle_max_cons(self): + """Called when limit for maximum number of connections is reached.""" + msg = "Too many connections. Service temporary unavailable." + self.respond("421 %s" % msg) + self.log(msg) + # If self.push is used, data could not be sent immediately in + # which case a new "loop" will occur exposing us to the risk of + # accepting new connections. Since this could cause asyncore to + # run out of fds (...and exposes the server to DoS attacks), we + # immediately close the channel by using close() instead of + # close_when_done(). If data has not been sent yet client will + # be silently disconnected. + self.close() + + def handle_max_cons_per_ip(self): + """Called when too many clients are connected from the same IP.""" + msg = "Too many connections from the same IP address." + self.respond("421 %s" % msg) + self.log(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.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) + # 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: + self.respond('500 Command too long.') + self.log('Command has been received exceeds buffer limit of %s.' % (buflimit)) + 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 + terminator. + + Depending on the command received it calls the command's + corresponding method (e.g. for received command "MKD pathname", + ftp_MKD() method is called with "pathname" as the argument). + """ + line = ''.join(self.in_buffer) + self.in_buffer = [] + self.in_buffer_len = 0 + + cmd = line.split(' ')[0].upper() + 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)) + + # 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 + + # 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 + + # 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: + self.respond('500 Command "%s" not understood.' % line) + + # 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.""" + + # Always true, we will only check later, once we have a cursor + 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'} + raise NotImplementedError + 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 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: + pass + else: + self.in_buffer.append(data) + return + self.log("Cannot handle OOB data.") + self.close() + + def handle_error(self): + try: + raise + except (KeyboardInterrupt, SystemExit, asyncore.ExitNow): + raise + except socket.error, err: + # fix around asyncore bug (http://bugs.python.org/issue1736101) + if err[0] in (errno.ECONNRESET, errno.ENOTCONN, errno.ESHUTDOWN, \ + errno.ECONNABORTED): + self.handle_close() + return + else: + logerror(traceback.format_exc()) + except: + logerror(traceback.format_exc()) + self.close() + + 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: + self.data_server.close() + del self.data_server + + if self.data_channel: + self.data_channel.close() + del self.data_channel + + 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) + asynchat.async_chat.close(self) + self.log("Disconnected.") + + # --- callbacks + + def on_dtp_connection(self): + """Called every time data channel connects (either active or + passive). + + Incoming and outgoing queues are checked for pending data. + If outbound data is pending, it is pushed into the data channel. + If awaiting inbound data, the data channel is enabled for + receiving. + """ + if self.data_server: + self.data_server.close() + self.data_server = None + + # check for data to send + if self.__out_dtp_queue: + data, isproducer, file = self.__out_dtp_queue + if file: + self.data_channel.file_obj = file + 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: + self.data_channel.file_obj = self.__in_dtp_queue + self.data_channel.enable_receiving(self.current_type) + 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() + + # --- utility + + def respond(self, resp): + """Send a response to the client using the command channel.""" + self.push(resp + '\r\n') + self.logline('==> %s' % resp) + + def push_dtp_data(self, data, isproducer=False, file=None): + """Pushes data into the data channel. + + It is usually called for those commands requiring some data to + be sent over the data channel (e.g. RETR). + If data channel does not exist yet, it queues the data to send + later; data will then be pushed into data channel when + on_dtp_connection() will be called. + + - (str/classobj) data: the data to send which may be a string + or a producer object). + - (bool) isproducer: whether treat data as a producer. + - (file) file: the file[-like] object to send (if any). + """ + if self.data_channel: + self.respond("125 Data connection already open. Transfer starting.") + if file: + self.data_channel.file_obj = file + 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) + + def log(self, msg): + """Log a message, including additional identifying session data.""" + log("[%s]@%s:%s %s" % (self.username, self.remote_ip, + self.remote_port, msg)) + + def logline(self, msg): + """Log a line including additional indentifying session data.""" + logline("%s:%s %s" % (self.remote_ip, self.remote_port, msg)) + + def flush_account(self): + """Flush account information by clearing attributes that need + to be reset on a REIN or new USER command. + """ + if self.data_channel: + if not self.data_channel.transfer_in_progress(): + self.data_channel.close() + self.data_channel = None + if self.data_server: + self.data_server.close() + self.data_server = None + + self.fs.rnfr = None + self.authenticated = False + self.username = "" + self.password = "" + self.attempted_logins = 0 + self.current_type = 'a' + self.restart_position = 0 + self.quit_pending = False + 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.""" + self.authorizer.impersonate_user(self.username, self.password) + try: + return function(*args, **kwargs) + finally: + self.authorizer.terminate_impersonation() + + # --- connection + + def try_as_current_user(self, function, args=None, kwargs=None, line=None, errno_resp=None): + """run function as current user, auto-respond in exceptions + @param args,kwargs the arguments, in list and dict respectively + @param errno_resp a dictionary of responses to IOError, OSError + """ + if errno_resp: + eresp = self.__errno_responses.copy() + eresp.update(errno_resp) + else: + eresp = self.__errno_responses + + uline = '' + if line: + uline = ' "%s"' % _to_unicode(line) + try: + if args is None: + args = () + if kwargs is None: + kwargs = {} + return self.run_as_current_user(function, *args, **kwargs) + except NotImplementedError, err: + cmdname = function.__name__ + why = err.args[0] or 'Not implemented' + self.log('FAIL %s() is not implemented: %s.' % (cmdname, why)) + self.respond('502 %s.' % why) + raise FTPExceptionSent(why) + except EnvironmentError, err: + cmdname = function.__name__ + try: + logline(traceback.format_exc()) + except Exception: + pass + ret_code = eresp.get(err.errno, '451') + why = (err.strerror) or 'Error in command.' + self.log('FAIL %s() %s errno=%s: %s.' % (cmdname, uline, err.errno, why)) + self.respond('%s %s.' % (str(ret_code), why)) + + raise FTPExceptionSent(why) + except Exception, err: + cmdname = function.__name__ + try: + logerror(traceback.format_exc()) + except Exception: + pass + why = (err.args and err.args[0]) or 'Exception' + self.log('FAIL %s() %s Exception: %s.' % (cmdname, uline, why)) + self.respond('451 %s.' % why) + raise FTPExceptionSent(why) + + def get_crdata2(self, *args, **kwargs): + return self.try_as_current_user(self.fs.get_crdata, args, kwargs, line=args[0]) + + def _make_eport(self, ip, port): + """Establish an active data channel with remote client which + issued a PORT or EPRT command. + """ + # 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: + if ip != self.remote_ip: + self.log("Rejected data connection to foreign address %s:%s." + % (ip, port)) + self.respond("501 Cannot 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: + if port < 1024: + self.log('PORT against the privileged port "%s" has been refused.' % port) + self.respond("501 Cannot connect over a privileged port.") + return + + # close existent DTP-server instance, if any. + if self.data_server: + self.data_server.close() + self.data_server = 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: + 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) + + def _make_epasv(self, extmode=False): + """Initialize a passive data channel with remote client which + issued a PASV or EPSV command. + If extmode argument is False we assume that client issued EPSV in + which case extended passive mode will be used (see RFC-2428). + """ + # close existing DTP-server instance, if any + if self.data_server: + self.data_server.close() + self.data_server = 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: + if len(self._map) >= self.server.max_cons: + msg = "Too many connections. Cannot open data channel." + self.respond("425 %s" % msg) + self.log(msg) + return + + # open data channel + self.data_server = self.passive_dtp(self, extmode) + + def ftp_PORT(self, line): + """Start an active data channel by using IPv4.""" + if self._epsvall: + self.respond("501 PORT not allowed after EPSV ALL.") + return + if self.af != socket.AF_INET: + self.respond("425 You cannot use PORT on IPv6 connections. " + "Use EPRT instead.") + return + # Parse PORT request for getting IP and PORT. + # Request comes in as: + # > h1,h2,h3,h4,p1,p2 + # ...where the client's IP address is h1.h2.h3.h4 and the TCP + # port number is (p1 * 256) + p2. + try: + addr = map(int, line.split(',')) + assert len(addr) == 6 + for x in addr[:4]: + assert 0 <= x <= 255 + ip = '%d.%d.%d.%d' % tuple(addr[:4]) + port = (addr[4] * 256) + addr[5] + assert 0 <= port <= 65535 + except (AssertionError, ValueError, OverflowError): + self.respond("501 Invalid PORT format.") + return + self._make_eport(ip, port) + + def ftp_EPRT(self, line): + """Start an active data channel by choosing the network protocol + to use (IPv4/IPv6) as defined in RFC-2428. + """ + if self._epsvall: + self.respond("501 EPRT not allowed after EPSV ALL.") + return + # Parse EPRT request for getting protocol, IP and PORT. + # Request comes in as: + # # protoipport + # ...where is an arbitrary delimiter character (usually "|") and + # is the network protocol to use (1 for IPv4, 2 for IPv6). + try: + af, ip, port = line.split(line[0])[1:-1] + port = int(port) + assert 0 <= port <= 65535 + except (AssertionError, ValueError, IndexError, OverflowError): + self.respond("501 Invalid EPRT format.") + return + + if af == "1": + if self.af != socket.AF_INET: + self.respond('522 Network protocol not supported (use 2).') + else: + try: + octs = map(int, ip.split('.')) + assert len(octs) == 4 + for x in octs: + assert 0 <= x <= 255 + except (AssertionError, ValueError, OverflowError): + self.respond("501 Invalid EPRT format.") + else: + self._make_eport(ip, port) + elif af == "2": + if self.af == socket.AF_INET: + self.respond('522 Network protocol not supported (use 1).') + else: + self._make_eport(ip, port) + else: + if self.af == socket.AF_INET: + self.respond('501 Unknown network protocol (use 1).') + else: + self.respond('501 Unknown network protocol (use 2).') + + def ftp_PASV(self, line): + """Start a passive data channel by using IPv4.""" + if self._epsvall: + self.respond("501 PASV not allowed after EPSV ALL.") + return + if self.af != socket.AF_INET: + self.respond("425 You cannot use PASV on IPv6 connections. " + "Use EPSV instead.") + else: + self._make_epasv(extmode=False) + + def ftp_EPSV(self, line): + """Start a passive data channel by using IPv4 or IPv6 as defined + in RFC-2428. + """ + # RFC-2428 specifies that if an optional parameter is given, + # we have to determine the address family from that otherwise + # use the same address family used on the control connection. + # In such a scenario a client may use IPv4 on the control channel + # and choose to use IPv6 for the data channel. + # But how could we use IPv6 on the data channel without knowing + # which IPv6 address to use for binding the socket? + # Unfortunately RFC-2428 does not provide satisfing information + # on how to do that. The assumption is that we don't have any way + # to know which address to use, hence we just use the same address + # family used on the control connection. + if not line: + self._make_epasv(extmode=True) + elif line == "1": + if self.af != socket.AF_INET: + self.respond('522 Network protocol not supported (use 2).') + else: + self._make_epasv(extmode=True) + elif line == "2": + if self.af == socket.AF_INET: + self.respond('522 Network protocol not supported (use 1).') + else: + self._make_epasv(extmode=True) + elif line.lower() == 'all': + self._epsvall = True + self.respond('220 Other commands other than EPSV are now disabled.') + else: + if self.af == socket.AF_INET: + self.respond('501 Unknown network protocol (use 1).') + else: + self.respond('501 Unknown network protocol (use 2).') + + def ftp_QUIT(self, line): + """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: + msg_quit = "Goodbye." + if len(msg_quit) <= 75: + self.respond("221 %s" % msg_quit) + else: + self.push("221-%s\r\n" % msg_quit) + self.respond("221 ") + + 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 + + def ftp_LIST(self, line): + """Return a list of files in the specified directory to the + client. + """ + # - 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. + if not line or line.lower() in ('-a', '-l', '-al', '-la'): + line = '' + datacr = None + try: + datacr = self.get_crdata2(line, mode='list') + iterator = self.try_as_current_user(self.fs.get_list_dir, (datacr,)) + except FTPExceptionSent: + self.fs.close_cr(datacr) + return + + try: + self.log('OK LIST "%s". Transfer starting.' % line) + producer = BufferedIteratorProducer(iterator) + self.push_dtp_data(producer, isproducer=True) + finally: + 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. + """ + if not line: + line = '' + + datacr = None + try: + datacr = self.get_crdata2(line, mode='list') + if not datacr: + datacr = (None, None, None) + if self.fs.isdir(datacr[1]): + nodelist = self.try_as_current_user(self.fs.listdir, (datacr,)) + else: + # if path is a file we just list its name + nodelist = [datacr[1], ] + + listing = [] + for nl in nodelist: + if isinstance(nl.path, (list, tuple)): + listing.append(nl.path[-1]) + else: + listing.append(nl.path) # assume string + except FTPExceptionSent: + self.fs.close_cr(datacr) + return + + self.fs.close_cr(datacr) + data = '' + if listing: + listing.sort() + data = ''.join([ _to_decode(x) + '\r\n' for x in listing ]) + self.log('OK NLST "%s". Transfer starting.' % line) + self.push_dtp_data(data) + + # --- MLST and MLSD commands + + # The MLST and MLSD commands are intended to standardize the file and + # directory information returned by the server-FTP process. These + # commands differ from the LIST command in that the format of the + # replies is strictly defined although extensible. + + def ftp_MLST(self, line): + """Return information about a pathname in a machine-processable + form as defined in RFC-3659. + """ + # if no argument, fall back on cwd as default + if not line: + line = '' + datacr = None + try: + datacr = self.get_crdata2(line, mode='list') + perms = self.authorizer.get_perms(self.username) + iterator = self.try_as_current_user(self.fs.format_mlsx, (datacr[0], datacr[1].parent, + [datacr[1], ], perms, self.current_facts), {'ignore_err':False}) + data = ''.join(iterator) + except FTPExceptionSent: + self.fs.close_cr(datacr) + return + else: + self.fs.close_cr(datacr) + # since TVFS is supported (see RFC-3659 chapter 6), a fully + # qualified pathname should be returned + data = data.split(' ')[0] + ' %s\r\n' % line + # response is expected on the command channel + self.push('250-Listing "%s":\r\n' % line) + # the fact set must be preceded by a space + self.push(' ' + data) + self.respond('250 End MLST.') + + def ftp_MLSD(self, line): + """Return contents of a directory in a machine-processable form + as defined in RFC-3659. + """ + # if no argument, fall back on cwd as default + if not line: + line = '' + + datacr = None + try: + datacr = self.get_crdata2(line, mode='list') + # RFC-3659 requires 501 response code if path is not a directory + if not self.fs.isdir(datacr[1]): + err = 'No such directory.' + self.log('FAIL MLSD "%s". %s.' % (line, err)) + self.respond("501 %s." % err) + return + listing = self.try_as_current_user(self.fs.listdir, (datacr,)) + except FTPExceptionSent: + self.fs.close_cr(datacr) + return + else: + self.fs.close_cr(datacr) + perms = self.authorizer.get_perms(self.username) + iterator = self.fs.format_mlsx(datacr[0], datacr[1], listing, perms, + self.current_facts) + producer = BufferedIteratorProducer(iterator) + self.log('OK MLSD "%s". Transfer starting.' % line) + self.push_dtp_data(producer, isproducer=True) + + def ftp_RETR(self, line): + """Retrieve the specified file (transfer from the server to the + client) + """ + datacr = None + try: + datacr = self.get_crdata2(line, mode='file') + fd = self.try_as_current_user(self.fs.open, (datacr, 'rb')) + except FTPExceptionSent: + self.fs.close_cr(datacr) + return + + 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 + # the existing file cannot be repositioned as specified in + # the REST. + ok = 0 + try: + assert not self.restart_position > self.fs.getsize(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.respond('554 %s' % why) + self.log('FAIL RETR "%s". %s.' % (line, why)) + self.fs.close_cr(datacr) + return + self.log('OK RETR "%s". Download starting.' % line) + producer = FileProducer(fd, self.current_type) + self.push_dtp_data(producer, isproducer=True, file=fd) + self.fs.close_cr(datacr) + + def ftp_STOR(self, line, mode='w'): + """Store a file (transfer from the client to the server).""" + # A resume could occur in case of APPE or REST commands. + # In that case we have to open file object in different ways: + # STOR: mode = 'w' + # APPE: mode = 'a' + # REST: mode = 'r+' (to permit seeking on file object) + if 'a' in mode: + cmd = 'APPE' + else: + cmd = 'STOR' + + datacr = None + try: + datacr = self.get_crdata2(line, mode='create') + if self.restart_position: + mode = 'r+' + fd = self.try_as_current_user(self.fs.create, (datacr, datacr[2], mode + 'b')) + assert fd + except FTPExceptionSent: + self.fs.close_cr(datacr) + return + + 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 the existing file cannot be repositioned as + # specified in the REST. + ok = 0 + try: + assert not self.restart_position > self.fs.getsize(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)) + return + + self.log('OK %s "%s". Upload starting.' % (cmd, line)) + 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.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 + # prohibition is obsolete. + # Note 2: 250 response wanted by RFC-959 has been declared + # incorrect in RFC-1123 that wants 125/150 instead. + # Note 3: RFC-1123 also provided an exact output format + # defined to be as follow: + # > 125 FILE: pppp + # ...where pppp represents the unique path name of the + # file that will be written. + + # watch for STOU preceded by REST, which makes no sense. + if self.restart_position: + self.respond("450 Cannot STOU while REST request is pending.") + return + + + if line: + datacr = self.get_crdata2(line, mode='create') + # TODO + else: + # TODO + basedir = self.fs.ftp2fs(self.fs.cwd, datacr) + prefix = 'ftpd.' + try: + fd = self.try_as_current_user(self.fs.mkstemp, kwargs={'prefix':prefix, + 'dir': basedir}, line=line) + except FTPExceptionSent: + self.fs.close_cr(datacr) + return + except IOError, err: # TODO + # hitted the max number of tries to find out file with + # unique name + if err.errno == errno.EEXIST: + why = 'No usable unique file name found.' + # something else happened + else: + why = _strerror(err) + self.respond("450 %s." % why) + self.log('FAIL STOU "%s". %s.' % (self.fs.ftpnorm(line), why)) + self.fs.close_cr(datacr) + return + + 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 Cannot STOU: not enough privileges.") + self.fs.close_cr(datacr) + return + + # now just acts like STOR except that restarting isn't allowed + self.log('OK STOU "%s". Upload starting.' % filename) + 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.fs.close_cr(datacr) + + + 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("550 Cannot APPE while REST request is pending.") + else: + self.ftp_STOR(line, mode='a') + + def ftp_REST(self, line): + """Restart a file transfer from a previous mark.""" + try: + marker = int(line) + if marker < 0: + raise ValueError + except (ValueError, OverflowError): + self.respond("501 Invalid parameter.") + else: + 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): + """Abort the current data transfer.""" + + # ABOR received while no data channel exists + if (self.data_server is None) and (self.data_channel is None): + resp = "225 No transfer to abort." + else: + # a PASV was received but connection wasn't made yet + if self.data_server: + self.data_server.close() + self.data_server = None + resp = "225 ABOR command successful; data channel closed." + + # If a data transfer is in progress the server must first + # close the data connection, returning a 426 reply to + # indicate that the transfer terminated abnormally, then it + # must send a 226 reply, indicating that the abort command + # 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: + if self.data_channel.transfer_in_progress(): + self.data_channel.close() + self.data_channel = None + self.respond("426 Connection closed; transfer aborted.") + self.log("OK ABOR. Transfer aborted, data channel closed.") + resp = "226 ABOR command successful." + else: + self.data_channel.close() + self.data_channel = None + self.log("OK ABOR. Data channel closed.") + resp = "225 ABOR command successful; data channel closed." + self.respond(resp) + + + # --- authentication + + 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 + # malicious client from determining valid usernames on a server, + # it is suggested by RFC-2577 that a server always return 331 to + # the USER command and then reject the combination of username + # and password for an invalid username when PASS is provided later. + if not self.authenticated: + self.respond('331 Username ok, send password.') + else: + # a new USER command could be entered at any point in order + # to change the access control flushing any user, password, + # and account information already supplied and beginning the + # login sequence again. + self.flush_account() + msg = 'Previous account information is flushed.' + self.log('OK USER "%s". %s.' % (line, msg)) + self.respond('331 %s, send password.' % msg) + self.username = line + + def ftp_PASS(self, line): + """Check username's password against the authorizer.""" + + if self.authenticated: + self.respond("503 User already authenticated.") + return + if not self.username: + self.respond("503 Login with USER first.") + return + + # 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: + self.respond("530 Maximum login attempts. Disconnecting.") + self.close() + else: + self.respond("530 Authentication failed.") + self.log('Authentication failed (user: "%s").' % self.username) + self.username = "" + + # wrong username + else: + 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: + 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.""" + # From RFC-959: + # REIN command terminates a USER, flushing all I/O and account + # information, except to allow any transfer in progress to be + # completed. All parameters are reset to the default settings + # and the control connection is left open. This is identical + # to the state in which a user finds himself immediately after + # the control connection is opened. + self.log("OK REIN. Flushing account information.") + self.flush_account() + # Note: RFC-959 erroneously mention "220" as the correct response + # code to be given in this case, but this is wrong... + self.respond("230 Ready for new user.") + + + # --- filesystem operations + + def ftp_PWD(self, line): + """Return the name of the current working directory to the client.""" + cwd = self.fs.get_cwd() + self.respond('257 "%s" is the current directory.' % cwd) + + def ftp_CWD(self, line): + """Change the current working directory.""" + # check: 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. + datacr = None + try: + datacr = self.get_crdata2(line, 'cwd') + self.try_as_current_user(self.fs.chdir, (datacr,), line=line, errno_resp={2: 530}) + cwd = self.fs.get_cwd() + self.log('OK CWD "%s".' % cwd) + self.respond('250 "%s" is the current directory.' % cwd) + except FTPExceptionSent: + return + finally: + self.fs.close_cr(datacr) + + def ftp_CDUP(self, line): + """Change into the parent directory.""" + # Note: RFC-959 says that code 200 is required but it also says + # that CDUP uses the same codes as CWD. + self.ftp_CWD('..') + + def ftp_SIZE(self, line): + """Return size of file in a format suitable for using with + 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 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.get_crdata2(line, mode='file') + size = self.try_as_current_user(self.fs.getsize, (datacr,), line=line) + except FTPExceptionSent: + self.fs.close_cr(datacr) + return + else: + self.respond("213 %s" % size) + self.log('OK SIZE "%s".' % line) + self.fs.close_cr(datacr) + + def ftp_MDTM(self, line): + """Return last modification time of file to the client as an ISO + 3307 style timestamp (YYYYMMDDHHMMSS) as defined in RFC-3659. + """ + datacr = None + + try: + if line.find('/', 1) < 0: + # root or db, just return local + lmt = None + else: + datacr = self.get_crdata2(line) + if not datacr: + raise IOError(errno.ENOENT, "%s is not retrievable." % line) + + lmt = self.try_as_current_user(self.fs.getmtime, (datacr,), line=line) + lmt = time.strftime("%Y%m%d%H%M%S", time.localtime(lmt)) + self.respond("213 %s" % lmt) + self.log('OK MDTM "%s".' % line) + except FTPExceptionSent: + return + finally: + self.fs.close_cr(datacr) + + def ftp_MKD(self, line): + """Create the specified directory.""" + try: + datacr = self.get_crdata2(line, mode='create') + self.try_as_current_user(self.fs.mkdir, (datacr, datacr[2]), line=line) + except FTPExceptionSent: + self.fs.close_cr(datacr) + return + else: + self.log('OK MKD "%s".' % line) + self.respond("257 Directory created.") + self.fs.close_cr(datacr) + + def ftp_RMD(self, line): + """Remove the specified directory.""" + datacr = None + try: + datacr = self.get_crdata2(line, mode='delete') + if not datacr[1]: + msg = "Cannot remove root directory." + self.respond("553 %s" % msg) + self.log('FAIL MKD "/". %s' % msg) + self.fs.close_cr(datacr) + return + self.try_as_current_user(self.fs.rmdir, (datacr,), line=line) + self.log('OK RMD "%s".' % line) + self.respond("250 Directory removed.") + except FTPExceptionSent: + pass + self.fs.close_cr(datacr) + + def ftp_DELE(self, line): + """Delete the specified file.""" + datacr = None + try: + datacr = self.get_crdata2(line, mode='delete') + self.try_as_current_user(self.fs.remove, (datacr,), line=line) + self.log('OK DELE "%s".' % line) + self.respond("250 File removed.") + except FTPExceptionSent: + pass + 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.get_crdata2(line, mode='rfnr') + if not datacr[1]: + self.respond("550 No such file or directory.") + elif not datacr[1]: + self.respond("553 Cannot rename the home directory.") + else: + self.fs.rnfr = datacr[1] + self.respond("350 Ready for destination name.") + except FTPExceptionSent: + pass + self.fs.close_cr(datacr) + + def ftp_RNTO(self, line): + """Rename file (destination name only, source is specified with + RNFR). + """ + if not self.fs.rnfr: + self.respond("503 Bad sequence of commands: use RNFR first.") + return + datacr = None + try: + datacr = self.get_crdata2(line, 'create') + oldname = self.fs.rnfr.path + if isinstance(oldname, (list, tuple)): + oldname = '/'.join(oldname) + self.try_as_current_user(self.fs.rename, (self.fs.rnfr, datacr), line=line) + self.fs.rnfr = None + self.log('OK RNFR/RNTO "%s ==> %s".' % \ + (_to_unicode(oldname), _to_unicode(line))) + self.respond("250 Renaming ok.") + except FTPExceptionSent: + pass + finally: + self.fs.rnfr = None + self.fs.close_cr(datacr) + + + # --- others + + def ftp_TYPE(self, line): + """Set current type data type to binary/ascii""" + line = line.upper() + if line in ("A", "AN", "A N"): + self.respond("200 Type set to: ASCII.") + self.current_type = 'a' + 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 (obsolete).""" + # obsolete (backward compatibility with older ftp clients) + if line in ('f', 'F'): + self.respond('200 File transfer structure set to: F.') + else: + self.respond('504 Unimplemented STRU type.') + + def ftp_MODE(self, line): + """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') + else: + self.respond('504 Unimplemented MODE type.') + + def ftp_STAT(self, line): + """Return statistics about current ftp session. If an argument + is provided return directory listing over command channel. + + Implementation note: + + 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 limit the search to only one directory + non-recursively, as LIST does. + """ + # return STATus information about ftpd + if not line: + s = [] + s.append('Connected to: %s:%s' % self.socket.getsockname()[:2]) + if self.authenticated: + s.append('Logged in as: %s' % self.username) + else: + if not self.username: + s.append("Waiting for username.") + else: + s.append("Waiting for password.") + if self.current_type == 'a': + type = 'ASCII' + else: + type = 'Binary' + s.append("TYPE: %s; STRUcture: File; MODE: Stream" % type) + if self.data_server: + s.append('Passive data channel waiting for connection.') + 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:') + s.append('Total bytes sent: %s' % bytes_sent) + s.append('Total bytes received: %s' % bytes_recv) + else: + s.append('Data connection closed.') + + self.push('211-FTP server status:\r\n') + self.push(''.join([' %s\r\n' % item for item in s])) + self.respond('211 End of status.') + # return directory LISTing over the command channel + else: + datacr = None + try: + datacr = self.fs.get_cr(line) + iterator = self.try_as_current_user(self.fs.get_stat_dir, (line, datacr), line=line) + except FTPExceptionSent: + pass + else: + 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'] + s = '' + for fact in self.available_facts: + if fact in self.current_facts: + s += fact + '*;' + else: + s += fact + ';' + features.append('MLST ' + s) + features.sort() + self.push("211-Features supported:\r\n") + self.push("".join([" %s\r\n" % x for x in features])) + self.respond('211 End FEAT.') + + def ftp_OPTS(self, line): + """Specify options for FTP commands as specified in RFC-2389.""" + try: + assert (not line.count(' ') > 1), 'Invalid number of arguments.' + if ' ' in line: + cmd, arg = line.split(' ') + assert (';' in arg), 'Invalid argument!' + else: + cmd, arg = line, '' + # actually the only command able to accept options is MLST + assert (cmd.upper() == 'MLST'), 'Unsupported command "%s".' % cmd + except AssertionError, err: + 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.respond('200 MLST OPTS ' + f) + + def ftp_NOOP(self, line): + """Do nothing.""" + self.respond("200 I successfully done nothin'.") + + def ftp_SYST(self, line): + """Return system type (always returns UNIX type: L8).""" + # This command is used to find out the type of operating system + # at the server. The reply shall have as its first word one of + # the system names listed in RFC-943. + # Since that we always return a "/bin/ls -lA"-like output on + # LIST we prefer to respond as if we would on Unix in any case. + self.respond("215 UNIX Type: L8") + + def ftp_ALLO(self, line): + """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: + 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 = proto_cmds.keys() + keys.sort() + while keys: + elems = tuple((keys[0:8])) + cmds.append(' %-6s' * len(elems) % elems + '\r\n') + del keys[0:8] + return ''.join(cmds) + + self.push("214-The following commands are recognized:\r\n") + self.push(formatted_help()) + self.respond("214 Help command successful.") + + + # --- support for deprecated cmds + + # RFC-1123 requires that the server treat XCUP, XCWD, XMKD, XPWD + # and XRMD commands as synonyms for CDUP, CWD, MKD, LIST and RMD. + # Such commands are obsoleted but some ftp clients (e.g. Windows + # ftp.exe) still use them. + + def ftp_XCUP(self, line): + """Change to the parent directory. Synonym for CDUP. Deprecated.""" + self.ftp_CDUP(line) + + def ftp_XCWD(self, line): + """Change the current working directory. Synonym for CWD. Deprecated.""" + self.ftp_CWD(line) + + def ftp_XMKD(self, line): + """Create the specified directory. Synonym for MKD. Deprecated.""" + self.ftp_MKD(line) + + def ftp_XPWD(self, line): + """Return the current working directory. Synonym for PWD. Deprecated.""" + self.ftp_PWD(line) + + def ftp_XRMD(self, line): + """Remove the specified directory. Synonym for RMD. Deprecated.""" + self.ftp_RMD(line) + + +class FTPServer(asyncore.dispatcher): + """This class is an asyncore.disptacher subclass. It creates a FTP + socket listening on
, dispatching the requests to a + (typically FTPHandler class). + + Depending on the type of address specified IPv4 or IPv6 connections + (or both, depending from the underlying system) will be accepted. + + All relevant session information is stored in class attributes + described below. + Overriding them is strongly recommended to avoid running out of + file descriptors (DoS)! + + - (int) max_cons: + number of maximum simultaneous connections accepted (defaults + to 0 == unlimited). + + - (int) max_cons_per_ip: + number of maximum connections accepted for the same IP address + (defaults to 0 == unlimited). + """ + + max_cons = 0 + max_cons_per_ip = 0 + + def __init__(self, address, handler): + """Initiate the FTP server opening listening on address. + + - (tuple) address: the host:port pair on which the command + channel will listen. + + - (classobj) handler: the handler class to use. + """ + asyncore.dispatcher.__init__(self) + self.handler = handler + self.ip_map = [] + host, port = address + + # AF_INET or AF_INET6 socket + # Get the correct address family for our host (allows IPv6 addresses) + try: + info = socket.getaddrinfo(host, port, socket.AF_UNSPEC, + socket.SOCK_STREAM, 0, socket.AI_PASSIVE) + except socket.gaierror: + # Probably a DNS issue. Assume IPv4. + self.create_socket(socket.AF_INET, socket.SOCK_STREAM) + self.set_reuse_addr() + self.bind((host, port)) + else: + for res in info: + af, socktype, proto, canonname, sa = res + try: + self.create_socket(af, socktype) + self.set_reuse_addr() + self.bind(sa) + except socket.error, msg: + if self.socket: + self.socket.close() + self.socket = None + continue + break + if not self.socket: + raise socket.error, msg + self.listen(5) + + def set_reuse_addr(self): + # Overridden for convenience. Avoid to reuse address on Windows. + if (os.name in ('nt', 'ce')) or (sys.platform == 'cygwin'): + return + asyncore.dispatcher.set_reuse_addr(self) + + def serve_forever(self, **kwargs): + """A wrap around asyncore.loop(); starts the asyncore polling + loop. + + The keyword arguments in kwargs are the same expected by + asyncore.loop() function: timeout, use_poll, map and count. + """ + 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 + + 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.""" + sock_obj, addr = self.accept() + log("[]%s:%s Connected." % addr[:2]) + + handler = self.handler(sock_obj, self) + ip = addr[0] + self.ip_map.append(ip) + + # For performance and security reasons we should always set a + # limit for the number of file descriptors that socket_map + # should contain. When we're running out of such limit we'll + # use the last available channel for sending a 421 response + # to the client before disconnecting it. + if self.max_cons: + if len(self._map) > self.max_cons: + handler.handle_max_cons() + return + + # accept only a limited number of connections from the same + # source address. + if self.max_cons_per_ip: + if self.ip_map.count(ip) > self.max_cons_per_ip: + handler.handle_max_cons_per_ip() + return + + handler.handle() + + def writable(self): + return 0 + + def handle_error(self): + """Called to handle any uncaught exceptions.""" + try: + raise + except (KeyboardInterrupt, SystemExit, asyncore.ExitNow): + raise + logerror(traceback.format_exc()) + self.close() + + def close_all(self, map=None, ignore_all=False): + """Stop serving; close all existent connections disconnecting + clients. + + - (dict) map: + A dictionary whose items are the channels to close. + If map is omitted, the default asyncore.socket_map is used. + + - (bool) ignore_all: + having it set to False results in raising exception in case + of unexpected errors. + + Implementation note: + + 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. + """ + if map is None: + map = self._map + for x in map.values(): + try: + x.close() + except OSError, x: + if x[0] == errno.EBADF: + pass + elif not ignore_all: + raise + except (asyncore.ExitNow, KeyboardInterrupt, SystemExit): + raise + except: + if not ignore_all: + raise + map.clear() + + +def test(): + # cmd line usage (provide a read-only anonymous ftp server): + # python -m pyftpdlib.FTPServer + authorizer = DummyAuthorizer() + authorizer.add_anonymous(os.getcwd(), perm='elradfmw') + FTPHandler.authorizer = authorizer + address = ('', 8021) + ftpd = FTPServer(address, FTPHandler) + ftpd.serve_forever() + +if __name__ == '__main__': + test() + diff --git a/document_ftp/i18n/ar.po b/document_ftp/i18n/ar.po new file mode 100644 index 00000000..580fe183 --- /dev/null +++ b/document_ftp/i18n/ar.po @@ -0,0 +1,130 @@ +# Arabic translation for openobject-addons +# Copyright (c) 2012 Rosetta Contributors and Canonical Ltd 2012 +# This file is distributed under the same license as the openobject-addons package. +# FIRST AUTHOR , 2012. +# +msgid "" +msgstr "" +"Project-Id-Version: openobject-addons\n" +"Report-Msgid-Bugs-To: FULL NAME \n" +"POT-Creation-Date: 2013-06-07 19:36+0000\n" +"PO-Revision-Date: 2013-11-26 18:27+0000\n" +"Last-Translator: kifcaliph \n" +"Language-Team: Arabic \n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"X-Launchpad-Export-Date: 2013-11-27 05:39+0000\n" +"X-Generator: Launchpad (build 16845)\n" + +#. module: document_ftp +#: view:document.ftp.configuration:0 +msgid "Configure FTP Server" +msgstr "Ω‚Ω… Ψ¨ΨΆΨ¨Ψ· Ψ§Ω„ΨΨ§Ψ―Ω… FTP" + +#. module: document_ftp +#: model:ir.model,name:document_ftp.model_document_ftp_configuration +msgid "Auto Directory Configuration" +msgstr "Ψ§Ω„ΨͺΩ‡ΩŠΨ¦Ψ© Ψ§Ω„ΨͺΩ„Ω‚Ψ§Ψ¦ΩŠΨ© Ω„Ω„Ω…Ψ³Ψ§Ψ±" + +#. module: document_ftp +#: view:document.ftp.configuration:0 +msgid "" +"Indicate the network address on which your OpenERP server should be " +"reachable for end-users. This depends on your network topology and " +"configuration, and will only affect the links displayed to the users. The " +"format is HOST:PORT and the default host (localhost) is only suitable for " +"access from the server machine itself.." +msgstr "" +"يشير ΨΉΩ†ΩˆΨ§Ω† Ψ§Ω„Ψ΄Ψ¨ΩƒΨ© Ψ§Ω„Ψͺي Ω„Ψ―ΩŠΩƒ ΨΉΩ„Ω‰ Ψ§Ω„ΨΨ§Ψ―Ω… OpenERP ΩŠΩ†Ψ¨ΨΊΩŠ Ψ£Ω† ΨͺΩƒΩˆΩ† Ω‚Ψ§Ψ¨Ω„Ψ© Ω„Ω„ΩˆΨ΅ΩˆΩ„ " +"Ω„Ω„Ω…Ψ³ΨͺΨΨ―Ω…ΩŠΩ† Ψ§Ω„Ω†Ω‡Ψ§Ψ¦ΩŠΩŠΩ†. Ω‡Ψ°Ψ§ يعΨͺΩ…Ψ― ΨΉΩ„Ω‰ Ω‡ΩŠΩƒΩ„ Ψ§Ω„Ψ΄Ψ¨ΩƒΨ© Ψ§Ω„ΨΨ§Ψ΅Ψ© Ψ¨Ωƒ ΩˆΨ§Ω„ΨͺΩƒΩˆΩŠΩ†ΨŒ وسوف " +"ΨͺΨ€Ψ«Ψ± فقط ΨΉΩ„Ω‰ Ψ§Ω„Ψ±ΩˆΨ§Ψ¨Ψ· Ψ§Ω„Ω…ΨΉΨ±ΩˆΨΆΨ© Ω„Ω„Ω…Ψ³ΨͺΨΨ―Ω…ΩŠΩ†. ΩˆΨ§Ω„Ψ΄ΩƒΩ„ Ω‡Ωˆ Ω…ΨΆΩŠΩ: منفذ ΩˆΨ§Ω„Ω…ΨΆΩŠΩ " +"الافΨͺراآي (Ψ§Ω„Ω…ΨΆΩŠΩ Ψ§Ω„Ω…Ψ­Ω„ΩŠ) ΩˆΩ‡ΩŠ Ω…Ω†Ψ§Ψ³Ψ¨Ψ© فقط Ω„Ω„ΩˆΨ΅ΩˆΩ„ Ω…Ω† Ψ¬Ω‡Ψ§Ψ² Ψ§Ω„ΨΨ§Ψ―Ω… نفسه .." + +#. module: document_ftp +#: model:ir.model,name:document_ftp.model_knowledge_config_settings +msgid "knowledge.config.settings" +msgstr "knowledge.config.settings" + +#. module: document_ftp +#: model:ir.actions.act_url,name:document_ftp.action_document_browse +msgid "Browse Files" +msgstr "Ψ§Ψ³ΨͺΨΉΨ±Ψ§ΨΆ الملفاΨͺ" + +#. module: document_ftp +#: help:knowledge.config.settings,document_ftp_url:0 +msgid "Click the url to browse the documents" +msgstr "Ψ§ΨΆΨΊΨ· ΨΉΩ„Ω‰ Ψ§Ω„Ψ±Ψ§Ψ¨Ψ· Ω„Ψͺءفح Ψ§Ω„Ω…Ψ³ΨͺΩ†Ψ―Ψ§Ψͺ" + +#. module: document_ftp +#: field:document.ftp.browse,url:0 +msgid "FTP Server" +msgstr "ΨΨ§Ψ―Ω… FTP" + +#. module: document_ftp +#: model:ir.actions.act_window,name:document_ftp.action_config_auto_directory +msgid "FTP Server Configuration" +msgstr "ΨΆΨ¨Ψ· ΨΨ―Ω…Ψ© FTP" + +#. module: document_ftp +#: field:knowledge.config.settings,document_ftp_url:0 +msgid "Browse Documents" +msgstr "Ψͺءفح Ψ§Ω„Ω…Ψ³ΨͺΩ†Ψ―Ψ§Ψͺ" + +#. module: document_ftp +#: view:document.ftp.browse:0 +msgid "_Browse" +msgstr "_Ψͺءفح" + +#. module: document_ftp +#: help:document.ftp.configuration,host:0 +msgid "" +"Server address or IP and port to which users should connect to for DMS access" +msgstr "" +"ΨΉΩ†ΩˆΨ§Ω† Ψ§Ω„ΨΨ§Ψ―Ω… أو Ψ§Ω„Ω…Ω„ΩƒΩŠΨ© Ψ§Ω„ΩΩƒΨ±ΩŠΨ© ΩˆΨ§Ω„Ω…Ω†ΩΨ° Ψ§Ω„Ψ°ΩŠ يجب ΨΉΩ„Ω‰ Ψ§Ω„Ω…Ψ³ΨͺΨΨ―Ω…ΩŠΩ† Ψ§Ω„Ψ§ΨͺΨ΅Ψ§Ω„ Ψ¨Ω‡ " +"Ω„Ω„Ψ­Ψ΅ΩˆΩ„ DMS" + +#. module: document_ftp +#: model:ir.ui.menu,name:document_ftp.menu_document_browse +msgid "Shared Repository (FTP)" +msgstr "Ω…Ψ³Ψͺودع Ω…Ψ΄ΨͺΨ±Ωƒ (FTP)" + +#. module: document_ftp +#: field:document.ftp.configuration,host:0 +msgid "Address" +msgstr "Ψ§Ω„ΨΉΩ†ΩˆΨ§Ω†" + +#. module: document_ftp +#: view:document.ftp.browse:0 +msgid "Cancel" +msgstr "Ψ₯Ω„ΨΊΨ§Ψ‘" + +#. module: document_ftp +#: model:ir.model,name:document_ftp.model_document_ftp_browse +msgid "Document FTP Browse" +msgstr "Ψͺءفح ΩˆΨ«ΩŠΩ‚Ψ© FTP" + +#. module: document_ftp +#: view:document.ftp.configuration:0 +msgid "Knowledge Application Configuration" +msgstr "Ψ§Ω„Ψ§ΨΉΨ―Ψ§Ψ―Ψ§Ψͺ Ω„Ω†ΨΈΨ§Ω… Ψ£Ψ―Ψ§Ψ±Ψ© Ψ§Ω„ΩˆΨ«Ψ§Ψ¦Ω‚" + +#. module: document_ftp +#: model:ir.actions.act_window,name:document_ftp.action_ftp_browse +msgid "Document Browse" +msgstr "Ω…Ψͺءفح Ψ§Ω„ΩˆΨ«ΩŠΩ‚Ψ©" + +#. module: document_ftp +#: view:document.ftp.browse:0 +msgid "or" +msgstr "أو" + +#. module: document_ftp +#: view:document.ftp.browse:0 +msgid "Browse Document" +msgstr "Ψͺءفح Ψ§Ω„ΩˆΨ«ΩŠΩ‚Ψ©" + +#. module: document_ftp +#: view:document.ftp.configuration:0 +msgid "res_config_contents" +msgstr "res_config_contents" diff --git a/document_ftp/i18n/bg.po b/document_ftp/i18n/bg.po new file mode 100644 index 00000000..3596c259 --- /dev/null +++ b/document_ftp/i18n/bg.po @@ -0,0 +1,131 @@ +# Bulgarian translation for openobject-addons +# Copyright (c) 2012 Rosetta Contributors and Canonical Ltd 2012 +# This file is distributed under the same license as the openobject-addons package. +# FIRST AUTHOR , 2012. +# +msgid "" +msgstr "" +"Project-Id-Version: openobject-addons\n" +"Report-Msgid-Bugs-To: FULL NAME \n" +"POT-Creation-Date: 2013-06-07 19:36+0000\n" +"PO-Revision-Date: 2012-12-21 23:00+0000\n" +"Last-Translator: FULL NAME \n" +"Language-Team: Bulgarian \n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"X-Launchpad-Export-Date: 2013-11-21 06:07+0000\n" +"X-Generator: Launchpad (build 16831)\n" + +#. module: document_ftp +#: view:document.ftp.configuration:0 +msgid "Configure FTP Server" +msgstr "НастройванС Π½Π° FTP ΡΡŠΡ€Π²ΡŠΡ€" + +#. module: document_ftp +#: model:ir.model,name:document_ftp.model_document_ftp_configuration +msgid "Auto Directory Configuration" +msgstr "Автоматично настройванС Π½Π° катСгория" + +#. module: document_ftp +#: view:document.ftp.configuration:0 +msgid "" +"Indicate the network address on which your OpenERP server should be " +"reachable for end-users. This depends on your network topology and " +"configuration, and will only affect the links displayed to the users. The " +"format is HOST:PORT and the default host (localhost) is only suitable for " +"access from the server machine itself.." +msgstr "" +"Показва мрСТовия адрСс, Π½Π° ΠΊΠΎΠΉΡ‚ΠΎ Π²Π°ΡˆΠΈΡΡ‚ OpenErp ΡΡŠΡ€Π²ΡŠΡ€ слСдва Π΄Π° бъдС " +"Π΄ΠΎΡΡ‚ΡŠΠΏΠ΅Π½ Π·Π° ΠΊΡ€Π°ΠΉΠ½ΠΈ ΠΏΠΎΡ‚Ρ€Π΅Π±ΠΈΡ‚Π΅Π»ΠΈ. Зависи ΠΎΡ‚ топологията Π½Π° Π²Π°ΡˆΠ°Ρ‚Π° ΠΌΡ€Π΅ΠΆΠ° ΠΈ " +"настройки, ΠΈ Ρ‰Π΅ влияС само Π½Π° Π²Ρ€ΡŠΠ·ΠΊΠΈΡ‚Π΅ ΠΏΠΎΠΊΠ°Π·Π²Π°Π½ΠΈ Π½Π° ΠΏΠΎΡ‚Ρ€Π΅Π±ΠΈΡ‚Π΅Π»ΠΈΡ‚Π΅. Π€ΠΎΡ€ΠΌΠ°Ρ‚ΡŠΡ‚ " +"Π΅ HOST:PORT ΠΈ ΠΏΠΎ ΠΏΠΎΠ΄Ρ€Π°Π·Π±ΠΈΡ€Π°Π½Π΅ (localhost) СдинствСно Π΅ подходящ Π·Π° Π΄ΠΎΡΡ‚ΡŠΠΏ " +"ΠΎΡ‚ самия ΡΡŠΡ€Π²ΡŠΡ€." + +#. module: document_ftp +#: model:ir.model,name:document_ftp.model_knowledge_config_settings +msgid "knowledge.config.settings" +msgstr "" + +#. module: document_ftp +#: model:ir.actions.act_url,name:document_ftp.action_document_browse +msgid "Browse Files" +msgstr "ΠŸΡ€Π΅Π³Π»Π΅Π΄ Π½Π° Ρ„Π°ΠΉΠ»ΠΎΠ²Π΅Ρ‚Π΅" + +#. module: document_ftp +#: help:knowledge.config.settings,document_ftp_url:0 +msgid "Click the url to browse the documents" +msgstr "" + +#. module: document_ftp +#: field:document.ftp.browse,url:0 +msgid "FTP Server" +msgstr "FTP ΡΡŠΡ€Π²ΡŠΡ€" + +#. module: document_ftp +#: model:ir.actions.act_window,name:document_ftp.action_config_auto_directory +msgid "FTP Server Configuration" +msgstr "Настройки Π½Π° FTP ΡΡŠΡ€Π²ΡŠΡ€" + +#. module: document_ftp +#: field:knowledge.config.settings,document_ftp_url:0 +msgid "Browse Documents" +msgstr "" + +#. module: document_ftp +#: view:document.ftp.browse:0 +msgid "_Browse" +msgstr "_Π Π°Π·Π³Π»Π΅ΠΆΠ΄Π°Π½Π΅" + +#. module: document_ftp +#: help:document.ftp.configuration,host:0 +msgid "" +"Server address or IP and port to which users should connect to for DMS access" +msgstr "" +"АдрСс Π½Π° ΡΡŠΡ€Π²ΡŠΡ€Π° ΠΈΠ»ΠΈ IP ΠΈ ΠΏΠΎΡ€Ρ‚Π°, към ΠΊΠΎΠΉΡ‚ΠΎ ΠΏΠΎΡ‚Ρ€Π΅Π±ΠΈΡ‚Π΅Π»ΠΈΡ‚Π΅ трябва Π΄Π° сС ΡΠ²ΡŠΡ€ΠΆΠ΅ " +"Π·Π° DMS Π·Π° Π΄ΠΎΡΡ‚ΡŠΠΏ" + +#. module: document_ftp +#: model:ir.ui.menu,name:document_ftp.menu_document_browse +msgid "Shared Repository (FTP)" +msgstr "Π‘ΠΏΠΎΠ΄Π΅Π»Π΅Π½ΠΎ Ρ…Ρ€Π°Π½ΠΈΠΈΠ»ΠΈΡ‰Π΅ (FTP)" + +#. module: document_ftp +#: field:document.ftp.configuration,host:0 +msgid "Address" +msgstr "АдрСс" + +#. module: document_ftp +#: view:document.ftp.browse:0 +msgid "Cancel" +msgstr "" + +#. module: document_ftp +#: model:ir.model,name:document_ftp.model_document_ftp_browse +msgid "Document FTP Browse" +msgstr "ΠŸΡ€Π΅Π³Π»Π΅Π΄ Π½Π° FTP Π΄ΠΎΠΊΡƒΠΌΠ΅Π½Ρ‚ΠΈ" + +#. module: document_ftp +#: view:document.ftp.configuration:0 +msgid "Knowledge Application Configuration" +msgstr "Настройки Π½Π° ΠΏΡ€ΠΈΠ»ΠΎΠΆΠ΅Π½ΠΈΠ΅ Π—ΠΠΠΠ˜Π―" + +#. module: document_ftp +#: model:ir.actions.act_window,name:document_ftp.action_ftp_browse +msgid "Document Browse" +msgstr "ΠŸΡ€Π΅Π³Π»Π΅Π΄ Π½Π° Π΄ΠΎΠΊΡƒΠΌΠ΅Π½Ρ‚" + +#. module: document_ftp +#: view:document.ftp.browse:0 +msgid "or" +msgstr "" + +#. module: document_ftp +#: view:document.ftp.browse:0 +msgid "Browse Document" +msgstr "ΠŸΡ€Π΅Π³Π»Π΅Π΄ Π½Π° Π”ΠΎΠΊΡƒΠΌΠ΅Π½Ρ‚" + +#. module: document_ftp +#: view:document.ftp.configuration:0 +msgid "res_config_contents" +msgstr "res_config_contents" diff --git a/document_ftp/i18n/ca.po b/document_ftp/i18n/ca.po new file mode 100644 index 00000000..7e6e202c --- /dev/null +++ b/document_ftp/i18n/ca.po @@ -0,0 +1,131 @@ +# Catalan translation for openobject-addons +# Copyright (c) 2012 Rosetta Contributors and Canonical Ltd 2012 +# This file is distributed under the same license as the openobject-addons package. +# FIRST AUTHOR , 2012. +# +msgid "" +msgstr "" +"Project-Id-Version: openobject-addons\n" +"Report-Msgid-Bugs-To: FULL NAME \n" +"POT-Creation-Date: 2013-06-07 19:36+0000\n" +"PO-Revision-Date: 2012-12-21 23:00+0000\n" +"Last-Translator: FULL NAME \n" +"Language-Team: Catalan \n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"X-Launchpad-Export-Date: 2013-11-21 06:07+0000\n" +"X-Generator: Launchpad (build 16831)\n" + +#. module: document_ftp +#: view:document.ftp.configuration:0 +msgid "Configure FTP Server" +msgstr "Configura servidor FTP" + +#. module: document_ftp +#: model:ir.model,name:document_ftp.model_document_ftp_configuration +msgid "Auto Directory Configuration" +msgstr "ConfiguraciΓ³ automΓ tica de directoris" + +#. module: document_ftp +#: view:document.ftp.configuration:0 +msgid "" +"Indicate the network address on which your OpenERP server should be " +"reachable for end-users. This depends on your network topology and " +"configuration, and will only affect the links displayed to the users. The " +"format is HOST:PORT and the default host (localhost) is only suitable for " +"access from the server machine itself.." +msgstr "" +"Indiqueu l'adreΓ§a de xarxa en la que el vostre servidor d'OpenERP hauria " +"d'estar accessible per als usuaris finals. AixΓ² depΓ¨n de la vostra topologia " +"de xarxa i configuraciΓ³, i nomΓ©s afectara als enllaΓ§os mostrats als usuaris. " +"El formato Γ©s SERVIDOR:PORT i el servidor per defecte (localhost) nomΓ©s Γ©s " +"adequat per a l'accΓ©s des de la prΓ²pia mΓ quina del servidor." + +#. module: document_ftp +#: model:ir.model,name:document_ftp.model_knowledge_config_settings +msgid "knowledge.config.settings" +msgstr "" + +#. module: document_ftp +#: model:ir.actions.act_url,name:document_ftp.action_document_browse +msgid "Browse Files" +msgstr "Navega pels fitxers" + +#. module: document_ftp +#: help:knowledge.config.settings,document_ftp_url:0 +msgid "Click the url to browse the documents" +msgstr "" + +#. module: document_ftp +#: field:document.ftp.browse,url:0 +msgid "FTP Server" +msgstr "Servidor FTP" + +#. module: document_ftp +#: model:ir.actions.act_window,name:document_ftp.action_config_auto_directory +msgid "FTP Server Configuration" +msgstr "ConfiguraciΓ³ del servidor FTP" + +#. module: document_ftp +#: field:knowledge.config.settings,document_ftp_url:0 +msgid "Browse Documents" +msgstr "" + +#. module: document_ftp +#: view:document.ftp.browse:0 +msgid "_Browse" +msgstr "_Navega" + +#. module: document_ftp +#: help:document.ftp.configuration,host:0 +msgid "" +"Server address or IP and port to which users should connect to for DMS access" +msgstr "" +"AdreΓ§a del servidor o IP i el port per accedir al sistema de gestiΓ³ de " +"documents." + +#. module: document_ftp +#: model:ir.ui.menu,name:document_ftp.menu_document_browse +msgid "Shared Repository (FTP)" +msgstr "Directorio compartido de documentos (FTP)" + +#. module: document_ftp +#: field:document.ftp.configuration,host:0 +msgid "Address" +msgstr "AdreΓ§a" + +#. module: document_ftp +#: view:document.ftp.browse:0 +msgid "Cancel" +msgstr "" + +#. module: document_ftp +#: model:ir.model,name:document_ftp.model_document_ftp_browse +msgid "Document FTP Browse" +msgstr "Navega pels documents per FTP" + +#. module: document_ftp +#: view:document.ftp.configuration:0 +msgid "Knowledge Application Configuration" +msgstr "ConfiguraciΓ³ aplicaciΓ³ del coneixement" + +#. module: document_ftp +#: model:ir.actions.act_window,name:document_ftp.action_ftp_browse +msgid "Document Browse" +msgstr "Navega pels documents" + +#. module: document_ftp +#: view:document.ftp.browse:0 +msgid "or" +msgstr "" + +#. module: document_ftp +#: view:document.ftp.browse:0 +msgid "Browse Document" +msgstr "Navega pels documents" + +#. module: document_ftp +#: view:document.ftp.configuration:0 +msgid "res_config_contents" +msgstr "res_config_continguts" diff --git a/document_ftp/i18n/cs.po b/document_ftp/i18n/cs.po new file mode 100644 index 00000000..1af83c60 --- /dev/null +++ b/document_ftp/i18n/cs.po @@ -0,0 +1,130 @@ +# Czech translation for openobject-addons +# Copyright (c) 2012 Rosetta Contributors and Canonical Ltd 2012 +# This file is distributed under the same license as the openobject-addons package. +# FIRST AUTHOR , 2012. +# +msgid "" +msgstr "" +"Project-Id-Version: openobject-addons\n" +"Report-Msgid-Bugs-To: FULL NAME \n" +"POT-Creation-Date: 2013-06-07 19:36+0000\n" +"PO-Revision-Date: 2012-12-21 23:00+0000\n" +"Last-Translator: FULL NAME \n" +"Language-Team: Czech \n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"X-Launchpad-Export-Date: 2013-11-21 06:07+0000\n" +"X-Generator: Launchpad (build 16831)\n" + +#. module: document_ftp +#: view:document.ftp.configuration:0 +msgid "Configure FTP Server" +msgstr "Konfigurovat FTP server" + +#. module: document_ftp +#: model:ir.model,name:document_ftp.model_document_ftp_configuration +msgid "Auto Directory Configuration" +msgstr "AutomatickΓ‘ konfigurace adresΓ‘Ε™Ε―" + +#. module: document_ftp +#: view:document.ftp.configuration:0 +msgid "" +"Indicate the network address on which your OpenERP server should be " +"reachable for end-users. This depends on your network topology and " +"configuration, and will only affect the links displayed to the users. The " +"format is HOST:PORT and the default host (localhost) is only suitable for " +"access from the server machine itself.." +msgstr "" +"Značí adresu sΓ­tΔ›, na kterΓ© by mΔ›l bΓ½t pro koncovΓ© uΕΎivatel dostupnΓ½ vΓ‘Ε‘ " +"OpenERP server. To zΓ‘visΓ­ na vaΕ‘Γ­ sΓ­Ε₯ovΓ© topologii a nastavenΓ­ a ovlivnΓ­ to " +"pouze odkazy zobrazenΓ© uΕΎivatelΕ―m. FormΓ‘t je POĆÍTAČ:PORT a vΓ½chozΓ­ počítač " +"(localhost) je vhodnΓ½ pouze pro pΕ™Γ­stup ze samotnΓ©ho serveru." + +#. module: document_ftp +#: model:ir.model,name:document_ftp.model_knowledge_config_settings +msgid "knowledge.config.settings" +msgstr "" + +#. module: document_ftp +#: model:ir.actions.act_url,name:document_ftp.action_document_browse +msgid "Browse Files" +msgstr "ProchΓ‘zet soubory" + +#. module: document_ftp +#: help:knowledge.config.settings,document_ftp_url:0 +msgid "Click the url to browse the documents" +msgstr "" + +#. module: document_ftp +#: field:document.ftp.browse,url:0 +msgid "FTP Server" +msgstr "FTP server" + +#. module: document_ftp +#: model:ir.actions.act_window,name:document_ftp.action_config_auto_directory +msgid "FTP Server Configuration" +msgstr "Konfigurace FTP serveru" + +#. module: document_ftp +#: field:knowledge.config.settings,document_ftp_url:0 +msgid "Browse Documents" +msgstr "" + +#. module: document_ftp +#: view:document.ftp.browse:0 +msgid "_Browse" +msgstr "_ProchΓ‘zet" + +#. module: document_ftp +#: help:document.ftp.configuration,host:0 +msgid "" +"Server address or IP and port to which users should connect to for DMS access" +msgstr "" +"Adresa serveru nebo IP a port, ke kterΓ©mu by se mΔ›li uΕΎivatelΓ© pΕ™ipojit pro " +"pΕ™Γ­stup DMS" + +#. module: document_ftp +#: model:ir.ui.menu,name:document_ftp.menu_document_browse +msgid "Shared Repository (FTP)" +msgstr "SdΓ­lenΓ© ΓΊloΕΎiΕ‘tΔ› (FTP)" + +#. module: document_ftp +#: field:document.ftp.configuration,host:0 +msgid "Address" +msgstr "Adresa" + +#. module: document_ftp +#: view:document.ftp.browse:0 +msgid "Cancel" +msgstr "" + +#. module: document_ftp +#: model:ir.model,name:document_ftp.model_document_ftp_browse +msgid "Document FTP Browse" +msgstr "ProchΓ‘zenΓ­ FTP dokumentΕ―" + +#. module: document_ftp +#: view:document.ftp.configuration:0 +msgid "Knowledge Application Configuration" +msgstr "NastavenΓ­ aplikace znalostΓ­" + +#. module: document_ftp +#: model:ir.actions.act_window,name:document_ftp.action_ftp_browse +msgid "Document Browse" +msgstr "ProchΓ‘zenΓ­ dokumentΕ―" + +#. module: document_ftp +#: view:document.ftp.browse:0 +msgid "or" +msgstr "" + +#. module: document_ftp +#: view:document.ftp.browse:0 +msgid "Browse Document" +msgstr "ProchΓ‘zet dokument" + +#. module: document_ftp +#: view:document.ftp.configuration:0 +msgid "res_config_contents" +msgstr "res_config_contents" diff --git a/document_ftp/i18n/da.po b/document_ftp/i18n/da.po new file mode 100644 index 00000000..53ee5596 --- /dev/null +++ b/document_ftp/i18n/da.po @@ -0,0 +1,124 @@ +# Danish translation for openobject-addons +# Copyright (c) 2012 Rosetta Contributors and Canonical Ltd 2012 +# This file is distributed under the same license as the openobject-addons package. +# FIRST AUTHOR , 2012. +# +msgid "" +msgstr "" +"Project-Id-Version: openobject-addons\n" +"Report-Msgid-Bugs-To: FULL NAME \n" +"POT-Creation-Date: 2013-06-07 19:36+0000\n" +"PO-Revision-Date: 2012-12-21 23:00+0000\n" +"Last-Translator: FULL NAME \n" +"Language-Team: Danish \n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"X-Launchpad-Export-Date: 2013-11-21 06:07+0000\n" +"X-Generator: Launchpad (build 16831)\n" + +#. module: document_ftp +#: view:document.ftp.configuration:0 +msgid "Configure FTP Server" +msgstr "" + +#. module: document_ftp +#: model:ir.model,name:document_ftp.model_document_ftp_configuration +msgid "Auto Directory Configuration" +msgstr "" + +#. module: document_ftp +#: view:document.ftp.configuration:0 +msgid "" +"Indicate the network address on which your OpenERP server should be " +"reachable for end-users. This depends on your network topology and " +"configuration, and will only affect the links displayed to the users. The " +"format is HOST:PORT and the default host (localhost) is only suitable for " +"access from the server machine itself.." +msgstr "" + +#. module: document_ftp +#: model:ir.model,name:document_ftp.model_knowledge_config_settings +msgid "knowledge.config.settings" +msgstr "" + +#. module: document_ftp +#: model:ir.actions.act_url,name:document_ftp.action_document_browse +msgid "Browse Files" +msgstr "" + +#. module: document_ftp +#: help:knowledge.config.settings,document_ftp_url:0 +msgid "Click the url to browse the documents" +msgstr "" + +#. module: document_ftp +#: field:document.ftp.browse,url:0 +msgid "FTP Server" +msgstr "" + +#. module: document_ftp +#: model:ir.actions.act_window,name:document_ftp.action_config_auto_directory +msgid "FTP Server Configuration" +msgstr "" + +#. module: document_ftp +#: field:knowledge.config.settings,document_ftp_url:0 +msgid "Browse Documents" +msgstr "" + +#. module: document_ftp +#: view:document.ftp.browse:0 +msgid "_Browse" +msgstr "" + +#. module: document_ftp +#: help:document.ftp.configuration,host:0 +msgid "" +"Server address or IP and port to which users should connect to for DMS access" +msgstr "" + +#. module: document_ftp +#: model:ir.ui.menu,name:document_ftp.menu_document_browse +msgid "Shared Repository (FTP)" +msgstr "" + +#. module: document_ftp +#: field:document.ftp.configuration,host:0 +msgid "Address" +msgstr "" + +#. module: document_ftp +#: view:document.ftp.browse:0 +msgid "Cancel" +msgstr "" + +#. module: document_ftp +#: model:ir.model,name:document_ftp.model_document_ftp_browse +msgid "Document FTP Browse" +msgstr "" + +#. module: document_ftp +#: view:document.ftp.configuration:0 +msgid "Knowledge Application Configuration" +msgstr "" + +#. module: document_ftp +#: model:ir.actions.act_window,name:document_ftp.action_ftp_browse +msgid "Document Browse" +msgstr "" + +#. module: document_ftp +#: view:document.ftp.browse:0 +msgid "or" +msgstr "" + +#. module: document_ftp +#: view:document.ftp.browse:0 +msgid "Browse Document" +msgstr "" + +#. module: document_ftp +#: view:document.ftp.configuration:0 +msgid "res_config_contents" +msgstr "" diff --git a/document_ftp/i18n/de.po b/document_ftp/i18n/de.po new file mode 100644 index 00000000..b584a975 --- /dev/null +++ b/document_ftp/i18n/de.po @@ -0,0 +1,131 @@ +# German translation for openobject-addons +# Copyright (c) 2012 Rosetta Contributors and Canonical Ltd 2012 +# This file is distributed under the same license as the openobject-addons package. +# FIRST AUTHOR , 2012. +# +msgid "" +msgstr "" +"Project-Id-Version: openobject-addons\n" +"Report-Msgid-Bugs-To: FULL NAME \n" +"POT-Creation-Date: 2013-06-07 19:36+0000\n" +"PO-Revision-Date: 2014-01-27 18:34+0000\n" +"Last-Translator: Ralf Hilgenstock \n" +"Language-Team: German \n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"X-Launchpad-Export-Date: 2014-01-28 07:02+0000\n" +"X-Generator: Launchpad (build 16914)\n" + +#. module: document_ftp +#: view:document.ftp.configuration:0 +msgid "Configure FTP Server" +msgstr "FTP Server konfigurieren" + +#. module: document_ftp +#: model:ir.model,name:document_ftp.model_document_ftp_configuration +msgid "Auto Directory Configuration" +msgstr "Auto Konfigurator Verzeichnisse" + +#. module: document_ftp +#: view:document.ftp.configuration:0 +msgid "" +"Indicate the network address on which your OpenERP server should be " +"reachable for end-users. This depends on your network topology and " +"configuration, and will only affect the links displayed to the users. The " +"format is HOST:PORT and the default host (localhost) is only suitable for " +"access from the server machine itself.." +msgstr "" +"Zeigt die Netzwerk IP Adresse ΓΌber die der OpenERP Server fΓΌr Endbenutzer " +"erreicht werden kann. Diese Adresse hΓ€ngt ab von der Architektur des " +"Netzwerks und wird lediglich einen Einfluss auf die Anzeige der Adresse beim " +"Benutzer haben. Das Format der Adresse ist HOST:PORT, wobei der Standard " +"Host (localhost) lediglich gΓΌltig ist fΓΌr einen direkten Zugriff vom Server " +"selbst." + +#. module: document_ftp +#: model:ir.model,name:document_ftp.model_knowledge_config_settings +msgid "knowledge.config.settings" +msgstr "Wissensdatenbank.Konfiguration.Einstellungen" + +#. module: document_ftp +#: model:ir.actions.act_url,name:document_ftp.action_document_browse +msgid "Browse Files" +msgstr "Dateien durchsuchen" + +#. module: document_ftp +#: help:knowledge.config.settings,document_ftp_url:0 +msgid "Click the url to browse the documents" +msgstr "URL klicken, um die Dokumente anzuzeigen" + +#. module: document_ftp +#: field:document.ftp.browse,url:0 +msgid "FTP Server" +msgstr "FTP-Server" + +#. module: document_ftp +#: model:ir.actions.act_window,name:document_ftp.action_config_auto_directory +msgid "FTP Server Configuration" +msgstr "Konfiguration FTP-Server" + +#. module: document_ftp +#: field:knowledge.config.settings,document_ftp_url:0 +msgid "Browse Documents" +msgstr "Dokumente anzeigen" + +#. module: document_ftp +#: view:document.ftp.browse:0 +msgid "_Browse" +msgstr "Suchen" + +#. module: document_ftp +#: help:document.ftp.configuration,host:0 +msgid "" +"Server address or IP and port to which users should connect to for DMS access" +msgstr "" +"Servername oder IP und Port ΓΌber den Benutzer per FTP auf Dokumente zugreifen" + +#. module: document_ftp +#: model:ir.ui.menu,name:document_ftp.menu_document_browse +msgid "Shared Repository (FTP)" +msgstr "" + +#. module: document_ftp +#: field:document.ftp.configuration,host:0 +msgid "Address" +msgstr "Adresse" + +#. module: document_ftp +#: view:document.ftp.browse:0 +msgid "Cancel" +msgstr "Abbrechen" + +#. module: document_ftp +#: model:ir.model,name:document_ftp.model_document_ftp_browse +msgid "Document FTP Browse" +msgstr "Suche per FTP-Dokument" + +#. module: document_ftp +#: view:document.ftp.configuration:0 +msgid "Knowledge Application Configuration" +msgstr "Knowledge-Anwendung konfigurieren" + +#. module: document_ftp +#: model:ir.actions.act_window,name:document_ftp.action_ftp_browse +msgid "Document Browse" +msgstr "Dokument suchen" + +#. module: document_ftp +#: view:document.ftp.browse:0 +msgid "or" +msgstr "oder" + +#. module: document_ftp +#: view:document.ftp.browse:0 +msgid "Browse Document" +msgstr "Dokument suchen" + +#. module: document_ftp +#: view:document.ftp.configuration:0 +msgid "res_config_contents" +msgstr "res_config_contents" diff --git a/document_ftp/i18n/document_ftp.pot b/document_ftp/i18n/document_ftp.pot new file mode 100644 index 00000000..8c842c35 --- /dev/null +++ b/document_ftp/i18n/document_ftp.pot @@ -0,0 +1,117 @@ +# Translation of OpenERP Server. +# This file contains the translation of the following modules: +# * document_ftp +# +msgid "" +msgstr "" +"Project-Id-Version: OpenERP Server 7.0\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2013-06-07 19:36+0000\n" +"PO-Revision-Date: 2013-06-07 19:36+0000\n" +"Last-Translator: <>\n" +"Language-Team: \n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: \n" +"Plural-Forms: \n" + +#. module: document_ftp +#: view:document.ftp.configuration:0 +msgid "Configure FTP Server" +msgstr "" + +#. module: document_ftp +#: model:ir.model,name:document_ftp.model_document_ftp_configuration +msgid "Auto Directory Configuration" +msgstr "" + +#. module: document_ftp +#: view:document.ftp.configuration:0 +msgid "Indicate the network address on which your OpenERP server should be reachable for end-users. This depends on your network topology and configuration, and will only affect the links displayed to the users. The format is HOST:PORT and the default host (localhost) is only suitable for access from the server machine itself.." +msgstr "" + +#. module: document_ftp +#: model:ir.model,name:document_ftp.model_knowledge_config_settings +msgid "knowledge.config.settings" +msgstr "" + +#. module: document_ftp +#: model:ir.actions.act_url,name:document_ftp.action_document_browse +msgid "Browse Files" +msgstr "" + +#. module: document_ftp +#: help:knowledge.config.settings,document_ftp_url:0 +msgid "Click the url to browse the documents" +msgstr "" + +#. module: document_ftp +#: field:document.ftp.browse,url:0 +msgid "FTP Server" +msgstr "" + +#. module: document_ftp +#: model:ir.actions.act_window,name:document_ftp.action_config_auto_directory +msgid "FTP Server Configuration" +msgstr "" + +#. module: document_ftp +#: field:knowledge.config.settings,document_ftp_url:0 +msgid "Browse Documents" +msgstr "" + +#. module: document_ftp +#: view:document.ftp.browse:0 +msgid "_Browse" +msgstr "" + +#. module: document_ftp +#: help:document.ftp.configuration,host:0 +msgid "Server address or IP and port to which users should connect to for DMS access" +msgstr "" + +#. module: document_ftp +#: model:ir.ui.menu,name:document_ftp.menu_document_browse +msgid "Shared Repository (FTP)" +msgstr "" + +#. module: document_ftp +#: field:document.ftp.configuration,host:0 +msgid "Address" +msgstr "" + +#. module: document_ftp +#: view:document.ftp.browse:0 +msgid "Cancel" +msgstr "" + +#. module: document_ftp +#: model:ir.model,name:document_ftp.model_document_ftp_browse +msgid "Document FTP Browse" +msgstr "" + +#. module: document_ftp +#: view:document.ftp.configuration:0 +msgid "Knowledge Application Configuration" +msgstr "" + +#. module: document_ftp +#: model:ir.actions.act_window,name:document_ftp.action_ftp_browse +msgid "Document Browse" +msgstr "" + +#. module: document_ftp +#: view:document.ftp.browse:0 +msgid "or" +msgstr "" + +#. module: document_ftp +#: view:document.ftp.browse:0 +msgid "Browse Document" +msgstr "" + +#. module: document_ftp +#: view:document.ftp.configuration:0 +msgid "res_config_contents" +msgstr "" + diff --git a/document_ftp/i18n/el.po b/document_ftp/i18n/el.po new file mode 100644 index 00000000..5f684b7f --- /dev/null +++ b/document_ftp/i18n/el.po @@ -0,0 +1,124 @@ +# Greek translation for openobject-addons +# Copyright (c) 2012 Rosetta Contributors and Canonical Ltd 2012 +# This file is distributed under the same license as the openobject-addons package. +# FIRST AUTHOR , 2012. +# +msgid "" +msgstr "" +"Project-Id-Version: openobject-addons\n" +"Report-Msgid-Bugs-To: FULL NAME \n" +"POT-Creation-Date: 2013-06-07 19:36+0000\n" +"PO-Revision-Date: 2012-12-21 23:00+0000\n" +"Last-Translator: FULL NAME \n" +"Language-Team: Greek \n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"X-Launchpad-Export-Date: 2013-11-21 06:07+0000\n" +"X-Generator: Launchpad (build 16831)\n" + +#. module: document_ftp +#: view:document.ftp.configuration:0 +msgid "Configure FTP Server" +msgstr "" + +#. module: document_ftp +#: model:ir.model,name:document_ftp.model_document_ftp_configuration +msgid "Auto Directory Configuration" +msgstr "" + +#. module: document_ftp +#: view:document.ftp.configuration:0 +msgid "" +"Indicate the network address on which your OpenERP server should be " +"reachable for end-users. This depends on your network topology and " +"configuration, and will only affect the links displayed to the users. The " +"format is HOST:PORT and the default host (localhost) is only suitable for " +"access from the server machine itself.." +msgstr "" + +#. module: document_ftp +#: model:ir.model,name:document_ftp.model_knowledge_config_settings +msgid "knowledge.config.settings" +msgstr "" + +#. module: document_ftp +#: model:ir.actions.act_url,name:document_ftp.action_document_browse +msgid "Browse Files" +msgstr "ΠΡριΞγηση στα ΑρχΡία" + +#. module: document_ftp +#: help:knowledge.config.settings,document_ftp_url:0 +msgid "Click the url to browse the documents" +msgstr "" + +#. module: document_ftp +#: field:document.ftp.browse,url:0 +msgid "FTP Server" +msgstr "ΕξυπηρΡτητΞΟ‚ FTP" + +#. module: document_ftp +#: model:ir.actions.act_window,name:document_ftp.action_config_auto_directory +msgid "FTP Server Configuration" +msgstr "ΠαραμΡτροποίηση ΔιακομιστΠFTP" + +#. module: document_ftp +#: field:knowledge.config.settings,document_ftp_url:0 +msgid "Browse Documents" +msgstr "" + +#. module: document_ftp +#: view:document.ftp.browse:0 +msgid "_Browse" +msgstr "_ΠΡριΞγηση" + +#. module: document_ftp +#: help:document.ftp.configuration,host:0 +msgid "" +"Server address or IP and port to which users should connect to for DMS access" +msgstr "" + +#. module: document_ftp +#: model:ir.ui.menu,name:document_ftp.menu_document_browse +msgid "Shared Repository (FTP)" +msgstr "" + +#. module: document_ftp +#: field:document.ftp.configuration,host:0 +msgid "Address" +msgstr "ΔιΡύθυνση" + +#. module: document_ftp +#: view:document.ftp.browse:0 +msgid "Cancel" +msgstr "" + +#. module: document_ftp +#: model:ir.model,name:document_ftp.model_document_ftp_browse +msgid "Document FTP Browse" +msgstr "" + +#. module: document_ftp +#: view:document.ftp.configuration:0 +msgid "Knowledge Application Configuration" +msgstr "" + +#. module: document_ftp +#: model:ir.actions.act_window,name:document_ftp.action_ftp_browse +msgid "Document Browse" +msgstr "" + +#. module: document_ftp +#: view:document.ftp.browse:0 +msgid "or" +msgstr "" + +#. module: document_ftp +#: view:document.ftp.browse:0 +msgid "Browse Document" +msgstr "" + +#. module: document_ftp +#: view:document.ftp.configuration:0 +msgid "res_config_contents" +msgstr "" diff --git a/document_ftp/i18n/en_GB.po b/document_ftp/i18n/en_GB.po new file mode 100644 index 00000000..2460f119 --- /dev/null +++ b/document_ftp/i18n/en_GB.po @@ -0,0 +1,130 @@ +# English (United Kingdom) translation for openobject-addons +# Copyright (c) 2012 Rosetta Contributors and Canonical Ltd 2012 +# This file is distributed under the same license as the openobject-addons package. +# FIRST AUTHOR , 2012. +# +msgid "" +msgstr "" +"Project-Id-Version: openobject-addons\n" +"Report-Msgid-Bugs-To: FULL NAME \n" +"POT-Creation-Date: 2013-06-07 19:36+0000\n" +"PO-Revision-Date: 2013-02-07 17:09+0000\n" +"Last-Translator: mrx5682 \n" +"Language-Team: English (United Kingdom) \n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"X-Launchpad-Export-Date: 2013-11-21 06:07+0000\n" +"X-Generator: Launchpad (build 16831)\n" + +#. module: document_ftp +#: view:document.ftp.configuration:0 +msgid "Configure FTP Server" +msgstr "Configure FTP Server" + +#. module: document_ftp +#: model:ir.model,name:document_ftp.model_document_ftp_configuration +msgid "Auto Directory Configuration" +msgstr "Auto Directory Configuration" + +#. module: document_ftp +#: view:document.ftp.configuration:0 +msgid "" +"Indicate the network address on which your OpenERP server should be " +"reachable for end-users. This depends on your network topology and " +"configuration, and will only affect the links displayed to the users. The " +"format is HOST:PORT and the default host (localhost) is only suitable for " +"access from the server machine itself.." +msgstr "" +"Indicate the network address on which your OpenERP server should be " +"reachable for end-users. This depends on your network topology and " +"configuration, and will only affect the links displayed to the users. The " +"format is HOST:PORT and the default host (localhost) is only suitable for " +"access from the server machine itself.." + +#. module: document_ftp +#: model:ir.model,name:document_ftp.model_knowledge_config_settings +msgid "knowledge.config.settings" +msgstr "knowledge.config.settings" + +#. module: document_ftp +#: model:ir.actions.act_url,name:document_ftp.action_document_browse +msgid "Browse Files" +msgstr "Browse Files" + +#. module: document_ftp +#: help:knowledge.config.settings,document_ftp_url:0 +msgid "Click the url to browse the documents" +msgstr "Click the url to browse the documents" + +#. module: document_ftp +#: field:document.ftp.browse,url:0 +msgid "FTP Server" +msgstr "FTP Server" + +#. module: document_ftp +#: model:ir.actions.act_window,name:document_ftp.action_config_auto_directory +msgid "FTP Server Configuration" +msgstr "FTP Server Configuration" + +#. module: document_ftp +#: field:knowledge.config.settings,document_ftp_url:0 +msgid "Browse Documents" +msgstr "Browse Documents" + +#. module: document_ftp +#: view:document.ftp.browse:0 +msgid "_Browse" +msgstr "_Browse" + +#. module: document_ftp +#: help:document.ftp.configuration,host:0 +msgid "" +"Server address or IP and port to which users should connect to for DMS access" +msgstr "" +"Server address or IP and port to which users should connect to for DMS access" + +#. module: document_ftp +#: model:ir.ui.menu,name:document_ftp.menu_document_browse +msgid "Shared Repository (FTP)" +msgstr "Shared Repository (FTP)" + +#. module: document_ftp +#: field:document.ftp.configuration,host:0 +msgid "Address" +msgstr "Address" + +#. module: document_ftp +#: view:document.ftp.browse:0 +msgid "Cancel" +msgstr "Cancel" + +#. module: document_ftp +#: model:ir.model,name:document_ftp.model_document_ftp_browse +msgid "Document FTP Browse" +msgstr "Document FTP Browse" + +#. module: document_ftp +#: view:document.ftp.configuration:0 +msgid "Knowledge Application Configuration" +msgstr "Knowledge Application Configuration" + +#. module: document_ftp +#: model:ir.actions.act_window,name:document_ftp.action_ftp_browse +msgid "Document Browse" +msgstr "Document Browse" + +#. module: document_ftp +#: view:document.ftp.browse:0 +msgid "or" +msgstr "or" + +#. module: document_ftp +#: view:document.ftp.browse:0 +msgid "Browse Document" +msgstr "Browse Document" + +#. module: document_ftp +#: view:document.ftp.configuration:0 +msgid "res_config_contents" +msgstr "res_config_contents" diff --git a/document_ftp/i18n/es.po b/document_ftp/i18n/es.po new file mode 100644 index 00000000..0ae47486 --- /dev/null +++ b/document_ftp/i18n/es.po @@ -0,0 +1,131 @@ +# Spanish translation for openobject-addons +# Copyright (c) 2012 Rosetta Contributors and Canonical Ltd 2012 +# This file is distributed under the same license as the openobject-addons package. +# FIRST AUTHOR , 2012. +# +msgid "" +msgstr "" +"Project-Id-Version: openobject-addons\n" +"Report-Msgid-Bugs-To: FULL NAME \n" +"POT-Creation-Date: 2013-06-07 19:36+0000\n" +"PO-Revision-Date: 2012-12-21 23:00+0000\n" +"Last-Translator: FULL NAME \n" +"Language-Team: Spanish \n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"X-Launchpad-Export-Date: 2013-11-21 06:07+0000\n" +"X-Generator: Launchpad (build 16831)\n" + +#. module: document_ftp +#: view:document.ftp.configuration:0 +msgid "Configure FTP Server" +msgstr "Configurar servidor FTP" + +#. module: document_ftp +#: model:ir.model,name:document_ftp.model_document_ftp_configuration +msgid "Auto Directory Configuration" +msgstr "ConfiguraciΓ³n automΓ‘tica de directorios" + +#. module: document_ftp +#: view:document.ftp.configuration:0 +msgid "" +"Indicate the network address on which your OpenERP server should be " +"reachable for end-users. This depends on your network topology and " +"configuration, and will only affect the links displayed to the users. The " +"format is HOST:PORT and the default host (localhost) is only suitable for " +"access from the server machine itself.." +msgstr "" +"Indique la direcciΓ³n de red en la cual su servidor de OpenERP deberΓ­a estar " +"accesible para los usuarios finales. Esto depende de su topologΓ­a de red y " +"configuraciΓ³n, y sΓ³lo afectarΓ‘ a los enlaces mostrados a los usuarios. El " +"formato es SERVIDOR:PUERTO y el servidor por defecto (localhost) sΓ³lo es " +"adecuado para el acceso desde la propia mΓ‘quina del servidor." + +#. module: document_ftp +#: model:ir.model,name:document_ftp.model_knowledge_config_settings +msgid "knowledge.config.settings" +msgstr "ParΓ‘metros de configuraciΓ³n de la base de conocimiento" + +#. module: document_ftp +#: model:ir.actions.act_url,name:document_ftp.action_document_browse +msgid "Browse Files" +msgstr "Navegar por los archivos" + +#. module: document_ftp +#: help:knowledge.config.settings,document_ftp_url:0 +msgid "Click the url to browse the documents" +msgstr "Pulse sobre el enlace para acceder a los documentos" + +#. module: document_ftp +#: field:document.ftp.browse,url:0 +msgid "FTP Server" +msgstr "Servidor FTP" + +#. module: document_ftp +#: model:ir.actions.act_window,name:document_ftp.action_config_auto_directory +msgid "FTP Server Configuration" +msgstr "ConfiguraciΓ³n del servidor FTP" + +#. module: document_ftp +#: field:knowledge.config.settings,document_ftp_url:0 +msgid "Browse Documents" +msgstr "Navegue por los documentos" + +#. module: document_ftp +#: view:document.ftp.browse:0 +msgid "_Browse" +msgstr "_Navegar" + +#. module: document_ftp +#: help:document.ftp.configuration,host:0 +msgid "" +"Server address or IP and port to which users should connect to for DMS access" +msgstr "" +"DirecciΓ³n del servidor o IP y el puerto para acceder al sistema de gestiΓ³n " +"de documentos." + +#. module: document_ftp +#: model:ir.ui.menu,name:document_ftp.menu_document_browse +msgid "Shared Repository (FTP)" +msgstr "Directorio compartido de documentos (FTP)" + +#. module: document_ftp +#: field:document.ftp.configuration,host:0 +msgid "Address" +msgstr "DirecciΓ³n" + +#. module: document_ftp +#: view:document.ftp.browse:0 +msgid "Cancel" +msgstr "Cancelar" + +#. module: document_ftp +#: model:ir.model,name:document_ftp.model_document_ftp_browse +msgid "Document FTP Browse" +msgstr "Navegar por los documentos por FTP" + +#. module: document_ftp +#: view:document.ftp.configuration:0 +msgid "Knowledge Application Configuration" +msgstr "ConfiguraciΓ³n aplicaciΓ³n del conocimiento" + +#. module: document_ftp +#: model:ir.actions.act_window,name:document_ftp.action_ftp_browse +msgid "Document Browse" +msgstr "Navegar por los documentos" + +#. module: document_ftp +#: view:document.ftp.browse:0 +msgid "or" +msgstr "o" + +#. module: document_ftp +#: view:document.ftp.browse:0 +msgid "Browse Document" +msgstr "Navegar por los documentos" + +#. module: document_ftp +#: view:document.ftp.configuration:0 +msgid "res_config_contents" +msgstr "res_config_contenidos" diff --git a/document_ftp/i18n/es_CR.po b/document_ftp/i18n/es_CR.po new file mode 100644 index 00000000..f524eccb --- /dev/null +++ b/document_ftp/i18n/es_CR.po @@ -0,0 +1,131 @@ +# Spanish (Costa Rica) translation for openobject-addons +# Copyright (c) 2012 Rosetta Contributors and Canonical Ltd 2012 +# This file is distributed under the same license as the openobject-addons package. +# FIRST AUTHOR , 2012. +# +msgid "" +msgstr "" +"Project-Id-Version: openobject-addons\n" +"Report-Msgid-Bugs-To: FULL NAME \n" +"POT-Creation-Date: 2013-06-07 19:36+0000\n" +"PO-Revision-Date: 2012-12-21 23:00+0000\n" +"Last-Translator: FULL NAME \n" +"Language-Team: Spanish (Costa Rica) \n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"X-Launchpad-Export-Date: 2013-11-21 06:07+0000\n" +"X-Generator: Launchpad (build 16831)\n" + +#. module: document_ftp +#: view:document.ftp.configuration:0 +msgid "Configure FTP Server" +msgstr "Configurar servidor FTP" + +#. module: document_ftp +#: model:ir.model,name:document_ftp.model_document_ftp_configuration +msgid "Auto Directory Configuration" +msgstr "ConfiguraciΓ³n automΓ‘tica de directorios" + +#. module: document_ftp +#: view:document.ftp.configuration:0 +msgid "" +"Indicate the network address on which your OpenERP server should be " +"reachable for end-users. This depends on your network topology and " +"configuration, and will only affect the links displayed to the users. The " +"format is HOST:PORT and the default host (localhost) is only suitable for " +"access from the server machine itself.." +msgstr "" +"Indique la direcciΓ³n de red en la cual su servidor de OpenERP deberΓ­a estar " +"accesible para los usuarios finales. Esto depende de su topologΓ­a de red y " +"configuraciΓ³n, y sΓ³lo afectarΓ‘ a los enlaces mostrados a los usuarios. El " +"formato es SERVIDOR:PUERTO y el servidor por defecto (localhost) sΓ³lo es " +"adecuado para el acceso desde la propia mΓ‘quina del servidor." + +#. module: document_ftp +#: model:ir.model,name:document_ftp.model_knowledge_config_settings +msgid "knowledge.config.settings" +msgstr "" + +#. module: document_ftp +#: model:ir.actions.act_url,name:document_ftp.action_document_browse +msgid "Browse Files" +msgstr "Navegar por los archivos" + +#. module: document_ftp +#: help:knowledge.config.settings,document_ftp_url:0 +msgid "Click the url to browse the documents" +msgstr "" + +#. module: document_ftp +#: field:document.ftp.browse,url:0 +msgid "FTP Server" +msgstr "Servidor FTP" + +#. module: document_ftp +#: model:ir.actions.act_window,name:document_ftp.action_config_auto_directory +msgid "FTP Server Configuration" +msgstr "ConfiguraciΓ³n del servidor FTP" + +#. module: document_ftp +#: field:knowledge.config.settings,document_ftp_url:0 +msgid "Browse Documents" +msgstr "" + +#. module: document_ftp +#: view:document.ftp.browse:0 +msgid "_Browse" +msgstr "_Navegar" + +#. module: document_ftp +#: help:document.ftp.configuration,host:0 +msgid "" +"Server address or IP and port to which users should connect to for DMS access" +msgstr "" +"DirecciΓ³n del servidor o IP y el puerto para acceder al sistema de gestiΓ³n " +"de documentos." + +#. module: document_ftp +#: model:ir.ui.menu,name:document_ftp.menu_document_browse +msgid "Shared Repository (FTP)" +msgstr "Directorio compartido de documentos (FTP)" + +#. module: document_ftp +#: field:document.ftp.configuration,host:0 +msgid "Address" +msgstr "DirecciΓ³n" + +#. module: document_ftp +#: view:document.ftp.browse:0 +msgid "Cancel" +msgstr "" + +#. module: document_ftp +#: model:ir.model,name:document_ftp.model_document_ftp_browse +msgid "Document FTP Browse" +msgstr "Navegar por los documentos por FTP" + +#. module: document_ftp +#: view:document.ftp.configuration:0 +msgid "Knowledge Application Configuration" +msgstr "ConfiguraciΓ³n aplicaciΓ³n del conocimiento" + +#. module: document_ftp +#: model:ir.actions.act_window,name:document_ftp.action_ftp_browse +msgid "Document Browse" +msgstr "Navegar por los documentos" + +#. module: document_ftp +#: view:document.ftp.browse:0 +msgid "or" +msgstr "" + +#. module: document_ftp +#: view:document.ftp.browse:0 +msgid "Browse Document" +msgstr "Navegar por los documentos" + +#. module: document_ftp +#: view:document.ftp.configuration:0 +msgid "res_config_contents" +msgstr "res_config_contenidos" diff --git a/document_ftp/i18n/es_EC.po b/document_ftp/i18n/es_EC.po new file mode 100644 index 00000000..929a63c7 --- /dev/null +++ b/document_ftp/i18n/es_EC.po @@ -0,0 +1,129 @@ +# Spanish (Ecuador) translation for openobject-addons +# Copyright (c) 2012 Rosetta Contributors and Canonical Ltd 2012 +# This file is distributed under the same license as the openobject-addons package. +# FIRST AUTHOR , 2012. +# +msgid "" +msgstr "" +"Project-Id-Version: openobject-addons\n" +"Report-Msgid-Bugs-To: FULL NAME \n" +"POT-Creation-Date: 2013-06-07 19:36+0000\n" +"PO-Revision-Date: 2012-12-21 23:00+0000\n" +"Last-Translator: FULL NAME \n" +"Language-Team: Spanish (Ecuador) \n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"X-Launchpad-Export-Date: 2013-11-21 06:07+0000\n" +"X-Generator: Launchpad (build 16831)\n" + +#. module: document_ftp +#: view:document.ftp.configuration:0 +msgid "Configure FTP Server" +msgstr "Configurar servidor FTP" + +#. module: document_ftp +#: model:ir.model,name:document_ftp.model_document_ftp_configuration +msgid "Auto Directory Configuration" +msgstr "ConfiguraciΓ³n automΓ‘tica de directorios" + +#. module: document_ftp +#: view:document.ftp.configuration:0 +msgid "" +"Indicate the network address on which your OpenERP server should be " +"reachable for end-users. This depends on your network topology and " +"configuration, and will only affect the links displayed to the users. The " +"format is HOST:PORT and the default host (localhost) is only suitable for " +"access from the server machine itself.." +msgstr "" +"Indique la direcciΓ³n de red en la cual su servidor de OpenERP deberΓ­a estar " +"disponible para los usuarios finales. Esto depende de su topologΓ­a de red y " +"configuraciΓ³n, y sΓ³lo afectarΓ‘ a los enlaces mostrados a los usuarios. El " +"formato es ANFITRIΓ“N:PUERTO y el anfitriΓ³n por defecto (localhost) sΓ³lo es " +"adecuado para acceso desde la propia mΓ‘quina del servidor." + +#. module: document_ftp +#: model:ir.model,name:document_ftp.model_knowledge_config_settings +msgid "knowledge.config.settings" +msgstr "" + +#. module: document_ftp +#: model:ir.actions.act_url,name:document_ftp.action_document_browse +msgid "Browse Files" +msgstr "Examinar archivos" + +#. module: document_ftp +#: help:knowledge.config.settings,document_ftp_url:0 +msgid "Click the url to browse the documents" +msgstr "" + +#. module: document_ftp +#: field:document.ftp.browse,url:0 +msgid "FTP Server" +msgstr "Servidor FTP" + +#. module: document_ftp +#: model:ir.actions.act_window,name:document_ftp.action_config_auto_directory +msgid "FTP Server Configuration" +msgstr "ConfiguraciΓ³n de Servidor FTP" + +#. module: document_ftp +#: field:knowledge.config.settings,document_ftp_url:0 +msgid "Browse Documents" +msgstr "" + +#. module: document_ftp +#: view:document.ftp.browse:0 +msgid "_Browse" +msgstr "_Examinar" + +#. module: document_ftp +#: help:document.ftp.configuration,host:0 +msgid "" +"Server address or IP and port to which users should connect to for DMS access" +msgstr "DirecciΓ³n del servidor o IP y el puerto para acceder al DMS." + +#. module: document_ftp +#: model:ir.ui.menu,name:document_ftp.menu_document_browse +msgid "Shared Repository (FTP)" +msgstr "Biblioteca compartida de mΓ³dulos (FTP)" + +#. module: document_ftp +#: field:document.ftp.configuration,host:0 +msgid "Address" +msgstr "DirecciΓ³n" + +#. module: document_ftp +#: view:document.ftp.browse:0 +msgid "Cancel" +msgstr "" + +#. module: document_ftp +#: model:ir.model,name:document_ftp.model_document_ftp_browse +msgid "Document FTP Browse" +msgstr "Examinar documento por FTP" + +#. module: document_ftp +#: view:document.ftp.configuration:0 +msgid "Knowledge Application Configuration" +msgstr "ConfiguraciΓ³n aplicaciΓ³n del conocimiento" + +#. module: document_ftp +#: model:ir.actions.act_window,name:document_ftp.action_ftp_browse +msgid "Document Browse" +msgstr "Examinar documento" + +#. module: document_ftp +#: view:document.ftp.browse:0 +msgid "or" +msgstr "" + +#. module: document_ftp +#: view:document.ftp.browse:0 +msgid "Browse Document" +msgstr "Examinar documento" + +#. module: document_ftp +#: view:document.ftp.configuration:0 +msgid "res_config_contents" +msgstr "Configurar Contenidos" diff --git a/document_ftp/i18n/es_MX.po b/document_ftp/i18n/es_MX.po new file mode 100644 index 00000000..4c568d45 --- /dev/null +++ b/document_ftp/i18n/es_MX.po @@ -0,0 +1,148 @@ +# Spanish translation for openobject-addons +# Copyright (c) 2010 Rosetta Contributors and Canonical Ltd 2010 +# This file is distributed under the same license as the openobject-addons package. +# FIRST AUTHOR , 2010. +# +msgid "" +msgstr "" +"Project-Id-Version: openobject-addons\n" +"Report-Msgid-Bugs-To: FULL NAME \n" +"POT-Creation-Date: 2011-01-11 11:15+0000\n" +"PO-Revision-Date: 2011-01-13 22:59+0000\n" +"Last-Translator: Jordi Esteve (www.zikzakmedia.com) " +"\n" +"Language-Team: Spanish \n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"X-Launchpad-Export-Date: 2011-09-05 05:55+0000\n" +"X-Generator: Launchpad (build 13830)\n" + +#. module: document_ftp +#: model:ir.model,name:document_ftp.model_document_ftp_configuration +msgid "Auto Directory Configuration" +msgstr "ConfiguraciΓ³n automΓ‘tica de directorios" + +#. module: document_ftp +#: view:document.ftp.configuration:0 +msgid "" +"Indicate the network address on which your OpenERP server should be " +"reachable for end-users. This depends on your network topology and " +"configuration, and will only affect the links displayed to the users. The " +"format is HOST:PORT and the default host (localhost) is only suitable for " +"access from the server machine itself.." +msgstr "" +"Indique la direcciΓ³n de red en la cual su servidor de OpenERP deberΓ­a estar " +"accesible para los usuarios finales. Esto depende de su topologΓ­a de red y " +"configuraciΓ³n, y sΓ³lo afectarΓ‘ a los enlaces mostrados a los usuarios. El " +"formato es SERVIDOR:PUERTO y el servidor por defecto (localhost) sΓ³lo es " +"adecuado para el acceso desde la propia mΓ‘quina del servidor." + +#. module: document_ftp +#: field:document.ftp.configuration,progress:0 +msgid "Configuration Progress" +msgstr "Progreso de la configuraciΓ³n" + +#. module: document_ftp +#: model:ir.actions.url,name:document_ftp.action_document_browse +msgid "Browse Files" +msgstr "Navegar por los archivos" + +#. module: document_ftp +#: field:document.ftp.configuration,config_logo:0 +msgid "Image" +msgstr "Imagen" + +#. module: document_ftp +#: field:document.ftp.configuration,host:0 +msgid "Address" +msgstr "DirecciΓ³n" + +#. module: document_ftp +#: field:document.ftp.browse,url:0 +msgid "FTP Server" +msgstr "Servidor FTP" + +#. module: document_ftp +#: model:ir.actions.act_window,name:document_ftp.action_config_auto_directory +msgid "FTP Server Configuration" +msgstr "ConfiguraciΓ³n del servidor FTP" + +#. module: document_ftp +#: model:ir.module.module,description:document_ftp.module_meta_information +msgid "" +"This is a support FTP Interface with document management system.\n" +" With this module you would not only be able to access documents through " +"OpenERP\n" +" but you would also be able to connect with them through the file system " +"using the\n" +" FTP client.\n" +msgstr "" +"Proporciona un interfaz FTP para el sistema de gestiΓ³n de documentos.\n" +" AdemΓ‘s del acceso a los documentos a travΓ©s de OpenERP, este mΓ³dulo\n" +" permite acceder a los mismos a travΓ©s del sistema de archivos " +"utilizando un\n" +" cliente FTP.\n" + +#. module: document_ftp +#: view:document.ftp.browse:0 +msgid "_Browse" +msgstr "_Navegar" + +#. module: document_ftp +#: help:document.ftp.configuration,host:0 +msgid "" +"Server address or IP and port to which users should connect to for DMS access" +msgstr "" +"DirecciΓ³n del servidor o IP y el puerto para acceder al sistema de gestiΓ³n " +"de documentos." + +#. module: document_ftp +#: model:ir.ui.menu,name:document_ftp.menu_document_browse +msgid "Shared Repository (FTP)" +msgstr "Directorio compartido de documentos (FTP)" + +#. module: document_ftp +#: view:document.ftp.browse:0 +msgid "_Cancel" +msgstr "_Cancelar" + +#. module: document_ftp +#: view:document.ftp.configuration:0 +msgid "Configure FTP Server" +msgstr "Configurar servidor FTP" + +#. module: document_ftp +#: model:ir.module.module,shortdesc:document_ftp.module_meta_information +msgid "Integrated FTP Server with Document Management System" +msgstr "Servidor FTP integrado al sistema de gestiΓ³n de documentos" + +#. module: document_ftp +#: view:document.ftp.configuration:0 +msgid "title" +msgstr "tΓ­tulo" + +#. module: document_ftp +#: model:ir.model,name:document_ftp.model_document_ftp_browse +msgid "Document FTP Browse" +msgstr "Navegar por los documentos por FTP" + +#. module: document_ftp +#: view:document.ftp.configuration:0 +msgid "Knowledge Application Configuration" +msgstr "ConfiguraciΓ³n aplicaciΓ³n del conocimiento" + +#. module: document_ftp +#: model:ir.actions.act_window,name:document_ftp.action_ftp_browse +msgid "Document Browse" +msgstr "Navegar por los documentos" + +#. module: document_ftp +#: view:document.ftp.browse:0 +msgid "Browse Document" +msgstr "Navegar por los documentos" + +#. module: document_ftp +#: view:document.ftp.configuration:0 +msgid "res_config_contents" +msgstr "res_config_contenidos" diff --git a/document_ftp/i18n/es_PY.po b/document_ftp/i18n/es_PY.po new file mode 100644 index 00000000..1ee94005 --- /dev/null +++ b/document_ftp/i18n/es_PY.po @@ -0,0 +1,126 @@ +# Spanish (Paraguay) translation for openobject-addons +# Copyright (c) 2012 Rosetta Contributors and Canonical Ltd 2012 +# This file is distributed under the same license as the openobject-addons package. +# FIRST AUTHOR , 2012. +# +msgid "" +msgstr "" +"Project-Id-Version: openobject-addons\n" +"Report-Msgid-Bugs-To: FULL NAME \n" +"POT-Creation-Date: 2013-06-07 19:36+0000\n" +"PO-Revision-Date: 2012-12-21 23:00+0000\n" +"Last-Translator: FULL NAME \n" +"Language-Team: Spanish (Paraguay) \n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"X-Launchpad-Export-Date: 2013-11-21 06:07+0000\n" +"X-Generator: Launchpad (build 16831)\n" + +#. module: document_ftp +#: view:document.ftp.configuration:0 +msgid "Configure FTP Server" +msgstr "Configurar servidor FTP" + +#. module: document_ftp +#: model:ir.model,name:document_ftp.model_document_ftp_configuration +msgid "Auto Directory Configuration" +msgstr "ConfiguraciΓ³n automΓ‘tica de carpetas" + +#. module: document_ftp +#: view:document.ftp.configuration:0 +msgid "" +"Indicate the network address on which your OpenERP server should be " +"reachable for end-users. This depends on your network topology and " +"configuration, and will only affect the links displayed to the users. The " +"format is HOST:PORT and the default host (localhost) is only suitable for " +"access from the server machine itself.." +msgstr "" + +#. module: document_ftp +#: model:ir.model,name:document_ftp.model_knowledge_config_settings +msgid "knowledge.config.settings" +msgstr "" + +#. module: document_ftp +#: model:ir.actions.act_url,name:document_ftp.action_document_browse +msgid "Browse Files" +msgstr "Navegar por los archivos" + +#. module: document_ftp +#: help:knowledge.config.settings,document_ftp_url:0 +msgid "Click the url to browse the documents" +msgstr "" + +#. module: document_ftp +#: field:document.ftp.browse,url:0 +msgid "FTP Server" +msgstr "Servidor FTP" + +#. module: document_ftp +#: model:ir.actions.act_window,name:document_ftp.action_config_auto_directory +msgid "FTP Server Configuration" +msgstr "ConfiguraciΓ³n del servidor FTP" + +#. module: document_ftp +#: field:knowledge.config.settings,document_ftp_url:0 +msgid "Browse Documents" +msgstr "" + +#. module: document_ftp +#: view:document.ftp.browse:0 +msgid "_Browse" +msgstr "_Navegar" + +#. module: document_ftp +#: help:document.ftp.configuration,host:0 +msgid "" +"Server address or IP and port to which users should connect to for DMS access" +msgstr "" +"DirecciΓ³n del servidor o IP y el puerto para acceder al sistema de gestiΓ³n " +"de documentos." + +#. module: document_ftp +#: model:ir.ui.menu,name:document_ftp.menu_document_browse +msgid "Shared Repository (FTP)" +msgstr "Directorio compartido de documentos (FTP)" + +#. module: document_ftp +#: field:document.ftp.configuration,host:0 +msgid "Address" +msgstr "DirecciΓ³n" + +#. module: document_ftp +#: view:document.ftp.browse:0 +msgid "Cancel" +msgstr "" + +#. module: document_ftp +#: model:ir.model,name:document_ftp.model_document_ftp_browse +msgid "Document FTP Browse" +msgstr "Navegar por los documentos por FTP" + +#. module: document_ftp +#: view:document.ftp.configuration:0 +msgid "Knowledge Application Configuration" +msgstr "ConfiguraciΓ³n de la aplicaciΓ³n de conocimiento" + +#. module: document_ftp +#: model:ir.actions.act_window,name:document_ftp.action_ftp_browse +msgid "Document Browse" +msgstr "Navegar por los documentos" + +#. module: document_ftp +#: view:document.ftp.browse:0 +msgid "or" +msgstr "" + +#. module: document_ftp +#: view:document.ftp.browse:0 +msgid "Browse Document" +msgstr "Navegar por los documentos" + +#. module: document_ftp +#: view:document.ftp.configuration:0 +msgid "res_config_contents" +msgstr "res_config_contenidos" diff --git a/document_ftp/i18n/es_VE.po b/document_ftp/i18n/es_VE.po new file mode 100644 index 00000000..4c568d45 --- /dev/null +++ b/document_ftp/i18n/es_VE.po @@ -0,0 +1,148 @@ +# Spanish translation for openobject-addons +# Copyright (c) 2010 Rosetta Contributors and Canonical Ltd 2010 +# This file is distributed under the same license as the openobject-addons package. +# FIRST AUTHOR , 2010. +# +msgid "" +msgstr "" +"Project-Id-Version: openobject-addons\n" +"Report-Msgid-Bugs-To: FULL NAME \n" +"POT-Creation-Date: 2011-01-11 11:15+0000\n" +"PO-Revision-Date: 2011-01-13 22:59+0000\n" +"Last-Translator: Jordi Esteve (www.zikzakmedia.com) " +"\n" +"Language-Team: Spanish \n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"X-Launchpad-Export-Date: 2011-09-05 05:55+0000\n" +"X-Generator: Launchpad (build 13830)\n" + +#. module: document_ftp +#: model:ir.model,name:document_ftp.model_document_ftp_configuration +msgid "Auto Directory Configuration" +msgstr "ConfiguraciΓ³n automΓ‘tica de directorios" + +#. module: document_ftp +#: view:document.ftp.configuration:0 +msgid "" +"Indicate the network address on which your OpenERP server should be " +"reachable for end-users. This depends on your network topology and " +"configuration, and will only affect the links displayed to the users. The " +"format is HOST:PORT and the default host (localhost) is only suitable for " +"access from the server machine itself.." +msgstr "" +"Indique la direcciΓ³n de red en la cual su servidor de OpenERP deberΓ­a estar " +"accesible para los usuarios finales. Esto depende de su topologΓ­a de red y " +"configuraciΓ³n, y sΓ³lo afectarΓ‘ a los enlaces mostrados a los usuarios. El " +"formato es SERVIDOR:PUERTO y el servidor por defecto (localhost) sΓ³lo es " +"adecuado para el acceso desde la propia mΓ‘quina del servidor." + +#. module: document_ftp +#: field:document.ftp.configuration,progress:0 +msgid "Configuration Progress" +msgstr "Progreso de la configuraciΓ³n" + +#. module: document_ftp +#: model:ir.actions.url,name:document_ftp.action_document_browse +msgid "Browse Files" +msgstr "Navegar por los archivos" + +#. module: document_ftp +#: field:document.ftp.configuration,config_logo:0 +msgid "Image" +msgstr "Imagen" + +#. module: document_ftp +#: field:document.ftp.configuration,host:0 +msgid "Address" +msgstr "DirecciΓ³n" + +#. module: document_ftp +#: field:document.ftp.browse,url:0 +msgid "FTP Server" +msgstr "Servidor FTP" + +#. module: document_ftp +#: model:ir.actions.act_window,name:document_ftp.action_config_auto_directory +msgid "FTP Server Configuration" +msgstr "ConfiguraciΓ³n del servidor FTP" + +#. module: document_ftp +#: model:ir.module.module,description:document_ftp.module_meta_information +msgid "" +"This is a support FTP Interface with document management system.\n" +" With this module you would not only be able to access documents through " +"OpenERP\n" +" but you would also be able to connect with them through the file system " +"using the\n" +" FTP client.\n" +msgstr "" +"Proporciona un interfaz FTP para el sistema de gestiΓ³n de documentos.\n" +" AdemΓ‘s del acceso a los documentos a travΓ©s de OpenERP, este mΓ³dulo\n" +" permite acceder a los mismos a travΓ©s del sistema de archivos " +"utilizando un\n" +" cliente FTP.\n" + +#. module: document_ftp +#: view:document.ftp.browse:0 +msgid "_Browse" +msgstr "_Navegar" + +#. module: document_ftp +#: help:document.ftp.configuration,host:0 +msgid "" +"Server address or IP and port to which users should connect to for DMS access" +msgstr "" +"DirecciΓ³n del servidor o IP y el puerto para acceder al sistema de gestiΓ³n " +"de documentos." + +#. module: document_ftp +#: model:ir.ui.menu,name:document_ftp.menu_document_browse +msgid "Shared Repository (FTP)" +msgstr "Directorio compartido de documentos (FTP)" + +#. module: document_ftp +#: view:document.ftp.browse:0 +msgid "_Cancel" +msgstr "_Cancelar" + +#. module: document_ftp +#: view:document.ftp.configuration:0 +msgid "Configure FTP Server" +msgstr "Configurar servidor FTP" + +#. module: document_ftp +#: model:ir.module.module,shortdesc:document_ftp.module_meta_information +msgid "Integrated FTP Server with Document Management System" +msgstr "Servidor FTP integrado al sistema de gestiΓ³n de documentos" + +#. module: document_ftp +#: view:document.ftp.configuration:0 +msgid "title" +msgstr "tΓ­tulo" + +#. module: document_ftp +#: model:ir.model,name:document_ftp.model_document_ftp_browse +msgid "Document FTP Browse" +msgstr "Navegar por los documentos por FTP" + +#. module: document_ftp +#: view:document.ftp.configuration:0 +msgid "Knowledge Application Configuration" +msgstr "ConfiguraciΓ³n aplicaciΓ³n del conocimiento" + +#. module: document_ftp +#: model:ir.actions.act_window,name:document_ftp.action_ftp_browse +msgid "Document Browse" +msgstr "Navegar por los documentos" + +#. module: document_ftp +#: view:document.ftp.browse:0 +msgid "Browse Document" +msgstr "Navegar por los documentos" + +#. module: document_ftp +#: view:document.ftp.configuration:0 +msgid "res_config_contents" +msgstr "res_config_contenidos" diff --git a/document_ftp/i18n/et.po b/document_ftp/i18n/et.po new file mode 100644 index 00000000..a0d22c5c --- /dev/null +++ b/document_ftp/i18n/et.po @@ -0,0 +1,124 @@ +# Estonian translation for openobject-addons +# Copyright (c) 2012 Rosetta Contributors and Canonical Ltd 2012 +# This file is distributed under the same license as the openobject-addons package. +# FIRST AUTHOR , 2012. +# +msgid "" +msgstr "" +"Project-Id-Version: openobject-addons\n" +"Report-Msgid-Bugs-To: FULL NAME \n" +"POT-Creation-Date: 2013-06-07 19:36+0000\n" +"PO-Revision-Date: 2012-12-21 23:00+0000\n" +"Last-Translator: FULL NAME \n" +"Language-Team: Estonian \n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"X-Launchpad-Export-Date: 2013-11-21 06:07+0000\n" +"X-Generator: Launchpad (build 16831)\n" + +#. module: document_ftp +#: view:document.ftp.configuration:0 +msgid "Configure FTP Server" +msgstr "" + +#. module: document_ftp +#: model:ir.model,name:document_ftp.model_document_ftp_configuration +msgid "Auto Directory Configuration" +msgstr "" + +#. module: document_ftp +#: view:document.ftp.configuration:0 +msgid "" +"Indicate the network address on which your OpenERP server should be " +"reachable for end-users. This depends on your network topology and " +"configuration, and will only affect the links displayed to the users. The " +"format is HOST:PORT and the default host (localhost) is only suitable for " +"access from the server machine itself.." +msgstr "" + +#. module: document_ftp +#: model:ir.model,name:document_ftp.model_knowledge_config_settings +msgid "knowledge.config.settings" +msgstr "" + +#. module: document_ftp +#: model:ir.actions.act_url,name:document_ftp.action_document_browse +msgid "Browse Files" +msgstr "Failide sirvimine" + +#. module: document_ftp +#: help:knowledge.config.settings,document_ftp_url:0 +msgid "Click the url to browse the documents" +msgstr "" + +#. module: document_ftp +#: field:document.ftp.browse,url:0 +msgid "FTP Server" +msgstr "FTP server" + +#. module: document_ftp +#: model:ir.actions.act_window,name:document_ftp.action_config_auto_directory +msgid "FTP Server Configuration" +msgstr "FTP serveri konfiguratsioon" + +#. module: document_ftp +#: field:knowledge.config.settings,document_ftp_url:0 +msgid "Browse Documents" +msgstr "" + +#. module: document_ftp +#: view:document.ftp.browse:0 +msgid "_Browse" +msgstr "_Sirvi" + +#. module: document_ftp +#: help:document.ftp.configuration,host:0 +msgid "" +"Server address or IP and port to which users should connect to for DMS access" +msgstr "" + +#. module: document_ftp +#: model:ir.ui.menu,name:document_ftp.menu_document_browse +msgid "Shared Repository (FTP)" +msgstr "" + +#. module: document_ftp +#: field:document.ftp.configuration,host:0 +msgid "Address" +msgstr "Aadress" + +#. module: document_ftp +#: view:document.ftp.browse:0 +msgid "Cancel" +msgstr "" + +#. module: document_ftp +#: model:ir.model,name:document_ftp.model_document_ftp_browse +msgid "Document FTP Browse" +msgstr "" + +#. module: document_ftp +#: view:document.ftp.configuration:0 +msgid "Knowledge Application Configuration" +msgstr "" + +#. module: document_ftp +#: model:ir.actions.act_window,name:document_ftp.action_ftp_browse +msgid "Document Browse" +msgstr "" + +#. module: document_ftp +#: view:document.ftp.browse:0 +msgid "or" +msgstr "" + +#. module: document_ftp +#: view:document.ftp.browse:0 +msgid "Browse Document" +msgstr "" + +#. module: document_ftp +#: view:document.ftp.configuration:0 +msgid "res_config_contents" +msgstr "" diff --git a/document_ftp/i18n/fi.po b/document_ftp/i18n/fi.po new file mode 100644 index 00000000..2a1684c2 --- /dev/null +++ b/document_ftp/i18n/fi.po @@ -0,0 +1,126 @@ +# Finnish translation for openobject-addons +# Copyright (c) 2012 Rosetta Contributors and Canonical Ltd 2012 +# This file is distributed under the same license as the openobject-addons package. +# FIRST AUTHOR , 2012. +# +msgid "" +msgstr "" +"Project-Id-Version: openobject-addons\n" +"Report-Msgid-Bugs-To: FULL NAME \n" +"POT-Creation-Date: 2013-06-07 19:36+0000\n" +"PO-Revision-Date: 2012-12-21 23:00+0000\n" +"Last-Translator: FULL NAME \n" +"Language-Team: Finnish \n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"X-Launchpad-Export-Date: 2013-11-21 06:07+0000\n" +"X-Generator: Launchpad (build 16831)\n" + +#. module: document_ftp +#: view:document.ftp.configuration:0 +msgid "Configure FTP Server" +msgstr "Konfiguroi FTP palvelin" + +#. module: document_ftp +#: model:ir.model,name:document_ftp.model_document_ftp_configuration +msgid "Auto Directory Configuration" +msgstr "Automaattinen hakemistojen konfigurointi" + +#. module: document_ftp +#: view:document.ftp.configuration:0 +msgid "" +"Indicate the network address on which your OpenERP server should be " +"reachable for end-users. This depends on your network topology and " +"configuration, and will only affect the links displayed to the users. The " +"format is HOST:PORT and the default host (localhost) is only suitable for " +"access from the server machine itself.." +msgstr "" + +#. module: document_ftp +#: model:ir.model,name:document_ftp.model_knowledge_config_settings +msgid "knowledge.config.settings" +msgstr "" + +#. module: document_ftp +#: model:ir.actions.act_url,name:document_ftp.action_document_browse +msgid "Browse Files" +msgstr "Selaa tiedostoja" + +#. module: document_ftp +#: help:knowledge.config.settings,document_ftp_url:0 +msgid "Click the url to browse the documents" +msgstr "" + +#. module: document_ftp +#: field:document.ftp.browse,url:0 +msgid "FTP Server" +msgstr "FTP-palvelin" + +#. module: document_ftp +#: model:ir.actions.act_window,name:document_ftp.action_config_auto_directory +msgid "FTP Server Configuration" +msgstr "FTP palvelimen konfiguraatio" + +#. module: document_ftp +#: field:knowledge.config.settings,document_ftp_url:0 +msgid "Browse Documents" +msgstr "" + +#. module: document_ftp +#: view:document.ftp.browse:0 +msgid "_Browse" +msgstr "Selaa" + +#. module: document_ftp +#: help:document.ftp.configuration,host:0 +msgid "" +"Server address or IP and port to which users should connect to for DMS access" +msgstr "" +"Palvelimen osoite tai IP ja portti mihin kΓ€yttΓ€jien tulisi ottaa yhteyttΓ€ " +"DMS:n kΓ€yttΓΆΓ€ varten" + +#. module: document_ftp +#: model:ir.ui.menu,name:document_ftp.menu_document_browse +msgid "Shared Repository (FTP)" +msgstr "Jaettu tietolΓ€hde (FTP)" + +#. module: document_ftp +#: field:document.ftp.configuration,host:0 +msgid "Address" +msgstr "Osoite" + +#. module: document_ftp +#: view:document.ftp.browse:0 +msgid "Cancel" +msgstr "" + +#. module: document_ftp +#: model:ir.model,name:document_ftp.model_document_ftp_browse +msgid "Document FTP Browse" +msgstr "Dokumenttien FTP selailu" + +#. module: document_ftp +#: view:document.ftp.configuration:0 +msgid "Knowledge Application Configuration" +msgstr "Tiedonhallintaohjelmiston konfiguraatio" + +#. module: document_ftp +#: model:ir.actions.act_window,name:document_ftp.action_ftp_browse +msgid "Document Browse" +msgstr "Dokumenttien selailu" + +#. module: document_ftp +#: view:document.ftp.browse:0 +msgid "or" +msgstr "" + +#. module: document_ftp +#: view:document.ftp.browse:0 +msgid "Browse Document" +msgstr "Selaa dokumenttia" + +#. module: document_ftp +#: view:document.ftp.configuration:0 +msgid "res_config_contents" +msgstr "" diff --git a/document_ftp/i18n/fr.po b/document_ftp/i18n/fr.po new file mode 100644 index 00000000..655e01ce --- /dev/null +++ b/document_ftp/i18n/fr.po @@ -0,0 +1,132 @@ +# French translation for openobject-addons +# Copyright (c) 2012 Rosetta Contributors and Canonical Ltd 2012 +# This file is distributed under the same license as the openobject-addons package. +# FIRST AUTHOR , 2012. +# +msgid "" +msgstr "" +"Project-Id-Version: openobject-addons\n" +"Report-Msgid-Bugs-To: FULL NAME \n" +"POT-Creation-Date: 2013-06-07 19:36+0000\n" +"PO-Revision-Date: 2013-01-03 13:59+0000\n" +"Last-Translator: Florian Hatat \n" +"Language-Team: French \n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"X-Launchpad-Export-Date: 2013-11-21 06:07+0000\n" +"X-Generator: Launchpad (build 16831)\n" + +#. module: document_ftp +#: view:document.ftp.configuration:0 +msgid "Configure FTP Server" +msgstr "Configurer le serveur FTP" + +#. module: document_ftp +#: model:ir.model,name:document_ftp.model_document_ftp_configuration +msgid "Auto Directory Configuration" +msgstr "Configuration automatique des rΓ©pertoires" + +#. module: document_ftp +#: view:document.ftp.configuration:0 +msgid "" +"Indicate the network address on which your OpenERP server should be " +"reachable for end-users. This depends on your network topology and " +"configuration, and will only affect the links displayed to the users. The " +"format is HOST:PORT and the default host (localhost) is only suitable for " +"access from the server machine itself.." +msgstr "" +"Indiquez l'adresse rΓ©seau sur laquelle votre serveur OpenERP doit Γͺtre " +"accessible pour les utilisateurs finaux. Ceci dΓ©pend de la topologie de " +"votre rΓ©seau et de votre configuration, et il affectera seulement les liens " +"affichΓ© aux utilisateurs. Le format est HΓ”TE:PORT et l'hΓ΄te par dΓ©faut " +"(localhost) est seulement appropriΓ© pour des accΓ¨s depuis la machine du " +"serveur." + +#. module: document_ftp +#: model:ir.model,name:document_ftp.model_knowledge_config_settings +msgid "knowledge.config.settings" +msgstr "knowledge.config.settings" + +#. module: document_ftp +#: model:ir.actions.act_url,name:document_ftp.action_document_browse +msgid "Browse Files" +msgstr "Parcourir les fichiers" + +#. module: document_ftp +#: help:knowledge.config.settings,document_ftp_url:0 +msgid "Click the url to browse the documents" +msgstr "Cliquer sur l'url pour parcourir les documents" + +#. module: document_ftp +#: field:document.ftp.browse,url:0 +msgid "FTP Server" +msgstr "Serveur FTP" + +#. module: document_ftp +#: model:ir.actions.act_window,name:document_ftp.action_config_auto_directory +msgid "FTP Server Configuration" +msgstr "Configuration du serveur FTP" + +#. module: document_ftp +#: field:knowledge.config.settings,document_ftp_url:0 +msgid "Browse Documents" +msgstr "Parcourir les documents" + +#. module: document_ftp +#: view:document.ftp.browse:0 +msgid "_Browse" +msgstr "_Parcourir" + +#. module: document_ftp +#: help:document.ftp.configuration,host:0 +msgid "" +"Server address or IP and port to which users should connect to for DMS access" +msgstr "" +"Adresse du serveur ou adresse IP et port auquel les utilisateurs devraient " +"se connecter pour l'accΓ¨s au GED." + +#. module: document_ftp +#: model:ir.ui.menu,name:document_ftp.menu_document_browse +msgid "Shared Repository (FTP)" +msgstr "RΓ©pertoire partagΓ© (FTP)" + +#. module: document_ftp +#: field:document.ftp.configuration,host:0 +msgid "Address" +msgstr "Adresse" + +#. module: document_ftp +#: view:document.ftp.browse:0 +msgid "Cancel" +msgstr "Annuler" + +#. module: document_ftp +#: model:ir.model,name:document_ftp.model_document_ftp_browse +msgid "Document FTP Browse" +msgstr "Parcourir les documents FTP" + +#. module: document_ftp +#: view:document.ftp.configuration:0 +msgid "Knowledge Application Configuration" +msgstr "Configuration de l'application de gestion des connaissances" + +#. module: document_ftp +#: model:ir.actions.act_window,name:document_ftp.action_ftp_browse +msgid "Document Browse" +msgstr "Parcourez le document" + +#. module: document_ftp +#: view:document.ftp.browse:0 +msgid "or" +msgstr "ou" + +#. module: document_ftp +#: view:document.ftp.browse:0 +msgid "Browse Document" +msgstr "Parcourir le document" + +#. module: document_ftp +#: view:document.ftp.configuration:0 +msgid "res_config_contents" +msgstr "res_config_contents" diff --git a/document_ftp/i18n/gl.po b/document_ftp/i18n/gl.po new file mode 100644 index 00000000..d27e5656 --- /dev/null +++ b/document_ftp/i18n/gl.po @@ -0,0 +1,131 @@ +# Galician translation for openobject-addons +# Copyright (c) 2012 Rosetta Contributors and Canonical Ltd 2012 +# This file is distributed under the same license as the openobject-addons package. +# FIRST AUTHOR , 2012. +# +msgid "" +msgstr "" +"Project-Id-Version: openobject-addons\n" +"Report-Msgid-Bugs-To: FULL NAME \n" +"POT-Creation-Date: 2013-06-07 19:36+0000\n" +"PO-Revision-Date: 2012-12-21 23:00+0000\n" +"Last-Translator: FULL NAME \n" +"Language-Team: Galician \n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"X-Launchpad-Export-Date: 2013-11-21 06:07+0000\n" +"X-Generator: Launchpad (build 16831)\n" + +#. module: document_ftp +#: view:document.ftp.configuration:0 +msgid "Configure FTP Server" +msgstr "Configurar servidor FTP" + +#. module: document_ftp +#: model:ir.model,name:document_ftp.model_document_ftp_configuration +msgid "Auto Directory Configuration" +msgstr "ConfiguraciΓ³n automΓ‘tica de directorios" + +#. module: document_ftp +#: view:document.ftp.configuration:0 +msgid "" +"Indicate the network address on which your OpenERP server should be " +"reachable for end-users. This depends on your network topology and " +"configuration, and will only affect the links displayed to the users. The " +"format is HOST:PORT and the default host (localhost) is only suitable for " +"access from the server machine itself.." +msgstr "" +"Indique a direcciΓ³n de rede na que o seu servidor de OpenERP deberΓ­a estar " +"dispoΓ±ible para os usuarios finais. Isto depende da topoloxΓ­a de rede e da " +"configuraciΓ³n, e sΓ³ afectarΓ‘ aos enlaces mostrados aos usuarios. O formato Γ© " +"ANFITRIΓ“N:PORTO e o anfitriΓ³n por defecto (localhost) sΓ³ Γ© adecuado para " +"acceso desde a propia mΓ‘quina do servidor." + +#. module: document_ftp +#: model:ir.model,name:document_ftp.model_knowledge_config_settings +msgid "knowledge.config.settings" +msgstr "" + +#. module: document_ftp +#: model:ir.actions.act_url,name:document_ftp.action_document_browse +msgid "Browse Files" +msgstr "Procurar arquivos" + +#. module: document_ftp +#: help:knowledge.config.settings,document_ftp_url:0 +msgid "Click the url to browse the documents" +msgstr "" + +#. module: document_ftp +#: field:document.ftp.browse,url:0 +msgid "FTP Server" +msgstr "Servidor FTP" + +#. module: document_ftp +#: model:ir.actions.act_window,name:document_ftp.action_config_auto_directory +msgid "FTP Server Configuration" +msgstr "ConfiguraciΓ³n de servidor FTP" + +#. module: document_ftp +#: field:knowledge.config.settings,document_ftp_url:0 +msgid "Browse Documents" +msgstr "" + +#. module: document_ftp +#: view:document.ftp.browse:0 +msgid "_Browse" +msgstr "_Explorar" + +#. module: document_ftp +#: help:document.ftp.configuration,host:0 +msgid "" +"Server address or IP and port to which users should connect to for DMS access" +msgstr "" +"DirecciΓ³n do servidor ou IP e porto para que os usuarios podan conectar ao " +"acceso DMS." + +#. module: document_ftp +#: model:ir.ui.menu,name:document_ftp.menu_document_browse +msgid "Shared Repository (FTP)" +msgstr "Repositorio compartido (FTP)" + +#. module: document_ftp +#: field:document.ftp.configuration,host:0 +msgid "Address" +msgstr "Enderezo" + +#. module: document_ftp +#: view:document.ftp.browse:0 +msgid "Cancel" +msgstr "" + +#. module: document_ftp +#: model:ir.model,name:document_ftp.model_document_ftp_browse +msgid "Document FTP Browse" +msgstr "Examinar documento por FTP" + +#. module: document_ftp +#: view:document.ftp.configuration:0 +msgid "Knowledge Application Configuration" +msgstr "ConfiguraciΓ³n da aplicaciΓ³n do coΓ±ecemento" + +#. module: document_ftp +#: model:ir.actions.act_window,name:document_ftp.action_ftp_browse +msgid "Document Browse" +msgstr "Examinar documento" + +#. module: document_ftp +#: view:document.ftp.browse:0 +msgid "or" +msgstr "" + +#. module: document_ftp +#: view:document.ftp.browse:0 +msgid "Browse Document" +msgstr "Examinar documento" + +#. module: document_ftp +#: view:document.ftp.configuration:0 +msgid "res_config_contents" +msgstr "res_config_contents" diff --git a/document_ftp/i18n/hr.po b/document_ftp/i18n/hr.po new file mode 100644 index 00000000..6bab7ef4 --- /dev/null +++ b/document_ftp/i18n/hr.po @@ -0,0 +1,131 @@ +# Croatian translation for openobject-addons +# Copyright (c) 2012 Rosetta Contributors and Canonical Ltd 2012 +# This file is distributed under the same license as the openobject-addons package. +# FIRST AUTHOR , 2012. +# +msgid "" +msgstr "" +"Project-Id-Version: openobject-addons\n" +"Report-Msgid-Bugs-To: FULL NAME \n" +"POT-Creation-Date: 2013-06-07 19:36+0000\n" +"PO-Revision-Date: 2013-02-09 15:43+0000\n" +"Last-Translator: Davor BojkiΔ‡ \n" +"Language-Team: Croatian \n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"X-Launchpad-Export-Date: 2013-11-21 06:07+0000\n" +"X-Generator: Launchpad (build 16831)\n" + +#. module: document_ftp +#: view:document.ftp.configuration:0 +msgid "Configure FTP Server" +msgstr "Postavke FTP posluΕΎitelja" + +#. module: document_ftp +#: model:ir.model,name:document_ftp.model_document_ftp_configuration +msgid "Auto Directory Configuration" +msgstr "Automatska postava mapa" + +#. module: document_ftp +#: view:document.ftp.configuration:0 +msgid "" +"Indicate the network address on which your OpenERP server should be " +"reachable for end-users. This depends on your network topology and " +"configuration, and will only affect the links displayed to the users. The " +"format is HOST:PORT and the default host (localhost) is only suitable for " +"access from the server machine itself.." +msgstr "" +"Predstavlja adresu na mreΕΎi, na kojoj bi OpenERP trebao biti dostupan " +"krajnjim korisnicima. Ona ovisi o VaΕ‘oj topologiji mreΕΎe kao i postavkama, i " +"utječe jedino na linkove prikazane krajnjim korisnicima. Format zapisa je " +"HOST:PORT a zadani host (localhost) korisi se jedino za pristup sa samog " +"posluΕΎitelja." + +#. module: document_ftp +#: model:ir.model,name:document_ftp.model_knowledge_config_settings +msgid "knowledge.config.settings" +msgstr "" + +#. module: document_ftp +#: model:ir.actions.act_url,name:document_ftp.action_document_browse +msgid "Browse Files" +msgstr "PretraΕΎi datoteke" + +#. module: document_ftp +#: help:knowledge.config.settings,document_ftp_url:0 +msgid "Click the url to browse the documents" +msgstr "Kliknite na poveznicu za pregled dokumenata" + +#. module: document_ftp +#: field:document.ftp.browse,url:0 +msgid "FTP Server" +msgstr "FTP posluΕΎitelj" + +#. module: document_ftp +#: model:ir.actions.act_window,name:document_ftp.action_config_auto_directory +msgid "FTP Server Configuration" +msgstr "Postave FTP posluΕΎitelja" + +#. module: document_ftp +#: field:knowledge.config.settings,document_ftp_url:0 +msgid "Browse Documents" +msgstr "Pregled dokumenata" + +#. module: document_ftp +#: view:document.ftp.browse:0 +msgid "_Browse" +msgstr "_Pregledaj" + +#. module: document_ftp +#: help:document.ftp.configuration,host:0 +msgid "" +"Server address or IP and port to which users should connect to for DMS access" +msgstr "" +"IP adresa i port na koji se korisnici spajaju na DMS (sustav upravljana " +"dokumentima)" + +#. module: document_ftp +#: model:ir.ui.menu,name:document_ftp.menu_document_browse +msgid "Shared Repository (FTP)" +msgstr "Dijeljeni repozitorij (FTP)" + +#. module: document_ftp +#: field:document.ftp.configuration,host:0 +msgid "Address" +msgstr "IP adresa" + +#. module: document_ftp +#: view:document.ftp.browse:0 +msgid "Cancel" +msgstr "Odustani" + +#. module: document_ftp +#: model:ir.model,name:document_ftp.model_document_ftp_browse +msgid "Document FTP Browse" +msgstr "Pregled dokumenata na FTP" + +#. module: document_ftp +#: view:document.ftp.configuration:0 +msgid "Knowledge Application Configuration" +msgstr "" + +#. module: document_ftp +#: model:ir.actions.act_window,name:document_ftp.action_ftp_browse +msgid "Document Browse" +msgstr "Pregled dokumenta" + +#. module: document_ftp +#: view:document.ftp.browse:0 +msgid "or" +msgstr "ili" + +#. module: document_ftp +#: view:document.ftp.browse:0 +msgid "Browse Document" +msgstr "Pregled dokumenta" + +#. module: document_ftp +#: view:document.ftp.configuration:0 +msgid "res_config_contents" +msgstr "" diff --git a/document_ftp/i18n/hu.po b/document_ftp/i18n/hu.po new file mode 100644 index 00000000..9262c452 --- /dev/null +++ b/document_ftp/i18n/hu.po @@ -0,0 +1,131 @@ +# Hungarian translation for openobject-addons +# Copyright (c) 2012 Rosetta Contributors and Canonical Ltd 2012 +# This file is distributed under the same license as the openobject-addons package. +# FIRST AUTHOR , 2012. +# +msgid "" +msgstr "" +"Project-Id-Version: openobject-addons\n" +"Report-Msgid-Bugs-To: FULL NAME \n" +"POT-Creation-Date: 2013-06-07 19:36+0000\n" +"PO-Revision-Date: 2013-03-02 10:23+0000\n" +"Last-Translator: krnkris \n" +"Language-Team: Hungarian \n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"X-Launchpad-Export-Date: 2013-11-21 06:07+0000\n" +"X-Generator: Launchpad (build 16831)\n" + +#. module: document_ftp +#: view:document.ftp.configuration:0 +msgid "Configure FTP Server" +msgstr "FTP szerver beΓ‘llΓ­tΓ‘sa" + +#. module: document_ftp +#: model:ir.model,name:document_ftp.model_document_ftp_configuration +msgid "Auto Directory Configuration" +msgstr "Automatikus kΓΆnyvtΓ‘rbeΓ‘llΓ­tΓ‘s" + +#. module: document_ftp +#: view:document.ftp.configuration:0 +msgid "" +"Indicate the network address on which your OpenERP server should be " +"reachable for end-users. This depends on your network topology and " +"configuration, and will only affect the links displayed to the users. The " +"format is HOST:PORT and the default host (localhost) is only suitable for " +"access from the server machine itself.." +msgstr "" +"HΓ‘lΓ³zati cΓ­mek megjelenΓ­tΓ©se melyen az OpenERP szervere elΓ©rhetΕ‘ a vΓ©g-" +"felhasznΓ‘lΓ³k rΓ©szΓ©re. Ez a hΓ‘lΓ³zati beΓ‘llΓ­tΓ‘sok topolΓ³giΓ‘jΓ‘tΓ³l fΓΌgg, Γ©s csak " +"a felhasznΓ‘lΓ³knak megjelenΓ­tett elΓ©rΓ©si utakkat befolyΓ‘solja. A HOST:PORT " +"forma Γ©s az alapΓ©rtelmezett host (localhost) csak akkor megfelelΕ‘, ha a " +"szerver maga az alapgΓ©p." + +#. module: document_ftp +#: model:ir.model,name:document_ftp.model_knowledge_config_settings +msgid "knowledge.config.settings" +msgstr "knowledge.config.settings" + +#. module: document_ftp +#: model:ir.actions.act_url,name:document_ftp.action_document_browse +msgid "Browse Files" +msgstr "FΓ‘jlok bΓΆngΓ©szΓ©se" + +#. module: document_ftp +#: help:knowledge.config.settings,document_ftp_url:0 +msgid "Click the url to browse the documents" +msgstr "Kattintson az URL elΓ©rΓ©si ΓΊtra a dokumentum bΓΆngΓ©szΓ©sΓ©hez" + +#. module: document_ftp +#: field:document.ftp.browse,url:0 +msgid "FTP Server" +msgstr "FTP szerver" + +#. module: document_ftp +#: model:ir.actions.act_window,name:document_ftp.action_config_auto_directory +msgid "FTP Server Configuration" +msgstr "FTP szerver konfigurΓ‘ciΓ³" + +#. module: document_ftp +#: field:knowledge.config.settings,document_ftp_url:0 +msgid "Browse Documents" +msgstr "Dokumantumok bΓΆngΓ©szΓ©se" + +#. module: document_ftp +#: view:document.ftp.browse:0 +msgid "_Browse" +msgstr "_BΓΆngΓ©szΓ©s" + +#. module: document_ftp +#: help:document.ftp.configuration,host:0 +msgid "" +"Server address or IP and port to which users should connect to for DMS access" +msgstr "" +"Szerver cΓ­m vagy IP Γ©s port amelyen a felhasznΓ‘lΓ³nak kapcsolΓ³dnia kell a DMS " +"elΓ©rΓ©shez" + +#. module: document_ftp +#: model:ir.ui.menu,name:document_ftp.menu_document_browse +msgid "Shared Repository (FTP)" +msgstr "Megosztott (FTP)" + +#. module: document_ftp +#: field:document.ftp.configuration,host:0 +msgid "Address" +msgstr "CΓ­m" + +#. module: document_ftp +#: view:document.ftp.browse:0 +msgid "Cancel" +msgstr "MΓ©gsem" + +#. module: document_ftp +#: model:ir.model,name:document_ftp.model_document_ftp_browse +msgid "Document FTP Browse" +msgstr "Documentum FTP bΓΆngΓ©szΓ©se" + +#. module: document_ftp +#: view:document.ftp.configuration:0 +msgid "Knowledge Application Configuration" +msgstr "TudΓ‘skezelΕ‘ programok beΓ‘llΓ­tΓ‘sa" + +#. module: document_ftp +#: model:ir.actions.act_window,name:document_ftp.action_ftp_browse +msgid "Document Browse" +msgstr "Dokumentum bΓΆngΓ©szΓ©se" + +#. module: document_ftp +#: view:document.ftp.browse:0 +msgid "or" +msgstr "vagy" + +#. module: document_ftp +#: view:document.ftp.browse:0 +msgid "Browse Document" +msgstr "Dokumentum bΓΆngΓ©szΓ©se" + +#. module: document_ftp +#: view:document.ftp.configuration:0 +msgid "res_config_contents" +msgstr "res_config_contents" diff --git a/document_ftp/i18n/it.po b/document_ftp/i18n/it.po new file mode 100644 index 00000000..72df7ccc --- /dev/null +++ b/document_ftp/i18n/it.po @@ -0,0 +1,131 @@ +# Italian translation for openobject-addons +# Copyright (c) 2012 Rosetta Contributors and Canonical Ltd 2012 +# This file is distributed under the same license as the openobject-addons package. +# FIRST AUTHOR , 2012. +# +msgid "" +msgstr "" +"Project-Id-Version: openobject-addons\n" +"Report-Msgid-Bugs-To: FULL NAME \n" +"POT-Creation-Date: 2013-06-07 19:36+0000\n" +"PO-Revision-Date: 2012-12-21 23:00+0000\n" +"Last-Translator: FULL NAME \n" +"Language-Team: Italian \n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"X-Launchpad-Export-Date: 2013-11-21 06:07+0000\n" +"X-Generator: Launchpad (build 16831)\n" + +#. module: document_ftp +#: view:document.ftp.configuration:0 +msgid "Configure FTP Server" +msgstr "Configura FTP server" + +#. module: document_ftp +#: model:ir.model,name:document_ftp.model_document_ftp_configuration +msgid "Auto Directory Configuration" +msgstr "Configurazione automatica Directory" + +#. module: document_ftp +#: view:document.ftp.configuration:0 +msgid "" +"Indicate the network address on which your OpenERP server should be " +"reachable for end-users. This depends on your network topology and " +"configuration, and will only affect the links displayed to the users. The " +"format is HOST:PORT and the default host (localhost) is only suitable for " +"access from the server machine itself.." +msgstr "" +"Indica l'indirizzo di rete su cui i vostri server OpenERP possono essere " +"disponibili all'utente finale. Questo dipende dalla topologia di rete e " +"dalla configurazione, e interessa solamente i link visualizzati dagli " +"utenti. Il formato Γ¨ HOST:PORTA e il default host (localhost) Γ¨ adatto " +"solamente per l'accesso dal server su se stesso.." + +#. module: document_ftp +#: model:ir.model,name:document_ftp.model_knowledge_config_settings +msgid "knowledge.config.settings" +msgstr "knowledge.config.settings" + +#. module: document_ftp +#: model:ir.actions.act_url,name:document_ftp.action_document_browse +msgid "Browse Files" +msgstr "Sfoglia files" + +#. module: document_ftp +#: help:knowledge.config.settings,document_ftp_url:0 +msgid "Click the url to browse the documents" +msgstr "Cliccare l'url per sfogliare i documenti" + +#. module: document_ftp +#: field:document.ftp.browse,url:0 +msgid "FTP Server" +msgstr "Server FTP" + +#. module: document_ftp +#: model:ir.actions.act_window,name:document_ftp.action_config_auto_directory +msgid "FTP Server Configuration" +msgstr "Configurazione server FTP" + +#. module: document_ftp +#: field:knowledge.config.settings,document_ftp_url:0 +msgid "Browse Documents" +msgstr "Sfoglia Documenti" + +#. module: document_ftp +#: view:document.ftp.browse:0 +msgid "_Browse" +msgstr "_Sfoglia" + +#. module: document_ftp +#: help:document.ftp.configuration,host:0 +msgid "" +"Server address or IP and port to which users should connect to for DMS access" +msgstr "" +"Indirizzo Server o IP e porta a cui gli utenti dovrebbero connettersi per " +"l'accesso al DMS" + +#. module: document_ftp +#: model:ir.ui.menu,name:document_ftp.menu_document_browse +msgid "Shared Repository (FTP)" +msgstr "Deposito condiviso (FTP)" + +#. module: document_ftp +#: field:document.ftp.configuration,host:0 +msgid "Address" +msgstr "Indirizzo" + +#. module: document_ftp +#: view:document.ftp.browse:0 +msgid "Cancel" +msgstr "Annulla" + +#. module: document_ftp +#: model:ir.model,name:document_ftp.model_document_ftp_browse +msgid "Document FTP Browse" +msgstr "Browse FTP documenti" + +#. module: document_ftp +#: view:document.ftp.configuration:0 +msgid "Knowledge Application Configuration" +msgstr "Configurazione applicazione know how" + +#. module: document_ftp +#: model:ir.actions.act_window,name:document_ftp.action_ftp_browse +msgid "Document Browse" +msgstr "Sfoglia documento" + +#. module: document_ftp +#: view:document.ftp.browse:0 +msgid "or" +msgstr "o" + +#. module: document_ftp +#: view:document.ftp.browse:0 +msgid "Browse Document" +msgstr "Sfoglia documento" + +#. module: document_ftp +#: view:document.ftp.configuration:0 +msgid "res_config_contents" +msgstr "res_config_contents" diff --git a/document_ftp/i18n/ja.po b/document_ftp/i18n/ja.po new file mode 100644 index 00000000..8e5e6335 --- /dev/null +++ b/document_ftp/i18n/ja.po @@ -0,0 +1,127 @@ +# Japanese translation for openobject-addons +# Copyright (c) 2012 Rosetta Contributors and Canonical Ltd 2012 +# This file is distributed under the same license as the openobject-addons package. +# FIRST AUTHOR , 2012. +# +msgid "" +msgstr "" +"Project-Id-Version: openobject-addons\n" +"Report-Msgid-Bugs-To: FULL NAME \n" +"POT-Creation-Date: 2013-06-07 19:36+0000\n" +"PO-Revision-Date: 2012-12-21 23:00+0000\n" +"Last-Translator: FULL NAME \n" +"Language-Team: Japanese \n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"X-Launchpad-Export-Date: 2013-11-21 06:07+0000\n" +"X-Generator: Launchpad (build 16831)\n" + +#. module: document_ftp +#: view:document.ftp.configuration:0 +msgid "Configure FTP Server" +msgstr "FTPァーバを設εšγ™γ‚‹" + +#. module: document_ftp +#: model:ir.model,name:document_ftp.model_document_ftp_configuration +msgid "Auto Directory Configuration" +msgstr "θ‡ͺε‹•γƒ‡γ‚£γƒ¬γ‚―γƒˆγθ¨­εš" + +#. module: document_ftp +#: view:document.ftp.configuration:0 +msgid "" +"Indicate the network address on which your OpenERP server should be " +"reachable for end-users. This depends on your network topology and " +"configuration, and will only affect the links displayed to the users. The " +"format is HOST:PORT and the default host (localhost) is only suitable for " +"access from the server machine itself.." +msgstr "" +"γƒ¦γƒΌγ‚ΆγŒγ‚’γ‚―γ‚»γ‚Ήγ™γ‚‹OpenERPァーバγγƒγƒƒγƒˆγƒ―γƒΌγ‚―γ‚’γƒ‰γƒ¬γ‚Ήγ‚’ζŒ‡εšγ—γ¦δΈ‹γ•γ„γ€‚γγ‚Œγ―γƒγƒƒγƒˆγƒ―γƒΌγ‚―γƒˆγƒγƒ­γ‚Έγ¨γγθ¨­εšγ«γ‚ˆγ£γ¦ζ±ΊγΎγ‚Šγ€γƒ¦γƒΌγ‚Άγ«θ‘¨η€Ίγ•γ‚Œγ‚‹γƒͺン" +"クにγͺγ‚ŠγΎγ™γ€‚\r\n" +"ζŒ‡εšγε½’式は HOST:PORT γ§γ‚γ‚Šγ€γƒ‡γƒ•γ‚©γƒ«γƒˆγγƒ›γ‚ΉγƒˆοΌˆγƒ­γƒΌγ‚«γƒ«γƒ›γ‚ΉγƒˆοΌ‰γŒγ‚΅γƒΌγƒγ‹γ‚‰γγ‚’γ‚―γ‚»γ‚Ήγ«ζœ€γ‚‚ι©γ—γ¦γ„γΎγ™γ€‚" + +#. module: document_ftp +#: model:ir.model,name:document_ftp.model_knowledge_config_settings +msgid "knowledge.config.settings" +msgstr "" + +#. module: document_ftp +#: model:ir.actions.act_url,name:document_ftp.action_document_browse +msgid "Browse Files" +msgstr "フゑむルをブラウズする" + +#. module: document_ftp +#: help:knowledge.config.settings,document_ftp_url:0 +msgid "Click the url to browse the documents" +msgstr "" + +#. module: document_ftp +#: field:document.ftp.browse,url:0 +msgid "FTP Server" +msgstr "FTPァーバ" + +#. module: document_ftp +#: model:ir.actions.act_window,name:document_ftp.action_config_auto_directory +msgid "FTP Server Configuration" +msgstr "FTPァーバγθ¨­εš" + +#. module: document_ftp +#: field:knowledge.config.settings,document_ftp_url:0 +msgid "Browse Documents" +msgstr "" + +#. module: document_ftp +#: view:document.ftp.browse:0 +msgid "_Browse" +msgstr "_ブラウズ(_B)" + +#. module: document_ftp +#: help:document.ftp.configuration,host:0 +msgid "" +"Server address or IP and port to which users should connect to for DMS access" +msgstr "ァーバをドレスあるいはDMSγ‚’γ‚―γ‚»γ‚ΉγγŸγ‚γ«ζŽ₯ηΆšγ™γ‚‹IPγ¨γƒγƒΌγƒˆ" + +#. module: document_ftp +#: model:ir.ui.menu,name:document_ftp.menu_document_browse +msgid "Shared Repository (FTP)" +msgstr "ε…±ζœ‰γƒͺγƒ—γ‚Έγƒˆγƒͺ(FTP)" + +#. module: document_ftp +#: field:document.ftp.configuration,host:0 +msgid "Address" +msgstr "住所" + +#. module: document_ftp +#: view:document.ftp.browse:0 +msgid "Cancel" +msgstr "" + +#. module: document_ftp +#: model:ir.model,name:document_ftp.model_document_ftp_browse +msgid "Document FTP Browse" +msgstr "FTP文書をブラウズする" + +#. module: document_ftp +#: view:document.ftp.configuration:0 +msgid "Knowledge Application Configuration" +msgstr "ηŸ₯θ­˜γ‚’γƒ—γƒͺケーションγθ¨­εš" + +#. module: document_ftp +#: model:ir.actions.act_window,name:document_ftp.action_ftp_browse +msgid "Document Browse" +msgstr "文書をブラウズする" + +#. module: document_ftp +#: view:document.ftp.browse:0 +msgid "or" +msgstr "" + +#. module: document_ftp +#: view:document.ftp.browse:0 +msgid "Browse Document" +msgstr "文書をブラウズする" + +#. module: document_ftp +#: view:document.ftp.configuration:0 +msgid "res_config_contents" +msgstr "res_config_contents" diff --git a/document_ftp/i18n/mk.po b/document_ftp/i18n/mk.po new file mode 100644 index 00000000..25251bc8 --- /dev/null +++ b/document_ftp/i18n/mk.po @@ -0,0 +1,131 @@ +# Macedonian translation for openobject-addons +# Copyright (c) 2013 Rosetta Contributors and Canonical Ltd 2013 +# This file is distributed under the same license as the openobject-addons package. +# FIRST AUTHOR , 2013. +# Sofce Dimitrijeva , 2013. +msgid "" +msgstr "" +"Project-Id-Version: openobject-addons\n" +"Report-Msgid-Bugs-To: FULL NAME \n" +"POT-Creation-Date: 2013-06-07 19:36+0000\n" +"PO-Revision-Date: 2013-03-28 22:42+0000\n" +"Last-Translator: Sofce Dimitrijeva \n" +"Language-Team: ESKON-INZENERING\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"X-Launchpad-Export-Date: 2013-11-21 06:07+0000\n" +"X-Generator: Launchpad (build 16831)\n" +"Language: mk\n" + +#. module: document_ftp +#: view:document.ftp.configuration:0 +msgid "Configure FTP Server" +msgstr "ΠšΠΎΠ½Ρ„ΠΈΠ³ΡƒΡ€ΠΈΡ€Π°Ρ˜ FTP сСрвСр" + +#. module: document_ftp +#: model:ir.model,name:document_ftp.model_document_ftp_configuration +msgid "Auto Directory Configuration" +msgstr "Автоматска ΠΊΠΎΠ½Ρ„ΠΈΠ³ΡƒΡ€Π°Ρ†ΠΈΡ˜Π° Π½Π° Π΄ΠΈΡ€Π΅ΠΊΡ‚ΠΎΡ€ΠΈΡƒΠΌ" + +#. module: document_ftp +#: view:document.ftp.configuration:0 +msgid "" +"Indicate the network address on which your OpenERP server should be " +"reachable for end-users. This depends on your network topology and " +"configuration, and will only affect the links displayed to the users. The " +"format is HOST:PORT and the default host (localhost) is only suitable for " +"access from the server machine itself.." +msgstr "" +"Ја ΠΏΠΎΠΊΠ°ΠΆΡƒΠ²Π° адрСсата Π½Π° која OpenERP ќС Π±ΠΈΠ΄Π΅ достапСн Π·Π° ΠΊΡ€Π°Ρ˜Π½ΠΈΡ‚Π΅ корисници. " +"Ова зависи ΠΎΠ΄ ΠΌΡ€Π΅ΠΆΠ½Π°Ρ‚Π° Ρ‚ΠΎΠΏΠΎΠ»ΠΎΠ³ΠΈΡ˜Π° ΠΈ ΠΊΠΎΠ½Ρ„ΠΈΠ³ΡƒΡ€Π°Ρ†ΠΈΡ˜Π° ΠΈ ΠΈΠΌΠ° влијаниС само Π²Ρ€Π· " +"Π»ΠΈΠ½ΠΊΠΎΠ²ΠΈΡ‚Π΅ ΠΏΡ€ΠΈΠΊΠ°ΠΆΠ°Π½ΠΈ Π½Π° корисницитС. Π€ΠΎΡ€ΠΌΠ°Ρ‚ΠΎΡ‚ Π΅ HOST:PORT ΠΈ стандардниот хост " +"(localhost) Π΅ Π·Π° пристап само ΠΏΡ€Π΅ΠΊΡƒ самиот сСрвСр.." + +#. module: document_ftp +#: model:ir.model,name:document_ftp.model_knowledge_config_settings +msgid "knowledge.config.settings" +msgstr "knowledge.config.settings" + +#. module: document_ftp +#: model:ir.actions.act_url,name:document_ftp.action_document_browse +msgid "Browse Files" +msgstr "ΠŸΡ€Π΅Π»ΠΈΡΡ‚ΡƒΠ²Π°ΡšΠ΅ Ρ„Π°Ρ˜Π»ΠΎΠ²ΠΈ" + +#. module: document_ftp +#: help:knowledge.config.settings,document_ftp_url:0 +msgid "Click the url to browse the documents" +msgstr "Кликни Π½Π° url-Ρ‚ΠΎ Π·Π° ΠΏΡ€Π΅Π»ΠΈΡΡ‚ΡƒΠ²Π°ΡšΠ΅ Π½Π° Π΄ΠΎΠΊΡƒΠΌΠ΅Π½Ρ‚ΠΈΡ‚Π΅" + +#. module: document_ftp +#: field:document.ftp.browse,url:0 +msgid "FTP Server" +msgstr "FTP сСрвСр" + +#. module: document_ftp +#: model:ir.actions.act_window,name:document_ftp.action_config_auto_directory +msgid "FTP Server Configuration" +msgstr "ΠšΠΎΠ½Ρ„ΠΈΠ³ΡƒΡ€Π°Ρ†ΠΈΡ˜Π° Π½Π° FTP сСрвСрот" + +#. module: document_ftp +#: field:knowledge.config.settings,document_ftp_url:0 +msgid "Browse Documents" +msgstr "ΠŸΡ€Π΅Π»ΠΈΡΡ‚ΡƒΠ²Π°ΡšΠ΅ Π΄ΠΎΠΊΡƒΠΌΠ΅Π½Ρ‚ΠΈ" + +#. module: document_ftp +#: view:document.ftp.browse:0 +msgid "_Browse" +msgstr "_ΠŸΡ€Π΅Π³Π»Π΅Π΄Π°Ρ˜" + +#. module: document_ftp +#: help:document.ftp.configuration,host:0 +msgid "" +"Server address or IP and port to which users should connect to for DMS access" +msgstr "" +"АдрСса Π½Π° сСрвСрот ΠΈΠ»ΠΈ IP ΠΈ ΠΏΠΎΡ€Ρ‚ Π½Π° ΠΊΠΎΠΈ корисницитС сС ΠΏΠΎΠ²Ρ€Π·ΡƒΠ²Π°Π°Ρ‚ Π·Π° пристап " +"Π΄ΠΎ DMS" + +#. module: document_ftp +#: model:ir.ui.menu,name:document_ftp.menu_document_browse +msgid "Shared Repository (FTP)" +msgstr "Π—Π°Π΅Π΄Π½ΠΈΡ‡ΠΊΠΈ ΠΌΠ°Π³Π°Ρ†ΠΈΠ½ (FTP)" + +#. module: document_ftp +#: field:document.ftp.configuration,host:0 +msgid "Address" +msgstr "АдрСса" + +#. module: document_ftp +#: view:document.ftp.browse:0 +msgid "Cancel" +msgstr "ΠžΡ‚ΠΊΠ°ΠΆΠΈ" + +#. module: document_ftp +#: model:ir.model,name:document_ftp.model_document_ftp_browse +msgid "Document FTP Browse" +msgstr "ΠŸΡ€Π΅Π»ΠΈΡΡ‚ΡƒΠ²Π°ΡšΠ΅ Π½Π° Π΄ΠΎΠΊΡƒΠΌΠ΅Π½Ρ‚ΠΈ ΠΏΡ€Π΅ΠΊΡƒ FTP" + +#. module: document_ftp +#: view:document.ftp.configuration:0 +msgid "Knowledge Application Configuration" +msgstr "ΠšΠΎΠ½Ρ„ΠΈΠ³ΡƒΡ€Π°Ρ†ΠΈΡ˜Π° Π·Π° Π°ΠΏΠ»ΠΈΠΊΠ°Ρ†ΠΈΡ˜Π° Π—Π½Π°Π΅ΡšΠ΅" + +#. module: document_ftp +#: model:ir.actions.act_window,name:document_ftp.action_ftp_browse +msgid "Document Browse" +msgstr "ΠŸΡ€Π΅Π»ΠΈΡΡ‚ΡƒΠ²Π°ΡšΠ΅ Π½Π° Π΄ΠΎΠΊΡƒΠΌΠ΅Π½Ρ‚ΠΈ" + +#. module: document_ftp +#: view:document.ftp.browse:0 +msgid "or" +msgstr "ΠΈΠ»ΠΈ" + +#. module: document_ftp +#: view:document.ftp.browse:0 +msgid "Browse Document" +msgstr "ΠŸΡ€Π΅Π»ΠΈΡΡ‚Π°Ρ˜ Π΄ΠΎΠΊΡƒΠΌΠ΅Π½Ρ‚" + +#. module: document_ftp +#: view:document.ftp.configuration:0 +msgid "res_config_contents" +msgstr "res_config_contents" diff --git a/document_ftp/i18n/mn.po b/document_ftp/i18n/mn.po new file mode 100644 index 00000000..d74026b0 --- /dev/null +++ b/document_ftp/i18n/mn.po @@ -0,0 +1,129 @@ +# Mongolian translation for openobject-addons +# Copyright (c) 2012 Rosetta Contributors and Canonical Ltd 2012 +# This file is distributed under the same license as the openobject-addons package. +# FIRST AUTHOR , 2012. +# +msgid "" +msgstr "" +"Project-Id-Version: openobject-addons\n" +"Report-Msgid-Bugs-To: FULL NAME \n" +"POT-Creation-Date: 2013-06-07 19:36+0000\n" +"PO-Revision-Date: 2012-12-21 23:00+0000\n" +"Last-Translator: FULL NAME \n" +"Language-Team: Mongolian \n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"X-Launchpad-Export-Date: 2013-11-21 06:07+0000\n" +"X-Generator: Launchpad (build 16831)\n" + +#. module: document_ftp +#: view:document.ftp.configuration:0 +msgid "Configure FTP Server" +msgstr "FTP БСрвэр Ρ‚ΠΎΡ…ΠΈΡ€ΡƒΡƒΠ»Π°Ρ…" + +#. module: document_ftp +#: model:ir.model,name:document_ftp.model_document_ftp_configuration +msgid "Auto Directory Configuration" +msgstr "Автомат Π”ΠΈΡ€Π΅ΠΊΡ‚ΠΎΡ€Ρ‹Π½ Π’ΠΎΡ…ΠΈΡ€Π³ΠΎΠΎ" + +#. module: document_ftp +#: view:document.ftp.configuration:0 +msgid "" +"Indicate the network address on which your OpenERP server should be " +"reachable for end-users. This depends on your network topology and " +"configuration, and will only affect the links displayed to the users. The " +"format is HOST:PORT and the default host (localhost) is only suitable for " +"access from the server machine itself.." +msgstr "" +"Эцсийн хэрэглэгчидийн OpenERP-Ρ€ΡƒΡƒ Ρ…Π°Π½Π΄Π°ΠΆ Ρ‡Π°Π΄Π°Ρ… с―лТээний хаягийг илэрхийлнэ. " +"Энэ Ρ‚Π°Π½Π°ΠΉ с―лТээний Π±―тэц, тохиргооноос Ρ…Π°ΠΌΠ°Π°Ρ€Π°Ρ… Π±Σ©Π³Σ©Σ©Π΄ Π·Σ©Π²Ρ…Σ©Π½ хэрэглэгчдэд " +"Ρ…Π°Ρ€Π°Π³Π΄Π°Ρ… холбоост Π» нөлөөтэй. Π€ΠΎΡ€ΠΌΠ°Ρ‚ нь HOST:PORT гэсэн Π·Π°Π³Π²Π°Ρ€Ρ‚Π°ΠΉ Π±Σ©Π³Σ©Σ©Π΄ " +"Π°Π½Ρ…Ρ‹Π½ host нь (localhost) гэТ Π±Π°ΠΉΠ΄Π°Π³ нь Π·Σ©Π²Ρ…Σ©Π½ сСрвСр машин дээрээс Ρ…Π°Π½Π΄Π°Ρ…Π°Π΄ " +"Π» Ρ‚ΠΎΡ…ΠΈΡ€Π½ΠΎ." + +#. module: document_ftp +#: model:ir.model,name:document_ftp.model_knowledge_config_settings +msgid "knowledge.config.settings" +msgstr "" + +#. module: document_ftp +#: model:ir.actions.act_url,name:document_ftp.action_document_browse +msgid "Browse Files" +msgstr "Π€Π°ΠΉΠ»ΡƒΡƒΠ΄Ρ‹Π³ Ρ‚ΠΎΠ»ΡŒΠ΄ΠΎΡ…" + +#. module: document_ftp +#: help:knowledge.config.settings,document_ftp_url:0 +msgid "Click the url to browse the documents" +msgstr "" + +#. module: document_ftp +#: field:document.ftp.browse,url:0 +msgid "FTP Server" +msgstr "FTP Π‘Π΅Ρ€Π²Π΅Ρ€" + +#. module: document_ftp +#: model:ir.actions.act_window,name:document_ftp.action_config_auto_directory +msgid "FTP Server Configuration" +msgstr "FTP Π‘Π΅Ρ€Π²Π΅Ρ€ΠΈΠΉΠ½ Ρ‚ΠΎΡ…ΠΈΡ€Π³ΠΎΠΎ" + +#. module: document_ftp +#: field:knowledge.config.settings,document_ftp_url:0 +msgid "Browse Documents" +msgstr "" + +#. module: document_ftp +#: view:document.ftp.browse:0 +msgid "_Browse" +msgstr "_Π’ΠΎΠ»ΡŒΠ΄ΠΎΡ…" + +#. module: document_ftp +#: help:document.ftp.configuration,host:0 +msgid "" +"Server address or IP and port to which users should connect to for DMS access" +msgstr "DMS Ρ…Π°Π½Π΄Π°Π»Ρ‚Π°Π°Ρ€ Ρ…Π°Π½Π΄Π°Ρ… сСрвСрийн хаяг эсвэл IP Π±ΠΎΠ»ΠΎΠ½ ΠΏΠΎΡ€Ρ‚." + +#. module: document_ftp +#: model:ir.ui.menu,name:document_ftp.menu_document_browse +msgid "Shared Repository (FTP)" +msgstr "Агуулахыг Ρ…ΡƒΠ²Π°Π°Π»Ρ†Π°Ρ… (FTP)" + +#. module: document_ftp +#: field:document.ftp.configuration,host:0 +msgid "Address" +msgstr "Π₯аяг" + +#. module: document_ftp +#: view:document.ftp.browse:0 +msgid "Cancel" +msgstr "" + +#. module: document_ftp +#: model:ir.model,name:document_ftp.model_document_ftp_browse +msgid "Document FTP Browse" +msgstr "FTP-ээр Π±Π°Ρ€ΠΈΠΌΡ‚ ΠΎΡ€ΡƒΡƒΠ»Π°Ρ…" + +#. module: document_ftp +#: view:document.ftp.configuration:0 +msgid "Knowledge Application Configuration" +msgstr "Мэдлэгийн ΠŸΡ€ΠΎΠ³Ρ€Π°ΠΌΡ‹Π½ Π’ΠΎΡ…ΠΈΡ€Π³ΠΎΠΎ" + +#. module: document_ftp +#: model:ir.actions.act_window,name:document_ftp.action_ftp_browse +msgid "Document Browse" +msgstr "Π‘Π°Ρ€ΠΈΠΌΡ‚ ΠΎΡ€ΡƒΡƒΠ»Π°Ρ…" + +#. module: document_ftp +#: view:document.ftp.browse:0 +msgid "or" +msgstr "" + +#. module: document_ftp +#: view:document.ftp.browse:0 +msgid "Browse Document" +msgstr "ΠžΡ€ΡƒΡƒΠ»Π°Ρ… Π±Π°Ρ€ΠΈΠΌΡ‚" + +#. module: document_ftp +#: view:document.ftp.configuration:0 +msgid "res_config_contents" +msgstr "res_config_contents" diff --git a/document_ftp/i18n/nb.po b/document_ftp/i18n/nb.po new file mode 100644 index 00000000..0e8eec6e --- /dev/null +++ b/document_ftp/i18n/nb.po @@ -0,0 +1,131 @@ +# Norwegian Bokmal translation for openobject-addons +# Copyright (c) 2012 Rosetta Contributors and Canonical Ltd 2012 +# This file is distributed under the same license as the openobject-addons package. +# FIRST AUTHOR , 2012. +# +msgid "" +msgstr "" +"Project-Id-Version: openobject-addons\n" +"Report-Msgid-Bugs-To: FULL NAME \n" +"POT-Creation-Date: 2013-06-07 19:36+0000\n" +"PO-Revision-Date: 2012-12-21 23:00+0000\n" +"Last-Translator: FULL NAME \n" +"Language-Team: Norwegian Bokmal \n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"X-Launchpad-Export-Date: 2013-11-21 06:07+0000\n" +"X-Generator: Launchpad (build 16831)\n" + +#. module: document_ftp +#: view:document.ftp.configuration:0 +msgid "Configure FTP Server" +msgstr "Konfigurer FTP serveren" + +#. module: document_ftp +#: model:ir.model,name:document_ftp.model_document_ftp_configuration +msgid "Auto Directory Configuration" +msgstr "Automatisk Katalog Konfigurasjon." + +#. module: document_ftp +#: view:document.ftp.configuration:0 +msgid "" +"Indicate the network address on which your OpenERP server should be " +"reachable for end-users. This depends on your network topology and " +"configuration, and will only affect the links displayed to the users. The " +"format is HOST:PORT and the default host (localhost) is only suitable for " +"access from the server machine itself.." +msgstr "" +"Indikere nettverksadressen som din OpenERP server bΓΈr vΓ¦re tilgjengelige for " +"sluttbrukerne. Dette avhenger av nettverkstopologi og konfigurasjon, og vil " +"bare pΓ₯virke lenker som vises til brukerne. Formatet er HOST: PORT og " +"standard verten (lokalhost) er bare egnet for tilgang fra serveren maskinen " +"selv .." + +#. module: document_ftp +#: model:ir.model,name:document_ftp.model_knowledge_config_settings +msgid "knowledge.config.settings" +msgstr "" + +#. module: document_ftp +#: model:ir.actions.act_url,name:document_ftp.action_document_browse +msgid "Browse Files" +msgstr "Bla i filer" + +#. module: document_ftp +#: help:knowledge.config.settings,document_ftp_url:0 +msgid "Click the url to browse the documents" +msgstr "" + +#. module: document_ftp +#: field:document.ftp.browse,url:0 +msgid "FTP Server" +msgstr "FTP-tjener" + +#. module: document_ftp +#: model:ir.actions.act_window,name:document_ftp.action_config_auto_directory +msgid "FTP Server Configuration" +msgstr "FTP Serverkonfigurasjon" + +#. module: document_ftp +#: field:knowledge.config.settings,document_ftp_url:0 +msgid "Browse Documents" +msgstr "" + +#. module: document_ftp +#: view:document.ftp.browse:0 +msgid "_Browse" +msgstr "_Bla gjennom" + +#. module: document_ftp +#: help:document.ftp.configuration,host:0 +msgid "" +"Server address or IP and port to which users should connect to for DMS access" +msgstr "" +"Server adresse eller IP og port til hvilke brukere som skal koble seg til " +"for DMS tilgang" + +#. module: document_ftp +#: model:ir.ui.menu,name:document_ftp.menu_document_browse +msgid "Shared Repository (FTP)" +msgstr "Wikimedia Commons (FTP)" + +#. module: document_ftp +#: field:document.ftp.configuration,host:0 +msgid "Address" +msgstr "Adresse" + +#. module: document_ftp +#: view:document.ftp.browse:0 +msgid "Cancel" +msgstr "" + +#. module: document_ftp +#: model:ir.model,name:document_ftp.model_document_ftp_browse +msgid "Document FTP Browse" +msgstr "Document FTP Bla i." + +#. module: document_ftp +#: view:document.ftp.configuration:0 +msgid "Knowledge Application Configuration" +msgstr "Kunnskap Programkonfigurasjon" + +#. module: document_ftp +#: model:ir.actions.act_window,name:document_ftp.action_ftp_browse +msgid "Document Browse" +msgstr "Dokument Bla i." + +#. module: document_ftp +#: view:document.ftp.browse:0 +msgid "or" +msgstr "" + +#. module: document_ftp +#: view:document.ftp.browse:0 +msgid "Browse Document" +msgstr "Bla igjennom dokument" + +#. module: document_ftp +#: view:document.ftp.configuration:0 +msgid "res_config_contents" +msgstr "res_konfig_innhold" diff --git a/document_ftp/i18n/nl.po b/document_ftp/i18n/nl.po new file mode 100644 index 00000000..00ab719e --- /dev/null +++ b/document_ftp/i18n/nl.po @@ -0,0 +1,131 @@ +# Dutch translation for openobject-addons +# Copyright (c) 2012 Rosetta Contributors and Canonical Ltd 2012 +# This file is distributed under the same license as the openobject-addons package. +# FIRST AUTHOR , 2012. +# +msgid "" +msgstr "" +"Project-Id-Version: openobject-addons\n" +"Report-Msgid-Bugs-To: FULL NAME \n" +"POT-Creation-Date: 2013-06-07 19:36+0000\n" +"PO-Revision-Date: 2012-12-21 23:00+0000\n" +"Last-Translator: FULL NAME \n" +"Language-Team: Dutch \n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"X-Launchpad-Export-Date: 2013-11-21 06:07+0000\n" +"X-Generator: Launchpad (build 16831)\n" + +#. module: document_ftp +#: view:document.ftp.configuration:0 +msgid "Configure FTP Server" +msgstr "FTP Server configureren" + +#. module: document_ftp +#: model:ir.model,name:document_ftp.model_document_ftp_configuration +msgid "Auto Directory Configuration" +msgstr "Auto map configuratie" + +#. module: document_ftp +#: view:document.ftp.configuration:0 +msgid "" +"Indicate the network address on which your OpenERP server should be " +"reachable for end-users. This depends on your network topology and " +"configuration, and will only affect the links displayed to the users. The " +"format is HOST:PORT and the default host (localhost) is only suitable for " +"access from the server machine itself.." +msgstr "" +"Geeft het netwerkadres aan waarop uw OpenERP server te vinden zou moeten " +"zijn voor eindgebruikers. Dit hangt af van netwerk topologie en configuratie " +"en zal alleen de link beΓ―nvloeden die wordt getoond aan de gebruikers. Het " +"formaat HOST:PORT en standaard host (localhost) zijn allen geschikt voor " +"toegang vanaf de server machine zelf." + +#. module: document_ftp +#: model:ir.model,name:document_ftp.model_knowledge_config_settings +msgid "knowledge.config.settings" +msgstr "knowledge.config.settings" + +#. module: document_ftp +#: model:ir.actions.act_url,name:document_ftp.action_document_browse +msgid "Browse Files" +msgstr "Bestanden bladeren" + +#. module: document_ftp +#: help:knowledge.config.settings,document_ftp_url:0 +msgid "Click the url to browse the documents" +msgstr "Klik op de url om door de documenten te bladeren" + +#. module: document_ftp +#: field:document.ftp.browse,url:0 +msgid "FTP Server" +msgstr "FTP server" + +#. module: document_ftp +#: model:ir.actions.act_window,name:document_ftp.action_config_auto_directory +msgid "FTP Server Configuration" +msgstr "FTP server configuratie" + +#. module: document_ftp +#: field:knowledge.config.settings,document_ftp_url:0 +msgid "Browse Documents" +msgstr "Blader door documenten" + +#. module: document_ftp +#: view:document.ftp.browse:0 +msgid "_Browse" +msgstr "_Bladeren" + +#. module: document_ftp +#: help:document.ftp.configuration,host:0 +msgid "" +"Server address or IP and port to which users should connect to for DMS access" +msgstr "" +"Server adres of IP en poort waarmee gebruikers moeten verbinden voor DMS " +"toegang" + +#. module: document_ftp +#: model:ir.ui.menu,name:document_ftp.menu_document_browse +msgid "Shared Repository (FTP)" +msgstr "Gedeelde repository (FTP)" + +#. module: document_ftp +#: field:document.ftp.configuration,host:0 +msgid "Address" +msgstr "Adres" + +#. module: document_ftp +#: view:document.ftp.browse:0 +msgid "Cancel" +msgstr "Annuleren" + +#. module: document_ftp +#: model:ir.model,name:document_ftp.model_document_ftp_browse +msgid "Document FTP Browse" +msgstr "Document FTP bladeren" + +#. module: document_ftp +#: view:document.ftp.configuration:0 +msgid "Knowledge Application Configuration" +msgstr "Kennis applicatie configuratie" + +#. module: document_ftp +#: model:ir.actions.act_window,name:document_ftp.action_ftp_browse +msgid "Document Browse" +msgstr "Document bladeren" + +#. module: document_ftp +#: view:document.ftp.browse:0 +msgid "or" +msgstr "of" + +#. module: document_ftp +#: view:document.ftp.browse:0 +msgid "Browse Document" +msgstr "Document bladeren" + +#. module: document_ftp +#: view:document.ftp.configuration:0 +msgid "res_config_contents" +msgstr "res_config_contents" diff --git a/document_ftp/i18n/pl.po b/document_ftp/i18n/pl.po new file mode 100644 index 00000000..03b319a0 --- /dev/null +++ b/document_ftp/i18n/pl.po @@ -0,0 +1,130 @@ +# Polish translation for openobject-addons +# Copyright (c) 2012 Rosetta Contributors and Canonical Ltd 2012 +# This file is distributed under the same license as the openobject-addons package. +# FIRST AUTHOR , 2012. +# +msgid "" +msgstr "" +"Project-Id-Version: openobject-addons\n" +"Report-Msgid-Bugs-To: FULL NAME \n" +"POT-Creation-Date: 2013-06-07 19:36+0000\n" +"PO-Revision-Date: 2013-11-20 18:01+0000\n" +"Last-Translator: MirosΕ‚aw Bojanowicz \n" +"Language-Team: Polish \n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"X-Launchpad-Export-Date: 2013-11-21 06:07+0000\n" +"X-Generator: Launchpad (build 16831)\n" + +#. module: document_ftp +#: view:document.ftp.configuration:0 +msgid "Configure FTP Server" +msgstr "Konfiguruj Serwer FTP" + +#. module: document_ftp +#: model:ir.model,name:document_ftp.model_document_ftp_configuration +msgid "Auto Directory Configuration" +msgstr "Autokonfiguracja katalogu" + +#. module: document_ftp +#: view:document.ftp.configuration:0 +msgid "" +"Indicate the network address on which your OpenERP server should be " +"reachable for end-users. This depends on your network topology and " +"configuration, and will only affect the links displayed to the users. The " +"format is HOST:PORT and the default host (localhost) is only suitable for " +"access from the server machine itself.." +msgstr "" +"Wskazuje adres sieciowy na ktΓ³rym twΓ³j serwer OpenERP powinien byΔ‡ dostΔ™pny " +"dla uΕΌytkownikΓ³w koΕ„cowych. To zaleΕΌy od topologii sieci i ustawieΕ„ i ma " +"efekt tylko w odniesieniu do linkΓ³w wyΕ›wietlanych uΕΌytkownikom. Format to " +"HOST:PORT i domyΕ›lny (localhost) jest do uΕΌytku tylko z poziomu serwera." + +#. module: document_ftp +#: model:ir.model,name:document_ftp.model_knowledge_config_settings +msgid "knowledge.config.settings" +msgstr "knowledge.config.settings" + +#. module: document_ftp +#: model:ir.actions.act_url,name:document_ftp.action_document_browse +msgid "Browse Files" +msgstr "PrzeglΔ…daj pliki" + +#. module: document_ftp +#: help:knowledge.config.settings,document_ftp_url:0 +msgid "Click the url to browse the documents" +msgstr "Kliknij adres url ΕΌeby przeglΔ…daΔ‡ dokumenty" + +#. module: document_ftp +#: field:document.ftp.browse,url:0 +msgid "FTP Server" +msgstr "Serwer FTP" + +#. module: document_ftp +#: model:ir.actions.act_window,name:document_ftp.action_config_auto_directory +msgid "FTP Server Configuration" +msgstr "Konfiguracja serwera FTP" + +#. module: document_ftp +#: field:knowledge.config.settings,document_ftp_url:0 +msgid "Browse Documents" +msgstr "PrzeglΔ…daj Dokumenty" + +#. module: document_ftp +#: view:document.ftp.browse:0 +msgid "_Browse" +msgstr "_PrzeglΔ…daj" + +#. module: document_ftp +#: help:document.ftp.configuration,host:0 +msgid "" +"Server address or IP and port to which users should connect to for DMS access" +msgstr "" +"Adres serwera lub IP i port do ktΓ³rego uΕΌytkownik powinien siΔ™ podΕ‚Δ…czyΔ‡ dla " +"dostΔ™pu do DMS (System ZarzΔ…dzania Dokumentami)." + +#. module: document_ftp +#: model:ir.ui.menu,name:document_ftp.menu_document_browse +msgid "Shared Repository (FTP)" +msgstr "WspΓ³Ε‚dzielone repozytorium (FTP)" + +#. module: document_ftp +#: field:document.ftp.configuration,host:0 +msgid "Address" +msgstr "Adresy" + +#. module: document_ftp +#: view:document.ftp.browse:0 +msgid "Cancel" +msgstr "Anuluj" + +#. module: document_ftp +#: model:ir.model,name:document_ftp.model_document_ftp_browse +msgid "Document FTP Browse" +msgstr "FTP PrzeglΔ…daj dokument" + +#. module: document_ftp +#: view:document.ftp.configuration:0 +msgid "Knowledge Application Configuration" +msgstr "Konfiguracja aplikacji wiedzy" + +#. module: document_ftp +#: model:ir.actions.act_window,name:document_ftp.action_ftp_browse +msgid "Document Browse" +msgstr "PrzeglΔ…danie dokumentΓ³w" + +#. module: document_ftp +#: view:document.ftp.browse:0 +msgid "or" +msgstr "lub" + +#. module: document_ftp +#: view:document.ftp.browse:0 +msgid "Browse Document" +msgstr "PrzeglΔ…daj dokumenty" + +#. module: document_ftp +#: view:document.ftp.configuration:0 +msgid "res_config_contents" +msgstr "res_config_contents" diff --git a/document_ftp/i18n/pt.po b/document_ftp/i18n/pt.po new file mode 100644 index 00000000..13a8a060 --- /dev/null +++ b/document_ftp/i18n/pt.po @@ -0,0 +1,131 @@ +# Portuguese translation for openobject-addons +# Copyright (c) 2012 Rosetta Contributors and Canonical Ltd 2012 +# This file is distributed under the same license as the openobject-addons package. +# FIRST AUTHOR , 2012. +# +msgid "" +msgstr "" +"Project-Id-Version: openobject-addons\n" +"Report-Msgid-Bugs-To: FULL NAME \n" +"POT-Creation-Date: 2013-06-07 19:36+0000\n" +"PO-Revision-Date: 2013-01-18 10:48+0000\n" +"Last-Translator: Rui Franco (multibase.pt) \n" +"Language-Team: Portuguese \n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"X-Launchpad-Export-Date: 2013-11-21 06:07+0000\n" +"X-Generator: Launchpad (build 16831)\n" + +#. module: document_ftp +#: view:document.ftp.configuration:0 +msgid "Configure FTP Server" +msgstr "Configurar servidor de FTP" + +#. module: document_ftp +#: model:ir.model,name:document_ftp.model_document_ftp_configuration +msgid "Auto Directory Configuration" +msgstr "ConfiguraΓ§Γ£o automΓ‘tica de diretΓ³rios" + +#. module: document_ftp +#: view:document.ftp.configuration:0 +msgid "" +"Indicate the network address on which your OpenERP server should be " +"reachable for end-users. This depends on your network topology and " +"configuration, and will only affect the links displayed to the users. The " +"format is HOST:PORT and the default host (localhost) is only suitable for " +"access from the server machine itself.." +msgstr "" +"Indicar o endereΓ§o de rede em que o servidor OpenERP deve ser acedido por " +"utilizadores finais. Isso depende da topologia da rede e configuraΓ§Γ£o, e sΓ³ " +"vai afectar os links exibidos aos utilizadores. O formato Γ© HOST:PORT e o " +"host padrΓ£o (localhost) sΓ³ Γ© adequado para o acesso da mΓ‘quina do servidor " +"em si.." + +#. module: document_ftp +#: model:ir.model,name:document_ftp.model_knowledge_config_settings +msgid "knowledge.config.settings" +msgstr "knowledge.config.settings" + +#. module: document_ftp +#: model:ir.actions.act_url,name:document_ftp.action_document_browse +msgid "Browse Files" +msgstr "Procurar Ficheiros" + +#. module: document_ftp +#: help:knowledge.config.settings,document_ftp_url:0 +msgid "Click the url to browse the documents" +msgstr "Carregue no URL para percorrer os documentos" + +#. module: document_ftp +#: field:document.ftp.browse,url:0 +msgid "FTP Server" +msgstr "Servidor FTP" + +#. module: document_ftp +#: model:ir.actions.act_window,name:document_ftp.action_config_auto_directory +msgid "FTP Server Configuration" +msgstr "ConfiguraΓ§Γ£o do servidor FTP" + +#. module: document_ftp +#: field:knowledge.config.settings,document_ftp_url:0 +msgid "Browse Documents" +msgstr "Percorrer os documentos" + +#. module: document_ftp +#: view:document.ftp.browse:0 +msgid "_Browse" +msgstr "_Procurar" + +#. module: document_ftp +#: help:document.ftp.configuration,host:0 +msgid "" +"Server address or IP and port to which users should connect to for DMS access" +msgstr "" +"EndereΓ§o do servidor ou IP e a porta Γ  qual os utilizadores devem se conetar " +"para acesso DMS" + +#. module: document_ftp +#: model:ir.ui.menu,name:document_ftp.menu_document_browse +msgid "Shared Repository (FTP)" +msgstr "RepositΓ³rio Partilhado (FTP)" + +#. module: document_ftp +#: field:document.ftp.configuration,host:0 +msgid "Address" +msgstr "EndereΓ§o" + +#. module: document_ftp +#: view:document.ftp.browse:0 +msgid "Cancel" +msgstr "Cancelar" + +#. module: document_ftp +#: model:ir.model,name:document_ftp.model_document_ftp_browse +msgid "Document FTP Browse" +msgstr "Procurar Documento FTP" + +#. module: document_ftp +#: view:document.ftp.configuration:0 +msgid "Knowledge Application Configuration" +msgstr "ConfiguraΓ§Γ£o da AplicaΓ§Γ£o do conhecimento" + +#. module: document_ftp +#: model:ir.actions.act_window,name:document_ftp.action_ftp_browse +msgid "Document Browse" +msgstr "Pesquisar Documento" + +#. module: document_ftp +#: view:document.ftp.browse:0 +msgid "or" +msgstr "ou" + +#. module: document_ftp +#: view:document.ftp.browse:0 +msgid "Browse Document" +msgstr "Procurar Documento" + +#. module: document_ftp +#: view:document.ftp.configuration:0 +msgid "res_config_contents" +msgstr "res_config_contents" diff --git a/document_ftp/i18n/pt_BR.po b/document_ftp/i18n/pt_BR.po new file mode 100644 index 00000000..0298141f --- /dev/null +++ b/document_ftp/i18n/pt_BR.po @@ -0,0 +1,130 @@ +# Brazilian Portuguese translation for openobject-addons +# Copyright (c) 2012 Rosetta Contributors and Canonical Ltd 2012 +# This file is distributed under the same license as the openobject-addons package. +# FIRST AUTHOR , 2012. +# +msgid "" +msgstr "" +"Project-Id-Version: openobject-addons\n" +"Report-Msgid-Bugs-To: FULL NAME \n" +"POT-Creation-Date: 2013-06-07 19:36+0000\n" +"PO-Revision-Date: 2012-12-21 23:00+0000\n" +"Last-Translator: FULL NAME \n" +"Language-Team: Brazilian Portuguese \n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"X-Launchpad-Export-Date: 2013-11-21 06:07+0000\n" +"X-Generator: Launchpad (build 16831)\n" + +#. module: document_ftp +#: view:document.ftp.configuration:0 +msgid "Configure FTP Server" +msgstr "Configurar Servidor de FTP" + +#. module: document_ftp +#: model:ir.model,name:document_ftp.model_document_ftp_configuration +msgid "Auto Directory Configuration" +msgstr "ConfiguraΓ§Γ£o AutomΓ‘tica de DiretΓ³rio" + +#. module: document_ftp +#: view:document.ftp.configuration:0 +msgid "" +"Indicate the network address on which your OpenERP server should be " +"reachable for end-users. This depends on your network topology and " +"configuration, and will only affect the links displayed to the users. The " +"format is HOST:PORT and the default host (localhost) is only suitable for " +"access from the server machine itself.." +msgstr "" +"Indique o endereΓ§o em que seu servidor OpenERP deve ser acessΓ­vel para os " +"usuΓ‘rios finais. Isso depende da sua topologia de rede e configuraΓ§Γ£o. O " +"formato Γ© HOST:PORT e o host padrΓ£o (localhost) Γ© adequado apenas para " +"acesso a partir da mΓ‘quina prΓ³pria maquina.." + +#. module: document_ftp +#: model:ir.model,name:document_ftp.model_knowledge_config_settings +msgid "knowledge.config.settings" +msgstr "knowledge.config.settings" + +#. module: document_ftp +#: model:ir.actions.act_url,name:document_ftp.action_document_browse +msgid "Browse Files" +msgstr "Navegar pelos Arquivos" + +#. module: document_ftp +#: help:knowledge.config.settings,document_ftp_url:0 +msgid "Click the url to browse the documents" +msgstr "Clique na url para navegar pelos documentos" + +#. module: document_ftp +#: field:document.ftp.browse,url:0 +msgid "FTP Server" +msgstr "Servidor FTP" + +#. module: document_ftp +#: model:ir.actions.act_window,name:document_ftp.action_config_auto_directory +msgid "FTP Server Configuration" +msgstr "ConfiguraΓ§Γ£o do Servidor FTP" + +#. module: document_ftp +#: field:knowledge.config.settings,document_ftp_url:0 +msgid "Browse Documents" +msgstr "Navegar pelos Documentos" + +#. module: document_ftp +#: view:document.ftp.browse:0 +msgid "_Browse" +msgstr "(_B) Navegar" + +#. module: document_ftp +#: help:document.ftp.configuration,host:0 +msgid "" +"Server address or IP and port to which users should connect to for DMS access" +msgstr "" +"O endereΓ§o do servidor ou IP e porta que o usuΓ‘rio deve conectar para acesso " +"DMS." + +#. module: document_ftp +#: model:ir.ui.menu,name:document_ftp.menu_document_browse +msgid "Shared Repository (FTP)" +msgstr "RepositΓ³rio Compartilhado (FTP)" + +#. module: document_ftp +#: field:document.ftp.configuration,host:0 +msgid "Address" +msgstr "EndereΓ§o" + +#. module: document_ftp +#: view:document.ftp.browse:0 +msgid "Cancel" +msgstr "Cancelar" + +#. module: document_ftp +#: model:ir.model,name:document_ftp.model_document_ftp_browse +msgid "Document FTP Browse" +msgstr "Navegador FTP de Documentos" + +#. module: document_ftp +#: view:document.ftp.configuration:0 +msgid "Knowledge Application Configuration" +msgstr "ConfiguraΓ§Γ£o da AplicaΓ§Γ£o de Conhecimento" + +#. module: document_ftp +#: model:ir.actions.act_window,name:document_ftp.action_ftp_browse +msgid "Document Browse" +msgstr "Navegador de Documentos" + +#. module: document_ftp +#: view:document.ftp.browse:0 +msgid "or" +msgstr "ou" + +#. module: document_ftp +#: view:document.ftp.browse:0 +msgid "Browse Document" +msgstr "Navegar por Documentos" + +#. module: document_ftp +#: view:document.ftp.configuration:0 +msgid "res_config_contents" +msgstr "res_config_contents" diff --git a/document_ftp/i18n/ro.po b/document_ftp/i18n/ro.po new file mode 100644 index 00000000..97a1f7f9 --- /dev/null +++ b/document_ftp/i18n/ro.po @@ -0,0 +1,131 @@ +# Romanian translation for openobject-addons +# Copyright (c) 2012 Rosetta Contributors and Canonical Ltd 2012 +# This file is distributed under the same license as the openobject-addons package. +# FIRST AUTHOR , 2012. +# +msgid "" +msgstr "" +"Project-Id-Version: openobject-addons\n" +"Report-Msgid-Bugs-To: FULL NAME \n" +"POT-Creation-Date: 2013-06-07 19:36+0000\n" +"PO-Revision-Date: 2013-01-23 20:42+0000\n" +"Last-Translator: ERPSystems.ro \n" +"Language-Team: Romanian \n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"X-Launchpad-Export-Date: 2013-11-21 06:07+0000\n" +"X-Generator: Launchpad (build 16831)\n" + +#. module: document_ftp +#: view:document.ftp.configuration:0 +msgid "Configure FTP Server" +msgstr "Configureaza Serverul FTP" + +#. module: document_ftp +#: model:ir.model,name:document_ftp.model_document_ftp_configuration +msgid "Auto Directory Configuration" +msgstr "Configurare Director Automata" + +#. module: document_ftp +#: view:document.ftp.configuration:0 +msgid "" +"Indicate the network address on which your OpenERP server should be " +"reachable for end-users. This depends on your network topology and " +"configuration, and will only affect the links displayed to the users. The " +"format is HOST:PORT and the default host (localhost) is only suitable for " +"access from the server machine itself.." +msgstr "" +"Indica adresa retelei la care serverul OpenERP ar trebui sa fie accesibil " +"utilizatorilor finali. Aceasta depinde de topologia si configurarea retelei " +"dumneavoastra, si va afecta doar link-urile afisate utilizatorilor. Formatul " +"este HOST:PORT, iar gazda predenifinta (localhost) (gazda locala) este " +"potrivita doar pentru accesul de la server.." + +#. module: document_ftp +#: model:ir.model,name:document_ftp.model_knowledge_config_settings +msgid "knowledge.config.settings" +msgstr "cunostinte.config.setari" + +#. module: document_ftp +#: model:ir.actions.act_url,name:document_ftp.action_document_browse +msgid "Browse Files" +msgstr "Rasfoieste fisiere" + +#. module: document_ftp +#: help:knowledge.config.settings,document_ftp_url:0 +msgid "Click the url to browse the documents" +msgstr "Faceti click pe url pentru a cauta documentele" + +#. module: document_ftp +#: field:document.ftp.browse,url:0 +msgid "FTP Server" +msgstr "Server FTP" + +#. module: document_ftp +#: model:ir.actions.act_window,name:document_ftp.action_config_auto_directory +msgid "FTP Server Configuration" +msgstr "Configurare Server FTP" + +#. module: document_ftp +#: field:knowledge.config.settings,document_ftp_url:0 +msgid "Browse Documents" +msgstr "Cautare Documente" + +#. module: document_ftp +#: view:document.ftp.browse:0 +msgid "_Browse" +msgstr "_Rasfoieste" + +#. module: document_ftp +#: help:document.ftp.configuration,host:0 +msgid "" +"Server address or IP and port to which users should connect to for DMS access" +msgstr "" +"Adresa serverului sau IP-ul si portul la care utilizatorii ar trebui sa se " +"conecteze pentru acces DMS" + +#. module: document_ftp +#: model:ir.ui.menu,name:document_ftp.menu_document_browse +msgid "Shared Repository (FTP)" +msgstr "Depozit comun (FTP)" + +#. module: document_ftp +#: field:document.ftp.configuration,host:0 +msgid "Address" +msgstr "Adresa" + +#. module: document_ftp +#: view:document.ftp.browse:0 +msgid "Cancel" +msgstr "Anuleaza" + +#. module: document_ftp +#: model:ir.model,name:document_ftp.model_document_ftp_browse +msgid "Document FTP Browse" +msgstr "Rasfoieste Documentul FTP" + +#. module: document_ftp +#: view:document.ftp.configuration:0 +msgid "Knowledge Application Configuration" +msgstr "Configurare Aplicare Cunostinte" + +#. module: document_ftp +#: model:ir.actions.act_window,name:document_ftp.action_ftp_browse +msgid "Document Browse" +msgstr "Rasfoire document" + +#. module: document_ftp +#: view:document.ftp.browse:0 +msgid "or" +msgstr "sau" + +#. module: document_ftp +#: view:document.ftp.browse:0 +msgid "Browse Document" +msgstr "Rasfoieste Documentul" + +#. module: document_ftp +#: view:document.ftp.configuration:0 +msgid "res_config_contents" +msgstr "res_config_continuturi" diff --git a/document_ftp/i18n/ru.po b/document_ftp/i18n/ru.po new file mode 100644 index 00000000..ce93a7c1 --- /dev/null +++ b/document_ftp/i18n/ru.po @@ -0,0 +1,130 @@ +# Russian translation for openobject-addons +# Copyright (c) 2012 Rosetta Contributors and Canonical Ltd 2012 +# This file is distributed under the same license as the openobject-addons package. +# FIRST AUTHOR , 2012. +# +msgid "" +msgstr "" +"Project-Id-Version: openobject-addons\n" +"Report-Msgid-Bugs-To: FULL NAME \n" +"POT-Creation-Date: 2013-06-07 19:36+0000\n" +"PO-Revision-Date: 2012-12-21 23:00+0000\n" +"Last-Translator: FULL NAME \n" +"Language-Team: Russian \n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"X-Launchpad-Export-Date: 2013-11-21 06:07+0000\n" +"X-Generator: Launchpad (build 16831)\n" + +#. module: document_ftp +#: view:document.ftp.configuration:0 +msgid "Configure FTP Server" +msgstr "Настройка FTP сСрвСра" + +#. module: document_ftp +#: model:ir.model,name:document_ftp.model_document_ftp_configuration +msgid "Auto Directory Configuration" +msgstr "АвтоматичСская настройка ΠΊΠ°Ρ‚Π°Π»ΠΎΠ³Π°" + +#. module: document_ftp +#: view:document.ftp.configuration:0 +msgid "" +"Indicate the network address on which your OpenERP server should be " +"reachable for end-users. This depends on your network topology and " +"configuration, and will only affect the links displayed to the users. The " +"format is HOST:PORT and the default host (localhost) is only suitable for " +"access from the server machine itself.." +msgstr "" +"Π£ΠΊΠ°Π·Ρ‹Π²Π°Π΅Ρ‚ сСтСвой адрСс, Π½Π° ΠΊΠΎΡ‚ΠΎΡ€ΠΎΠΌ сСрвСр OpenERP Π΄ΠΎΠ»ΠΆΠ΅Π½ Π±Ρ‹Ρ‚ΡŒ доступСн для " +"ΠΊΠΎΠ½Π΅Ρ‡Π½Ρ‹Ρ… ΠΏΠΎΠ»ΡŒΠ·ΠΎΠ²Π°Ρ‚Π΅Π»Π΅ΠΉ. Π­Ρ‚ΠΎ зависит ΠΎΡ‚ ΠΊΠΎΠ½Ρ„ΠΈΠ³ΡƒΡ€Π°Ρ†ΠΈΠΈ ΠΈ Ρ‚ΠΎΠΏΠΎΠ»ΠΎΠ³ΠΈΠΈ сСти ΠΈ " +"влияСт Ρ‚ΠΎΠ»ΡŒΠΊΠΎ Π½Π° ΠΎΡ‚ΠΎΠ±Ρ€Π°ΠΆΠ΅Π½ΠΈΠ΅ ссылок. Π€ΠΎΡ€ΠΌΠ°Ρ‚ АДРЕБ:ПОРВ ΠΈ адрСс ΠΏΠΎ ΡƒΠΌΠΎΠ»Ρ‡Π°Π½ΠΈΡŽ " +"(localhost) ΠΏΠΎΠ΄Ρ…ΠΎΠ΄ΠΈΡ‚ Ρ‚ΠΎΠ»ΡŒΠΊΠΎ для доступа с самого сСрвСра." + +#. module: document_ftp +#: model:ir.model,name:document_ftp.model_knowledge_config_settings +msgid "knowledge.config.settings" +msgstr "knowledge.config.settings" + +#. module: document_ftp +#: model:ir.actions.act_url,name:document_ftp.action_document_browse +msgid "Browse Files" +msgstr "ΠŸΡ€ΠΎΡΠΌΠΎΡ‚Ρ€ Ρ„Π°ΠΉΠ»ΠΎΠ²" + +#. module: document_ftp +#: help:knowledge.config.settings,document_ftp_url:0 +msgid "Click the url to browse the documents" +msgstr "Π©Π΅Π»ΠΊΠ½ΠΈΡ‚Π΅ URL-адрСс для просмотра Π΄ΠΎΠΊΡƒΠΌΠ΅Π½Ρ‚ΠΎΠ²" + +#. module: document_ftp +#: field:document.ftp.browse,url:0 +msgid "FTP Server" +msgstr "FTP Π‘Π΅Ρ€Π²Π΅Ρ€" + +#. module: document_ftp +#: model:ir.actions.act_window,name:document_ftp.action_config_auto_directory +msgid "FTP Server Configuration" +msgstr "Настройка FTP сСрвСра" + +#. module: document_ftp +#: field:knowledge.config.settings,document_ftp_url:0 +msgid "Browse Documents" +msgstr "ΠŸΡ€ΠΎΡΠΌΠΎΡ‚Ρ€ Π΄ΠΎΠΊΡƒΠΌΠ΅Π½Ρ‚ΠΎΠ²" + +#. module: document_ftp +#: view:document.ftp.browse:0 +msgid "_Browse" +msgstr "_ΠžΠ±Π·ΠΎΡ€" + +#. module: document_ftp +#: help:document.ftp.configuration,host:0 +msgid "" +"Server address or IP and port to which users should connect to for DMS access" +msgstr "" +"АдрСс сСрвСра ΠΈΠ»ΠΈ IP ΠΈ ΠΏΠΎΡ€Ρ‚ для соСдинСния ΠΏΠΎΠ»ΡŒΠ·ΠΎΠ²Π°Ρ‚Π΅Π»Ρ ΠΈ доступа ΠΊ " +"Π΄ΠΎΠΊΡƒΠΌΠ΅Π½Ρ‚Π°ΠΌ" + +#. module: document_ftp +#: model:ir.ui.menu,name:document_ftp.menu_document_browse +msgid "Shared Repository (FTP)" +msgstr "ΠžΠ±Ρ‰Π΅Π΅ Ρ…Ρ€Π°Π½ΠΈΠ»ΠΈΡ‰Π΅ (FTP)" + +#. module: document_ftp +#: field:document.ftp.configuration,host:0 +msgid "Address" +msgstr "АдрСс" + +#. module: document_ftp +#: view:document.ftp.browse:0 +msgid "Cancel" +msgstr "ΠžΡ‚ΠΌΠ΅Π½Π°" + +#. module: document_ftp +#: model:ir.model,name:document_ftp.model_document_ftp_browse +msgid "Document FTP Browse" +msgstr "ΠŸΡ€ΠΎΡΠΌΠΎΡ‚Ρ€ Π΄ΠΎΠΊΡƒΠΌΠ΅Π½Ρ‚Π° Ρ‡Π΅Ρ€Π΅Π· FTP" + +#. module: document_ftp +#: view:document.ftp.configuration:0 +msgid "Knowledge Application Configuration" +msgstr "Настройка прилоТСния Знания" + +#. module: document_ftp +#: model:ir.actions.act_window,name:document_ftp.action_ftp_browse +msgid "Document Browse" +msgstr "ΠŸΡ€ΠΎΡΠΌΠΎΡ‚Ρ€ Π΄ΠΎΠΊΡƒΠΌΠ΅Π½Ρ‚Π°" + +#. module: document_ftp +#: view:document.ftp.browse:0 +msgid "or" +msgstr "ΠΈΠ»ΠΈ" + +#. module: document_ftp +#: view:document.ftp.browse:0 +msgid "Browse Document" +msgstr "ΠŸΡ€ΠΎΡΠΌΠΎΡ‚Ρ€ Π΄ΠΎΠΊΡƒΠΌΠ΅Π½Ρ‚Π°" + +#. module: document_ftp +#: view:document.ftp.configuration:0 +msgid "res_config_contents" +msgstr "res_config_contents" diff --git a/document_ftp/i18n/sk.po b/document_ftp/i18n/sk.po new file mode 100644 index 00000000..6567b51c --- /dev/null +++ b/document_ftp/i18n/sk.po @@ -0,0 +1,124 @@ +# Slovak translation for openobject-addons +# Copyright (c) 2012 Rosetta Contributors and Canonical Ltd 2012 +# This file is distributed under the same license as the openobject-addons package. +# FIRST AUTHOR , 2012. +# +msgid "" +msgstr "" +"Project-Id-Version: openobject-addons\n" +"Report-Msgid-Bugs-To: FULL NAME \n" +"POT-Creation-Date: 2013-06-07 19:36+0000\n" +"PO-Revision-Date: 2012-12-21 23:00+0000\n" +"Last-Translator: FULL NAME \n" +"Language-Team: Slovak \n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"X-Launchpad-Export-Date: 2013-11-21 06:07+0000\n" +"X-Generator: Launchpad (build 16831)\n" + +#. module: document_ftp +#: view:document.ftp.configuration:0 +msgid "Configure FTP Server" +msgstr "" + +#. module: document_ftp +#: model:ir.model,name:document_ftp.model_document_ftp_configuration +msgid "Auto Directory Configuration" +msgstr "" + +#. module: document_ftp +#: view:document.ftp.configuration:0 +msgid "" +"Indicate the network address on which your OpenERP server should be " +"reachable for end-users. This depends on your network topology and " +"configuration, and will only affect the links displayed to the users. The " +"format is HOST:PORT and the default host (localhost) is only suitable for " +"access from the server machine itself.." +msgstr "" + +#. module: document_ftp +#: model:ir.model,name:document_ftp.model_knowledge_config_settings +msgid "knowledge.config.settings" +msgstr "" + +#. module: document_ftp +#: model:ir.actions.act_url,name:document_ftp.action_document_browse +msgid "Browse Files" +msgstr "PrehΔΎadΓ‘vaΕ₯ sΓΊbory" + +#. module: document_ftp +#: help:knowledge.config.settings,document_ftp_url:0 +msgid "Click the url to browse the documents" +msgstr "" + +#. module: document_ftp +#: field:document.ftp.browse,url:0 +msgid "FTP Server" +msgstr "FTP Server" + +#. module: document_ftp +#: model:ir.actions.act_window,name:document_ftp.action_config_auto_directory +msgid "FTP Server Configuration" +msgstr "Nastavenia FTP Servera" + +#. module: document_ftp +#: field:knowledge.config.settings,document_ftp_url:0 +msgid "Browse Documents" +msgstr "" + +#. module: document_ftp +#: view:document.ftp.browse:0 +msgid "_Browse" +msgstr "" + +#. module: document_ftp +#: help:document.ftp.configuration,host:0 +msgid "" +"Server address or IP and port to which users should connect to for DMS access" +msgstr "" + +#. module: document_ftp +#: model:ir.ui.menu,name:document_ftp.menu_document_browse +msgid "Shared Repository (FTP)" +msgstr "" + +#. module: document_ftp +#: field:document.ftp.configuration,host:0 +msgid "Address" +msgstr "Adresa" + +#. module: document_ftp +#: view:document.ftp.browse:0 +msgid "Cancel" +msgstr "" + +#. module: document_ftp +#: model:ir.model,name:document_ftp.model_document_ftp_browse +msgid "Document FTP Browse" +msgstr "" + +#. module: document_ftp +#: view:document.ftp.configuration:0 +msgid "Knowledge Application Configuration" +msgstr "" + +#. module: document_ftp +#: model:ir.actions.act_window,name:document_ftp.action_ftp_browse +msgid "Document Browse" +msgstr "" + +#. module: document_ftp +#: view:document.ftp.browse:0 +msgid "or" +msgstr "" + +#. module: document_ftp +#: view:document.ftp.browse:0 +msgid "Browse Document" +msgstr "" + +#. module: document_ftp +#: view:document.ftp.configuration:0 +msgid "res_config_contents" +msgstr "" diff --git a/document_ftp/i18n/sl.po b/document_ftp/i18n/sl.po new file mode 100644 index 00000000..622c5754 --- /dev/null +++ b/document_ftp/i18n/sl.po @@ -0,0 +1,130 @@ +# Slovenian translation for openobject-addons +# Copyright (c) 2012 Rosetta Contributors and Canonical Ltd 2012 +# This file is distributed under the same license as the openobject-addons package. +# FIRST AUTHOR , 2012. +# +msgid "" +msgstr "" +"Project-Id-Version: openobject-addons\n" +"Report-Msgid-Bugs-To: FULL NAME \n" +"POT-Creation-Date: 2013-06-07 19:36+0000\n" +"PO-Revision-Date: 2013-11-09 10:27+0000\n" +"Last-Translator: Darja Zorman \n" +"Language-Team: Slovenian \n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"X-Launchpad-Export-Date: 2013-11-21 06:07+0000\n" +"X-Generator: Launchpad (build 16831)\n" + +#. module: document_ftp +#: view:document.ftp.configuration:0 +msgid "Configure FTP Server" +msgstr "Konfiguriraj FTP streΕΎnik" + +#. module: document_ftp +#: model:ir.model,name:document_ftp.model_document_ftp_configuration +msgid "Auto Directory Configuration" +msgstr "Avto konfiguracija map" + +#. module: document_ftp +#: view:document.ftp.configuration:0 +msgid "" +"Indicate the network address on which your OpenERP server should be " +"reachable for end-users. This depends on your network topology and " +"configuration, and will only affect the links displayed to the users. The " +"format is HOST:PORT and the default host (localhost) is only suitable for " +"access from the server machine itself.." +msgstr "" +"Določa omreΕΎni naslov, na katerem bo dosegljiv vaΕ‘ OpenERP server za končne " +"uporabnike. To je odvisno od topologije in nastavitev vaΕ‘ega omreΕΎja. Vpliva " +"samo na povezavo, ki se izpiΕ‘e uporabniku. Format je HOST:PORT in privzeti " +"streΕΎnik (localhost) je primeren samo za dostop iz serverja samega." + +#. module: document_ftp +#: model:ir.model,name:document_ftp.model_knowledge_config_settings +msgid "knowledge.config.settings" +msgstr "knowledge.config.settings" + +#. module: document_ftp +#: model:ir.actions.act_url,name:document_ftp.action_document_browse +msgid "Browse Files" +msgstr "Brskanje Datoteke" + +#. module: document_ftp +#: help:knowledge.config.settings,document_ftp_url:0 +msgid "Click the url to browse the documents" +msgstr "Potrdite url za brskanje po dokumentih" + +#. module: document_ftp +#: field:document.ftp.browse,url:0 +msgid "FTP Server" +msgstr "StreΕΎnik FTP" + +#. module: document_ftp +#: model:ir.actions.act_window,name:document_ftp.action_config_auto_directory +msgid "FTP Server Configuration" +msgstr "Konfiguracija FTP streΕΎnika" + +#. module: document_ftp +#: field:knowledge.config.settings,document_ftp_url:0 +msgid "Browse Documents" +msgstr "brskanje po dokumentih" + +#. module: document_ftp +#: view:document.ftp.browse:0 +msgid "_Browse" +msgstr "_Prebrskaj" + +#. module: document_ftp +#: help:document.ftp.configuration,host:0 +msgid "" +"Server address or IP and port to which users should connect to for DMS access" +msgstr "" +"Naslov streΕΎnika ali IP in vrata, na katerega se bodo uporabniki povezali za " +"DMS dostop" + +#. module: document_ftp +#: model:ir.ui.menu,name:document_ftp.menu_document_browse +msgid "Shared Repository (FTP)" +msgstr "Deljeni repozitorij (FTP)" + +#. module: document_ftp +#: field:document.ftp.configuration,host:0 +msgid "Address" +msgstr "Naslov" + +#. module: document_ftp +#: view:document.ftp.browse:0 +msgid "Cancel" +msgstr "Preklic" + +#. module: document_ftp +#: model:ir.model,name:document_ftp.model_document_ftp_browse +msgid "Document FTP Browse" +msgstr "FTP brskanje po dokumentih" + +#. module: document_ftp +#: view:document.ftp.configuration:0 +msgid "Knowledge Application Configuration" +msgstr "Nastavitev aplikacije znanja" + +#. module: document_ftp +#: model:ir.actions.act_window,name:document_ftp.action_ftp_browse +msgid "Document Browse" +msgstr "Brskanje po dokumentih" + +#. module: document_ftp +#: view:document.ftp.browse:0 +msgid "or" +msgstr "ali" + +#. module: document_ftp +#: view:document.ftp.browse:0 +msgid "Browse Document" +msgstr "Brskanje po dokumentu" + +#. module: document_ftp +#: view:document.ftp.configuration:0 +msgid "res_config_contents" +msgstr "res_config_contents" diff --git a/document_ftp/i18n/sr.po b/document_ftp/i18n/sr.po new file mode 100644 index 00000000..2af0fc6c --- /dev/null +++ b/document_ftp/i18n/sr.po @@ -0,0 +1,126 @@ +# Serbian translation for openobject-addons +# Copyright (c) 2012 Rosetta Contributors and Canonical Ltd 2012 +# This file is distributed under the same license as the openobject-addons package. +# FIRST AUTHOR , 2012. +# +msgid "" +msgstr "" +"Project-Id-Version: openobject-addons\n" +"Report-Msgid-Bugs-To: FULL NAME \n" +"POT-Creation-Date: 2013-06-07 19:36+0000\n" +"PO-Revision-Date: 2012-12-21 23:00+0000\n" +"Last-Translator: FULL NAME \n" +"Language-Team: Serbian \n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"X-Launchpad-Export-Date: 2013-11-21 06:07+0000\n" +"X-Generator: Launchpad (build 16831)\n" + +#. module: document_ftp +#: view:document.ftp.configuration:0 +msgid "Configure FTP Server" +msgstr "" + +#. module: document_ftp +#: model:ir.model,name:document_ftp.model_document_ftp_configuration +msgid "Auto Directory Configuration" +msgstr "Auto Podesavanje dIrektorijuma" + +#. module: document_ftp +#: view:document.ftp.configuration:0 +msgid "" +"Indicate the network address on which your OpenERP server should be " +"reachable for end-users. This depends on your network topology and " +"configuration, and will only affect the links displayed to the users. The " +"format is HOST:PORT and the default host (localhost) is only suitable for " +"access from the server machine itself.." +msgstr "" + +#. module: document_ftp +#: model:ir.model,name:document_ftp.model_knowledge_config_settings +msgid "knowledge.config.settings" +msgstr "" + +#. module: document_ftp +#: model:ir.actions.act_url,name:document_ftp.action_document_browse +msgid "Browse Files" +msgstr "Pretrazi fajlove" + +#. module: document_ftp +#: help:knowledge.config.settings,document_ftp_url:0 +msgid "Click the url to browse the documents" +msgstr "" + +#. module: document_ftp +#: field:document.ftp.browse,url:0 +msgid "FTP Server" +msgstr "FTP Server" + +#. module: document_ftp +#: model:ir.actions.act_window,name:document_ftp.action_config_auto_directory +msgid "FTP Server Configuration" +msgstr "KOnfiguracija FTP Servera" + +#. module: document_ftp +#: field:knowledge.config.settings,document_ftp_url:0 +msgid "Browse Documents" +msgstr "" + +#. module: document_ftp +#: view:document.ftp.browse:0 +msgid "_Browse" +msgstr "_Browse" + +#. module: document_ftp +#: help:document.ftp.configuration,host:0 +msgid "" +"Server address or IP and port to which users should connect to for DMS access" +msgstr "" +"Adresa Servera ili IP i Port na koji bi korisnik trebalo da se konektuje za " +"DMS pristup" + +#. module: document_ftp +#: model:ir.ui.menu,name:document_ftp.menu_document_browse +msgid "Shared Repository (FTP)" +msgstr "Deljeno skladiste (FTP)" + +#. module: document_ftp +#: field:document.ftp.configuration,host:0 +msgid "Address" +msgstr "" + +#. module: document_ftp +#: view:document.ftp.browse:0 +msgid "Cancel" +msgstr "" + +#. module: document_ftp +#: model:ir.model,name:document_ftp.model_document_ftp_browse +msgid "Document FTP Browse" +msgstr "FTP pretrazi Dokumente" + +#. module: document_ftp +#: view:document.ftp.configuration:0 +msgid "Knowledge Application Configuration" +msgstr "" + +#. module: document_ftp +#: model:ir.actions.act_window,name:document_ftp.action_ftp_browse +msgid "Document Browse" +msgstr "Pretrazi Dokumente" + +#. module: document_ftp +#: view:document.ftp.browse:0 +msgid "or" +msgstr "" + +#. module: document_ftp +#: view:document.ftp.browse:0 +msgid "Browse Document" +msgstr "Pretrazi Dokument" + +#. module: document_ftp +#: view:document.ftp.configuration:0 +msgid "res_config_contents" +msgstr "res_config_contents" diff --git a/document_ftp/i18n/sr@latin.po b/document_ftp/i18n/sr@latin.po new file mode 100644 index 00000000..08f95389 --- /dev/null +++ b/document_ftp/i18n/sr@latin.po @@ -0,0 +1,131 @@ +# Serbian Latin translation for openobject-addons +# Copyright (c) 2012 Rosetta Contributors and Canonical Ltd 2012 +# This file is distributed under the same license as the openobject-addons package. +# FIRST AUTHOR , 2012. +# +msgid "" +msgstr "" +"Project-Id-Version: openobject-addons\n" +"Report-Msgid-Bugs-To: FULL NAME \n" +"POT-Creation-Date: 2013-06-07 19:36+0000\n" +"PO-Revision-Date: 2012-12-21 23:00+0000\n" +"Last-Translator: FULL NAME \n" +"Language-Team: Serbian Latin \n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"X-Launchpad-Export-Date: 2013-11-21 06:07+0000\n" +"X-Generator: Launchpad (build 16831)\n" + +#. module: document_ftp +#: view:document.ftp.configuration:0 +msgid "Configure FTP Server" +msgstr "Podesi FTP server" + +#. module: document_ftp +#: model:ir.model,name:document_ftp.model_document_ftp_configuration +msgid "Auto Directory Configuration" +msgstr "Auto-podeΕ‘avanje direktorijuma" + +#. module: document_ftp +#: view:document.ftp.configuration:0 +msgid "" +"Indicate the network address on which your OpenERP server should be " +"reachable for end-users. This depends on your network topology and " +"configuration, and will only affect the links displayed to the users. The " +"format is HOST:PORT and the default host (localhost) is only suitable for " +"access from the server machine itself.." +msgstr "" +"Odredite adresu mreΕΎe na koju bi VaΕ‘i OpenERP server trebalo da bude " +"dostupan za konačne korisnike. Ovo zavisi od VaΕ‘e mreΕΎne toplogije i " +"podeΕ‘avanja, i neΔ‡e imati uticaja na linkove prikazane korisnicima. Format " +"je HOST:PORT i domaΔ‡in po defaultu (localhost) je jedino prikladan za " +"pristup na server sa same maΕ‘ine." + +#. module: document_ftp +#: model:ir.model,name:document_ftp.model_knowledge_config_settings +msgid "knowledge.config.settings" +msgstr "" + +#. module: document_ftp +#: model:ir.actions.act_url,name:document_ftp.action_document_browse +msgid "Browse Files" +msgstr "PretraΕΎi datoteke" + +#. module: document_ftp +#: help:knowledge.config.settings,document_ftp_url:0 +msgid "Click the url to browse the documents" +msgstr "" + +#. module: document_ftp +#: field:document.ftp.browse,url:0 +msgid "FTP Server" +msgstr "FTP Server" + +#. module: document_ftp +#: model:ir.actions.act_window,name:document_ftp.action_config_auto_directory +msgid "FTP Server Configuration" +msgstr "PodeΕ‘avanje FTP servera" + +#. module: document_ftp +#: field:knowledge.config.settings,document_ftp_url:0 +msgid "Browse Documents" +msgstr "" + +#. module: document_ftp +#: view:document.ftp.browse:0 +msgid "_Browse" +msgstr "_PretraΕΎi" + +#. module: document_ftp +#: help:document.ftp.configuration,host:0 +msgid "" +"Server address or IP and port to which users should connect to for DMS access" +msgstr "" +"Adresa Servera ili IP i Port na koji bi korisnik trebalo da se konektuje za " +"DMS pristup" + +#. module: document_ftp +#: model:ir.ui.menu,name:document_ftp.menu_document_browse +msgid "Shared Repository (FTP)" +msgstr "Deljeno skladiΕ‘te" + +#. module: document_ftp +#: field:document.ftp.configuration,host:0 +msgid "Address" +msgstr "Adresa" + +#. module: document_ftp +#: view:document.ftp.browse:0 +msgid "Cancel" +msgstr "" + +#. module: document_ftp +#: model:ir.model,name:document_ftp.model_document_ftp_browse +msgid "Document FTP Browse" +msgstr "PretraΕΎi FTP dokumente" + +#. module: document_ftp +#: view:document.ftp.configuration:0 +msgid "Knowledge Application Configuration" +msgstr "PodeΕ‘avanje aplikacije znanja" + +#. module: document_ftp +#: model:ir.actions.act_window,name:document_ftp.action_ftp_browse +msgid "Document Browse" +msgstr "Pretrazi Dokumente" + +#. module: document_ftp +#: view:document.ftp.browse:0 +msgid "or" +msgstr "" + +#. module: document_ftp +#: view:document.ftp.browse:0 +msgid "Browse Document" +msgstr "PretraΕΎi dokumenta" + +#. module: document_ftp +#: view:document.ftp.configuration:0 +msgid "res_config_contents" +msgstr "res_config_contents" diff --git a/document_ftp/i18n/sv.po b/document_ftp/i18n/sv.po new file mode 100644 index 00000000..dc2e1d31 --- /dev/null +++ b/document_ftp/i18n/sv.po @@ -0,0 +1,131 @@ +# Swedish translation for openobject-addons +# Copyright (c) 2012 Rosetta Contributors and Canonical Ltd 2012 +# This file is distributed under the same license as the openobject-addons package. +# FIRST AUTHOR , 2012. +# +msgid "" +msgstr "" +"Project-Id-Version: openobject-addons\n" +"Report-Msgid-Bugs-To: FULL NAME \n" +"POT-Creation-Date: 2013-06-07 19:36+0000\n" +"PO-Revision-Date: 2014-03-27 12:39+0000\n" +"Last-Translator: Anders Wallenquist \n" +"Language-Team: Swedish \n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"X-Launchpad-Export-Date: 2014-03-28 06:44+0000\n" +"X-Generator: Launchpad (build 16967)\n" + +#. module: document_ftp +#: view:document.ftp.configuration:0 +msgid "Configure FTP Server" +msgstr "Konfigurera FTP-servern" + +#. module: document_ftp +#: model:ir.model,name:document_ftp.model_document_ftp_configuration +msgid "Auto Directory Configuration" +msgstr "Automatisk katalogkonfiguration" + +#. module: document_ftp +#: view:document.ftp.configuration:0 +msgid "" +"Indicate the network address on which your OpenERP server should be " +"reachable for end-users. This depends on your network topology and " +"configuration, and will only affect the links displayed to the users. The " +"format is HOST:PORT and the default host (localhost) is only suitable for " +"access from the server machine itself.." +msgstr "" +"Ange nΓ€tverksadressen som din OpenERP servern ska vara nΓ₯bar pΓ₯ fΓΆr " +"slutanvΓ€ndare. Det beror pΓ₯ ditt nΓ€tverk topologi och konfiguration, och " +"kommer endast att pΓ₯verka hur lΓ€nkarna visas fΓΆr anvΓ€ndarna. Formatet Γ€r " +"vΓ€rd: PORT och fΓΆrvald vΓ€rd (localhost) Γ€r endast lΓ€mplig fΓΆr Γ₯tkomst frΓ₯n " +"servern sjΓ€lva maskinen .." + +#. module: document_ftp +#: model:ir.model,name:document_ftp.model_knowledge_config_settings +msgid "knowledge.config.settings" +msgstr "knowledge.config.settings" + +#. module: document_ftp +#: model:ir.actions.act_url,name:document_ftp.action_document_browse +msgid "Browse Files" +msgstr "BlΓ€ddra bland filer" + +#. module: document_ftp +#: help:knowledge.config.settings,document_ftp_url:0 +msgid "Click the url to browse the documents" +msgstr "Klicka pΓ₯ url-en fΓΆr att blΓ€ddra bland dokumenten" + +#. module: document_ftp +#: field:document.ftp.browse,url:0 +msgid "FTP Server" +msgstr "FTP-server" + +#. module: document_ftp +#: model:ir.actions.act_window,name:document_ftp.action_config_auto_directory +msgid "FTP Server Configuration" +msgstr "FTP-serverkonfiguration" + +#. module: document_ftp +#: field:knowledge.config.settings,document_ftp_url:0 +msgid "Browse Documents" +msgstr "DokumentblΓ€ddring" + +#. module: document_ftp +#: view:document.ftp.browse:0 +msgid "_Browse" +msgstr "_BlΓ€ddra" + +#. module: document_ftp +#: help:document.ftp.configuration,host:0 +msgid "" +"Server address or IP and port to which users should connect to for DMS access" +msgstr "" +"Serveradress eller IP och den port som anvΓ€ndarna ska ansluta till fΓΆr DMS-" +"tillgΓ₯ng" + +#. module: document_ftp +#: model:ir.ui.menu,name:document_ftp.menu_document_browse +msgid "Shared Repository (FTP)" +msgstr "Delat arkiv (FTP)" + +#. module: document_ftp +#: field:document.ftp.configuration,host:0 +msgid "Address" +msgstr "Adress" + +#. module: document_ftp +#: view:document.ftp.browse:0 +msgid "Cancel" +msgstr "Avbryt" + +#. module: document_ftp +#: model:ir.model,name:document_ftp.model_document_ftp_browse +msgid "Document FTP Browse" +msgstr "BlΓ€ddra bland dokumenten via FTP" + +#. module: document_ftp +#: view:document.ftp.configuration:0 +msgid "Knowledge Application Configuration" +msgstr "Konfiguration av kunskapshanteringsapplikationen" + +#. module: document_ftp +#: model:ir.actions.act_window,name:document_ftp.action_ftp_browse +msgid "Document Browse" +msgstr "DokumentblΓ€ddring" + +#. module: document_ftp +#: view:document.ftp.browse:0 +msgid "or" +msgstr "eller" + +#. module: document_ftp +#: view:document.ftp.browse:0 +msgid "Browse Document" +msgstr "BlΓ€ddra bland dokument" + +#. module: document_ftp +#: view:document.ftp.configuration:0 +msgid "res_config_contents" +msgstr "res_config_contents" diff --git a/document_ftp/i18n/tr.po b/document_ftp/i18n/tr.po new file mode 100644 index 00000000..ff71f253 --- /dev/null +++ b/document_ftp/i18n/tr.po @@ -0,0 +1,130 @@ +# Turkish translation for openobject-addons +# Copyright (c) 2012 Rosetta Contributors and Canonical Ltd 2012 +# This file is distributed under the same license as the openobject-addons package. +# FIRST AUTHOR , 2012. +# +msgid "" +msgstr "" +"Project-Id-Version: openobject-addons\n" +"Report-Msgid-Bugs-To: FULL NAME \n" +"POT-Creation-Date: 2013-06-07 19:36+0000\n" +"PO-Revision-Date: 2013-02-06 22:28+0000\n" +"Last-Translator: Ahmet AltΔ±nışık \n" +"Language-Team: Turkish \n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"X-Launchpad-Export-Date: 2013-11-21 06:07+0000\n" +"X-Generator: Launchpad (build 16831)\n" + +#. module: document_ftp +#: view:document.ftp.configuration:0 +msgid "Configure FTP Server" +msgstr "FTP Sunucusunu YapΔ±landΔ±r" + +#. module: document_ftp +#: model:ir.model,name:document_ftp.model_document_ftp_configuration +msgid "Auto Directory Configuration" +msgstr "Otomatik KlasΓΆr YapΔ±landΔ±rmasΔ±" + +#. module: document_ftp +#: view:document.ftp.configuration:0 +msgid "" +"Indicate the network address on which your OpenERP server should be " +"reachable for end-users. This depends on your network topology and " +"configuration, and will only affect the links displayed to the users. The " +"format is HOST:PORT and the default host (localhost) is only suitable for " +"access from the server machine itself.." +msgstr "" +"OpenERP sunucusunda son kullanΔ±cΔ±larΔ±n erişebileceği ağ adresini belirtir. " +"Bu, ağ yapΔ±nΔ±za ve yapΔ±landΔ±rmasΔ±na bağlΔ±dΔ±r ve yalnΔ±zca kullanΔ±cΔ±lara " +"gΓΆsterilen ağlarΔ± etkiler. FormatΔ± HOST:PORT şeklindedir ve varsayΔ±lan " +"sunucu (localhost) yalnΔ±zca sunucu cihazΔ±ndan erişim iΓ§in uygundur." + +#. module: document_ftp +#: model:ir.model,name:document_ftp.model_knowledge_config_settings +msgid "knowledge.config.settings" +msgstr "knowledge.config.settings" + +#. module: document_ftp +#: model:ir.actions.act_url,name:document_ftp.action_document_browse +msgid "Browse Files" +msgstr "Dosyalara GΓΆzat" + +#. module: document_ftp +#: help:knowledge.config.settings,document_ftp_url:0 +msgid "Click the url to browse the documents" +msgstr "DΓΆkΓΌmanlarΔ± incelemek iΓ§in adrese tΔ±klayΔ±n" + +#. module: document_ftp +#: field:document.ftp.browse,url:0 +msgid "FTP Server" +msgstr "FTP Sunucusu" + +#. module: document_ftp +#: model:ir.actions.act_window,name:document_ftp.action_config_auto_directory +msgid "FTP Server Configuration" +msgstr "FTP Sunucusu YapΔ±landΔ±rmasΔ±" + +#. module: document_ftp +#: field:knowledge.config.settings,document_ftp_url:0 +msgid "Browse Documents" +msgstr "DΓΆkΓΌmanlarΔ± Δ°ncele" + +#. module: document_ftp +#: view:document.ftp.browse:0 +msgid "_Browse" +msgstr "_GΓΆzat" + +#. module: document_ftp +#: help:document.ftp.configuration,host:0 +msgid "" +"Server address or IP and port to which users should connect to for DMS access" +msgstr "" +"DMS erişimi iΓ§in kullanΔ±cΔ±larΔ±n bağlanacağı sunucu adresi ya da IP ve " +"bağlantΔ± noktasΔ±dΔ±r." + +#. module: document_ftp +#: model:ir.ui.menu,name:document_ftp.menu_document_browse +msgid "Shared Repository (FTP)" +msgstr "Paylaşılan Havuz (FTP)" + +#. module: document_ftp +#: field:document.ftp.configuration,host:0 +msgid "Address" +msgstr "Adres" + +#. module: document_ftp +#: view:document.ftp.browse:0 +msgid "Cancel" +msgstr "Δ°ptal" + +#. module: document_ftp +#: model:ir.model,name:document_ftp.model_document_ftp_browse +msgid "Document FTP Browse" +msgstr "FTP BelgeTaramasΔ±" + +#. module: document_ftp +#: view:document.ftp.configuration:0 +msgid "Knowledge Application Configuration" +msgstr "Bilgi Birikimi Uygulama AyarlarΔ±" + +#. module: document_ftp +#: model:ir.actions.act_window,name:document_ftp.action_ftp_browse +msgid "Document Browse" +msgstr "Belge Tarama" + +#. module: document_ftp +#: view:document.ftp.browse:0 +msgid "or" +msgstr "ya da" + +#. module: document_ftp +#: view:document.ftp.browse:0 +msgid "Browse Document" +msgstr "Belge Tara" + +#. module: document_ftp +#: view:document.ftp.configuration:0 +msgid "res_config_contents" +msgstr "res_config_contents" diff --git a/document_ftp/i18n/vi.po b/document_ftp/i18n/vi.po new file mode 100644 index 00000000..66585e99 --- /dev/null +++ b/document_ftp/i18n/vi.po @@ -0,0 +1,124 @@ +# Vietnamese translation for openobject-addons +# Copyright (c) 2013 Rosetta Contributors and Canonical Ltd 2013 +# This file is distributed under the same license as the openobject-addons package. +# FIRST AUTHOR , 2013. +# +msgid "" +msgstr "" +"Project-Id-Version: openobject-addons\n" +"Report-Msgid-Bugs-To: FULL NAME \n" +"POT-Creation-Date: 2013-06-07 19:36+0000\n" +"PO-Revision-Date: 2013-01-17 09:02+0000\n" +"Last-Translator: FULL NAME \n" +"Language-Team: Vietnamese \n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"X-Launchpad-Export-Date: 2013-11-21 06:07+0000\n" +"X-Generator: Launchpad (build 16831)\n" + +#. module: document_ftp +#: view:document.ftp.configuration:0 +msgid "Configure FTP Server" +msgstr "" + +#. module: document_ftp +#: model:ir.model,name:document_ftp.model_document_ftp_configuration +msgid "Auto Directory Configuration" +msgstr "" + +#. module: document_ftp +#: view:document.ftp.configuration:0 +msgid "" +"Indicate the network address on which your OpenERP server should be " +"reachable for end-users. This depends on your network topology and " +"configuration, and will only affect the links displayed to the users. The " +"format is HOST:PORT and the default host (localhost) is only suitable for " +"access from the server machine itself.." +msgstr "" + +#. module: document_ftp +#: model:ir.model,name:document_ftp.model_knowledge_config_settings +msgid "knowledge.config.settings" +msgstr "" + +#. module: document_ftp +#: model:ir.actions.act_url,name:document_ftp.action_document_browse +msgid "Browse Files" +msgstr "" + +#. module: document_ftp +#: help:knowledge.config.settings,document_ftp_url:0 +msgid "Click the url to browse the documents" +msgstr "" + +#. module: document_ftp +#: field:document.ftp.browse,url:0 +msgid "FTP Server" +msgstr "" + +#. module: document_ftp +#: model:ir.actions.act_window,name:document_ftp.action_config_auto_directory +msgid "FTP Server Configuration" +msgstr "" + +#. module: document_ftp +#: field:knowledge.config.settings,document_ftp_url:0 +msgid "Browse Documents" +msgstr "" + +#. module: document_ftp +#: view:document.ftp.browse:0 +msgid "_Browse" +msgstr "" + +#. module: document_ftp +#: help:document.ftp.configuration,host:0 +msgid "" +"Server address or IP and port to which users should connect to for DMS access" +msgstr "" + +#. module: document_ftp +#: model:ir.ui.menu,name:document_ftp.menu_document_browse +msgid "Shared Repository (FTP)" +msgstr "" + +#. module: document_ftp +#: field:document.ftp.configuration,host:0 +msgid "Address" +msgstr "" + +#. module: document_ftp +#: view:document.ftp.browse:0 +msgid "Cancel" +msgstr "" + +#. module: document_ftp +#: model:ir.model,name:document_ftp.model_document_ftp_browse +msgid "Document FTP Browse" +msgstr "" + +#. module: document_ftp +#: view:document.ftp.configuration:0 +msgid "Knowledge Application Configuration" +msgstr "" + +#. module: document_ftp +#: model:ir.actions.act_window,name:document_ftp.action_ftp_browse +msgid "Document Browse" +msgstr "" + +#. module: document_ftp +#: view:document.ftp.browse:0 +msgid "or" +msgstr "" + +#. module: document_ftp +#: view:document.ftp.browse:0 +msgid "Browse Document" +msgstr "" + +#. module: document_ftp +#: view:document.ftp.configuration:0 +msgid "res_config_contents" +msgstr "" diff --git a/document_ftp/i18n/zh_CN.po b/document_ftp/i18n/zh_CN.po new file mode 100644 index 00000000..6a5b2fa1 --- /dev/null +++ b/document_ftp/i18n/zh_CN.po @@ -0,0 +1,126 @@ +# Chinese (Simplified) translation for openobject-addons +# Copyright (c) 2012 Rosetta Contributors and Canonical Ltd 2012 +# This file is distributed under the same license as the openobject-addons package. +# FIRST AUTHOR , 2012. +# +msgid "" +msgstr "" +"Project-Id-Version: openobject-addons\n" +"Report-Msgid-Bugs-To: FULL NAME \n" +"POT-Creation-Date: 2013-06-07 19:36+0000\n" +"PO-Revision-Date: 2013-07-19 16:04+0000\n" +"Last-Translator: η›ˆι€š ccdos \n" +"Language-Team: Chinese (Simplified) \n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"X-Launchpad-Export-Date: 2013-11-21 06:07+0000\n" +"X-Generator: Launchpad (build 16831)\n" + +#. module: document_ftp +#: view:document.ftp.configuration:0 +msgid "Configure FTP Server" +msgstr "ι…η½ FTP ζœεŠ‘ε™¨" + +#. module: document_ftp +#: model:ir.model,name:document_ftp.model_document_ftp_configuration +msgid "Auto Directory Configuration" +msgstr "θ‡ͺ动η›ε½•θΎη½" + +#. module: document_ftp +#: view:document.ftp.configuration:0 +msgid "" +"Indicate the network address on which your OpenERP server should be " +"reachable for end-users. This depends on your network topology and " +"configuration, and will only affect the links displayed to the users. The " +"format is HOST:PORT and the default host (localhost) is only suitable for " +"access from the server machine itself.." +msgstr "" +"θΎ“ε…₯δ½ ηš„ζœ€η»ˆη”¨ζˆ·η”¨δΊŽθΏžζŽ₯δ½ OpenERPζœεŠ‘ε™¨ηš„η½‘ε€γ€‚εŸΊδΊŽδ½ ηš„η½‘η»œι…η½οΌŒδΌšε½±ε“ε’ζˆ·η«―ζ˜Ύη€Ίηš„ι“ΎζŽ₯γ€‚ζ ΌεΌζ˜―οΌšδΈ»ζœΊ:端口。注意localhostεͺζ˜―εœ¨ζœεŠ‘ε™¨ζœ¬ζœΊ" +"ζ‰ζœ‰η”¨ηš„γ€‚" + +#. module: document_ftp +#: model:ir.model,name:document_ftp.model_knowledge_config_settings +msgid "knowledge.config.settings" +msgstr "knowledge.config.settings" + +#. module: document_ftp +#: model:ir.actions.act_url,name:document_ftp.action_document_browse +msgid "Browse Files" +msgstr "ζ΅θ§ˆζ–‡δ»Ά" + +#. module: document_ftp +#: help:knowledge.config.settings,document_ftp_url:0 +msgid "Click the url to browse the documents" +msgstr "点击 urk ζ₯ζ΅θ§ˆζ–‡ζ‘£" + +#. module: document_ftp +#: field:document.ftp.browse,url:0 +msgid "FTP Server" +msgstr "FTP ζœεŠ‘ε™¨" + +#. module: document_ftp +#: model:ir.actions.act_window,name:document_ftp.action_config_auto_directory +msgid "FTP Server Configuration" +msgstr "FTP ζœεŠ‘ε™¨θΎη½" + +#. module: document_ftp +#: field:knowledge.config.settings,document_ftp_url:0 +msgid "Browse Documents" +msgstr "ζ΅θ§ˆζ–‡ζ‘£" + +#. module: document_ftp +#: view:document.ftp.browse:0 +msgid "_Browse" +msgstr "桏览(_B)" + +#. module: document_ftp +#: help:document.ftp.configuration,host:0 +msgid "" +"Server address or IP and port to which users should connect to for DMS access" +msgstr "ζœεŠ‘ε™¨εœ°ε€ζˆ–IPεœ°ε€οΌŒδ»₯εŠη«―ε£γ€‚η”¨δΊŽη”¨ζˆ·θΏžζŽ₯ζ–‡ζ‘£η‘η†η³»η»Ÿγ€‚" + +#. module: document_ftp +#: model:ir.ui.menu,name:document_ftp.menu_document_browse +msgid "Shared Repository (FTP)" +msgstr "ε…±δΊ«δ»“εΊ“οΌˆFTPοΌ‰" + +#. module: document_ftp +#: field:document.ftp.configuration,host:0 +msgid "Address" +msgstr "εœ°ε€" + +#. module: document_ftp +#: view:document.ftp.browse:0 +msgid "Cancel" +msgstr "ε–ζΆˆ" + +#. module: document_ftp +#: model:ir.model,name:document_ftp.model_document_ftp_browse +msgid "Document FTP Browse" +msgstr "ζ–‡ζ‘£ FTP 桏览" + +#. module: document_ftp +#: view:document.ftp.configuration:0 +msgid "Knowledge Application Configuration" +msgstr "ηŸ₯θ―†η‘理应用θΎη½" + +#. module: document_ftp +#: model:ir.actions.act_window,name:document_ftp.action_ftp_browse +msgid "Document Browse" +msgstr "ζ–‡ζ‘£ζ΅θ§ˆ" + +#. module: document_ftp +#: view:document.ftp.browse:0 +msgid "or" +msgstr "ζˆ–" + +#. module: document_ftp +#: view:document.ftp.browse:0 +msgid "Browse Document" +msgstr "ζ΅θ§ˆζ–‡ζ‘£" + +#. module: document_ftp +#: view:document.ftp.configuration:0 +msgid "res_config_contents" +msgstr "res_config_contents" diff --git a/document_ftp/i18n/zh_TW.po b/document_ftp/i18n/zh_TW.po new file mode 100644 index 00000000..3c7461f7 --- /dev/null +++ b/document_ftp/i18n/zh_TW.po @@ -0,0 +1,126 @@ +# Chinese (Traditional) translation for openobject-addons +# Copyright (c) 2012 Rosetta Contributors and Canonical Ltd 2012 +# This file is distributed under the same license as the openobject-addons package. +# FIRST AUTHOR , 2012. +# +msgid "" +msgstr "" +"Project-Id-Version: openobject-addons\n" +"Report-Msgid-Bugs-To: FULL NAME \n" +"POT-Creation-Date: 2013-06-07 19:36+0000\n" +"PO-Revision-Date: 2012-12-21 23:00+0000\n" +"Last-Translator: FULL NAME \n" +"Language-Team: Chinese (Traditional) \n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"X-Launchpad-Export-Date: 2013-11-21 06:07+0000\n" +"X-Generator: Launchpad (build 16831)\n" + +#. module: document_ftp +#: view:document.ftp.configuration:0 +msgid "Configure FTP Server" +msgstr "θ¨­η½FTP δΌΊζœε™¨" + +#. module: document_ftp +#: model:ir.model,name:document_ftp.model_document_ftp_configuration +msgid "Auto Directory Configuration" +msgstr "θ‡ͺε‹•η›ιŒ„配η½" + +#. module: document_ftp +#: view:document.ftp.configuration:0 +msgid "" +"Indicate the network address on which your OpenERP server should be " +"reachable for end-users. This depends on your network topology and " +"configuration, and will only affect the links displayed to the users. The " +"format is HOST:PORT and the default host (localhost) is only suitable for " +"access from the server machine itself.." +msgstr "" +"θͺͺ明OpenERPηš„ζœε‹™ε™¨δΈŠηš„ηΆ²η΅‘εœ°ε€ζ‡‰θ©²η‚Ίζœ€η΅‚η”¨ζˆΆθ¨ͺε•γ€‚ι€™ε–ζ±Ίζ–Όζ‚¨ηš„ηΆ²η΅‘ζ‹“ζ’²ε’Œι…η½οΌŒθ€ŒδΈ”εͺζœƒε½±ιŸΏι‘―η€Ίη΅¦η”¨ζˆΆηš„ιˆζŽ₯γ€‚ηš„ζ ΌεΌη‚ΊHOST:PORTε’Œι θ¨­ηš„δΈ»ζ©Ÿ" +"(localhostοΌ‰ηš„θ¨ͺε•εΎžζœε‹™ε™¨ζœ¬θΊ«εͺ適合.." + +#. module: document_ftp +#: model:ir.model,name:document_ftp.model_knowledge_config_settings +msgid "knowledge.config.settings" +msgstr "" + +#. module: document_ftp +#: model:ir.actions.act_url,name:document_ftp.action_document_browse +msgid "Browse Files" +msgstr "瀏覽ζͺ”ζ‘ˆ" + +#. module: document_ftp +#: help:knowledge.config.settings,document_ftp_url:0 +msgid "Click the url to browse the documents" +msgstr "" + +#. module: document_ftp +#: field:document.ftp.browse,url:0 +msgid "FTP Server" +msgstr "FTP δΌΊζœε™¨" + +#. module: document_ftp +#: model:ir.actions.act_window,name:document_ftp.action_config_auto_directory +msgid "FTP Server Configuration" +msgstr "FTP Server Configuration" + +#. module: document_ftp +#: field:knowledge.config.settings,document_ftp_url:0 +msgid "Browse Documents" +msgstr "" + +#. module: document_ftp +#: view:document.ftp.browse:0 +msgid "_Browse" +msgstr "瀏覽(_B)" + +#. module: document_ftp +#: help:document.ftp.configuration,host:0 +msgid "" +"Server address or IP and port to which users should connect to for DMS access" +msgstr "ζœε‹™ε™¨εœ°ε€ζˆ–IPεœ°ε€ε’Œη«―ε£οΌŒη”¨ζˆΆζ‡‰θ©²ι€£ζŽ₯到DMSθ¨ͺ問" + +#. module: document_ftp +#: model:ir.ui.menu,name:document_ftp.menu_document_browse +msgid "Shared Repository (FTP)" +msgstr "δΎ›δΊ«ηš„ε„²ε­˜εΊ«" + +#. module: document_ftp +#: field:document.ftp.configuration,host:0 +msgid "Address" +msgstr "εœ°ε€" + +#. module: document_ftp +#: view:document.ftp.browse:0 +msgid "Cancel" +msgstr "" + +#. module: document_ftp +#: model:ir.model,name:document_ftp.model_document_ftp_browse +msgid "Document FTP Browse" +msgstr "瀏覽FTPζ–‡δ»Ά" + +#. module: document_ftp +#: view:document.ftp.configuration:0 +msgid "Knowledge Application Configuration" +msgstr "ηŸ₯θ­˜ζ‡‰η”¨ι…η½" + +#. module: document_ftp +#: model:ir.actions.act_window,name:document_ftp.action_ftp_browse +msgid "Document Browse" +msgstr "文仢瀏覽" + +#. module: document_ftp +#: view:document.ftp.browse:0 +msgid "or" +msgstr "" + +#. module: document_ftp +#: view:document.ftp.browse:0 +msgid "Browse Document" +msgstr "瀏覽文仢" + +#. module: document_ftp +#: view:document.ftp.configuration:0 +msgid "res_config_contents" +msgstr "res_config_contents" diff --git a/document_ftp/images/1_configure_ftp.jpeg b/document_ftp/images/1_configure_ftp.jpeg new file mode 100644 index 0000000000000000000000000000000000000000..3a14d433debcda9bdbf35970622c8f77f3b998e1 GIT binary patch literal 79350 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

moFG7M3 z{x+kXB3NAt#`{!1*RSoyHb(YAWH4>KV?A0>0e+(h=wKYg+~t6vUB--50JSwAf0Na` z*mqobyC0(9I+8sm`Jmgl-idzDzbK3RF!Q#?lRrTqsBXu;T){@;KZpgmc@Kvtuq9=1M^w3{P6hQe##w{P% zqr6kk85DfwQIP>4;J}1jj_!`Ta8-B^mT^6(_6++(+e=@W3%?apeS2-APfuDxHtlrF zuIr`hmwf(I=a(#%z6H~FZoDopeUA5d>OI7MPtkq<57Dx!f-!U(mO&woieMJbdty}2 zilb|x5glQ9d76Sem`;@V1(7-qSJ1bj^dqRZp5sV^d8l@STip4M(W5=+)%+r?pdIIO zXpvST81w=(j>w0kzz4U#kR=D>OKR8jCh9^~%n3Lfu}I!@F;@HzK00hLva`hTWveIg z^ds91B9z%hS1?sPb2O_*~voZ&CG18EwP=d8-wk+jquH(IeXIdMdqiM zJ+@(8w2kD8KRxieuzJNWTSY&y?t*hdoVeA+w-c3cfOuXWO5^3Qv}4p{mCJ67SQYCz ztLy0o4^#N22lUV+_tJpI?96bPAnH}Hh_7^kJ-yu&%rP}p{sY7OHr5{yN%T3~ z^_zFzBugmPxL@^z0BQf7awN>gKS^k$WvYjaUYGwl2dFS5s0pF7@o33$(WnP3MUC^k5_|QHh4{CqW7?@HdX1>UL zMT+pdxRsx&(3tf3-_B(Tni4Yj2t{&w-DLSf0*0E=H!qC72?hool#P560h3Ub8IwGY z#i-uLyzNF;axjL2_t(zYBh2PL+@|}n<1EN?6d4vq4OJ-HlpkfPEztfM zeVvzPa%)M>!$cO5Zc;xne4SHN+BsNmSb}EXfkind44o1xjrB;I=YM*US&&@i{>kI` zO`ClGR4}|j-rhae+kAH?cJ}q)dPm0+l_wRT=>RRP)lg6-sh8qi#R~&NW6D4ZLoNAw znFc&ytc7OFVpA(x)qhK57Zs*!yY_y3~P#H+Wgkk50~(ImZCBF(P61IrTc9fwvq zc14_&EzQTac9-hj)9Nhpn;anro_T6@CvpfH?lmJQTM027M$JG5L5ReeWW?HHxMXq<8WGNM4HCTWn=1>+&vs`&F^j5k z4tuWSr^&^D$SVEgN^lf-lRKM5(mJR~ft&n8GCk+=#QWVmz$UHQeN9>ZX8kQn&~`CN ze#LF)W-GM0uY-~rX#-y6i6|1f-u63_(w8!~mT z?exKl7YO|>&n$F?xHS79>F9CxaOa0>Sv>}KUaw5S%|x(0)bS^-J0mN}sG!{*0(qjT zae(SS4-k9py%mk{-7yuCS1JEzbb$K>sA=-+4~tJpmLL>cP7Y_*fbnHPR1;^k5pq+Q zWaq;Js$N{Td|Yp5?*51tMfzDAwCAxk9jt(xAy0_`?}XR{e4{c^<#K@0D#c;^gy?y! z=~}qXdvFy43aW8V-b%31dn7t)%a`5;bECsHa=iTiC1$cQxPzKNr2UEt3 zvJn+cPO%?5;>B9bli*y89w>~B^Mi8oBS*#eSyd65_F8!|iJS>9ciD`(&3FzEv{{md5HP_mN$s0*y+!ncDX27?GY z?k@osClNeBO&5;Fq`Dc$w6^lNWG<_!*}20wgh@SpIPX>Ar)3QRTUd^PGdGNPMBV9JhqgRx5ty@>sH9SBc9sOut(8$ z)NMpzHjkhp+?p)FF6t1(}d_vWyZS#xdS#O0uAZ*4kcPV>2my{px=?dS8zp@!@{9yC=b ztvEH6i)CnG@UWp_yRmhxjAy1Hq5|S~I4OGNp*pHouDu!(mnHQJTxk?RQ~XWP3P#-ax9x+c&05 zt?W2XoA5e61G(6nQRMLk%78RJ)M(`H749_x!mhz}6+mK3{2sy_+!g!b*$M}AZXgd! zB$pzzANbC*+81K$U~G*euX722*NA@~>gl~N2FJ=BbatviOy}xqWWUMkYUEog4`e?P zsZumSG#K#yHy{QtD!{NREBTKN*r!c{2=d*tEJfMYXouDZgsT*t zx8VtP&dN?YM|?8rv3Ir%wOslc0Sx8Y1m7)aDJ7ms#$4sJv^I5eQu+hOHaCsT?gCAP z(aGM>37lf*%e=%kLu>T)0Alu)+u;FRiZV(T$D*s9rWAs7{wO8n#6B*(_}@44`GZ`{ zwZ%A3M42lzJ#3i*F?cXeJEToA3+{knch3~2$zH>{R(<+X)_0{G!{m{a5Eo6$xd9AR zX5|MmFCJ8{n!q}kWQwVKWbemVm`>zQ62%oT62teoQnx-W`6Esp z3$`>7IZ5#4w+@fCi+t|1#pvE&@iIDUA5PjH92%~g3h~j4wtSf#y;5G%z!;f${KZM? zF6r48GiZuet8P+dhbda`(YQSZZ2U#nHjS=YDvC3&^Tm33l$bh|u)brlN8|!y@0rWx zAD`lkLX7R)a%LA&pd;qjia}!jaE_>MX04g*0X1RC!Td`{gzBkn!<*@;R$K%*dRg@u z2?d7z<6RL+&ZRlJ$S>A9UW;AOFGwbtYBaU753Ti5F2NdB{64L|PH_psarN`~tjU zWeHopjnK)CzNRvm*k9mpx#f_@bH6B4vsLrKDrJw{X1ZU2RtsgX%K`D}^UAB|f?|d| z_@PNP^V?-x#~ys?vmT;gskiZhFpOeG-76`nikk*CV1I5_h3wQKRaR6W1cbBz_Ew+d zqKDiSkDznO%gN+15>kk~?w)^brP+ZW9m)3kI{sB*HNp?BNIqNp#C%5=r`r?#-BJ}lbq22xKj zS1UPy&!!rFK4^h^6|V3~ULJZrhN9rU=N_J58CGg)rR7j3+J(I!6Rm0PdcG4=f~W;e z5N4zTsC7OsD$&I!1YT~e_%g+v-YGMaCdY`U)oETB6eyMw-nmoA(z4nmH*P5=%`d*u zJ1trA^x>w{zX2N2S}E;|*Z20`L71aewk<>S3x$*Q(Ag8<>*c~GzkF%UnnN=t)1tNn zIO@6W8C)>{ZaUA32N-cPA&;Ej!$Nx=fj0Uj-^oaV3=LRL4Mzx-~1Bv}`7d z(kv2P-5p4M!mXdx3unUvwiQO+min0CV<=RyVB>#W$o9#b#hR+*xYXlVhMP~mKU$~+Z$19hu zjvfAVM8mm>uKLIX2o0SMm@lC$7W0TGVm?8E#dS-+wsM1HIL zWQY}nzD$n3Q66eCF#jpc^<};HL}R2E-y1Dc7~p09GoOBMX?wRd=&bw_{2AXNb+5A- zAEP4S1)srF6el2^E(~{b@vTB2% z`8(JRMlI6^{n}sC`YM`Vgnccv1<1W^-OvZPnj;F*^jkC?%`0RY8gI{NK+=Cs5iMbr zyyfMh1HmtKV~wZS)UlBKSgkIU zSIGU98#=>nkXO*cheTOYRxH28Bbe(uk!!WZ%~?ojj#HMA3l&VOyaHQSWNrUUaEI#w zF|xu2p%2C8KZ_CLhqgaNIZsi!Svc-mN$6LRnp5LTcdP(g`hyRrW)qiv=!?R-SVVG% z%|3XkBe&D>Evlv6n~&4Hr%^B6WJ*$jSUYyC6G!R~%OtDw3!ZG}4R0LHc-=k64TZGK z?`)Ac#Pc|Y6PGKtVjAriIR0~(& zXjmbo-e$U~-8Lf_PyfGe212E3wk}#6dpiTgjdg#z08r?+dKNL95q(-)<%B)Jz zbA&5Wt+$+(2bK?X8fYd|JFpFcmYIaQ>BpkkYm)6qivfompK1KB(fVZ$k>+3UdEH^7 zd~hsbzB-l@$){v$r=kbQ-_wz87 zY2a-MJfsM_u^-T5F^QJcQFMA~=>lq$BMdE7TNb&tY_-@z(PoI0un_CGQdr-lAQ-x# zzq-i7=SV4cgeSUmITH_qr!c<*Ds{3F16NBEWbu@4~_6u)2CZf7T`wK z?f^?E-lslH6fNkgOs_6!BLT*Tm+K10-@dKx2yJ9;X^KxV69|4?$jM?lsZWs26D&n! z8(_zJUWkfNGgjP@=eK#{LJ5Hs)@cCdTsz1s!|N15W4)tWe0VRLT)b|TBTqC20rf|3 z1GKfB>&2@RMcFhHqQtd&13t*1+lc@>EZ+>8iTE`R+IO?K1kp51+A8NGhQ7dC^_QV; zeMjeRBYZX7C@4klTBO*FW-`6ExDx6J(cWnPo`h%>nb?)U(Iv!#rdmOtUHPiN?LQ45 zqEqyP1O{ZbeYw$C?o-j>xX{+w+zro*#T2$5lc^_;Mmy9h*3fZ#7F+9)L5z(>T&C-z z7;>-yNii0!UED|-!glYcy%K@faUo$YhyC@p;4TL+!C zYQ_3Al%avvCg!EUuSbxKQp1qsEHfx(CWWAQ{4QXxno^HsJ^mXfV=-&g}aqTkW ze15L1KpK&N;oG(+jm^6(9Mrt^B7v242Wi6ItEGXJOS*7a-A6}qd|zui+RG%9vm^dT}qwgO=PV12?!6_}t|(2(+I&ZtF#A6!1#BK9!v zit87t0*i5@3RNPr!#D`=E~2sPiuk6( z-V@_NB(2Dcc;KjsRaQX?70ns!EiJ*pOojy-4}~Ix!SDGlYf{WnCp;;GrKz?kK;Otb zi#MD%8P%e{ILHmq&=3HT(-M40pgmP8GtQ0QJ(QA!D1;pG04R zU8iv#dN>)G2;M-@K>GE;)g zVBF#w{ct5U3n3}qHOGaDEynD3nK5s?he%}aytReDN2<0R9~f{uIPmA86grkW1Wt0S zF99c|sJH3l^Fd#HTudV)3kD{AUUosE02d_inh$(D50-B`xxg0}=kfRFv7AqwMV+?b7M3`eY;_P#QujH4#5!fH)F9cI3S?6*?^dGVwC~ zZkSSg8sv8Wq{FVn#9#nn*gnc+`$a^aS}-M57>1DNZ-i>09JpmMZbq&7YZ+bHg7 zdCoAlM+vSW*n{ENc&V~}OL3{PyO)_Ts-=TnVLB{Mr;EYi0%8^te&Dq+^mJaL=bW^` zFyc#$4_+r=C*0>t{Jz(igm3pQLS3oY{E!CS4XUFZ%knBz^H8kK8=o44vQU(djfgDr>`m(qWwa*5!-UnpUc1*E{6UDtZ zFb{r8+PGjDU}vq#1?{}owR)G-3kf@O(&hJ?Juj2OlR#7f_(R}GF&?&zw<_r>do=xK zpeA^zrj(F5?>M3BG4zDJytj*m0vD2kJ#pq~N&NvW_rGvyrM?d9-=NNU&XXlX`9L2Lc{35Pwyz_mscV}A zDG<27@Pjo(uXvPi+Po)W_6?!whpm<-M9}mXUfRlb>!h2BCaA8WyspSqvHFq5o6uMl z&44FHbw0e8m1P`~vF_*@^#aoNeLzYRL|(t_s6)$he*Uz8=K$@pC+6VK2pX%_kH|3U z<+rSJ5p2bKuh(>CD+9MjcS;-#hLoGGR~d*TQVWp#7=p*L6?Ky6?&CL@a@+EMRS(}X zF1A7+yvWnfMh0|$Dqkb&!~#}s{9diVu~^NAU$u&W0o)+db-*cEA;I-@;PUD;P%B^y zqt*Ty110TrzSMiNYu@5BQ>E|()!F@7Ro4yu#ru6R-t{=ytQ_*86y+l)WH__r4m4ke zJrGo&_797C93CB;Su=YJ1J{0_;qq{hz`neh=`zXWx-Xy4@1tvtb(n~8?+}5NO+Y!v z3SlV%>qy*2_S=;8HA(wzjTii}NeM4v7$NaX)>4Un1;j(Ofh0RF^l!#icj~ar>HREj zJLuX})pQGMRtk5qg;`!de^<<7pLmc)4=?MTo)PqDGc~X65{VxkJ&pD{CGv7uNbQNz7`Szd!%5;Ao8g zsn6RVM{6$a4ZB8o-UnaCu{fa#;m;d@4E=5p{1%uQh z(Md)Q?0JdTu%6-NRjpukJ>@tr2Y4%*SdmU_} zR`~K>3KUQ#&whyRFng=6SZn5SUH3NW`xg;na&lJy(`B+6g8)zDS;`fbyP;kmzP8;y zz13xCy3{3DWVJNtDrYo;qXkH+=P;U9+L`zYC$g@>%;JfJoA2OJhZ0xhwn-S;ca&bD zSx(HNEc761XF@>+Zl`r)n0{hhr=*1U2*yz#9929cGUBQCAXZ=}!;E#)^?YV%Z1Q$j zfv{0EQ-9}6=&8MaBXNbxs!I;<>`Np#XMJeTzTtUB-uT8 zy9612#;@}K?i~tqG|X9Qk*_X5ok`?O_96T*FP`Sqzdm6c`Dwt=>BB(&R}?VO&GBvO zWXc+$j4y8FIp-2>ig(v(m@D+d%ko~DO0)Pyg=R>ZG*MJ_-IT$d(A;NMbeQA5-hd^h zAjaAxz_yCyE)PyssV&huao34<*V?}Ci&qdmxmZlyF*X|en3rbTIBJl_pvQbQY|IcR z=e}376MskJ6k}7B_ZWRY5dfk=sEB8-7my#ZV@V(uUB02GR{i1~Mr~lKO^yyVR7wTr zm%QN_kEu$}2iei*pPo*pTNS2OvN3H`UdGOS8#4bcRDZuY5}D{{K1BZY><0a7Hgmv3 zbb%)-_WnS2B)nT8glem6Ez8kwP#p`eGmZqBpKVY(55qY=R%7*eGvry*bTN2wPh-l9 z)R7hs>WdV&qLDH}ab3f83v{fCY~j`0+=p8XqP)dCMJ6xSr5O^kt#j4wZqcmlP;}ma z{)%*(E9BCjW83GOO&QwqRn$2R^2uMJ7i6^Ooi%po;L`!i*0Xe=T+e1wvlOu-uq$Lm zO!j7aTMFxIv0pDpJbG2kayA6maN&c}w+ZtYYB6ax(b-U5_q@`rEu&CzTI+dk-d`v0 zAXj=SH1?U2%Hv75jdQKa^`;Y?>l!&1Ljc^ zPSxP&t}85MnA=OKuqjB|GZ3j<@7iF?7L6@cWt=;0-cw}~C+-oLo(e02@yPX~LBnp{ zWOyNe2d;^&m?=w3jbBzO15+Rb9W~@4-|mlB_~;yiwN4^2*SL9X=BAV1#dI8tBIyr( z(fRBe77z9k^I~`_Yvudh+h3Uy6K_is`hNE-t3e*dVsmU|jp952=4jYhw)2Nsv{#v#3d&`%r%H;9ks1K8&C>@G6ZZ?v>Px$3+yOrYTrtQq z972V^Q8*|@sW#F$@7+fZ>r-=c>uB6=8UnNu-c+bec}ojrS+R|m z9-pd3DaMr}7^zb6Zb+sXS7Yz2YgQ`y>|9o^$1A=H*~UXu?ZzLj4^oX{Q?4g0wR zpipbXvPAS3b&nc!;}M8AHp@NXAXxEpwtWD>iZ|7sqS`ChglIS_SUmeu&IRKHl*z>T zRGX%8Dtie`cmgO2-@Lk&Kb5u9iX3U zK$1>dJt8PbH5Xf@U5xsj8HdKM5`|D$pk89CW<+iRb>F|iFAdeE%$@g-2Nc-pND5KP z6B~yVdaMPL77qBl&; z&Y!3e(6H60LWyhdIl@aBKRB9s&fDf>6w0w>Sxo!=;Asv`0c%D(a<3(U>h|g%+BI#Z zes!F7Tk3rdvdXe{TD$@nju&S(j*e=XoHTT*KyG;`)jSi^xU|{Z1GKWeE(J_%pLYl? z6L2L|w4_tHNJ-zZLk8(!o!!`ik}C)2y}L^q2Tf+ME}%3l3>g|}R7*$<1!uYbI(hFK zyO33D@(x(@2#?jjcHOw}f^}?2c6a)*uAAwL-J{rC!VK48Ai40BnZwH^FMP?3OVtg` ze5IH(7z-Xc8tWU|CXBQt>xB<%M_MjMEbn8D&q7m@RT1?Q$SG)CL;T1psT>}*!I^dM zVx915zDEwS$X4Fqh4$ft^i5W^DrnkYWXJa6OM14h3c2Z}R@>N&SOWld3{aeuJV8&h z&Bn>0xf|S{1vJi_F-5h)t^~IVl(WHBv#c)nws&UgqEq-6$Kow3Fg|k`Z_cJAH2GD8 z&9|^v?CfjhE48wacmcle{q}R_Opg8Oj0O1#gbtPWc$kwxjUkX-Yu3>8p1bfJ%xij= zYJAyYDn}hv_p0cMwMw4C4h=f2fD#t2!)iF7dS6#6u8w$rjfK(2emW6>8cYdeEGRS- zuJggT@J;#OOkn$qETQQrgjy`>sv_sdTMDL9rlDCeXqBq84qQPmi8>0U4RA=BmBl^| zdmrRzP3;SwOfal)ys-`yF-S|A#`6b&j)HMxgCDl2<}7@_Ah6?@;@W}bs~;NC-*&XO zboJkT-y7nBAg9s{Sy7+KMxoi%BVyTphO?z=eQeig9dK5)s#b!;uj4`R*Ghx-5v3 z4B5AgeAr}%aG5w;`1VBfeos_I)R7Ls_*V4 zvG+~OC@@Ia;l^)co#tZZZW$FNBSqAe`DL7geTvwJW8K(Y0|Go>{PnVi6OZguP_3p? z6bc@mJT=F&`;^EKK;j5z?U0n1G{)r#;tjHH$(48VUnGtlHHGk+CUsYB;#_H-g;7CBp83rNPUt43_B&jq)LOlBh1aNZbs9;>!m zpm`@T4Tx^luy6OXXg*-oAMh5ATU)5MjjHJd2xpbOuOK=f?3x|7LmJIuTpEzi@AGp% zm@$aCf7s8zmx#(0J(L~;G^S)UJMSfR7IDofzNxwZ_wM&el-u)q)PJw+X28hG(@rXw z$hTFcKHHq|)px%4mJhn4DqS;5o4QZXCValvMD1-<2ie>*nq-P~x@sTg-mdQ3HT`k> zL@;WSA!Xg)UzTzCgKj55_UT=@og4czGnV%(d~%L5ZPh2_iG2(^=7EXwP$PnXi?8ua zQ^!+Po`sN&-))0^G8dre=xej^7|9)?$Ai)CH;f-L12b_Fs7}z8#A`j%$ZfULttwy~ zXzH7+>9ZcHt=y|6&yW@#Y-Y8Sfu;Ou3U3GvW?utRqAIGCbdLdqRSNZhW6Y%4=^ggE z=2?iV;02HnNdA{DkAm>kAkPR;7Vgo2gLvxChKAySsZTYM1Au_mKyJSGikgq&Uvwwy zw%D^cQd&9(aYU&FEwMV8*ljWp8KZ$6tUGxNYW9NjY<5jUN^MhTs6G@qi$tdoVrX1L zjE2oO;^q#ekK}RM0Y@Hhoe&#^i6%S+UDqnUPs=A~l_BN=A53nJ!D+5Achj$Hm1u!} zJOTqqbF%L?c~jCqgha;g7)+LS$-FNY0>q?w&qy|%X_%%zoVD?(%V*~x9Ch_Z2A$Yz zew)kT^0t??o2n@ARkNxL^m9YUM;i_DaW+CO0en(uVah`8gKJytA4aOGrj6)!Fxxyv+BagK4Lg?Vm~OHtcC z*M%y_UEEwMoKS8R617x%gvKnhVi6G|f|&pt8|ayF1UQcco9)-;BFWl8V#~9QbWh4J zx+@cDGjDdX^$&QN7VebC_-nPyWThnl0R{ z^jd{?v+;L2vN6mb`@C)&6yoS`D?YRaXR;Pv`E8sn)6;)z0^@GZ`-!KJ*LuW4h#mv8 z^nGeAKOQun4S7Qei?J=ff0`PS)ZS1cY+wINBrZv+1950#qRDLP{OyaUsSR^PLSmVD zh#RVLHLQ|F+Q`eApSpfV*SmMTItgeYqK1ZIxtQ#UL+@4|mF0YK{kCpvYDY@3y~-zK z4@KMvOeX!2Vq%8b^EM$4Z@=N+_`NPhSvhaV}_uP9r6rDfRpwr1#dDQ-Z? z5)|8v?AJ#5IkK>TuK@#IX5hn3m7GM~ut5Mph}C9hw6I|9$nt!mydAaQ)Z8O)7FL@^|v9o<^F<9 zf}hFiqUMG_Vv$|t4})Anr+$^dfJF(O?QS};vIStwD9ZRm0m_UI2lrt8INyK z=)dg%(nU{R1pboDS{}O9Nx(i8m@;%K%k%>R?DkUZQxxk>|Lqr!ZP z+*^l~Wom1sS)&1vXA4NaG~#rPX`x0fJW5=ofR*D$aU%0XQ^z*;O9m9UB&s&3JYc!P z)+?T=>H(}n)21AWa%Bo8y(8A|nc7cG)JmvgAf}eEnB}2iQMjIDa&&YujBDb{Y5oQO zRc}lvw1~}Lncp*ffks7zQ0c}d_2>i4Y?-{{DNG{5h*S3=eM4wLrs7=6EKH4^V8?Gg zSlSG7W2ju-N6T^uUsZ=_axv-WI>z`H73%U4;TI=U?~iD6^}0V6#K9 z7a>#YW9q|OS-N5(YDLo(GG}fPB-v=eVZd=*r0Z<>4d`nQ%Y2)NocMM-vk6mO6+iB3 z>RRxAMy|F%*z6t=B(kj~oLL%%Q^?XVa@v$8F6B1Ndg2nRUyTr&z4HtoHc( zPzzz#lT_pvk7Vv)E|--b<S`AX%zwLm2@eL_uHq@@%Z=gMqPq)6#ad3q~@3 zj<;SUHMK|djk2o=mQcMg)M9;0w=%?oA|`|XmT^xoS!;B|X=W~_BvO&R&ZSjQ?FndU zXSy;jAtlJ6-Mr(Oy=%c{*fZACM+L?1YfeILMfqc~WmWJ)D6M1RVsBfMXbKe5g)bYG za{Gx7QsH#(a1JYKyp;p5;U@0%LDn79G^afpN? z&R{v#Q+ahw2@rkOI+k2}4F-Rzh4;9d?EN6cIR-C4zIT^u1KYP3RrfW4Rf0a>-s z4qOWuh5~*2o8G1oZ?08qz%07CU4OV2RGm`R0cU3nGpdff8{1aA=Bb#-e5_4BU5ZMb zv3FMzk*yezHHYUHSzNM6^((D5H1%0$y2`aIR6?6w3snE|YDF5Xu7fgR-L8%?Xpue$ zkWh97`529@lmT!#9?YAJ{@Fo`I0&Y&JHw%>$k9jH9eh3H=0Vu=X?5Pa*4~;Ty+o?( zu>*U~#j%vV$8iC)8kfsV;zv*4oSDC0*PI9#pL5akxuhRZ(=un8qUEz-5?d(;e6^34 zc@H17FF$|Q(|11Q3Pi^)xV1+L3BW){pxq4DOf2;c&X$&st^!7Yk=?|&h6x|{p&%SL zix8;O`x??9B%peGYBgM8A_}Z+XU0qetrzK&&6LX3-0dwy47c|7CH}^3iRAxx@>Oi679UN_gj_V;u-94IP4eFE%qD z!F7EkKS3QAg4KuJrE-+*lQPP|ZZ;tJ=9INn6WsH)gYkq?mA7${=R(uv+}$|H1yrOZ zqsN|PSKdrwN}i%g>PUcKz`(}3Y*C%$VAqe*`eC=k>D7`7eFL^&d3h*_yypk_prqu4DQM>ZDY*EVg12RAIP%gAC; z(>;ZA7h!?ICYEY?iP{NU-j=ZIN3i>&S>DD!!sIe~8{|OWW@?TMRLv0|sz|3}ZI&e; zT$kABs!DIi_>@2qj&?<5_nbaHTb+gTyKa>p^>*+>;ku2##K~>-9?3p^>&9&Gi~(0R z2X+gbr;=MzBmJW73~Md%ybC3jZnkbd*d}g$sfsV0 z((%ooxljVv+h@Pjc{-u3%@>nGgxGF%57YX(%?{k#bFj?rFZM*jKMD*z-e3tR^ihtX zEi>)K6^GS&>^?byzZg5FeSeYSv&h8kdme%#U6+d83?>%>!p9?UWVfdw;y` zGNTy1vry+wElSF&wIe`~ZQMR7A0kRKn}-cuO)f-!VFxEZ+&BiAiR117D~Mcix7Rf^s={L8-bXrzT<Xk`B z+af8k&V(lQZmLY0nZir~K1-WIE-Fj=g3BR`$KPSla}R~q^P|8jxYne**ei1E^R-+3 zySq-fYv2r0&J^a6@IWU<;?XSTRwGZM_C=X(&nME;1L<$Qz5g1C%u_*@PF;IOcJX7Q zykK*?Z}ndlEcKuF^LHqfI-BjX=G_g68bBcQ$9P?ou!=ilYQ+@7O^^v8!Uvz}#TjYb z*Kw;2;hFBixt_Gm)@zkYc+TM)1%>#$tc?Cf`pzGc>9O;wPH7YkP#I)gW#d#`qh>ix zV)^e z6P)`c#ziP}<3?enq2y5^*bsvZsaa}?q@Ip~@jls;@~-moOszt=izFn(#k`HHW>m79 zH5#?n-d*uMdhfoU#@V_#@%|G@g!$7X97wvoJa3)eR~a^1>EaeA4n&Lau&>cfHHL66 zLn^a*W;X<~Z!1G&#fn_wXN)vEPSG_^dY~0IdD?KD3!(+YwO}a@rEgm`fZ%!84S^m5 zok+iI1>@kIyj#F_u#~vMUu2mJ(mWnyr{y${%08$Hwm?B5JiAAOFXytRKYXuq3mavE z5@N)}098wc;QohkG3FiaDpZk&{_1w6Nq!w^#zdhCzMTw3b+6GedW8T5D!EO6<1!1b z@qWG5E{wtqmUdcSR}eOmj-8Aq2qLp=%PFO+37`70u_+x7E@S)Ilhdw-E)IC%g+3@4 z<;t^E_g+bo@veUIg-gfc2|6(97!I^=v8@843|m)+e%F*T$0J$f0_(Tf((6#y&EWlR zquuOgH%UI_Qt8?8wKDOON9Y!Cy(>VUikM)Y*(z|!w}EqD$g%S!8`H2ALu==cirnFQ zE@{<|;&VzQ$7OrC4lW^U>hpIM6|!d-wuIogh=zDadq&RXWEi()RFt~=CJe|ED6is7>0Yro0) zKow$l|E+rJkEX>soQ3(xLsh%eT?o+v^WYF!5(`jBe{H!9bhG!k+~hZ@wNYB)Ey@-g zp;yqg6^^Oe{kFfmDUvx22Cokak>cvKByGAs>t1`?Lvi(p<-UUc35JE|+(Cz$c=%s+tmnBNn&eT*H5gIt4~Er+3N-g`%3Yg zUw6tZ$GiKjeV;32jCniHr8nU&mpN5$$PJ(?_A1JVP07UG_mSb+04qm=+W7X}Dhp=t z)xM0E-Prcnn}QWaJB33I2dT&To)^Gc4wF%(r{rZ-B26n`Rc(TUg>JtwkHj4zURHBL z>}Q-=INkM1N4w7C8v`K>dE&`RTs-+a4hnnmUzrAjD7;H^x>%-ZmVDPs@2sEA^aipQ zqJ~y{EqiaOR4kcAecdAYz)nA7;NqhU!U{J9+ zV$k1PZBE|LREnQ9vTxBIiB26(N9eb4pn{H|t{|%+OFW3Yzw{vFR-;VL#TUh%lNz>r zBilX>`J#t8k>=x0uV2*nhe$se>Wwt0i3EEl`5555XB|TEoytD+?)kk52GvGDjc*(( zHYIWbY_eSb?-HuH8@9A&Z)*Dbda%7!e8&+-ZOzo*VWv*n+=HC({4%2LJuupjwbE z#0bv9yHIMO3xTY%Jp$K)K>EzxdP{msJk!u3rlJkIcby?zI_H$(41 zle*H&^3>j}Yv6WDHktJ{AW(8JJ5Z)ACVK7e?)BZHxNuz0m*YVB!{hF7^Ak+($G3FC z=Y<`A3SDPkwqDJczXEG6g}MLe|SSr z;ZMPO?!4!$uH-N|SjP9)-6P^hqT$a4j`O>8!(WztN0cPxCI>&L{l4k%bjZi~ry#a- zk^go+J${OR%r1Wx_Y3xP^TF>Iv44@#g|F@I%YVDe$^ly)z8`#D0dtx}_or~@`0o*N z{OjVHufjWi9G`F;(8bW!{cuWne3cV?rXBvL5cTWR)L&%S+Vh&Y9@@XJ&i$!l;pErv z*i(u>HS+&JJ@j7|zW+Wu*9^L{q>6q?T7KF_k^0KW_D{j?eD~<_Hu^EDV6Wrvi(lXU z{xkT3HHN#jw!qyywjzVIj5*Xz}% z7Zf#&pH8_@f06zAfuOTq`4 z{ySh7S#^uXyw#OxCxS<_Bcr0FgGq4yWP-rkXXk5EpSA%uSK~tJD_w)Wd=q;Sbe)dk zxz)t4Pa{@ePjB8*jH^01iHnZC#Qk-9bNZw2DfR4Kl`CyClXXhF2X`8j-d%+Ug%1zB zba+8|_mA^9Y3EzYj$h@rv>n{zw%<|;wqN3Yt@Tqr;HYHo{OvKt$m{Qj!|kWIvuita zd6(Gk+Wz=)mxcYZ<{uqX{4u2KQ@`jwrvL2y%AoW|#|j)RfyNipP&qK<+Y^nRsh7Wd zx;4oX{_OdWL0^_q>R4M<5|{Z8|15p=(cQ<7_7Xnxy!^9gzduGD^N%|Izfp(ebXzam zZSs-i$_gSoK_(DfZjhCVfy&_}bl$-NBT#PaI;n4oFXFVE3+wXt{IRl6DO>d2msjXh zo#z^5Yf{aAJVkbMpEh&UNbyNsP|NA5OQjnIr#67*LhUQR$MnsA;|(f9c5}?WVaE#V z@W?wR4{`3bnrryaCN4z_Kcs_m*k(YZx zrCn^g79oBj8;$muCz(R-z&B@+e)~#%_c7M~yhZLichJU*jv4sOk~{BeH@z*p@wOpm z>n;Kl#cs6rBU)ulUppw1c(XQE-6mp8OR1X{(UrjTg*5F) ze!|*?9*|#ePG{g<0llY@+pn#%DDI;JO^QSv-Ly;B4V33{ZO0pX)qx+0=)NtDS}l;9 zWBOo^uJM;fm&+Ck)8lkgcnn4wy0SUjqpVzRCB@(rqUW&YnO)dTn2mp0g3Ji11aH-) zJ-VPvAgoK@sb&}`xo47C(Y}bq4~hwa{e^__=JZ`n1_+J%>rs4T`_bbDYA6dZa?izA zBl$URMWdL&<-MXdlsaTSlT_9|5%bbN?a0)pj(L;Z%caG03oqE^k`a-ulHi!to~Vcn zQWEB80U$6RteWaPGnekta7%erZUz{RoXJL z5%IhKgMQomdn;w~8S>kcRbGvv*!`~-QL&|0CTq)y-&Eu*uW+0FMJ78h%hj~#_m<+0 z+VwV7(A>R3$3tvJCu|kl83sKyKMGq}MF- zMDGECN!Kv-;|laVmBLec znazpfBbC3m;GpVh8}7`$GvpKU$-U=YO8K*h2iIrfgb;VY|FIpU7yHLV{pp_mdo6Z& z18de#A>zlg$Hjk<9W6WWss7=v=u7~sJ7vlv556bX<;)WeI6s0T6WkUFy>-7xzh4UR z#ogQ)tg{;=?ugPGDs8+)=9idOB!=_pz>AVLe?6XkbsmBFDnQ39clRsJm+y|tafBy+ zZ6kk?k-7gx79z$IjJynMIEuUQPHBErs@=5VKd<=yX7T4fe8&0E`|)SCPhb1{?jm2u z^wj!acx&8eqkpU33&}etoBNB5+)C@r9CY8wcYliSYi{In zdu(%F_-xVdx5NLeRb3N%wzR~@z)Ng#&KF*r`Qu+?Ke&;PPq}&@pZ{KZFij;f zG4i^{z$4Tot!l-7sf65E;Ax+^Ze)#e~@D$?sL#|Gi;+1^YPlxap!v z-txTdZ1Q|TK;-{4{f9@~x-zZMHfD}kg#`MfwU&Df+Ho$|0@!qR&QJ(I`Bii*1FZpY z6*ZYA^X+ZFjtP9w43O+MxS8Fd2;}QlRuFU(T;f;Uy_q5>EcS>H6PJ-*IJ)M9z%T=l zG%ixQ`mUU=21U3s!LNfG&Ls9+^HXX_d=v>6Xr_H1Xn)k=|4TpyrB!RvhjT4U0<(W@ z63JViQFZJiE&;L+T07#ik;XfZoN+E}mIhSy2z1YoMCOd~fEUzBC9K_3lsk?-9AV+p z^JrhoM95e;f{T~qciH;!y6HjOx8sr~Xk1AEBWl%l2D#$Wq&Ve=Ix*S8&kvZOmKU3t>oH8o0k-EHQhNxm4E5$IIeygEZp!spxDM+#XxzXtrxHA;tT z&FP^Gn^lSJc#a#A`0ZJ~)TGIu$M(eE>Yu*7XS?<5EVB#L#LqO>juP_m>kAaNjeq_$ zSGBJ%Q!c8qfIYd)^sBW2?m2@gZK{VrwkVwUiCIGlE?u`ihB#!dI<8wH>hZnddm<-b zkm)ZnUnh&M_79!QiH589glir?zpJ66TH{N9eZNg4bT)U8ujKK;D!B&a=iyAw+ZWH_ za+R2ymW|}7;x@wbdg@vuG^DsW8_wEv$Sthy$4g%y=0ZM=U!|ik z{bcyY@1zsvB5{Xz!?$KQvjMJZb0Y1nHPz=48@<*W-fTybofJ=L0`hg~)#_Ox$5YD6 zHAW_`>H-C*X>k_Ux;fdNDx2Eo$2r51DZY;w)1=DSSU?8By<_xHb@`}D?e+Blrb#43 zzvyde!quh2fC#Silblzg=hc3pnJ@kIWP|RA??ZibuNsnUuzs`Jr6)-3x++;vI`v2Jg&njNooNxX`_F~)X7savl zuXlQH_YVPIB}TIyO;$EZYA5S`NqEd2n%F1t@rP%DW2)Lok0zMcK<~7Bh@4$+e4o!s zQ4$m4&1wGW*lRql?n8_;=6r!fi44;Ga^C*;Qj_M>tD+v!$0mE@agEtR<8jAMc?wG< zcXx(7ww$;d|Fx7vzaaeS*)d(arw{c?(r^6vH@VRLzH`S{-ZGzgh79sv*1XEFo}+ly z3Vu9Lxf9NO)!?7He{}Hw=Xr3$KVTya;*$Y(TugdVHYu;+5CO(A87n#_KV^t#5aLZq z?n`+VQDIV3HdK5|AQryreOa08%-%otr##nMOUP-F#kbdffA99b=8pVD@iA>_TY}>| zLUlNh;!vl4lYKOE6*q&&~R+(58=mFIlwMMYjLS8E>a z5?$cur#0Pny&xn(GCAOEDY77f5`2w8{i;bqGGcH4y|nwASr)!;@o8E&EI7lPP-*$8 zz4#(#lU z92gi0R87>1HU0SFIU`YxI7)^nIO;<{Z)x%UuiUAo56a#mGj3b<*&5O772Drz>^usB zuW`r>YXtAe1xTF#_FiK7HvDTZ`j^b-i$~vkWJ+aRe_O}Jzv1t>+wW{}F8q8#b~T-h z?EU|w67e4mSmjK0_VINxvj2OW#Q!yT;=iZzk7Q1Tzg?D^pO$rLVE6(V8G{Yk<$M2= z3eUUxFW|>`x>sNm%?QeW0kg$XQXkU2_c9LX#3oLO@DF zhXY7}gd!!Bgd)Hnw zkB=-@KtLlP%c)bRSnmJ)u>4@K-1(1R|69j@RPw)7!G8$<_?v~3^;E~1-_M*9WjW1x z>I~)G2m0Hr7+8Po2NW z!gBW9snchEJI|WV$-~RXZ*7xL8|MN;oHB;TCg#|<1=OsNskI|Flq^%wL!(>fqPLaV z#U)e>{OiOdr8Knf+zSo~efq5ZXVoG^j5SuULU**wlOoGiCkzQ+Sx`RN@a&z}2Cha_iR>=sMOeGxNu+s$gLRAp>4OCyBB ze<_%Xx~>&mgpRTspea9tVGDu|f~2ti{t{l8iAWEM?ry94lgl9vfjH3ho8?E?rXMUX z0ZW&^Z9Mrz@ndWI!SbM$Z+HKgMaur4vYLZwVat);C)YNbpKaCURescj4vufJR<_UD}6Eyur4Bgv74gF5pbTZhQ`grj@8A>_fi zjkO*{jvM#g+emMO%?Id5;VJ&-|O36c)$BE%-&MxQcCq8u<{`i3P}#!J!&bA&@>0I~-Sl zD@dea47j)qbH2{U27K4Lcv0&%%z#3vzxspapAlzpVemqO2kW%WB6eT4DHKTf?U2Jy zh-TPQCN8F$PEmea4W(?62&y+EY}fhZe8o|o{id~&G3;9EEw!nlq*Hemcdwh4;Q#@U zcD`CPZtVG2yPeSz4|aYYLJ8@1X;BHN&`mTRSAEs(Wo`3aV-#~y;0BQP_&>(VQ-f)h z#Rbbfs&ncK5|jq71K)cmM@r)w$M;UmUa}3&@86@T4dBBM6#LLlVWwUY6kC-WGoz%k zA1o$td8-*?<1%Y*i{$SQvC*$fT%P#oIk7df|O4~~q7a>nwg%1PAEVphvkve&y=2!F`3coDt+JD1tQL@^MIRTD z7_`I9*|a^GjebuZYlRJ%50HUb8eZBM8=mWkNhdmtjTz&HQ1lrbn$QnYl}H#$sD<~- zYqUiJYP#xe>{W9qj%6rN;RlPvL}0}~LR#0~ zH;=mzUikz&4gDdc;5NI^bP4&7(D{E1)s{YE*w=KN;WW_jKV%*)JmK1fC6OeElP?Wf zJR35ep#Im5yU4G+xdVHt{g2G^f7)@C=hUyZ|LXVu+Y$cZ?=Q~(^7sD{PX9k0>s}y& zmB~GaV-&c+eo{Kt!wPhOVCz>E7G4AG&9fB|d_sJjKrQUmCageE8qP?!Dn^X8L;T}^ zkBSqrQHYYm*v%ybX7>?cAD1`7-eVfsjtS{DJi0hD73hGx1?Fuvu23MtgWu!C?7*NvcdepJzih z#J-o7;=}awFnwr&DdC{^8yQ+rm$;VFa}vh*NU5W!VMGWQ6?n5!u!tl?^eWd?%8BFf z!P<^~vOpHuN|MLO{2Ht%GtkZk(^R9zfCuFxRpKTZwYc9O5us@mA)yoZZfhX{z%BLQ zNRfY`&$)HUOG+ne{1Oiav;~Y=8~dzM3W+Turk5`hOTgJ>yt0&hb6_GzQM@hF4X<{x zUL9(X91(H6QmG-CY!d!8EaLLRERKVXwBJg2v8aM5To+b{5ut{c8A<#60WWYo_N_V> zzLc+Kbc4NiZmwT$&!YrvR4Bef)(1wm1$6kKLM#LNFHV@Ki^Mp6t8)@zCf>NtxXidX z0lYc=lBxGv*Y7BLDW*+)qjU+QgQBX7V3fXBO~t5?R1;hpE>3g@T;Kj?YTwe|7p)N( zznw&x*X-BO(DiuL($;bq5`vtWSr~zZT{d&`nir8*8&7_=uv1rgPYYYJ!+DHBOne9Y zV7clf#m7$JWw4DiBL`pYS_Of6E`%5cHL4NpKdZ>QV#!P>!+3Hou*LF2r5w(9ZlnY> z6FO1}JX18AHgu2cICpdEIlgK6l35?!anzV$xVo2FHY}rGb7BLpXI`1GGAZg^HGlTJ zY$h9*0Z8l*M-4jQ5KYfkZA)^oGQ~Wt4vwi|YR^y^iOM52+KV8cqJ^r4v50L}@Iw;_t{EM<+54-J(?YEwIzjlnTIO33UYuN8(paUk zip!#|-*Itx$@_k)P&|J9g-Qw%6t@`LXi`g*ox!^t&^p8X+uc;Fa!wcV*8^LGU3I89 zhs0{s;6P#ncjrVHzjvQbja1{it?J!S+bSiooaeG>Rk%8i=OJ5JQVO39LV0KoyiE>G z8tSlst#_i>8j|s+NGWexd}@k_&FXHAY}dzDT2JmZ??+=0^peji>5`f%#eASil7W~E zNLZ(H>DhQk7G?IvqaRbx+iPv`Unfv{j?;y8yRy+Rl0# zO8%NF;F+F1#d>%2aJH8n)jGUbzEpx)eV!tPu@_8M{G`YEIdP~Q5gu$+Fg{>=x+0+4 z=Hly_XfqeloEa?_8U56Bz(Pi&eA*7>qtSzcjN5!C5fQ?S70ISryhDapoAuYQqdV}G z^Ponhp3=#0QmshC7!^_4$mvNV(CkI$NXw@4cLqiEH&<;K!|qeXlY_UFQObP>97Me1 zSoMtZ)M&n6U`heg`lDHDr>NJmsn9@==NG}l5d2hcS-QclS z13;?4%EH0E=A#c$xM@5?hS7&M82fb63#3yq7YCEKD!;e5+>5!)Zeb^nJ8-QRDYXJF zEh4Z!t=zg9&p+a3@d4&Rx_a1dWN&?Ay3W>O6!cOUIB7k7AObkUYjC2#Q$K)po)o!e zv)dnbvjQ5egd#3^??bv=kMS4m^PKW&R(t?|8i{s-=+Tt;fZBx>4~2ZR9#i_m<v- zo-z7RgvTCd#r_u>HRobt+aP`rVufJraN?DN(mj+`2Q;&+8V!eMul)Y;1+rr>pDaM#oM7Y0dxD=_3r`^Y{_dz(r(V34fryl|aYSKu?FSY((UJM<&jR?r^uWe4Zw?Sl*O2UjbEQf|sljn<2eD>y!sMYHiiKI%=9;t7>s% z(7W3AcwCUBuYW&3w^+jD?XLHuU(F1c=qk?Su5%F816q6zxdn`Ex%E7LF|()ai(Xy% zf@10q7QpOhJ>Uw#s593-?70xJ+CbZRwLc4H^dYeXr#^6AIHfpgt@+tm+@iG|uf*ia zgGx70YBcsNqN*)*{va~5@dwM)_q^{~k%gQ;SdQjGLYS*h+jpgYu(+SC`N6_;JllD4 z>-e_Iwcu-a^Pd7udfjYIJHM*Y$F^tv-KINoGV-#BD=jVn9_hfp%{#aJ&evT}&dRAP z?cn%;`)iS@t1CdU*=H(nZXLR8(wbzPT^>jHtVdK=n_?Lf{)Zu7H;hL2Mb$dLDD_w=FLYxp~>vWwt$_pLHO))CGCNGUJFaU zW?d^=jdjlpMcmH?`1}T;Mj#P9;0(gjANj#A14Gy!zReHLu5-^=8f&ustl7DEe)t5d zQv4Qq8SA6%8YTQ;q=l5f#XcWKJO{MB7j}F9QGc(mHBglz%_Ilza{|@Y74Z2JW}%HU zsG`+_6}T)n`0JxRy_<_nOLvi!=W1dV@?O#_%l8|cNcb`le5#Ugrzv5rJSjCy{ENTn zcf!*uI+ZvZ=mEMcD628W`+;i(YKhq((r5q@%EA-stKP>D*1vvn*<~s*-J217 zRoH($jAnnM;qJm#u)2dCeRZ{Z`=AK>ihazT^1S=h7n3Hjgl7rLj%TEJ1^tmr-9EEb zRy1&RZvzbCrQzk|a?gD`c;3zXb0=U@7lxG%DITX@EY*$0E-*b@E~olz?TmyAtJl=Y zwJNCbCeta*Ri9oj%ih#aWaV!L)3a}&6Q9&^>HuHNs|OqsSXU(8mBVZ8x2D+p25P+J zFw4gM-5n;&88GO*vQ?ry*CP{IgMiO}0{z$V{^`GaH)JeFNCS; zTr_I=jYeco<0dGeEfwU~%cZC6Eig-~EkZo`Y9N%c#)zDq=bVBAj|&-6O`>;oTdB`j zzubDVa<0q?#xtCw;pT16zJ)KlqutFF3To{0Vl z-;F!L*AJ$rUThv;Ps&SGF?sU){F*^%;&eGS2VlD@2%7zUw5N6!)9CMF{|cxcc5&Ks z7PB3foQ**aE4&>e8RjUUQ{4wzzGr)H7GXoco6Bn_U3va7HiOa~Hxooh9+Np=ZX}2% zhY0|$dH#L;EHXozd~~JC_r*;rzcpL9eZydBb=%t?ELk^J*yp8naQ#s!(-hXn)?vLd zu;B`zd{*r%pBrZvFRrBt%6Ah>C{EHW>CALo~7$|iACMOlK3@yq!ad#Hw5bBCxsZ=PEn}f zMq$=3NSRKmeI7M4Z$b?j7R-lkYQdN~llJMoMmrxW@)T2;?^i5qPwjkKloO{Q=k-o6 z{?-_;ZQ!OUymo>7y>HAXd@o>rS)iKlv+b0+61A%zqReC;=+{#NWXmsuNM>e@ciSGk z+q=Ga-n)LdR%$1BnY3#N`#3l><2JNW7?1_2$bdi`9)P+%Z!3KupB#%Z>?CK%W^NdsE^ z>;rUaZApz2Q1ZYq&2Bq5+Oon%>%(S$URtVFRY0-OqqXM-3jMzmkvyx_-Sr^==yJAS zu3d(pjB~74@%(v7a~FvshxzjyD75@~GVm*hYK3K?-I!e#P09DW;y0fie6j84dF!va zp-$n6b)@7PK481)l$2kJXceUUwcRC4^-ge?8k3L9C&KvN_xYT25gbY4jx@9Mo!T@) zx<+%&cqwL+c9I+YkRE<}xFR6{pdQ`3nPcvBp@$gsHwg@`>$J(^RcQaP+mqb%HC=r# zvrdK-_~AiOLXF-tCSrWQye^?mO&U5i-fuZ?9iTzk$|zh2!ei=RI)*iYFx0>e^O!3> zpW$%%ll+Vi3Nq2Xh-qE4|NIRfP`Lz#+cGz%P9<`&t6VA$-_zTepw(fcLXR->eEj5$ zYycSTo24mL>&+O6eOKM^1@B6I$piCFQ+I0+mduXu=Yxd6geQGM_g&q{#_EL3YElSm z%?jAx+JLHQQseP%HFSRxdD+wb-m@?y1rZiBlodr=G|e1&^`zMPx-6I5!CygDc=c%R z(uGJw-OYE8b#%_!yN0FU#TU9=di}FM0^6dtPk~y(FRZ0g_NV(tZPJlEGW3U7FikO% zOt`+*v?w#Uk)4=Jr)z!?!$T5$ZZ@O+Nv^#i+Rw=KrZe^lbTc9(xMn-cXd%;O5E>3$g2 zdFD7Z{QKCQWo_qlch#s>HY|pC@o84@1(CNj7dK-Ym3QQV4b18TYdTb`cCX4;>O+Ww zg$qn-ORP9H_&YjbfCprl-?f6#AJkqOPW8LIX6*Pxn!D~IPD>@x8hPT}VfNtcpVGF! z(;mEQ?ZvZ(KlYrOv0q>(uBw&WF<X+Qr-5+RHRwF+eJkH%mue+r>fJ47! zezOub_IyOH3zBO)cPvNU_J3RkRWMz<2Br&GxeaC3Ao5laMv`UV9GM%eqj=rg6{n%g`G&A8 z)Ds%ppEDJ%?m(c1D|Po46P(yeP&Pl;C$!2}t%!L%Fl+ua1sPkCb6k!O?C%orw(?1J zqF2_}-u2P~gMpbwboSW6D#sQy)9w=*jk4qvCl=~4O)UycjR?9qj>~V>QhU|3(K2SN z23VP;60i-B+km_GYEK8Zu>e)|)~4JrS-D@%Nwq3Lj5Tjzyu`YpsSv!gn!SgBjU7$O zSbj&#d_W}%hhHhXIMHF33eFebmxys;^T2o-C{0bPvo!9=hyD4Vo5lX$IR9%QEDYZ- zHxwhs=Mf_&5E}H_Y7Lwhpp(n1J({|VwaT7t1*I+LX77->jZ}xpoJn*4q+*`&ry#|I zBL^22r*jJYwdIkb&3z}>hRivYpy4FYajRNt^t4vE&aKwgwTw0GPXr?uiTm2iC{JG#@s&$( zEQa&@Rzl6s;x92bZ!R|B#Xn$-&mOs_>s0s4hDF=v3q*hJxM>>WB=Dq9H0p1Wma921 zy(bwOa*(stqX^Ry2(FXp4T09DGZBS;*Q{@sR)xrUV4zs|@M|9_a6>u*Et&r5KyA%H zO)5bu#yu)#DJ^z}Y* zZ;tlOW#0e7wE_C$2g};eN!Z5n_biOB?+=#!sUhE$;*EF{RiP(T*tLU$L#gAsw&fNo zwFN&j4nr=EXTa>x9BNs@qr~*D&pMpZY)s2kh#i1pL{TS(=m*eG$orBlgvAzA zwAUJwvsd{cQmCSXB$-u<7}Q9aHpdJ2LVDgw1(ylWmwq|%sh3aLTv&}8ez2I%eSelX zMLg^yRcP9WnrB7cX6s@<;rWmtnYH{VZ#Vbnm6O01XjP00YxhCk`sK~tor51Nx&9^A zDT-2G9OtPEJ1gGvJ1e8gZLd{eqF*7F}Kas#LvK6P> z78eEr30lSJHe?&}l>~j(SAQ{MsCR)>aE%XNXJ=xZN)+ntU#TuasmF`=CyR^4>?ZAg zUXih8Z}QDLbM-|SHPIuXrx(UeO+qY2QXyA=ep8$9)8oDP5lW}sN=0&r718eV0xH$6 z)nuVIJPd5KmcNTRSwb*AdEMio9cx-19lf?!BFs@61U-&=O1?ykh_M2>L!s}cUMxIu z%6}}XF6Q_MBAd&KN6b?175-q+ zo;O$u_4Sof=nM=D^lxmr*grVkP@nZpzo;18CVNERVvFfwQ_qM_l* zdrRMWJAO{0Klu~4fv0sGNBnZW>Md8vScl77$I^EW?{!SSJkI;XkIug}V9?dhp3CEY z)T1$VC9fkP#6=^veTF|);AEmZBqZcuBkz&U;riX3z2GNNQ7u)Ld{px??HnuoC0Z54 z_Y;(eOKc8+4?qDw%xYjZ$Lwm3@+G=5^7z&e_HGnW(NGpSm4s63n-LrRkI zw8Yrr%+aSh*&&kYHxp{iOxaF;8!;9zP$qicXFb`Xrz@PLL}a#`c(Rv2_Kd}r+>I?!>d9|YpI(=5(6{kc&lp+`Yr7zMA3 zlqo9V`O+&lG@Z2W79)0bV&h~-x^c+8(Jz%}T7aq5SNuxAkk_ayvBdu2y#xTcSu{2L zXQsT-BtaIamM;vb6=SbTy#HiepwIa4A1q24KUk!R(rvqf5(R0FXw?R8`?7$GT0-G) ziK6uEL_J{>uR@cZ&-*`EM5gMAc=8fxufl?FLk0%M1DA4w=}bh3s;@XECtQPPRkB|K z0a+Q;(;i*zfCG9}ht1@M`vvaZo%SUO9;kuo7Z4Z4DTfv_<0fA7-_lp3BIu|Ny9{)B z%7=0SZhAERN_qq)t(c(uE=Dr<=2NLt0bV7NWri!gX65Kg#Q+ zHfgs^7XIfDG9rG8IyCux|IogkvGhbrVSV+`J#qI|YDO?JqUOQt^S@O70`?bu{*O6H z@!hH|Y>bP%k(hvi{#iSuG%Yn)xb-4P%?(8WpiWj+Mo}P?2`g2%x}UY<`iE2RtiICB zHaTYdXu8F;Zpt11Vv+ff7xLR=dAk__2dWY+(Dge=>>~wKyODi~M<%oXZdZaYvv?t} z`_H;XG`g{c7_s8(QNn@XqLYb!R*+Vr#kY>bT{W|-NZUjudTXiuq&5Sl?bz;T+{~Bq zY$H2wUhlrcSiuIpMSD`-pX6RktmbWsDR-GI8{C>FUtb0NgQa$A5fxGC=~P~F zl{+5u_f{S2;C)I&6*w`8%gXKiBKru$m`|d@Ab7^vdY*cTw2R!(57)RFl!lSZU_agT zGREGEWb&06&!Nh2JsWO)m;o3YhFTZp`GtUnxBdBszZy)8lTxP-g8a;tb8M)#%;fEj zUF%1#OZ5Jx48WjOuGZ{l1)v>>?iIFZl?b|TX87ATD>tT%v8mw~Gd(T$-L5lcbbceZ zg$u~2N;&n4p(wbH$Vy0Xv3XaTNH5nsEyLjZ`F`Hs*$gXd6$gMOVHK+Sg?&;>nI;(< zW2wjlbfDX|Be>z?qH4Ry2Ms&vD=V`ZJB9o_?=X{U^(C(aUWHP+IkY6hm8?=T`NI6| z3g(mU#q1IY(&NF!7R@oCS^ye5`AU&r&|Vt=2`)*#kd2qYH8d`}^F}}scZ3qc?3v)c zo=G0>2C{7+xgfq~y_MmeE0Luh8RWkaC+3|mylARsWjZZ#BSkN-+ASC|cva)$9`iyD zL2xJ;l0JDc%fwRS!q;8+o}R3u5YX;H3S2`OYkfac7@&!e8JLQ&o0!a>gK+#-jw;0> zgD3M10Yhwt=IX-(xU_?JfmUs`=(5h&m8L4rjL5gz#?8Lg0gQ!3KpJyuOzwywlCij= z47Vs& z8h%ZQdJ2BMQlYlP?rzxBRQtY9BS{ZYY%42kGnNksaAOwvl)?P)YhAIdFqmV&O9jyp z@&n6o*XH(}%$4owQVE@ed)C6P#fv9b-D|7dNc^Fe(L{^#ZM1jA(({5|Bbt9;^hyeR zPQuVmWr$RTOKB;iB_#(43bHF$xqKNEy^`|^^@%W;8tgy_i-Qk zQrPJdV8z*YDW|%abm$cF+(LdrPjr{hauFG(VE=?a(VdU^I9D+SYI=80LwGG_{uXe) zf(f|&)J&pNdQc}AztpOdqMphvVbvq@mOj~67%-5>4fZfY%hda%if7u*sTZmnV`OyV zh^}aTxUWl!PX!DxtCmv-?J%=!TY3~BYrHK+kV0jD6bij6p z>GWWe5G~2FYfVdgZT92$Ouk+t1C>_H7a+a^og^?Wr|0G%v7I$gVxziV8AWvX8cgJE zUA6z+(kx`N*I?yw;DgmdERCw|+azBG5%JD5g8lsOot`vdW*8a1Mnw)!ml_AlqwlWx zx7wQ5k^FHaX3k&zM^@m8jM5NUuO90lU*TRW9Aq<_|8d3ZM1v#evDJz99?=(8F(YXoe_1tIv; zw{uPCp!{Ujj7X+=(0Bbkrty>2`3tLgq1$ajgS+-AE_?2B0$FS5O#Q$F5$=w6ftz8P zQjvbw6lEgEj`5x2d-;WiItz>6V4TDvz zOa-xSlFL(k!guEO&H;Et$H z--9L;CVbkC=$%@z;m1^zFE!qymW{QR3{`7WovOFJ!QFzemAK8)CWfy#JI>nYEL*uV z!Q<@0T{t#&z4~~OE%cxI2S5L4=d$Q{?z~(QR#N@j6my!1FNI1f!@F6{L;MKv{ zzy-~`+vlierR6+OcL)!8!|;zkVx{CIRf(<^cY(g*B&@|uS-)(SF}tOgZH`7;Z9~aq zpjr%_J`&NzA?PK z=i9K>e~CKnwGMlLuIhw)lUqkl;g(@1UbR)fSIinSC4=%u>Wcfkm^Z&V!d0-Ba$3|~ z9N~Y`0xN#7sL^d+Tl--TQneqhFh<-o(85e?HDoY$j*VFP`;^xd6}fH@yR^F)gnOjwAQ@@FR#gla_ug8fhFedN!7W z!ZxbNBPTEG2q}~&>SQrM+_ZE;zo69W-@<482$m;XEyw*94&J=~Iq5ZV1EIhrdUh#W zt|j6$sjL8{EmxFHziPJLWoSp_ZxKpoEW5fLSPo^-lVPH9+P`=PI zmW{9pjZ5i3O*GsHGyQ}P`pT*{>TBs!s7H$xjt_?*dMi!F4svOBNm4DQNek3fdkOl#Vppb%)8kos5Q zdH!CrW0**2L0Hoi-o#f3aXu&$EkaA0V))HHr@qu?G9QUm%Y>`P1-- z`M$MC{Re?p6{|*aT}@3932s)^Fl_O?2$Q6Vpix+m9gRK)+T_pCNweDF-I?ZH3{MAH zlV0toj`(fLpA>`SD{0n_Hs zJrsbTZd&7_G?_ivBmh`X>9G(rHG%WWdRNpg3 zmS18uOxO_}F?#A*nq`z?cQEiE0v@I(;QG>JL?*?ov&wI37$5L4&}O=rPB$+DuO`T^ zI?R}p%wBxcPx&t7-qodtf04b|@Y^a;tG{^EIcp4zkl!FUylPQ>(C))Zlq~y6>E7bn zxU_kD`&^8KtQ4!{-dJFQ^$f~vt;Q{_-J*gV?5H%Y?0;6D`fOT; z#D>Tyi{^U9Ta?kJ;#{5O7+qo#oMRDrOp|Rymrhv}IRY~)y212%Uz$m#0ufTtl? zah;bR;1BlLAsL zfE5y4b6mYe_Ix(wLAfTXc;GKptTl|4 zQSU+Z;~?554WhWlC=nN*FmJH1GH)Q6nbbD6)E63HrS|;L_EK(HE~o3QdtH|~#8~x2 z_p-6#g}u^k&Ew()KUgSZ)LUU>rGj3N<91wlLjds*Y}aZzE_Gye?e2j|bJ&>8dEo^5QkjQ?Ag312^OFlqwEnDT>u zGlnvrWFO)2;c7*>*|^rkok|O|u*>PsW4K^YsjV}?QSLAB(wt;LMea3wK)^^sy24#2 zd;v|U1z8F*ZQfE_^HrT{Y7RW^?tUII?x;Dg1;mY(r#Yz?j6yBu z1vV!2B_q(cC%yNf#IbDWW!Ku_E8sNeMahEf0wS%D_WIxS-NnaOLD(U(W8HG>L8T+P z1luJKjb=fak#vW~}-WQgIXY99={5IZ1lm@v(3A);duv>P`lmiyfKuEE1P6({X2 zCR)x-l!TShpuO+SA?hph_$FhKXZf#ftZXQAy9wqt86dXNT(3#N#xX;$E<bIRseNhiU>0?*BS%G#^jG9-!<*Hg`D8{=AGHzZps&L?eC?UWb!#`*7OQ|bd9y7*XJY&LQ6zCw<;_QQZcY}LIhTEd|| z5ZlY*6>rtd`M9#YSmlfA0*1Z)Qd2g3XcfaB(k(U=OYnksZ`zdQLcWOKxvf`5WqK1sb8jL8!vW2*!cpDH_;9`t;KvD`)!?epe4hQl zknLuJXU4WV_3>W_YV(>|A!?tNOYnd19waGrAwLgSnh7iE`x3PPy}JoT05x59R7Y5( zwi;@MhQ0M%RTqGCMHQv`Qgu=Pu>x=UPbhBssx)X)QoC(oQk(9Z&R5?w(f@fY>>H$`WkmiC!a8;M7(ya{Ct(MOn7L=S9Pb(~A z3UqBy%F2An5fw&yy~T6#LgT@34Z$$+RCo)=G5eWbQ-!6;=T-eP1HWqty~y$Zxg4DF zd+i(fJ!9JY^l8GTR64FrySBl>%e6^<{jV8tQ)-k|y0Twfns{w~!<@Z!q2vNNvJ=~(}?Wkqo(L6C`|dW-J)Y{Cm)o-lRB#uDS=v!a=x0tw9zmYNAE9&JPE z>z*=_4t~aa5zsZ*u3Ml1>9Cnp*H}1rJl(|(!RBT+4g5or=9BmXv_(#^j|gJD0vwAT zy!OSE`?-I%lptK1qHL<1fbxeIrve|-(YCCU_8N1VrEKNfD4Xm-1-rU;MxuaN#za_+z_XGPbnz3G$g zuY&){xL@%6BE>JC`ESAZ|FA1u*8hSVcLUUnXEJN~s+$|G3t%6-oxcChu%Y73e2;uF zof1X6OXsVBO*$6hXYIyn8xo3(`lSQPH}+->}S5Y49*zWM>&mP` z$4kVU-o1>~-TS%K&9`5`K)5R5Mw39tmG9dLzx)2PWDvG{FfNR%&Qve-39K;HmXRJ% zkngG;hHDg;5hBZuF2yZ;nv*E5@<)yL;)V#3+1asbL+M!y?e!7dBai!2Ep|ZBvfiYb z#JX!Clm@mpaWzI->TdC>k_O~^NarH?rF;+J2#$2H1ey?=F)&du{x&1fF4o~;L&;QQ z8CF9hK3-K5Aa(`A3>lXi&!mfeVsOt-(RQ3AJ0q~s^qfQ6TuHvz zC1qlP+@NRiU`N6ZTKIAOui1j-`Yw9lAAfYIkF8xsAJC(^IE1?kc5_{W>Ns%iLKtre z)IB6tWE&>?(1$0}vyO8j!TU}7Fo2kfmZYy3@vAlFr{o5?*prroUOc2*hOm#Xh7rBBerfE_?nJJhmPsEZMZ~Lu=vq`ipweCLQqaXd zD1v<47MmmeCVf!(J~P0@JG-nn9B!5V`O7U)jn9n=O2OG5t|*xh8#@=HNO=yiiSt8Y zgUZ^_SlXjR6LqfMRXe&fvU4Q?>v-+)&NH|8YE-nrmN4hS*PMNveMb5J&syFl6t)Pq@UvV*XqxiAzrBMB)Ze_*wyKp6r$u_hMF8WPOOu=+bbD`4{yQLW_?7YIS{G*ZhKb!P4QUQIS+5qwZ)a z1-Fn%Vc81d{e_364Sv2YJqs2o1rb9(SklT8pa^6C!MoU^!jBc`ch4|PN$B)tqumnQ zBCY+G*2)WIH|jLHP78@%D+6vw#yGQ=Y0vPeH}?3y8DcTgom12U}^?- zyR$*-7Ad2nJ{gkT2TVKbOB!CQw4&nX*@E2X#zIv-RWI_C(EIM;9YyjeKY*i*8zAFi z;~~k%Ez5SrIOH}c!VamJaYg0jWVywP;p|k;r*n_Lsl`P}H+{?0$6xo|(oSIg7`ilR z=;ZQAR;;FyFzzmEQK)k5jrV}qb*XEk1oGpu@kEW+tIs0tAapmZO)V>|bwlgAU7^yT zC&BdUxA-=`C4xn+9&LqV1XCw^vsjRC(&3tjQkzb{-c`gYc~DOOKw5=Q)o_#F`7-xr z02*2B4)s!&oe$~w`g}6aT#G|l+ar1$T8os_Oqn{GJ8pfg3S$A|1+9Vm@fS^*!RB7abi6uGU4uBQlP}v39dh>CDJQe&f2iNEstIxI!`^^ope~qv%^O74ZXv~a=$mC*}TB%r3 zjt&S|U24Kr>b}pK77BlRfV2s7lc;F)l}Iu4T_rT`ujVXPN9_>G8ESB0iTUvtcLN{U z`>0s(F|3M9$Nij}_!cY~lcy&CygJdk`|v?|cy4)?_QDXrdB~x%=F@Ebe&;-p!pV~H60`N}lJ3eEIz?`P)FcIX7ij4O zQ#YkQep~olSnR2N2`~E%Z#gXF!FFYLE3V>&P3WXeN@olrxSPFU97;k`z-TlXuPlW} z4vKb_m#3|wtwF)}M6=3r(8le}^4M7K;?2j}oK+Tzm)?pFzO+|?7SF@(%!hbyh=A$Q z&mXPB3e~q>fdt$a5WY zBgDjzt%@DD&Nh1Q&8D!%7Q^jb*$XjSwfQjg+Lq5MqKQu~<{TzT!ZPtt`dyHvHQwre z#L@q}`JgY$Pt7`J6E|aOm|eY8%ef_*ef4(0e0sIg+7_Fx5d-kKyRLotysq{&omAT< zu~2- zl%3as1P2)3How6|YsGhGH3|>lAxgU@BhMA({Igu78xYePW8+(P2tzshIoeq6u3mvj zp|N07!GzJBS|<&M;8kz(4y4+RKtEj*rA~7nGC6ld{d)OdjxX}Zz;*X3Gc5m;K*_DS z^Jn-*yXYhI8q)d$ymDcpd=@g44@6Ze51M?LTwR9KT$!&Y<9N02NJ+LZsiOFS&`j|A zP((uqsm$KFfhuO_U)hzgnBqf%xuNhkbfkjT)7A@K)wS5yHERZZo?M!7j4ZJpcJ_sL z>`o)tuAgzzzMAVz_{-%go?n|ROe&tg+08WozxUk>iHL5^BfJ3wDqmeuP%n;-&B~c{ z^Du&*TRe7oy$y=2^Qz#v2SFeIg(S4p{?8GRQiDRDs<~Ll&u8OhE8ShZOJpDKxcN*> z4E4VFen7|&WTb@mwYV~5;NH4Igo%-w17s+MVdX}e4&lP*Og4o5%*z-7d8RY8J*0=u zyTqzl0AzwXIH5JxP}lM%S>?WD!O|7vyz%`BDIO`f`R#Xy-Q_0wQf@+t8kacT^+i5w zQpN7K?sdQE6jR$#6DAj~?sqyl%Bf%DnYzHZl?}rXMgHvX(O6Y`T`PA{7oo!CsOP|3 zXI~30bS1eLR?V9UGz6q8IZ`IZ#-+4{OvehAym~guQ$w>)it9fT!|Yo#hITI5Mq~3w z-LMv!`!MxYYzFE09|nm1D>5>~d><GBl2XsU*d7hAv$$Xe@1$fddaDh}Up?A+i z;;imQW-bh5w4q9kRsW_XB|Lc>FvQ7b4j0^OF;qwKKk@yT(dZSgUk6w5{!n$|NUM=J z)Eh?by3dd+q`l{|@0k1UxJK|^+?78M-4$_|r}2c3sY2H5qn#$}YO^MX*FnfC^GBv1@jloD&oAT{*mt@NzLSi+<$=5hdo<}Zo z|5tn09o1yE?K93;06|4Wiiiq`G*LPtFrowj3DQCdML>G5kY$jdzX!BFhx$2sFqRT;8%HzPr=gW)-$|YR|>{o4yEAz9~4bfJWA8!T8VurE9 zZ}$Mz20v*LVCCA?B%({TrHg|bkovOp`ro&O<*UA}2NrW{&@NFnqH>mM@bK1#Ebuxr zXMzEd%h#Emt3%;JeO1Q$ay{>~PKa68I3yNdHr_89se#xXt&s-l#Dz4Fxu&Q&^1!t! z_#H@-G512WrR%m(meelHoOGdida7aScApucv?&7Ktk*LxrZ%8_Rk|!W(xM)osCqQ0 zi1AYqq~Ia`6s8FyV(h2mDuarm4!66?S{i3a#9?!MVI!uLc-+nSt7BE?DtL`7%?3J2 z1|5><;T%Nw&Dvxz*Iiq_>+H`uJ*xt)e9ofX5_R5qiCE)xhkoU;L$?D%~v+$kqrqzd23{U$bz!*!y|tTHKK-FVb2MBysK zEYk!Sm~>3^zLOeW-v*3uD7asiH~$CLFN<<>7mm&y7t6E3y6gc8bYIMzA={_h4QX?^ z`z!dp2T8_}B3dLHl5+$vPM0oTS-QGR;FX}J=@xlhXDs(!^$hdWr<^gWeM6RJQG6f( zV%K1{&bx0Ibq3}sAyOJ~&nXpx#)zV#hhm~NL)%;>H8%#MI8M(azXiXN<&<}PF^Dpi zblC#gR|Yc@oG6%DPq{pZrlx};fdhsETY|=)`I*w3m++|^>}2OtJ$5N@!IlY z9nZ683*rMqE~E$~D_zsps|@TPb1ds^uSx!nLkgl(i(eN;P09O?cbh;scY6PRD!3@0 z;`YPFdJhK?;yPj8+`FY9Hu|D?_>XxEh;3M_`MPd9_3{4h;_9hE_gC^3zM+ zVmY$u7k#f9*ma~zy?svUxOP^&snf>W+NQy)%Nl>zVZeBd!_#@r$G}~Kf>VX+#BR|J zXk|1V4|pTv{UE1D8?0?`wD6qWF`Q9Od(=x}EZqvnKX#<K%XRF>mXP7r zKKT^TSf)9faAC!mZ9k)9K505Xf}(GoEgfJmGfkxciHer+V^!DsJGhT=GnS7Ru8dJ@ z3&Ef|p~gCSd4X)}Cx0}Wc6SUDOm79EwjQ2-IR0Y>{@7s0{72hM5*7RFOggu!-&}m< zrV$inzXv$};H-9XP^U6f`(@Jezcv2-DdC&0J6I*ODTdm<@!n6#Ve9VBpwh0b)guwQ ztVOq;e_C{geI;kI{ORK1AI3IvW^b-}Z*3&~q3}wT?vVD3tUl$R&VH4BD~WDh_*L(J zWbj{Y=Buy1{ZGW1XKQM^?6gC*`8|+5K-Ali5ztYnnhcB4s4ZhB~W>$!Y29aR`J4o<9%N&5Lv&cDm;R#{xku z#9>u`rBpv7YI*i-4A7udLso@YhVqs-)pgUycj$H2gDNGI1@)vQsWR9a|sUL z-<}6LKGP^3J{q#a$qV94+f1{of|5&K5OY#wHVfBF%SqTuu?+cfKL9l-#~MO~&lwkdDTn$INaQ4wT<7jJOur=W@~iT?|CR@D|UlS($jnNln&o zln5IE3e-)hKWPJ4ec3cJY}khO9mffH-{=H&I7kkKdiKVV&QS#&%97?)jGi6F?pm;H zR>MUIVo4%dELpxO>gg+d$hU~L!#WCD*{v|vpB^f@Ev-W-D-LEv=LWOmJ}1li?yO~= zp%V?s|IKu8;_K=nl*TOWK4$V!ZrY1>yQ(Af!k zF4@!4mB&)yOqz~sJ|m!$Dn^}fCPyE~n$(NY0`S`n?^Wz;PNeu=`C)aadsiK@8(y?~ zoUeOsa&o>%$JCF`V1MT)3jP)w0&bgJFV}DWMtu+48RX$vrHVYIbA{h|V4a)O9)KUa z2e@gt2RQP(563LXC{%Tost3KR(ff|k>}>ED;^m~{9$B5IbWLHp)9~yc1&x^f3D~F3zJ#i&1;U<;(uIZJ zhok70QNcP`vHmNLSmv&|+V@*<*YG6M0*+N|?UG>Z(O^U*uSV0=Lb0!DScr4k$ zmIk8(yh78`_|ne?6!6u6{vR9Ae_0gp1j^imDi{==G+Y3J-+b~uiLubN2q?I>`dvy> zGE^pkUyXo*OJ2Tdh5&v*G&}W4ySBOL+LUXP<49uSkY* z*d%Xf#D63CMdbfbSmCHu3+}mrJtj-EuRFg_ob=Amj{^LP}zc1(O$ajRp+W zK_+f(bGYtK@-T(*?*S~2&3QOMl4iU0D`}VuTpHr@F%!E?Z}tF#djN%WUfRF!@SflX z(unYosB-F!=sm#Lj`^7PE?fLgsB*=Kx^mCWt<=|{mRisg?EH%IBvfWNoP?wWH zN_d!IImoW-&1HjHv>-;l;ohVJnblPN1lcLza67weTTL%n$%tr7R<`Reo5Hxdh$R;+ z%7zZ{^NV4 zcdeUZ?+uSmM4n|@z=Y!`W;UmK`*jOuONxt^*?a?&yo|bM89a>UMI1VCzU83i9J~9l z6PbGe1h?sfQQdXB+q9$AI7Zv$M>+-JsD3um>9`)Pg6DA6tsqkLPcb)Kt&>=fpTwHQ z*v?LKVQyGHdEHPTh`7IbxeSs$E)v;4v}=~GhoG2dl3`pm9Ts86uj={yennT#DZe)qSb4oM|QC4>2doTJmEWl%tv z?DK|o#o=7%hY?d=@*dT9n;x8BK81Du))&mUM~Z!$|BKIDGnHo2FAQ6 z4B#~xgb>OCGSq=F8Tu%*)F4U`SqV-l!^s5cH8i%g_WH~^<@YM+*#q6|ZJjGV2z9u` z%s4xV=`|0$XtKMLb{BWy3Tc&ArC<-xb@)&P_p3rKcQE!*amCeFmpLcgPutn;TwDl*60zB(;r{^ClijO#5(gb7A=F5b{>c03njY2xUPRag438NFnH0*Apm z^Bb<)EPsqP(|WCr(&HTP zH|y#$Cy>Kskk=}k!D*pQ3Tcc)F1U|5gGo48b1>l))ig&GH+c^1Yu4Wd1UXee-uggc z%Ze;5eQEG^iPa_Eqo$g|q&EV7=og8aJ3UjD9jc08S?Kx@Dr8IeUd~`vvwWh+KrhNF z0SGhcGtEczcIxL1UjnNbr?Cu$KGQC`p^?xCjWoDiWdCmGQ2B%$`9gkF`gJ>wCcip( zGlpxYYak1$$TBdiKOahU&pK@^2b$UgobMZFK4sEhAd#KJPlFTFWQUICiIY~V2j8ky zaoH9`7b2up>M0hPWu8OVDvV8BAul-*17c*8wHAJ^%b~)UOYGqGCdK9t?WT##76K;y zO;R|+it0=igpe&#S`c|6)S89d=_(QS`atrxA+MGlV$C~>btpag*a->gkX3$FRcf*E zU7T^42m}UJHyF!fGO{plFvpkSj8zco(|TEsFi?P~)IiVNl+%g3t^p%Az{e_1Y%a^PFk}=Nx*4c1oDzD4@L~(gp<)C|Yw~*E$ z45!k^A^2wWVmjNsY^f$R2X{&w^$JrcU zD`_yE29Z-gh4wh6wJ!aOgYM!N8Yp@!NUCK4%Z!ZmlHad2ehLSE9Ysy=)Q+F4E6jgW!rkq=B7G^ zc2eR$plcUn@XSy?uGKg&b5g$T$>g1ZXJgv#R;s>tAd+G^;||Hq8#Y;s81r6&3B_3G z-Fvt05lE9`xlDP9VXO*z_y-_Ft`;rV+~%?j60HQ^zERe2R0Q39Fzp~48wC2umsHvp zNRh`tEV2c4b(0r0T0TU_Q!;(*fOy8T@no1+vuH{5_3;e#n5~sHY_v;p6z8eFl1SHl zYcMmER?ej8#>2(GjaWboLOP_cWzQL`MB3fRw7cK`BGE?$UiqYvwZ?DmQe&d`+o>vV zq643W)z%?yoLEWe8=of)QEU=Xy1q72mu|N?KzcG&$-;LQzt6FM8Y~$p^25nf1}^vD zeq7Hsh95ohO!Fue#*3g#v(xjXh3?gY@l{q@l-R_09zg+d08%Ow2sH4~S&j43SYsO} z>f7nYtG6^5`nzl2CSDrW(w7=xP_?NY7s;zl>$YI<0*R&%r41yV4^zb6_kl8tB$+e! zVySj@! zKq-gTvm^_ce5oTQiP`IH88=gmu!t}LEJ>tvV<37?p#Cru=hM1Ov02D(IKp}`1)~Js*LB^2LT(br(x*FF_h3i8_@qzOUubc8ocHEbW zhcpz`je7FF0y9c znh_?VZ7y=LiH_N3K!@?L(ljY@f8NYxK55~&8hB?}{saAq$B)ST??Q`yiHLslIi>(? zcxP^YVY`i??j1|?=+;5x=<6$_Q{a<&^ZevI&rI8G-7+UBXW>plriXKfvuuJ(N0G>o z*8LACYnjC&*9SkZ@J~AAszE*dx8sJ8)6ozx=t_cq^%5K}JU5UGM=0<}f_#XYmx!{8 zlXLu|Btk+msDhU3P;2+6OM1cE++j}`>m4|=Nq?5IGOhpbIqLxM(@P{S2$-6|CXeXs9&+P#= z=jxh|)a}+jkbWGyN_!kecgCLOIGV`ZOI1aIpbnEa?{M|<*!7_g;ELnqnTb|W8gF^3^@M8!ypeA40# zzs2a8Et8o~5zih1qmq12%n4oC18_|Ab=#HQd~*9`t^6l^@9c^5pUZe!@eSyz?p|%h z7q;3zW{VAu_L_CV^<7Z;TtW0FwTkShZR-ptv-CEWeHOPuKbNYDc#P+ z<~J6RM?RMhJ#qF!ocLl)AlBFamfUYFSdY`Hmik^$+!t@31OGW%&Cr=!{=Z3ge4oSZ z&(%tMBG!JB?)*N7XP*nDmi%ijuokBUD!~td+bLm{L7tLLkk_O<6UVk_$5sz>>PNi) z6NI%?kS7|37fk0Zr4^V}Ih4%ICS=LrFd&!PaI9BAm<6g@#pU^WOnccPhj+=6?_S`559)j++dbqRnrVURt!*sqM0CJnWZD;f8~toh|2los$^6eUC?Jwj z8s*@jLDh&leJWRrb8_KHdmmQVfKRkNU2*QpQVX{N zqVoJ70$BeYGWkirQzlMf&avkWv};t0RLy_t^fc4 literal 0 HcmV?d00001 diff --git a/document_ftp/images/3_document_ftp.jpeg b/document_ftp/images/3_document_ftp.jpeg new file mode 100644 index 0000000000000000000000000000000000000000..ef1ea46c4bfba7017b257bd0dccc5adbda2853ed GIT binary patch literal 109474 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

1g; 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 xe). + +# ThinkOpen Solutions Brasil (). +# +# 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 openerp.osv import fields, osv +from openerp.tools import config + +class documnet_ftp_setting(osv.osv_memory): + _name = 'knowledge.config.settings' + _inherit = 'knowledge.config.settings' + _columns = { + 'ftp_server_host' : fields.char(string='Host'), + 'ftp_server_port' : fields.char(string='Port'), + 'document_ftp_url': fields.char('Browse Documents', size=128, + help="""Click the url to browse the documents""", readonly=True), + } + + def get_default_ftp_config(self, cr, uid, fields, context=None): + action = self.pool.get('ir.model.data').get_object(cr, uid, 'document_ftp', 'action_document_browse') + return {'document_ftp_url': action.url} + diff --git a/document_ftp/res_config_view.xml b/document_ftp/res_config_view.xml new file mode 100644 index 00000000..3ba0b549 --- /dev/null +++ b/document_ftp/res_config_view.xml @@ -0,0 +1,17 @@ + + + + + Knowledge Application + knowledge.config.settings + + + + + + + + + + + diff --git a/document_ftp/security/ir.model.access.csv b/document_ftp/security/ir.model.access.csv new file mode 100644 index 00000000..4a08a4aa --- /dev/null +++ b/document_ftp/security/ir.model.access.csv @@ -0,0 +1 @@ +id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink diff --git a/document_ftp/test/document_ftp_test.yml b/document_ftp/test/document_ftp_test.yml new file mode 100644 index 00000000..b3ff2a89 --- /dev/null +++ b/document_ftp/test/document_ftp_test.yml @@ -0,0 +1,87 @@ +- | + In order to test the document_ftp module in OpenERP, I will try different operations on the FTP interface and check their impacts on OpenERP's documents and vice-versa. +- + In order to test the behaviour of resource Directory, I will make one resource Directory "Labels" in OpenERP having type "Other Resources" and Directory mapped to object "Partner" +- + !record {model: 'document.directory', id: dir_label}: + name : "Labels" + storage_id : document.storage_default + type : ressource + content_ids: + - name: "Label" + report_id : base.res_partner_address_report +- + Assign "res.partner" object to ressource_type_id. +- + !python {model: document.directory}: | + ids = self.pool.get('ir.model').search(cr, uid, [('model','=','res.partner')]) + id = self.write(cr, uid, [ref("dir_label")], {'ressource_type_id' : ids[0]}, context) +- + In order to check static directory in OpenERP which is the real directory just like system's local folders, + First I create a directory in OpenERP named "Directory 1" with storage as "Default File storage" and type as "Static Directory" +- + !record {model: 'document.directory', id: directory_file}: + name : "File" + storage_id : document.storage_default + type : directory +- + I am create one Document name "Document" and select "File" as its Directory, +- + When I am creating the record, "Resource Title" is filled automatic with "Document". +- + !record {model: 'ir.attachment', id: document_1}: + name : "Document" + parent_id : directory_file +- + In order to connect FTP server and set "File" path, + I create one directory "New" in "File" directory from FTP and check its effect in OpenERP. +- + Also Rename the directory name "New" to "New Directory". +- + Remove directory "New Directory" and remove file "Document". +- + !python {model: ir.attachment}: | + from ftplib import FTP + from tools.misc import detect_ip_addr + from tools import config + ftp = FTP() + if detect_ip_addr: + host = config.get('ftp_server_host', detect_ip_addr()) + else: + host = config.get('ftp_server_host', '127.0.0.1') + port = config.get('ftp_server_port','8021') + ftp.connect(host,port) + user = self.pool.get('res.users').read(cr, uid, uid, context) + ftp.login(user.get('login',''),user.get('password','')) + ftp.cwd("/" + cr.dbname+"/Documents/File/") + ftp.mkd("New") + ftp.rename('New','New Directory') + ftp.cwd("/" + cr.dbname+"/Documents/File/") + ftp.rmd('New Directory') + ftp.delete('Document') + ftp.quit() +- + In order to check directory created from FTP is working perfectly +- + Now I will test the same for Resource directory which is mapped with OpenERP object. + When you open this directory from FTP clients, it displays each record of mapped resource object as directory. +- + Now I test FTP client and Open the "Labels" Directory to check Resource Directory in FTP. + I can see that all Labels of OpenERP are shown as children of "Labels" in FTP client as Directories. +- + !python {model: ir.attachment}: | + from ftplib import FTP + from tools.misc import detect_ip_addr + from tools import config + ftp = FTP() + if detect_ip_addr: + host = config.get('ftp_server_host', detect_ip_addr()) + else: + host = config.get('ftp_server_host', '127.0.0.1') + port = config.get('ftp_server_port','8021') + ftp.connect(host,port) + user = self.pool.get('res.users').read(cr, uid, uid, context) + ftp.login(user.get('login',''),user.get('password','')) + ftp.cwd("/" + cr.dbname+"/Documents/Labels/") +- + I make sure that I Open Labels Directory successfully. diff --git a/document_ftp/test/document_ftp_test2.yml b/document_ftp/test/document_ftp_test2.yml new file mode 100644 index 00000000..ce8e2590 --- /dev/null +++ b/document_ftp/test/document_ftp_test2.yml @@ -0,0 +1,289 @@ +- + In order to test the document_ftp functionality +- + I open the 8021 port and see for ftp presence there +- + !python {model: ir.attachment}: | + from document_ftp import test_easyftp as te + ftp = te.get_plain_ftp(timeout=2.0) + assert ftp.sock and (ftp.lastresp == '220'), ftp.lastresp + ftp.close() +- + I read the list of databases at port 8021 and confirm our db is + there +- + !python {model: ir.attachment}: | + from document_ftp import test_easyftp as te + ftp = te.get_ftp_login(cr, uid, self) + assert cr.dbname in ftp.nlst("/") + ftp.close() +- + I try to locate the default "Documents" folder in the db. +- + !python {model: ir.attachment}: | + from document_ftp import test_easyftp as te + ftp = te.get_ftp_login(cr, uid, self) + ftp.cwd('Documents') + ftp.close() +- + I create a "test.txt" file at the server (directly). The file + should have the "abcd" content +- + !python {model: ir.attachment}: | + from document_ftp import test_easyftp as te + from cStringIO import StringIO + ftp = te.get_ftp_folder(cr, uid, self, 'Documents') + fdata = StringIO('abcd') + ftp.storbinary('STOR test.txt', fdata) + ftp.close() +- + I look for the "test.txt" file at the server +- + !python {model: ir.attachment}: | + from document_ftp import test_easyftp as te + ftp = te.get_ftp_folder(cr, uid, self, 'Documents') + assert ftp.nlst("test.txt") == ['test.txt'] + ftp.close() +- + I check that the content of "test.txt" is "abcd" +- + !python {model: ir.attachment}: | + from document_ftp import test_easyftp as te + from cStringIO import StringIO + ftp = te.get_ftp_folder(cr, uid, self, 'Documents') + gotdata = te.get_ftp_fulldata(ftp, "test.txt") + ftp.close() + assert gotdata == 'abcd', 'Data: %r' % gotdata +- + I append the string 'defgh' to "test.txt" +- + !python {model: ir.attachment}: | + from document_ftp import test_easyftp as te + from cStringIO import StringIO + ftp = te.get_ftp_folder(cr, uid, self, 'Documents') + fdata = StringIO('defgh') + ftp.storbinary('APPE test.txt', fdata) + ftp.close() +- + I check that the content of "text.txt" is 'abcddefgh' +- + !python {model: ir.attachment}: | + from document_ftp import test_easyftp as te + from cStringIO import StringIO + ftp = te.get_ftp_folder(cr, uid, self, 'Documents') + gotdata = te.get_ftp_fulldata(ftp, "test.txt") + ftp.close() + assert gotdata == 'abcddefgh', 'Data: %r' % gotdata +- + I try to cd into an non-existing folder 'Not-This' +- + !python {model: ir.attachment}: | + from document_ftp import test_easyftp as te + import ftplib + ftp = te.get_ftp_login(cr, uid, self) + try: + ftp.cwd('/Not-This') + assert False, "We should't be able to change here" + except ftplib.error_perm: + pass + except OSError, err: + ftp.close() + assert err.errno == 2, err.errno + ftp.close() +- + I create a "test2.txt" file through FTP. +- + !python {model: ir.attachment}: | + from document_ftp import test_easyftp as te + from cStringIO import StringIO + ftp = te.get_ftp_folder(cr, uid, self, 'Documents') + fdata = StringIO('abcd') + ftp.storbinary('STOR test2.txt', fdata) + ftp.close() +- + I look for the "test2.txt" file at the server +- + !python {model: ir.attachment }: | + cr.rollback() # restart transaction to see changes (FTP-FS uses its own cursor) + ids = self.search(cr, uid, [('name', '=', 'test2.txt')]) + assert ids, "No test2.txt file found." +- + I delete the "test2.txt" file using FTP. +- + !python {model: ir.attachment}: | + from document_ftp import test_easyftp as te + from cStringIO import StringIO + ftp = te.get_ftp_folder(cr, uid, self, 'Documents') + ftp.delete('test2.txt') + ftp.close() +- + I check at the server that test2.txt is deleted +- + !python {model: ir.attachment }: | + cr.rollback() # restart transaction to see changes (FTP-FS uses its own cursor) + ids = self.search(cr, uid, [('name', '=', 'test2.txt')]) + assert not ids, "test2.txt file can still be found." +- + I create a test2.txt file again. +- + !python {model: ir.attachment}: | + from document_ftp import test_easyftp as te + from cStringIO import StringIO + ftp = te.get_ftp_folder(cr, uid, self, 'Documents') + fdata = StringIO('abcd') + ftp.storbinary('STOR test2.txt', fdata) + ftp.close() + cr.rollback() # restart transaction to see changes (FTP-FS uses its own cursor) +- + I delete the test2.txt from the server (RPC). +- + !delete { model: ir.attachment, id:, search: "[('name','=','test2.txt')]" } +- + I also commit, because ftp would run in a different transaction. +- + !python {model: ir.attachment}: | + cr.commit() +- + I check through FTP that test2.txt does not appear. +- + !python {model: ir.attachment}: | + from document_ftp import test_easyftp as te + import ftplib + ftp = te.get_ftp_folder(cr, uid, self, 'Documents') + try: + nlst_result = ftp.nlst("test2.txt") + except ftplib.error_perm: # 550 error: 'path not exists' + nlst_result = [] + assert "test2.txt" not in nlst_result, "Files: %r" % nlst_result + ftp.close() +- + I create a "test-name.txt" file +- + !python {model: ir.attachment}: | + from document_ftp import test_easyftp as te + from cStringIO import StringIO + ftp = te.get_ftp_folder(cr, uid, self, 'Documents') + fdata = StringIO('abcd') + ftp.storbinary('STOR test-name.txt', fdata) + ftp.close() +- + I rename the "test-name.txt" file through ftp. +- + !python {model: ir.attachment}: | + from document_ftp import test_easyftp as te + ftp = te.get_ftp_folder(cr, uid, self, 'Documents') + ftp.rename("test-name.txt", "test-renamed.txt") + ftp.close() +- + I check that test-name.txt has been renamed. +- + !python {model: ir.attachment}: | + from document_ftp import test_easyftp as te + from ftplib import error_perm + ftp = te.get_ftp_folder(cr, uid, self, 'Documents') + try: + res = ftp.nlst("test-name.txt") + assert res == [], "File has not been renamed!" + except error_perm, e: + pass + assert ftp.nlst("test-renamed.txt") == ['test-renamed.txt'] + ftp.close() +- + I create a new folder 'Test-Folder2' through FTP +- + !python {model: ir.attachment}: | + from document_ftp import test_easyftp as te + ftp = te.get_ftp_folder(cr, uid, self, 'Documents') + ftp.mkd("Test-Folder2") + ftp.close() +- + I create a file 'test3.txt' at the 'Test-Folder2' +- + !python {model: ir.attachment}: | + from document_ftp import test_easyftp as te + from cStringIO import StringIO + ftp = te.get_ftp_folder(cr, uid, self, 'Documents/Test-Folder2') + fdata = StringIO('abcd') + ftp.storbinary('STOR test3.txt', fdata) + ftp.close() +- + I try to retrieve test3.txt +- + !python {model: ir.attachment}: | + from document_ftp import test_easyftp as te + ftp = te.get_ftp_folder(cr, uid, self, 'Documents/Test-Folder2') + assert ftp.nlst("test3.txt") == ['test3.txt'], "File test3.txt is not there!" + ftp.close() +- + I create a new folder, 'Test-Folder3', through FTP + I try to move test3.txt to 'Test-Folder3' +- + !python {model: ir.attachment}: | + from document_ftp import test_easyftp as te + ftp = te.get_ftp_folder(cr, uid, self, 'Documents') + ftp.mkd("Test-Folder3") + ftp.close() + # TODO move +- + I remove the 'Test-Folder3' +- + !python {model: ir.attachment}: | + from document_ftp import test_easyftp as te + ftp = te.get_ftp_folder(cr, uid, self, 'Documents') + ftp.rmd("Test-Folder3") + ftp.close() +- + I check that test3.txt is removed. +- + I create 5 files through FTP +- + !python {model: ir.attachment}: | + from document_ftp import test_easyftp as te + from cStringIO import StringIO + ftp = te.get_ftp_folder(cr, uid, self, 'Documents/Test-Folder2') + fdata = StringIO('abcd') + for i in range(0, 5): + fdata.seek(0) + ftp.storbinary('STOR test-name%s.txt' %i, fdata) + ftp.close() +- + I list the 5 files, check speed +- + !python {model: ir.attachment}: | + from document_ftp import test_easyftp as te + ftp = te.get_ftp_folder(cr, uid, self, 'Documents/Test-Folder2') + assert len(ftp.nlst()) >= 5, "We haven't managed to store 5 files!" +- + I read the 5 files, check speed +- + I move the 5 files to 'Test-Folder2' +- + I delete the 5 files +- + !python {model: ir.attachment}: | + from document_ftp import test_easyftp as te + from cStringIO import StringIO + ftp = te.get_ftp_folder(cr, uid, self, 'Documents/Test-Folder2') + ftp.delete('test3.txt') + for i in range(0, 5): + ftp.delete('test-name%s.txt' %i) + ftp.close() + +- + I delete the "test.txt" and "test-renamed.txt" file +- + !python {model: ir.attachment}: | + from document_ftp import test_easyftp as te + from cStringIO import StringIO + ftp = te.get_ftp_folder(cr, uid, self, 'Documents') + ftp.delete('test.txt') + ftp.delete('test-renamed.txt') + ftp.close() +- + I remove the 'Test-Folder2' +- + !python {model: ir.attachment}: | + from document_ftp import test_easyftp as te + ftp = te.get_ftp_folder(cr, uid, self, 'Documents') + ftp.rmd("Test-Folder2") + ftp.close() diff --git a/document_ftp/test/document_ftp_test3.yml b/document_ftp/test/document_ftp_test3.yml new file mode 100644 index 00000000..a51f259f --- /dev/null +++ b/document_ftp/test/document_ftp_test3.yml @@ -0,0 +1,92 @@ +- + In order to check international character functionality +- + !python {model: ir.attachment}: | + from document_ftp import test_easyftp as te + ftp = te.get_plain_ftp(timeout=1.0) +- + I create in the server a folder called 'Δοκιμαστικός Φάκελλος' +- + !record {model: document.directory, id: dir_itests }: + name: 'Δοκιμαστικός Φάκελλος' + parent_id: document.dir_root +- + And then I create another folder, under it, through FTP +- + !python {model: ir.attachment}: | + cr.commit() + from document_ftp import test_easyftp as te + ftp = te.get_ftp_folder(cr, uid, self, 'Documents/Δοκιμαστικός Φάκελλος') + ftp.mkd("Φάκελλος από κάτω") +- + I check that this folder exists at the server +- + !assert {model: document.directory, id: , search: "[('name','=','Φάκελλος από κάτω')]" }: + - parent_id != False +- + I login with FTP and check that 'Δοκιμαστικός Φάκελλος' is there +- + !python {model: ir.attachment}: | + from document_ftp import test_easyftp as te + ftp = te.get_ftp_folder(cr, uid, self, 'Documents/Δοκιμαστικός Φάκελλος/Φάκελλος από κάτω') +- + I create a file named 'Δοκιμή' into that folder +- + !python {model: ir.attachment}: | + from document_ftp import test_easyftp as te + from cStringIO import StringIO + ftp = te.get_ftp_folder(cr, uid, self, 'Documents/Δοκιμαστικός Φάκελλος/Φάκελλος από κάτω') + fdata = StringIO('κείμενο με utf-8') + ftp.storbinary('STOR Δοκιμή.txt', fdata) +- + I remove the 'Δοκιμή.txt' file +- + !python {model: ir.attachment}: | + from document_ftp import test_easyftp as te + from cStringIO import StringIO + ftp = te.get_ftp_folder(cr, uid, self, 'Documents/Δοκιμαστικός Φάκελλος/Φάκελλος από κάτω') + ftp.delete('Δοκιμή.txt') +- + I rename 'Φάκελλος από κάτω' into 'άλλος' +- + !python {model: ir.attachment}: | + from document_ftp import test_easyftp as te + ftp = te.get_ftp_folder(cr, uid, self, 'Documents/Δοκιμαστικός Φάκελλος') + ftp.rename("Φάκελλος από κάτω", "άλλος") +- + I place a file 'file Φ3' in 'άλλος' +- + !python {model: ir.attachment}: | + from document_ftp import test_easyftp as te + from cStringIO import StringIO + ftp = te.get_ftp_folder(cr, uid, self, 'Documents/Δοκιμαστικός Φάκελλος/άλλος') + fdata = StringIO('κι άλλο κείμενο') + ftp.storbinary('STOR file Φ3.txt', fdata) +- + I rename the file into file+range(1..200) (large filename) +- + !python {model: ir.attachment}: | + from document_ftp import test_easyftp as te + from cStringIO import StringIO + ftp = te.get_ftp_folder(cr, uid, self, 'Documents/Δοκιμαστικός Φάκελλος/άλλος') + vuvuzela = 'b'+''.join('z' * 200)+'!' + ftp.rename("file Φ3.txt", vuvuzela) +- + I delete the file with the large name +- + !python {model: ir.attachment}: | + from document_ftp import test_easyftp as te + from cStringIO import StringIO + ftp = te.get_ftp_folder(cr, uid, self, 'Documents/Δοκιμαστικός Φάκελλος/άλλος') + vuvuzela = 'b'+''.join('z' * 200)+'!' + ftp.delete(vuvuzela) + +- + I delete the testing folders +- + !python {model: ir.attachment}: | + from document_ftp import test_easyftp as te + from cStringIO import StringIO + ftp = te.get_ftp_folder(cr, uid, self, 'Documents') + ftp.rmd('Δοκιμαστικός Φάκελλος/άλλος') + ftp.rmd('Δοκιμαστικός Φάκελλος') diff --git a/document_ftp/test/document_ftp_test4.yml b/document_ftp/test/document_ftp_test4.yml new file mode 100644 index 00000000..9b311f64 --- /dev/null +++ b/document_ftp/test/document_ftp_test4.yml @@ -0,0 +1,187 @@ +- + In order to check dynamic folder functionality of document + FTP +- + !python {model: ir.attachment}: | + from document_ftp import test_easyftp as te + ftp = te.get_plain_ftp(timeout=1.0) +- | + I create two partners 'Partner1' and 'Partner2'. + I create three partner categories: 'none', 'pat1' and 'all' + I attach Partner1 to pat1, Partner1+Partner2 to 'all' +- + !record {model: res.partner.category, id: tpat_categ_none }: + name: 'No partners' +- + !record {model: res.partner.category, id: tpat_categ_pat1 }: + name: 'Pat 1' +- + !record {model: res.partner.category, id: tpat_categ_all }: + name: 'All Partner1+2' +- + !record {model: res.partner, id: tpartner1 }: + name: Partner 1 + category_id: + - tpat_categ_pat1 + - tpat_categ_all +- + !record {model: res.partner, id: tpartner_2 }: + name: 'Partner 2' + category_id: + - tpat_categ_all +- + I create a resource folder of partners, by the (none, pat1, all) + categories. +- + !record {model: document.directory, id: dir_tests2 }: + name: Partners Testing + parent_id: document.dir_root + type: ressource + ressource_type_id: base.model_res_partner_category + domain: [] # TODO +- + I commit (because FTP operations are on different transaction) +- + !python {model: document.directory, id: }: | + cr.commit() +- + I browse through ftp in the resource folder, checking that three + categories are there. +- + !python {model: ir.attachment}: | + from document_ftp import test_easyftp as te + ftp = te.get_ftp_folder(cr, uid, self, 'Documents/Partners Testing') + dirs = ftp.nlst() + for dir in [ 'All Partner1+2', 'No partners', 'Pat 1' ]: + assert dir in dirs, "Dir %s not in folder" % dir +- + I create a 'partners' folder by the first resource one. +- + !record {model: document.directory, id: dir_respart1 }: + name: Partners of Test + parent_id: dir_tests2 + type: ressource + ressource_type_id: base.model_res_partner + domain: "[('category_id','in',[active_id])]" + ressource_parent_type_id : base.model_res_partner_category +- + I commit (because FTP operations are on different transaction) +- + !python {model: document.directory, id: }: | + cr.commit() +- + I check through FTP that the correct partners are listed at each + 'partners' folder. +- + !python {model: ir.attachment}: | + from document_ftp import test_easyftp as te + ftp = te.get_ftp_folder(cr, uid, self, 'Documents/Partners Testing') + correct = { 'All Partner1+2': [ 'Partner 1', 'Partner 2' ], + 'No partners': [], + 'Pat 1': ['Partner 1',] } + for dir in correct: + res = ftp.nlst(dir+'/Partners of Test') + assert res == correct[dir], "Dir %s falsely contains %s" %(dir, res) +- + I create an ir.attachment, attached (not related) to Partner1 +- + !record {model: ir.attachment, id: file_test1}: + name: File of pat1 + res_model: res.partner + res_id: !eval ref("tpartner1") +- + I commit (because FTP operations are on different transaction) +- + !python {model: document.directory, id: }: | + cr.commit() +- + I check that pat1/Partner1 folder has the file. + I check that all/Partner1 folder has the file +- + !python {model: ir.attachment}: | + from document_ftp import test_easyftp as te + ftp = te.get_ftp_folder(cr, uid, self, 'Documents/Partners Testing') + dirs = [ 'All Partner1+2', 'Pat 1' ] + for dir in dirs: + res = ftp.nlst(dir+'/Partners of Test/Partner 1') + assert 'File of pat1' in res, "Dir %s contains only %s" %(dir, res) +- + I place a file at the 'pat1'/Partner1 folder, through FTP +- + !python {model: ir.attachment}: | + from document_ftp import test_easyftp as te + from cStringIO import StringIO + ftp = te.get_ftp_folder(cr, uid, self, 'Documents/Partners Testing/Pat 1/Partners of Test/Partner 1') + fdata = StringIO('abcd') + ftp.storbinary('STOR pat1-dynamic.txt', fdata) + cr.rollback() # restart transaction to see changes (FTP-FS uses its own cursor) +- + I check at the server that the file is attached to Partner1 +- + !assert {model: ir.attachment, id: , search: "[('name','=','pat1-dynamic.txt')]" }: + - parent_id.name == 'Documents' + - res_model == 'res.partner' + - res_id != False +- + I try to create a file directly under the Partners Testing folder +- + !python {model: ir.attachment}: | + from document_ftp import test_easyftp as te + import ftplib + from cStringIO import StringIO + ftp = te.get_ftp_folder(cr, uid, self, 'Documents/Partners Testing') + fdata = StringIO('abcd') + try: + ftp.storbinary('STOR stray.txt', fdata) + assert False, "We should't be able to create files here" + except ftplib.error_perm: + # That's what should happen + pass +- + I try to create a folder directly under the Partners Testing folder +- + !python {model: ir.attachment}: | + from document_ftp import test_easyftp as te + import ftplib + from cStringIO import StringIO + ftp = te.get_ftp_folder(cr, uid, self, 'Documents/Partners Testing') + try: + ftp.mkd('Weird folder') + assert False, "We should't be able to create folders here" + except ftplib.error_perm: + # That's what should happen + pass +- + I check that all/Partner1 also has the file +- | + Bonus Piste: + I create a 'Partner3' under 'all' + +- + I delete "pat1-dynamic.txt" File. +- + !python {model: ir.attachment}: | + from document_ftp import test_easyftp as te + from cStringIO import StringIO + ftp = te.get_ftp_folder(cr, uid, self, 'Documents/Partners Testing/Pat 1/Partners of Test/Partner 1') + ftp.delete('pat1-dynamic.txt') + ftp.close() + cr.rollback() # restart transaction to see changes (FTP-FS uses its own cursor) + +- + I delete the Partners Testing folder, "File of pat1" file, Partner and Partner category. +- + !python {model: document.directory}: | + attach_pool = self.pool.get('ir.attachment') + partner_categ_pool = self.pool.get('res.partner.category') + partner_pool = self.pool.get('res.partner') + + self.unlink(cr, uid, [ref('dir_tests2')]) + self.unlink(cr, uid, [ref('dir_respart1')]) + attach_pool.unlink(cr, uid, [ref('file_test1')]) + partner_categ_pool.unlink(cr, uid, [ref('tpat_categ_none')]) + partner_categ_pool.unlink(cr, uid, [ref('tpat_categ_pat1')]) + partner_categ_pool.unlink(cr, uid, [ref('tpat_categ_all')]) + partner_pool.unlink(cr, uid, [ref('tpartner1')]) + partner_pool.unlink(cr, uid, [ref('tpartner_2')]) + cr.commit() #required because all the operations via FTP were committed + diff --git a/document_ftp/test/document_ftp_test5.yml b/document_ftp/test/document_ftp_test5.yml new file mode 100644 index 00000000..a66f4099 --- /dev/null +++ b/document_ftp/test/document_ftp_test5.yml @@ -0,0 +1,30 @@ +- + In order to check the permissions setup and functionality of the + document module: +- + I create a testing user for the documents +- + I assign some ... group to the testing user +- + I create a "group testing" user, which also belongs to the same ... group +- + I create a "blocked" user. +- + I create (as root) a testing folder in the document hierarchy, and + assign ownership to the testing user, groups to the ... group. +- + I create a "private" folder inside the testing folder. +- + I try to read the testing folder as the testing user +- + I try to read the folder as the group user, it should fail. +- + I try to read the folder as the blocked user. +- + I create a "group" folder, with the ... group. +- + I try to read the "group" folder as the testing user +- + I try to read the "group" folder as the group user +- + I try to read the "group" folder as the blocked user diff --git a/document_ftp/test_easyftp.py b/document_ftp/test_easyftp.py new file mode 100644 index 00000000..407bb912 --- /dev/null +++ b/document_ftp/test_easyftp.py @@ -0,0 +1,71 @@ +# -*- coding: utf-8 -*- +############################################################################## +# +# OpenERP, Open Source Management Solution +# Copyright (C) 2004-2010 Tiny SPRL (). + +# ThinkOpen Solutions Brasil (). +# +# 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 . +# +############################################################################## + +""" This is a testing module, which exports some functions for the YAML tests. + Instead of repeating the same FTP code all over, we prefer to have + it in this file +""" + +from ftplib import FTP +from openerp.tools import config + +def get_plain_ftp(timeout=10.0): + ftp = FTP() + host = config.get('ftp_server_host', '177.134.67.3') + port = config.get('ftp_server_port', '8021') + ftp.connect(host, port, timeout) + return ftp + +def get_ftp_login(cr, uid, ormobj): + ftp = get_plain_ftp() + user = ormobj.pool.get('res.users').browse(cr, uid, uid) + passwd = user.password or '' + if passwd.startswith("$1$"): + # md5 by base crypt. We cannot decode, wild guess + # that passwd = login + passwd = user.login + ftp.login(user.login, passwd) + ftp.cwd("/" + cr.dbname) + return ftp + +def get_ftp_anonymous(cr): + ftp = get_plain_ftp() + ftp.login('anonymous', 'the-test') + ftp.cwd("/") + return ftp + +def get_ftp_folder(cr, uid, ormobj, foldername): + ftp = get_ftp_login(cr, uid, ormobj) + ftp.cwd("/" + cr.dbname + "/" + foldername) + return ftp + +def get_ftp_fulldata(ftp, fname, limit=8192): + from functools import partial + data = [] + def ffp(data, ndata): + if len(data) + len(ndata) > limit: + raise IndexError('Data over the limit.') + data.append(ndata) + ftp.retrbinary('RETR %s' % fname, partial(ffp, data)) + return ''.join(data) + diff --git a/document_ftp/wizard/__init__.py b/document_ftp/wizard/__init__.py new file mode 100644 index 00000000..ba533caf --- /dev/null +++ b/document_ftp/wizard/__init__.py @@ -0,0 +1,26 @@ +# -*- coding: utf-8 -*- +############################################################################## +# +# OpenERP, Open Source Management Solution +# Copyright (C) 2004-2010 Tiny SPRL (). + +# ThinkOpen Solutions Brasil (). +# +# 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 . import ftp_browse +from . import ftp_configuration + diff --git a/document_ftp/wizard/ftp_browse.py b/document_ftp/wizard/ftp_browse.py new file mode 100644 index 00000000..d65fd92b --- /dev/null +++ b/document_ftp/wizard/ftp_browse.py @@ -0,0 +1,65 @@ +# -*- coding: utf-8 -*- +############################################################################## +# +# OpenERP, Open Source Management Solution +# Copyright (C) 2004-2010 Tiny SPRL (). + +# ThinkOpen Solutions Brasil (). +# +# 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 openerp.osv import fields, osv +from .. import ftpserver + +class document_ftp_browse(osv.osv_memory): + _name = 'document.ftp.browse' + _description = 'Document FTP Browse' + + _columns = { + 'url' : fields.char('FTP Server', size=64, required=True), + } + + def default_get(self, cr, uid, fields, context=None): + res = {} + if 'url' in fields: + user_pool = self.pool.get('res.users') + current_user = user_pool.browse(cr, uid, uid, context=context) + data_pool = self.pool.get('ir.model.data') + aid = data_pool._get_id(cr, uid, 'document_ftp', 'action_document_browse') + aid = data_pool.browse(cr, uid, aid, context=context).res_id + ftp_url = self.pool.get('ir.actions.act_url').browse(cr, uid, aid, context=context) + url = ftp_url.url and ftp_url.url.split('ftp://') or [] + if url: + url = url[1] + if url[-1] == '/': + url = url[:-1] + else: + url = '%s:%s' % (ftpserver.HOST, ftpserver.PORT) + res['url'] = 'ftp://%s@%s' % (current_user.login, url) + return res + + def browse_ftp(self, cr, uid, ids, context=None): + data_id = ids and ids[0] or False + data = self.browse(cr, uid, data_id, context=context) + final_url = data.url + return { + 'type': 'ir.actions.act_url', + 'url':final_url, + 'target': 'new' + } + +document_ftp_browse() + diff --git a/document_ftp/wizard/ftp_browse_view.xml b/document_ftp/wizard/ftp_browse_view.xml new file mode 100644 index 00000000..7ccc7897 --- /dev/null +++ b/document_ftp/wizard/ftp_browse_view.xml @@ -0,0 +1,39 @@ + + + + + Document FTP Browse + document.ftp.browse + +

+ + + +
+
+ + + + + + Document Browse + ir.actions.act_window + document.ftp.browse + + form + form + new + + + + + + diff --git a/document_ftp/wizard/ftp_configuration.py b/document_ftp/wizard/ftp_configuration.py new file mode 100644 index 00000000..64ee369e --- /dev/null +++ b/document_ftp/wizard/ftp_configuration.py @@ -0,0 +1,53 @@ +# -*- coding: utf-8 -*- +############################################################################## +# +# OpenERP, Open Source Management Solution +# Copyright (C) 2004-2010 Tiny SPRL (). + +# ThinkOpen Solutions Brasil (). +# +# 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 openerp.osv import fields, osv +from openerp.tools import config + +class document_ftp_configuration(osv.osv_memory): + + _name = 'document.ftp.configuration' + _description = 'Auto Directory Configuration' + _inherit = 'res.config' + _rec_name = 'host' + _columns = { + 'host': fields.char('Address', size=64, + help="Server address or IP and port to which users should connect to for DMS access", + required=True), + } + + _defaults = { + 'host': config.get('ftp_server_host', 'localhost') + ':' + config.get('ftp_server_port', '8021'), + } + + def execute(self, cr, uid, ids, context=None): + conf = self.browse(cr, uid, ids[0], context=context) + data_pool = self.pool.get('ir.model.data') + # Update the action for FTP browse. + aid = data_pool._get_id(cr, uid, 'document_ftp', 'action_document_browse') + aid = data_pool.browse(cr, uid, aid, context=context).res_id + self.pool.get('ir.actions.act_url').write(cr, uid, [aid], + {'url': 'ftp://' + (conf.host or 'localhost:8021') + '/' + cr.dbname + '/'}) + +document_ftp_configuration() + diff --git a/document_ftp/wizard/ftp_configuration_view.xml b/document_ftp/wizard/ftp_configuration_view.xml new file mode 100644 index 00000000..7cb4d25c --- /dev/null +++ b/document_ftp/wizard/ftp_configuration_view.xml @@ -0,0 +1,44 @@ + + + + + Browse Files + ftp://localhost:8021/ + + + + FTP Server Configuration + document.ftp.configuration + + + +
+ Knowledge Application + Configuration +
+ + + +
+
+
+ + + FTP Server Configuration + ir.actions.act_window + document.ftp.configuration + + form + form + new + + + + + automatic + +
+
From d8e4bbc1619165e20f7744906f418d8b7be11a5a Mon Sep 17 00:00:00 2001 From: Carlos Almeida Date: Sat, 17 Oct 2015 01:01:56 -0300 Subject: [PATCH 2/8] [FIX] pep8 fixes --- document_ftp/__openerp__.py | 7 +++- document_ftp/ftpserver/__init__.py | 15 +++---- document_ftp/ftpserver/abstracted_fs.py | 41 +++++++++---------- document_ftp/ftpserver/authorizer.py | 4 +- document_ftp/ftpserver/ftpserver.py | 51 ++++++++++++------------ document_ftp/res_config.py | 6 +-- document_ftp/wizard/ftp_browse.py | 4 +- document_ftp/wizard/ftp_configuration.py | 5 ++- 8 files changed, 67 insertions(+), 66 deletions(-) diff --git a/document_ftp/__openerp__.py b/document_ftp/__openerp__.py index 1bb322da..0c11d637 100644 --- a/document_ftp/__openerp__.py +++ b/document_ftp/__openerp__.py @@ -26,7 +26,8 @@ 'name': 'Shared Repositories (FTP)', 'version': '8.0.0.0.1', 'category': 'Knowledge Management', - 'author': 'OpenERP SA', + 'author': 'Community Association (OCA)', + "license": "AGPL-3", 'website': 'http://www.openerp.com', 'depends': ['base', 'document'], 'data': [ @@ -42,7 +43,9 @@ ], 'installable': True, 'auto_install': False, - 'images': ['images/1_configure_ftp.jpeg', 'images/2_document_browse.jpeg', 'images/3_document_ftp.jpeg'], + 'images': ['images/1_configure_ftp.jpeg', + 'images/2_document_browse.jpeg', + 'images/3_document_ftp.jpeg'], 'post_load': 'post_load', } diff --git a/document_ftp/ftpserver/__init__.py b/document_ftp/ftpserver/__init__.py index 2d55be57..285a4410 100644 --- a/document_ftp/ftpserver/__init__.py +++ b/document_ftp/ftpserver/__init__.py @@ -24,9 +24,9 @@ import threading import logging -import authorizer -import abstracted_fs -import ftpserver +import document_ftp.ftpserver.Authorizer +import document_ftp.ftpserver.AbstractedFs +import document_ftp.ftpserver.ftpserver import openerp from openerp.tools import config @@ -37,7 +37,8 @@ def start_server(): if openerp.multi_process: _logger.info("FTP disabled in multiprocess mode") return - ip_address = ([(s.connect(('8.8.8.8', 80)), s.getsockname()[0], s.close()) for s in [socket.socket(socket.AF_INET, socket.SOCK_DGRAM)]][0][1]) + ip_address = ([(s.connect(('8.8.8.8', 80)), s.getsockname()[0], s.close()) \ + for s in [socket.socket(socket.AF_INET, socket.SOCK_DGRAM)]][0][1]) if not ip_address: ip_address = '127.0.0.1' HOST = config.get('ftp_server_host', str(ip_address)) @@ -47,10 +48,10 @@ def start_server(): if len(pps) == 2: PASSIVE_PORTS = int(pps[0]), int(pps[1]) - class ftp_server(threading.Thread): + class FtpServer(threading.Thread): def run(self): - autho = authorizer.authorizer() + autho = authorizer.Authorizer() ftpserver.FTPHandler.authorizer = autho ftpserver.max_cons = 300 ftpserver.max_cons_per_ip = 50 @@ -69,6 +70,6 @@ def start_server(): _logger.info("\n Server FTP Not Started\n") else: _logger.info("\n Serving FTP on %s:%s\n" % (HOST, PORT)) - ds = ftp_server() + ds = FtpServer() ds.daemon = True ds.start() diff --git a/document_ftp/ftpserver/abstracted_fs.py b/document_ftp/ftpserver/abstracted_fs.py index 9d611f85..800cea65 100644 --- a/document_ftp/ftpserver/abstracted_fs.py +++ b/document_ftp/ftpserver/abstracted_fs.py @@ -1,4 +1,4 @@ -# -*- encoding: utf-8 -*- +# -*- coding: utf-8 -*- import os import time @@ -11,7 +11,6 @@ import fnmatch from openerp import pooler, netsvc, sql_db from openerp.service import security -from openerp.osv import osv from openerp.addons.document.document import get_node_context @@ -30,10 +29,10 @@ def _get_month_name(month): elif month == 11:return 'Nov' elif month == 12:return 'Dec' -from ftpserver import _to_decode, _to_unicode +from document_ftp.ftpserver.ftpserver import _to_decode, _to_unicode -class abstracted_fs(object): +class AbstractedFs(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. @@ -190,21 +189,19 @@ class abstracted_fs(object): """ 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 = 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)]) - if len(res): - pre = prefix.split('.') - prefix = pre[0] + '.v' + str(len(res)) + '.' + pre[1] - return self.create(dir, suffix + prefix, text) - - +# text = not 'b' in mode +# # for unique file , maintain version if duplicate file +# if dir: +# cr = dir.cr +# uid = dir.uid +# 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)]) +# 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): @@ -341,7 +338,7 @@ class abstracted_fs(object): def listdir(self, datacr): """List the content of a directory.""" - class false_node(object): + class FalseNode(object): write_date = 0.0 create_date = 0.0 unixperms = 040550 @@ -357,8 +354,8 @@ class abstracted_fs(object): result = [] for db in self.db_list(): try: - result.append(false_node(db)) - except osv.except_osv: + result.append(FalseNode(db)) + except: pass return result cr, node, rem = datacr diff --git a/document_ftp/ftpserver/authorizer.py b/document_ftp/ftpserver/authorizer.py index 5bcb21b6..1fa59391 100644 --- a/document_ftp/ftpserver/authorizer.py +++ b/document_ftp/ftpserver/authorizer.py @@ -1,6 +1,6 @@ -# -*- encoding: utf-8 -*- +# -*- coding: utf-8 -*- -class authorizer: +class Authorizer: read_perms = "elr" write_perms = "adfmw" diff --git a/document_ftp/ftpserver/ftpserver.py b/document_ftp/ftpserver/ftpserver.py index 99d2e50a..c3acba05 100755 --- a/document_ftp/ftpserver/ftpserver.py +++ b/document_ftp/ftpserver/ftpserver.py @@ -1,5 +1,5 @@ #!/usr/bin/env python -# -*- encoding: utf-8 -*- +# -*- coding: utf-8 -*- # ftpserver.py # # pyftpdlib is released under the MIT license, reproduced below: @@ -1579,29 +1579,29 @@ class FTPHandler(asynchat.async_chat): 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'} + 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'} raise NotImplementedError - 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 +# 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 for the socket @@ -2098,7 +2098,7 @@ class FTPHandler(asynchat.async_chat): data = '' if listing: listing.sort() - data = ''.join([ _to_decode(x) + '\r\n' for x in listing ]) + data = ''.join([_to_decode(x) + '\r\n' for x in listing]) self.log('OK NLST "%s". Transfer starting.' % line) self.push_dtp_data(data) @@ -2287,7 +2287,7 @@ class FTPHandler(asynchat.async_chat): basedir = self.fs.ftp2fs(self.fs.cwd, datacr) prefix = 'ftpd.' try: - fd = self.try_as_current_user(self.fs.mkstemp, kwargs={'prefix':prefix, + fd = self.try_as_current_user(self.fs.mkstemp, kwargs={'prefix': prefix, 'dir': basedir}, line=line) except FTPExceptionSent: self.fs.close_cr(datacr) @@ -2480,7 +2480,6 @@ class FTPHandler(asynchat.async_chat): # code to be given in this case, but this is wrong... self.respond("230 Ready for new user.") - # --- filesystem operations def ftp_PWD(self, line): diff --git a/document_ftp/res_config.py b/document_ftp/res_config.py index cb17bf0e..90371821 100644 --- a/document_ftp/res_config.py +++ b/document_ftp/res_config.py @@ -24,12 +24,12 @@ from openerp.osv import fields, osv from openerp.tools import config -class documnet_ftp_setting(osv.osv_memory): +class DocumentFtpSetting(osv.osv_memory): _name = 'knowledge.config.settings' _inherit = 'knowledge.config.settings' _columns = { - 'ftp_server_host' : fields.char(string='Host'), - 'ftp_server_port' : fields.char(string='Port'), + 'ftp_server_host': fields.char(string='Host'), + 'ftp_server_port': fields.char(string='Port'), 'document_ftp_url': fields.char('Browse Documents', size=128, help="""Click the url to browse the documents""", readonly=True), } diff --git a/document_ftp/wizard/ftp_browse.py b/document_ftp/wizard/ftp_browse.py index d65fd92b..e418d561 100644 --- a/document_ftp/wizard/ftp_browse.py +++ b/document_ftp/wizard/ftp_browse.py @@ -24,7 +24,7 @@ from openerp.osv import fields, osv from .. import ftpserver -class document_ftp_browse(osv.osv_memory): +class DocumentFtpBrowse(osv.osv_memory): _name = 'document.ftp.browse' _description = 'Document FTP Browse' @@ -61,5 +61,5 @@ class document_ftp_browse(osv.osv_memory): 'target': 'new' } -document_ftp_browse() +DocumentFtpBrowse() diff --git a/document_ftp/wizard/ftp_configuration.py b/document_ftp/wizard/ftp_configuration.py index 64ee369e..08c57820 100644 --- a/document_ftp/wizard/ftp_configuration.py +++ b/document_ftp/wizard/ftp_configuration.py @@ -22,9 +22,10 @@ ############################################################################## from openerp.osv import fields, osv +from openerp.osv.orm import TransientModel from openerp.tools import config -class document_ftp_configuration(osv.osv_memory): +class DocumentFtpConfiguration(TransientModel): _name = 'document.ftp.configuration' _description = 'Auto Directory Configuration' @@ -49,5 +50,5 @@ class document_ftp_configuration(osv.osv_memory): self.pool.get('ir.actions.act_url').write(cr, uid, [aid], {'url': 'ftp://' + (conf.host or 'localhost:8021') + '/' + cr.dbname + '/'}) -document_ftp_configuration() +DocumentFtpConfiguration() From ffe9e5f949a52df1b74a8aaaf82496b050245034 Mon Sep 17 00:00:00 2001 From: Carlos Almeida Date: Sat, 17 Oct 2015 01:23:47 -0300 Subject: [PATCH 3/8] [FIX] more pep8 fixes --- document_ftp/ftpserver/__init__.py | 2 +- document_ftp/ftpserver/ftpserver.py | 7 ++++--- document_ftp/res_config.py | 5 +++-- document_ftp/wizard/ftp_browse.py | 5 +++-- document_ftp/wizard/ftp_configuration.py | 4 ++-- 5 files changed, 13 insertions(+), 10 deletions(-) diff --git a/document_ftp/ftpserver/__init__.py b/document_ftp/ftpserver/__init__.py index 285a4410..95b3a889 100644 --- a/document_ftp/ftpserver/__init__.py +++ b/document_ftp/ftpserver/__init__.py @@ -55,7 +55,7 @@ def start_server(): ftpserver.FTPHandler.authorizer = autho ftpserver.max_cons = 300 ftpserver.max_cons_per_ip = 50 - ftpserver.FTPHandler.abstracted_fs = abstracted_fs.abstracted_fs + ftpserver.FTPHandler.abstracted_fs = abstracted_fs.AbstractedFs if PASSIVE_PORTS: ftpserver.FTPHandler.passive_ports = PASSIVE_PORTS diff --git a/document_ftp/ftpserver/ftpserver.py b/document_ftp/ftpserver/ftpserver.py index c3acba05..c3642823 100755 --- a/document_ftp/ftpserver/ftpserver.py +++ b/document_ftp/ftpserver/ftpserver.py @@ -123,6 +123,7 @@ import random import stat from collections import deque from tarfile import filemode +import logging LOG_ACTIVE = True @@ -239,12 +240,12 @@ class AuthorizerError(Error): def log(msg): """Log messages intended for the end user.""" if LOG_ACTIVE: - print msg + logger.info('%s', msg) def logline(msg): """Log commands and responses passing through the command channel.""" if LOG_ACTIVE: - print msg + logger.info('%s', msg) def logerror(msg): """Log traceback outputs occurring in case of errors.""" @@ -1400,7 +1401,7 @@ class FTPHandler(asynchat.async_chat): self.set_terminator("\r\n") # session attributes - self.fs = self.abstracted_fs() + self.fs = self.AbstractedFs() self.authenticated = False self.username = "" self.password = "" diff --git a/document_ftp/res_config.py b/document_ftp/res_config.py index 90371821..b824c4d7 100644 --- a/document_ftp/res_config.py +++ b/document_ftp/res_config.py @@ -21,10 +21,11 @@ # ############################################################################## -from openerp.osv import fields, osv +from openerp.osv import fields +from openerp.osv.orm import TransientModel from openerp.tools import config -class DocumentFtpSetting(osv.osv_memory): +class DocumentFtpSetting(TransientModel): _name = 'knowledge.config.settings' _inherit = 'knowledge.config.settings' _columns = { diff --git a/document_ftp/wizard/ftp_browse.py b/document_ftp/wizard/ftp_browse.py index e418d561..f4c65b09 100644 --- a/document_ftp/wizard/ftp_browse.py +++ b/document_ftp/wizard/ftp_browse.py @@ -21,10 +21,11 @@ # ############################################################################## -from openerp.osv import fields, osv +from openerp.osv import fields +from openerp.osv.orm import TransientModel from .. import ftpserver -class DocumentFtpBrowse(osv.osv_memory): +class DocumentFtpBrowse(TransientModel): _name = 'document.ftp.browse' _description = 'Document FTP Browse' diff --git a/document_ftp/wizard/ftp_configuration.py b/document_ftp/wizard/ftp_configuration.py index 08c57820..ae16f2c1 100644 --- a/document_ftp/wizard/ftp_configuration.py +++ b/document_ftp/wizard/ftp_configuration.py @@ -21,8 +21,8 @@ # ############################################################################## -from openerp.osv import fields, osv -from openerp.osv.orm import TransientModel +from openerp.osv import fields +from openerp.osv.orm import field, TransientModel from openerp.tools import config class DocumentFtpConfiguration(TransientModel): From 03dc8e46da5a488821322524c291f3b037b98504 Mon Sep 17 00:00:00 2001 From: Carlos Almeida Date: Sat, 17 Oct 2015 01:27:31 -0300 Subject: [PATCH 4/8] [FIX] more pep8 fixes --- document_ftp/res_config.py | 2 +- document_ftp/wizard/ftp_browse.py | 2 +- document_ftp/wizard/ftp_configuration.py | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/document_ftp/res_config.py b/document_ftp/res_config.py index b824c4d7..db50c2e6 100644 --- a/document_ftp/res_config.py +++ b/document_ftp/res_config.py @@ -21,7 +21,7 @@ # ############################################################################## -from openerp.osv import fields +from openerp.osv.orm import fields from openerp.osv.orm import TransientModel from openerp.tools import config diff --git a/document_ftp/wizard/ftp_browse.py b/document_ftp/wizard/ftp_browse.py index f4c65b09..ab16b7c8 100644 --- a/document_ftp/wizard/ftp_browse.py +++ b/document_ftp/wizard/ftp_browse.py @@ -21,7 +21,7 @@ # ############################################################################## -from openerp.osv import fields +from openerp.osv.orm import fields from openerp.osv.orm import TransientModel from .. import ftpserver diff --git a/document_ftp/wizard/ftp_configuration.py b/document_ftp/wizard/ftp_configuration.py index ae16f2c1..4cef31a7 100644 --- a/document_ftp/wizard/ftp_configuration.py +++ b/document_ftp/wizard/ftp_configuration.py @@ -21,7 +21,7 @@ # ############################################################################## -from openerp.osv import fields +from openerp.osv.orm import fields from openerp.osv.orm import field, TransientModel from openerp.tools import config From 109ce4c35bb883a4a01f2a9a6e9ff3fe7937c95f Mon Sep 17 00:00:00 2001 From: Carlos Almeida Date: Sat, 17 Oct 2015 01:30:49 -0300 Subject: [PATCH 5/8] [FIX] more pep8 fixes --- document_ftp/__openerp__.py | 2 +- document_ftp/ftpserver/abstracted_fs.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/document_ftp/__openerp__.py b/document_ftp/__openerp__.py index 0c11d637..351fa48e 100644 --- a/document_ftp/__openerp__.py +++ b/document_ftp/__openerp__.py @@ -26,7 +26,7 @@ 'name': 'Shared Repositories (FTP)', 'version': '8.0.0.0.1', 'category': 'Knowledge Management', - 'author': 'Community Association (OCA)', + 'author': 'Odoo Community Association (OCA)', "license": "AGPL-3", 'website': 'http://www.openerp.com', 'depends': ['base', 'document'], diff --git a/document_ftp/ftpserver/abstracted_fs.py b/document_ftp/ftpserver/abstracted_fs.py index 800cea65..1d8cbc55 100644 --- a/document_ftp/ftpserver/abstracted_fs.py +++ b/document_ftp/ftpserver/abstracted_fs.py @@ -461,7 +461,7 @@ class AbstractedFs(object): a broken or circular symbolic link. """ raise DeprecationWarning() - return path and True or False +# return path and True or False exists = lexists From 54520fb4f6d3e7638843f78dea5f44d7256d1ddc Mon Sep 17 00:00:00 2001 From: Carlos Almeida Date: Sat, 17 Oct 2015 01:53:55 -0300 Subject: [PATCH 6/8] [FIX] flake8 fixes --- document_ftp/__openerp__.py | 1 - document_ftp/ftpserver/__init__.py | 33 ++++++++++++++-------------- document_ftp/ftpserver/authorizer.py | 5 ++++- document_ftp/wizard/__init__.py | 7 +++--- document_ftp/wizard/ftp_browse.py | 1 + 5 files changed, 25 insertions(+), 22 deletions(-) diff --git a/document_ftp/__openerp__.py b/document_ftp/__openerp__.py index 351fa48e..76d67764 100644 --- a/document_ftp/__openerp__.py +++ b/document_ftp/__openerp__.py @@ -48,4 +48,3 @@ 'images/3_document_ftp.jpeg'], 'post_load': 'post_load', } - diff --git a/document_ftp/ftpserver/__init__.py b/document_ftp/ftpserver/__init__.py index 95b3a889..5d54c3ae 100644 --- a/document_ftp/ftpserver/__init__.py +++ b/document_ftp/ftpserver/__init__.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- ############################################################################## -# +# # OpenERP, Open Source Management Solution # Copyright (C) 2004-2010 Tiny SPRL (). @@ -17,7 +17,7 @@ # 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 . +# along with this program. If not, see . # ############################################################################## @@ -26,18 +26,19 @@ import logging import document_ftp.ftpserver.Authorizer import document_ftp.ftpserver.AbstractedFs -import document_ftp.ftpserver.ftpserver +import document_ftp.ftpserver.FtpServer import openerp from openerp.tools import config _logger = logging.getLogger(__name__) import socket + def start_server(): if openerp.multi_process: _logger.info("FTP disabled in multiprocess mode") return - ip_address = ([(s.connect(('8.8.8.8', 80)), s.getsockname()[0], s.close()) \ + ip_address = ([(s.connect(('8.8.8.8', 80)), s.getsockname()[0], s.close()) for s in [socket.socket(socket.AF_INET, socket.SOCK_DGRAM)]][0][1]) if not ip_address: ip_address = '127.0.0.1' @@ -49,21 +50,21 @@ def start_server(): PASSIVE_PORTS = int(pps[0]), int(pps[1]) class FtpServer(threading.Thread): - + def run(self): - autho = authorizer.Authorizer() - ftpserver.FTPHandler.authorizer = autho - ftpserver.max_cons = 300 - ftpserver.max_cons_per_ip = 50 - ftpserver.FTPHandler.abstracted_fs = abstracted_fs.AbstractedFs + autho = Authorizer.Authorizer() + FtpServer.FTPHandler.Authorizer = autho + FtpServer.max_cons = 300 + FtpServer.max_cons_per_ip = 50 + FtpServer.FTPHandler.AbstractedFs = AbstractedFs.AbstractedFs if PASSIVE_PORTS: - ftpserver.FTPHandler.passive_ports = PASSIVE_PORTS + FtpServer.FTPHandler.passive_ports = PASSIVE_PORTS - ftpserver.log = lambda msg: _logger.info(msg) - ftpserver.logline = lambda msg: None - ftpserver.logerror = lambda msg: _logger.error(msg) + FtpServer.log = lambda msg: _logger.info(msg) + FtpServer.logline = lambda msg: None + FtpServer.logerror = lambda msg: _logger.error(msg) - ftpd = ftpserver.FTPServer((HOST, PORT), ftpserver.FTPHandler) + ftpd = FtpServer.FTPServer((HOST, PORT), FtpServer.FTPHandler) ftpd.serve_forever() if HOST.lower() == 'none': @@ -72,4 +73,4 @@ def start_server(): _logger.info("\n Serving FTP on %s:%s\n" % (HOST, PORT)) ds = FtpServer() ds.daemon = True - ds.start() + ds.start() \ No newline at end of file diff --git a/document_ftp/ftpserver/authorizer.py b/document_ftp/ftpserver/authorizer.py index 1fa59391..3ec103ef 100644 --- a/document_ftp/ftpserver/authorizer.py +++ b/document_ftp/ftpserver/authorizer.py @@ -1,5 +1,9 @@ # -*- coding: utf-8 -*- +import pooler +import security + + class Authorizer: read_perms = "elr" write_perms = "adfmw" @@ -67,4 +71,3 @@ class Authorizer: def get_msg_quit(self, username): """Return the user's quitting message.""" return 'Bye.' - diff --git a/document_ftp/wizard/__init__.py b/document_ftp/wizard/__init__.py index ba533caf..cd464038 100644 --- a/document_ftp/wizard/__init__.py +++ b/document_ftp/wizard/__init__.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- ############################################################################## -# +# # OpenERP, Open Source Management Solution # Copyright (C) 2004-2010 Tiny SPRL (). @@ -17,10 +17,9 @@ # 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 . +# along with this program. If not, see . # ############################################################################## from . import ftp_browse -from . import ftp_configuration - +from . import ftp_configuration \ No newline at end of file diff --git a/document_ftp/wizard/ftp_browse.py b/document_ftp/wizard/ftp_browse.py index ab16b7c8..d3b603a2 100644 --- a/document_ftp/wizard/ftp_browse.py +++ b/document_ftp/wizard/ftp_browse.py @@ -25,6 +25,7 @@ from openerp.osv.orm import fields from openerp.osv.orm import TransientModel from .. import ftpserver + class DocumentFtpBrowse(TransientModel): _name = 'document.ftp.browse' _description = 'Document FTP Browse' From c182925850e9777b0fda65a09fad595c7b737880 Mon Sep 17 00:00:00 2001 From: Carlos Almeida Date: Sat, 17 Oct 2015 02:03:59 -0300 Subject: [PATCH 7/8] [FIX] flake8 fixes --- document_ftp/__init__.py | 5 ++--- document_ftp/ftpserver/__init__.py | 31 +++++++++++++++--------------- 2 files changed, 18 insertions(+), 18 deletions(-) diff --git a/document_ftp/__init__.py b/document_ftp/__init__.py index 098e6cb7..f5385e0a 100644 --- a/document_ftp/__init__.py +++ b/document_ftp/__init__.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- ############################################################################## -# +# # OpenERP, Open Source Management Solution # Copyright (C) 2004-2010 Tiny SPRL (). # @@ -17,7 +17,7 @@ # 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 . +# along with this program. If not, see . # ############################################################################## @@ -26,4 +26,3 @@ from . import wizard from . import res_config post_load = ftpserver.start_server - diff --git a/document_ftp/ftpserver/__init__.py b/document_ftp/ftpserver/__init__.py index 5d54c3ae..4f4dcb46 100644 --- a/document_ftp/ftpserver/__init__.py +++ b/document_ftp/ftpserver/__init__.py @@ -24,9 +24,9 @@ import threading import logging -import document_ftp.ftpserver.Authorizer -import document_ftp.ftpserver.AbstractedFs -import document_ftp.ftpserver.FtpServer +import document_ftp.ftpserver.authorizer +import document_ftp.ftpserver.abstracted_fs +import document_ftp.ftpserver.ftp_server import openerp from openerp.tools import config @@ -38,8 +38,9 @@ def start_server(): if openerp.multi_process: _logger.info("FTP disabled in multiprocess mode") return - ip_address = ([(s.connect(('8.8.8.8', 80)), s.getsockname()[0], s.close()) - for s in [socket.socket(socket.AF_INET, socket.SOCK_DGRAM)]][0][1]) + ip_address = ([(s.connect(('8.8.8.8', 80)), s.getsockname()[0], s.close()) + for s in [socket.socket(socket.AF_INET, + socket.SOCK_DGRAM)]][0][1]) if not ip_address: ip_address = '127.0.0.1' HOST = config.get('ftp_server_host', str(ip_address)) @@ -52,19 +53,19 @@ def start_server(): class FtpServer(threading.Thread): def run(self): - autho = Authorizer.Authorizer() - FtpServer.FTPHandler.Authorizer = autho - FtpServer.max_cons = 300 - FtpServer.max_cons_per_ip = 50 - FtpServer.FTPHandler.AbstractedFs = AbstractedFs.AbstractedFs + autho = authorizer.Authorizer() + ftp_server.FTPHandler.Authorizer = autho + ftp_server.max_cons = 300 + ftp_server.max_cons_per_ip = 50 + ftp_server.FTPHandler.AbstractedFs = abstracted_fs.AbstractedFs if PASSIVE_PORTS: - FtpServer.FTPHandler.passive_ports = PASSIVE_PORTS + ftp_server.FTPHandler.passive_ports = PASSIVE_PORTS - FtpServer.log = lambda msg: _logger.info(msg) - FtpServer.logline = lambda msg: None - FtpServer.logerror = lambda msg: _logger.error(msg) + ftp_server.log = lambda msg: _logger.info(msg) + ftp_server.logline = lambda msg: None + ftp_server.logerror = lambda msg: _logger.error(msg) - ftpd = FtpServer.FTPServer((HOST, PORT), FtpServer.FTPHandler) + ftpd = ftp_server.FTPServer((HOST, PORT), ftp_server.FTPHandler) ftpd.serve_forever() if HOST.lower() == 'none': From 784c3f5ab146b993637aee444c4c12672258646f Mon Sep 17 00:00:00 2001 From: Carlos Almeida Date: Sat, 17 Oct 2015 02:22:23 -0300 Subject: [PATCH 8/8] [FIX] flake8 fixes --- document_ftp/ftpserver/__init__.py | 6 +++--- document_ftp/wizard/__init__.py | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/document_ftp/ftpserver/__init__.py b/document_ftp/ftpserver/__init__.py index 4f4dcb46..736e168f 100644 --- a/document_ftp/ftpserver/__init__.py +++ b/document_ftp/ftpserver/__init__.py @@ -24,9 +24,9 @@ import threading import logging -import document_ftp.ftpserver.authorizer -import document_ftp.ftpserver.abstracted_fs -import document_ftp.ftpserver.ftp_server +from . import authorizer +from . import abstracted_fs +from . import ftp_server import openerp from openerp.tools import config diff --git a/document_ftp/wizard/__init__.py b/document_ftp/wizard/__init__.py index cd464038..305fc47c 100644 --- a/document_ftp/wizard/__init__.py +++ b/document_ftp/wizard/__init__.py @@ -22,4 +22,4 @@ ############################################################################## from . import ftp_browse -from . import ftp_configuration \ No newline at end of file +from . import ftp_configuration