Changesets can be listed by changeset number.
The Git repository is here.
- Revision:
- 15
- Log:
Attempt to update Typo to a Typo SVN HEAD release from around the
time the prototype installation was set up on the RISC OS Open Limited
web site. Timestamps place this at 04-Jul so a revision from 05-Jul or
earlier was pulled and copied over the 2.6.0 tarball stable code.
- Author:
- adh
- Date:
- Sat Jul 22 23:27:35 +0100 2006
- Size:
- 6448 Bytes
1 | # == Overview |
2 | # |
3 | # This module will extend the CGI module with methods to track the upload |
4 | # progress for multipart forms for use with progress meters. The progress is |
5 | # saved in the session to be used from any request from any server with the |
6 | # same session. In other words, this module will work across application |
7 | # instances. |
8 | # |
9 | # === Usage |
10 | # |
11 | # Just do your file-uploads as you normally would, but include an upload_id in |
12 | # the query string of your form action. Your form post action should look |
13 | # like: |
14 | # |
15 | # <form method="post" enctype="multipart/form-data" action="postaction?upload_id=SOMEIDYOUSET"> |
16 | # <input type="file" name="client_file"/> |
17 | # </form> |
18 | # |
19 | # Query the upload state in a progress by reading the progress from the session |
20 | # |
21 | # class UploadController < ApplicationController |
22 | # def upload_status |
23 | # render :text => "Percent complete: " + @session[:uploads]['SOMEIDYOUSET'].completed_percent" |
24 | # end |
25 | # end |
26 | # |
27 | # === Session options |
28 | # |
29 | # Upload progress uses the session options defined in |
30 | # ActionController::CgiRequest::DEFAULT_SESSION_OPTIONS. If you are passing |
31 | # custom session options to your dispatcher then please follow the |
32 | # "recommended way to change session options":http://wiki.rubyonrails.com/rails/show/HowtoChangeSessionOptions |
33 | # |
34 | # === Update frequency |
35 | # |
36 | # During an upload, the progress will be written to the session every 2 |
37 | # seconds. This prevents excessive writes yet maintains a decent picture of |
38 | # the upload progress for larger files. |
39 | # |
40 | # User interfaces that update more often that every 2 seconds will display the same results. |
41 | # Consider this update frequency when designing your progress polling. |
42 | # |
43 | |
44 | require 'cgi' |
45 | |
46 | class CGI #:nodoc: |
47 | class ProgressIO < SimpleDelegator #:nodoc: |
48 | MIN_SAVE_INTERVAL = 1.0 # Number of seconds between session saves |
49 | |
50 | attr_reader :progress, :session |
51 | |
52 | def initialize(orig_io, progress, session) |
53 | @session = session |
54 | @progress = progress |
55 | |
56 | @start_time = Time.now |
57 | @last_save_time = @start_time |
58 | save_progress |
59 | |
60 | super(orig_io) |
61 | end |
62 | |
63 | def read(*args) |
64 | data = __getobj__.read(*args) |
65 | |
66 | if data and data.size > 0 |
67 | now = Time.now |
68 | elapsed = now - @start_time |
69 | progress.update!(data.size, elapsed) |
70 | |
71 | if now - @last_save_time > MIN_SAVE_INTERVAL |
72 | save_progress |
73 | @last_save_time = now |
74 | end |
75 | else |
76 | ActionController::Base.logger.debug("CGI::ProgressIO#read returns nothing when it should return nil if IO is finished: [#{args.inspect}], a cancelled upload or old FCGI bindings. Resetting the upload progress") |
77 | |
78 | progress.reset! |
79 | save_progress |
80 | end |
81 | |
82 | data |
83 | end |
84 | |
85 | def save_progress |
86 | @session.update |
87 | end |
88 | |
89 | def finish |
90 | @session.update |
91 | ActionController::Base.logger.debug("Finished processing multipart upload in #{@progress.elapsed_seconds.to_s}s") |
92 | end |
93 | end |
94 | |
95 | module QueryExtension #:nodoc: |
96 | # Need to do lazy aliasing on the instance that we are extending because of the way QueryExtension |
97 | # gets included for each instance of the CGI object rather than on a module level. This method is a |
98 | # bit obtrusive because we are overriding CGI::QueryExtension::extended which could be used in the |
99 | # future. Need to research a better method |
100 | def self.extended(obj) |
101 | obj.instance_eval do |
102 | # unless defined? will prevent clobbering the progress IO on multiple extensions |
103 | alias :stdinput_without_progress :stdinput unless defined? stdinput_without_progress |
104 | alias :stdinput :stdinput_with_progress |
105 | end |
106 | end |
107 | |
108 | def stdinput_with_progress |
109 | @stdin_with_progress or stdinput_without_progress |
110 | end |
111 | |
112 | private |
113 | # Bootstrapped on UploadProgress::upload_status_for |
114 | def read_multipart_with_progress(boundary, content_length) |
115 | begin |
116 | begin |
117 | # Session disabled if the default session options have been set to 'false' |
118 | options = ActionController::CgiRequest::DEFAULT_SESSION_OPTIONS |
119 | raise RuntimeError.new("Multipart upload progress disabled, no session options") unless options |
120 | |
121 | options = options.stringify_keys |
122 | |
123 | # Pull in the application controller to satisfy any dependencies on class definitions |
124 | # of instances stored in the session. |
125 | # Be sure to stay compatible with Rails 1.0/const_load! |
126 | if Object.const_defined?(:Controllers) and Controllers.respond_to?(:const_load!) |
127 | Controllers.const_load!(:ApplicationController, "application") unless Controllers.const_defined?(:ApplicationController) |
128 | else |
129 | require_dependency('application.rb') unless Object.const_defined?(:ApplicationController) |
130 | end |
131 | |
132 | # Assumes that @cookies has already been setup |
133 | # Raises nomethod if upload_id is not defined |
134 | @params = CGI::parse(read_params_from_query) |
135 | upload_id = @params[(options['upload_key'] || 'upload_id')].first |
136 | raise RuntimeError.new("Multipart upload progress disabled, no upload id in query string") unless upload_id |
137 | |
138 | upload_progress = UploadProgress::Progress.new(content_length) |
139 | |
140 | session = Session.new(self, options) |
141 | session[:uploads] = {} unless session[:uploads] |
142 | session[:uploads].delete(upload_id) # in case the same upload id is used twice |
143 | session[:uploads][upload_id] = upload_progress |
144 | |
145 | @stdin_with_progress = CGI::ProgressIO.new(stdinput_without_progress, upload_progress, session) |
146 | ActionController::Base.logger.debug("Multipart upload with progress (id: #{upload_id}, size: #{content_length})") |
147 | rescue |
148 | ActionController::Base.logger.debug("Exception during setup of read_multipart_with_progress: #{$!}") |
149 | end |
150 | ensure |
151 | begin |
152 | params = read_multipart_without_progress(boundary, content_length) |
153 | @stdin_with_progress.finish if @stdin_with_progress.respond_to? :finish |
154 | ensure |
155 | @stdin_with_progress = nil |
156 | session.close if session |
157 | end |
158 | end |
159 | params |
160 | end |
161 | |
162 | # Prevent redefinition of aliases on multiple includes |
163 | unless private_instance_methods.include?("read_multipart_without_progress") |
164 | alias_method :read_multipart_without_progress, :read_multipart |
165 | alias_method :read_multipart, :read_multipart_with_progress |
166 | end |
167 | |
168 | end |
169 | end |