opentox-ruby 0.0.2 → 1.0.0
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.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
|