paperclip-storage-ftp 1.2.6 → 1.2.7

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 52dd89bd22677f466873efbf00464db5185f89e7
4
- data.tar.gz: cc7bf20e77a50e009cf45b2feca86c311d3451aa
3
+ metadata.gz: de0a8d8ea7ed9dc2328bf8c6b9d5ff9eaae40f35
4
+ data.tar.gz: 7da4635ee11a19c26c051e4a5c23229efa00521a
5
5
  SHA512:
6
- metadata.gz: 854d6376410b4c7e7dbed7d0ad0ecc35aa349c2974618aeef3340c4795e0bcc1a237c68dd4dae433a559cfd69b85860afdd63618dd8cf459c91355dbe9d57eb5
7
- data.tar.gz: 7463b456f29a1bc116a4ff64ae34626a90c9682e043f6c3046c613ad7e8d8c6053c833eaee6c466330873e64cc593fccc9beda548a225b052505be4fcb48af5a
6
+ metadata.gz: 7947b3eb940d332765d567b71c6f548e5ed1ef8bdba0654801a46496981d7a8f8d89af5ff3803889192cc3ab2d6be29d200a1937c117942cbaf1e24531a49436
7
+ data.tar.gz: 267ed5502e2193a0acc7357d64b6a33c501eac55fc35101046da96405ea0f1fb01b773c04961e494a8c3d2a2ea6225355f4dcad6555285f1b55dcceb3e300b44
data/.gitignore CHANGED
@@ -24,6 +24,8 @@ tmp
24
24
  vendor/apache-ftpserver/res/ftpd.pid*
25
25
  vendor/apache-ftpserver/res/home/*
26
26
  vendor/apache-ftpserver/res/user1/*
27
+ !vendor/apache-ftpserver/res/user1/.gitkeep
27
28
  vendor/apache-ftpserver/res/user2/*
29
+ !vendor/apache-ftpserver/res/user2/.gitkeep
28
30
  vendor/apache-ftpserver/res/log/*
29
31
  .jrubyrc
@@ -8,3 +8,5 @@ gemfile:
8
8
  matrix:
9
9
  allow_failures:
10
10
  - rvm: jruby-19mode
11
+ sudo: false
12
+ cache: bundler
data/README.md CHANGED
@@ -87,6 +87,10 @@ end
87
87
 
88
88
  ## Changelog
89
89
 
90
+ ### 1.2.7
91
+
92
+ * Reduce number of FTP commands for creating directories [#27](https://github.com/xing/paperclip-storage-ftp/pull/27)
93
+
90
94
  ### 1.2.6
91
95
 
92
96
  * New option `:ftp_keep_empty_directories` to disable the removal of empty parent directories when deleting files (introduced in 1.2.2). See usage example above.
@@ -39,11 +39,14 @@ module Paperclip
39
39
  with_ftp_servers do |servers|
40
40
  servers.map do |server|
41
41
  run_thread do
42
+ write_queue = {}
42
43
  @queued_for_write.each do |style_name, file|
43
44
  remote_path = path(style_name)
44
45
  log("saving ftp://#{server.user}@#{server.host}:#{remote_path}")
45
- server.put_file(file.path, remote_path)
46
+ write_queue[file.path] = remote_path
46
47
  end
48
+
49
+ server.put_files(write_queue)
47
50
  end
48
51
  end.each(&:join)
49
52
  end
@@ -60,10 +60,29 @@ module Paperclip
60
60
 
61
61
  def put_file(local_file_path, remote_file_path)
62
62
  pathname = Pathname.new(remote_file_path)
63
- mkdir_p(pathname.dirname.to_s)
64
63
  connection.putbinaryfile(local_file_path, remote_file_path)
65
64
  end
66
65
 
66
+ def put_files(file_paths)
67
+ tree = directory_tree(file_paths.values)
68
+ mktree(tree)
69
+
70
+ file_paths.each do |local_file_path, remote_file_path|
71
+ put_file(local_file_path, remote_file_path)
72
+ end
73
+ end
74
+
75
+ def directory_tree(file_paths)
76
+ directories = file_paths.map do |path|
77
+ Pathname.new(path).dirname.to_s.split("/").reject(&:empty?)
78
+ end
79
+ tree = Hash.new {|h, k| h[k] = Hash.new(&h.default_proc)}
80
+ directories.each do |directory|
81
+ directory.inject(tree){|h,k| h[k]}
82
+ end
83
+ tree
84
+ end
85
+
67
86
  def delete_file(remote_file_path)
68
87
  connection.delete(remote_file_path)
69
88
  rescue Net::FTPPermError
@@ -79,16 +98,20 @@ module Paperclip
79
98
  # Stop trying to remove parent directories
80
99
  end
81
100
 
82
- def mkdir_p(dirname)
83
- pathname = Pathname.new(dirname)
84
- pathname.descend do |p|
101
+ def mktree(tree, base = "/")
102
+ return unless tree.any?
103
+ list = connection.nlst(base)
104
+ tree.reject{|k,_| list.include?(k)}.each do |directory, sub_directories|
85
105
  begin
86
- connection.mkdir(p.to_s)
106
+ connection.mkdir(base + directory)
87
107
  rescue Net::FTPPermError
88
- # This error can be caused by an existing directory.
89
- # Ignore, and keep on trying to create child directories.
108
+ # This error can be caused by an already existing directory,
109
+ # maybe it was created in the meantime.
90
110
  end
91
111
  end
112
+ tree.each do |directory, sub_directories|
113
+ mktree(sub_directories, base + directory + "/")
114
+ end
92
115
  end
93
116
 
94
117
  private
@@ -12,7 +12,7 @@ Gem::Specification.new do |gem|
12
12
  gem.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
13
13
  gem.name = "paperclip-storage-ftp"
14
14
  gem.require_paths = ["lib"]
15
- gem.version = "1.2.6"
15
+ gem.version = "1.2.7"
16
16
 
17
17
  gem.add_dependency("paperclip")
18
18
 
@@ -137,4 +137,18 @@ describe "paperclip-storage-ftp", :integration => true do
137
137
  end
138
138
  end
139
139
  end
140
+
141
+ context "performance" do
142
+ let(:user) { UserWithOneServerAndDeepPath.new }
143
+ let(:padded_user_id) { user.id.to_s.rjust(3, "0") }
144
+ let(:uploaded_file_deep_path) { FtpServer::USER1_PATH + "/img/user_with_one_server_and_deep_paths/avatars/000/000/#{padded_user_id}/original/avatar.jpg" }
145
+
146
+ it "triggers minimal amount of ftp commands" do
147
+ expect_any_instance_of(Net::FTP).to receive(:nlst).exactly(7).times.and_call_original
148
+ expect_any_instance_of(Net::FTP).to receive(:mkdir).exactly(9).times.and_call_original
149
+ user.avatar = file
150
+ user.save!
151
+ File.exist?(uploaded_file_deep_path).should be true
152
+ end
153
+ end
140
154
  end
@@ -2,6 +2,7 @@ require "spec_helper"
2
2
 
3
3
  describe Paperclip::Storage::Ftp::Server do
4
4
  let(:server) { Paperclip::Storage::Ftp::Server.new }
5
+ let(:connection) { double("connection") }
5
6
 
6
7
  context "initialize" do
7
8
  it "accepts options to initialize attributes" do
@@ -25,7 +26,7 @@ describe Paperclip::Storage::Ftp::Server do
25
26
 
26
27
  context "#file_exists?" do
27
28
  before do
28
- server.stub(:connection).and_return(double("connection"))
29
+ server.stub(:connection).and_return(connection)
29
30
  end
30
31
 
31
32
  it "returns true if the file exists on the server" do
@@ -56,7 +57,7 @@ describe Paperclip::Storage::Ftp::Server do
56
57
 
57
58
  context "#get_file" do
58
59
  before do
59
- server.stub(:connection).and_return(double("connection"))
60
+ server.stub(:connection).and_return(connection)
60
61
  end
61
62
 
62
63
  it "returns the file object" do
@@ -67,19 +68,163 @@ describe Paperclip::Storage::Ftp::Server do
67
68
 
68
69
  context "#put_file" do
69
70
  before do
70
- server.stub(:connection).and_return(double("connection"))
71
+ server.stub(:connection).and_return(connection)
71
72
  end
72
73
 
73
74
  it "stores the file on the server" do
74
- server.should_receive(:mkdir_p).with("/files")
75
75
  server.connection.should_receive(:putbinaryfile).with("/tmp/original.jpg", "/files/original.jpg")
76
76
  server.put_file("/tmp/original.jpg", "/files/original.jpg")
77
77
  end
78
78
  end
79
79
 
80
+ context "#put_files" do
81
+ before do
82
+ server.stub(:connection).and_return(connection)
83
+ end
84
+
85
+ shared_examples "proper handling" do
86
+ it "passes files to #put_file" do
87
+ server.should_receive(:mktree).with(tree)
88
+ server.should_receive(:put_file).with(files.keys.first, files.values.first).ordered
89
+ server.should_receive(:put_file).with(files.keys.last, files.values.last).ordered
90
+ server.put_files files
91
+ end
92
+ end
93
+
94
+ context "common directories" do
95
+ let(:files) do
96
+ {
97
+ "/tmp/foo1.jpg" => "/bar/foo1.jpg",
98
+ "/tmp/foo2.jpg" => "/bar/foo2.jpg"
99
+ }
100
+ end
101
+ let(:tree) { { "bar"=>{} } }
102
+
103
+ include_examples "proper handling"
104
+ end
105
+
106
+ context "no common directories" do
107
+ let(:files) do
108
+ {
109
+ "/tmp/foo1.jpg" => "/bar/foo1.jpg",
110
+ "/tmp/foo2.jpg" => "/baz/foo2.jpg"
111
+ }
112
+ end
113
+ let(:tree) { { "bar"=>{}, "baz"=>{} } }
114
+
115
+ include_examples "proper handling"
116
+ end
117
+
118
+ context "exactly one file" do
119
+ let(:files) do
120
+ { "/tmp/foo1.jpg" => "/bar/foo1.jpg" }
121
+ end
122
+ let(:tree) { { "bar"=>{} } }
123
+
124
+ it "passes file to #put_file" do
125
+ server.should_receive(:mktree).with(tree)
126
+ server.should_receive(:put_file).with(files.keys.first, files.values.first)
127
+ server.put_files files
128
+ end
129
+ end
130
+
131
+ context "no files" do
132
+ let(:files) { {} }
133
+
134
+ it "does not to anything" do
135
+ server.should_not_receive(:put_file)
136
+ connection.should_not_receive(:mkdir)
137
+ server.put_files files
138
+ end
139
+ end
140
+ end
141
+
142
+ context "#directory_tree" do
143
+ let(:files) {
144
+ %w(/foo/bar1.jpg /foo/bar2.jpg /foo/bar/baz.jpg /foo/foo/bar.jpg /foobar/foobar.jpg /root.jpg)
145
+ }
146
+
147
+ it "handles empty file list" do
148
+ expect(server.directory_tree([])).to eq({})
149
+ end
150
+
151
+ it "extracts nested directory structure" do
152
+ expect(server.directory_tree(files)).to eq(
153
+ {
154
+ "foo" => {
155
+ "bar" => {},
156
+ "foo" => {}
157
+ },
158
+ "foobar"=>{}
159
+ }
160
+ )
161
+ end
162
+ end
163
+
164
+ context "#mktree" do
165
+ before do
166
+ server.stub(:connection).and_return(connection)
167
+ end
168
+ let(:tree) do
169
+ {
170
+ "foo"=>{
171
+ "bar"=>{},
172
+ "baz"=>{"qux"=>{}}},
173
+ "foobar"=>{}
174
+ }
175
+ end
176
+
177
+ it "handles empty tree" do
178
+ server.mktree({})
179
+ end
180
+
181
+ context "empty ftp tree" do
182
+ it "creates entire nested tree" do
183
+ connection.should_receive(:nlst).with("/").ordered.and_return([])
184
+ connection.should_receive(:mkdir).with("/foo").ordered
185
+ connection.should_receive(:mkdir).with("/foobar").ordered
186
+ connection.should_receive(:nlst).with("/foo/").ordered.and_return([])
187
+ connection.should_receive(:mkdir).with("/foo/bar").ordered
188
+ connection.should_receive(:mkdir).with("/foo/baz").ordered
189
+ connection.should_receive(:nlst).with("/foo/baz/").ordered.and_return([])
190
+ connection.should_receive(:mkdir).with("/foo/baz/qux").ordered
191
+ server.mktree(tree)
192
+ end
193
+ end
194
+
195
+ context "partially existent ftp tree" do
196
+ it "creates only the missing directories" do
197
+ connection.should_receive(:nlst).with("/").ordered.and_return(["foo"])
198
+ connection.should_receive(:mkdir).with("/foobar").ordered
199
+ connection.should_receive(:nlst).with("/foo/").ordered.and_return(["baz"])
200
+ connection.should_receive(:mkdir).with("/foo/bar").ordered
201
+ connection.should_receive(:nlst).with("/foo/baz/").ordered.and_return(["qux"])
202
+ server.mktree(tree)
203
+ end
204
+ end
205
+
206
+ context "intermittent creation of directories" do
207
+ let(:tree) do
208
+ {
209
+ "foo"=>{},
210
+ "bar"=>{"foobar"=>{}}
211
+ }
212
+ end
213
+
214
+ it "handles Net::FTPPermError" do
215
+ connection.should_receive(:nlst).with("/").ordered.and_return([])
216
+ connection.should_receive(:mkdir).with("/foo").ordered.and_raise(Net::FTPPermError)
217
+ connection.should_receive(:mkdir).with("/bar").ordered.and_raise(Net::FTPPermError)
218
+ connection.should_receive(:nlst).with("/bar/").ordered.and_return([])
219
+ connection.should_receive(:mkdir).with("/bar/foobar").ordered.and_raise(Net::FTPPermError)
220
+ server.mktree(tree)
221
+ end
222
+ end
223
+ end
224
+
80
225
  context "#delete_file" do
81
226
  before do
82
- server.stub(:connection).and_return(double("connection"))
227
+ server.stub(:connection).and_return(connection)
83
228
  end
84
229
 
85
230
  it "deletes the file on the server" do
@@ -96,7 +241,7 @@ describe Paperclip::Storage::Ftp::Server do
96
241
 
97
242
  context "#rmdir_p" do
98
243
  before do
99
- server.stub(:connection).and_return(double("connection"))
244
+ server.stub(:connection).and_return(connection)
100
245
  end
101
246
 
102
247
  it "deletes the directory and all parent directories" do
@@ -119,24 +264,4 @@ describe Paperclip::Storage::Ftp::Server do
119
264
  server.connection.should == ftp
120
265
  end
121
266
  end
122
-
123
- context "mkdir_p" do
124
- before do
125
- server.stub(:connection).and_return(double("connection"))
126
- end
127
-
128
- it "creates the directory and all its parent directories" do
129
- server.connection.should_receive(:mkdir).with("/").ordered
130
- server.connection.should_receive(:mkdir).with("/files").ordered
131
- server.connection.should_receive(:mkdir).with("/files/foo").ordered
132
- server.connection.should_receive(:mkdir).with("/files/foo/bar").ordered
133
- server.mkdir_p("/files/foo/bar")
134
- end
135
-
136
- it "does not stop on Net::FTPPermError" do
137
- server.connection.should_receive(:mkdir).with("/").and_raise(Net::FTPPermError)
138
- server.connection.should_receive(:mkdir).with("/files")
139
- server.mkdir_p("/files")
140
- end
141
- end
142
267
  end
@@ -100,10 +100,13 @@ describe Paperclip::Storage::Ftp do
100
100
  :thumb => thumb_file
101
101
  })
102
102
 
103
- first_server.should_receive(:put_file).with("/tmp/original/foo.jpg", "/files/original/foo.jpg")
104
- first_server.should_receive(:put_file).with("/tmp/thumb/foo.jpg", "/files/thumb/foo.jpg")
105
- second_server.should_receive(:put_file).with("/tmp/original/foo.jpg", "/files/original/foo.jpg")
106
- second_server.should_receive(:put_file).with("/tmp/thumb/foo.jpg", "/files/thumb/foo.jpg")
103
+ write_queue = {
104
+ "/tmp/original/foo.jpg" => "/files/original/foo.jpg",
105
+ "/tmp/thumb/foo.jpg" => "/files/thumb/foo.jpg"
106
+ }
107
+
108
+ first_server.should_receive(:put_files).with(write_queue)
109
+ second_server.should_receive(:put_files).with(write_queue)
107
110
 
108
111
  attachment.should_receive(:with_ftp_servers).and_yield([first_server, second_server])
109
112
 
@@ -88,6 +88,20 @@ class UserWithInvalidPort < UserBase
88
88
  end
89
89
  end
90
90
 
91
+ class UserWithOneServerAndDeepPath < UserBase
92
+ setup_avatar_attachment(avatar_options.merge(
93
+ :path => "/img/:class/:attachment/:id_partition/:style/:filename",
94
+ :ftp_servers => [
95
+ {
96
+ :host => "127.0.0.1",
97
+ :user => "user1",
98
+ :password => "secret1",
99
+ :port => 2121
100
+ }
101
+ ]
102
+ ))
103
+ end
104
+
91
105
  class UserIgnoringFailingConnection < UserWithInvalidPort
92
106
  setup_avatar_attachment(avatar_options.merge(
93
107
  :ftp_ignore_failing_connections => true
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: paperclip-storage-ftp
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.2.6
4
+ version: 1.2.7
5
5
  platform: ruby
6
6
  authors:
7
7
  - Sebastian Röbke
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2015-07-22 00:00:00.000000000 Z
11
+ date: 2015-10-20 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: paperclip
@@ -135,6 +135,8 @@ files:
135
135
  - vendor/apache-ftpserver/res/conf/users.properties
136
136
  - vendor/apache-ftpserver/res/ftp-db.sql
137
137
  - vendor/apache-ftpserver/res/ftpserver.jks
138
+ - vendor/apache-ftpserver/res/user1/.gitkeep
139
+ - vendor/apache-ftpserver/res/user2/.gitkeep
138
140
  homepage: https://github.com/xing/paperclip-storage-ftp
139
141
  licenses:
140
142
  - MIT
@@ -155,7 +157,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
155
157
  version: '0'
156
158
  requirements: []
157
159
  rubyforge_project:
158
- rubygems_version: 2.4.7
160
+ rubygems_version: 2.4.8
159
161
  signing_key:
160
162
  specification_version: 4
161
163
  summary: Allow Paperclip attachments to be stored on FTP servers