upr 0.1.0 → 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (32) hide show
  1. data/GIT-VERSION-GEN +1 -1
  2. data/GNUmakefile +1 -1
  3. data/LICENSE +9 -4
  4. data/README +29 -13
  5. data/examples/rails_app-2.3.4/app/controllers/application_controller.rb +8 -1
  6. data/examples/rails_app-2.3.4/app/controllers/files_controller.rb +35 -12
  7. data/examples/rails_app-2.3.4/app/controllers/progress_controller.rb +11 -0
  8. data/examples/rails_app-2.3.4/app/models/upr_status.rb +34 -10
  9. data/examples/rails_app-2.3.4/app/views/files/index.html.erb +14 -4
  10. data/examples/rails_app-2.3.4/app/views/files/new.html.erb +71 -0
  11. data/examples/rails_app-2.3.4/app/views/files/pull.html.erb +60 -0
  12. data/examples/rails_app-2.3.4/config.ru +3 -0
  13. data/examples/rails_app-2.3.4/config/initializers/ruby_19_compat.rb +26 -0
  14. data/examples/rails_app-2.3.4/public/javascripts/ajax_pull/ajax_pull.js +85 -0
  15. data/examples/rails_app-2.3.4/public/javascripts/ajax_pull/prototype-1_5_1.js +3271 -0
  16. data/examples/rails_app-2.3.4/public/javascripts/ajax_pull/upload_progress.js +139 -0
  17. data/examples/rails_app-2.3.4/public/javascripts/jquery.js +32 -0
  18. data/examples/rails_app-2.3.4/public/javascripts/jquery.uploadProgress.js +116 -0
  19. data/examples/rails_app-2.3.4/public/stylesheets/site.css +41 -0
  20. data/examples/rails_app-2.3.4/rainbows_config.rb +1 -1
  21. data/examples/rails_app-2.3.4/test/fixtures/upr_statuses.yml +22 -0
  22. data/examples/rails_app-2.3.4/test/unit/upr_status_test.rb +41 -0
  23. data/lib/upr.rb +4 -5
  24. data/lib/upr/input_wrapper.rb +17 -13
  25. data/lib/upr/json.rb +143 -0
  26. data/lib/upr/monitor.rb +18 -6
  27. data/lib/upr/params.rb +24 -0
  28. data/lib/upr/status.rb +9 -0
  29. data/lib/upr/status_methods.rb +13 -0
  30. data/test/test_monitor.rb +58 -0
  31. data/upr.gemspec +1 -0
  32. metadata +37 -5
@@ -0,0 +1,143 @@
1
+ # -*- encoding: binary -*-
2
+ begin
3
+ require 'json'
4
+ rescue LoadError
5
+ raise LoadError, "either json or json-pure is required"
6
+ end
7
+ require 'rack'
8
+
9
+ module Upr
10
+
11
+ # JSON protocol based on Lighttpd's mod_uploadprogress
12
+ # http://trac.lighttpd.net/trac/wiki/Docs:ModUploadProgress
13
+ class JSON < Struct.new(:frequency, :backend, :upload_id)
14
+
15
+ include Params
16
+
17
+ # We use this in case length is nil when clients send chunked uploads
18
+ INT_MAX = 0x7fffffff
19
+
20
+ SLEEP_CLASS = defined?(Actor) ? Actor : Kernel
21
+
22
+ # our default response headers, we need to set no-transform to
23
+ # prevent deflaters from compressing our already-small small input
24
+ # and also to prevent buffering/corking of the response inside
25
+ # deflater buffers.
26
+ RESPONSE_HEADERS = {
27
+ 'Content-Type' => 'application/json',
28
+ 'Cache-Control' => 'no-cache, no-transform',
29
+ }
30
+
31
+ def initialize(options = {})
32
+ super(options[:frequency] || 1, options[:backend], options[:upload_id])
33
+
34
+ # support :drb for compatibility with mongrel_upload_progress
35
+ if options[:drb]
36
+ backend and raise ArgumentError, ":backend and :drb are incompatible"
37
+ require 'drb'
38
+ DRb.start_service
39
+ self.backend = DRbObject.new(nil, options[:drb])
40
+ elsif String === backend
41
+ # allow people to use strings in case their backend gets
42
+ # lazy-loaded (like an ActiveRecord model)
43
+ self.backend = eval(backend)
44
+ elsif backend.nil?
45
+ raise ArgumentError, "backend MUST be specified"
46
+ end
47
+
48
+ # only for use with rails_proc
49
+ upload_id.nil? and self.upload_id = options[:env]
50
+ end
51
+
52
+ def rails_render_options
53
+ env = upload_id
54
+ self.upload_id = extract_upload_id(env)
55
+ text = if Rack::Request.new(env).GET.include?("long")
56
+ Proc.new { |response,output| each { |line| output.write(line) } }
57
+ else
58
+ _once
59
+ end
60
+ { :content_type => 'application/json', :text => text }
61
+ end
62
+
63
+ def _once
64
+ if status = backend.read(upload_id)
65
+ if status.done?
66
+ _json_object(:state => 'done')
67
+ elsif status.seen == 0
68
+ _json_object(:state => 'starting')
69
+ elsif status.error?
70
+ _error_msg("upload failed")
71
+ else
72
+ _update_msg(status)
73
+ end
74
+ else
75
+ timeout = Time.now + 2
76
+ until status = backend.read(upload_id)
77
+ SLEEP_CLASS.sleep(0.1)
78
+ return _error_msg("couldn't get status") if Time.now > timeout
79
+ end
80
+ _json_object(:state => 'starting')
81
+ end
82
+ end
83
+
84
+ # Rack interface reservced for future use with streaming AJAX
85
+ def call(env)
86
+ if uid = extract_upload_id(env)
87
+ _wrap(env, uid)
88
+ else
89
+ [ 400, RESPONSE_HEADERS.dup, [ _error_msg("upload_id not given") ] ]
90
+ end
91
+ end
92
+
93
+ # Rack interface reservced for future use with streaming AJAX
94
+ def each(&block)
95
+ sleeper = defined?(Actor) ? Actor : Kernel
96
+ timeout = Time.now + 2
97
+ eol = ";\n"
98
+ yield _json_object(:state => 'starting') << eol
99
+ begin
100
+ until status = backend.read(upload_id)
101
+ sleeper.sleep(0.1)
102
+ break if Time.now > timeout
103
+ end
104
+ if status
105
+ begin
106
+ yield _update_msg(status) << eol
107
+ break if status.done?
108
+ sleeper.sleep(frequency)
109
+ end while status = backend.read(upload_id)
110
+ yield _json_object(:state => 'done') << eol
111
+ else
112
+ yield _error_msg("couldn't get status") << eol
113
+ end
114
+ rescue => e
115
+ yield _error_msg(e.message) << eol
116
+ end
117
+ end
118
+
119
+ # Rack interface reservced for future use with streaming AJAX
120
+ def _wrap(env, uid)
121
+ _self = dup
122
+ _self.upload_id = uid
123
+ [ 200, RESPONSE_HEADERS.dup, _self ]
124
+ end
125
+
126
+ def _error_msg(msg)
127
+ _json_object(:state => 'error', :status => 400, :message => msg)
128
+ end
129
+
130
+ def _json_object(options)
131
+ # $stderr.syswrite "#{options.inspect} #{$upr.inspect}\n"
132
+ options.to_json
133
+ end
134
+
135
+ def _update_msg(status)
136
+ raise "client error" if status.error?
137
+ received = status.seen
138
+ size = status.length || INT_MAX
139
+ _json_object(:state => 'uploading', :size => size, :received => received)
140
+ end
141
+
142
+ end
143
+ end
@@ -8,11 +8,11 @@ module Upr
8
8
  # Usage (in config.ru with Moneta::Memory store):
9
9
  # require 'upr'
10
10
  # require 'moneta/memory'
11
- # use Upr, :backend => Upr::MonetaMonitor.new(Moneta::Memory.new)
11
+ # use Upr, :backend => Upr::Monitor.new(Moneta::Memory.new)
12
12
  # run YourApplication.new
13
13
  class Monitor < Struct.new(:moneta)
14
- # nuke anything not read/updated in 10 seconds
15
- OPT = { :expires_in => 10 }
14
+ # nuke anything not read/updated in 60 seconds
15
+ OPT = { :expires_in => 60 }
16
16
 
17
17
  def initialize(moneta_store = nil)
18
18
  super
@@ -27,13 +27,25 @@ module Upr
27
27
  end
28
28
 
29
29
  def read(upid)
30
- moneta.update_key(upid, OPT)
31
30
  moneta[upid]
32
31
  end
33
32
 
34
33
  def incr(upid, nr)
35
- status = moneta[upid]
36
- status.seen += nr
34
+ status = moneta[upid] or return
35
+ status.seen += nr if status.seen >= 0
36
+ moneta.store(upid, status, OPT)
37
+ end
38
+
39
+ def finish(upid)
40
+ status = moneta[upid] or return
41
+ status.length ||= status.seen
42
+ status.seen = status.length
43
+ moneta.store(upid, status, OPT)
44
+ end
45
+
46
+ def error!(upid)
47
+ status = moneta[upid] or return
48
+ status.seen = -1
37
49
  moneta.store(upid, status, OPT)
38
50
  end
39
51
 
@@ -0,0 +1,24 @@
1
+ # -*- encoding: binary -*-
2
+ require 'rack'
3
+
4
+ module Upr
5
+
6
+ module Params
7
+
8
+ # we'll add compatibility for existing upload progress modules
9
+ # we find here, but under no circumstances will we help
10
+ # proliferate new and subtly incompatible mechanisms.
11
+ # X-Progress-ID is used in both lighttpd and nginx (3rd party module)
12
+ # "upload_id" is used by mongrel_upload_progress
13
+ def extract_upload_id(env)
14
+ upid = env['HTTP_X_PROGRESS_ID'] and return upid
15
+
16
+ # can't blindly parse params here since we don't want to read
17
+ # the POST body if there is one, so only parse stuff in the
18
+ # query string...
19
+ params = Rack::Request.new(env).GET
20
+ env["upr.upload_id"] = params["X-Progress-ID"] || params["upload_id"]
21
+ end
22
+
23
+ end
24
+ end
@@ -0,0 +1,9 @@
1
+ # -*- encoding: binary -*-
2
+ require 'upr/status_methods'
3
+ module Upr
4
+
5
+ # this is what we store in the Moneta-backed monitor
6
+ class Status < Struct.new(:seen, :length)
7
+ include StatusMethods
8
+ end
9
+ end
@@ -0,0 +1,13 @@
1
+ # -*- encoding: binary -*-
2
+ module Upr
3
+ # mixin module for both Upr::Status and UprStatus (AR example module)
4
+ module StatusMethods
5
+ def error?
6
+ seen < 0
7
+ end
8
+
9
+ def done?
10
+ length && seen >= length
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,58 @@
1
+ require 'test/unit'
2
+ require 'upr'
3
+
4
+ class MonitorTest < Test::Unit::TestCase
5
+ def setup
6
+ @monitor = Upr::Monitor.new
7
+ end
8
+
9
+ def test_start_with_length
10
+ assert_kind_of Upr::Status, @monitor.start('abcde', 5)
11
+ status = @monitor.read('abcde')
12
+ assert_equal 5, status.length
13
+ assert_equal 0, status.seen
14
+ assert ! status.error?
15
+ assert ! status.done?
16
+ end
17
+
18
+ def test_start_without_length
19
+ assert_kind_of Upr::Status, @monitor.start('abcde', nil)
20
+ status = @monitor.read('abcde')
21
+ assert_nil status.length
22
+ assert_equal 0, status.seen
23
+ assert ! status.error?
24
+ assert ! status.done?
25
+ end
26
+
27
+ def test_to_incr
28
+ assert_kind_of Upr::Status, @monitor.start('abcde', 5)
29
+ status = @monitor.incr('abcde', 2)
30
+ assert_kind_of Upr::Status, status
31
+ assert_equal 2, status.seen
32
+ assert ! status.error?
33
+ assert ! status.done?
34
+ @monitor.incr('abcde', 3)
35
+ assert_equal 5, status.seen
36
+ assert ! status.error?
37
+ assert status.done?
38
+ end
39
+
40
+ def test_finish_with_length
41
+ assert_kind_of Upr::Status, status = @monitor.start('abcde', 5)
42
+ @monitor.finish('abcde')
43
+ assert ! status.error?
44
+ assert status.done?
45
+ assert_equal 5, status.seen
46
+ assert_equal 5, status.length
47
+ end
48
+
49
+ def test_finish_without_length
50
+ assert_kind_of Upr::Status, status = @monitor.start('abcde', nil)
51
+ @monitor.finish('abcde')
52
+ assert ! status.error?
53
+ assert status.done?
54
+ assert_equal 0, status.seen
55
+ assert_equal 0, status.length
56
+ end
57
+
58
+ end
@@ -32,6 +32,7 @@ Gem::Specification.new do |s|
32
32
  s.rubyforge_project = %q{rainbows}
33
33
 
34
34
  s.add_dependency(%q<moneta>)
35
+ s.add_dependency(%q<rack>)
35
36
 
36
37
  # Folks on intranets sharing humongous files can use Unicorn, too
37
38
  # s.add_dependency(%q<rainbows>)
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: upr
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.0
4
+ version: 0.2.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - upr hackers
@@ -9,7 +9,7 @@ autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
11
 
12
- date: 2009-11-11 00:00:00 -08:00
12
+ date: 2009-11-14 00:00:00 -08:00
13
13
  default_executable:
14
14
  dependencies:
15
15
  - !ruby/object:Gem::Dependency
@@ -22,11 +22,22 @@ dependencies:
22
22
  - !ruby/object:Gem::Version
23
23
  version: "0"
24
24
  version:
25
+ - !ruby/object:Gem::Dependency
26
+ name: rack
27
+ type: :runtime
28
+ version_requirement:
29
+ version_requirements: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - ">="
32
+ - !ruby/object:Gem::Version
33
+ version: "0"
34
+ version:
25
35
  description: |-
26
36
  upr is Rack middleware that allows browser-side upload progress
27
- monitoring. It is based on the "mongrel_upload_progress" module, but
28
- allows any Moneta-backing store in additon to DRb. There is also a
29
- packaged example for using an ActiveRecord model for Rails.
37
+ monitoring. It is based on (and should be client-side compatible with)
38
+ several upload progress modules including ones used by leading web
39
+ servers. It allows any Moneta backing store in addition to DRb. There
40
+ is also a packaged example for using an ActiveRecord model for Rails.
30
41
  email: upr@librelist.com
31
42
  executables:
32
43
  - upr-drb
@@ -36,7 +47,11 @@ extra_rdoc_files:
36
47
  - ChangeLog
37
48
  - lib/upr.rb
38
49
  - lib/upr/input_wrapper.rb
50
+ - lib/upr/json.rb
39
51
  - lib/upr/monitor.rb
52
+ - lib/upr/params.rb
53
+ - lib/upr/status.rb
54
+ - lib/upr/status_methods.rb
40
55
  - LICENSE
41
56
  - NEWS
42
57
  - README
@@ -57,9 +72,12 @@ files:
57
72
  - examples/rails_app-2.3.4/Rakefile
58
73
  - examples/rails_app-2.3.4/app/controllers/application_controller.rb
59
74
  - examples/rails_app-2.3.4/app/controllers/files_controller.rb
75
+ - examples/rails_app-2.3.4/app/controllers/progress_controller.rb
60
76
  - examples/rails_app-2.3.4/app/helpers/application_helper.rb
61
77
  - examples/rails_app-2.3.4/app/models/upr_status.rb
62
78
  - examples/rails_app-2.3.4/app/views/files/index.html.erb
79
+ - examples/rails_app-2.3.4/app/views/files/new.html.erb
80
+ - examples/rails_app-2.3.4/app/views/files/pull.html.erb
63
81
  - examples/rails_app-2.3.4/config.ru
64
82
  - examples/rails_app-2.3.4/config/boot.rb
65
83
  - examples/rails_app-2.3.4/config/database.yml
@@ -68,6 +86,7 @@ files:
68
86
  - examples/rails_app-2.3.4/config/environments/production.rb
69
87
  - examples/rails_app-2.3.4/config/environments/test.rb
70
88
  - examples/rails_app-2.3.4/config/initializers/new_rails_defaults.rb
89
+ - examples/rails_app-2.3.4/config/initializers/ruby_19_compat.rb
71
90
  - examples/rails_app-2.3.4/config/routes.rb
72
91
  - examples/rails_app-2.3.4/db/.gitignore
73
92
  - examples/rails_app-2.3.4/db/migrate/19700000000000_add_upr_status.rb
@@ -78,20 +97,33 @@ files:
78
97
  - examples/rails_app-2.3.4/public/422.html
79
98
  - examples/rails_app-2.3.4/public/500.html
80
99
  - examples/rails_app-2.3.4/public/favicon.ico
100
+ - examples/rails_app-2.3.4/public/javascripts/ajax_pull/ajax_pull.js
101
+ - examples/rails_app-2.3.4/public/javascripts/ajax_pull/prototype-1_5_1.js
102
+ - examples/rails_app-2.3.4/public/javascripts/ajax_pull/upload_progress.js
81
103
  - examples/rails_app-2.3.4/public/javascripts/application.js
82
104
  - examples/rails_app-2.3.4/public/javascripts/controls.js
83
105
  - examples/rails_app-2.3.4/public/javascripts/dragdrop.js
84
106
  - examples/rails_app-2.3.4/public/javascripts/effects.js
107
+ - examples/rails_app-2.3.4/public/javascripts/jquery.js
108
+ - examples/rails_app-2.3.4/public/javascripts/jquery.uploadProgress.js
85
109
  - examples/rails_app-2.3.4/public/javascripts/prototype.js
86
110
  - examples/rails_app-2.3.4/public/javascripts/upr.js
87
111
  - examples/rails_app-2.3.4/public/robots.txt
112
+ - examples/rails_app-2.3.4/public/stylesheets/site.css
88
113
  - examples/rails_app-2.3.4/rainbows_config.rb
114
+ - examples/rails_app-2.3.4/test/fixtures/upr_statuses.yml
89
115
  - examples/rails_app-2.3.4/test/test_helper.rb
116
+ - examples/rails_app-2.3.4/test/unit/upr_status_test.rb
90
117
  - lib/upr.rb
91
118
  - lib/upr/input_wrapper.rb
119
+ - lib/upr/json.rb
92
120
  - lib/upr/monitor.rb
121
+ - lib/upr/params.rb
122
+ - lib/upr/status.rb
123
+ - lib/upr/status_methods.rb
93
124
  - local.mk.sample
94
125
  - setup.rb
126
+ - test/test_monitor.rb
95
127
  - upr.gemspec
96
128
  has_rdoc: true
97
129
  homepage: http://upr.bogomips.org/