A library that provides an Integrant key for managing objects in a FTP server. This library is a wrapper around miner/clj-ftp.
To use this library add the following key to your configuration:
:dev.gethop.object-storage/ftp
This key expects a configuration map with one unique mandatory key, plus other optional one. These are the mandatory keys:
:ftp-uri
: FTP server URI to connect to.
These are the optional keys:
:ftp-options
: a map of options for configuring the FTP client.- Since this library wraps around miner/clj-ftp all configuration options found there is also valid here.
Key initialization returns a FTP
record that can be used to perform the FTP operations described below.
Basic configuration:
:dev.gethop.object-storage/ftp
{:ftp-uri #duct/env ["FTP_URI" Str :or "ftp://user:mypassword@my-ftp-server"]}
Configuration with custom FTP client configuration:
:dev.gethop.object-storage/ftp
{:ftp-uri #duct/env ["FTP_URI" Str :or "ftp://user:mypassword@my-ftp-server"]
:ftp-options {:default-timeout-ms 30000
:security-mode :explicit
:local-data-connection-mode :active
:file-type :binary}]}
If you are using the library as part of a Duct-based project, adding any of the previous configurations to your config.edn
file will perform all the steps necessary to initialize the key and return a FTP
record for the associated configuration. In order to show a few interactive usages of the library, we will do all the steps manually in the REPL.
First we require the relevant namespaces:
user> (require '[integrant.core :as ig]
'[dev.gethop.object-storage.core :as core])
nil
user>
Next we create the configuration var holding the FTP integration configuration details:
user> (def config {:ftp-uri #duct/env ["FTP_URI" Str :or "ftp://user:mypassword@my-ftp-server"]})
#'user/config
user>
Now that we have all pieces in place, we can initialize the :dev.gethop.object-storage/ftp
Integrant key to get a FTP
record. As we are doing all this from the REPL, we have to manually require dev.gethop.object-storage.ftp
namespace, where the init-key
multimethod for that key is defined (this is not needed when Duct takes care of initializing the key as part of the application start up):
user> (require '[dev.gethop.object-storage.ftp :as ftp])
nil
user>
And we finally initialize the key with the configuration defined above, to get our FTP
record:
user> (def ftp-record (->
config
(->> (ig/init-key :dev.gethop.object-storage/ftp))))
#'user/ftp-record
user> ftp-record
#dev.gethop.object-storage.ftp.FTP{{:ftp-uri "ftp://user:mypassword@my-ftp-server"
:ftp-options nil}
user>
Now that we have our FTP
record, we are ready to use the methods defined by the protocol ObjectStorage.
(get-object ftp-record object-id)
- description: Retrieves an object from a FTP server
- parameters:
ftp-record
: aFTP
recordobject-id
: the object
- return value: a map with the following keys
:success?
: boolean stating if the operation was successful or not:object
: If the operation was successful, this key contains an InputStream-compatible stream, on the desired object. Note that the InputStream returned by get-object should be closed (.e.g, via slurp).error-details
: a map with additional details on the problem encountered while trying to retrieve the object.
Lets see an example. First a successful invocation:
user> (core/get-object ftp-record "test1.txt")
{:success? true,
:object #object[java.io.BufferedInputStream 0x7cc725c "java.io.BufferedInputStream@7cc725c"]}
Invocation with a non-existing object-id:
user> (core/get-object ftp-record "i-dont-exist")
{:success? false}
(put-object ftp-record object-id object)
- description: uploads an object to the FTP server with
object-id
as its filename - parameters:
ftp-record
: aFTP
recordobject-id
: the object identifier in the FTP server in other words the filenameobject
: the object we want to upload can be a file or an input stream.
- return value: a map with the following keys
:success?
: boolean stating if the operation was successful or noterror-details
: a map with additional details on the problem encountered while trying to retrieve the object.
Example:
user> (core/put-object ftp-record "test2.txt" (io/file "files/test2.txt"))
{:success? true}
Invocation with a non-existing object:
user> (core/put-object ftp-record "test2.txt" (io/file "files/i-dont-exist.txt"))
{:success? false,
:error-details "files/i-dont-exist.txt (No such file or directory)"}
(delete-object ftp-record object-id object)
- description: deletes an object with
object-id
in the FTP server. - parameters:
ftp-record
: aFTP
recordobject-id
: the object identifier in the FTP server in other words the filename
- return value: a map with the following keys
:success?
: boolean stating if the operation was successful or noterror-details
: a map with additional details on the problem encountered while trying to retrieve the object.
Example:
user> (core/delete-object ftp-record "test2.txt")
{:success? true}
Invocation with a non-existing object:
user> (core/delete-object ftp-record "i-dont-exist.txt")
{:success? false}
(rename-object ftp-record object-id new-object-id)
- description: renames an object with
object-id
to thenew-object-id
in the FTP server. - parameters:
ftp-record
: aFTP
recordobject-id
: the object identifier in the FTP server in other words the filenamenew-object-id
: the new object identifier
- return value: a map with the following keys
:success?
: boolean stating if the operation was successful or noterror-details
: a map with additional details on the problem encountered while trying to retrieve the object.
Example:
user> (core/rename-object ftp-record "test1.txt" "new-test1.txt")
{:success? true}
Invocation with a non-existing object:
user> (core/rename-object ftp-record "i-dont-exist.txt" "new-i-dont-exist.txt")
{:success? false}
(list-objects ftp-record parent-object-id)
- description: lists all objects under the
parent-object-id
- parameters:
ftp-record
: aFTP
recordparent-object-id
: the identifier of a folder within the FTP server.
- return value: a map with the following keys
:success?
: boolean stating if the operation was successful or not:objects
: A collection of maps each representing an object with the following attributes:object-id
: object's identifierlast-modified
: an instant objectsize
: size in bytestype
: the type of the object that can be::file
,:directory
,:symbolic-link
or:unknown
error-details
: a map with additional details on the problem encountered while trying to retrieve the object.
Example:
user> (core/list-objects ftp-record "")
{:success? true,
:objects
({:object-id "/files/folder-1/file-1",
:last-modified #inst "2019-12-05T19:06:00.000+01:00",
:size 15,
:type :file}
{:object-id "/files/folder-1/folder-1-1",
:last-modified #inst "2020-02-28T10:13:00.000+01:00",
:size 4096,
:type :directory}
{:object-id "/files/folder-1/folder-1-1/file-2",
:last-modified #inst "2020-02-28T10:13:00.000+01:00",
:size 6,
:type :file}
{:object-id "/files/folder-1/folder-1-1/file-3",
:last-modified #inst "2020-02-28T09:52:00.000+01:00",
:size 8,
:type :file})}
The library includes self-contained units tests, including some integration tests that depend on a FTP server. Those tests have the ^:integration
metadata keyword associated to them, so you can exclude them from our unit tests runs.
If you want to run the integration tests, the following environment variable is needed:
TEST_OBJECT_STORAGE_FTP_URI
: The uri of the FTP server used the integration tests. Be aware that the tests will leave trash files in the server. It may corrupt or delete files, so don't execute it against a real server.
Copyright (c) 2024 Biotz, SL.
This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0. If a copy of the MPL was not distributed with this file, You can obtain one at https://mozilla.org/MPL/2.0/