merb 0.0.7 → 0.0.8
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.
- 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
|