add JSON generator
[upr.git] / lib / upr / json.rb
blobadcf47e8d92b00a2a745f223f9e2b5c172dbe7f3
1 # -*- encoding: binary -*-
2 begin
3   require 'json'
4 rescue LoadError
5   raise LoadError, "either json or json-pure is required"
6 end
8 module Upr
10   # JSON protocol based on Lighttpd's mod_uploadprogress
11   # http://trac.lighttpd.net/trac/wiki/Docs:ModUploadProgress
12   class JSON < Struct.new(:frequency, :backend, :upload_id)
14     include Params
16     # We use this in case length is nil when clients send chunked uploads
17     INT_MAX = 0x7fffffff
19     SLEEP_CLASS = defined?(Actor) ? Actor : Kernel
21     RESP_HEADERS = {
22       'Content-Type' => 'application/json',
23       'Cache-Control' => 'no-cache',
24     }
26     def initialize(options = {})
27       super(options[:frequency] || 1, options[:backend], options[:upload_id])
29       # support :drb for compatibility with mongrel_upload_progress
30       if options[:drb]
31         backend and raise ArgumentError, ":backend and :drb are incompatible"
32         require 'drb'
33         DRb.start_service
34         self.backend = DRbObject.new(nil, options[:drb])
35       elsif String === backend
36         # allow people to use strings in case their backend gets
37         # lazy-loaded (like an ActiveRecord model)
38         self.backend = eval(backend)
39       elsif backend.nil?
40         raise ArgumentError, "backend MUST be specified"
41       end
43       # only for use with rails_proc
44       upload_id.nil? and self.upload_id = options[:env]
45     end
47     def rails_render_options
48       env = upload_id
49       self.upload_id = extract_upload_id(env)
50       {
51         :content_type => 'application/json',
52         :text => _once
53       }
54     end
56     def _once
57       if status = backend.read(upload_id)
58         if status.done?
59           _json_object(:state => 'done')
60         elsif status.seen == 0
61           _json_object(:state => 'starting')
62         elsif status.error?
63           _error_msg("upload failed")
64         else
65           _update_msg(status)
66         end
67       else
68         timeout = Time.now + 2
69         until status = backend.read(upload_id)
70           SLEEP_CLASS.sleep(0.1)
71           return _error_msg("couldn't get status") if Time.now > timeout
72         end
73         _json_object(:state => 'starting')
74       end
75     end
77     # Rack interface reservced for future use with streaming AJAX
78     def call(env)
79       if uid = extract_upload_id(env)
80         _wrap(env, uid)
81       else
82         [ 400, RESP_HEADERS.dup, [ _error_msg("upload_id not given") ] ]
83       end
84     end
86     # Rack interface reservced for future use with streaming AJAX
87     def each(&block)
88       sleeper = defined?(Actor) ? Actor : Kernel
89       timeout = Time.now + 2
90       eol = ";\n"
91       yield _json_object(:state => 'starting') << eol
92       begin
93         until status = backend.read(upload_id)
94           sleeper.sleep(0.1)
95           break if Time.now > timeout
96         end
97         if status
98           begin
99             yield _update_msg(status) << eol
100             break if status.done?
101             sleeper.sleep(frequency)
102           end while status = backend.read(upload_id)
103           yield _json_object(:state => 'done') << eol
104         else
105           yield _error_msg("couldn't get status") << eol
106         end
107       rescue => e
108         yield _error_msg(e.message) << eol
109       end
110     end
112     # Rack interface reservced for future use with streaming AJAX
113     def _wrap(env, uid)
114       _self = dup
115       _self.upload_id = uid
116       [ 200, RESP_HEADERS.dup, _self ]
117     end
119     def _error_msg(msg)
120       _json_object(:state => 'error', :status => 400, :message => msg)
121     end
123     def _json_object(options)
124       # $stderr.syswrite "#{options.inspect} #{$upr.inspect}\n"
125       options.to_json
126     end
128     def _update_msg(status)
129       raise "client error" if status.error?
130       received = status.seen
131       size = status.length || INT_MAX
132       _json_object(:state => 'uploading', :size => size, :received => received)
133     end
135   end