upload_cache 1.0.0

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.
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
+