granicus-platform-api 0.1.9 → 0.9.1

Sign up to get free protection for your applications and to get access to all the features.
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