merb 0.0.7 → 0.0.8
Sign up to get free protection for your applications and to get access to all the features.
- data/README +66 -31
- data/Rakefile +3 -1
- data/bin/merb +47 -13
- data/examples/app_skeleton/Rakefile +4 -3
- data/examples/app_skeleton/dist/app/helpers/global_helper.rb +6 -0
- data/examples/app_skeleton/dist/conf/merb.yml +11 -0
- data/examples/app_skeleton/dist/conf/mup.conf +5 -0
- data/examples/app_skeleton/dist/conf/router.rb +1 -3
- data/examples/app_skeleton/scripts/merb_stop +10 -2
- data/examples/sample_app/Rakefile +3 -3
- data/examples/sample_app/dist/app/controllers/files.rb +3 -3
- data/examples/sample_app/dist/app/controllers/posts.rb +25 -23
- data/examples/sample_app/dist/app/controllers/test.rb +7 -3
- data/examples/sample_app/dist/app/helpers/global_helper.rb +7 -0
- data/examples/sample_app/dist/app/helpers/posts_helper.rb +4 -0
- data/examples/sample_app/dist/app/views/layout/application.herb +5 -4
- data/examples/sample_app/dist/app/views/layout/foo.herb +1 -1
- data/examples/sample_app/dist/app/views/posts/new.herb +9 -2
- data/examples/sample_app/dist/app/views/shared/_test.herb +1 -0
- data/examples/sample_app/dist/conf/merb.yml +7 -7
- data/examples/sample_app/dist/conf/merb_init.rb +8 -1
- data/examples/sample_app/dist/conf/mup.conf +5 -11
- data/examples/sample_app/dist/conf/router.rb +1 -1
- data/examples/sample_app/dist/public/test.html +5 -0
- data/examples/sample_app/dist/schema/migrations/002_add_sessions_table.rb +1 -1
- data/examples/sample_app/dist/schema/schema.rb +1 -1
- data/examples/sample_app/log/merb.4000.pid +1 -0
- data/lib/merb.rb +35 -17
- data/lib/merb/core_ext.rb +2 -0
- data/lib/merb/{merb_class_extensions.rb → core_ext/merb_class.rb} +42 -0
- data/lib/merb/core_ext/merb_enumerable.rb +7 -0
- data/lib/merb/{merb_utils.rb → core_ext/merb_hash.rb} +1 -78
- data/lib/merb/core_ext/merb_kernel.rb +16 -0
- data/lib/merb/core_ext/merb_module.rb +10 -0
- data/lib/merb/core_ext/merb_numeric.rb +20 -0
- data/lib/merb/core_ext/merb_object.rb +6 -0
- data/lib/merb/core_ext/merb_string.rb +40 -0
- data/lib/merb/core_ext/merb_symbol.rb +12 -0
- data/lib/merb/merb_constants.rb +18 -0
- data/lib/merb/merb_controller.rb +150 -76
- data/lib/merb/{session/merb_drb_server.rb → merb_drb_server.rb} +13 -46
- data/lib/merb/merb_exceptions.rb +4 -0
- data/lib/merb/merb_handler.rb +29 -17
- data/lib/merb/merb_request.rb +95 -0
- data/lib/merb/merb_upload_handler.rb +46 -0
- data/lib/merb/merb_upload_progress.rb +48 -0
- data/lib/merb/merb_view_context.rb +46 -0
- data/lib/merb/merb_yaml_store.rb +31 -0
- data/lib/merb/mixins/basic_authentication_mixin.rb +2 -2
- data/lib/merb/mixins/controller_mixin.rb +24 -75
- data/lib/merb/mixins/erubis_capture_mixin.rb +84 -0
- data/lib/merb/mixins/javascript_mixin.rb +103 -19
- data/lib/merb/mixins/merb_status_codes.rb +59 -0
- data/lib/merb/mixins/render_mixin.rb +114 -40
- data/lib/merb/mixins/responder_mixin.rb +2 -1
- data/lib/merb/session/merb_ar_session.rb +120 -0
- data/lib/merb/session/merb_drb_session.rb +0 -6
- data/lib/merb/vendor/paginator/paginator.rb +102 -99
- metadata +44 -8
- data/examples/sample_app/script/startdrb +0 -8
- data/lib/merb/session/merb_session.rb +0 -64
- data/lib/mutex_hotfix.rb +0 -34
@@ -0,0 +1,40 @@
|
|
1
|
+
class String
|
2
|
+
|
3
|
+
# reloads controller classes on each request if
|
4
|
+
# :allow_reloading is set to true in the config
|
5
|
+
# file or command line options.
|
6
|
+
def import
|
7
|
+
if Merb::Server.config[:allow_reloading]
|
8
|
+
Object.send(:remove_const, self.camel_case.intern) rescue nil
|
9
|
+
load(self.snake_case + '.rb')
|
10
|
+
else
|
11
|
+
require(self.snake_case)
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
# "FooBar".snake_case #=> "foo_bar"
|
16
|
+
def snake_case
|
17
|
+
return self unless self =~ %r/[A-Z]/
|
18
|
+
self.reverse.scan(%r/[A-Z]+|[^A-Z]*[A-Z]+?/).reverse.map{|word| word.reverse.downcase}.join '_'
|
19
|
+
end
|
20
|
+
|
21
|
+
# "foo_bar".camel_case #=> "FooBar"
|
22
|
+
def camel_case
|
23
|
+
return self if self =~ %r/[A-Z]/ and self !~ %r/_/
|
24
|
+
words = self.strip.split %r/\s*_+\s*/
|
25
|
+
words.map!{|w| w.downcase.sub(%r/^./){|c| c.upcase}}
|
26
|
+
words.join
|
27
|
+
end
|
28
|
+
|
29
|
+
# Concatenates a path
|
30
|
+
def /(o)
|
31
|
+
File.join(self, o.to_s)
|
32
|
+
end
|
33
|
+
|
34
|
+
def to_const
|
35
|
+
const = const.to_s.dup
|
36
|
+
base = const.sub!(/^::/, '') ? Object : ( self.kind_of?(Module) ? self : self.class )
|
37
|
+
const.split(/::/).inject(base){ |mod, name| mod.const_get(name) }
|
38
|
+
end
|
39
|
+
|
40
|
+
end
|
@@ -0,0 +1,18 @@
|
|
1
|
+
module Merb
|
2
|
+
module Const
|
3
|
+
|
4
|
+
ESCAPE_TABLE = {
|
5
|
+
'&' => '&',
|
6
|
+
'<' => '<',
|
7
|
+
'>' => '>',
|
8
|
+
'"' => '"',
|
9
|
+
"'" => ''',
|
10
|
+
}.freeze
|
11
|
+
|
12
|
+
DEFAULT_SEND_FILE_OPTIONS = {
|
13
|
+
:type => 'application/octet-stream'.freeze,
|
14
|
+
:disposition => 'attachment'.freeze
|
15
|
+
}.freeze
|
16
|
+
|
17
|
+
end
|
18
|
+
end
|
data/lib/merb/merb_controller.rb
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
require File.dirname(__FILE__)+'/mixins/controller_mixin'
|
2
2
|
require File.dirname(__FILE__)+'/mixins/render_mixin'
|
3
|
-
require File.dirname(__FILE__)+'/mixins/javascript_mixin'
|
4
3
|
require File.dirname(__FILE__)+'/mixins/responder_mixin'
|
4
|
+
require File.dirname(__FILE__)+'/merb_request'
|
5
5
|
|
6
6
|
module Merb
|
7
7
|
|
@@ -12,90 +12,99 @@ module Merb
|
|
12
12
|
# to your controller via params. It also parses the ?query=string and
|
13
13
|
# puts that into params as well.
|
14
14
|
class Controller
|
15
|
+
|
16
|
+
meta_accessor :layout
|
17
|
+
|
15
18
|
include Merb::ControllerMixin
|
16
19
|
include Merb::RenderMixin
|
17
|
-
include Merb::JavascriptMixin
|
18
20
|
include Merb::ResponderMixin
|
19
21
|
|
20
|
-
if Merb::Server.config[:session]
|
21
|
-
require "drb"
|
22
|
-
DRb.start_service('druby://localhost:0')
|
23
|
-
Merb.const_set :DRbSession, DRbObject.new(nil, "druby://#{Merb::Server.config[:host]}:#{Merb::Server.config[:session]}")
|
24
|
-
require File.dirname(__FILE__)+"/session/merb_drb_session"
|
25
|
-
include ::Merb::SessionMixin
|
26
|
-
puts "drb session mixed in"
|
27
|
-
end
|
28
|
-
|
29
22
|
attr_accessor :status, :body
|
30
23
|
|
24
|
+
MULTIPART_REGEXP = /\Amultipart\/form-data.*boundary=\"?([^\";,]+)/n.freeze
|
25
|
+
CONTENT_DISPOSITION_REGEXP = /^Content-Disposition: form-data;/.freeze
|
26
|
+
FIELD_ATTRIBUTE_REGEXP = /(?:\s(\w+)="([^"]+)")/.freeze
|
27
|
+
CONTENT_TYPE_REGEXP = /^Content-Type: (.+?)(\r$|\Z)/m.freeze
|
28
|
+
|
31
29
|
# parses the http request into params, headers and cookies
|
32
30
|
# that you can use in your controller classes. Also handles
|
33
31
|
# file uploads by writing a tempfile and passing a reference
|
34
32
|
# in params.
|
35
33
|
def initialize(req, env, args, method=(env['REQUEST_METHOD']||'GET'))
|
36
|
-
env = MerbHash[env.to_hash]
|
37
|
-
@layout = :application
|
34
|
+
env = ::MerbHash[env.to_hash]
|
38
35
|
@status, @method, @env, @headers, @root = 200, method.downcase.to_sym, env,
|
39
36
|
{'Content-Type' =>'text/html'}, env['SCRIPT_NAME'].sub(/\/$/,'')
|
40
|
-
|
41
|
-
|
42
|
-
|
37
|
+
cookies = query_parse(env['HTTP_COOKIE'], ';,')
|
38
|
+
querystring = query_parse(env['QUERY_STRING'])
|
39
|
+
self.layout ||= :application
|
43
40
|
@in = req
|
44
|
-
if
|
45
|
-
|
41
|
+
if MULTIPART_REGEXP =~ env['CONTENT_TYPE']
|
42
|
+
boundary_regexp = /(?:\r?\n|\A)#{Regexp::quote("--#$1")}(?:--)?\r$/
|
46
43
|
until @in.eof?
|
47
|
-
|
48
|
-
for
|
49
|
-
case
|
44
|
+
attrs=MerbHash[]
|
45
|
+
for line in @in
|
46
|
+
case line
|
50
47
|
when "\r\n" : break
|
51
|
-
when
|
52
|
-
|
53
|
-
when
|
54
|
-
|
55
|
-
fh[:type] = $1
|
48
|
+
when CONTENT_DISPOSITION_REGEXP
|
49
|
+
attrs.update ::MerbHash[*$'.scan(FIELD_ATTRIBUTE_REGEXP).flatten]
|
50
|
+
when CONTENT_TYPE_REGEXP
|
51
|
+
attrs[:type] = $1
|
56
52
|
end
|
57
53
|
end
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
54
|
+
name=attrs[:name]
|
55
|
+
io_buffer=if attrs[:filename]
|
56
|
+
io_buffer=attrs[:tempfile]=Tempfile.new(:Merb)
|
57
|
+
io_buffer.binmode
|
62
58
|
else
|
63
|
-
|
59
|
+
attrs=""
|
64
60
|
end
|
65
|
-
while
|
66
|
-
if
|
67
|
-
|
68
|
-
@in.seek(-$'.size,IO::SEEK_CUR)
|
61
|
+
while chunk=@in.read(16384)
|
62
|
+
if chunk =~ boundary_regexp
|
63
|
+
io_buffer << $`.chomp
|
64
|
+
@in.seek(-$'.size, IO::SEEK_CUR)
|
69
65
|
break
|
70
66
|
end
|
71
|
-
|
67
|
+
io_buffer << chunk
|
72
68
|
end
|
73
|
-
|
74
|
-
|
69
|
+
querystring[name]=attrs if name
|
70
|
+
attrs[:tempfile].rewind if attrs.is_a?MerbHash
|
75
71
|
end
|
76
|
-
elsif @method == :post
|
72
|
+
elsif @method == :post
|
77
73
|
if ['application/json', 'text/x-json'].include?(env['CONTENT_TYPE'])
|
78
74
|
MERB_LOGGER.info("JSON Request")
|
79
75
|
json = JSON.parse(@in.read || "") || {}
|
80
|
-
json = MerbHash.new(json) if json.is_a? Hash
|
81
|
-
|
76
|
+
json = ::MerbHash.new(json) if json.is_a? Hash
|
77
|
+
querystring.merge!(json)
|
82
78
|
else
|
83
|
-
|
79
|
+
querystring.merge!(query_parse(@in.read))
|
84
80
|
end
|
85
81
|
end
|
86
|
-
@cookies, @params =
|
87
|
-
@cookies.merge!(:
|
82
|
+
@cookies, @params = cookies.dup, querystring.dup.merge(args)
|
83
|
+
@cookies.merge!(:session_id => @params[:session_id]) if @params.has_key?(:session_id)
|
84
|
+
@method = @params.delete(:_method).downcase.to_sym if @params.has_key?(:_method)
|
85
|
+
@request = Request.new(@env, @method)
|
88
86
|
MERB_LOGGER.info("Params: #{params.inspect}")
|
89
87
|
end
|
90
88
|
|
91
89
|
def dispatch(action=:to_s)
|
92
90
|
start = Time.now
|
93
91
|
setup_session if respond_to?:setup_session
|
94
|
-
|
92
|
+
cought = catch(:halt) { call_filters(before_filters) }
|
93
|
+
case cought
|
94
|
+
when :filter_chain_completed
|
95
95
|
@body = send(action)
|
96
|
-
|
96
|
+
when String
|
97
|
+
@body = cought
|
98
|
+
when nil
|
97
99
|
@body = filters_halted
|
98
|
-
|
100
|
+
when Symbol
|
101
|
+
@body = send(cought)
|
102
|
+
when Proc
|
103
|
+
@body = cought.call(self)
|
104
|
+
else
|
105
|
+
raise MerbControllerError, "The before filter chain is broken dude."
|
106
|
+
end
|
107
|
+
call_filters(after_filters)
|
99
108
|
finalize_session if respond_to?:finalize_session
|
100
109
|
MERB_LOGGER.info("Time spent in #{action} action: #{Time.now - start} seconds")
|
101
110
|
end
|
@@ -105,7 +114,13 @@ module Merb
|
|
105
114
|
def filters_halted
|
106
115
|
"<html><body><h1>Filter Chain Halted!</h1></body></html>"
|
107
116
|
end
|
108
|
-
|
117
|
+
|
118
|
+
# accessor for @request. Please use request and
|
119
|
+
# never @request directly.
|
120
|
+
def request
|
121
|
+
@request
|
122
|
+
end
|
123
|
+
|
109
124
|
# accessor for @params. Please use params and
|
110
125
|
# never @params directly.
|
111
126
|
def params
|
@@ -123,18 +138,27 @@ module Merb
|
|
123
138
|
def headers
|
124
139
|
@headers
|
125
140
|
end
|
126
|
-
|
141
|
+
|
142
|
+
# accessor for @session. Please use session and
|
143
|
+
# never @session directly.
|
144
|
+
def session
|
145
|
+
@session
|
146
|
+
end
|
147
|
+
|
127
148
|
# meta_accessor sets up a class instance variable that can
|
128
149
|
# be unique for each class but also inherits the meta attrs
|
129
150
|
# from its superclasses. Since @@class variables are almost
|
130
|
-
# global vars within an inheritance tree
|
151
|
+
# global vars within an inheritance tree, we use
|
152
|
+
# @class_instance_variables instead
|
131
153
|
meta_accessor :before_filters
|
154
|
+
meta_accessor :after_filters
|
132
155
|
|
156
|
+
# calls a filter chain according to rules.
|
133
157
|
def call_filters(filter_set)
|
134
158
|
(filter_set || []).each do |(filter, rule)|
|
135
159
|
ok = false
|
136
|
-
if rule.has_key?(:
|
137
|
-
if rule[:
|
160
|
+
if rule.has_key?(:only)
|
161
|
+
if rule[:only].include?(params[:action].intern)
|
138
162
|
ok = true
|
139
163
|
end
|
140
164
|
elsif rule.has_key?(:exclude)
|
@@ -150,23 +174,63 @@ module Merb
|
|
150
174
|
when Proc
|
151
175
|
filter.call(self) if ok
|
152
176
|
end
|
153
|
-
end
|
177
|
+
end
|
178
|
+
return :filter_chain_completed
|
154
179
|
end
|
155
180
|
|
156
181
|
# #before is a class method that allows you to specify before
|
157
|
-
# filters in your controllers. Filters can either
|
158
|
-
# or string that corresponds to a method name or a
|
159
|
-
# if it is a method name that method will be
|
160
|
-
# is a proc it will be called with an argument
|
182
|
+
# filters in your controllers. Filters can either be a symbol
|
183
|
+
# or string that corresponds to a method name to call, or a
|
184
|
+
# proc object. if it is a method name that method will be
|
185
|
+
# called and if it is a proc it will be called with an argument
|
186
|
+
# of self where self is the current controller object. When
|
161
187
|
# you use a proc as a filter it needs to take one parameter.
|
188
|
+
#
|
189
|
+
# examples:
|
190
|
+
# before :some_filter
|
191
|
+
# before :authenticate, :exclude => [:login, :signup]
|
192
|
+
# before Proc.new {|c| c.some_method }, :only => :foo
|
193
|
+
#
|
194
|
+
# You can use either :only => :actionname or :exclude => [:this, :that]
|
195
|
+
# but not both at once. :only will only run before the listed actions
|
196
|
+
# and :exclude will run for every action that is not listed.
|
197
|
+
#
|
198
|
+
# Merb's before filter chain is very flixible. To halt the
|
199
|
+
# filter chain you use throw :halt . If throw is called with
|
200
|
+
# only one argument of :halt the return of the method filters_halted
|
201
|
+
# will be what is rendered to the view. You can overide filters_halted
|
202
|
+
# in your own controllers to control what it outputs. But the throw
|
203
|
+
# construct is much more powerful then just that. throw :halt can
|
204
|
+
# also take a second argument. Here is what that second arg can be
|
205
|
+
# and the behavior each type can have:
|
206
|
+
#
|
207
|
+
# when the second arg is a string then that string will be what
|
208
|
+
# is rendered to the browser. Since merb's render method returns
|
209
|
+
# a string you can render a template or just use a plain string:
|
210
|
+
#
|
211
|
+
# String:
|
212
|
+
# throw :halt, "You don't have permissions to do that!"
|
213
|
+
# throw :halt, render(:action => :access_denied)
|
214
|
+
#
|
215
|
+
# if the second arg is a symbol then the method named after that
|
216
|
+
# symbol will be called
|
217
|
+
# Symbol:
|
218
|
+
# throw :halt, :must_click_disclaimer
|
219
|
+
#
|
220
|
+
# If the second arg is a Proc, it will be called and its return
|
221
|
+
# value will be what is rendered to the browser:
|
222
|
+
# Proc:
|
223
|
+
# throw :halt, Proc.new {|c| c.access_denied }
|
224
|
+
# throw :halt, Proc.new {|c| Tidy.new(c.index) }
|
225
|
+
#
|
162
226
|
def self.before(filter, opts={})
|
163
227
|
raise(ArgumentError,
|
164
|
-
"You can specify either :
|
228
|
+
"You can specify either :only or :exclude but
|
165
229
|
not both at the same time for the same filter."
|
166
|
-
) if opts.has_key?(:
|
230
|
+
) if opts.has_key?(:only) && opts.has_key?(:exclude)
|
167
231
|
|
168
|
-
if opts[:
|
169
|
-
opts[:
|
232
|
+
if opts[:only] && opts[:only].is_a?(Symbol)
|
233
|
+
opts[:only] = [opts[:only]]
|
170
234
|
end
|
171
235
|
if opts[:exclude] && opts[:exclude].is_a?(Symbol)
|
172
236
|
opts[:exclude] = [opts[:exclude]]
|
@@ -181,23 +245,33 @@ module Merb
|
|
181
245
|
)
|
182
246
|
end
|
183
247
|
end
|
184
|
-
|
185
|
-
|
186
|
-
|
187
|
-
|
188
|
-
|
248
|
+
|
249
|
+
# #after is a class method that allows you to specify after
|
250
|
+
# filters in your controllers. Filters can either be a symbol
|
251
|
+
# or string that corresponds to a method name or a proc object.
|
252
|
+
# if it is a method name that method will be called and if it
|
253
|
+
# is a proc it will be called with an argument of self. When
|
254
|
+
# you use a proc as a filter it needs to take one parameter.
|
255
|
+
# you can gain access to the response body like so:
|
256
|
+
# after Proc.new {|c| Tidy.new(c.body) }, :only => :index
|
257
|
+
#
|
258
|
+
def self.after(filter, opts={})
|
259
|
+
raise(ArgumentError,
|
260
|
+
"You can specify either :only or :exclude but
|
261
|
+
not both at the same time for the same filter."
|
262
|
+
) if opts.has_key?(:only) && opts.has_key?(:exclude)
|
263
|
+
raise(ArgumentError,
|
264
|
+
'after filters can only be a Proc object'
|
265
|
+
) unless Proc === filter
|
266
|
+
if opts[:only] && opts[:only].is_a?(Symbol)
|
267
|
+
opts[:only] = [opts[:only]]
|
268
|
+
end
|
269
|
+
if opts[:exclude] && opts[:exclude].is_a?(Symbol)
|
270
|
+
opts[:exclude] = [opts[:exclude]]
|
271
|
+
end
|
272
|
+
(self.after_filters ||= []) << [filter, opts]
|
189
273
|
end
|
190
274
|
|
191
275
|
end
|
192
276
|
|
193
277
|
end
|
194
|
-
|
195
|
-
class Noroutefound < Merb::Controller
|
196
|
-
# This is the class that handles requests that don't
|
197
|
-
# match any defined routes.
|
198
|
-
def method_missing
|
199
|
-
@status = 404
|
200
|
-
"<html><body><h1>No Matching Route!</h1></body></html>"
|
201
|
-
end
|
202
|
-
|
203
|
-
end
|
@@ -1,5 +1,5 @@
|
|
1
1
|
require 'drb'
|
2
|
-
require '
|
2
|
+
require File.dirname(__FILE__)+'/merb_upload_progress'
|
3
3
|
|
4
4
|
module Merb
|
5
5
|
|
@@ -15,6 +15,7 @@ module Merb
|
|
15
15
|
@timestamps = Hash.new
|
16
16
|
@mutex = Mutex.new
|
17
17
|
@session_ttl = opts.fetch(:session_ttl, 15*60) # default 15 minutes
|
18
|
+
start_timer
|
18
19
|
self
|
19
20
|
end
|
20
21
|
|
@@ -51,57 +52,23 @@ module Merb
|
|
51
52
|
GC.start
|
52
53
|
end
|
53
54
|
|
55
|
+
def start_timer
|
56
|
+
Thread.new do
|
57
|
+
sleep @session_ttl
|
58
|
+
reap_old_sessions
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
54
62
|
def sessions
|
55
63
|
@sessions
|
56
64
|
end
|
57
65
|
|
66
|
+
def upload_progress
|
67
|
+
::Merb::UploadProgress.new
|
68
|
+
end
|
69
|
+
|
58
70
|
end # end singleton class
|
59
71
|
|
60
72
|
end # end DRbSession
|
61
73
|
|
62
|
-
# Keeps track of the status of all currently processing uploads
|
63
|
-
class UploadProgress
|
64
|
-
attr_accessor :debug
|
65
|
-
def initialize
|
66
|
-
@guard = Mutex.new
|
67
|
-
@counters = {}
|
68
|
-
end
|
69
|
-
|
70
|
-
def check(upid)
|
71
|
-
@counters[upid].last rescue nil
|
72
|
-
end
|
73
|
-
|
74
|
-
def last_checked(upid)
|
75
|
-
@counters[upid].first rescue nil
|
76
|
-
end
|
77
|
-
|
78
|
-
def update_checked_time(upid)
|
79
|
-
@guard.synchronize { @counters[upid][0] = Time.now }
|
80
|
-
end
|
81
|
-
|
82
|
-
def add(upid, size)
|
83
|
-
@guard.synchronize do
|
84
|
-
@counters[upid] = [Time.now, {:size => size, :received => 0}]
|
85
|
-
puts "#{upid}: Added" if @debug
|
86
|
-
end
|
87
|
-
end
|
88
|
-
|
89
|
-
def mark(upid, len)
|
90
|
-
return unless status = check(upid)
|
91
|
-
puts "#{upid}: Marking" if @debug
|
92
|
-
@guard.synchronize { status[:received] = status[:size] - len }
|
93
|
-
end
|
94
|
-
|
95
|
-
def finish(upid)
|
96
|
-
@guard.synchronize do
|
97
|
-
puts "#{upid}: Finished" if @debug
|
98
|
-
@counters.delete(upid)
|
99
|
-
end
|
100
|
-
end
|
101
|
-
|
102
|
-
def list
|
103
|
-
@counters.keys.sort
|
104
|
-
end
|
105
|
-
end
|
106
|
-
|
107
74
|
end
|