quick_file 0.0.1
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 +6 -0
- data/Gemfile +4 -0
- data/Rakefile +1 -0
- data/lib/quick_file/upload.rb +260 -0
- data/lib/quick_file/version.rb +3 -0
- data/lib/quick_file.rb +94 -0
- data/quick_file.gemspec +28 -0
- metadata +85 -0
data/.gitignore
ADDED
data/Gemfile
ADDED
data/Rakefile
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
require "bundler/gem_tasks"
|
@@ -0,0 +1,260 @@
|
|
1
|
+
module QuickFile
|
2
|
+
|
3
|
+
module Upload
|
4
|
+
extend ActiveSupport::Concern
|
5
|
+
|
6
|
+
##
|
7
|
+
# required fields: id, state, original_filename, styles
|
8
|
+
# steps
|
9
|
+
# 1. cache
|
10
|
+
# 2. process
|
11
|
+
# 3. store
|
12
|
+
|
13
|
+
STATES = {:loaded => 0, :cached => 1, :processing => 2, :processed => 3, :storing => 4, :stored => 5, :deleted => 6, :error => 7}
|
14
|
+
|
15
|
+
included do
|
16
|
+
cattr_accessor :processes
|
17
|
+
self.processes = {}
|
18
|
+
|
19
|
+
end
|
20
|
+
|
21
|
+
module ClassMethods
|
22
|
+
def add_style(style_name, blk)
|
23
|
+
processes[style_name.to_s] = {:blk => blk}
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
module InstanceMethods
|
28
|
+
def initialize
|
29
|
+
@file = nil
|
30
|
+
styles = {}
|
31
|
+
errors = []
|
32
|
+
end
|
33
|
+
|
34
|
+
def uploaded_file=(uf)
|
35
|
+
errors = []
|
36
|
+
@file = uf
|
37
|
+
self.original_filename = uf.original_filename
|
38
|
+
self.state = STATES[:loaded]
|
39
|
+
cache!
|
40
|
+
end
|
41
|
+
|
42
|
+
def loaded?
|
43
|
+
self.state == STATES[:loaded]
|
44
|
+
end
|
45
|
+
|
46
|
+
def cached?
|
47
|
+
self.state == STATES[:cached]
|
48
|
+
end
|
49
|
+
|
50
|
+
def processed?
|
51
|
+
self.state == STATES[:processed]
|
52
|
+
end
|
53
|
+
|
54
|
+
def stored?
|
55
|
+
self.state == STATES[:stored]
|
56
|
+
end
|
57
|
+
|
58
|
+
def error?
|
59
|
+
self.state == STATES[:error]
|
60
|
+
end
|
61
|
+
|
62
|
+
def sanitized_basename
|
63
|
+
File.basename original_filename.gsub( /[^a-zA-Z0-9_\-\.]/, '_'), File.extname(original_filename)
|
64
|
+
end
|
65
|
+
|
66
|
+
def sanitized_filename
|
67
|
+
File.basename original_filename.gsub( /[^a-zA-Z0-9_\-\.]/, '_')
|
68
|
+
end
|
69
|
+
|
70
|
+
def extension
|
71
|
+
File.extname(original_filename)
|
72
|
+
end
|
73
|
+
|
74
|
+
def cache!
|
75
|
+
return unless loaded?
|
76
|
+
cp = save_cache_file(QuickFile.generate_cache_name(extension), @file)
|
77
|
+
styles["original"] = {"cache" => cp,
|
78
|
+
"ct" => QuickFile.content_type_for(cp),
|
79
|
+
"sz" => File.size(cp)}
|
80
|
+
self.validate!
|
81
|
+
if errors.size > 0
|
82
|
+
self.state = STATES[:error]
|
83
|
+
File.delete(cp)
|
84
|
+
else
|
85
|
+
self.state = STATES[:cached]
|
86
|
+
end
|
87
|
+
self.save
|
88
|
+
end
|
89
|
+
|
90
|
+
def save_cache_file(cn, file)
|
91
|
+
Dir.mkdir QuickFile::CACHE_DIR unless File.directory?(QuickFile::CACHE_DIR)
|
92
|
+
cp = QuickFile.cache_path(cn)
|
93
|
+
File.open(cp, "wb") { |f| f.write(file.read) }
|
94
|
+
cp
|
95
|
+
end
|
96
|
+
|
97
|
+
def process!
|
98
|
+
return unless cached?
|
99
|
+
self.state = STATES[:processing]
|
100
|
+
save
|
101
|
+
begin
|
102
|
+
puts "#{processes.size} processes"
|
103
|
+
processes.each do |style_name, opts|
|
104
|
+
puts "Processing #{style_name}..."
|
105
|
+
fn = opts[:blk].call(styles["original"]["cache"])
|
106
|
+
unless fn.nil?
|
107
|
+
if (styles.key?(style_name) && !styles[style_name]["cache"].nil?)
|
108
|
+
File.delete(styles[style_name]["cache"])
|
109
|
+
end
|
110
|
+
styles[style_name.to_s] = {"cache" => fn,
|
111
|
+
"ct" => QuickFile.content_type_for(fn),
|
112
|
+
"sz" => File.size(fn)}
|
113
|
+
end
|
114
|
+
end
|
115
|
+
self.state = STATES[:processed]
|
116
|
+
rescue StandardError => e
|
117
|
+
puts e.message
|
118
|
+
self.errors << "PROCESS: #{e.message}"
|
119
|
+
self.state = STATES[:error]
|
120
|
+
end
|
121
|
+
|
122
|
+
self.save
|
123
|
+
|
124
|
+
store!
|
125
|
+
end
|
126
|
+
|
127
|
+
def reprocess!
|
128
|
+
return unless (stored? || error?)
|
129
|
+
# download original file
|
130
|
+
cp = QuickFile.new_cache_file File.extname(self.path)
|
131
|
+
QuickFile.download(url, cp)
|
132
|
+
styles["original"] = {"cache" => cp,
|
133
|
+
"ct" => QuickFile.content_type_for(cp),
|
134
|
+
"sz" => File.size(cp)}
|
135
|
+
self.state = STATES[:cached]
|
136
|
+
self.save
|
137
|
+
self.process!
|
138
|
+
end
|
139
|
+
|
140
|
+
def add_file!(style_name, path)
|
141
|
+
styles[style_name.to_s] = {"path" => path}
|
142
|
+
get_style(style_name.to_s)
|
143
|
+
self.state = STATES[:stored]
|
144
|
+
save
|
145
|
+
end
|
146
|
+
|
147
|
+
def path(style_name=nil)
|
148
|
+
style_name ||= :original
|
149
|
+
styles[style_name.to_s]["path"]
|
150
|
+
end
|
151
|
+
|
152
|
+
def content_type(style_name=nil)
|
153
|
+
style_name ||= :original
|
154
|
+
styles[style_name.to_s]["ct"]
|
155
|
+
end
|
156
|
+
|
157
|
+
def size(style_name=nil)
|
158
|
+
style_name ||= :original
|
159
|
+
styles[style_name.to_s]["sz"]
|
160
|
+
end
|
161
|
+
|
162
|
+
def is_image?(style_name=nil)
|
163
|
+
style_name ||= :original
|
164
|
+
return false if content_type(style_name).nil?
|
165
|
+
content_type(style_name).include? "image"
|
166
|
+
end
|
167
|
+
|
168
|
+
def is_video?(style_name=nil)
|
169
|
+
style_name ||= :original
|
170
|
+
fp = styles[style_name]["path"] || styles[style_name]["cache"]
|
171
|
+
QuickFile.is_video_file? fp
|
172
|
+
end
|
173
|
+
|
174
|
+
def style_exists?(style_name)
|
175
|
+
!styles[style_name.to_s].nil?
|
176
|
+
end
|
177
|
+
|
178
|
+
def store!
|
179
|
+
return unless processed?
|
180
|
+
begin
|
181
|
+
styles.keys.each do |style_name|
|
182
|
+
store_style! style_name unless styles[style_name]["cache"].nil?
|
183
|
+
end
|
184
|
+
self.state = STATES[:stored]
|
185
|
+
rescue StandardError => e
|
186
|
+
puts e.message
|
187
|
+
self.errors << "STORE: #{e.message}"
|
188
|
+
if self.errors.count < 5
|
189
|
+
self.store!
|
190
|
+
else
|
191
|
+
self.state = STATES[:error]
|
192
|
+
end
|
193
|
+
end
|
194
|
+
save
|
195
|
+
end
|
196
|
+
|
197
|
+
def storage_protocol
|
198
|
+
case storage_type.to_sym
|
199
|
+
when :s3
|
200
|
+
return :fog
|
201
|
+
when :fog
|
202
|
+
return :fog
|
203
|
+
end
|
204
|
+
end
|
205
|
+
|
206
|
+
def store_style!(style_name)
|
207
|
+
fn = styles[style_name]["cache"]
|
208
|
+
sp = storage_path(style_name, File.extname(fn))
|
209
|
+
if storage_protocol == :fog
|
210
|
+
QuickFile.fog_directory.files.create({
|
211
|
+
:body => File.open(fn).read,
|
212
|
+
:content_type => QuickFile.content_type_for(fn),
|
213
|
+
:key => sp,
|
214
|
+
:public => QuickFile.options[:fog_public]
|
215
|
+
})
|
216
|
+
end
|
217
|
+
styles[style_name]["path"] = sp
|
218
|
+
styles[style_name]["ct"] = QuickFile.content_type_for(fn)
|
219
|
+
styles[style_name]["sz"] = File.size(fn)
|
220
|
+
styles[style_name].delete("cache")
|
221
|
+
File.delete(fn)
|
222
|
+
|
223
|
+
save
|
224
|
+
end
|
225
|
+
|
226
|
+
def get_style(style_name)
|
227
|
+
fn = path(style_name)
|
228
|
+
if storage_protocol == :fog
|
229
|
+
f = QuickFile.fog_directory.files.get(fn)
|
230
|
+
if f.nil?
|
231
|
+
styles[style_name]["ct"] = QuickFile.content_type_for(fn)
|
232
|
+
styles[style_name]["sz"] = 0
|
233
|
+
else
|
234
|
+
styles[style_name]["ct"] = f.content_type.nil? ? QuickFile.content_type_for(fn) : f.content_type
|
235
|
+
styles[style_name]["sz"] = f.content_length
|
236
|
+
end
|
237
|
+
save
|
238
|
+
end
|
239
|
+
f
|
240
|
+
end
|
241
|
+
|
242
|
+
def url(style_name=nil, opts={:secure=>true})
|
243
|
+
proto = opts[:secure] ? "https://" : "http://"
|
244
|
+
style_name ||= "original"
|
245
|
+
return default_url(style_name) unless (styles[style_name] && styles[style_name]["path"])
|
246
|
+
"#{proto}#{QuickFile.host_url[storage_type.to_sym]}#{styles[style_name]["path"]}"
|
247
|
+
end
|
248
|
+
|
249
|
+
def delete
|
250
|
+
# delete uploaded files
|
251
|
+
styles.each do |k,v|
|
252
|
+
QuickFile.fog_directory.files.new(:key => v["path"]).destroy if v["path"]
|
253
|
+
File.delete(v["cache"]) if (v["cache"] && File.exists?(v["cache"]))
|
254
|
+
end
|
255
|
+
self.state = STATES[:deleted]
|
256
|
+
end
|
257
|
+
end
|
258
|
+
|
259
|
+
end
|
260
|
+
end
|
data/lib/quick_file.rb
ADDED
@@ -0,0 +1,94 @@
|
|
1
|
+
require "quick_file/version"
|
2
|
+
require "quick_file/upload"
|
3
|
+
require "RMagick"
|
4
|
+
|
5
|
+
module QuickFile
|
6
|
+
CACHE_DIR = "/tmp"
|
7
|
+
|
8
|
+
class << self
|
9
|
+
def configure
|
10
|
+
yield options if block_given?
|
11
|
+
end
|
12
|
+
|
13
|
+
def options
|
14
|
+
@@options ||= UPLOAD_OPTIONS
|
15
|
+
end
|
16
|
+
|
17
|
+
def generate_cache_name(ext)
|
18
|
+
"#{SecureRandom.hex(5)}#{ext}"
|
19
|
+
end
|
20
|
+
|
21
|
+
def new_cache_file(ext)
|
22
|
+
QuickFile.cache_path QuickFile.generate_cache_name(ext)
|
23
|
+
end
|
24
|
+
|
25
|
+
def content_type_for(filename)
|
26
|
+
mime = MIME::Types.type_for(filename)[0]
|
27
|
+
return mime.simplified if mime
|
28
|
+
return "application/file"
|
29
|
+
end
|
30
|
+
|
31
|
+
def is_video_file?(filename)
|
32
|
+
filename.downcase.end_with?('.mov', '.3gp', '.wmv', '.m4v', '.mp4')
|
33
|
+
end
|
34
|
+
|
35
|
+
def fog_connection
|
36
|
+
@@fog_connection ||= begin
|
37
|
+
Fog::Storage.new(options[:fog_credentials])
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
def fog_directory
|
42
|
+
@@fog_directory ||= begin
|
43
|
+
fog_connection.directories.new(
|
44
|
+
:key => options[:fog_directory],
|
45
|
+
:public => options[:fog_public]
|
46
|
+
)
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
def host_url
|
51
|
+
@@host_url ||= begin
|
52
|
+
{
|
53
|
+
:s3 => "s3.amazonaws.com/#{options[:fog_directory]}/"
|
54
|
+
}
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
def resize_to_fit(file, x, y)
|
59
|
+
img = Magick::Image.read(file).first
|
60
|
+
nim = img.resize_to_fit x, y
|
61
|
+
outfile = cache_path(generate_cache_name(File.extname(file)))
|
62
|
+
nim.write outfile
|
63
|
+
outfile
|
64
|
+
end
|
65
|
+
|
66
|
+
def resize_to_fill(file, x, y)
|
67
|
+
img = Magick::Image.read(file).first
|
68
|
+
nim = img.resize_to_fill(x, y)
|
69
|
+
outfile = cache_path(generate_cache_name(File.extname(file)))
|
70
|
+
nim.write outfile
|
71
|
+
outfile
|
72
|
+
end
|
73
|
+
|
74
|
+
def cache_path(cn)
|
75
|
+
File.join(CACHE_DIR, cn)
|
76
|
+
end
|
77
|
+
|
78
|
+
def download(url, to)
|
79
|
+
out = open(to, "wb")
|
80
|
+
out.write(open(url).read)
|
81
|
+
out.close
|
82
|
+
end
|
83
|
+
|
84
|
+
def image_from_url(url)
|
85
|
+
open(url, 'rb') do |f|
|
86
|
+
image = Magick::Image.from_blob(f.read).first
|
87
|
+
end
|
88
|
+
image
|
89
|
+
end
|
90
|
+
|
91
|
+
|
92
|
+
end
|
93
|
+
|
94
|
+
end
|
data/quick_file.gemspec
ADDED
@@ -0,0 +1,28 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
$:.push File.expand_path("../lib", __FILE__)
|
3
|
+
require "quick_file/version"
|
4
|
+
|
5
|
+
Gem::Specification.new do |s|
|
6
|
+
s.name = "quick_file"
|
7
|
+
s.version = QuickFile::VERSION
|
8
|
+
s.authors = ["Alan Graham"]
|
9
|
+
s.email = ["alangraham5@gmail.com"]
|
10
|
+
s.homepage = ""
|
11
|
+
s.summary = %q{A file upload library}
|
12
|
+
s.description = %q{A file upload library for use with MongoDB and S3}
|
13
|
+
|
14
|
+
s.rubyforge_project = "quick_file"
|
15
|
+
|
16
|
+
s.files = `git ls-files`.split("\n")
|
17
|
+
s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
|
18
|
+
s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
|
19
|
+
s.require_paths = ["lib"]
|
20
|
+
|
21
|
+
s.add_dependency "rmagick"
|
22
|
+
s.add_dependency "fog"
|
23
|
+
s.add_dependency "active_support"
|
24
|
+
|
25
|
+
# specify any dependencies here; for example:
|
26
|
+
# s.add_development_dependency "rspec"
|
27
|
+
# s.add_runtime_dependency "rest-client"
|
28
|
+
end
|
metadata
ADDED
@@ -0,0 +1,85 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: quick_file
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.1
|
5
|
+
prerelease:
|
6
|
+
platform: ruby
|
7
|
+
authors:
|
8
|
+
- Alan Graham
|
9
|
+
autorequire:
|
10
|
+
bindir: bin
|
11
|
+
cert_chain: []
|
12
|
+
date: 2012-03-27 00:00:00.000000000Z
|
13
|
+
dependencies:
|
14
|
+
- !ruby/object:Gem::Dependency
|
15
|
+
name: rmagick
|
16
|
+
requirement: &70310906168420 !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: *70310906168420
|
25
|
+
- !ruby/object:Gem::Dependency
|
26
|
+
name: fog
|
27
|
+
requirement: &70310906168000 !ruby/object:Gem::Requirement
|
28
|
+
none: false
|
29
|
+
requirements:
|
30
|
+
- - ! '>='
|
31
|
+
- !ruby/object:Gem::Version
|
32
|
+
version: '0'
|
33
|
+
type: :runtime
|
34
|
+
prerelease: false
|
35
|
+
version_requirements: *70310906168000
|
36
|
+
- !ruby/object:Gem::Dependency
|
37
|
+
name: active_support
|
38
|
+
requirement: &70310906167580 !ruby/object:Gem::Requirement
|
39
|
+
none: false
|
40
|
+
requirements:
|
41
|
+
- - ! '>='
|
42
|
+
- !ruby/object:Gem::Version
|
43
|
+
version: '0'
|
44
|
+
type: :runtime
|
45
|
+
prerelease: false
|
46
|
+
version_requirements: *70310906167580
|
47
|
+
description: A file upload library for use with MongoDB and S3
|
48
|
+
email:
|
49
|
+
- alangraham5@gmail.com
|
50
|
+
executables: []
|
51
|
+
extensions: []
|
52
|
+
extra_rdoc_files: []
|
53
|
+
files:
|
54
|
+
- .gitignore
|
55
|
+
- Gemfile
|
56
|
+
- Rakefile
|
57
|
+
- lib/quick_file.rb
|
58
|
+
- lib/quick_file/upload.rb
|
59
|
+
- lib/quick_file/version.rb
|
60
|
+
- quick_file.gemspec
|
61
|
+
homepage: ''
|
62
|
+
licenses: []
|
63
|
+
post_install_message:
|
64
|
+
rdoc_options: []
|
65
|
+
require_paths:
|
66
|
+
- lib
|
67
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
68
|
+
none: false
|
69
|
+
requirements:
|
70
|
+
- - ! '>='
|
71
|
+
- !ruby/object:Gem::Version
|
72
|
+
version: '0'
|
73
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
74
|
+
none: false
|
75
|
+
requirements:
|
76
|
+
- - ! '>='
|
77
|
+
- !ruby/object:Gem::Version
|
78
|
+
version: '0'
|
79
|
+
requirements: []
|
80
|
+
rubyforge_project: quick_file
|
81
|
+
rubygems_version: 1.8.15
|
82
|
+
signing_key:
|
83
|
+
specification_version: 3
|
84
|
+
summary: A file upload library
|
85
|
+
test_files: []
|