upr 0.1.0 → 0.2.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 (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/