activestorage-ftp 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 10f69e9a517b7eb01c78baf94a8cfbde39b60aecb34a2fdf70f6ce2bd862bdad
4
+ data.tar.gz: 22528e4214f7f111ff8291d3b5842bac210c347937531619c44f415df076630a
5
+ SHA512:
6
+ metadata.gz: c71cfe3d8b2bab2cfce28991261b39a2b23fec07f4d77e390edbb5e7a5e7e2e7fa6da0a81e1ab3d51acad32b1329c42d922fb2bc7f3513637f829f9aca5b5157
7
+ data.tar.gz: 6e0fa031fcf8e636788e7fc79783b63a114f685dfb8c37c1d3f9106b034d02bb4ba504bacee83fb3647fee76bf0fefd25d05d46f350fee1bcb1e5ef292ebac00
@@ -0,0 +1,3 @@
1
+ ## 0.1.0
2
+
3
+ - First release.
@@ -0,0 +1,39 @@
1
+ # ActiveStorage FTP Service
2
+
3
+ FTP Active Storage service.
4
+
5
+ ## Installation
6
+
7
+ Add this line to your application's Gemfile:
8
+
9
+ ```ruby
10
+ gem 'activestorage-ftp'
11
+ ```
12
+
13
+ And then execute:
14
+
15
+ ```bash
16
+ $ bundle install
17
+ ```
18
+
19
+ ## Usage
20
+
21
+ config/storage.yml
22
+
23
+ ```yml
24
+ production:
25
+ service: Ftp
26
+ ftp_host: <%= ENV["FTP_HOST"] %>
27
+ ftp_port: <%= ENV["FTP_PORT"] %>
28
+ ftp_user: <%= ENV["FTP_USER"] %>
29
+ ftp_passwd: <%= ENV["FTP_PASSWD"] %>
30
+ ftp_folder: <%= ENV["FTP_FOLDER"] %>
31
+ ftp_url: <%= ENV["FTP_URL"] %>
32
+ # optional
33
+ ftp_passive: true
34
+ ftp_chmod: 0600
35
+ ```
36
+
37
+ ## License
38
+
39
+ The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
@@ -0,0 +1,211 @@
1
+ # frozen_string_literal: true
2
+ require "active_storage_ftp/ex_ftp"
3
+ require "active_storage_ftp/ex_ftptls"
4
+ require "digest/md5"
5
+ require "active_support/core_ext/numeric/bytes"
6
+
7
+ module ActiveStorage
8
+
9
+ class Service::FtpService < Service
10
+
11
+ def initialize(**config)
12
+ @config = config
13
+ end
14
+
15
+ def upload(key, io, checksum: nil, **)
16
+ instrument :upload, key: key, checksum: checksum do
17
+ connection do |ftp|
18
+ path_for(key).tap do |path|
19
+ ftp.mkdir_p(::File.dirname path)
20
+ ftp.chdir(::File.dirname path)
21
+ ftp.storbinary("STOR #{File.basename(key)}", io, Net::FTP::DEFAULT_BLOCKSIZE)
22
+ if ftp_chmod
23
+ ftp.sendcmd("SITE CHMOD #{ftp_chmod.to_s(8)} #{path_for(key)}")
24
+ end
25
+ end
26
+ end
27
+ ensure_integrity_of(key, checksum) if checksum
28
+ end
29
+ end
30
+
31
+ def download(key)
32
+ if block_given?
33
+ instrument :streaming_download, key: key do
34
+ open(http_url_for(key)) do |file|
35
+ while data = file.read(64.kilobytes)
36
+ yield data
37
+ end
38
+ end
39
+ end
40
+ else
41
+ instrument :download, key: key do
42
+ open(http_url_for(key)) do |file|
43
+ file.read
44
+ end
45
+ end
46
+ end
47
+ end
48
+
49
+ def download_chunk(key, range)
50
+ instrument :download_chunk, key: key, range: range do
51
+ open(http_url_for(key)) do |file|
52
+ file.seek range.begin
53
+ file.read range.size
54
+ end
55
+ end
56
+ end
57
+
58
+ def delete(key)
59
+ instrument :delete, key: key do
60
+ begin
61
+ connection do |ftp|
62
+ ftp.chdir(::File.dirname path_for(key))
63
+ ftp.delete(::File.basename path_for(key))
64
+ end
65
+ rescue
66
+ # Ignore files already deleted
67
+ end
68
+ end
69
+ end
70
+
71
+ def delete_prefixed(prefix)
72
+ instrument :delete_prefixed, prefix: prefix do
73
+ connection do |ftp|
74
+ ftp.chdir(path_for(prefix))
75
+ ftp.list.each do |file|
76
+ ftp.delete(file.split.last)
77
+ end
78
+ end
79
+ end
80
+ end
81
+
82
+ def exist?(key)
83
+ instrument :exist, key: key do |payload|
84
+ response = request_head(key)
85
+ answer = response.code.to_i == 200
86
+ payload[:exist] = answer
87
+ answer
88
+ end
89
+ end
90
+
91
+ def url(key, expires_in:, filename:, disposition:, content_type:)
92
+ instrument :url, key: key do |payload|
93
+ generated_url = http_url_for(key)
94
+ payload[:url] = generated_url
95
+ generated_url
96
+ end
97
+ end
98
+
99
+ def url_for_direct_upload(key, expires_in:, content_type:, content_length:, checksum:)
100
+ instrument :url, key: key do |payload|
101
+ verified_token_with_expiration = ActiveStorage.verifier.generate(
102
+ {
103
+ key: key,
104
+ content_type: content_type,
105
+ content_length: content_length,
106
+ checksum: checksum
107
+ },
108
+ {expires_in: expires_in,
109
+ purpose: :blob_token}
110
+ )
111
+
112
+ generated_url = url_helpers.update_rails_disk_service_url(verified_token_with_expiration, host: current_host)
113
+ payload[:url] = generated_url
114
+ generated_url
115
+
116
+ end
117
+ end
118
+
119
+ def headers_for_direct_upload(key, content_type:, **)
120
+ {"Content-Type" => content_type}
121
+ end
122
+
123
+ def path_for(key) #:nodoc:
124
+ File.join ftp_folder, folder_for(key), key
125
+ end
126
+
127
+ private
128
+
129
+ attr_reader :config
130
+
131
+ def folder_for(key)
132
+ [key[0..1], key[2..3]].join("/")
133
+ end
134
+
135
+ def ensure_integrity_of(key, checksum)
136
+ response = request_head(key)
137
+ unless "#{response['Content-MD5']}==" == checksum
138
+ delete key
139
+ raise ActiveStorage::IntegrityError
140
+ end
141
+ end
142
+
143
+ def url_helpers
144
+ @url_helpers ||= Rails.application.routes.url_helpers
145
+ end
146
+
147
+ def current_host
148
+ ActiveStorage::Current.host
149
+ end
150
+
151
+ def request_head(key)
152
+ uri = URI(http_url_for(key))
153
+ request = Net::HTTP.new(uri.host, uri.port)
154
+ request.use_ssl = uri.scheme == 'https'
155
+ request.request_head(uri.path)
156
+ end
157
+
158
+ def http_url_for(key)
159
+ ([ftp_url, folder_for(key), key].join('/'))
160
+ end
161
+
162
+ def inferred_content_type
163
+ SanitizedFile.new(path).content_type
164
+ end
165
+
166
+ def ftp_host
167
+ config.fetch(:ftp_host)
168
+ end
169
+
170
+ def ftp_port
171
+ config.fetch(:ftp_port)
172
+ end
173
+
174
+ def ftp_user
175
+ config.fetch(:ftp_user)
176
+ end
177
+
178
+ def ftp_passwd
179
+ config.fetch(:ftp_passwd)
180
+ end
181
+
182
+ def ftp_folder
183
+ config.fetch(:ftp_folder)
184
+ end
185
+
186
+ def ftp_url
187
+ config.fetch(:ftp_url)
188
+ end
189
+
190
+ def ftp_passive
191
+ config.fetch(:ftp_passive)
192
+ end
193
+
194
+ def ftp_chmod
195
+ config.fetch(:ftp_chmod, 0600)
196
+ end
197
+
198
+ def connection
199
+ ftp = ExFTP.new
200
+ ftp.connect(ftp_host, ftp_port)
201
+ begin
202
+ ftp.passive = ftp_passive
203
+ ftp.login(ftp_user, ftp_passwd)
204
+ yield ftp
205
+ ensure
206
+ ftp.quit
207
+ end
208
+ end
209
+
210
+ end
211
+ end
@@ -0,0 +1,25 @@
1
+ require 'net/ftp'
2
+
3
+ class ExFTP < Net::FTP
4
+ def mkdir_p(dir)
5
+ parts = dir.split("/")
6
+ if parts.first == "~"
7
+ growing_path = ""
8
+ else
9
+ growing_path = "/"
10
+ end
11
+ for part in parts
12
+ next if part == ""
13
+ if growing_path == ""
14
+ growing_path = part
15
+ else
16
+ growing_path = File.join(growing_path, part)
17
+ end
18
+ begin
19
+ mkdir(growing_path)
20
+ chdir(growing_path)
21
+ rescue Net::FTPPermError, Net::FTPTempError => e
22
+ end
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,25 @@
1
+ require 'double_bag_ftps'
2
+
3
+ class ExFTPTLS < DoubleBagFTPS
4
+ def mkdir_p(dir)
5
+ parts = dir.split("/")
6
+ if parts.first == "~"
7
+ growing_path = ""
8
+ else
9
+ growing_path = "/"
10
+ end
11
+ for part in parts
12
+ next if part == ""
13
+ if growing_path == ""
14
+ growing_path = part
15
+ else
16
+ growing_path = File.join(growing_path, part)
17
+ end
18
+ begin
19
+ mkdir(growing_path)
20
+ chdir(growing_path)
21
+ rescue Net::FTPPermError, Net::FTPTempError => e
22
+ end
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,4 @@
1
+ module ActiveStorageFtp
2
+ class Railtie < ::Rails::Railtie
3
+ end
4
+ end
@@ -0,0 +1,3 @@
1
+ module ActiveStorageFtp
2
+ VERSION = '0.1.0'
3
+ end
@@ -0,0 +1,5 @@
1
+ # require "active_storage_ftp/railtie"
2
+
3
+ module ActiveStorageFtp
4
+ # Your code goes here...
5
+ end
metadata ADDED
@@ -0,0 +1,99 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: activestorage-ftp
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Alexey Gordienko
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2019-05-31 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: rails
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - ">="
18
+ - !ruby/object:Gem::Version
19
+ version: 5.2.0
20
+ - - "~>"
21
+ - !ruby/object:Gem::Version
22
+ version: '5.2'
23
+ type: :runtime
24
+ prerelease: false
25
+ version_requirements: !ruby/object:Gem::Requirement
26
+ requirements:
27
+ - - ">="
28
+ - !ruby/object:Gem::Version
29
+ version: 5.2.0
30
+ - - "~>"
31
+ - !ruby/object:Gem::Version
32
+ version: '5.2'
33
+ - !ruby/object:Gem::Dependency
34
+ name: net-sftp
35
+ requirement: !ruby/object:Gem::Requirement
36
+ requirements:
37
+ - - "~>"
38
+ - !ruby/object:Gem::Version
39
+ version: 2.1.2
40
+ type: :runtime
41
+ prerelease: false
42
+ version_requirements: !ruby/object:Gem::Requirement
43
+ requirements:
44
+ - - "~>"
45
+ - !ruby/object:Gem::Version
46
+ version: 2.1.2
47
+ - !ruby/object:Gem::Dependency
48
+ name: double-bag-ftps
49
+ requirement: !ruby/object:Gem::Requirement
50
+ requirements:
51
+ - - '='
52
+ - !ruby/object:Gem::Version
53
+ version: 0.1.3
54
+ type: :runtime
55
+ prerelease: false
56
+ version_requirements: !ruby/object:Gem::Requirement
57
+ requirements:
58
+ - - '='
59
+ - !ruby/object:Gem::Version
60
+ version: 0.1.3
61
+ description: FTP Active Storage service.
62
+ email: alx@anadyr.org
63
+ executables: []
64
+ extensions: []
65
+ extra_rdoc_files: []
66
+ files:
67
+ - CHANGELOG.md
68
+ - README.md
69
+ - lib/active_storage/service/ftp_service.rb
70
+ - lib/active_storage_ftp/ex_ftp.rb
71
+ - lib/active_storage_ftp/ex_ftptls.rb
72
+ - lib/active_storage_ftp/railtie.rb
73
+ - lib/active_storage_ftp/version.rb
74
+ - lib/activestorage-ftp.rb
75
+ homepage: https://github.com/gordienko/activestorage-ftp
76
+ licenses:
77
+ - MIT
78
+ metadata: {}
79
+ post_install_message:
80
+ rdoc_options: []
81
+ require_paths:
82
+ - lib
83
+ required_ruby_version: !ruby/object:Gem::Requirement
84
+ requirements:
85
+ - - ">="
86
+ - !ruby/object:Gem::Version
87
+ version: '0'
88
+ required_rubygems_version: !ruby/object:Gem::Requirement
89
+ requirements:
90
+ - - ">="
91
+ - !ruby/object:Gem::Version
92
+ version: '0'
93
+ requirements: []
94
+ rubyforge_project:
95
+ rubygems_version: 2.7.9
96
+ signing_key:
97
+ specification_version: 4
98
+ summary: FTP Active Storage service
99
+ test_files: []