argos-ruby 1.0.5 → 1.1.0

Sign up to get free protection for your applications and to get access to all the features.
data/lib/argos/diag.rb CHANGED
@@ -5,15 +5,12 @@ module Argos
5
5
  # Usage
6
6
  #
7
7
  # diag = Argos::Diag.new
8
- # diag.log = Logger.new(STDERR)
9
8
  # puts diag.parse(filename).to_json
10
9
  #
11
- # For information about Argos, see: http://www.argos-system.org
12
- #
13
10
  # @author Espen Egeland
14
11
  # @author Conrad Helgeland
15
12
  class Diag < Array
16
- include Argos
13
+ include Argos::Ascii
17
14
 
18
15
  LOCATION_CLASS = ["3", "2", "1", "0", "A", "B", "Z"]
19
16
 
@@ -56,6 +53,7 @@ module Argos
56
53
  def initialize
57
54
  @errors = []
58
55
  @programs = []
56
+ @log = Logger.new(STDERR)
59
57
  end
60
58
 
61
59
  def filter?
@@ -235,12 +233,12 @@ module Argos
235
233
  sensor_data: sensor_data,
236
234
  technology: "argos",
237
235
  type: type,
238
- location: "file://"+filename,
236
+ file: "file://"+filename,
239
237
  source: "#{sha1}",
240
238
  }
241
239
 
242
240
  idbase = diag.clone
243
- idbase.delete :location
241
+ idbase.delete :file
244
242
  id = Digest::SHA1.hexdigest(idbase.to_json)
245
243
 
246
244
  diag[:parser] = Argos.library_version
@@ -328,8 +326,6 @@ end
328
326
  # 168 64 30
329
327
 
330
328
 
331
-
332
-
333
329
  #200910Oct.DIA
334
330
  #: Line: 26132 Invalid format:
335
331
  #52061 Date : 03.10.09 09:47:05 LC : Z IQ : 38 Lat1 : 76.998N Lon1 : 22.398W Lat2 : 74.370N Lon2 : 10.000 Nb mes : 003 Nb mes>-120dB : 000 Best level : -129 dB Pass duration : 100s NOPC : 0 Calcul freq : 401 677501.2 Hz Altitude : 0 m 40 35 00 00 00 00 00 240 00 00 00 00 64 00 197
data/lib/argos/ds.rb CHANGED
@@ -5,17 +5,13 @@ module Argos
5
5
  # Usage
6
6
  #
7
7
  # ds = Argos::Ds.new
8
- # ds.log = Logger.new(STDERR)
9
8
  # puts ds.parse(filename).to_json
10
9
  #
11
- #
12
- # For information about Argos, see: http://www.argos-system.org
13
- #
14
10
  # @author Espen Egeland
15
11
  # @author Conrad Helgeland
16
- # @todo errors => warn or remove unless asked for? (debug)
12
+ #
17
13
  class Ds < Array
18
- include Argos
14
+ include Argos::Ascii
19
15
 
20
16
  attr_writer :log, :filename, :bundle
21
17
 
@@ -29,6 +25,7 @@ module Argos
29
25
 
30
26
  def initialize
31
27
  @errors = []
28
+ @log = Logger.new(STDERR)
32
29
  end
33
30
 
34
31
  def filter?
@@ -312,7 +309,7 @@ module Argos
312
309
  m = m.merge(measurement)
313
310
  m = m.merge ({ technology: "argos",
314
311
  type: type,
315
- location: "file://"+filename,
312
+ file: "file://"+filename,
316
313
  source: sha1
317
314
  })
318
315
 
@@ -354,7 +351,7 @@ module Argos
354
351
 
355
352
  idbase = m.clone
356
353
  idbase.delete :errors
357
- idbase.delete :location
354
+ idbase.delete :file
358
355
  idbase.delete :warn
359
356
 
360
357
  id = Digest::SHA1.hexdigest(idbase.to_json)
@@ -1,4 +1,8 @@
1
1
  module Argos
2
2
  class Exception < ::Exception
3
3
  end
4
+ class SoapException < Exception
5
+ end
6
+ class SoapFault < SoapException
7
+ end
4
8
  end
data/lib/argos/soap.rb ADDED
@@ -0,0 +1,372 @@
1
+ root = File.dirname(File.realpath(__FILE__))+"/../.."
2
+
3
+ Dir.chdir root do
4
+ begin
5
+ require "savon"
6
+ rescue LoadError
7
+ # Prepend Savon lib to path, bundler seems not work
8
+ unless defined? Savon
9
+ # Cannot use #find_name returns []
10
+ # raise Bundler.rubygems.find_name('savon').to_json #.first.full_gem_path
11
+ savonbundle = `bundle show savon`.chomp
12
+ $LOAD_PATH.unshift(savonbundle+"/lib")
13
+ require_relative "#{savonbundle}/lib/savon"
14
+ end
15
+ end
16
+ end
17
+
18
+ module Argos
19
+ class Soap
20
+
21
+ # A "simple" Soap client for Argos-system satellite tracking webservice
22
+ # http://wanderingbarque.com/nonintersecting/2006/11/15/the-s-stands-for-simple/
23
+
24
+ # client [Savon]
25
+ # request [String] Soap:Envelope (XML request body)
26
+ # response [Savon::Response]
27
+ # operation [Savon::Operation]
28
+ # xml [String] (Extracted, inner) XML
29
+ attr_accessor :client, :request, :response, :operation, :xml, :filter,
30
+ :platformId, :programNumber, :nbDaysFromNow, :period
31
+
32
+ attr_writer :username, :password
33
+
34
+ URI = "http://ws-argos.cls.fr/argosDws/services/DixService"
35
+ # Alternative: "http://ws-argos.clsamerica.com/argosDws/services/DixService"
36
+
37
+ WSDL = "#{URI}?wsdl"
38
+
39
+ ARGOS_NS = "http://service.dataxmldistribution.argos.cls.fr/types"
40
+ SOAP_NS = "http://www.w3.org/2003/05/soap-envelope"
41
+
42
+ NAMESPACES = {
43
+ "soap" => SOAP_NS,
44
+ "argos" => ARGOS_NS
45
+ }
46
+
47
+ # Constructor
48
+ # soap = Argos::Soap.new({username: "argos-system-user", password: "argos-system-pw"})
49
+ def initialize(config={})
50
+ config.each do |k,v|
51
+ case k.to_sym
52
+ when :username
53
+ @username=v
54
+ when :password
55
+ @password=v
56
+ when :wsdl
57
+ @wsdl=v
58
+ when :programNumber
59
+ @programNumber = v
60
+ when :platformId
61
+ @platformId = v
62
+ when :nbDaysFromNow
63
+ @nbDaysFromNow = v.to_i
64
+ when :period
65
+ @period = v
66
+ when :filter
67
+ @filter = v
68
+ else
69
+ #raise ArgumentError, "Unkown config key: #{k}"
70
+ end
71
+ end
72
+ end
73
+
74
+ # Build baseRequest Hash
75
+ # The service requires programNumber or PlatformId, but if you do not provide any,
76
+ # this method will call the service (@see #programs) and get the current user's programs
77
+ # @return [Hash]
78
+ def baseRequest
79
+ # if override key is platformId... delete programNumber...
80
+ # if override key is period... delete nbDaysFromNow...
81
+ baseRequest = { username: _username, password: _password }
82
+
83
+ # First choice (program or platform)
84
+ if @programNumber.nil? and @platformId.nil?
85
+ # Fetch all programs if neither is provided
86
+ baseRequest[:programNumber] = programs.map {|p|p.to_s}.join(",")
87
+ elsif @programNumber =~ /\d+/ and @platformId =~ /\d+/
88
+ raise "Cannot provide both programNumber and platformId"
89
+ elsif @programNumber =~ /\d+/
90
+ baseRequest[:programNumber] = @programNumber
91
+ elsif @platformId =~ /\d+/
92
+ baseRequest[:platformId] = @platformId
93
+ end
94
+
95
+ # 2nd choice (time)
96
+ if @nbDaysFromNow.nil? and @period.nil?
97
+ # Default to 20 days of data (the maximum)
98
+ baseRequest[:nbDaysFromNow] = 20
99
+ elsif @nbDaysFromNow =~ /\d+/ and not @period.nil?
100
+ raise "Cannot provide both nbDaysFromNow and period"
101
+ elsif @nbDaysFromNow.to_s =~ /\d+/
102
+ baseRequest[:nbDaysFromNow] = @nbDaysFromNow.to_i
103
+ else
104
+ baseRequest[:period] = @period
105
+ end
106
+
107
+ baseRequest = baseRequest.merge({
108
+ #<xs:element minOccurs="0" name="referenceDate" type="tns:referenceDateType"/>
109
+ #<xs:element minOccurs="0" name="locClass" type="xs:string"/>
110
+ #<xs:element minOccurs="0" name="geographicArea" type="xs:string"/>
111
+ #<xs:element minOccurs="0" name="compression" type="xs:int"/>
112
+ #<xs:element minOccurs="0" name="mostRecentPassages" type="xs:boolean"/>
113
+ })
114
+ baseRequest
115
+
116
+ end
117
+
118
+ # @return [Savon]
119
+ def client
120
+ @client ||= begin
121
+
122
+ tmpwsdl = "/tmp/argos-soap.wsdl"
123
+ if File.exists?(tmpwsdl)
124
+ uri = tmpwsdl
125
+ else
126
+ uri = @wsdl||=WSDL
127
+ end
128
+
129
+ client = ::Savon.new(uri)
130
+ if not File.exists?(tmpwsdl)
131
+ response = ::Net::HTTP.get_response(::URI.parse(WSDL))
132
+ File.open(tmpwsdl, "wb") { |file| file.write(response.body)}
133
+ end
134
+ client
135
+ end
136
+ end
137
+
138
+ def filter?
139
+ not @filter.nil? and filter.respond_to?(:call)
140
+ end
141
+
142
+ # @return [String]
143
+ def getCsv
144
+ o = _operation(:getCsv)
145
+ o.body = { csvRequest: baseRequest.merge(
146
+ showHeader: true).merge(xmlRequest)
147
+ }
148
+ @response = o.call
149
+ @request = o.build
150
+ @text = _extract_escaped_xml("csvResponse").call(response)
151
+ end
152
+
153
+ # @return [Hash]
154
+ def getKml
155
+ _call_xml_operation(:getKml, { kmlRequest: baseRequest.merge(xmlRequest)}, _extract_escaped_xml("kmlResponse"))
156
+ end
157
+
158
+ # @return [Hash]
159
+ # {"data":{"program":[{"programNumber":"9660","platform":[{ .. },{ .. }]}],"@version":"1.0"}}
160
+ # Each platform Hash (.. above): {"platformId":"129990","lastLocationClass":"3","lastCollectDate":"2013-10-03T08:32:24.000Z","lastLocationDate":"2013-05-22T04:55:15.000Z","lastLatitude":"47.67801","lastLongitude":"-122.13419"}
161
+ def getPlatformList
162
+ _call_xml_operation(:getPlatformList, { platformListRequest:
163
+ # Cannot use #baseRequest here because that methods calls #programs which also calls #getPlatformList...
164
+ { username: _username, password: _password },
165
+ }, _extract_escaped_xml("platformListResponse"))
166
+ end
167
+
168
+ # @return [Hash]
169
+ def getXml
170
+ _call_xml_operation(:getXml,
171
+ { xmlRequest: baseRequest.merge(xmlRequest)},
172
+ _extract_escaped_xml("xmlResponse"))
173
+ end
174
+
175
+ # @return [Hash]
176
+ def getStreamXml
177
+ _call_xml_operation(:getStreamXml,
178
+ { streamXmlRequest: baseRequest.merge(xmlRequest)},
179
+ _extract_motm)
180
+ end
181
+
182
+ # @return [Hash]
183
+ def getXsd
184
+ _call_xml_operation(:getXsd, { xsdRequest: {} }, _extract_escaped_xml("xsdResponse"))
185
+ end
186
+
187
+ # @return [Hash]
188
+ # choice: programNumber | platformId | wmo*
189
+ # nbMaxObs
190
+ def getObsXml
191
+ _call_xml_operation(:getObsXml,
192
+ { observationRequest: baseRequest.merge(xmlRequest)},
193
+ _extract_escaped_xml("observationResponse"))
194
+ end
195
+
196
+ # @return [Text]
197
+ # choice: programNumber | platformId | wmo*
198
+ # nbMaxObs
199
+ def getObsCsv
200
+ o = _operation(:getObsCsv)
201
+ o.body = { observationRequest: baseRequest.merge(xmlRequest)
202
+ }
203
+ @response = o.call
204
+ @request = o.build
205
+ @text = _extract_escaped_xml("observationResponse").call(response)
206
+ end
207
+
208
+ # @return [Array]
209
+ def platforms(programNumber=nil)
210
+ platforms = []
211
+ programs = getPlatformList["data"]["program"]
212
+ if programNumber.to_s =~ /\d+/
213
+ programs.select! {|p| p["programNumber"].to_i == programNumber.to_i }
214
+ end
215
+ programs.each do |program|
216
+ platforms += program["platform"].map {|p| p["platformId"].to_i}
217
+ end
218
+ platforms
219
+ end
220
+
221
+ def period(startDate, endDate)
222
+ { startDate: startDate, endDate: endDate }
223
+ end
224
+
225
+ # @return [Array]
226
+ def programs
227
+ getPlatformList["data"]["program"].map {|p| p["programNumber"].to_i }
228
+ end
229
+
230
+ # @return [String]
231
+ def raw
232
+ response.raw
233
+ end
234
+
235
+ # @return [String]
236
+ def request
237
+ operation.build
238
+ end
239
+
240
+ # @return [Hash] {"DixService":{"ports":{"DixServicePort":{"type":"http://schemas.xmlsoap.org/wsdl/soap12/","location":"http://ws-argos.cls.fr/argosDws/services/DixService"}}}}
241
+ def services
242
+ client.services
243
+ end
244
+
245
+ # @return [String]
246
+ def text
247
+ @text||=""
248
+ end
249
+
250
+ # @return [Array] [:getCsv, :getStreamXml, :getKml, :getXml, :getXsd, :getPlatformList, :getObsCsv, :getObsXml]
251
+ def operations
252
+ @response = client.operations(:DixService, :DixServicePort)
253
+ end
254
+
255
+ def namespaces
256
+ NAMESPACES
257
+ end
258
+
259
+ def xmlRequest
260
+ {
261
+ displayLocation: true,
262
+ displayDiagnostic: true,
263
+ displayMessage: true,
264
+ displayCollect: true,
265
+ displayRawData: true,
266
+ displaySensor: true,
267
+ #argDistrib: "",
268
+ displayImageLocation: true,
269
+ displayHexId: true
270
+ }
271
+ end
272
+
273
+ protected
274
+
275
+ # Build and call @operation, set @response, @request, and @xml
276
+ # @raise on faults
277
+ # @return [Hash]
278
+ def _call_xml_operation(op_sym, body, extract=nil)
279
+ @operation = _operation(op_sym)
280
+ @operation.body = body
281
+ @response = operation.call
282
+
283
+ # Check for http errors?
284
+
285
+ # Handle faults (before extracting data)
286
+ _envelope.xpath("soap:Body/soap:Fault", namespaces).each do | fault |
287
+ raise fault.to_s
288
+ end
289
+
290
+ # Extract data
291
+ if extract.respond_to?(:call)
292
+ @xml = extract.call(response)
293
+ else
294
+ @xml = response.raw
295
+ end
296
+
297
+ # Handle errors
298
+ ng = Nokogiri.XML(xml)
299
+ ng.xpath("/data/errors/error").each do | error |
300
+ raise error.to_s
301
+ # FIXME Custom exception so that it mighht be trapped on raw responses...
302
+ end
303
+
304
+ # Validation
305
+ # Validation against XSD does not work: ["Element 'data': No matching global declaration available for the validation root."]
306
+ # schema = Nokogiri::XML::Schema(File.read("/tmp/argos-data.xsd"))
307
+ # v = schema.validate(ng)
308
+
309
+ # Convert XML to Hash
310
+ nori = Nori.new
311
+ nori.parse(xml)
312
+ end
313
+
314
+ # This is a shame, but who's to blame when there's multiple XML prologs (the second is escaped) and even mix of (declared) encodings (UTF-8 in soap envelope, ISO-8859-1 inside)
315
+ # Note: the inner data elements are non-namespaced (see da), so that recreating as proper XML would need to set xmlns=""
316
+ def _extract_escaped_xml(responseElement)
317
+ lambda {|response| CGI.unescapeHTML(response.raw.split("<#{responseElement} xmlns=\"http://service.dataxmldistribution.argos.cls.fr/types\"><return>")[1].split("</return>")[0])}
318
+ end
319
+
320
+ # This is proof-of-concept quality code.
321
+ # @todo Need to extract boundary and start markers from Content-Type header:
322
+ # Content-Type: multipart/related; type="application/xop+xml"; boundary="uuid:14b8db9f-a393-4786-be3f-f0f7b12e14a2"; start="<root.message@cxf.apache.org>"; start-info="application/soap+xml"
323
+
324
+ # @return [String]
325
+
326
+ def _extract_motm
327
+ lambda {|response|
328
+ # Scan for MOTM signature --uuid:*
329
+ if response.raw =~ (/^(--[\w:-]+)--$/)
330
+ # Get the last message, which is -2 because of the trailing --
331
+ xml = response.raw.split($1)[-2].strip
332
+
333
+ # Get rid of HTTP headers
334
+ if xml =~ /\r\n\r\n[<]/
335
+ xml = xml.split(/\r\n\r\n/)[-1]
336
+ end
337
+
338
+ else
339
+ raise "Cannot parse MOTM"
340
+ end
341
+ }
342
+ end
343
+
344
+ # @return [Nokogiri:*]
345
+ def _envelope
346
+ ng = Nokogiri.XML(response.raw).xpath("/soap:Envelope", namespaces)
347
+ if not ng.any?
348
+ # Again, this is a shame...
349
+ envstr = '<soap:Envelope xmlns:soap="http://www.w3.org/2003/05/soap-envelope">'
350
+ extracted = envstr + response.raw.split(envstr)[1].split("</soap:Envelope>")[0] + "</soap:Envelope>"
351
+ ng = Nokogiri.XML(extracted).xpath("/soap:Envelope", namespaces)
352
+ end
353
+ ng
354
+ end
355
+
356
+ # @return [Savon::Operation]
357
+ def _operation(operation_name)
358
+ client.operation(:DixService, :DixServicePort, operation_name)
359
+ end
360
+
361
+ # @return [String]
362
+ def _username
363
+ @username||=ENV["ARGOS_SOAP_USERNAME"]
364
+ end
365
+
366
+ # @return [String]
367
+ def _password
368
+ @password||=ENV["ARGOS_SOAP_PASSWORD"]
369
+ end
370
+
371
+ end
372
+ end