paperclipftp 0.1.0 → 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (5) hide show
  1. data/LICENSE +1 -1
  2. data/README.rdoc +48 -17
  3. data/VERSION +1 -1
  4. data/lib/paperclipftp.rb +165 -91
  5. metadata +16 -7
data/LICENSE CHANGED
@@ -1,4 +1,4 @@
1
- Copyright (c) 2009 Damian Caruso
1
+ Copyright (c) 2010 Damian Caruso
2
2
 
3
3
  Permission is hereby granted, free of charge, to any person obtaining
4
4
  a copy of this software and associated documentation files (the
data/README.rdoc CHANGED
@@ -1,6 +1,6 @@
1
1
  = PaperclipFTP
2
2
 
3
- Ftp storage support for paperclip file attachment plugin.
3
+ Ftp storage support for paperclip file attachment plugin.
4
4
  Useful for CDNs with ftp access method for file uploading.
5
5
 
6
6
  == Install
@@ -10,30 +10,61 @@ Useful for CDNs with ftp access method for file uploading.
10
10
 
11
11
  == Usage
12
12
 
13
- Create the file config/paperclipftp.yml:
13
+ In your model:
14
14
 
15
- development:
16
- host: domain.com
17
- username: user
18
- password: password
19
-
20
- test:
21
- ...
22
-
23
- production:
24
- ...
15
+ class User < ActiveRecord::Base
16
+ has_attached_file :avatar,
17
+ :styles => { :medium => "300x300>", :thumb => "100x100>" },
18
+ :path => "/path-to-images/:attachment/:id/:style/:filename",
19
+ :url => "http://my-cdn-domain.com/:attachment/:id/:style/:filename",
20
+ :storage => :ftp,
21
+ :ftp_credentials => Rails.root.join('config', 'paperclipftp.yaml')
22
+ end
25
23
 
26
- In your model:
24
+ or
27
25
 
28
26
  class User < ActiveRecord::Base
29
27
  has_attached_file :avatar,
30
28
  :styles => { :medium => "300x300>", :thumb => "100x100>" },
29
+ :path => "/path-to-images/:attachment/:id/:style/:filename",
30
+ :url => "http://my-cdn-domain.com/:attachment/:id/:style/:filename",
31
31
  :storage => :ftp,
32
- :path => "/:attachment/:attachment/:id/:style/:filename"
33
- :url => "http://domain.com/:attachment/:attachment/:id/:style/:filename"
32
+ :ftp_credentials => { :host => 'ftp.domain.com', :username => 'username', :password => 'password' },
33
+ :ftp_passive_mode => false,
34
+ :ftp_timeout => 90,
35
+ :ftp_verify_size_on_upload => false,
36
+ :ftp_debug_mode => false
34
37
  end
35
-
36
- You must add in the url option the web access to that file.
38
+
39
+ Options storage, ftp_credentials, path and url are mandatory.
40
+
41
+ * +storage+: Should be set to 'ftp' value in order to utilize paperclipftp plugin
42
+ * +ftp_credentials+: Takes a path, a File, or a Hash. The path (or File) must point
43
+ to a YAML file containing the +host+, +username+, and +password+ to ftp server.
44
+ You can 'environment-space' this just like you do to your
45
+ database.yml file, so different environments can use different accounts:
46
+ development:
47
+ host: domain.com
48
+ username: user
49
+ password: password
50
+ test:
51
+ ...
52
+ production:
53
+ ...
54
+ This is not required, however, and the file may simply look like this:
55
+ host: domain.com
56
+ username: user
57
+ password: password
58
+ In which case, those values will be used in all environments.
59
+ * +path+: This is the templated path to the directory on the ftp server in which the file will be stored.
60
+ * +url+: This is the templated url to the files as accessed from CDN.
61
+
62
+ Other options are optional.
63
+
64
+ * +ftp_passive_mode+: True if ftp communication should happen in passive mode
65
+ * +ftp_timeout+: Max amount of time in seconds to allocate to ftp operations of the plugin
66
+ * +ftp_debug_mode+: If true, dumps copy of all ftp commands to stderr
67
+ * +ftp_verify_size_on_upload+: If true, file size will be verified after each upload
37
68
 
38
69
  == Copyright
39
70
 
data/VERSION CHANGED
@@ -1 +1 @@
1
- 0.1.0
1
+ 0.2.0
data/lib/paperclipftp.rb CHANGED
@@ -1,91 +1,165 @@
1
- module Paperclip
2
- module Storage
3
- module Ftp
4
- def self.extended base
5
- require 'net/ftp'
6
- base.instance_eval do
7
- @ftp_credentials = parse_credentials
8
- end
9
- end
10
-
11
- def ftp
12
- if @ftp.nil? || @ftp.closed?
13
- @ftp = Net::FTP.new(@ftp_credentials[:host], @ftp_credentials[:username], @ftp_credentials[:password])
14
- end
15
- @ftp
16
- end
17
-
18
- def exists?(style = default_style)
19
- move_to_remote_path(File.dirname(path(style)))
20
- ftp.size(File.basename(path(style))) > 0 ? true : false
21
- rescue Net::FTPPermError => e
22
- #File not exists
23
- false
24
- rescue Net::FTPReplyError => e
25
- ftp.close
26
- raise e
27
- end
28
-
29
- def to_file style = default_style
30
- @queued_for_write[style] || "ftp://#{path(style)}"
31
- end
32
- alias_method :to_io, :to_file
33
-
34
- def flush_writes
35
- @queued_for_write.each do |style, file|
36
- file.close
37
- move_to_remote_path(File.dirname(path(style)))
38
- log("uploading #{path(style)}")
39
- ftp.putbinaryfile(file.path, File.basename(path(style)))
40
- end
41
- @queued_for_write = {}
42
- rescue Net::FTPReplyError => e
43
- raise e
44
- rescue Net::FTPPermError => e
45
- raise e
46
- ensure
47
- ftp.close
48
- end
49
-
50
- def flush_deletes
51
- @queued_for_delete.each do |path|
52
- begin
53
- move_to_remote_path(File.dirname(path))
54
- log("deleting #{path}")
55
- ftp.delete(File.basename(path))
56
- rescue Net::FTPPermError, Net::FTPReplyError
57
- end
58
- end
59
- @queued_for_delete = []
60
- rescue Net::FTPReplyError => e
61
- raise e
62
- rescue Net::FTPPermError => e
63
- raise e
64
- ensure
65
- ftp.close
66
- end
67
-
68
- def move_to_remote_path(rpath)
69
- ftp.chdir("/")
70
- rpath.split(File::SEPARATOR).each do |rdir|
71
- rdir = rdir.strip
72
- unless rdir.blank?
73
- list = ftp.ls.collect { |f| f.split.last }
74
- unless list.include?(rdir)
75
- ftp.mkdir(rdir)
76
- end
77
- ftp.chdir(rdir)
78
- end
79
- end
80
- end
81
-
82
- def parse_credentials
83
- creds = YAML.load_file(File.join(RAILS_ROOT,"config","paperclipftp.yml"))
84
- creds = creds.stringify_keys
85
- (creds[RAILS_ENV] || creds).symbolize_keys
86
- end
87
- private :parse_credentials
88
-
89
- end
90
- end
91
- end
1
+ module Paperclip
2
+ module Storage
3
+ module Ftp
4
+ class FtpTimeout < Timeout::Error; end
5
+
6
+ def self.extended base
7
+ require 'net/ftp'
8
+ base.instance_eval do
9
+ @ftp_credentials = parse_credentials(@options[:ftp_credentials])
10
+ @passive_mode = !!@options[:ftp_passive_mode]
11
+ @debug_mode = !!@options[:ftp_debug_mode]
12
+ @verify_size = !!@options[:ftp_verify_size_on_upload]
13
+ @timeout = @options[:ftp_timeout] || 3600
14
+ end
15
+ end
16
+
17
+ def exists?(style = default_style)
18
+ Timeout::timeout(@timeout, FtpTimeout) do
19
+ file_size(ftp_path(style)) > 0
20
+ end
21
+ end
22
+
23
+ def to_file style = default_style
24
+ return @queued_for_write[style] if @queued_for_write[style]
25
+ Timeout::timeout(@timeout, FtpTimeout) do
26
+ file = Tempfile.new(ftp_path(style))
27
+ ftp.getbinaryfile(ftp_path(style), file.path)
28
+ file.rewind
29
+ return file
30
+ end
31
+ end
32
+
33
+ alias_method :to_io, :to_file
34
+
35
+ def flush_writes
36
+ @queued_for_write.each do |style, file|
37
+ Timeout::timeout(@timeout, FtpTimeout) do
38
+ file.close
39
+ remote_path = ftp_path(style)
40
+ log("uploading #{remote_path}")
41
+ first_try = true
42
+ begin
43
+ ftp.putbinaryfile(file.path, remote_path)
44
+ rescue Net::FTPPermError => e
45
+ if first_try
46
+ first_try = false
47
+ create_parent_folder_for(remote_path)
48
+ retry
49
+ else
50
+ raise e
51
+ end
52
+ end
53
+ if @verify_size
54
+ # avoiding those weird occasional 0 file sizes by not using instance method file.size
55
+ local_file_size = File.size(file.path)
56
+ remote_file_size = file_size(remote_path)
57
+ raise Net::FTPError.new "Uploaded #{remote_file_size} bytes instead of #{local_file_size} bytes" unless remote_file_size == local_file_size
58
+ end
59
+ end
60
+ end
61
+ @queued_for_write = {}
62
+ rescue Net::FTPReplyError => e
63
+ raise e
64
+ rescue Net::FTPPermError => e
65
+ raise e
66
+ ensure
67
+ close_ftp_connection
68
+ end
69
+
70
+ def flush_deletes
71
+ @queued_for_delete.each do |path|
72
+ Timeout::timeout(@timeout, FtpTimeout) do
73
+ begin
74
+ log("deleting #{path}")
75
+ ftp.delete('/' + path)
76
+ rescue Net::FTPPermError, Net::FTPReplyError
77
+ end
78
+ end
79
+ end
80
+ @queued_for_delete = []
81
+ rescue Net::FTPReplyError => e
82
+ raise e
83
+ rescue Net::FTPPermError => e
84
+ raise e
85
+ ensure
86
+ close_ftp_connection
87
+ end
88
+
89
+ private
90
+
91
+ def ftp
92
+ if @ftp.nil? || @ftp.closed?
93
+ Timeout::timeout(@timeout, FtpTimeout) do
94
+ @ftp = Net::FTP.new(@ftp_credentials[:host], @ftp_credentials[:username], @ftp_credentials[:password])
95
+ @ftp.debug_mode = @debug_mode
96
+ @ftp.passive = @passive_mode
97
+ end
98
+ end
99
+ @ftp
100
+ end
101
+
102
+ def close_ftp_connection
103
+ unless @ftp.nil? || @ftp.closed?
104
+ @ftp.close
105
+ @ftp = nil
106
+ end
107
+ end
108
+
109
+ def create_parent_folder_for(remote_path)
110
+ dir_path = File.dirname(remote_path)
111
+ ftp.chdir("/")
112
+ dir_path.split(File::SEPARATOR).each do |rdir|
113
+ next if rdir.blank?
114
+ first_time = true
115
+ begin
116
+ ftp.chdir(rdir)
117
+ rescue Net::FTPPermError => e
118
+ if first_time
119
+ ftp.mkdir(rdir)
120
+ first_time = false
121
+ retry
122
+ else
123
+ raise e
124
+ end
125
+ end
126
+ end
127
+ end
128
+
129
+ def ftp_path(style)
130
+ path = path(style)
131
+ path.nil? ? nil : '/' + path
132
+ end
133
+
134
+ def file_size(remote_path)
135
+ ftp.size(remote_path)
136
+ rescue Net::FTPPermError => e
137
+ #File not exists
138
+ -1
139
+ rescue Net::FTPReplyError => e
140
+ close_ftp_connection
141
+ raise e
142
+ end
143
+
144
+ def parse_credentials creds
145
+ creds = find_credentials(creds).stringify_keys
146
+ (creds[Rails.env] || creds).symbolize_keys
147
+ end
148
+
149
+ def find_credentials creds
150
+ case creds
151
+ when File
152
+ YAML::load(ERB.new(File.read(creds.path)).result)
153
+ when String, Pathname
154
+ YAML::load(ERB.new(File.read(creds)).result)
155
+ when Hash
156
+ creds
157
+ else
158
+ raise ArgumentError, "Credentials are not a path, file, or hash."
159
+ end
160
+ end
161
+
162
+
163
+ end
164
+ end
165
+ end
metadata CHANGED
@@ -1,12 +1,13 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: paperclipftp
3
3
  version: !ruby/object:Gem::Version
4
- prerelease: false
4
+ hash: 23
5
+ prerelease:
5
6
  segments:
6
7
  - 0
7
- - 1
8
+ - 2
8
9
  - 0
9
- version: 0.1.0
10
+ version: 0.2.0
10
11
  platform: ruby
11
12
  authors:
12
13
  - Damian Caruso
@@ -14,16 +15,18 @@ autorequire:
14
15
  bindir: bin
15
16
  cert_chain: []
16
17
 
17
- date: 2010-03-08 00:00:00 -03:00
18
+ date: 2011-05-13 00:00:00 -04:00
18
19
  default_executable:
19
20
  dependencies:
20
21
  - !ruby/object:Gem::Dependency
21
22
  name: paperclip
22
23
  prerelease: false
23
24
  requirement: &id001 !ruby/object:Gem::Requirement
25
+ none: false
24
26
  requirements:
25
27
  - - ">="
26
28
  - !ruby/object:Gem::Version
29
+ hash: 3
27
30
  segments:
28
31
  - 2
29
32
  - 3
@@ -35,9 +38,11 @@ dependencies:
35
38
  name: yard
36
39
  prerelease: false
37
40
  requirement: &id002 !ruby/object:Gem::Requirement
41
+ none: false
38
42
  requirements:
39
43
  - - ">="
40
44
  - !ruby/object:Gem::Version
45
+ hash: 3
41
46
  segments:
42
47
  - 0
43
48
  version: "0"
@@ -65,28 +70,32 @@ homepage: http://github.com/cdamian/paperclipftp
65
70
  licenses: []
66
71
 
67
72
  post_install_message:
68
- rdoc_options:
69
- - --charset=UTF-8
73
+ rdoc_options: []
74
+
70
75
  require_paths:
71
76
  - lib
72
77
  required_ruby_version: !ruby/object:Gem::Requirement
78
+ none: false
73
79
  requirements:
74
80
  - - ">="
75
81
  - !ruby/object:Gem::Version
82
+ hash: 3
76
83
  segments:
77
84
  - 0
78
85
  version: "0"
79
86
  required_rubygems_version: !ruby/object:Gem::Requirement
87
+ none: false
80
88
  requirements:
81
89
  - - ">="
82
90
  - !ruby/object:Gem::Version
91
+ hash: 3
83
92
  segments:
84
93
  - 0
85
94
  version: "0"
86
95
  requirements: []
87
96
 
88
97
  rubyforge_project:
89
- rubygems_version: 1.3.6
98
+ rubygems_version: 1.6.2
90
99
  signing_key:
91
100
  specification_version: 3
92
101
  summary: Ftp storage support for paperclip file attachment