Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions requirements.txt
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
paramiko
pyftpdlib
python-slugify>=3.0.2
vcrpy>=2.1.1
cerberus
Expand Down
1 change: 1 addition & 0 deletions setup/storage_backend_ftp/odoo/addons/storage_backend_ftp
6 changes: 6 additions & 0 deletions setup/storage_backend_ftp/setup.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
import setuptools

setuptools.setup(
setup_requires=['setuptools-odoo'],
odoo_addon=True,
)
73 changes: 73 additions & 0 deletions storage_backend_ftp/README.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
===================
Storage Backend FTP
===================

.. !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
!! This file is generated by oca-gen-addon-readme !!
!! changes will be overwritten. !!
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!

.. |badge1| image:: https://img.shields.io/badge/maturity-Beta-yellow.png
:target: https://odoo-community.org/page/development-status
:alt: Beta
.. |badge2| image:: https://img.shields.io/badge/licence-LGPL--3-blue.png
:target: http://www.gnu.org/licenses/lgpl-3.0-standalone.html
:alt: License: LGPL-3
.. |badge3| image:: https://img.shields.io/badge/github-OCA%2Fstorage-lightgray.png?logo=github
:target: https://github.com/OCA/storage/tree/13.0/storage_backend_ftp
:alt: OCA/storage
.. |badge4| image:: https://img.shields.io/badge/weblate-Translate%20me-F47D42.png
:target: https://translation.odoo-community.org/projects/storage-13-0/storage-13-0-storage_backend_ftp
:alt: Translate me on Weblate
.. |badge5| image:: https://img.shields.io/badge/runbot-Try%20me-875A7B.png
:target: https://runbot.odoo-community.org/runbot/275/13.0
:alt: Try me on Runbot

|badge1| |badge2| |badge3| |badge4| |badge5|

Add FTP as storage backend

**Table of contents**

.. contents::
:local:

Bug Tracker
===========

Bugs are tracked on `GitHub Issues <https://github.com/OCA/storage/issues>`_.
In case of trouble, please check there if your issue has already been reported.
If you spotted it first, help us smashing it by providing a detailed and welcomed
`feedback <https://github.com/OCA/storage/issues/new?body=module:%20storage_backend_ftp%0Aversion:%2013.0%0A%0A**Steps%20to%20reproduce**%0A-%20...%0A%0A**Current%20behavior**%0A%0A**Expected%20behavior**>`_.

Do not contact contributors directly about support or help with technical issues.

Credits
=======

Authors
~~~~~~~

* Acsone SA/NV

Contributors
~~~~~~~~~~~~

* François Honoré <francois.honore@acsone.eu>

Maintainers
~~~~~~~~~~~

This module is maintained by the OCA.

.. image:: https://odoo-community.org/logo.png
:alt: Odoo Community Association
:target: https://odoo-community.org

OCA, or the Odoo Community Association, is a nonprofit organization whose
mission is to support the collaborative development of Odoo features and
promote its widespread use.

This module is part of the `OCA/storage <https://github.com/OCA/storage/tree/13.0/storage_backend_ftp>`_ project on GitHub.

You are welcome to contribute. To learn how please visit https://odoo-community.org/page/Contribute.
2 changes: 2 additions & 0 deletions storage_backend_ftp/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
from . import models
from . import components
14 changes: 14 additions & 0 deletions storage_backend_ftp/__manifest__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
# Copyright 2021 ACSONE SA/NV (<http://acsone.eu>)
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html).
{
"name": "Storage Backend FTP",
"summary": "Implement FTP Storage",
"version": "13.0.1.0.0",
"category": "Storage",
"website": "https://github.com/OCA/storage",
"author": " Acsone SA/NV,Odoo Community Association (OCA)",
"license": "LGPL-3",
"external_dependencies": {"python": ["pyftpdlib"]},
"depends": ["storage_backend"],
"data": ["views/backend_storage_view.xml"],
}
1 change: 1 addition & 0 deletions storage_backend_ftp/components/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
from . import ftp_adapter
148 changes: 148 additions & 0 deletions storage_backend_ftp/components/ftp_adapter.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,148 @@
# Copyright 2021 ACSONE SA/NV (<http://acsone.eu>)
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html).
import errno
import io
import logging
import os
import ssl
from contextlib import contextmanager
from io import BytesIO

from odoo.addons.component.core import Component

_logger = logging.getLogger(__name__)

try:
import ftplib
except ImportError as err: # pragma: no cover
_logger.debug(err)


def ftp_mkdirs(client, path):
try:
client.mkd(path)
except IOError as e:
if e.errno == errno.ENOENT and path:
ftp_mkdirs(client, os.path.dirname(path))
client.mkd(path)
else:
raise # pragma: no cover


@contextmanager
def ftp(backend):
params = {}
security = None
if backend.ftp_encryption == "ftp":
ftp = ftplib.FTP
elif backend.ftp_encryption == "tls":
ftp = ftplib.FTP_TLS
# Due to a bug into between ftplib and ssl, this part (about ssl) might not work!
# https://bugs.python.org/issue31727
security = None
if backend.ftp_security == "tls":
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

pls use a dict 😉

security = ssl.PROTOCOL_TLS
elif backend.ftp_security == "tlsv1":
security = ssl.PROTOCOL_TLSv1
elif backend.ftp_security == "tlsv1_1":
security = ssl.PROTOCOL_TLSv1_1
elif backend.ftp_security == "tlsv1_2":
security = ssl.PROTOCOL_TLSv1_2
elif backend.ftp_security == "sslv2":
security = ssl.PROTOCOL_SSLv2
elif backend.ftp_security == "sslv23":
security = ssl.PROTOCOL_SSLv23
elif backend.ftp_security == "sslv3":
security = ssl.PROTOCOL_SSLv3
if security:
ctx = ssl._create_stdlib_context(security)
params.update({"context": ctx})
else:
raise NotImplementedError()
with ftp(**params) as client:
client.connect(host=backend.ftp_server, port=backend.ftp_port)
if security:
client.auth()
client.login(backend.ftp_login, backend.ftp_password)
if security:
client.ssl_version = security
client.prot_p()
if backend.ftp_passive:
client.set_pasv(True)
yield client


class FTPStorageBackendAdapter(Component):
_name = "ftp.adapter"
_inherit = "base.storage.adapter"
_usage = "ftp"

def add(self, relative_path, data, **kwargs):
with ftp(self.collection) as client:
full_path = self._fullpath(relative_path)
dirname = os.path.dirname(full_path)
if dirname:
try:
client.cwd(dirname)
except IOError as e:
if e.errno == errno.ENOENT:
ftp_mkdirs(client, dirname)
else:
raise # pragma: no cover
with io.BytesIO(data) as tmp_file:
try:
client.storbinary("STOR " + full_path, tmp_file)
except ftplib.Error as e:
raise ValueError(repr(e))
except OSError as e:
raise ValueError(repr(e))

def get(self, relative_path, **kwargs):
full_path = self._fullpath(relative_path)
with ftp(self.collection) as client, BytesIO() as buff:
try:
client.retrbinary("RETR " + full_path, buff.write)
data = buff.getvalue()
except ftplib.Error as e:
raise FileNotFoundError(repr(e))
return data

def list(self, relative_path):
full_path = self._fullpath(relative_path)
with ftp(self.collection) as client:
try:
return client.nlst(full_path)
except IOError as e:
if e.errno == errno.ENOENT:
# The path do not exist return an empty list
return []
else:
raise # pragma: no cover

def move_files(self, files, destination_path):
_logger.debug("mv %s %s", files, destination_path)
with ftp(self.collection) as client:
for ftp_file in files:
dest_file_path = os.path.join(
destination_path, os.path.basename(ftp_file)
)
# Remove existing file at the destination path (an error is raised
# otherwise)
result = []
try:
result = client.nlst(dest_file_path)
except ftplib.Error:
_logger.debug("destination %s is free", dest_file_path)
if result:
client.delete(dest_file_path)
# Move the file
client.rename(ftp_file, dest_file_path)

def delete(self, relative_path):
full_path = self._fullpath(relative_path)
with ftp(self.collection) as client:
return client.delete(full_path)

def validate_config(self):
with ftp(self.collection) as client:
client.getwelcome()
1 change: 1 addition & 0 deletions storage_backend_ftp/models/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
from . import storage_backend
49 changes: 49 additions & 0 deletions storage_backend_ftp/models/storage_backend.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
# Copyright 2021 ACSONE SA/NV (<http://acsone.eu>)
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html).
from odoo import fields, models


class StorageBackend(models.Model):
_inherit = "storage.backend"

backend_type = fields.Selection(selection_add=[("ftp", "FTP")])
ftp_server = fields.Char(string="FTP Host")
ftp_port = fields.Integer(string="FTP Port", default=21)
ftp_encryption = fields.Selection(
string="FTP Encryption method",
selection=[("ftp", "FTP"), ("tls", "FTP over TLS")],
default="ftp",
required=True,
)
ftp_security = fields.Selection(
string="FTP security option",
selection=[
("none", "None"),
("tlsv1", "TLS"),
("tlsv1_1", "TLSv1_1"),
("tlsv1_2", "TLSv1_2"),
("sslv2", "SSLv2"),
("sslv23", "SSLv23"),
("sslv3", "SSLv3"),
],
required=True,
)
ftp_login = fields.Char(string="FTP Login", help="Login to connect to ftp server")
ftp_password = fields.Char(string="FTP Password")
ftp_passive = fields.Boolean(string="FTP Passive", default=False)

@property
def _server_env_fields(self):
env_fields = super()._server_env_fields
env_fields.update(
{
"ftp_password": {},
"ftp_login": {},
"ftp_server": {},
"ftp_port": {},
"ftp_encryption": {},
"ftp_security": {},
"ftp_passive": {},
}
)
return env_fields
2 changes: 2 additions & 0 deletions storage_backend_ftp/readme/CONTRIBUTORS.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
* François Honoré <francois.honore@acsone.eu>
* Lois Rilo <lois.rilo@forgeflow.com>
1 change: 1 addition & 0 deletions storage_backend_ftp/readme/DESCRIPTION.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Add FTP as storage backend
Loading