attached 0.0.9 → 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
data/Gemfile CHANGED
@@ -4,4 +4,6 @@ gem 'rails'
4
4
 
5
5
  gem 'guid'
6
6
 
7
- gem 'aws-s3', :require => 'aws/s3'
7
+ gem 'aws-s3', :require => 'aws/s3'
8
+
9
+ gem 'rmagick'
@@ -1,6 +1,6 @@
1
1
  = Attached
2
2
 
3
- Attached is a Ruby on Rails file attachment tool that lets users upload to the cloud, then process in the cloud. The tool supports Amazon S3 by default. It surpasses Paperclip by providing built-in support for direct uploads to the cloud and by allowing integration with cloud based processing tools. However, in almost every other way Paperclip is better. The source code inspired (and copied) from Paperclip. If you aren't working in the cloud exclusively then this isn't for you!
3
+ Attached is a Ruby on Rails file attachment tool that lets users upload to the cloud, then process in the cloud. The tool supports Amazon S3 by default. It surpasses Paperclip by providing built-in support for direct uploads to the cloud and by allowing integration with cloud based processing tools. However, in almost every other way Paperclip is better. The source code is inspired (and copied) from Paperclip. If you aren't working in the cloud exclusively then this isn't for you!
4
4
 
5
5
  == Installation
6
6
 
@@ -13,9 +13,9 @@ Migration:
13
13
  class CreateVideo < ActiveRecord::Migration
14
14
  def self.up
15
15
  create_table :videos do |t|
16
- t.string :video_identifier
17
- t.string :video_extension
18
- t.integer :video_size
16
+ t.string :encoding_identifier
17
+ t.string :encoding_extension
18
+ t.integer :encoding_size
19
19
 
20
20
  t.timestamps
21
21
  end
@@ -28,27 +28,52 @@ Migration:
28
28
 
29
29
  Model:
30
30
 
31
- has_attached :video, :styles => {
32
- :mp4_720p => { :extension => 'mp4' },
33
- :mp4_480p => { :extension => 'mp4' },
34
- :ogv_720p => { :extension => 'ogv' },
35
- :ogv_480p => { :extension => 'ogv' },
31
+ has_attached :encoding, :styles => {
32
+ :mp4_720p => { :extension => '.mp4' },
33
+ :mp4_480p => { :extension => '.mp4' },
34
+ :ogg_720p => { :extension => '.ogv' },
35
+ :ogg_480p => { :extension => '.ogv' },
36
36
  }
37
37
 
38
+ after_save do
39
+ remote.encode(self.encoding.url)
40
+ end
41
+
38
42
  Form:
39
43
 
40
44
  <%= form_for @video, :html => { :multipart => true } do |form| %>
41
- <%= form.file_field :video %>
42
- <p class="errors"><%= @video.errors[:video] %></p>
45
+ <%= form.file_field :encoding %>
46
+ <p class="errors"><%= @video.errors[:encoding] %></p>
43
47
  <% end %>
44
48
 
45
49
  View:
46
50
 
47
51
  <video>
48
- <source src="<%= @video.video.url(:mp4_480p) %>" />
49
- <source src="<%= @video.video.url(:ogv_480p) %>" />
52
+ <source src="<%= @video.encoding.url(:mp4_480p) %>" />
53
+ <source src="<%= @video.encoding.url(:ogg_480p) %>" />
50
54
  </video>
51
55
 
56
+ == Advanced
57
+
58
+ === Storage
59
+
60
+ has_attached :avatar, :provider => :amazon, :credentials => {
61
+ :secret_access_key => "*****",
62
+ :access_key_id => "*****",
63
+ }
64
+
65
+ has_attached :avatar, :provider => :google, :credentials => {
66
+ :secret_access_key => "*****",
67
+ :access_key_id => "*****",
68
+ }
69
+
70
+ === Processor
71
+
72
+ has_attached :avatar, :processor => :resize, :styles => {
73
+ :small => { :size => "200x200#" }
74
+ :large => { :size => "200x200#" }
75
+ }
76
+
52
77
  == Copyright
53
78
 
54
79
  Copyright (c) 2010 Kevin Sylvestre. See LICENSE for details.
@@ -31,8 +31,9 @@ module Attached
31
31
  #
32
32
  # Options:
33
33
  #
34
- # * :styles -
35
- # * :storage -
34
+ # * :styles -
35
+ # * :storage -
36
+ # * :processor -
36
37
  #
37
38
  # Usage:
38
39
  #
@@ -118,8 +119,8 @@ module Attached
118
119
 
119
120
  range = minimum..maximum
120
121
 
121
- message.gsub!(/:minimum/, number_to_human_size(minimum)) unless minimum == zero
122
- message.gsub!(/:maximum/, number_to_human_size(maximum)) unless maximum == infi
122
+ message.gsub!(/:minimum/, number_to_size(minimum)) unless minimum == zero
123
+ message.gsub!(/:maximum/, number_to_size(maximum)) unless maximum == infi
123
124
 
124
125
  validates_inclusion_of :"#{name}_size", :in => range, :message => message,
125
126
  :if => options[:if], :unless => options[:unless]
@@ -151,21 +152,29 @@ module Attached
151
152
  private
152
153
 
153
154
 
154
- SINGULAR = 1
155
+ # Convert a number to a human readable size.
156
+ #
157
+ # Usage:
158
+ #
159
+ # number_to_size(1) # 1 byte
160
+ # number_to_size(2) # 2 bytes
161
+ # number_to_size(1024) # 1 kilobyte
162
+ # number_to_size(2048) # 2 kilobytes
155
163
 
156
- def number_to_human_size(number, options = {})
164
+ def number_to_size(number, options = {})
157
165
  return if number == 0.0 / 1.0
158
166
  return if number == 1.0 / 0.0
159
167
 
160
- base = 1024
161
- units = ["byte", "kilobyte", "megabyte", "gigabyte", "terabyte", "petabyte"]
168
+ singular = options['singular'] || 1
169
+ base = options['base'] || 1024
170
+ units = options['units'] || ["byte", "kilobyte", "megabyte", "gigabyte", "terabyte", "petabyte"]
162
171
 
163
172
  exponent = (Math.log(number) / Math.log(base)).floor
164
173
 
165
174
  number /= base ** exponent
166
175
  unit = units[exponent]
167
176
 
168
- number == SINGULAR ? unit.gsub!(/s$/, '') : unit.gsub!(/$/, 's')
177
+ number == singular ? unit.gsub!(/s$/, '') : unit.gsub!(/$/, 's')
169
178
 
170
179
  "#{number} #{unit}"
171
180
  end
@@ -1,6 +1,8 @@
1
1
  require 'guid'
2
2
 
3
3
  require 'attached/storage'
4
+ require 'attached/processor'
5
+ require 'attached/image'
4
6
 
5
7
  module Attached
6
8
 
@@ -11,13 +13,28 @@ module Attached
11
13
  attr_reader :name
12
14
  attr_reader :instance
13
15
  attr_reader :options
16
+ attr_reader :queue
17
+ attr_reader :path
18
+ attr_reader :styles
19
+ attr_reader :default
20
+ attr_reader :medium
21
+ attr_reader :credentials
22
+ attr_reader :processors
23
+ attr_reader :processor
14
24
 
15
25
 
26
+ # A default set of options that can be extended to customize the path, storage or credentials.
27
+ #
28
+ # Usage:
29
+ #
30
+ # Attached::Attachment.options = { :storage => :fs, :path => "/:name/:style/:identifier:extension" }
31
+
16
32
  def self.options
17
33
  @options ||= {
18
- :storage => :s3,
19
- :path => "/:name/:style/:identifier:extension",
20
- :styles => {},
34
+ :path => "/:name/:style/:identifier:extension",
35
+ :default => :original,
36
+ :styles => {},
37
+ :processors => [],
21
38
  }
22
39
  end
23
40
 
@@ -34,16 +51,28 @@ module Attached
34
51
  # * :path - The location where the attachment is stored
35
52
  # * :storage - The storage medium represented as a symbol such as ':s3'
36
53
  # * :credentials - A file, hash, or path used to authenticate with the specified storage medium
54
+ # * :styles - A hash containing optional parameters including extension and identifier
37
55
 
38
56
  def initialize(name, instance, options = {})
39
- @name = name
40
- @instance = instance
57
+ @name = name
58
+ @instance = instance
59
+ @options = self.class.options.merge(options)
60
+
61
+ @queue = {}
62
+
63
+ @path = @options[:path]
64
+ @styles = @options[:styles]
65
+ @default = @options[:default]
66
+ @medium = @options[:medium]
67
+ @credentials = @options[:credentials]
68
+ @processors = @options[:processors]
69
+ @processor = @options[:processor]
41
70
 
42
- @options = self.class.options.merge(options)
71
+ @processors << @processor if @processor
43
72
  end
44
73
 
45
74
 
46
- #
75
+ # Check if an attachment has been modified.
47
76
  #
48
77
  # Usage:
49
78
  #
@@ -54,6 +83,8 @@ module Attached
54
83
  end
55
84
 
56
85
 
86
+ # Assign an attachment to a file.
87
+ #
57
88
  # Usage:
58
89
  #
59
90
  # @object.avatar.assign(...)
@@ -62,34 +93,47 @@ module Attached
62
93
  @file = file.tempfile
63
94
 
64
95
  extension = File.extname(file.original_filename)
65
-
96
+
66
97
  instance_set :size, file.size
67
98
  instance_set :extension, extension
68
99
  instance_set :identifier, identifier
100
+
101
+ self.queue[self.default] = self.file
102
+
103
+ process
69
104
  end
70
105
 
71
106
 
107
+ # Save an attachment.
108
+ #
72
109
  # Usage:
73
110
  #
74
111
  # @object.avatar.save
75
112
 
76
113
  def save
77
- @storage ||= Attached::Storage.medium(options[:storage], options[:credentials])
114
+ @storage ||= Attached::Storage.storage(self.medium, self.credentials)
115
+
116
+ @queue.each do |style, file|
117
+ @storage.save(file, self.path(style)) if file and self.path(style)
118
+ end
78
119
 
79
- @storage.save(self.file, self.path) if self.file and self.path
120
+ @queue = {}
80
121
  end
81
122
 
82
123
 
124
+ # Destroy an attachment.
125
+ #
83
126
  # Usage:
84
127
  #
85
128
  # @object.avatar.destroy
86
129
 
87
130
  def destroy
88
- @storage ||= Attached::Storage.medium(options[:storage], options[:credentials])
131
+ @storage ||= Attached::Storage.storage(self.medium, self.credentials)
89
132
 
90
133
  @storage.destroy(self.path) if self.path
91
134
  end
92
135
 
136
+
93
137
  # Acesss the URL for an attachment.
94
138
  #
95
139
  # Usage:
@@ -98,8 +142,8 @@ module Attached
98
142
  # @object.avatar.url(:small)
99
143
  # @object.avatar.url(:large)
100
144
 
101
- def url(style = :original)
102
- @storage ||= Attached::Storage.medium(options[:storage], options[:credentials])
145
+ def url(style = self.default)
146
+ @storage ||= Attached::Storage.storage(self.medium, self.credentials)
103
147
 
104
148
  return "#{@storage.host}#{path(style)}"
105
149
  end
@@ -113,8 +157,8 @@ module Attached
113
157
  # @object.avatar.url(:small)
114
158
  # @object.avatar.url(:large)
115
159
 
116
- def path(style = :original)
117
- path = options[:path].clone
160
+ def path(style = self.default)
161
+ path = @path.clone
118
162
 
119
163
  path.gsub!(/:name/, name.to_s)
120
164
  path.gsub!(/:style/, style.to_s)
@@ -124,6 +168,7 @@ module Attached
124
168
  return path
125
169
  end
126
170
 
171
+
127
172
  # Access the size for an attachment.
128
173
  #
129
174
  # Usage:
@@ -144,12 +189,13 @@ module Attached
144
189
 
145
190
  def extension(style = nil)
146
191
  style and
147
- options[:styles] and
148
- options[:styles][style] and
149
- options[:styles][style][:extension] or
192
+ self.styles and
193
+ self.styles[style] and
194
+ self.styles[style][:extension] or
150
195
  instance_get(:extension)
151
196
  end
152
197
 
198
+
153
199
  # Access the identifier for an attachment. It will first check the styles
154
200
  # to see if one is specified before checking the instance.
155
201
  #
@@ -159,9 +205,9 @@ module Attached
159
205
 
160
206
  def identifier(style = nil)
161
207
  style and
162
- options[:styles] and
163
- options[:styles][style] and
164
- options[:styles][style][:identifier] or
208
+ self.styles and
209
+ self.styles[style] and
210
+ self.styles[style][:identifier] or
165
211
  instance_get(:identifier)
166
212
  end
167
213
 
@@ -191,6 +237,22 @@ module Attached
191
237
 
192
238
  private
193
239
 
240
+ # Helper function for calling processors.
241
+ #
242
+ # Usage:
243
+ #
244
+ # self.process
245
+
246
+ def process
247
+ @processors.each do |processor|
248
+ self.styles.each do |style, options|
249
+ case processor
250
+ when :image then self.queue[style] = Attached::Image.process(self.queue[style] || self.file, options, self)
251
+ end
252
+ end
253
+ end
254
+ end
255
+
194
256
 
195
257
  # Helper function for setting instance variables.
196
258
  #
@@ -200,7 +262,7 @@ module Attached
200
262
 
201
263
  def instance_set(attribute, value)
202
264
  setter = :"#{self.name}_#{attribute}="
203
- self.instance.send(setter, value)
265
+ self.instance.send(setter, value) if instance.respond_to?(setter)
204
266
  end
205
267
 
206
268
 
@@ -212,7 +274,7 @@ module Attached
212
274
 
213
275
  def instance_get(attribute)
214
276
  getter = :"#{self.name}_#{attribute}"
215
- self.instance.send(getter)
277
+ self.instance.send(getter) if instance.respond_to?(getter)
216
278
  end
217
279
 
218
280
 
@@ -0,0 +1,68 @@
1
+ require 'attached/processor'
2
+
3
+ require 'rmagick'
4
+
5
+ module Attached
6
+
7
+ class Image < Processor
8
+
9
+
10
+ attr_reader :path
11
+ attr_reader :extname
12
+
13
+ # Create a processor.
14
+ #
15
+ # Parameters:
16
+ #
17
+ # * file - The file to be processed.
18
+ # * options - The options to be applied to the processing.
19
+ # * attachment - The attachment the processor is being run for.
20
+
21
+ def initialize(file, options = {}, attachment = nil)
22
+ super
23
+
24
+ @path = @file.path
25
+ @extname = File.extname(@file.path)
26
+ end
27
+
28
+
29
+ # Helper function for calling processors.
30
+ #
31
+ # Usage:
32
+ #
33
+ # self.process
34
+
35
+ def process
36
+ result = Tempfile.new(["", options['extension'] || self.extname])
37
+ result.binmode
38
+
39
+ image = ::Magick::Image.read(self.path)
40
+ image_list = ::Magick::ImageList.new
41
+
42
+ width, height, operation = self.options[:size].match(/\b(\d*)x?(\d*)\b([\#\<\>])?/)[1..3] if self.options[:size]
43
+
44
+ width ||= self.options[:width]
45
+ height ||= self.options[:height]
46
+ operation ||= self.options[:operation]
47
+
48
+ width = width.to_i
49
+ height = height.to_i
50
+
51
+ image.each do |frame|
52
+ case operation
53
+ when /!/ then puts "hi"
54
+ when /#/ then image_list << frame.resize_to_fill(width, height)
55
+ when /</ then image_list << frame.resize_to_fit(width, height)
56
+ when />/ then image_list << frame.resize_to_fit(width, height)
57
+ else image_list << frame.resize(width, height)
58
+ end
59
+ end
60
+
61
+ image_list.write(result.path)
62
+
63
+ return result
64
+ end
65
+
66
+
67
+ end
68
+ end
@@ -0,0 +1,48 @@
1
+ module Attached
2
+
3
+ class Processor
4
+
5
+
6
+ attr_accessor :file
7
+ attr_accessor :options
8
+ attr_accessor :attachment
9
+
10
+
11
+ # Create and run a processor.
12
+ #
13
+ # Parameters:
14
+ #
15
+ # * file - The file to be processed.
16
+ # * options - The options to be applied to the processing.
17
+ # * attachment - The attachment the processor is being run for.
18
+
19
+ def self.process(file, options = {}, attachment = nil)
20
+ new(file, options, attachment).process
21
+ end
22
+
23
+
24
+ # Create a processor.
25
+ #
26
+ # Parameters:
27
+ #
28
+ # * file - The file to be processed.
29
+ # * options - The options to be applied to the processing.
30
+ # * attachment - The attachment the processor is being run for.
31
+
32
+ def initialize(file, options = {}, attachment = nil)
33
+ @file = file
34
+ @options = options
35
+ @attachment = attachment
36
+ end
37
+
38
+
39
+ # Run the processor.
40
+
41
+ def process
42
+ raise NotImplementedError.new
43
+ end
44
+
45
+
46
+ end
47
+
48
+ end
@@ -9,9 +9,9 @@ module Attached
9
9
  #
10
10
  # Attached::Storage.medium(s3)
11
11
 
12
- def self.medium(storage = :s3, credentials = nil)
12
+ def self.storage(medium = :s3, credentials = nil)
13
13
 
14
- case storage
14
+ case medium
15
15
  when :s3 then return Attached::Storage::S3.new(credentials)
16
16
  end
17
17
 
@@ -50,7 +50,7 @@ module Attached
50
50
  def save(file, path)
51
51
  connect()
52
52
  begin
53
- AWS::S3::S3Object.store(path, file, bucket, :access => :authenticated_read)
53
+ AWS::S3::S3Object.store(path, file, bucket, :access => :public_read)
54
54
  rescue AWS::S3::NoSuchBucket => e
55
55
  AWS::S3::Bucket.create(bucket)
56
56
  retry
metadata CHANGED
@@ -4,9 +4,9 @@ version: !ruby/object:Gem::Version
4
4
  prerelease: false
5
5
  segments:
6
6
  - 0
7
+ - 1
7
8
  - 0
8
- - 9
9
- version: 0.0.9
9
+ version: 0.1.0
10
10
  platform: ruby
11
11
  authors:
12
12
  - Kevin Sylvestre
@@ -14,7 +14,7 @@ autorequire:
14
14
  bindir: bin
15
15
  cert_chain: []
16
16
 
17
- date: 2010-12-08 00:00:00 -05:00
17
+ date: 2010-12-15 00:00:00 -05:00
18
18
  default_executable:
19
19
  dependencies:
20
20
  - !ruby/object:Gem::Dependency
@@ -43,6 +43,19 @@ dependencies:
43
43
  version: "0"
44
44
  type: :runtime
45
45
  version_requirements: *id002
46
+ - !ruby/object:Gem::Dependency
47
+ name: rmagick
48
+ prerelease: false
49
+ requirement: &id003 !ruby/object:Gem::Requirement
50
+ none: false
51
+ requirements:
52
+ - - ">="
53
+ - !ruby/object:Gem::Version
54
+ segments:
55
+ - 0
56
+ version: "0"
57
+ type: :runtime
58
+ version_requirements: *id003
46
59
  description: Attached is a Ruby on Rails cloud attachment and processor library inspired by Paperclip. Attached lets users push files to the cloud, then perform remote processing on the files.
47
60
  email:
48
61
  - kevin@ksylvest.com
@@ -54,6 +67,8 @@ extra_rdoc_files: []
54
67
 
55
68
  files:
56
69
  - lib/attached/attachment.rb
70
+ - lib/attached/image.rb
71
+ - lib/attached/processor.rb
57
72
  - lib/attached/railtie.rb
58
73
  - lib/attached/storage/base.rb
59
74
  - lib/attached/storage/s3.rb