image_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 (5) hide show
  1. data/README +49 -0
  2. data/Rakefile +365 -0
  3. data/image_cache.gemspec +28 -0
  4. data/lib/image_cache.rb +188 -0
  5. metadata +83 -0
data/README ADDED
@@ -0,0 +1,49 @@
1
+ NAME
2
+
3
+ image_cache.rb
4
+
5
+ DESCRIPTION
6
+
7
+ a small utility library to facility caching image uploads between form
8
+ validation failures. designed for rails, but usable anywhere.
9
+
10
+ USAGE
11
+
12
+ in the controller
13
+
14
+ def upload
15
+ @image_cache = ImageCache.for(params, :image)
16
+
17
+ @record = Model.new(params)
18
+
19
+ if request.get?
20
+ render and return
21
+ end
22
+
23
+ if request.post?
24
+ @record.save!
25
+ @image_cache.clear!
26
+ end
27
+ end
28
+
29
+
30
+ in the view
31
+
32
+ <input type='file' name='image />
33
+
34
+ <%= @image_cache.hidden %>
35
+
36
+ <!-- optionally, you can show any uploaded image -->
37
+
38
+ <% if url = @image_cache.url %>
39
+ you already uploaded: <img src='<%= raw url %>' />
40
+ <% end %>
41
+
42
+
43
+ in a rake task
44
+
45
+ ImageCache.clear! ### nuke old files once per day
46
+
47
+
48
+
49
+
@@ -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,28 @@
1
+ ## image_cache.gemspec
2
+ #
3
+
4
+ Gem::Specification::new do |spec|
5
+ spec.name = "image_cache"
6
+ spec.version = "1.0.0"
7
+ spec.platform = Gem::Platform::RUBY
8
+ spec.summary = "image_cache"
9
+ spec.description = "description: image_cache kicks the ass"
10
+
11
+ spec.files = ["image_cache.gemspec", "lib", "lib/image_cache.rb", "Rakefile", "README"]
12
+ spec.executables = []
13
+
14
+ spec.require_path = "lib"
15
+
16
+ spec.has_rdoc = true
17
+ spec.test_files = nil
18
+
19
+ # spec.add_dependency 'lib', '>= version'
20
+ spec.add_dependency 'uuidtools'
21
+
22
+ spec.extensions.push(*[])
23
+
24
+ spec.rubyforge_project = "codeforpeople"
25
+ spec.author = "Ara T. Howard"
26
+ spec.email = "ara.t.howard@gmail.com"
27
+ spec.homepage = "http://github.com/ahoward/image_cache"
28
+ end
@@ -0,0 +1,188 @@
1
+ require 'uuidtools'
2
+ require 'fileutils'
3
+ require 'cgi'
4
+
5
+ class ImageCache
6
+ Version = '1.0.0'
7
+
8
+ class << ImageCache
9
+ def version
10
+ ImageCache::Version
11
+ end
12
+
13
+ def base
14
+ @base ||= 'images/cache'
15
+ end
16
+
17
+ def base=(base)
18
+ @base = base.to_s.sub(%r|^/+|, '')
19
+ end
20
+
21
+ def url
22
+ '/' + base
23
+ end
24
+
25
+ def root
26
+ @root ||= File.join(Rails.root, 'public', base)
27
+ end
28
+
29
+ def uuid(*args)
30
+ UUIDTools::UUID.timestamp_create.to_s
31
+ end
32
+
33
+ def tmpdir(&block)
34
+ tmpdir = File.join(root, uuid)
35
+
36
+ if block
37
+ FileUtils.mkdir_p(tmpdir)
38
+ block.call(tmpdir)
39
+ else
40
+ tmpdir
41
+ end
42
+ end
43
+
44
+ def cleanname(path)
45
+ basename = File.basename(path.to_s)
46
+ CGI.unescape(basename).gsub(%r/[^0-9a-zA-Z_@)(~.-]/, '_').gsub(%r/_+/,'_')
47
+ end
48
+
49
+ def cache_key_for(key)
50
+ "#{ key }__cache"
51
+ end
52
+
53
+ def for(params, key = :image)
54
+ image = params[key]
55
+ if image.respond_to?(:read)
56
+ tmpdir do |tmp|
57
+ basename = cleanname(image.original_path)
58
+
59
+ path = File.join(tmp, basename)
60
+ open(path, 'w'){|fd| fd.write(image.read)}
61
+ image_cache = new(key, path)
62
+ params[key] = image_cache.io
63
+ return image_cache
64
+ end
65
+ end
66
+
67
+ cache_key = cache_key_for(key)
68
+ image_cache = params[cache_key]
69
+ if image_cache
70
+ dirname, basename = File.split(image_cache)
71
+ path = root + '/' + File.join(File.basename(dirname), basename)
72
+ image_cache = new(key, path)
73
+ params[key] = image_cache.io
74
+ return image_cache
75
+ end
76
+
77
+ return new(key, path=nil)
78
+ end
79
+
80
+ def finalizer(object_id)
81
+ if fd = IOs[object_id]
82
+ IO.for_fd(fd).close
83
+ IOs.delete(object_id)
84
+ end
85
+ end
86
+
87
+ UUIDPattern = %r/^[a-zA-Z0-9-]+$/io
88
+ Age = 60 * 60 * 24
89
+
90
+ def clear!(options = {})
91
+ glob = File.join(root, '*')
92
+ age = Integer(options[:age] || options['age'] || Age)
93
+ since = options[:since] || options['since'] || Time.now
94
+
95
+ Dir.glob(glob) do |entry|
96
+ begin
97
+ next unless test(?d, entry)
98
+ next unless File.basename(entry) =~ UUIDPattern
99
+
100
+ files = Dir.glob(File.join(entry, '**/**'))
101
+
102
+ all_files_are_old =
103
+ files.all? do |file|
104
+ begin
105
+ stat = File.stat(file)
106
+ age = since - stat.atime
107
+ age >= Age
108
+ rescue
109
+ false
110
+ end
111
+ end
112
+
113
+ FileUtils.rm_rf(entry) if all_files_are_old
114
+ rescue
115
+ next
116
+ end
117
+ end
118
+ end
119
+ end
120
+
121
+ attr_accessor :key
122
+ attr_accessor :cache_key
123
+ attr_accessor :path
124
+ attr_accessor :dirname
125
+ attr_accessor :basename
126
+ attr_accessor :value
127
+ attr_accessor :io
128
+
129
+ IOs = {}
130
+
131
+ def initialize(key, path)
132
+ @key = key.to_s
133
+ @cache_key = ImageCache.cache_key_for(key)
134
+
135
+ if path
136
+ @path = path
137
+ @dirname, @basename = File.split(@path)
138
+ @value = File.join(File.basename(@dirname), @basename).strip
139
+ @io = open(@path)
140
+ IOs[object_id] = @io.fileno
141
+ ObjectSpace.define_finalizer(self, ImageCache.method(:finalizer).to_proc)
142
+ else
143
+ @path = nil
144
+ @value = nil
145
+ end
146
+ end
147
+
148
+ def hidden
149
+ raw("<input type='hidden' name='#{ @cache_key }' value='#{ @value }' />") if @value
150
+ end
151
+
152
+ def to_s
153
+ hidden.to_s
154
+ end
155
+
156
+ def url
157
+ File.join(ImageCache.url, @value) if @value
158
+ end
159
+
160
+ def raw(*args)
161
+ string = args.join
162
+ if string.respond_to?(:html_safe)
163
+ string.html_safe
164
+ else
165
+ string
166
+ end
167
+ end
168
+
169
+ def clear!
170
+ FileUtils.rm_rf(@dirname) if test(?d, @dirname)
171
+ rescue
172
+ nil
173
+ ensure
174
+ @io.close
175
+ IOs.delete(object_id)
176
+ Thread.new{ ImageCache.clear! }
177
+ end
178
+ end
179
+
180
+ Image_cache = ImageCache
181
+
182
+ if defined?(Rails.env)
183
+ unless Rails.env.production?
184
+ if defined?(unloadable)
185
+ unloadable(ImageCache)
186
+ end
187
+ end
188
+ end
metadata ADDED
@@ -0,0 +1,83 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: image_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-02-10 00:00:00 -07: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: image_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
+ - image_cache.gemspec
45
+ - lib/image_cache.rb
46
+ - Rakefile
47
+ - README
48
+ has_rdoc: true
49
+ homepage: http://github.com/ahoward/image_cache
50
+ licenses: []
51
+
52
+ post_install_message:
53
+ rdoc_options: []
54
+
55
+ require_paths:
56
+ - lib
57
+ required_ruby_version: !ruby/object:Gem::Requirement
58
+ none: false
59
+ requirements:
60
+ - - ">="
61
+ - !ruby/object:Gem::Version
62
+ hash: 3
63
+ segments:
64
+ - 0
65
+ version: "0"
66
+ required_rubygems_version: !ruby/object:Gem::Requirement
67
+ none: false
68
+ requirements:
69
+ - - ">="
70
+ - !ruby/object:Gem::Version
71
+ hash: 3
72
+ segments:
73
+ - 0
74
+ version: "0"
75
+ requirements: []
76
+
77
+ rubyforge_project: codeforpeople
78
+ rubygems_version: 1.4.2
79
+ signing_key:
80
+ specification_version: 3
81
+ summary: image_cache
82
+ test_files: []
83
+