paperclip-storage-ftp 1.0.0.rc1

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.
data/.gitignore ADDED
@@ -0,0 +1,19 @@
1
+ *.gem
2
+ *.rbc
3
+ .bundle
4
+ .config
5
+ .yardoc
6
+ Gemfile.lock
7
+ InstalledFiles
8
+ _yardoc
9
+ coverage
10
+ doc/
11
+ lib/bundler/man
12
+ pkg
13
+ rdoc
14
+ spec/reports
15
+ test/tmp
16
+ test/version_tmp
17
+ tmp
18
+ .rspec
19
+ .rvmrc
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in paperclip-multistorage.gemspec
4
+ gemspec
data/LICENSE ADDED
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2012 XING AG
2
+
3
+ MIT License
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining
6
+ a copy of this software and associated documentation files (the
7
+ "Software"), to deal in the Software without restriction, including
8
+ without limitation the rights to use, copy, modify, merge, publish,
9
+ distribute, sublicense, and/or sell copies of the Software, and to
10
+ permit persons to whom the Software is furnished to do so, subject to
11
+ the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be
14
+ included in all copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,64 @@
1
+ # FTP Storage for Paperclip
2
+
3
+ Allow [Paperclip](https://github.com/thoughtbot/paperclip) attachments
4
+ to be stored on FTP servers.
5
+
6
+ ## Installation
7
+
8
+ Add this line to your application's Gemfile:
9
+
10
+ gem 'paperclip-storage-ftp'
11
+
12
+ And then execute:
13
+
14
+ $ bundle
15
+
16
+ Or install it yourself as:
17
+
18
+ $ gem install paperclip-storage-ftp
19
+
20
+ ## Usage
21
+
22
+ Somewhere in your code:
23
+
24
+ require "paperclip/storage/ftp"
25
+
26
+ In your model:
27
+
28
+ class User < ActiveRecord::Base
29
+ has_attached_file :avatar,
30
+
31
+ # Choose the FTP storage backend
32
+ :storage => :ftp,
33
+
34
+ # Set where to store the file on the FTP server(s).
35
+ # This supports Paperclip::Interpolations.
36
+ :path => "/path_on_ftp_server/:attachment/:id/:style/:filename"
37
+
38
+ # The full URL of where the attachment is publicly accessible.
39
+ # This supports Paperclip::Interpolations.
40
+ :url => "/url_prefix/:attachment/:id/:style/:filename"
41
+
42
+ # The list of FTP servers to use
43
+ :ftp_servers => [
44
+ {
45
+ :host => "ftp1.example.com",
46
+ :user => "foo",
47
+ :password => "bar"
48
+ },
49
+ # Add more servers if needed
50
+ {
51
+ :host => "ftp2.example.com",
52
+ :user => "foo",
53
+ :password => "bar"
54
+ }
55
+ ]
56
+ end
57
+
58
+ ## Contributing
59
+
60
+ 1. Fork it
61
+ 2. Create your feature branch (`git checkout -b my-new-feature`)
62
+ 3. Commit your changes (`git commit -am 'Added some feature'`)
63
+ 4. Push to the branch (`git push origin my-new-feature`)
64
+ 5. Create new Pull Request
data/Rakefile ADDED
@@ -0,0 +1,9 @@
1
+ #!/usr/bin/env rake
2
+ require "bundler/gem_tasks"
3
+ require "rspec/core/rake_task"
4
+
5
+ RSpec::Core::RakeTask.new do |t|
6
+ t.rspec_opts = "--color"
7
+ end
8
+
9
+ task :default => :spec
@@ -0,0 +1,78 @@
1
+ require "paperclip"
2
+ require "active_support/all"
3
+ require "tempfile"
4
+
5
+ require "paperclip/storage/ftp/server"
6
+
7
+ module Paperclip
8
+ module Storage
9
+ module Ftp
10
+ def exists?(style_name = default_style)
11
+ if original_filename
12
+ primary_ftp_server.file_exists?(path(style_name))
13
+ else
14
+ false
15
+ end
16
+ end
17
+
18
+ def to_file(style_name = default_style)
19
+ if @queued_for_write[style_name]
20
+ @queued_for_write[style_name].rewind
21
+ @queued_for_write[style_name]
22
+ else
23
+ filename = path(style_name)
24
+ extname = File.extname(filename)
25
+ basename = File.basename(filename, extname)
26
+ file = Tempfile.new([basename, extname])
27
+ primary_ftp_server.get_file(filename, file.path)
28
+ file.rewind
29
+ file
30
+ end
31
+ end
32
+
33
+ def flush_writes #:nodoc:
34
+ @queued_for_write.each do |style_name, file|
35
+ file.close
36
+ ftp_servers.each do |server|
37
+ remote_path = path(style_name)
38
+ log("saving ftp://#{server.user}@#{server.host}:#{remote_path}")
39
+ server.put_file(file.path, remote_path)
40
+ end
41
+ end
42
+
43
+ after_flush_writes # allows attachment to clean up temp files
44
+
45
+ @queued_for_write = {}
46
+ end
47
+
48
+ def flush_deletes
49
+ @queued_for_delete.each do |path|
50
+ ftp_servers.each do |server|
51
+ log("deleting ftp://#{server.user}@#{server.host}:#{path}")
52
+ server.delete_file(path)
53
+ end
54
+ end
55
+ @queued_for_delete = []
56
+ end
57
+
58
+ def ftp_servers
59
+ @ftp_servers ||= begin
60
+ ftp_servers = []
61
+ @options[:ftp_servers].each do |config|
62
+ server = Server.new(
63
+ :host => config[:host],
64
+ :user => config[:user],
65
+ :password => config[:password]
66
+ )
67
+ ftp_servers << server
68
+ end
69
+ ftp_servers
70
+ end
71
+ end
72
+
73
+ def primary_ftp_server
74
+ ftp_servers.first
75
+ end
76
+ end
77
+ end
78
+ end
@@ -0,0 +1,54 @@
1
+ require "pathname"
2
+ require "net/ftp"
3
+
4
+ module Paperclip
5
+ module Storage
6
+ module Ftp
7
+ class Server
8
+ attr_accessor :host, :user, :password
9
+ attr_writer :connection
10
+
11
+ def initialize(options = {})
12
+ options.each do |k,v|
13
+ send("#{k}=", v)
14
+ end
15
+ end
16
+
17
+ def file_exists?(path)
18
+ pathname = Pathname.new(path)
19
+ connection.nlst(pathname.dirname.to_s).include?(pathname.basename.to_s)
20
+ end
21
+
22
+ def get_file(remote_file_path, local_file_path)
23
+ connection.getbinaryfile(remote_file_path, local_file_path)
24
+ end
25
+
26
+ def put_file(local_file_path, remote_file_path)
27
+ pathname = Pathname.new(remote_file_path)
28
+ mkdir_p(pathname.dirname.to_s)
29
+ connection.putbinaryfile(local_file_path, remote_file_path)
30
+ end
31
+
32
+ def delete_file(remote_file_path)
33
+ connection.delete(remote_file_path)
34
+ end
35
+
36
+ def connection
37
+ @connection ||= Net::FTP.open(host, user, password)
38
+ end
39
+
40
+ def mkdir_p(dirname)
41
+ pathname = Pathname.new(dirname)
42
+ pathname.descend do |p|
43
+ begin
44
+ connection.mkdir(p.to_s)
45
+ rescue Net::FTPPermError
46
+ # This error can be caused by an existing directory.
47
+ # Ignore, and keep on trying to create child directories.
48
+ end
49
+ end
50
+ end
51
+ end
52
+ end
53
+ end
54
+ end
@@ -0,0 +1,20 @@
1
+ # -*- encoding: utf-8 -*-
2
+ Gem::Specification.new do |gem|
3
+ gem.authors = ["Sebastian Röbke"]
4
+ gem.email = ["sebastian.roebke@xing.com"]
5
+ gem.description = %q{Allow Paperclip attachments to be stored on FTP servers}
6
+ gem.summary = %q{Allow Paperclip attachments to be stored on FTP servers}
7
+ gem.homepage = "https://github.com/xing/paperclip-storage-ftp"
8
+
9
+ gem.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
10
+ gem.files = `git ls-files`.split("\n")
11
+ gem.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
12
+ gem.name = "paperclip-storage-ftp"
13
+ gem.require_paths = ["lib"]
14
+ gem.version = "1.0.0.rc1"
15
+
16
+ gem.add_dependency("paperclip")
17
+
18
+ gem.add_development_dependency("rspec")
19
+ gem.add_development_dependency("rake")
20
+ end
@@ -0,0 +1,88 @@
1
+ require File.expand_path("../../../../spec_helper", __FILE__)
2
+
3
+ describe Paperclip::Storage::Ftp::Server do
4
+ let(:server) { Paperclip::Storage::Ftp::Server.new }
5
+
6
+ context "initialize" do
7
+ it "accepts options to initialize attributes" do
8
+ options = {
9
+ :host => "ftp.example.com",
10
+ :user => "user",
11
+ :password => "password"
12
+ }
13
+ server = Paperclip::Storage::Ftp::Server.new(options)
14
+ server.host.should == "ftp.example.com"
15
+ server.user.should == "user"
16
+ server.password.should == "password"
17
+ end
18
+ end
19
+
20
+ context "#file_exists?" do
21
+ it "returns true if the file exists on the server" do
22
+ server.connection = double("connection")
23
+ server.connection.should_receive(:nlst).with("/files/original").and_return(["foo.jpg"])
24
+ server.file_exists?("/files/original/foo.jpg").should be_true
25
+ end
26
+
27
+ it "returns false if the file does not exist on the server" do
28
+ server.connection = double("connection")
29
+ server.connection.should_receive(:nlst).with("/files/original").and_return([])
30
+ server.file_exists?("/files/original/foo.jpg").should be_false
31
+ end
32
+ end
33
+
34
+ context "#get_file" do
35
+ it "returns the file object" do
36
+ server.connection = double("connection")
37
+ server.connection.should_receive(:getbinaryfile).with("/files/original.jpg", "/tmp/original.jpg")
38
+ server.get_file("/files/original.jpg", "/tmp/original.jpg")
39
+ end
40
+ end
41
+
42
+ context "#put_file" do
43
+ it "stores the file on the server" do
44
+ server.connection = double("connection")
45
+ server.should_receive(:mkdir_p).with("/files")
46
+ server.connection.should_receive(:putbinaryfile).with("/tmp/original.jpg", "/files/original.jpg")
47
+ server.put_file("/tmp/original.jpg", "/files/original.jpg")
48
+ end
49
+ end
50
+
51
+ context "#delete_file" do
52
+ it "deletes the file on the server" do
53
+ server.connection = double("connection")
54
+ server.connection.should_receive(:delete).with("/files/original.jpg")
55
+ server.delete_file("/files/original.jpg")
56
+ end
57
+ end
58
+
59
+ context "#connection" do
60
+ it "returns a memoized ftp connection to the given server" do
61
+ server.host = "ftp.example.com"
62
+ server.user = "user"
63
+ server.password = "password"
64
+
65
+ Net::FTP.should_receive(:open).with(server.host, server.user, server.password).once.and_return(:foo)
66
+
67
+ 2.times { server.connection.should == :foo }
68
+ end
69
+ end
70
+
71
+ context "mkdir_p" do
72
+ it "creates the directory and all its parent directories" do
73
+ server.connection = double("connection")
74
+ server.connection.should_receive(:mkdir).with("/").ordered
75
+ server.connection.should_receive(:mkdir).with("/files").ordered
76
+ server.connection.should_receive(:mkdir).with("/files/foo").ordered
77
+ server.connection.should_receive(:mkdir).with("/files/foo/bar").ordered
78
+ server.mkdir_p("/files/foo/bar")
79
+ end
80
+
81
+ it "does not stop on Net::FTPPermError" do
82
+ server.connection = double("connection")
83
+ server.connection.should_receive(:mkdir).with("/").and_raise(Net::FTPPermError)
84
+ server.connection.should_receive(:mkdir).with("/files")
85
+ server.mkdir_p("/files")
86
+ end
87
+ end
88
+ end
@@ -0,0 +1,128 @@
1
+ require File.expand_path("../../../spec_helper", __FILE__)
2
+
3
+ describe Paperclip::Storage::Ftp do
4
+ let(:attachment) do
5
+ model_instance = double()
6
+ model_instance.stub(:id).and_return(1)
7
+ model_instance.stub(:image_file_name).and_return("foo.jpg")
8
+
9
+ Paperclip::Attachment.new(:image, model_instance, {
10
+ :storage => :ftp,
11
+ :path => "/files/:style/:filename",
12
+ :ftp_servers => [
13
+ {
14
+ :host => "ftp1.example.com",
15
+ :user => "user1",
16
+ :password => "password1"
17
+ },
18
+ {
19
+ :host => "ftp2.example.com",
20
+ :user => "user2",
21
+ :password => "password2"
22
+ }
23
+ ]
24
+ })
25
+ end
26
+
27
+ context "#exists?" do
28
+ it "returns false if original_filename not set" do
29
+ attachment.stub(:original_filename).and_return(nil)
30
+ attachment.exists?.should be_false
31
+ end
32
+
33
+ it "returns true if the file exists on the primary server" do
34
+ attachment.primary_ftp_server.should_receive(:file_exists?).with("/files/original/foo.jpg").and_return(true)
35
+ attachment.exists?.should be_true
36
+ end
37
+
38
+ it "accepts an optional style_name parameter to build the correct file path" do
39
+ attachment.primary_ftp_server.should_receive(:file_exists?).with("/files/thumb/foo.jpg").and_return(true)
40
+ attachment.exists?(:thumb)
41
+ end
42
+ end
43
+
44
+ context "#to_file" do
45
+ it "returns the file from the primary server as a local tempfile" do
46
+ tempfile = double("tempfile")
47
+ tempfile.should_receive(:path).and_return("/tmp/foo")
48
+ tempfile.should_receive(:rewind).with(no_args)
49
+ Tempfile.should_receive(:new).with(["foo", ".jpg"]).and_return(tempfile)
50
+ attachment.primary_ftp_server.should_receive(:get_file).with("/files/original/foo.jpg", "/tmp/foo").and_return(:foo)
51
+ attachment.to_file.should == tempfile
52
+ end
53
+
54
+ it "accepts an optional style_name parameter to build the correct file path" do
55
+ tempfile = double("tempfile")
56
+ tempfile.should_receive(:path).and_return("/tmp/foo")
57
+ tempfile.should_receive(:rewind).with(no_args)
58
+ Tempfile.should_receive(:new).with(["foo", ".jpg"]).and_return(tempfile)
59
+ attachment.primary_ftp_server.should_receive(:get_file).with("/files/thumb/foo.jpg", anything)
60
+ attachment.to_file(:thumb)
61
+ end
62
+
63
+ it "gets an existing file object from the local write queue, if available" do
64
+ file = double("file")
65
+ file.should_receive(:rewind)
66
+ attachment.instance_variable_set(:@queued_for_write, {:original => file})
67
+ attachment.to_file.should == file
68
+ end
69
+ end
70
+
71
+ context "#flush_writes" do
72
+ it "stores the files on every server" do
73
+ original_file = double("original_file", :path => "/tmp/original/foo.jpg")
74
+ thumb_file = double("thumb_file", :path => "/tmp/thumb/foo.jpg")
75
+
76
+ attachment.instance_variable_set(:@queued_for_write, {
77
+ :original => original_file,
78
+ :thumb => thumb_file
79
+ })
80
+
81
+ thumb_file.should_receive(:close).with(no_args)
82
+ original_file.should_receive(:close).with(no_args)
83
+ attachment.ftp_servers.first.should_receive(:put_file).with("/tmp/original/foo.jpg", "/files/original/foo.jpg")
84
+ attachment.ftp_servers.first.should_receive(:put_file).with("/tmp/thumb/foo.jpg", "/files/thumb/foo.jpg")
85
+ attachment.ftp_servers.second.should_receive(:put_file).with("/tmp/original/foo.jpg", "/files/original/foo.jpg")
86
+ attachment.ftp_servers.second.should_receive(:put_file).with("/tmp/thumb/foo.jpg", "/files/thumb/foo.jpg")
87
+ attachment.should_receive(:after_flush_writes).with(no_args)
88
+
89
+ attachment.flush_writes
90
+
91
+ attachment.queued_for_write.should == {}
92
+ end
93
+ end
94
+
95
+ context "#flush_deletes" do
96
+ it "deletes the files on every server" do
97
+ attachment.instance_variable_set(:@queued_for_delete, [
98
+ "/files/original/foo.jpg",
99
+ "/files/thumb/foo.jpg"
100
+ ])
101
+ attachment.ftp_servers.first.should_receive(:delete_file).with("/files/original/foo.jpg")
102
+ attachment.ftp_servers.first.should_receive(:delete_file).with("/files/thumb/foo.jpg")
103
+ attachment.ftp_servers.second.should_receive(:delete_file).with("/files/original/foo.jpg")
104
+ attachment.ftp_servers.second.should_receive(:delete_file).with("/files/thumb/foo.jpg")
105
+
106
+ attachment.flush_deletes
107
+
108
+ attachment.instance_variable_get(:@queued_for_delete).should == []
109
+ end
110
+ end
111
+
112
+ context "#ftp_servers" do
113
+ it "returns the configured ftp servers" do
114
+ attachment.ftp_servers.first.host.should == "ftp1.example.com"
115
+ attachment.ftp_servers.first.user.should == "user1"
116
+ attachment.ftp_servers.first.password.should == "password1"
117
+ attachment.ftp_servers.second.host.should == "ftp2.example.com"
118
+ attachment.ftp_servers.second.user.should == "user2"
119
+ attachment.ftp_servers.second.password.should == "password2"
120
+ end
121
+ end
122
+
123
+ context "#primary_ftp_server" do
124
+ it "returns the first server in the list" do
125
+ attachment.primary_ftp_server.should == attachment.ftp_servers.first
126
+ end
127
+ end
128
+ end
@@ -0,0 +1,8 @@
1
+ RSpec.configure do |config|
2
+ config.treat_symbols_as_metadata_keys_with_true_values = true
3
+ config.run_all_when_everything_filtered = true
4
+ end
5
+
6
+ require "paperclip/storage/ftp"
7
+
8
+ Paperclip.options[:log] = false
metadata ADDED
@@ -0,0 +1,110 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: paperclip-storage-ftp
3
+ version: !ruby/object:Gem::Version
4
+ version: 1.0.0.rc1
5
+ prerelease: 6
6
+ platform: ruby
7
+ authors:
8
+ - Sebastian Röbke
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+ date: 2012-09-26 00:00:00.000000000 Z
13
+ dependencies:
14
+ - !ruby/object:Gem::Dependency
15
+ name: paperclip
16
+ requirement: !ruby/object:Gem::Requirement
17
+ none: false
18
+ requirements:
19
+ - - ! '>='
20
+ - !ruby/object:Gem::Version
21
+ version: '0'
22
+ type: :runtime
23
+ prerelease: false
24
+ version_requirements: !ruby/object:Gem::Requirement
25
+ none: false
26
+ requirements:
27
+ - - ! '>='
28
+ - !ruby/object:Gem::Version
29
+ version: '0'
30
+ - !ruby/object:Gem::Dependency
31
+ name: rspec
32
+ requirement: !ruby/object:Gem::Requirement
33
+ none: false
34
+ requirements:
35
+ - - ! '>='
36
+ - !ruby/object:Gem::Version
37
+ version: '0'
38
+ type: :development
39
+ prerelease: false
40
+ version_requirements: !ruby/object:Gem::Requirement
41
+ none: false
42
+ requirements:
43
+ - - ! '>='
44
+ - !ruby/object:Gem::Version
45
+ version: '0'
46
+ - !ruby/object:Gem::Dependency
47
+ name: rake
48
+ requirement: !ruby/object:Gem::Requirement
49
+ none: false
50
+ requirements:
51
+ - - ! '>='
52
+ - !ruby/object:Gem::Version
53
+ version: '0'
54
+ type: :development
55
+ prerelease: false
56
+ version_requirements: !ruby/object:Gem::Requirement
57
+ none: false
58
+ requirements:
59
+ - - ! '>='
60
+ - !ruby/object:Gem::Version
61
+ version: '0'
62
+ description: Allow Paperclip attachments to be stored on FTP servers
63
+ email:
64
+ - sebastian.roebke@xing.com
65
+ executables: []
66
+ extensions: []
67
+ extra_rdoc_files: []
68
+ files:
69
+ - .gitignore
70
+ - Gemfile
71
+ - LICENSE
72
+ - README.md
73
+ - Rakefile
74
+ - lib/paperclip/storage/ftp.rb
75
+ - lib/paperclip/storage/ftp/server.rb
76
+ - paperclip-storage-ftp.gemspec
77
+ - spec/paperclip/storage/ftp/server_spec.rb
78
+ - spec/paperclip/storage/ftp_spec.rb
79
+ - spec/spec_helper.rb
80
+ homepage: https://github.com/xing/paperclip-storage-ftp
81
+ licenses: []
82
+ post_install_message:
83
+ rdoc_options: []
84
+ require_paths:
85
+ - lib
86
+ required_ruby_version: !ruby/object:Gem::Requirement
87
+ none: false
88
+ requirements:
89
+ - - ! '>='
90
+ - !ruby/object:Gem::Version
91
+ version: '0'
92
+ segments:
93
+ - 0
94
+ hash: 3195692786080207386
95
+ required_rubygems_version: !ruby/object:Gem::Requirement
96
+ none: false
97
+ requirements:
98
+ - - ! '>'
99
+ - !ruby/object:Gem::Version
100
+ version: 1.3.1
101
+ requirements: []
102
+ rubyforge_project:
103
+ rubygems_version: 1.8.24
104
+ signing_key:
105
+ specification_version: 3
106
+ summary: Allow Paperclip attachments to be stored on FTP servers
107
+ test_files:
108
+ - spec/paperclip/storage/ftp/server_spec.rb
109
+ - spec/paperclip/storage/ftp_spec.rb
110
+ - spec/spec_helper.rb