upload_cache 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (4) hide show
  1. data/README +42 -0
  2. data/Rakefile +365 -0
  3. data/lib/upload_cache.rb +240 -0
  4. metadata +82 -0
data/README ADDED
@@ -0,0 +1,42 @@
1
+ NAME
2
+ upload_cache.rb
3
+
4
+ DESCRIPTION
5
+ a small utility library to facility caching http file uploads between
6
+ form validation failures. designed for rails, but usable anywhere.
7
+
8
+ USAGE
9
+ in the controller
10
+
11
+ def upload
12
+ @upload_cache = UploadCache.for(params, :upload)
13
+
14
+ @record = Model.new(params)
15
+
16
+ if request.get?
17
+ render and return
18
+ end
19
+
20
+ if request.post?
21
+ @record.save!
22
+ @upload_cache.clear!
23
+ end
24
+ end
25
+
26
+
27
+ in the view
28
+
29
+ <input type='file' name='upload />
30
+
31
+ <%= @upload_cache.hidden %>
32
+
33
+ <!-- optionally, you can show any uploaded upload -->
34
+
35
+ <% if url = @upload_cache.url %>
36
+ you already uploaded: <img src='<%= raw url %>' />
37
+ <% end %>
38
+
39
+
40
+ in a rake task
41
+
42
+ UploadCache.clear! ### nuke old files once per day
data/Rakefile ADDED
@@ -0,0 +1,365 @@
1
+ This.rubyforge_project = 'codeforpeople'
2
+ This.author = "Ara T. Howard"
3
+ This.email = "ara.t.howard@gmail.com"
4
+ This.homepage = "http://github.com/ahoward/#{ This.lib }"
5
+
6
+
7
+ task :default do
8
+ puts((Rake::Task.tasks.map{|task| task.name.gsub(/::/,':')} - ['default']).sort)
9
+ end
10
+
11
+ task :test do
12
+ run_tests!
13
+ end
14
+
15
+ namespace :test do
16
+ task(:unit){ run_tests!(:unit) }
17
+ task(:functional){ run_tests!(:functional) }
18
+ task(:integration){ run_tests!(:integration) }
19
+ end
20
+
21
+ def run_tests!(which = nil)
22
+ which ||= '**'
23
+ test_dir = File.join(This.dir, "test")
24
+ test_glob ||= File.join(test_dir, "#{ which }/**_test.rb")
25
+ test_rbs = Dir.glob(test_glob).sort
26
+
27
+ div = ('=' * 119)
28
+ line = ('-' * 119)
29
+ helper = "-r ./test/helper.rb" if test(?e, "./test/helper.rb")
30
+
31
+ test_rbs.each_with_index do |test_rb, index|
32
+ testno = index + 1
33
+ command = "#{ This.ruby } -I ./lib -I ./test/lib #{ helper } #{ test_rb }"
34
+
35
+ puts
36
+ say(div, :color => :cyan, :bold => true)
37
+ say("@#{ testno } => ", :bold => true, :method => :print)
38
+ say(command, :color => :cyan, :bold => true)
39
+ say(line, :color => :cyan, :bold => true)
40
+
41
+ system(command)
42
+
43
+ say(line, :color => :cyan, :bold => true)
44
+
45
+ status = $?.exitstatus
46
+
47
+ if status.zero?
48
+ say("@#{ testno } <= ", :bold => true, :color => :white, :method => :print)
49
+ say("SUCCESS", :color => :green, :bold => true)
50
+ else
51
+ say("@#{ testno } <= ", :bold => true, :color => :white, :method => :print)
52
+ say("FAILURE", :color => :red, :bold => true)
53
+ end
54
+ say(line, :color => :cyan, :bold => true)
55
+
56
+ exit(status) unless status.zero?
57
+ end
58
+ end
59
+
60
+
61
+ task :gemspec do
62
+ ignore_extensions = 'git', 'svn', 'tmp', /sw./, 'bak', 'gem'
63
+ ignore_directories = %w[ pkg ]
64
+ ignore_files = %w[ test/log ]
65
+
66
+ shiteless =
67
+ lambda do |list|
68
+ list.delete_if do |entry|
69
+ next unless test(?e, entry)
70
+ extension = File.basename(entry).split(%r/[.]/).last
71
+ ignore_extensions.any?{|ext| ext === extension}
72
+ end
73
+ list.delete_if do |entry|
74
+ next unless test(?d, entry)
75
+ dirname = File.expand_path(entry)
76
+ ignore_directories.any?{|dir| File.expand_path(dir) == dirname}
77
+ end
78
+ list.delete_if do |entry|
79
+ next unless test(?f, entry)
80
+ filename = File.expand_path(entry)
81
+ ignore_files.any?{|file| File.expand_path(file) == filename}
82
+ end
83
+ end
84
+
85
+ lib = This.lib
86
+ object = This.object
87
+ version = This.version
88
+ files = shiteless[Dir::glob("**/**")]
89
+ executables = shiteless[Dir::glob("bin/*")].map{|exe| File.basename(exe)}
90
+ has_rdoc = true #File.exist?('doc')
91
+ test_files = "test/#{ lib }.rb" if File.file?("test/#{ lib }.rb")
92
+ summary = object.respond_to?(:summary) ? object.summary : "summary: #{ lib } kicks the ass"
93
+ description = object.respond_to?(:description) ? object.description : "description: #{ lib } kicks the ass"
94
+
95
+ if This.extensions.nil?
96
+ This.extensions = []
97
+ extensions = This.extensions
98
+ %w( Makefile configure extconf.rb ).each do |ext|
99
+ extensions << ext if File.exists?(ext)
100
+ end
101
+ end
102
+ extensions = [extensions].flatten.compact
103
+
104
+ template =
105
+ if test(?e, 'gemspec.erb')
106
+ Template{ IO.read('gemspec.erb') }
107
+ else
108
+ Template {
109
+ <<-__
110
+ ## #{ lib }.gemspec
111
+ #
112
+
113
+ Gem::Specification::new do |spec|
114
+ spec.name = #{ lib.inspect }
115
+ spec.version = #{ version.inspect }
116
+ spec.platform = Gem::Platform::RUBY
117
+ spec.summary = #{ lib.inspect }
118
+ spec.description = #{ description.inspect }
119
+
120
+ spec.files = #{ files.inspect }
121
+ spec.executables = #{ executables.inspect }
122
+
123
+ spec.require_path = "lib"
124
+
125
+ spec.has_rdoc = #{ has_rdoc.inspect }
126
+ spec.test_files = #{ test_files.inspect }
127
+
128
+ # spec.add_dependency 'lib', '>= version'
129
+ spec.add_dependency 'uuidtools'
130
+
131
+ spec.extensions.push(*#{ extensions.inspect })
132
+
133
+ spec.rubyforge_project = #{ This.rubyforge_project.inspect }
134
+ spec.author = #{ This.author.inspect }
135
+ spec.email = #{ This.email.inspect }
136
+ spec.homepage = #{ This.homepage.inspect }
137
+ end
138
+ __
139
+ }
140
+ end
141
+
142
+ Fu.mkdir_p(This.pkgdir)
143
+ This.gemspec = File.join(This.dir, "#{ This.lib }.gemspec") #File.join(This.pkgdir, "gemspec.rb")
144
+ open("#{ This.gemspec }", "w"){|fd| fd.puts(template)}
145
+ end
146
+
147
+ task :gem => [:clean, :gemspec] do
148
+ Fu.mkdir_p(This.pkgdir)
149
+ before = Dir['*.gem']
150
+ cmd = "gem build #{ This.gemspec }"
151
+ `#{ cmd }`
152
+ after = Dir['*.gem']
153
+ gem = ((after - before).first || after.first) or abort('no gem!')
154
+ Fu.mv gem, This.pkgdir
155
+ This.gem = File.basename(gem)
156
+ end
157
+
158
+ task :readme do
159
+ samples = ''
160
+ prompt = '~ > '
161
+ lib = This.lib
162
+ version = This.version
163
+
164
+ Dir['sample*/*'].sort.each do |sample|
165
+ samples << "\n" << " <========< #{ sample } >========>" << "\n\n"
166
+
167
+ cmd = "cat #{ sample }"
168
+ samples << Util.indent(prompt + cmd, 2) << "\n\n"
169
+ samples << Util.indent(`#{ cmd }`, 4) << "\n"
170
+
171
+ cmd = "ruby #{ sample }"
172
+ samples << Util.indent(prompt + cmd, 2) << "\n\n"
173
+
174
+ cmd = "ruby -e'STDOUT.sync=true; exec %(ruby -I ./lib #{ sample })'"
175
+ samples << Util.indent(`#{ cmd } 2>&1`, 4) << "\n"
176
+ end
177
+
178
+ template =
179
+ if test(?e, 'readme.erb')
180
+ Template{ IO.read('readme.erb') }
181
+ else
182
+ Template {
183
+ <<-__
184
+ NAME
185
+ #{ lib }
186
+
187
+ DESCRIPTION
188
+
189
+ INSTALL
190
+ gem install #{ lib }
191
+
192
+ SAMPLES
193
+ #{ samples }
194
+ __
195
+ }
196
+ end
197
+
198
+ open("README", "w"){|fd| fd.puts template}
199
+ end
200
+
201
+
202
+ task :clean do
203
+ Dir[File.join(This.pkgdir, '**/**')].each{|entry| Fu.rm_rf(entry)}
204
+ end
205
+
206
+
207
+ task :release => [:clean, :gemspec, :gem] do
208
+ gems = Dir[File.join(This.pkgdir, '*.gem')].flatten
209
+ raise "which one? : #{ gems.inspect }" if gems.size > 1
210
+ raise "no gems?" if gems.size < 1
211
+ cmd = "rubyforge login && rubyforge add_release #{ This.rubyforge_project } #{ This.lib } #{ This.version } #{ This.pkgdir }/#{ This.gem }"
212
+ puts cmd
213
+ system cmd
214
+ cmd = "gem push #{ This.pkgdir }/#{ This.gem }"
215
+ puts cmd
216
+ system cmd
217
+ end
218
+
219
+
220
+
221
+
222
+
223
+ BEGIN {
224
+ # support for this rakefile
225
+ #
226
+ $VERBOSE = nil
227
+
228
+ require 'ostruct'
229
+ require 'erb'
230
+ require 'fileutils'
231
+ require 'rbconfig'
232
+
233
+ # fu shortcut
234
+ #
235
+ Fu = FileUtils
236
+
237
+ # cache a bunch of stuff about this rakefile/environment
238
+ #
239
+ This = OpenStruct.new
240
+
241
+ This.file = File.expand_path(__FILE__)
242
+ This.dir = File.dirname(This.file)
243
+ This.pkgdir = File.join(This.dir, 'pkg')
244
+
245
+ # grok lib
246
+ #
247
+ lib = ENV['LIB']
248
+ unless lib
249
+ lib = File.basename(Dir.pwd).sub(/[-].*$/, '')
250
+ end
251
+ This.lib = lib
252
+
253
+ # grok version
254
+ #
255
+ version = ENV['VERSION']
256
+ unless version
257
+ require "./lib/#{ This.lib }"
258
+ This.name = lib.capitalize
259
+ This.object = eval(This.name)
260
+ version = This.object.send(:version)
261
+ end
262
+ This.version = version
263
+
264
+ # we need to know the name of the lib an it's version
265
+ #
266
+ abort('no lib') unless This.lib
267
+ abort('no version') unless This.version
268
+
269
+ # discover full path to this ruby executable
270
+ #
271
+ c = Config::CONFIG
272
+ bindir = c["bindir"] || c['BINDIR']
273
+ ruby_install_name = c['ruby_install_name'] || c['RUBY_INSTALL_NAME'] || 'ruby'
274
+ ruby_ext = c['EXEEXT'] || ''
275
+ ruby = File.join(bindir, (ruby_install_name + ruby_ext))
276
+ This.ruby = ruby
277
+
278
+ # some utils
279
+ #
280
+ module Util
281
+ def indent(s, n = 2)
282
+ s = unindent(s)
283
+ ws = ' ' * n
284
+ s.gsub(%r/^/, ws)
285
+ end
286
+
287
+ def unindent(s)
288
+ indent = nil
289
+ s.each_line do |line|
290
+ next if line =~ %r/^\s*$/
291
+ indent = line[%r/^\s*/] and break
292
+ end
293
+ indent ? s.gsub(%r/^#{ indent }/, "") : s
294
+ end
295
+ extend self
296
+ end
297
+
298
+ # template support
299
+ #
300
+ class Template
301
+ def initialize(&block)
302
+ @block = block.binding
303
+ @template = block.call.to_s
304
+ end
305
+ def expand(b=nil)
306
+ ERB.new(Util.unindent(@template)).result(b||@block)
307
+ end
308
+ alias_method 'to_s', 'expand'
309
+ end
310
+ def Template(*args, &block) Template.new(*args, &block) end
311
+
312
+ # colored console output support
313
+ #
314
+ This.ansi = {
315
+ :clear => "\e[0m",
316
+ :reset => "\e[0m",
317
+ :erase_line => "\e[K",
318
+ :erase_char => "\e[P",
319
+ :bold => "\e[1m",
320
+ :dark => "\e[2m",
321
+ :underline => "\e[4m",
322
+ :underscore => "\e[4m",
323
+ :blink => "\e[5m",
324
+ :reverse => "\e[7m",
325
+ :concealed => "\e[8m",
326
+ :black => "\e[30m",
327
+ :red => "\e[31m",
328
+ :green => "\e[32m",
329
+ :yellow => "\e[33m",
330
+ :blue => "\e[34m",
331
+ :magenta => "\e[35m",
332
+ :cyan => "\e[36m",
333
+ :white => "\e[37m",
334
+ :on_black => "\e[40m",
335
+ :on_red => "\e[41m",
336
+ :on_green => "\e[42m",
337
+ :on_yellow => "\e[43m",
338
+ :on_blue => "\e[44m",
339
+ :on_magenta => "\e[45m",
340
+ :on_cyan => "\e[46m",
341
+ :on_white => "\e[47m"
342
+ }
343
+ def say(phrase, *args)
344
+ options = args.last.is_a?(Hash) ? args.pop : {}
345
+ options[:color] = args.shift.to_s.to_sym unless args.empty?
346
+ keys = options.keys
347
+ keys.each{|key| options[key.to_s.to_sym] = options.delete(key)}
348
+
349
+ color = options[:color]
350
+ bold = options.has_key?(:bold)
351
+
352
+ parts = [phrase]
353
+ parts.unshift(This.ansi[color]) if color
354
+ parts.unshift(This.ansi[:bold]) if bold
355
+ parts.push(This.ansi[:clear]) if parts.size > 1
356
+
357
+ method = options[:method] || :puts
358
+
359
+ Kernel.send(method, parts.join)
360
+ end
361
+
362
+ # always run out of the project dir
363
+ #
364
+ Dir.chdir(This.dir)
365
+ }
@@ -0,0 +1,240 @@
1
+ require 'uuidtools'
2
+ require 'fileutils'
3
+ require 'cgi'
4
+
5
+ class UploadCache
6
+ Version = '1.0.0'
7
+
8
+ README = <<-__
9
+ NAME
10
+ upload_cache.rb
11
+
12
+ DESCRIPTION
13
+ a small utility library to facility caching http file uploads between
14
+ form validation failures. designed for rails, but usable anywhere.
15
+
16
+ USAGE
17
+ in the controller
18
+
19
+ def upload
20
+ @upload_cache = UploadCache.for(params, :upload)
21
+
22
+ @record = Model.new(params)
23
+
24
+ if request.get?
25
+ render and return
26
+ end
27
+
28
+ if request.post?
29
+ @record.save!
30
+ @upload_cache.clear!
31
+ end
32
+ end
33
+
34
+
35
+ in the view
36
+
37
+ <input type='file' name='upload />
38
+
39
+ <%= @upload_cache.hidden %>
40
+
41
+ <!-- optionally, you can show any uploaded upload -->
42
+
43
+ <% if url = @upload_cache.url %>
44
+ you already uploaded: <img src='<%= raw url %>' />
45
+ <% end %>
46
+
47
+
48
+ in a rake task
49
+
50
+ UploadCache.clear! ### nuke old files once per day
51
+
52
+ __
53
+
54
+ class << UploadCache
55
+ def version
56
+ UploadCache::Version
57
+ end
58
+
59
+ def base
60
+ @base ||= 'system/uploads/cache'
61
+ end
62
+
63
+ def base=(base)
64
+ @base = base.to_s.sub(%r|^/+|, '')
65
+ end
66
+
67
+ def url
68
+ '/' + base
69
+ end
70
+
71
+ def root
72
+ @root ||= File.join(Rails.root, 'public', base)
73
+ end
74
+
75
+ def uuid(*args)
76
+ UUIDTools::UUID.timestamp_create.to_s
77
+ end
78
+
79
+ def tmpdir(&block)
80
+ tmpdir = File.join(root, uuid)
81
+
82
+ if block
83
+ FileUtils.mkdir_p(tmpdir)
84
+ block.call(tmpdir)
85
+ else
86
+ tmpdir
87
+ end
88
+ end
89
+
90
+ def cleanname(path)
91
+ basename = File.basename(path.to_s)
92
+ CGI.unescape(basename).gsub(%r/[^0-9a-zA-Z_@)(~.-]/, '_').gsub(%r/_+/,'_')
93
+ end
94
+
95
+ def cache_key_for(key)
96
+ "#{ key }__cache"
97
+ end
98
+
99
+ def for(params, key = :upload)
100
+ upload = params[key]
101
+ if upload.respond_to?(:read)
102
+ tmpdir do |tmp|
103
+ original_basename = upload.respond_to?(:original_path) ? upload.original_path : upload.original_filename
104
+ basename = cleanname(original_basename)
105
+
106
+ path = File.join(tmp, basename)
107
+ open(path, 'w'){|fd| fd.write(upload.read)}
108
+ upload_cache = new(key, path)
109
+ params[key] = upload_cache.io
110
+ return upload_cache
111
+ end
112
+ end
113
+
114
+ cache_key = cache_key_for(key)
115
+ upload_cache = params[cache_key]
116
+ if upload_cache
117
+ dirname, basename = File.split(upload_cache)
118
+ path = root + '/' + File.join(File.basename(dirname), basename)
119
+ upload_cache = new(key, path)
120
+ params[key] = upload_cache.io
121
+ return upload_cache
122
+ end
123
+
124
+ return new(key, path=nil)
125
+ end
126
+
127
+ def finalizer(object_id)
128
+ if fd = IOs[object_id]
129
+ IO.for_fd(fd).close
130
+ IOs.delete(object_id)
131
+ end
132
+ end
133
+
134
+ UUIDPattern = %r/^[a-zA-Z0-9-]+$/io
135
+ Age = 60 * 60 * 24
136
+
137
+ def clear!(options = {})
138
+ glob = File.join(root, '*')
139
+ age = Integer(options[:age] || options['age'] || Age)
140
+ since = options[:since] || options['since'] || Time.now
141
+
142
+ Dir.glob(glob) do |entry|
143
+ begin
144
+ next unless test(?d, entry)
145
+ next unless File.basename(entry) =~ UUIDPattern
146
+
147
+ files = Dir.glob(File.join(entry, '**/**'))
148
+
149
+ all_files_are_old =
150
+ files.all? do |file|
151
+ begin
152
+ stat = File.stat(file)
153
+ age = since - stat.atime
154
+ age >= Age
155
+ rescue
156
+ false
157
+ end
158
+ end
159
+
160
+ FileUtils.rm_rf(entry) if all_files_are_old
161
+ rescue
162
+ next
163
+ end
164
+ end
165
+ end
166
+ end
167
+
168
+ attr_accessor :key
169
+ attr_accessor :cache_key
170
+ attr_accessor :path
171
+ attr_accessor :dirname
172
+ attr_accessor :basename
173
+ attr_accessor :value
174
+ attr_accessor :io
175
+
176
+ IOs = {}
177
+
178
+ def initialize(key, path)
179
+ @key = key.to_s
180
+ @cache_key = UploadCache.cache_key_for(key)
181
+
182
+ if path
183
+ @path = path
184
+ @dirname, @basename = File.split(@path)
185
+ @value = File.join(File.basename(@dirname), @basename).strip
186
+ @io = open(@path)
187
+ IOs[object_id] = @io.fileno
188
+ ObjectSpace.define_finalizer(self, UploadCache.method(:finalizer).to_proc)
189
+ else
190
+ @path = nil
191
+ @value = nil
192
+ end
193
+ end
194
+
195
+ def hidden
196
+ raw("<input type='hidden' name='#{ @cache_key }' value='#{ @value }' />") if @value
197
+ end
198
+
199
+ def to_s
200
+ hidden.to_s
201
+ end
202
+
203
+ def url
204
+ File.join(UploadCache.url, @value) if @value
205
+ end
206
+
207
+ module HtmlSafe
208
+ def html_safe() self end
209
+ def html_safe?() self end
210
+ end
211
+
212
+ def raw(*args)
213
+ string = args.join
214
+ unless string.respond_to?(:html_safe)
215
+ string.extend(HtmlSafe)
216
+ end
217
+ string.html_safe
218
+ end
219
+
220
+ def clear!
221
+ FileUtils.rm_rf(@dirname) if test(?d, @dirname)
222
+ rescue
223
+ nil
224
+ ensure
225
+ @io.close
226
+ IOs.delete(object_id)
227
+ Thread.new{ UploadCache.clear! }
228
+ end
229
+ end
230
+
231
+ Upload_cache = UploadCache unless defined?(Upload_cache)
232
+
233
+ if defined?(Rails.env)
234
+ unless Rails.env.production?
235
+ if defined?(unloadable)
236
+ unloadable(UploadCache)
237
+ unloadable(Upload_cache)
238
+ end
239
+ end
240
+ end
metadata ADDED
@@ -0,0 +1,82 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: upload_cache
3
+ version: !ruby/object:Gem::Version
4
+ hash: 23
5
+ prerelease:
6
+ segments:
7
+ - 1
8
+ - 0
9
+ - 0
10
+ version: 1.0.0
11
+ platform: ruby
12
+ authors:
13
+ - Ara T. Howard
14
+ autorequire:
15
+ bindir: bin
16
+ cert_chain: []
17
+
18
+ date: 2011-03-16 00:00:00 -06:00
19
+ default_executable:
20
+ dependencies:
21
+ - !ruby/object:Gem::Dependency
22
+ name: uuidtools
23
+ prerelease: false
24
+ requirement: &id001 !ruby/object:Gem::Requirement
25
+ none: false
26
+ requirements:
27
+ - - ">="
28
+ - !ruby/object:Gem::Version
29
+ hash: 3
30
+ segments:
31
+ - 0
32
+ version: "0"
33
+ type: :runtime
34
+ version_requirements: *id001
35
+ description: "description: upload_cache kicks the ass"
36
+ email: ara.t.howard@gmail.com
37
+ executables: []
38
+
39
+ extensions: []
40
+
41
+ extra_rdoc_files: []
42
+
43
+ files:
44
+ - lib/upload_cache.rb
45
+ - Rakefile
46
+ - README
47
+ has_rdoc: true
48
+ homepage: http://github.com/ahoward/upload_cache
49
+ licenses: []
50
+
51
+ post_install_message:
52
+ rdoc_options: []
53
+
54
+ require_paths:
55
+ - lib
56
+ required_ruby_version: !ruby/object:Gem::Requirement
57
+ none: false
58
+ requirements:
59
+ - - ">="
60
+ - !ruby/object:Gem::Version
61
+ hash: 3
62
+ segments:
63
+ - 0
64
+ version: "0"
65
+ required_rubygems_version: !ruby/object:Gem::Requirement
66
+ none: false
67
+ requirements:
68
+ - - ">="
69
+ - !ruby/object:Gem::Version
70
+ hash: 3
71
+ segments:
72
+ - 0
73
+ version: "0"
74
+ requirements: []
75
+
76
+ rubyforge_project: codeforpeople
77
+ rubygems_version: 1.4.2
78
+ signing_key:
79
+ specification_version: 3
80
+ summary: upload_cache
81
+ test_files: []
82
+