opentox-ruby 0.0.2 → 1.0.0
Sign up to get free protection for your applications and to get access to all the features.
- data/README.markdown +41 -0
- data/Rakefile +4 -0
- data/VERSION +1 -1
- data/lib/algorithm.rb +15 -2
- data/lib/authorization.rb +381 -0
- data/lib/compound.rb +6 -0
- data/lib/config/config_ru.rb +1 -0
- data/lib/dataset.rb +98 -43
- data/lib/environment.rb +9 -18
- data/lib/error.rb +99 -0
- data/lib/feature.rb +30 -2
- data/lib/helper.rb +90 -17
- data/lib/model.rb +81 -34
- data/lib/ontology_service.rb +43 -0
- data/lib/opentox-ruby.rb +3 -2
- data/lib/opentox.rb +9 -4
- data/lib/overwrite.rb +87 -28
- data/lib/parser.rb +117 -22
- data/lib/policy.rb +261 -0
- data/lib/rest_client_wrapper.rb +110 -99
- data/lib/serializer.rb +130 -1
- data/lib/task.rb +179 -42
- data/lib/templates/config.yaml +45 -0
- data/lib/templates/default_guest_policy.xml +53 -0
- data/lib/templates/default_policy.xml +53 -0
- data/lib/to-html.rb +112 -0
- data/lib/validation.rb +183 -57
- metadata +31 -94
- data/README.rdoc +0 -23
data/lib/rest_client_wrapper.rb
CHANGED
@@ -1,32 +1,4 @@
|
|
1
1
|
module OpenTox
|
2
|
-
|
3
|
-
#PENDING: implement ot error api, move to own file
|
4
|
-
class Error
|
5
|
-
|
6
|
-
attr_accessor :code, :body, :uri, :payload, :headers
|
7
|
-
|
8
|
-
def initialize(code, body, uri, payload, headers)
|
9
|
-
self.code = code
|
10
|
-
self.body = body.to_s[0..1000]
|
11
|
-
self.uri = uri
|
12
|
-
self.payload = payload
|
13
|
-
self.headers = headers
|
14
|
-
end
|
15
|
-
|
16
|
-
def self.parse(error_array_string)
|
17
|
-
begin
|
18
|
-
err = YAML.load(error_array_string)
|
19
|
-
if err and err.is_a?(Array) and err.size>0 and err[0].is_a?(Error)
|
20
|
-
return err
|
21
|
-
else
|
22
|
-
return nil
|
23
|
-
end
|
24
|
-
rescue
|
25
|
-
return nil
|
26
|
-
end
|
27
|
-
end
|
28
|
-
|
29
|
-
end
|
30
2
|
|
31
3
|
class WrapperResult < String
|
32
4
|
attr_accessor :content_type, :code
|
@@ -34,45 +6,80 @@ module OpenTox
|
|
34
6
|
|
35
7
|
class RestClientWrapper
|
36
8
|
|
37
|
-
|
38
|
-
|
9
|
+
# performs a GET REST call
|
10
|
+
# raises OpenTox::Error if call fails (rescued in overwrite.rb -> halt 502)
|
11
|
+
# per default: waits for Task to finish and returns result URI of Task
|
12
|
+
# @param [String] uri destination URI
|
13
|
+
# @param [optional,Hash] headers contains params like accept-header
|
14
|
+
# @param [optional,OpenTox::Task] waiting_task (can be a OpenTox::Subtask as well), progress is updated accordingly
|
15
|
+
# @param [wait,Boolean] wait set to false to NOT wait for task if result is task
|
16
|
+
# @return [OpenTox::WrapperResult] a String containing the result-body of the REST call
|
17
|
+
def self.get(uri, headers={}, waiting_task=nil, wait=true )
|
18
|
+
execute( "get", uri, nil, headers, waiting_task, wait)
|
39
19
|
end
|
40
20
|
|
41
|
-
|
42
|
-
|
21
|
+
# performs a POST REST call
|
22
|
+
# raises OpenTox::Error if call fails (rescued in overwrite.rb -> halt 502)
|
23
|
+
# per default: waits for Task to finish and returns result URI of Task
|
24
|
+
# @param [String] uri destination URI
|
25
|
+
# @param [optional,String] payload data posted to the service
|
26
|
+
# @param [optional,Hash] headers contains params like accept-header
|
27
|
+
# @param [optional,OpenTox::Task] waiting_task (can be a OpenTox::Subtask as well), progress is updated accordingly
|
28
|
+
# @param [wait,Boolean] wait set to false to NOT wait for task if result is task
|
29
|
+
# @return [OpenTox::WrapperResult] a String containing the result-body of the REST call
|
30
|
+
def self.post(uri, payload=nil, headers={}, waiting_task=nil, wait=true )
|
31
|
+
execute( "post", uri, payload, headers, waiting_task, wait )
|
43
32
|
end
|
44
33
|
|
45
|
-
|
46
|
-
|
34
|
+
# performs a PUT REST call
|
35
|
+
# raises OpenTox::Error if call fails (rescued in overwrite.rb -> halt 502)
|
36
|
+
# @param [String] uri destination URI
|
37
|
+
# @param [optional,Hash] headers contains params like accept-header
|
38
|
+
# @param [optional,String] payload data put to the service
|
39
|
+
# @return [OpenTox::WrapperResult] a String containing the result-body of the REST call
|
40
|
+
def self.put(uri, payload=nil, headers={} )
|
41
|
+
execute( "put", uri, payload, headers )
|
47
42
|
end
|
48
43
|
|
49
|
-
|
50
|
-
|
44
|
+
# performs a DELETE REST call
|
45
|
+
# raises OpenTox::Error if call fails (rescued in overwrite.rb -> halt 502)
|
46
|
+
# @param [String] uri destination URI
|
47
|
+
# @param [optional,Hash] headers contains params like accept-header
|
48
|
+
# @return [OpenTox::WrapperResult] a String containing the result-body of the REST call
|
49
|
+
def self.delete(uri, headers=nil )
|
50
|
+
execute( "delete", uri, nil, headers)
|
51
51
|
end
|
52
52
|
|
53
|
-
def self.raise_uri_error(error_msg, uri, headers=nil, payload=nil)
|
54
|
-
do_halt( "-", error_msg, uri, headers, payload )
|
55
|
-
end
|
56
|
-
|
57
53
|
private
|
58
|
-
def self.execute( rest_call, uri, headers,
|
54
|
+
def self.execute( rest_call, uri, payload=nil, headers={}, waiting_task=nil, wait=true )
|
59
55
|
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
56
|
+
raise OpenTox::BadRequestError.new "uri is null" unless uri
|
57
|
+
raise OpenTox::BadRequestError.new "not a uri: "+uri.to_s unless uri.to_s.uri?
|
58
|
+
raise "headers are no hash: "+headers.inspect unless headers==nil or headers.is_a?(Hash)
|
59
|
+
raise OpenTox::BadRequestError.new "accept should go into the headers" if payload and payload.is_a?(Hash) and payload[:accept]
|
60
|
+
raise OpenTox::BadRequestError.new "content_type should go into the headers" if payload and payload.is_a?(Hash) and payload[:content_type]
|
61
|
+
raise "__waiting_task__ must be 'nil' or '(sub)task', is "+waiting_task.class.to_s if
|
62
|
+
waiting_task!=nil and !(waiting_task.is_a?(Task) || waiting_task.is_a?(SubTask))
|
64
63
|
headers.each{ |k,v| headers.delete(k) if v==nil } if headers #remove keys with empty values, as this can cause problems
|
64
|
+
## PENDING partner services accept subjectid only in header
|
65
|
+
headers = {} unless headers
|
66
|
+
headers[:subjectid] = payload.delete(:subjectid) if payload and payload.is_a?(Hash) and payload.has_key?(:subjectid)
|
67
|
+
|
68
|
+
# PENDING needed for NUTA, until we finally agree on how to send subjectid
|
69
|
+
headers[:subjectid] = payload.delete(:subjectid) if uri=~/ntua/ and payload and payload.is_a?(Hash) and payload.has_key?(:subjectid)
|
65
70
|
|
66
71
|
begin
|
67
|
-
#LOGGER.debug "RestCall: "+rest_call.to_s+" "+uri.to_s+" "+headers.inspect
|
68
|
-
resource = RestClient::Resource.new(uri,{:timeout => 60})
|
69
|
-
if
|
72
|
+
#LOGGER.debug "RestCall: "+rest_call.to_s+" "+uri.to_s+" "+headers.inspect+" "+payload.inspect
|
73
|
+
resource = RestClient::Resource.new(uri,{:timeout => 60})
|
74
|
+
if rest_call=="post" || rest_call=="put"
|
70
75
|
result = resource.send(rest_call, payload, headers)
|
71
|
-
elsif headers
|
72
|
-
result = resource.send(rest_call, headers)
|
73
76
|
else
|
74
|
-
result = resource.send(rest_call)
|
77
|
+
result = resource.send(rest_call, headers)
|
75
78
|
end
|
79
|
+
#LOGGER.debug "result body size: #{result.body.size}"
|
80
|
+
|
81
|
+
# PENDING NTUA does return errors with 200
|
82
|
+
raise RestClient::ExceptionWithResponse.new(result) if uri=~/ntua/ and result.body =~ /about.*http:\/\/anonymous.org\/error/
|
76
83
|
|
77
84
|
# result is a string, with the additional fields content_type and code
|
78
85
|
res = WrapperResult.new(result.body)
|
@@ -84,29 +91,31 @@ module OpenTox
|
|
84
91
|
return res if res.code==200 || !wait
|
85
92
|
|
86
93
|
while (res.code==201 || res.code==202)
|
87
|
-
res = wait_for_task(res, uri)
|
94
|
+
res = wait_for_task(res, uri, waiting_task)
|
88
95
|
end
|
89
96
|
raise "illegal status code: '"+res.code.to_s+"'" unless res.code==200
|
90
97
|
return res
|
91
98
|
|
92
99
|
rescue RestClient::RequestTimeout => ex
|
93
|
-
|
100
|
+
received_error ex.message, 408, nil, {:rest_uri => uri, :headers => headers, :payload => payload}
|
101
|
+
rescue Errno::ECONNREFUSED => ex
|
102
|
+
received_error ex.message, 500, nil, {:rest_uri => uri, :headers => headers, :payload => payload}
|
103
|
+
rescue RestClient::ExceptionWithResponse => ex
|
104
|
+
# error comming from a different webservice,
|
105
|
+
received_error ex.http_body, ex.http_code, ex.response.net_http_res.content_type, {:rest_uri => uri, :headers => headers, :payload => payload}
|
106
|
+
rescue OpenTox::RestCallError => ex
|
107
|
+
# already a rest-error, probably comes from wait_for_task, just pass through
|
108
|
+
raise ex
|
94
109
|
rescue => ex
|
95
|
-
#
|
96
|
-
|
97
|
-
begin
|
98
|
-
code = ex.http_code
|
99
|
-
msg = ex.http_body
|
100
|
-
rescue
|
101
|
-
code = 500
|
102
|
-
msg = ex.to_s
|
103
|
-
end
|
104
|
-
do_halt code,msg,uri,headers,payload
|
110
|
+
# some internal error occuring in rest_client_wrapper, just pass through
|
111
|
+
raise ex
|
105
112
|
end
|
106
113
|
end
|
107
114
|
|
108
|
-
def self.wait_for_task( res, base_uri )
|
109
|
-
|
115
|
+
def self.wait_for_task( res, base_uri, waiting_task=nil )
|
116
|
+
#TODO remove TUM hack
|
117
|
+
res.content_type = "text/uri-list" if base_uri =~/tu-muenchen/ and res.content_type == "application/x-www-form-urlencoded;charset=UTF-8"
|
118
|
+
|
110
119
|
task = nil
|
111
120
|
case res.content_type
|
112
121
|
when /application\/rdf\+xml/
|
@@ -115,53 +124,55 @@ module OpenTox
|
|
115
124
|
task = OpenTox::Task.from_yaml(res)
|
116
125
|
when /text\//
|
117
126
|
raise "uri list has more than one entry, should be a task" if res.content_type=~/text\/uri-list/ and res.split("\n").size > 1 #if uri list contains more then one uri, its not a task
|
118
|
-
task = OpenTox::Task.find(res.to_s) if res.to_s.uri?
|
127
|
+
task = OpenTox::Task.find(res.to_s.chomp) if res.to_s.uri?
|
119
128
|
else
|
120
|
-
raise "unknown content-type for task: '"+res.content_type.to_s+"'"
|
129
|
+
raise "unknown content-type for task : '"+res.content_type.to_s+"'"+" base-uri: "+base_uri.to_s+" content: "+res[0..200].to_s
|
121
130
|
end
|
122
131
|
|
123
132
|
LOGGER.debug "result is a task '"+task.uri.to_s+"', wait for completion"
|
124
|
-
task.wait_for_completion
|
125
|
-
|
126
|
-
|
133
|
+
task.wait_for_completion waiting_task
|
134
|
+
unless task.completed? # maybe task was cancelled / error
|
135
|
+
if task.errorReport
|
136
|
+
received_error task.errorReport, task.http_code, nil, {:rest_uri => task.uri, :rest_code => task.http_code}
|
137
|
+
else
|
138
|
+
raise "task status: '"+task.status.to_s+"' but errorReport nil"
|
139
|
+
end
|
140
|
+
end
|
141
|
+
|
127
142
|
res = WrapperResult.new task.result_uri
|
128
143
|
res.code = task.http_code
|
129
144
|
res.content_type = "text/uri-list"
|
130
145
|
return res
|
131
146
|
end
|
132
147
|
|
133
|
-
def self.
|
134
|
-
|
135
|
-
#
|
136
|
-
|
137
|
-
if
|
138
|
-
|
148
|
+
def self.received_error( body, code, content_type=nil, params=nil )
|
149
|
+
|
150
|
+
# try to parse body
|
151
|
+
report = nil
|
152
|
+
if body.is_a?(OpenTox::ErrorReport)
|
153
|
+
report = body
|
139
154
|
else
|
140
|
-
|
155
|
+
case content_type
|
156
|
+
when /yaml/
|
157
|
+
report = YAML.load(body)
|
158
|
+
when /rdf/
|
159
|
+
report = OpenTox::ErrorReport.from_rdf(body)
|
160
|
+
end
|
141
161
|
end
|
142
162
|
|
143
|
-
|
144
|
-
|
145
|
-
|
146
|
-
|
147
|
-
|
148
|
-
|
149
|
-
|
150
|
-
|
151
|
-
|
152
|
-
|
153
|
-
|
154
|
-
|
155
|
-
|
156
|
-
|
157
|
-
if $self_task #this global var in Task.create to mark that the current process is running in a task
|
158
|
-
raise error.to_yaml # the error is caught, logged, and task state is set to error in Task.create
|
159
|
-
#elsif $sinatra #else halt sinatra
|
160
|
-
#$sinatra.halt(502,error.to_yaml)
|
161
|
-
elsif defined?(halt)
|
162
|
-
halt(502,error.to_yaml)
|
163
|
-
else #for testing purposes (if classes used directly)
|
164
|
-
raise error.to_yaml
|
163
|
+
unless report
|
164
|
+
# parsing was not successfull
|
165
|
+
# raise 'plain' RestCallError
|
166
|
+
err = OpenTox::RestCallError.new("REST call returned error: '"+body.to_s+"'")
|
167
|
+
err.rest_params = params
|
168
|
+
raise err
|
169
|
+
else
|
170
|
+
# parsing sucessfull
|
171
|
+
# raise RestCallError with parsed report as error cause
|
172
|
+
err = OpenTox::RestCallError.new("REST call subsequent error")
|
173
|
+
err.errorCause = report
|
174
|
+
err.rest_params = params
|
175
|
+
raise err
|
165
176
|
end
|
166
177
|
end
|
167
178
|
end
|
data/lib/serializer.rb
CHANGED
@@ -14,7 +14,7 @@ module OpenTox
|
|
14
14
|
def initialize
|
15
15
|
|
16
16
|
@object = {
|
17
|
-
# this should come from
|
17
|
+
# this should come from opentox.owl
|
18
18
|
OT.Compound => { RDF["type"] => [{ "type" => "uri", "value" => OWL['Class'] }] } ,
|
19
19
|
OT.Feature => { RDF["type"] => [{ "type" => "uri", "value" => OWL['Class'] }] } ,
|
20
20
|
OT.NominalFeature => { RDF["type"] => [{ "type" => "uri", "value" => OWL['Class'] }] } ,
|
@@ -26,6 +26,16 @@ module OpenTox
|
|
26
26
|
OT.Algorithm => { RDF["type"] => [{ "type" => "uri", "value" => OWL['Class'] }] } ,
|
27
27
|
OT.Parameter => { RDF["type"] => [{ "type" => "uri", "value" => OWL['Class'] }] } ,
|
28
28
|
OT.Task => { RDF["type"] => [{ "type" => "uri", "value" => OWL['Class'] }] } ,
|
29
|
+
#classes for validation
|
30
|
+
OT.Validation => { RDF["type"] => [{ "type" => "uri", "value" => OWL['Class'] }] } ,
|
31
|
+
OT.ClassificationStatistics => { RDF["type"] => [{ "type" => "uri", "value" => OWL['Class'] }] } ,
|
32
|
+
OT.ConfusionMatrix => { RDF["type"] => [{ "type" => "uri", "value" => OWL['Class'] }] } ,
|
33
|
+
OT.ConfusionMatrixCell => { RDF["type"] => [{ "type" => "uri", "value" => OWL['Class'] }] } ,
|
34
|
+
OT.ClassValueStatistics => { RDF["type"] => [{ "type" => "uri", "value" => OWL['Class'] }] } ,
|
35
|
+
OT.RegressionStatistics => { RDF["type"] => [{ "type" => "uri", "value" => OWL['Class'] }] } ,
|
36
|
+
OT.Crossvalidation => { RDF["type"] => [{ "type" => "uri", "value" => OWL['Class'] }] } ,
|
37
|
+
OT.CrossvalidationInfo => { RDF["type"] => [{ "type" => "uri", "value" => OWL['Class'] }] } ,
|
38
|
+
OT.ErrorReport => { RDF["type"] => [{ "type" => "uri", "value" => OWL['Class'] }] } ,
|
29
39
|
|
30
40
|
OT.compound => { RDF["type"] => [{ "type" => "uri", "value" => OWL.ObjectProperty }] } ,
|
31
41
|
OT.feature => { RDF["type"] => [{ "type" => "uri", "value" => OWL.ObjectProperty }] } ,
|
@@ -34,6 +44,22 @@ module OpenTox
|
|
34
44
|
OT.values => { RDF["type"] => [{ "type" => "uri", "value" => OWL.ObjectProperty }] } ,
|
35
45
|
OT.algorithm => { RDF["type"] => [{ "type" => "uri", "value" => OWL.ObjectProperty }] } ,
|
36
46
|
OT.parameters => { RDF["type"] => [{ "type" => "uri", "value" => OWL.ObjectProperty }] } ,
|
47
|
+
#object props for validation#
|
48
|
+
OT.model => { RDF["type"] => [{ "type" => "uri", "value" => OWL.ObjectProperty }] } ,
|
49
|
+
OT.trainingDataset => { RDF["type"] => [{ "type" => "uri", "value" => OWL.ObjectProperty }] } ,
|
50
|
+
OT.predictionFeature => { RDF["type"] => [{ "type" => "uri", "value" => OWL.ObjectProperty }] } ,
|
51
|
+
OT.predictionDataset => { RDF["type"] => [{ "type" => "uri", "value" => OWL.ObjectProperty }] } ,
|
52
|
+
OT.crossvalidation => { RDF["type"] => [{ "type" => "uri", "value" => OWL.ObjectProperty }] } ,
|
53
|
+
OT.testTargetDataset => { RDF["type"] => [{ "type" => "uri", "value" => OWL.ObjectProperty }] } ,
|
54
|
+
OT.testDataset => { RDF["type"] => [{ "type" => "uri", "value" => OWL.ObjectProperty }] } ,
|
55
|
+
OT.classificationStatistics => { RDF["type"] => [{ "type" => "uri", "value" => OWL.ObjectProperty }] } ,
|
56
|
+
OT.confusionMatrix => { RDF["type"] => [{ "type" => "uri", "value" => OWL.ObjectProperty }] } ,
|
57
|
+
OT.confusionMatrixCell => { RDF["type"] => [{ "type" => "uri", "value" => OWL.ObjectProperty }] } ,
|
58
|
+
OT.classValueStatistics => { RDF["type"] => [{ "type" => "uri", "value" => OWL.ObjectProperty }] } ,
|
59
|
+
OT.regressionStatistics => { RDF["type"] => [{ "type" => "uri", "value" => OWL.ObjectProperty }] } ,
|
60
|
+
OT.validation => { RDF["type"] => [{ "type" => "uri", "value" => OWL.ObjectProperty }] } ,
|
61
|
+
OT.crossvalidationInfo => { RDF["type"] => [{ "type" => "uri", "value" => OWL.ObjectProperty }] } ,
|
62
|
+
OT.dataset => { RDF["type"] => [{ "type" => "uri", "value" => OWL.ObjectProperty }] } ,
|
37
63
|
|
38
64
|
DC.title => { RDF["type"] => [{ "type" => "uri", "value" => OWL.AnnotationProperty }] } ,
|
39
65
|
DC.identifier => { RDF["type"] => [{ "type" => "uri", "value" => OWL.AnnotationProperty }] } ,
|
@@ -47,6 +73,51 @@ module OpenTox
|
|
47
73
|
OT.hasStatus => { RDF["type"] => [{ "type" => "uri", "value" => OWL.AnnotationProperty }] } ,
|
48
74
|
OT.resultURI => { RDF["type"] => [{ "type" => "uri", "value" => OWL.AnnotationProperty }] } ,
|
49
75
|
OT.percentageCompleted => { RDF["type"] => [{ "type" => "uri", "value" => OWL.AnnotationProperty }] } ,
|
76
|
+
# annotation props for validation
|
77
|
+
OT.numUnpredicted => { RDF["type"] => [{ "type" => "uri", "value" => OWL.AnnotationProperty }] } ,
|
78
|
+
OT.crossvalidationFold => { RDF["type"] => [{ "type" => "uri", "value" => OWL.AnnotationProperty }] } ,
|
79
|
+
OT.numInstances => { RDF["type"] => [{ "type" => "uri", "value" => OWL.AnnotationProperty }] } ,
|
80
|
+
OT.numWithoutClass => { RDF["type"] => [{ "type" => "uri", "value" => OWL.AnnotationProperty }] } ,
|
81
|
+
OT.percentWithoutClass => { RDF["type"] => [{ "type" => "uri", "value" => OWL.AnnotationProperty }] } ,
|
82
|
+
OT.percentUnpredicted => { RDF["type"] => [{ "type" => "uri", "value" => OWL.AnnotationProperty }] } ,
|
83
|
+
OT.confusionMatrixActual => { RDF["type"] => [{ "type" => "uri", "value" => OWL.AnnotationProperty }] } ,
|
84
|
+
OT.confusionMatrixPredicted => { RDF["type"] => [{ "type" => "uri", "value" => OWL.AnnotationProperty }] } ,
|
85
|
+
OT.confusionMatrixValue => { RDF["type"] => [{ "type" => "uri", "value" => OWL.AnnotationProperty }] } ,
|
86
|
+
OT.numIncorrect => { RDF["type"] => [{ "type" => "uri", "value" => OWL.AnnotationProperty }] } ,
|
87
|
+
OT.percentCorrect => { RDF["type"] => [{ "type" => "uri", "value" => OWL.AnnotationProperty }] } ,
|
88
|
+
OT.numCorrect => { RDF["type"] => [{ "type" => "uri", "value" => OWL.AnnotationProperty }] } ,
|
89
|
+
OT.accuracy => { RDF["type"] => [{ "type" => "uri", "value" => OWL.AnnotationProperty }] } ,
|
90
|
+
OT.trueNegativeRate => { RDF["type"] => [{ "type" => "uri", "value" => OWL.AnnotationProperty }] } ,
|
91
|
+
OT.truePositiveRate => { RDF["type"] => [{ "type" => "uri", "value" => OWL.AnnotationProperty }] } ,
|
92
|
+
OT.falseNegativeRate => { RDF["type"] => [{ "type" => "uri", "value" => OWL.AnnotationProperty }] } ,
|
93
|
+
OT.falsePositiveRate => { RDF["type"] => [{ "type" => "uri", "value" => OWL.AnnotationProperty }] } ,
|
94
|
+
OT.numTrueNegatives => { RDF["type"] => [{ "type" => "uri", "value" => OWL.AnnotationProperty }] } ,
|
95
|
+
OT.numTruePositives => { RDF["type"] => [{ "type" => "uri", "value" => OWL.AnnotationProperty }] } ,
|
96
|
+
OT.numFalseNegatives => { RDF["type"] => [{ "type" => "uri", "value" => OWL.AnnotationProperty }] } ,
|
97
|
+
OT.numFalsePositives => { RDF["type"] => [{ "type" => "uri", "value" => OWL.AnnotationProperty }] } ,
|
98
|
+
OT.classValue => { RDF["type"] => [{ "type" => "uri", "value" => OWL.AnnotationProperty }] } ,
|
99
|
+
OT.precision => { RDF["type"] => [{ "type" => "uri", "value" => OWL.AnnotationProperty }] } ,
|
100
|
+
OT.areaUnderRoc => { RDF["type"] => [{ "type" => "uri", "value" => OWL.AnnotationProperty }] } ,
|
101
|
+
OT.weightedAreaUnderRoc => { RDF["type"] => [{ "type" => "uri", "value" => OWL.AnnotationProperty }] } ,
|
102
|
+
OT.fMeasure => { RDF["type"] => [{ "type" => "uri", "value" => OWL.AnnotationProperty }] } ,
|
103
|
+
OT.percentIncorrect => { RDF["type"] => [{ "type" => "uri", "value" => OWL.AnnotationProperty }] } ,
|
104
|
+
OT.validationType => { RDF["type"] => [{ "type" => "uri", "value" => OWL.AnnotationProperty }] } ,
|
105
|
+
OT.realRuntime => { RDF["type"] => [{ "type" => "uri", "value" => OWL.AnnotationProperty }] } ,
|
106
|
+
OT.sampleCorrelationCoefficient => { RDF["type"] => [{ "type" => "uri", "value" => OWL.AnnotationProperty }] } ,
|
107
|
+
OT.targetVarianceActual => { RDF["type"] => [{ "type" => "uri", "value" => OWL.AnnotationProperty }] } ,
|
108
|
+
OT.targetVariancePredicted => { RDF["type"] => [{ "type" => "uri", "value" => OWL.AnnotationProperty }] } ,
|
109
|
+
OT.meanAbsoluteError => { RDF["type"] => [{ "type" => "uri", "value" => OWL.AnnotationProperty }] } ,
|
110
|
+
OT.sumSquaredError => { RDF["type"] => [{ "type" => "uri", "value" => OWL.AnnotationProperty }] } ,
|
111
|
+
OT.rootMeanSquaredError => { RDF["type"] => [{ "type" => "uri", "value" => OWL.AnnotationProperty }] } ,
|
112
|
+
OT.rSquare => { RDF["type"] => [{ "type" => "uri", "value" => OWL.AnnotationProperty }] } ,
|
113
|
+
OT.stratified => { RDF["type"] => [{ "type" => "uri", "value" => OWL.AnnotationProperty }] } ,
|
114
|
+
OT.numFolds => { RDF["type"] => [{ "type" => "uri", "value" => OWL.AnnotationProperty }] } ,
|
115
|
+
OT.randomSeed => { RDF["type"] => [{ "type" => "uri", "value" => OWL.AnnotationProperty }] } ,
|
116
|
+
OT.reportType => { RDF["type"] => [{ "type" => "uri", "value" => OWL.AnnotationProperty }] } ,
|
117
|
+
OT.message => { RDF["type"] => [{ "type" => "uri", "value" => OWL.AnnotationProperty }] } ,
|
118
|
+
OT.statusCode => { RDF["type"] => [{ "type" => "uri", "value" => OWL.AnnotationProperty }] } ,
|
119
|
+
OT.actor => { RDF["type"] => [{ "type" => "uri", "value" => OWL.AnnotationProperty }] } ,
|
120
|
+
OT.errorCode => { RDF["type"] => [{ "type" => "uri", "value" => OWL.AnnotationProperty }] } ,
|
50
121
|
|
51
122
|
OT.hasSource => { RDF["type"] => [{ "type" => "uri", "value" => OWL.DatatypeProperty }] } ,
|
52
123
|
OT.value => { RDF["type"] => [{ "type" => "uri", "value" => OWL.DatatypeProperty }] } ,
|
@@ -121,6 +192,64 @@ module OpenTox
|
|
121
192
|
@object[uri] = { RDF["type"] => [{ "type" => "uri", "value" => OT.Task }] }
|
122
193
|
add_metadata uri, metadata
|
123
194
|
end
|
195
|
+
|
196
|
+
# Add a resource defined by resource_class and content
|
197
|
+
# (see documentation of add_content for example)
|
198
|
+
# @param [String] uri of resource
|
199
|
+
# @param [String] resource class, e.g. OT.Validation
|
200
|
+
# @param [Hash] content as hash
|
201
|
+
def add_resource(uri, resource_class, content)
|
202
|
+
@object[uri] = { RDF["type"] => [{ "type" => "uri", "value" => resource_class }] }
|
203
|
+
@@content_id = 1
|
204
|
+
add_content uri, content
|
205
|
+
end
|
206
|
+
|
207
|
+
private
|
208
|
+
@@content_id = 1
|
209
|
+
|
210
|
+
# Recursiv function to add content
|
211
|
+
# @example
|
212
|
+
# { DC.description => "bla",
|
213
|
+
# OT.similar_resources => [ "http://uri1", "http://uri2" ],
|
214
|
+
# OT.matrixCells =>
|
215
|
+
# [ { RDF.type => OT.MatrixCell, OT.cellIndex=1 OT.cellValue => "xy" },
|
216
|
+
# { RDF.type => OT.MatrixCell, OT.cellIndex=2 OT.cellValue => "z" } ],
|
217
|
+
# OT.info => { RDF.type => OT.ImportantInfo,
|
218
|
+
# DC.description => "blub" }
|
219
|
+
# }
|
220
|
+
# @param [String] uri
|
221
|
+
# @param [Hash] content as hash, uri must already have been added to @object
|
222
|
+
def add_content(uri, hash)
|
223
|
+
raise "content is no hash: "+hash.class.to_s unless hash.is_a?(Hash)
|
224
|
+
hash.each do |u,v|
|
225
|
+
if v.is_a? Hash
|
226
|
+
# value is again a hash, i.e. a new owl class is added
|
227
|
+
# first make sure type (==class) is set
|
228
|
+
type = v[RDF.type]
|
229
|
+
raise "type missing for "+u.to_s+" content:\n"+v.inspect unless type
|
230
|
+
raise "class unknown "+type.to_s+" (for "+u.to_s+")" unless @object.has_key?(type)
|
231
|
+
# create new node and add to current uri
|
232
|
+
genid = "_:#{type.split('#')[-1]}#{@@content_id}"
|
233
|
+
@@content_id += 1
|
234
|
+
@object[uri] = {} unless @object[uri]
|
235
|
+
@object[uri][u] = [{ "type" => "bnode", "value" => genid }]
|
236
|
+
# add content to new class
|
237
|
+
add_content(genid,v)
|
238
|
+
elsif v.is_a? Array
|
239
|
+
# value is an array, i.e. a list of values with property is added
|
240
|
+
v.each{ |vv| add_content( uri, { u => vv } ) }
|
241
|
+
else # v.is_a? String
|
242
|
+
# simple string value
|
243
|
+
@object[uri] = {} unless @object[uri]
|
244
|
+
@object[uri][u] = [] unless @object[uri][u]
|
245
|
+
raise "property unknown "+u.to_s if !@object.has_key?(u) and u!=RDF.type
|
246
|
+
# use << to allow different values for one property
|
247
|
+
@object[uri][u] << {"type" => type(v), "value" => v }
|
248
|
+
end
|
249
|
+
end
|
250
|
+
end
|
251
|
+
|
252
|
+
public
|
124
253
|
|
125
254
|
# Add metadata
|
126
255
|
# @param [Hash] metadata
|
data/lib/task.rb
CHANGED
@@ -1,4 +1,3 @@
|
|
1
|
-
$self_task=nil
|
2
1
|
|
3
2
|
module OpenTox
|
4
3
|
|
@@ -13,7 +12,7 @@ module OpenTox
|
|
13
12
|
DC.title => "",
|
14
13
|
DC.date => "",
|
15
14
|
OT.hasStatus => "Running",
|
16
|
-
OT.percentageCompleted =>
|
15
|
+
OT.percentageCompleted => 0.0,
|
17
16
|
OT.resultURI => "",
|
18
17
|
DC.creator => "", # not mandatory according to API
|
19
18
|
DC.description => "", # not mandatory according to API
|
@@ -34,7 +33,7 @@ module OpenTox
|
|
34
33
|
def self.create( title=nil, creator=nil, max_duration=DEFAULT_TASK_MAX_DURATION, description=nil )
|
35
34
|
|
36
35
|
params = {:title=>title, :creator=>creator, :max_duration=>max_duration, :description=>description }
|
37
|
-
task_uri = RestClientWrapper.post(CONFIG[:services]["opentox-task"], params, nil, false).to_s
|
36
|
+
task_uri = RestClientWrapper.post(CONFIG[:services]["opentox-task"], params, {}, nil, false).to_s
|
38
37
|
task = Task.new(task_uri.chomp)
|
39
38
|
|
40
39
|
# measure current memory consumption
|
@@ -49,34 +48,24 @@ module OpenTox
|
|
49
48
|
|
50
49
|
cpu_load = `cat /proc/loadavg`.split(/\s+/)[0..2].collect{|c| c.to_f}
|
51
50
|
nr_cpu_cores = `cat /proc/cpuinfo |grep "cpu cores"|cut -d ":" -f2|tr -d " "`.split("\n").collect{|c| c.to_i}.inject{|sum,n| sum+n}
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
51
|
+
nr_cpu_cores = 1 if !nr_cpu_cores
|
52
|
+
#if cpu_load[0] > nr_cpu_cores and cpu_load[0] > cpu_load[1] and cpu_load[1] > cpu_load[2] # average CPU load of the last minute is high and CPU load is increasing
|
53
|
+
# LOGGER.warn "Cannot start task - CPU load too high (#{cpu_load.join(", ")})"
|
54
|
+
# task.cancel
|
55
|
+
# return task
|
56
|
+
# #raise "Server too busy to start a new task"
|
57
|
+
#end
|
59
58
|
|
60
59
|
task_pid = Spork.spork(:logger => LOGGER) do
|
61
60
|
LOGGER.debug "Task #{task.uri} started #{Time.now}"
|
62
|
-
$self_task = task
|
63
|
-
|
64
61
|
begin
|
65
|
-
result =
|
66
|
-
yield task
|
67
|
-
end
|
68
|
-
# catching halt, set task state to error
|
69
|
-
if result && result.is_a?(Array) && result.size==2 && result[0]>202
|
70
|
-
LOGGER.error "task was halted: "+result.inspect
|
71
|
-
task.error(result[1])
|
72
|
-
return
|
73
|
-
end
|
62
|
+
result = yield task
|
74
63
|
LOGGER.debug "Task #{task.uri} done #{Time.now} -> "+result.to_s
|
75
64
|
task.completed(result)
|
76
|
-
rescue =>
|
77
|
-
LOGGER.error "task failed: "+
|
78
|
-
LOGGER.error "
|
79
|
-
task.error(
|
65
|
+
rescue => error
|
66
|
+
LOGGER.error "task failed: "+error.class.to_s+": "+error.message
|
67
|
+
LOGGER.error ":\n"+error.backtrace.join("\n")
|
68
|
+
task.error(OpenTox::ErrorReport.create(error, creator))
|
80
69
|
end
|
81
70
|
end
|
82
71
|
task.pid = task_pid
|
@@ -88,11 +77,23 @@ module OpenTox
|
|
88
77
|
# @param [String] uri Task URI
|
89
78
|
# @return [OpenTox::Task] Task object
|
90
79
|
def self.find(uri)
|
80
|
+
return nil unless uri
|
91
81
|
task = Task.new(uri)
|
92
82
|
task.load_metadata
|
83
|
+
raise "could not load task metadata" if task.metadata==nil or task.metadata.size==0
|
93
84
|
task
|
94
85
|
end
|
95
86
|
|
87
|
+
# Find a task for querying, status changes
|
88
|
+
# @param [String] uri Task URI
|
89
|
+
# @return [OpenTox::Task] Task object
|
90
|
+
def self.exist?(uri)
|
91
|
+
begin
|
92
|
+
return find(uri)
|
93
|
+
rescue
|
94
|
+
end
|
95
|
+
end
|
96
|
+
|
96
97
|
# Get a list of all tasks
|
97
98
|
# @param [optional, String] uri URI of task service
|
98
99
|
# @return [text/uri-list] Task URIs
|
@@ -103,16 +104,19 @@ module OpenTox
|
|
103
104
|
def self.from_yaml(yaml)
|
104
105
|
@metadata = YAML.load(yaml)
|
105
106
|
end
|
106
|
-
|
107
|
+
|
107
108
|
def self.from_rdfxml(rdfxml)
|
108
|
-
|
109
|
-
|
110
|
-
|
109
|
+
owl = OpenTox::Parser::Owl.from_rdf(rdfxml, OT.Task)
|
110
|
+
task = Task.new(owl.uri)
|
111
|
+
task.add_metadata(owl.metadata)
|
112
|
+
task
|
111
113
|
end
|
112
114
|
|
113
115
|
def to_rdfxml
|
114
116
|
s = Serializer::Owl.new
|
117
|
+
@metadata[OT.errorReport] = @uri+"/ErrorReport/tmpId" if @error_report
|
115
118
|
s.add_task(@uri,@metadata)
|
119
|
+
s.add_resource(@uri+"/ErrorReport/tmpId", OT.errorReport, @error_report.rdf_content) if @error_report
|
116
120
|
s.to_rdfxml
|
117
121
|
end
|
118
122
|
|
@@ -128,8 +132,12 @@ module OpenTox
|
|
128
132
|
@metadata[DC.description]
|
129
133
|
end
|
130
134
|
|
135
|
+
def errorReport
|
136
|
+
@metadata[OT.errorReport]
|
137
|
+
end
|
138
|
+
|
131
139
|
def cancel
|
132
|
-
RestClientWrapper.put(File.join(@uri,'Cancelled'))
|
140
|
+
RestClientWrapper.put(File.join(@uri,'Cancelled'),{:cannot_be => "empty"})
|
133
141
|
load_metadata
|
134
142
|
end
|
135
143
|
|
@@ -138,11 +146,17 @@ module OpenTox
|
|
138
146
|
load_metadata
|
139
147
|
end
|
140
148
|
|
141
|
-
def error(
|
142
|
-
|
149
|
+
def error(error_report)
|
150
|
+
raise "no error report" unless error_report.is_a?(OpenTox::ErrorReport)
|
151
|
+
RestClientWrapper.put(File.join(@uri,'Error'),{:errorReport => error_report.to_yaml})
|
143
152
|
load_metadata
|
144
153
|
end
|
145
154
|
|
155
|
+
# not stored just for to_rdf
|
156
|
+
def add_error_report( error_report )
|
157
|
+
@error_report = error_report
|
158
|
+
end
|
159
|
+
|
146
160
|
def pid=(pid)
|
147
161
|
RestClientWrapper.put(File.join(@uri,'pid'), {:pid => pid})
|
148
162
|
end
|
@@ -160,19 +174,20 @@ module OpenTox
|
|
160
174
|
end
|
161
175
|
|
162
176
|
def load_metadata
|
163
|
-
if (CONFIG[:yaml_hosts].include?(URI.parse(uri).host))
|
164
|
-
result = RestClientWrapper.get(@uri, {:accept => 'application/x-yaml'}, false)
|
177
|
+
if (CONFIG[:yaml_hosts].include?(URI.parse(@uri).host))
|
178
|
+
result = RestClientWrapper.get(@uri, {:accept => 'application/x-yaml'}, nil, false)
|
165
179
|
@metadata = YAML.load result.to_s
|
166
180
|
@http_code = result.code
|
167
181
|
else
|
168
182
|
@metadata = Parser::Owl::Generic.new(@uri).load_metadata
|
169
|
-
@http_code = RestClientWrapper.get(uri, {:accept => 'application/rdf+xml'}, false).code
|
183
|
+
@http_code = RestClientWrapper.get(uri, {:accept => 'application/rdf+xml'}, nil, false).code
|
170
184
|
end
|
185
|
+
raise "could not load task metadata for task "+@uri.to_s if @metadata==nil || @metadata.size==0
|
171
186
|
end
|
172
187
|
|
173
188
|
# create is private now, use OpenTox::Task.as_task
|
174
189
|
#def self.create( params )
|
175
|
-
#task_uri = RestClientWrapper.post(CONFIG[:services]["opentox-task"], params,
|
190
|
+
#task_uri = RestClientWrapper.post(CONFIG[:services]["opentox-task"], params, {}, false).to_s
|
176
191
|
#Task.find(task_uri.chomp)
|
177
192
|
#end
|
178
193
|
|
@@ -217,8 +232,11 @@ module OpenTox
|
|
217
232
|
=end
|
218
233
|
|
219
234
|
# waits for a task, unless time exceeds or state is no longer running
|
220
|
-
|
235
|
+
# @param [optional,OpenTox::Task] waiting_task (can be a OpenTox::Subtask as well), progress is updated accordingly
|
236
|
+
# @param [optional,Numeric] dur seconds pausing before cheking again for completion
|
237
|
+
def wait_for_completion( waiting_task=nil, dur=0.3)
|
221
238
|
|
239
|
+
waiting_task.waiting_for(self.uri) if waiting_task
|
222
240
|
due_to_time = Time.new + DEFAULT_TASK_MAX_DURATION
|
223
241
|
LOGGER.debug "start waiting for task "+@uri.to_s+" at: "+Time.new.to_s+", waiting at least until "+due_to_time.to_s
|
224
242
|
|
@@ -227,21 +245,42 @@ module OpenTox
|
|
227
245
|
while self.running?
|
228
246
|
sleep dur
|
229
247
|
load_metadata
|
248
|
+
# if another (sub)task is waiting for self, set progress accordingly
|
249
|
+
waiting_task.progress(@metadata[OT.percentageCompleted].to_f) if waiting_task
|
230
250
|
check_state
|
231
251
|
if (Time.new > due_to_time)
|
232
252
|
raise "max wait time exceeded ("+DEFAULT_TASK_MAX_DURATION.to_s+"sec), task: '"+@uri.to_s+"'"
|
233
253
|
end
|
234
254
|
end
|
235
|
-
|
236
|
-
LOGGER.debug "Task '"+@metadata[OT.hasStatus]+"': "+@uri.to_s+", Result: "+@metadata[OT.resultURI].to_s
|
255
|
+
waiting_task.waiting_for(nil) if waiting_task
|
256
|
+
LOGGER.debug "Task '"+@metadata[OT.hasStatus].to_s+"': "+@uri.to_s+", Result: "+@metadata[OT.resultURI].to_s
|
237
257
|
end
|
238
|
-
|
258
|
+
|
259
|
+
# updates percentageCompleted value (can only be increased)
|
260
|
+
# task has to be running
|
261
|
+
# @param [Numeric] pct value between 0 and 100
|
262
|
+
def progress(pct)
|
263
|
+
#puts "task := "+pct.to_s
|
264
|
+
raise "no numeric >= 0 and <= 100 : '"+pct.to_s+"'" unless pct.is_a?(Numeric) and pct>=0 and pct<=100
|
265
|
+
if (pct > @metadata[OT.percentageCompleted] + 0.0001)
|
266
|
+
RestClientWrapper.put(File.join(@uri,'Running'),{:percentageCompleted => pct})
|
267
|
+
load_metadata
|
268
|
+
end
|
269
|
+
end
|
270
|
+
|
271
|
+
def waiting_for(task_uri)
|
272
|
+
RestClientWrapper.put(File.join(@uri,'Running'),{:waiting_for => task_uri})
|
273
|
+
end
|
274
|
+
|
239
275
|
private
|
276
|
+
VALID_TASK_STATES = ["Cancelled", "Completed", "Running", "Error"]
|
277
|
+
|
240
278
|
def check_state
|
241
279
|
begin
|
280
|
+
raise "illegal task state, invalid status: '"+@metadata[OT.hasStatus].to_s+"'" unless
|
281
|
+
@metadata[OT.hasStatus] unless VALID_TASK_STATES.include?(@metadata[OT.hasStatus])
|
242
282
|
raise "illegal task state, task is completed, resultURI is no URI: '"+@metadata[OT.resultURI].to_s+
|
243
283
|
"'" unless @metadata[OT.resultURI] and @metadata[OT.resultURI].to_s.uri? if completed?
|
244
|
-
|
245
284
|
if @http_code == 202
|
246
285
|
raise "#{@uri}: illegal task state, code is 202, but hasStatus is not Running: '"+@metadata[OT.hasStatus]+"'" unless running?
|
247
286
|
elsif @http_code == 201
|
@@ -250,10 +289,108 @@ module OpenTox
|
|
250
289
|
"'" unless @metadata[OT.resultURI] and @metadata[OT.resultURI].to_s.uri?
|
251
290
|
end
|
252
291
|
rescue => ex
|
253
|
-
|
292
|
+
raise OpenTox::BadRequestError.new ex.message+" (task-uri:"+@uri+")"
|
254
293
|
end
|
255
294
|
end
|
295
|
+
end
|
296
|
+
|
297
|
+
# Convenience class to split a (sub)task into subtasks
|
298
|
+
#
|
299
|
+
# example:
|
300
|
+
# a crossvalidation is split into creating datasets and performing the validations
|
301
|
+
# creating the dataset is 1/3 of the work, perform the validations is 2/3:
|
302
|
+
# Task.as_task do |task|
|
303
|
+
# create_datasets( SubTask.new(task, 0, 33) )
|
304
|
+
# perfom_validations( SubTask.new(task, 33, 100) )
|
305
|
+
# end
|
306
|
+
# inside the create_datasets / perform_validations you can use subtask.progress(<val>)
|
307
|
+
# with vals from 0-100
|
308
|
+
#
|
309
|
+
# note that you can split a subtask into further subtasks
|
310
|
+
class SubTask
|
311
|
+
|
312
|
+
def initialize(task, min, max)
|
313
|
+
raise "not a task or subtask" if task!=nil and !(task.is_a?(Task) or task.is_a?(SubTask))
|
314
|
+
raise "invalid max ("+max.to_s+"), min ("+min.to_s+") params" unless
|
315
|
+
min.is_a?(Numeric) and max.is_a?(Numeric) and min >= 0 and max <= 100 and max > min
|
316
|
+
@task = task
|
317
|
+
@min = min
|
318
|
+
@max = max
|
319
|
+
@delta = max - min
|
320
|
+
end
|
321
|
+
|
322
|
+
# convenience method to handle null tasks
|
323
|
+
def self.create(task, min, max)
|
324
|
+
if task
|
325
|
+
SubTask.new(task, min, max)
|
326
|
+
else
|
327
|
+
nil
|
328
|
+
end
|
329
|
+
end
|
330
|
+
|
331
|
+
def waiting_for(task_uri)
|
332
|
+
@task.waiting_for(task_uri)
|
333
|
+
end
|
334
|
+
|
335
|
+
def progress(pct)
|
336
|
+
raise "no numeric >= 0 and <= 100 : '"+pct.to_s+"'" unless pct.is_a?(Numeric) and pct>=0 and pct<=100
|
337
|
+
#puts "subtask := "+pct.to_s+" -> task := "+(@min + @delta * pct.to_f * 0.01).to_s
|
338
|
+
@task.progress( @min + @delta * pct.to_f * 0.01 )
|
339
|
+
end
|
340
|
+
|
341
|
+
def running?()
|
342
|
+
@task.running?
|
343
|
+
end
|
344
|
+
end
|
256
345
|
|
346
|
+
|
347
|
+
# The David Gallagher feature:
|
348
|
+
# a fake sub task to keep the progress bar movin for external jobs
|
349
|
+
# note: param could be a subtask
|
350
|
+
#
|
351
|
+
# usage (for a call that is normally finished in under 60 seconds):
|
352
|
+
# fsk = FakeSubTask.new(task, 60)
|
353
|
+
# external_lib_call.start
|
354
|
+
# external_lib_call.wait_until_finished
|
355
|
+
# fsk.finished
|
356
|
+
#
|
357
|
+
# what happens:
|
358
|
+
# the FakeSubTask updates the task.progress each second until
|
359
|
+
# runtime is up or the finished mehtod is called
|
360
|
+
#
|
361
|
+
# example if the param runtime is too low:
|
362
|
+
# 25% .. 50% .. 75% .. 100% .. 100% .. 100% .. 100% .. 100%
|
363
|
+
# example if the param runtime is too high:
|
364
|
+
# 5% .. 10% .. 15% .. 20% .. 25% .. 30% .. 35% .. 100%
|
365
|
+
# the latter example is better (keep the bar movin!)
|
366
|
+
# -> better make a conservative runtime estimate
|
367
|
+
class FakeSubTask
|
368
|
+
|
369
|
+
def initialize(task, runtime)
|
370
|
+
@task = task
|
371
|
+
@thread = Thread.new do
|
372
|
+
timeleft = runtime
|
373
|
+
while (timeleft > 0 and @task.running?)
|
374
|
+
sleep 1
|
375
|
+
timeleft -= 1
|
376
|
+
@task.progress( (runtime - timeleft) / runtime.to_f * 100 )
|
377
|
+
end
|
378
|
+
end
|
379
|
+
end
|
380
|
+
|
381
|
+
# convenience method to handle null tasks
|
382
|
+
def self.create(task, runtime)
|
383
|
+
if task
|
384
|
+
FakeSubTask.new(task, runtime)
|
385
|
+
else
|
386
|
+
nil
|
387
|
+
end
|
388
|
+
end
|
389
|
+
|
390
|
+
def finished
|
391
|
+
@thread.exit
|
392
|
+
@task.progress(100) if @task.running?
|
393
|
+
end
|
257
394
|
end
|
258
395
|
|
259
396
|
end
|