granicus-platform-api 0.1.9 → 0.9.1

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/.gitignore CHANGED
@@ -1,2 +1,5 @@
1
1
  coverage
2
2
  *.gem
3
+ Gemfile.lock
4
+ .idea
5
+
@@ -30,7 +30,7 @@ This wrapper is developed against 1.9.2.
30
30
 
31
31
  = License
32
32
 
33
- Copyright (c) 2011 Javier Muniz
33
+ Copyright (c) 2011 Granicus, Inc.
34
34
 
35
35
  Permission is hereby granted, free of charge, to any person obtaining
36
36
  a copy of this software and associated documentation files (the
@@ -56,4 +56,4 @@ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
56
56
 
57
57
  = Credits
58
58
 
59
- Javier Muniz
59
+ Javier Muniz, Ryan Wold
@@ -1,467 +1,504 @@
1
- require 'savon'
2
-
3
- module GranicusPlatformAPI
4
- class Client
5
-
6
- # mappings between soap types and our complex types
7
- # this area should be rewritten to auto-generate data sets properly
8
- # and refactored to separate standard xsd types from custom types in class
9
- # map
10
- def self.typegenerators
11
- @@typegenerators
12
- end
13
-
14
- def self.typegenerators=(obj)
15
- @@typegenerators = obj
16
- end
17
-
18
- self.typegenerators = {}
19
-
20
- self.typegenerators["AgendaItem"] = lambda { AgendaItem.new }
21
- self.typegenerators["Attendee"] = lambda { Attendee.new }
22
- self.typegenerators["AttendeeStatus"] = lambda { AttendeeStatus.new }
23
- self.typegenerators["CameraData"] = lambda { CameraData.new }
24
- self.typegenerators["CaptionData"] = lambda { CaptionData.new }
25
- self.typegenerators["ClipData"] = lambda { ClipData.new }
26
- self.typegenerators["Document"] = lambda { Document.new }
27
- self.typegenerators["EventData"] = lambda { EventData.new }
28
- self.typegenerators["FolderData"] = lambda { FolderData.new }
29
- self.typegenerators["GroupData"] = lambda { GroupData.new }
30
- self.typegenerators["KeyMapping"] = lambda { KeyMapping.new }
31
- self.typegenerators["MetaDataData"] = lambda { MetaDataData.new }
32
- self.typegenerators["Motion"] = lambda { Motion.new }
33
- self.typegenerators["Note"] = lambda { Note.new }
34
- self.typegenerators["Rollcall"] = lambda { Rollcall.new }
35
- self.typegenerators["ServerData"] = lambda { ServerData.new }
36
- self.typegenerators["ServerInterfaceData"] = lambda { ServerInterfaceData.new }
37
- self.typegenerators["TemplateData"] = lambda { TemplateData.new }
38
- self.typegenerators["ViewData"] = lambda { ViewData.new }
39
- self.typegenerators["VoteEntry"] = lambda { VoteEntry.new }
40
- self.typegenerators["VoteRecord"] = lambda { VoteRecord.new }
41
-
42
- # classmap for generating proper attributes! hash within savon calls
43
- def self.classmap
44
- @@classmap
45
- end
46
-
47
- def self.classmap=(obj)
48
- @@classmap = obj
49
- end
50
-
51
- # built-in types
52
- self.classmap = {}
53
- self.classmap['Fixnum'] = "xsd:int"
54
- self.classmap['String'] = "xsd:string"
55
- self.classmap['TrueClass'] = 'xsd:boolean'
56
- self.classmap['FalseClass'] = 'xsd:boolean'
57
- self.classmap['Time'] = 'xsd:dateTime'
58
-
59
- # start custom types
60
- self.classmap['AgendaItem'] = 'granicus:AgendaItem'
61
- self.classmap['Attendee'] = 'granicus:Attendee'
62
- self.classmap['AttendeeStatus'] = 'granicus:AttendeeStatus'
63
- self.classmap['CameraData'] = 'granicus:CameraData'
64
- self.classmap['CaptionData'] = 'granicus:CaptionData'
65
- self.classmap['ClipData'] = 'granicus:ClipData'
66
- self.classmap['Document'] = 'granicus:Document'
67
- self.classmap['EventData'] = 'granicus:EventData'
68
- self.classmap['FolderData'] = 'granicus:FolderData'
69
- self.classmap['GroupData'] = 'granicus:GroupData'
70
- self.classmap['KeyMapping'] = 'granicus:KeyMapping'
71
- self.classmap['MetaDataData'] = 'granicus:MetaDataData'
72
- self.classmap['Motion'] = 'granicus:Motion'
73
- self.classmap['Note'] = 'granicus:Note'
74
- self.classmap['Rollcall'] = 'granicus:Rollcall'
75
- self.classmap['ServerData'] = 'granicus:ServerData'
76
- self.classmap['ServerInterfaceData'] = 'granicus:ServerInterfaceData'
77
- self.classmap['TemplateData'] = 'granicus:TemplateData'
78
- self.classmap['ViewData'] = 'granicus:ViewData'
79
- self.classmap['VoteEntry'] = 'granicus:VoteEntry'
80
- self.classmap['VoteRecord'] = 'granicus:VoteRecord'
81
-
82
- # create a client
83
- def initialize(granicus_site=nil,username=nil,password=nil,options={})
84
- # setup our private members
85
- @options = options
86
- @impersonation_token = nil
87
- @connected = false
88
-
89
- # configure savon
90
- Savon.configure do |config|
91
- config.log = false
92
- end
93
- HTTPI.log = false
94
-
95
- # connect if we have a site and credentials
96
- unless granicus_site.nil?
97
- self.site = granicus_site
98
- end
99
-
100
- unless username.nil? or password.nil?
101
- login(username,password)
102
- end
103
- end
104
-
105
- # options
106
- def options
107
- @options
108
- end
109
- def options=(value)
110
- @options = value
111
- end
112
-
113
- # connect up to a site
114
- def connect(granicus_site,username,password,options={})
115
- logout if @connected
116
-
117
- # create the client
118
- self.site = granicus_site
119
-
120
- # call login
121
- login username, password
122
- end
123
-
124
- # site property
125
- def site
126
- return @granicus_site
127
- end
128
-
129
- def site=(value)
130
- @granicus_site = value
131
- @client = Savon::Client.new do |wsdl, http|
132
- wsdl.document = File.expand_path("../granicus-platform-api.xml", __FILE__)
133
- wsdl.endpoint = "http://#{value}/SDK/User/index.php"
134
- http.proxy = @options[:proxy] if not @options[:proxy].nil?
135
- end
136
- end
137
-
138
- # impersonate a user
139
- def impersonate(token)
140
- @impersonation_token = token
141
- @client.http.headers["Cookie"] = "SESS1=#{token}; path=/"
142
- end
143
-
144
- def impersonation_token
145
- @impersonation_token
146
- end
147
-
148
- # login
149
- def login(username,password)
150
- logout if @connected
151
- call_soap_method(:login,'//ns4:LoginResponse/return',{'Username' => username, 'Password' => password})
152
- @impersonation_token = @response.http.headers['Set-Cookie'].gsub(/SESS1=(.*); path=\//,'\\1')
153
- @connected = true
154
- end
155
-
156
- # return the current logged on user name
157
- def get_current_user_logon
158
- call_soap_method(:get_current_user_logon,'//ns4:GetCurrentUserLogonResponse/Logon')
159
- end
160
-
161
- # logout
162
- def logout
163
- call_soap_method(:logout,'//ns4:LogoutResponse')
164
- @connected = false
165
- end
166
-
167
- # return all of the cameras
168
- def get_cameras
169
- call_soap_method(:get_cameras,'//ns5:GetCamerasResponse/cameras')
170
- end
171
-
172
- # create a camera
173
- def create_camera(camera)
174
- call_soap_method(:create_camera,'//ns4:CreateCameraResponse/CameraID',{ 'CameraData' => camera })
175
- end
176
-
177
- # return the requested camera
178
- def get_camera(camera_id)
179
- call_soap_method(:get_camera,'//ns5:GetCameraResponse/camera',{ 'CameraID' => camera_id })
180
- end
181
-
182
- # update a camera
183
- def update_camera(camera)
184
- call_soap_method(:update_camera,'//ns4:UpdateCameraResponse',{ 'camera' => camera })
185
- end
186
-
187
- # delete the requested camera
188
- def delete_camera(camera_id)
189
- call_soap_method(:delete_camera,'//ns4:DeleteCameraResponse',{ 'CameraID' => camera_id})
190
- end
191
-
192
- # return all of the events
193
- def get_events
194
- call_soap_method(:get_events,'//ns5:GetEventsResponse/events')
195
- end
196
-
197
- # return all of the events with matching foreign id
198
- def get_events_by_foreign_id(foreign_id)
199
- call_soap_method(:get_events_by_foreign_id,'//ns5:GetEventsByForeignIDResponse/events',{ 'ForeignID' => foreign_id })
200
- end
201
-
202
- # create an event
203
- def create_event(event)
204
- call_soap_method(:create_event,'//ns4:CreateEventResponse/EventID',{ 'EventData' => event })
205
- end
206
-
207
- # return the requested event
208
- def get_event(event_id)
209
- call_soap_method(:get_event,'//ns5:GetEventResponse/event',{ 'EventID' => event_id })
210
- end
211
-
212
- # return the requested event by uid
213
- def get_event_by_uid(event_uid)
214
- call_soap_method(:get_event_by_uid,'//ns5:GetEventByUIDResponse/event',{ 'EventUID' => event_uid })
215
- end
216
-
217
- # update an event
218
- def update_event(event)
219
- call_soap_method(:update_event,'//ns4:UpdateEventResponse',{ 'event' => event })
220
- end
221
-
222
- # delete the requested event
223
- def delete_event(event_id)
224
- call_soap_method(:delete_event,'//ns4:DeleteEventResponse',{ 'EventID' => event_id})
225
- end
226
-
227
- # return all of the event meta data
228
- def get_event_meta_data(event_id)
229
- build_meta_tree call_soap_method(:get_event_meta_data,'//ns5:GetEventMetaDataResponse/metadata',{ 'EventID' => event_id })
230
- end
231
-
232
- # return all of the event meta data by UID
233
- def get_event_meta_data_by_uid(event_uid)
234
- build_meta_tree call_soap_method(:get_event_meta_data_by_uid,'//ns5:GetEventMetaDataByUIDResponse/metadata',{ 'EventUID' => event_uid })
235
- end
236
-
237
- # import metadata for an event
238
- def import_event_meta_data(event_id,meta_data,clear_existing=true,as_tree=true)
239
- call_soap_method(:import_event_meta_data,'//ns5:ImportEventMetaDataResponse/KeyTable',{
240
- 'EventID' => event_id,
241
- 'MetaData' => meta_data,
242
- 'ClearExisting' => clear_existing,
243
- 'AsTree' => as_tree})
244
- end
245
-
246
- # set the event agenda url
247
- def set_event_agenda_url(event_id,url)
248
- call_soap_method(:set_event_agenda_url,'//ns4:SetEventAgendaURLResponse',{ 'EventID' => event_id, 'URL' => url })
249
- end
250
-
251
- # return all of the clip meta data
252
- def get_clip_meta_data(clip_id)
253
- build_meta_tree call_soap_method(:get_clip_meta_data,'//ns5:GetClipMetaDataResponse/metadata',{ 'ClipID' => clip_id })
254
- end
255
-
256
- # import metadata for a clip
257
- # ImportClipMetaData Method (ClipID, MetaData, ClearExisting, AsTree)
258
- def import_clip_meta_data(clip_id,meta_data,clear_existing=true,as_tree=true)
259
- call_soap_method(:import_clip_meta_data,'//ns5:ImportClipMetaDataResponse/KeyTable',{
260
- 'ClipID' => clip_id,
261
- 'MetaData' => meta_data,
262
- 'ClearExisting' => clear_existing,
263
- 'AsTree' => as_tree})
264
- end
265
-
266
- # get meta data by id
267
- def get_meta_data(meta_id)
268
- call_soap_method(:get_meta_data,'//ns5:GetMetaDataResponse/MetaData', { 'MetaDataID' => meta_id })
269
- end
270
-
271
- # update metadata
272
- def update_meta_data(meta_data)
273
- call_soap_method(:update_meta_data,'//ns4:UpdateMetaDataResponse', { 'MetaData' => meta_data })
274
- end
275
-
276
- # return all of the folders
277
- def get_folders
278
- call_soap_method(:get_folders,'//ns5:GetFoldersResponse/folders')
279
- end
280
-
281
- # return all of the clips
282
- def get_clips(folder_id)
283
- call_soap_method(:get_clips,'//ns5:GetClipsResponse/clips',{ 'FolderID' => folder_id })
284
- end
285
-
286
- # return all of the clips with matching foreign id
287
- def get_clips_by_foreign_id(foreign_id)
288
- call_soap_method(:get_clips_by_foreign_id,'//ns5:GetClipsByForeignIDResponse/clips',{ 'ForeignID' => foreign_id })
289
- end
290
-
291
- # return the requested clip
292
- def get_clip(clip_id)
293
- call_soap_method(:get_clip,'//ns5:GetClipResponse/clip',{ 'ClipID' => clip_id })
294
- end
295
-
296
- # update a clip
297
- def update_clip(clip)
298
- call_soap_method(:update_clip,'//ns4:UpdateClipResponse',{ 'clip' => clip })
299
- end
300
-
301
- # return the requested clip
302
- def get_clip_by_uid(clip_uid)
303
- call_soap_method(:get_clip_by_uid,'//ns5:GetClipByUIDResponse/clip',{ 'ClipUID' => clip_uid })
304
- end
305
-
306
- # get servers
307
- def get_servers
308
- call_soap_method(:get_servers,'//ns5:GetServersResponse/servers')
309
- end
310
-
311
- # return the requested server
312
- def get_server(server_id)
313
- call_soap_method(:get_server,'//ns5:GetServerResponse/server',{ 'ServerID' => server_id })
314
- end
315
-
316
- #private
317
-
318
- def call_soap_method(method,returnfilter,args={})
319
- debug = @options[:debug]
320
- @response = @client.request :wsdl, method do
321
- soap.namespaces['xmlns:granicus'] = "http://granicus.com/xsd"
322
- soap.namespaces['xmlns:SOAP-ENC'] = "http://schemas.xmlsoap.org/soap/encoding/"
323
- soap.body = prepare_hash args
324
- if debug then
325
- puts soap.body
326
- end
327
- end
328
-
329
- doc = Nokogiri::XML(@response.to_xml) do |config|
330
- config.noblanks
331
- end
332
- response = handle_response(doc.xpath(returnfilter, doc.root.namespaces)[0])
333
- if debug
334
- puts response
335
- end
336
- response
337
- end
338
-
339
- def prepare_hash(hash={})
340
- attributes = {}
341
- new_hash = {}
342
- hash.each do |key,value|
343
- case value.class.to_s
344
- when /GranicusPlatformAPI::/, 'Hash'
345
- new_hash[key] = prepare_hash value
346
- when 'Array'
347
- new_hash[key] = prepare_array value
348
- else
349
- new_hash[key] = value
350
- end
351
- attributes[key] = attribute_of value
352
- end
353
- new_hash.merge({ :attributes! => attributes })
354
- end
355
-
356
- def prepare_array(array)
357
- return { "item" => array } if array.count == 0
358
- new_array = []
359
- array.each do |item|
360
- case item.class.to_s
361
- when /GranicusPlatformAPI::/, 'Hash'
362
- new_array << prepare_hash(item)
363
- when 'Array'
364
- new_array << prepare_array(item)
365
- else
366
- new_array << item
367
- end
368
- end
369
- { "item" => new_array, :attributes! => { "item" => attribute_of(array[0]) } }
370
- end
371
-
372
- def attribute_of(value)
373
- case value.class.to_s
374
- when 'Array'
375
- return {"xsi:type" => 'SOAP-ENC:Array'} if value.count == 0
376
- xsd_type = self.class.classmap[value[0].class.to_s.split('::').last]
377
- if xsd_type.nil?
378
- puts "Couldn't get array xsd:type for #{value[0].class}"
379
- {"xsi:type" => 'SOAP-ENC:Array'}
380
- else
381
- {"xsi:type" => 'SOAP-ENC:Array', "SOAP-ENC:arrayType" => "#{xsd_type}[#{value.count}]"}
382
- end
383
- else
384
- xsd_type = self.class.classmap[value.class.to_s.split('::').last]
385
- if xsd_type.nil?
386
- puts "Couldn't get xsd:type for #{value.class}"
387
- nil
388
- else
389
- {"xsi:type" => xsd_type }
390
- end
391
- end
392
- end
393
-
394
- def handle_response(node)
395
- if node.is_a? Nokogiri::XML::NodeSet or node.is_a? Array then
396
- return node.map {|el| handle_response el }
397
- end
398
- return node.to_s unless node['type']
399
- typespace,type = node['type'].split(':')
400
- case typespace
401
- when 'xsd'
402
- proc = self.class.typecasts[type]
403
- unless proc.nil?
404
- proc.call(node.children[0].to_s)
405
- else
406
- puts "Unknown xsd:type: #{type}"
407
- node.children[0].to_s
408
- end
409
- when 'SOAP-ENC'
410
- if type == 'Array' then
411
- node.children.map {|element| handle_response element }
412
- else
413
- puts "Unknown SOAP-ENC:type: #{type}"
414
- node.to_s
415
- end
416
- else
417
- # we have a custom type, attempt to generate it. if that fails use a hash
418
- proc = self.class.typegenerators[type]
419
- value = {}
420
- unless proc.nil?
421
- value = proc.call
422
- else
423
- puts "Unknown custom type: #{type}"
424
- end
425
- node.children.each do |value_node|
426
- value[value_node.name] = handle_response value_node
427
- end
428
- value
429
- end
430
- end
431
-
432
- # translate metadata list returned by the get_event and get_clip meta data functions into a tree
433
- def build_meta_tree(list,parent_id=0)
434
- tree = []
435
- list.each do |item|
436
- if item.ParentID == parent_id
437
- item.Children = build_meta_tree list, item.ID
438
- tree << item
439
- end
440
- end
441
- tree
442
- end
443
-
444
- # typecasts ripped from rubiii/nori, adapted for xsd types
445
- def self.typecasts
446
- @@typecasts
447
- end
448
-
449
- def self.typecasts=(obj)
450
- @@typecasts = obj
451
- end
452
-
453
- self.typecasts = {}
454
- self.typecasts["int"] = lambda { |v| v.nil? ? nil : v.to_i }
455
- self.typecasts["boolean"] = lambda { |v| v.nil? ? nil : (v.strip != "false") }
456
- self.typecasts["datetime"] = lambda { |v| v.nil? ? nil : Time.parse(v).utc }
457
- self.typecasts["date"] = lambda { |v| v.nil? ? nil : Date.parse(v) }
458
- self.typecasts["dateTime"] = lambda { |v| v.nil? ? nil : Time.parse(v).utc }
459
- self.typecasts["decimal"] = lambda { |v| v.nil? ? nil : BigDecimal(v.to_s) }
460
- self.typecasts["double"] = lambda { |v| v.nil? ? nil : v.to_f }
461
- self.typecasts["float"] = lambda { |v| v.nil? ? nil : v.to_f }
462
- self.typecasts["symbol"] = lambda { |v| v.nil? ? nil : v.to_sym }
463
- self.typecasts["string"] = lambda { |v| v.to_s }
464
- self.typecasts["yaml"] = lambda { |v| v.nil? ? nil : YAML.load(v) }
465
- self.typecasts["base64Binary"] = lambda { |v| v.unpack('m').first }
466
- end
1
+ require 'savon'
2
+
3
+ module GranicusPlatformAPI
4
+ class Client
5
+
6
+ attr_reader :connected
7
+
8
+ # mappings between soap types and our complex types
9
+ # this area should be rewritten to auto-generate data sets properly
10
+ # and refactored to separate standard xsd types from custom types in class
11
+ # map
12
+ def self.typegenerators
13
+ @@typegenerators
14
+ end
15
+
16
+ def self.typegenerators=(obj)
17
+ @@typegenerators = obj
18
+ end
19
+
20
+ self.typegenerators = {}
21
+
22
+ self.typegenerators["AgendaItem"] = lambda { AgendaItem.new }
23
+ self.typegenerators["Attendee"] = lambda { Attendee.new }
24
+ self.typegenerators["AttendeeStatus"] = lambda { AttendeeStatus.new }
25
+ self.typegenerators["CameraData"] = lambda { CameraData.new }
26
+ self.typegenerators["CaptionData"] = lambda { CaptionData.new }
27
+ self.typegenerators["ClipData"] = lambda { ClipData.new }
28
+ self.typegenerators["Document"] = lambda { Document.new }
29
+ self.typegenerators["EventData"] = lambda { EventData.new }
30
+ self.typegenerators["FolderData"] = lambda { FolderData.new }
31
+ self.typegenerators["GroupData"] = lambda { GroupData.new }
32
+ self.typegenerators["KeyMapping"] = lambda { KeyMapping.new }
33
+ self.typegenerators["MetaDataData"] = lambda { MetaDataData.new }
34
+ self.typegenerators["EComment"] = lambda { EComment.new }
35
+ self.typegenerators["Motion"] = lambda { Motion.new }
36
+ self.typegenerators["Note"] = lambda { Note.new }
37
+ self.typegenerators["Rollcall"] = lambda { Rollcall.new }
38
+ self.typegenerators["ServerData"] = lambda { ServerData.new }
39
+ self.typegenerators["ServerInterfaceData"] = lambda { ServerInterfaceData.new }
40
+ self.typegenerators["TemplateData"] = lambda { TemplateData.new }
41
+ self.typegenerators["ViewData"] = lambda { ViewData.new }
42
+ self.typegenerators["VoteEntry"] = lambda { VoteEntry.new }
43
+ self.typegenerators["VoteRecord"] = lambda { VoteRecord.new }
44
+ self.typegenerators["Setting"] = lambda { Setting.new }
45
+
46
+ # classmap for generating proper attributes! hash within savon calls
47
+ def self.classmap
48
+ @@classmap
49
+ end
50
+
51
+ def self.classmap=(obj)
52
+ @@classmap = obj
53
+ end
54
+
55
+ # built-in types
56
+ self.classmap = {}
57
+ self.classmap['Fixnum'] = "xsd:int"
58
+ self.classmap['String'] = "xsd:string"
59
+ self.classmap['TrueClass'] = 'xsd:boolean'
60
+ self.classmap['FalseClass'] = 'xsd:boolean'
61
+ self.classmap['Time'] = 'xsd:dateTime'
62
+ self.classmap['File'] = 'xsd:base64Binary'
63
+
64
+ # start custom types
65
+ self.classmap['AgendaItem'] = 'granicus:AgendaItem'
66
+ self.classmap['Attendee'] = 'granicus:Attendee'
67
+ self.classmap['AttendeeStatus'] = 'granicus:AttendeeStatus'
68
+ self.classmap['CameraData'] = 'granicus:CameraData'
69
+ self.classmap['CaptionData'] = 'granicus:CaptionData'
70
+ self.classmap['ClipData'] = 'granicus:ClipData'
71
+ self.classmap['Document'] = 'granicus:Document'
72
+ self.classmap['EComment'] = 'granicus:EComment'
73
+ self.classmap['EventData'] = 'granicus:EventData'
74
+ self.classmap['FolderData'] = 'granicus:FolderData'
75
+ self.classmap['GroupData'] = 'granicus:GroupData'
76
+ self.classmap['KeyMapping'] = 'granicus:KeyMapping'
77
+ self.classmap['MetaDataData'] = 'granicus:MetaDataData'
78
+ self.classmap['Motion'] = 'granicus:Motion'
79
+ self.classmap['Note'] = 'granicus:Note'
80
+ self.classmap['Rollcall'] = 'granicus:Rollcall'
81
+ self.classmap['ServerData'] = 'granicus:ServerData'
82
+ self.classmap['ServerInterfaceData'] = 'granicus:ServerInterfaceData'
83
+ self.classmap['TemplateData'] = 'granicus:TemplateData'
84
+ self.classmap['ViewData'] = 'granicus:ViewData'
85
+ self.classmap['VoteEntry'] = 'granicus:VoteEntry'
86
+ self.classmap['VoteRecord'] = 'granicus:VoteRecord'
87
+
88
+ # create a client
89
+ def initialize(granicus_site=nil, username=nil, password=nil, options={})
90
+ # setup our private members
91
+ @options = options
92
+ @impersonation_token = nil
93
+ @connected = false
94
+
95
+ # configure savon
96
+ Savon.configure do |config|
97
+ config.log = false
98
+ end
99
+ HTTPI.log = false
100
+
101
+ # connect if we have a site and credentials
102
+ unless granicus_site.nil?
103
+ self.site = granicus_site
104
+ end
105
+
106
+ unless username.nil? or password.nil?
107
+ login(username, password)
108
+ end
109
+ end
110
+
111
+ # options
112
+ def options
113
+ @options
114
+ end
115
+
116
+ def options=(value)
117
+ @options = value
118
+ end
119
+
120
+ # connect up to a site
121
+ def connect(granicus_site, username, password, options={})
122
+ logout if @connected
123
+
124
+ # create the client
125
+ self.site = granicus_site
126
+
127
+ # call login
128
+ login username, password
129
+ end
130
+
131
+ # site property
132
+ def site
133
+ return @granicus_site
134
+ end
135
+
136
+ def site=(value)
137
+ @granicus_site = value
138
+ @client = Savon::Client.new do |wsdl, http|
139
+ wsdl.document = File.expand_path("../granicus-platform-api.xml", __FILE__)
140
+ wsdl.endpoint = "http://#{value}/SDK/User/index.php"
141
+ http.proxy = @options[:proxy] if not @options[:proxy].nil?
142
+ end
143
+ end
144
+
145
+ # impersonate a user
146
+ def impersonate(token)
147
+ @impersonation_token = token
148
+ @client.http.headers["Cookie"] = "SESS1=#{token}; path=/"
149
+ end
150
+
151
+ def impersonation_token
152
+ @impersonation_token
153
+ end
154
+
155
+ # login
156
+ def login(username, password)
157
+ logout if @connected
158
+ call_soap_method(:login, '//ns4:LoginResponse/return', {'Username' => username, 'Password' => password})
159
+ @impersonation_token = @response.http.headers['Set-Cookie'].gsub(/SESS1=(.*); path=\//, '\\1')
160
+ @connected = true
161
+ end
162
+
163
+ # return the current logged on user name
164
+ def get_current_user_logon
165
+ call_soap_method(:get_current_user_logon, '//ns4:GetCurrentUserLogonResponse/Logon')
166
+ end
167
+
168
+ # logout
169
+ def logout
170
+ call_soap_method(:logout, '//ns4:LogoutResponse')
171
+ @connected = false
172
+ end
173
+
174
+ # return all of the cameras
175
+ def get_cameras
176
+ call_soap_method(:get_cameras, '//ns5:GetCamerasResponse/cameras')
177
+ end
178
+
179
+ # create a camera
180
+ def create_camera(camera)
181
+ call_soap_method(:create_camera, '//ns4:CreateCameraResponse/CameraID', {'CameraData' => camera})
182
+ end
183
+
184
+ # return the requested camera
185
+ def get_camera(camera_id)
186
+ call_soap_method(:get_camera, '//ns5:GetCameraResponse/camera', {'CameraID' => camera_id})
187
+ end
188
+
189
+ # update a camera
190
+ def update_camera(camera)
191
+ call_soap_method(:update_camera, '//ns4:UpdateCameraResponse', {'camera' => camera})
192
+ end
193
+
194
+ # delete the requested camera
195
+ def delete_camera(camera_id)
196
+ call_soap_method(:delete_camera, '//ns4:DeleteCameraResponse', {'CameraID' => camera_id})
197
+ end
198
+
199
+ # return all of the events
200
+ def get_events
201
+ call_soap_method(:get_events, '//ns5:GetEventsResponse/events')
202
+ end
203
+
204
+ # return all of the events with matching foreign id
205
+ def get_events_by_foreign_id(foreign_id)
206
+ call_soap_method(:get_events_by_foreign_id, '//ns5:GetEventsByForeignIDResponse/events', {'ForeignID' => foreign_id})
207
+ end
208
+
209
+ # create an event
210
+ def create_event(event)
211
+ call_soap_method(:create_event, '//ns4:CreateEventResponse/EventID', {'EventData' => event})
212
+ end
213
+
214
+ # return the requested event
215
+ def get_event(event_id)
216
+ call_soap_method(:get_event, '//ns5:GetEventResponse/event', {'EventID' => event_id})
217
+ end
218
+
219
+ # return the requested event by uid
220
+ def get_event_by_uid(event_uid)
221
+ call_soap_method(:get_event_by_uid, '//ns5:GetEventByUIDResponse/event', {'EventUID' => event_uid})
222
+ end
223
+
224
+ # update an event
225
+ def update_event(event)
226
+ call_soap_method(:update_event, '//ns4:UpdateEventResponse', {'event' => event})
227
+ end
228
+
229
+ # delete the requested event
230
+ def delete_event(event_id)
231
+ call_soap_method(:delete_event, '//ns4:DeleteEventResponse', {'EventID' => event_id})
232
+ end
233
+
234
+ # return all of the event meta data
235
+ def get_event_meta_data(event_id)
236
+ build_meta_tree call_soap_method(:get_event_meta_data, '//ns5:GetEventMetaDataResponse/metadata', {'EventID' => event_id})
237
+ end
238
+
239
+ # return all of the event meta data by UID
240
+ def get_event_meta_data_by_uid(event_uid)
241
+ build_meta_tree call_soap_method(:get_event_meta_data_by_uid, '//ns5:GetEventMetaDataByUIDResponse/metadata', {'EventUID' => event_uid})
242
+ end
243
+
244
+ # import metadata for an event
245
+ def import_event_meta_data(event_id, meta_data, clear_existing=true, as_tree=true)
246
+ call_soap_method(:import_event_meta_data, '//ns5:ImportEventMetaDataResponse/KeyTable', {
247
+ 'EventID' => event_id,
248
+ 'MetaData' => meta_data,
249
+ 'ClearExisting' => clear_existing,
250
+ 'AsTree' => as_tree})
251
+ end
252
+
253
+ # set the event agenda url
254
+ def set_event_agenda_url(event_id, url)
255
+ call_soap_method(:set_event_agenda_url, '//ns4:SetEventAgendaURLResponse', {'EventID' => event_id, 'URL' => url})
256
+ end
257
+
258
+ # return all of the clip meta data
259
+ def get_clip_meta_data(clip_id)
260
+ build_meta_tree call_soap_method(:get_clip_meta_data, '//ns5:GetClipMetaDataResponse/metadata', {'ClipID' => clip_id})
261
+ end
262
+
263
+ # import metadata for a clip
264
+ # ImportClipMetaData Method (ClipID, MetaData, ClearExisting, AsTree)
265
+ def import_clip_meta_data(clip_id, meta_data, clear_existing=true, as_tree=true)
266
+ call_soap_method(:import_clip_meta_data, '//ns5:ImportClipMetaDataResponse/KeyTable', {
267
+ 'ClipID' => clip_id,
268
+ 'MetaData' => meta_data,
269
+ 'ClearExisting' => clear_existing,
270
+ 'AsTree' => as_tree})
271
+ end
272
+
273
+ # add metadata to a clip
274
+ def add_clip_meta_data(clip_id, meta_data, options={})
275
+ call_soap_method( :add_clip_meta_data, '//ns5:AddClipMetaDataResponse/KeyTable', {
276
+ 'ClipID' => clip_id,
277
+ 'MetaData' => meta_data}, options)
278
+ end
279
+
280
+ # fetch an attachment
281
+ def fetch_attachment(meta_id)
282
+ call_soap_method(:fetch_attachment, '//ns5:FetchAttachmentResponse/Attachment', {'MetaDataID' => meta_id})
283
+ end
284
+
285
+ # get meta data by id
286
+ def get_meta_data(meta_id)
287
+ call_soap_method(:get_meta_data, '//ns5:GetMetaDataResponse/MetaData', {'MetaDataID' => meta_id})
288
+ end
289
+
290
+ # update metadata
291
+ def update_meta_data(meta_data)
292
+ call_soap_method(:update_meta_data, '//ns4:UpdateMetaDataResponse', {'MetaData' => meta_data})
293
+ end
294
+
295
+ def get_ecomments_by_event_id(event_id)
296
+ call_soap_method(:get_ecomments_by_event_id, '//ns5:GetEcommentsByEventIDResponse/EComments', {'EventID' => event_id})
297
+ end
298
+
299
+ def get_ecomments_by_agenda_item_uid(uid)
300
+ call_soap_method(:get_ecomments_by_agenda_item_uid, '//ns5:GetEcommentsByAgendaItemUIDResponse/EComments', {'AgendaItemUID' => uid})
301
+ end
302
+
303
+ # return all of the folders
304
+ def get_folders
305
+ call_soap_method(:get_folders, '//ns5:GetFoldersResponse/folders')
306
+ end
307
+
308
+ # return all of the clips
309
+ def get_clips(folder_id)
310
+ call_soap_method(:get_clips, '//ns5:GetClipsResponse/clips', {'FolderID' => folder_id})
311
+ end
312
+
313
+ # return all of the clips with matching foreign id
314
+ def get_clips_by_foreign_id(foreign_id)
315
+ call_soap_method(:get_clips_by_foreign_id, '//ns5:GetClipsByForeignIDResponse/clips', {'ForeignID' => foreign_id})
316
+ end
317
+
318
+ # return the requested clip
319
+ def get_clip(clip_id)
320
+ call_soap_method(:get_clip, '//ns5:GetClipResponse/clip', {'ClipID' => clip_id})
321
+ end
322
+
323
+ # update a clip
324
+ def update_clip(clip)
325
+ call_soap_method(:update_clip, '//ns4:UpdateClipResponse', {'clip' => clip})
326
+ end
327
+
328
+ # return the requested clip
329
+ def get_clip_by_uid(clip_uid)
330
+ call_soap_method(:get_clip_by_uid, '//ns5:GetClipByUIDResponse/clip', {'ClipUID' => clip_uid})
331
+ end
332
+
333
+ # get servers
334
+ def get_servers
335
+ call_soap_method(:get_servers, '//ns5:GetServersResponse/servers')
336
+ end
337
+
338
+ # return the requested server
339
+ def get_server(server_id)
340
+ call_soap_method(:get_server, '//ns5:GetServerResponse/server', {'ServerID' => server_id})
341
+ end
342
+
343
+ # get settings
344
+ def get_settings
345
+ call_soap_method(:get_settings, '//ns5:GetSettingsResponse/settings')
346
+ end
347
+
348
+ #private
349
+
350
+ def call_soap_method(method, returnfilter, args={}, options={})
351
+ debug = options[:debug]
352
+ @response = @client.request :wsdl, method do
353
+ soap.namespaces['xmlns:granicus'] = "http://granicus.com/xsd"
354
+ soap.namespaces['xmlns:SOAP-ENC'] = "http://schemas.xmlsoap.org/soap/encoding/"
355
+ soap.body = prepare_hash args
356
+ if debug
357
+ puts soap.body
358
+ end
359
+ end
360
+
361
+ doc = Nokogiri::XML(@response.to_xml) do |config|
362
+ config.noblanks
363
+ end
364
+ if debug
365
+ puts doc
366
+ end
367
+ response = handle_response(doc.xpath(returnfilter, doc.root.namespaces)[0])
368
+ if debug
369
+ puts response
370
+ end
371
+ response
372
+ end
373
+
374
+ def prepare_hash(hash={})
375
+ attributes = {}
376
+ new_hash = {}
377
+ hash.each do |key, value|
378
+ case value.class.to_s
379
+ when /GranicusPlatformAPI::/, 'Hash'
380
+ new_hash[key] = prepare_hash value
381
+ when 'Array'
382
+ new_hash[key] = prepare_array value
383
+ when 'File'
384
+ new_hash[key] = Base64.encode64(value.read().force_encoding('BINARY'))
385
+ else
386
+ new_hash[key] = value
387
+ end
388
+ attributes[key] = attribute_of value
389
+ end
390
+ new_hash.merge({:attributes! => attributes})
391
+ end
392
+
393
+ def prepare_array(array)
394
+ return {"item" => array} if array.count == 0
395
+ new_array = []
396
+ array.each do |item|
397
+ case item.class.to_s
398
+ when /GranicusPlatformAPI::/, 'Hash'
399
+ new_array << prepare_hash(item)
400
+ when 'Array'
401
+ new_array << prepare_array(item)
402
+ else
403
+ new_array << item
404
+ end
405
+ end
406
+ {"item" => new_array, :attributes! => {"item" => attribute_of(array[0])}}
407
+ end
408
+
409
+ def attribute_of(value)
410
+ case value.class.to_s
411
+ when 'Array'
412
+ return {"xsi:type" => 'SOAP-ENC:Array'} if value.count == 0
413
+ xsd_type = self.class.classmap[value[0].class.to_s.split('::').last]
414
+ if xsd_type.nil?
415
+ puts "Couldn't get array xsd:type for #{value[0].class}"
416
+ {"xsi:type" => 'SOAP-ENC:Array'}
417
+ else
418
+ {"xsi:type" => 'SOAP-ENC:Array', "SOAP-ENC:arrayType" => "#{xsd_type}[#{value.count}]"}
419
+ end
420
+ else
421
+ xsd_type = self.class.classmap[value.class.to_s.split('::').last]
422
+ if xsd_type.nil?
423
+ puts "Couldn't get xsd:type for #{value.class}"
424
+ nil
425
+ else
426
+ {"xsi:type" => xsd_type}
427
+ end
428
+ end
429
+ end
430
+
431
+ def handle_response(node)
432
+ if node.is_a? Nokogiri::XML::NodeSet or node.is_a? Array then
433
+ return node.map { |el| handle_response el }
434
+ end
435
+ return node.to_s unless node['type']
436
+ typespace, type = node['type'].split(':')
437
+ case typespace
438
+ when 'xsd'
439
+ proc = self.class.typecasts[type]
440
+ unless proc.nil?
441
+ proc.call(node.children[0].to_s)
442
+ else
443
+ puts "Unknown xsd:type: #{type}"
444
+ node.children[0].to_s
445
+ end
446
+ when 'SOAP-ENC'
447
+ if type == 'Array' then
448
+ node.children.map { |element| handle_response element }
449
+ else
450
+ puts "Unknown SOAP-ENC:type: #{type}"
451
+ node.to_s
452
+ end
453
+ else
454
+ # we have a custom type, attempt to generate it. if that fails use a hash
455
+ proc = self.class.typegenerators[type]
456
+ value = {}
457
+ unless proc.nil?
458
+ value = proc.call
459
+ else
460
+ puts "Unknown custom type: #{type}"
461
+ end
462
+ node.children.each do |value_node|
463
+ value[value_node.name] = handle_response value_node
464
+ end
465
+ value
466
+ end
467
+ end
468
+
469
+ # translate metadata list returned by the get_event and get_clip meta data functions into a tree
470
+ def build_meta_tree(list, parent_id=0)
471
+ tree = []
472
+ list.each do |item|
473
+ if item.ParentID == parent_id
474
+ item.Children = build_meta_tree list, item.ID
475
+ tree << item
476
+ end
477
+ end
478
+ tree
479
+ end
480
+
481
+ # typecasts ripped from rubiii/nori, adapted for xsd types
482
+ def self.typecasts
483
+ @@typecasts
484
+ end
485
+
486
+ def self.typecasts=(obj)
487
+ @@typecasts = obj
488
+ end
489
+
490
+ self.typecasts = {}
491
+ self.typecasts["int"] = lambda { |v| v.nil? ? nil : v.to_i }
492
+ self.typecasts["boolean"] = lambda { |v| v.nil? ? nil : (v.strip != "false") }
493
+ self.typecasts["datetime"] = lambda { |v| v.nil? ? nil : Time.parse(v).utc }
494
+ self.typecasts["date"] = lambda { |v| v.nil? ? nil : Date.parse(v) }
495
+ self.typecasts["dateTime"] = lambda { |v| v.nil? ? nil : Time.parse(v).utc }
496
+ self.typecasts["decimal"] = lambda { |v| v.nil? ? nil : BigDecimal(v.to_s) }
497
+ self.typecasts["double"] = lambda { |v| v.nil? ? nil : v.to_f }
498
+ self.typecasts["float"] = lambda { |v| v.nil? ? nil : v.to_f }
499
+ self.typecasts["symbol"] = lambda { |v| v.nil? ? nil : v.to_sym }
500
+ self.typecasts["string"] = lambda { |v| v.to_s }
501
+ self.typecasts["yaml"] = lambda { |v| v.nil? ? nil : YAML.load(v) }
502
+ self.typecasts["base64Binary"] = lambda { |v| v.unpack('m').first }
503
+ end
467
504
  end