episodic-platform 0.9

Sign up to get free protection for your applications and to get access to all the features.
Files changed (36) hide show
  1. data/History.txt +4 -0
  2. data/Manifest.txt +35 -0
  3. data/PostInstall.txt +7 -0
  4. data/README.rdoc +86 -0
  5. data/Rakefile +45 -0
  6. data/lib/episodic/platform.rb +82 -0
  7. data/lib/episodic/platform/analytics_methods.rb +190 -0
  8. data/lib/episodic/platform/base.rb +64 -0
  9. data/lib/episodic/platform/collection_response.rb +45 -0
  10. data/lib/episodic/platform/connection.rb +305 -0
  11. data/lib/episodic/platform/exceptions.rb +105 -0
  12. data/lib/episodic/platform/query_methods.rb +721 -0
  13. data/lib/episodic/platform/response.rb +94 -0
  14. data/lib/episodic/platform/write_methods.rb +446 -0
  15. data/script/console +10 -0
  16. data/script/destroy +14 -0
  17. data/script/generate +14 -0
  18. data/test/fixtures/1-0.mp4 +0 -0
  19. data/test/fixtures/create-episode-response-s3.xml +15 -0
  20. data/test/fixtures/create-episode-response.xml +3 -0
  21. data/test/fixtures/episodes-response.xml +408 -0
  22. data/test/fixtures/episodes-summary-report-response.xml +4 -0
  23. data/test/fixtures/invalid-param-response-multiple.xml +8 -0
  24. data/test/fixtures/invalid-param-response-single.xml +9 -0
  25. data/test/fixtures/playlists-response.xml +611 -0
  26. data/test/fixtures/shows-response.xml +26 -0
  27. data/test/test_analytics_requests.rb +42 -0
  28. data/test/test_analytics_responses.rb +14 -0
  29. data/test/test_connection.rb +31 -0
  30. data/test/test_error_responses.rb +29 -0
  31. data/test/test_helper.rb +8 -0
  32. data/test/test_query_requests.rb +62 -0
  33. data/test/test_query_responses.rb +165 -0
  34. data/test/test_write_requests.rb +56 -0
  35. data/test/test_write_responses.rb +24 -0
  36. metadata +135 -0
@@ -0,0 +1,94 @@
1
+ module Episodic
2
+ module Platform
3
+
4
+ #
5
+ # Base class for responses. This class takes the response and parses the body using XmlSimple.
6
+ # If the response body contains an error then this method will throw the appropriate exception.
7
+ #
8
+ class Response
9
+
10
+ #
11
+ # Constructor
12
+ #
13
+ # ==== Parameters
14
+ #
15
+ # response<Episodic::Platform::HttpResponse>:: The response object returned from an Episodic Platform API request.
16
+ # xml_options<Hash>:: A set of options used by XmlSimple when parsing the response body
17
+ #
18
+ def initialize response, xml_options = {}
19
+ @response = response
20
+ xml_options['ForceArray'] ||= false
21
+ @parsed_body = ::XmlSimple.xml_in(@response.body, xml_options.merge({'KeepRoot' => true}))
22
+
23
+ # Now that we have parsed the response, we can make sure that it is valid or otherwise
24
+ # throw an exception.
25
+ if @parsed_body["error"]
26
+ handle_error_response(@parsed_body["error"])
27
+ else
28
+ # Remove the root element
29
+ @parsed_body = @parsed_body[@parsed_body.keys.first]
30
+ end
31
+ end
32
+
33
+ #
34
+ # Provides access to the unparsed XML response
35
+ #
36
+ # ==== Returns
37
+ #
38
+ # String:: The XML response as a string.
39
+ #
40
+ def xml
41
+ return @response.body
42
+ end
43
+
44
+ protected
45
+
46
+ #
47
+ # Uses the code in the error response XML to determine which exception should be raised and raises it.
48
+ # The message in the XML is included in the exception.
49
+ #
50
+ # ==== Parameters
51
+ #
52
+ # error<Hash>:: The parsed error response.
53
+ #
54
+ def handle_error_response error
55
+ code = error["code"].to_i
56
+ message = error["message"]
57
+
58
+ # Figure out which exception to raise
59
+ case code
60
+ when 1 then raise ::Episodic::Platform::InvalidAPIKey.new(message, @response)
61
+ when 2 then raise ::Episodic::Platform::ReportNotFound.new(message, @response)
62
+ when 3 then raise ::Episodic::Platform::MissingRequiredParameter.new(message, @response)
63
+ when 4 then raise ::Episodic::Platform::InvalidParameters.new(message, @response, error["invalid_parameters"])
64
+ when 5 then raise ::Episodic::Platform::RequestExpired.new(message, @response)
65
+ when 6 then raise ::Episodic::Platform::NotFound.new(message, @response)
66
+ when 7 then raise ::Episodic::Platform::APIAccessDisabled.new(message, @response)
67
+ else
68
+ raise ::Episodic::Platform::ResponseError.new(message, @response)
69
+ end
70
+ end
71
+ end
72
+
73
+ #
74
+ # The raw response. This contains the response code and body.
75
+ #
76
+ class HTTPResponse
77
+
78
+ attr_reader :response_code, :body
79
+
80
+ #
81
+ # Constructor
82
+ #
83
+ # ==== Parameters
84
+ #
85
+ # response_code<Integer>:: The code returned from the request
86
+ # body<String>:: The xml of the response
87
+ #
88
+ def initialize response_code, body
89
+ @response_code = response_code
90
+ @body = body
91
+ end
92
+ end
93
+ end
94
+ end
@@ -0,0 +1,446 @@
1
+ module Episodic #:nodoc:
2
+
3
+ module Platform
4
+
5
+ #
6
+ # Class for making requests against the Episodic Platform Write API.
7
+ #
8
+ class WriteMethods < Base
9
+
10
+ class << self
11
+
12
+ #
13
+ # The Create Asset method is used to upload a new video or image asset for use in one of your shows.
14
+ #
15
+ # NOTE: This method of uploading has been deprecated. The preferred method is to call
16
+ # <tt>Episodic::Platform::create_episode</tt> or <tt>Episodic::Platform::update_episode</tt>
17
+ # with <tt>upload_types</tt> specified.
18
+ #
19
+ # You may still use this method but we will limit your usage to 5 assets a day.
20
+ #
21
+ # ==== Parameters
22
+ #
23
+ # show_id<String>:: The ID of the show to create the asset in.
24
+ # name<String>:: The name of the new asset. This value must be less than 255 characters.
25
+ # filename<String>:: The full path to the file on the file system. This is the image or video.
26
+ #
27
+ # ==== Returns
28
+ #
29
+ # Episodic::Platform::WriteResponse:: The parsed response.
30
+ #
31
+ def create_asset show_id, name, filename
32
+ params = {}
33
+
34
+ # Add the required fields
35
+ params[:show_id] = show_id
36
+ params[:name] = name
37
+
38
+ response = connection.do_post(construct_url("write", "create_asset"), params, {"uploaded_data" => filename})
39
+
40
+ return Episodic::Platform::WriteResponse.new(response)
41
+ end
42
+
43
+ #
44
+ # Creates an episode in the Episodic System in the specified show. This method will
45
+ # return the information needed to upload the video file for this episode. If you are
46
+ # adding a file to the episode then the <tt>upload_file_for_episode</tt> must be called
47
+ # immediately after this method. For example,
48
+ #
49
+ # ::Episodic::Platform::WriteMethods.create_episode("oz04s1q0i29t", "My Episode", {:upload_types => "s3", :video_filename => "1-0.mp4"})
50
+ # ::Episodic::Platform::WriteMethods.upload_file_for_episode(response.upload_for_filepath("/path/to/file/1-0.mp4"))
51
+ #
52
+ # The last parameter to this method is a list of optional attributes. Acceptable
53
+ # options are:
54
+ # air_date - Setting this date in the future will tell the Episodic Platform to publish
55
+ # the episode but not make it available in playlists until this date has passed.
56
+ # This defaults to now if not provided.
57
+ # custom_fields- If you have set up custom metadata fields on the show that you are
58
+ # creating the episode in you can also assign values to those fields by passing
59
+ # in a Hash of name/value pairs where the name is the name of your custom field.
60
+ # In the case that the field you are trying to set is a external select field then
61
+ # the value should be a Hash mapping ids to display values.
62
+ # description - A string value to be used as the description for the episode.
63
+ # Descriptions must be less than 255 characters
64
+ # off_air_date - When this date is reached the episode will be removed from all
65
+ # playlists. This defaults to indefinite if not provided.
66
+ # publish - This must either <tt>true</tt> or <tt>false</tt> to indicate whether the
67
+ # episode should be submitted for publishing. The default is <tt>false</tt>.
68
+ # publish_format_ids - Publishing resolutions and bitrates defaults are set on the
69
+ # containing show but can be overridden on the episode. If you wish to override the
70
+ # defaults, this value should be an Array of publishing profile ids.
71
+ # tags - A comma delimitted list of tags for the new episode.
72
+ # upload_types - This is only used when there is a 'video_filename' and/or 'thumbnail_filename' included.
73
+ # The caller may pass in a single value or a comma delimited list. However, it is important to note that your
74
+ # network must support the one of specified upload types or the call will fail. Currently, the only valid value
75
+ # is 's3'.
76
+ # asset_ids - The list of assets in the order they are to appear in the episode. Ingored if the upload_types and
77
+ # asset_filename parameters are not blank.
78
+ # thumbnail_id - The id of the thumbnail to set on the episode. Ignored if the upload_types and thumbnail_filename parameters
79
+ # are not blank.
80
+ # video_filename - If an upload_type is specified this is the name of the file that will be uploaded and made the single video for
81
+ # the episode.
82
+ # thumbnail_filename - If an upload_type is specified this is the name of the file that will be uploaded and made the thumbnail
83
+ # for the episode.
84
+ # ping_url - The URL the Episodic system will issue a GET request against to notify you
85
+ # that publishing has completed. The ping URL should accept two parameters: the
86
+ # episode id and a status which will be one of 'success' or 'failure'.
87
+ #
88
+ # ==== Parameters
89
+ #
90
+ # show_id<String>:: The ID of the show to create the episode in.
91
+ # name<String>:: The name of the episode. This must be unique across your show.
92
+ # options<Hash>:: A hash of optional attributes.
93
+ #
94
+ # ==== Returns
95
+ #
96
+ # Episodic::Platform::CreateUpdateEpisodeResponse:: An object that contains the XML response as well as some
97
+ # helper methods including <tt>upload_for_filepath</tt> to be used when calling
98
+ # <tt>Episodic::Platform::WriteMethods.upload_file_for_episode</tt>.
99
+ #
100
+ def create_episode(show_id, name, options = {})
101
+
102
+ # Clone the options into our params hash
103
+ params = options.clone
104
+
105
+ # Add the required fields
106
+ params[:show_id] = show_id
107
+ params[:name] = name
108
+
109
+ response = connection.do_post(construct_url("write", "create_episode"), params)
110
+
111
+ return Episodic::Platform::CreateUpdateEpisodeResponse.new(response)
112
+
113
+ end
114
+
115
+ #
116
+ # Creates a manual playlist in the Episodic System in the specified show.
117
+ #
118
+ # The last parameter to this method is a list of optional attributes. Acceptable
119
+ # options are:
120
+ # description - A string value to be used as the description for the playlist.
121
+ # Descriptions must be less than 255 characters
122
+ # episode_ids - An array or list of comma separated valid episode ids in the order
123
+ # they should appear in the playlist.
124
+ # behavior - Indicates what the player should do when an episode in the playlist finishes.
125
+ # Valid values are '0' (display a list of other episodes in the playlist),
126
+ # '1' (start playing the next episode immediately) or '2' (display the list
127
+ # but start playing after 'auto_play_delay' seconds). The default is '0'.
128
+ # auto_play_delay - If the 'behavior' value is '2' then this is the number of seconds
129
+ # to wait until the next episode is played. The default is 5.
130
+ # custom_fields - If you have set up custom metadata fields on the show that you are
131
+ # creating the playlist in you can also assign values to those fields by passing
132
+ # in a Hash of name/value pairs where the name is the name of your custom field.
133
+ # In the case that the field you are trying to set is a external select field then
134
+ # the value should be a Hash mapping ids to display values.
135
+ # upload_types - The caller may pass in a single value or a comma delimited list. However,
136
+ # it is important to note that your network must support the one of specified upload
137
+ # types or the call will fail. Currently, the only valid value is 's3'.
138
+ # video_filename - If an upload_type is specified this is the name of the file that will be
139
+ # uploaded and made the single video for the episode.
140
+ # thumbnail_filename - If an upload_type is specified this is the name of the file that will
141
+ # be uploaded and made the thumbnail for the episode.
142
+ #
143
+ # ==== Parameters
144
+ #
145
+ # show_id<String>:: The ID of the show to create the playlist in.
146
+ # name<String>:: The name of the playlist. This must be unique across your show.
147
+ # options<Hash>:: A hash of optional attributes.
148
+ #
149
+ # ==== Returns
150
+ #
151
+ # Episodic::Platform::WriteResponse:: The parsed response.
152
+ #
153
+ def create_playlist show_id, name, options = {}
154
+
155
+ # Clone the options into our params hash
156
+ params = options.clone
157
+
158
+ # Add the required fields
159
+ params[:show_id] = show_id
160
+ params[:name] = name
161
+
162
+ response = connection.do_post(construct_url("write", "create_playlist"), params)
163
+
164
+ return Episodic::Platform::WriteResponse.new(response)
165
+ end
166
+
167
+ #
168
+ # Updates an episode in the Episodic System. This method will
169
+ # return the information needed to upload the video file for this episode. If you are
170
+ # adding a file to the episode then the <tt>upload_file_for_episode</tt> must be called
171
+ # immediately after this method. For example,
172
+ #
173
+ # ::Episodic::Platform::WriteMethods.update_episode("jz54h6q0i39y", {:upload_types => "s3", :thumbnail_filename => "my_thumb.jpg"})
174
+ # ::Episodic::Platform::WriteMethods.upload_file_for_episode(response.upload_for_filepath("/path/to/file/my_thumb.jpg"))
175
+ #
176
+ # The last parameter to this method is a list of optional attributes. Acceptable
177
+ # options are:
178
+ # air_date - Setting this date in the future will tell the Episodic Platform to publish
179
+ # the episode but not make it available in playlists until this date has passed.
180
+ # This defaults to now if not provided.
181
+ # custom_fields- If you have set up custom metadata fields on the show that you are
182
+ # creating the episode in you can also assign values to those fields by passing
183
+ # in a Hash of name/value pairs where the name is the name of your custom field.
184
+ # In the case that the field you are trying to set is a external select field then
185
+ # the value should be a Hash mapping ids to display values.
186
+ # description - A string value to be used as the description for the episode.
187
+ # Descriptions must be less than 255 characters
188
+ # off_air_date - When this date is reached the episode will be removed from all
189
+ # playlists. This defaults to indefinite if not provided.
190
+ # publish_format_ids - Publishing resolutions and bitrates defaults are set on the
191
+ # containing show but can be overridden on the episode. If you wish to override the
192
+ # defaults, this value should be an Array of publishing profile ids.
193
+ # tags - A comma delimitted list of tags for the new episode.
194
+ # upload_types - This is only used when there is a 'video_filename' and/or 'thumbnail_filename' included.
195
+ # The caller may pass in a single value or a comma delimited list. However, it is important to note that your
196
+ # network must support the one of specified upload types or the call will fail. Currently, the only valid value
197
+ # is 's3'.
198
+ # thumbnail_id - The id of the thumbnail to set on the episode. Ignored if the upload_types and thumbnail_filename parameters
199
+ # are not blank.
200
+ # video_filename - If an upload_type is specified this is the name of the file that will be uploaded and made the single video for
201
+ # the episode.
202
+ # thumbnail_filename - If an upload_type is specified this is the name of the file that will be uploaded and made the thumbnail
203
+ # for the episode.
204
+ #
205
+ # ==== Parameters
206
+ #
207
+ # id<String>:: The ID of the episode to update.
208
+ # options<Hash>:: A hash of optional attributes.
209
+ #
210
+ # ==== Returns
211
+ #
212
+ # Episodic::Platform::CreateUpdateEpisodeResponse:: An object that contains the XML response as well as some
213
+ # helper methods including <tt>upload_for_filepath</tt> to be used when calling
214
+ # <tt>Episodic::Platform::WriteMethods.upload_file_for_episode</tt>.
215
+ #
216
+ def update_episode(id, options = {})
217
+
218
+ # Clone the options into our params hash
219
+ params = options.clone
220
+
221
+ # Add the required fields
222
+ params[:id] = id
223
+
224
+ response = connection.do_post(construct_url("write", "update_episode"), params)
225
+
226
+ return Episodic::Platform::CreateUpdateEpisodeResponse.new(response)
227
+
228
+ end
229
+
230
+ #
231
+ # Updates a manual playlist in the Episodic System with the specified ID.
232
+ #
233
+ # The last parameter to this method is a list of optional attributes. Acceptable
234
+ # options are:
235
+ # name - The name of the playlist. his must be unique across your show.
236
+ # description - A string value to be used as the description for the playlist.
237
+ # Descriptions must be less than 255 characters
238
+ # episode_ids - An array or list of comma separated valid episode ids in the order
239
+ # they should appear in the playlist.
240
+ # replace_episodes - Indicates if the existing episodes should be replaced by the new
241
+ # ones or added to. The default is 'false'.
242
+ # behavior - Indicates what the player should do when an episode in the playlist finishes.
243
+ # Valid values are '0' (display a list of other episodes in the playlist),
244
+ # '1' (start playing the next episode immediately) or '2' (display the list
245
+ # but start playing after 'auto_play_delay' seconds). The default is '0'.
246
+ # auto_play_delay - If the 'behavior' value is '2' then this is the number of seconds
247
+ # to wait until the next episode is played. The default is 5.
248
+ # custom_fields - If you have set up custom metadata fields on the show that you are
249
+ # creating the playlist in you can also assign values to those fields by passing
250
+ # in a Hash of name/value pairs where the name is the name of your custom field.
251
+ # In the case that the field you are trying to set is a external select field then
252
+ # the value should be a Hash mapping ids to display values.
253
+ #
254
+ # ==== Parameters
255
+ #
256
+ # id<String>:: The ID of the playlist to update.
257
+ # options<Hash>:: A hash of optional attributes.
258
+ #
259
+ # ==== Returns
260
+ #
261
+ # Episodic::Platform::WriteResponse:: The parsed response.
262
+ #
263
+ def update_playlist id, options = {}
264
+ # Clone the options into our params hash
265
+ params = options.clone
266
+
267
+ # Add the required fields
268
+ params[:id] = id
269
+
270
+ response = connection.do_post(construct_url("write", "update_playlist"), params)
271
+
272
+ return Episodic::Platform::WriteResponse.new(response)
273
+ end
274
+
275
+ #
276
+ # Uploads the video and/or images to the Episodic System. This method requires that you first called <tt>create_episode</tt> or
277
+ # <tt>update_episode</tt> since the second parameter passed to this method is returned from
278
+ # one of those method calls.
279
+ #
280
+ # ==== Parameters
281
+ #
282
+ # pending_upload<Hash>:: This is the result of the call to <tt>upload_for_filepath</tt>
283
+ # on the object returned from <tt>create_episode</tt> or <tt>update_episode_video</tt>.
284
+ #
285
+ # ==== Returns
286
+ #
287
+ # Boolean:: <tt>true</tt> if the upload was successful. Otherwise, this will raise an exception
288
+ #
289
+ def upload_file_for_episode(pending_upload)
290
+
291
+ c = Curl::Easy.new(pending_upload[:upload].url)
292
+ c.multipart_form_post = true
293
+
294
+ fields = []
295
+ pending_upload[:upload].params.each_pair do |key, value|
296
+ fields << Curl::PostField.content(key, value)
297
+ end
298
+
299
+ fields << Curl::PostField.file("file", pending_upload[:filepath])
300
+
301
+ begin
302
+ c.http_post(*fields)
303
+ raise ::Episodic::Platform::FileUploadFailed.new("Status #{c.response_code} returned from file upload request") if c.response_code > 399
304
+ return true
305
+ rescue Curl::Err::CurlError => e
306
+ raise ::Episodic::Platform::FileUploadFailed.new(e.message)
307
+ end
308
+ end
309
+
310
+ protected
311
+
312
+ #
313
+ # Method factored out to make unit testing easier. This method simply creates the <tt>::Episodic::Platform::WriteResponse</tt>
314
+ # object.
315
+ #
316
+ # ==== Parameters
317
+ #
318
+ # response<Episodic::Platform::HTTPResponse>:: The response from any create request (i.e. create_episode, create_playlist, etc)
319
+ #
320
+ # ==== Returns
321
+ #
322
+ # ::Episodic::Platform::WriteResponse:: The parsed response.
323
+ #
324
+ def parse_create_response response
325
+ return ::Episodic::Platform::EpisodesResponse.new(response)
326
+ end
327
+ end
328
+ end
329
+
330
+ #
331
+ # All write methods have a similar response structure. This class extends <tt>Episodic::Platform::Response</tt>
332
+ # and adds a method to get the id of the created/updated object ('playlist_id', 'episode_id', etc).
333
+ #
334
+ class WriteResponse < Response
335
+
336
+ #
337
+ # Constructor
338
+ #
339
+ # ==== Parameters
340
+ #
341
+ # response<Episodic::Platform::HTTPResponse>:: The response object returned from an Episodic Platform API request.
342
+ # xml_options<Hash>:: A set of options used by XmlSimple when parsing the response body
343
+ #
344
+ def initialize response, xml_options = {"ForceArray" => false}
345
+ super(response, xml_options)
346
+ end
347
+
348
+ #
349
+ # Override to just check for the value in the attributes
350
+ #
351
+ def method_missing(method_sym, *arguments, &block)
352
+ return @parsed_body[method_sym.to_s]
353
+ end
354
+
355
+ #
356
+ # Always return true.
357
+ #
358
+ def respond_to?(symbol, include_private = false)
359
+ return true
360
+ end
361
+ end
362
+
363
+ #
364
+ # Extends <tt>Episodic::Platform::WriteResponse</tt> to add methods for getting upload
365
+ # information.
366
+ #
367
+ class CreateUpdateEpisodeResponse < WriteResponse
368
+
369
+ #
370
+ # Override to define array elements.
371
+ #
372
+ # ==== Parameters
373
+ #
374
+ # response<Episodic::Platform::HTTPResponse>:: The response object returned from an Episodic Platform API request.
375
+ #
376
+ def initialize(response)
377
+ super(response, "ForceArray" => ["upload"])
378
+ end
379
+
380
+ #
381
+ # Get the array of <tt>Upload</tt> objects that represent the pending uploads
382
+ #
383
+ # ==== Returns
384
+ #
385
+ # Array:: An array of <tt>Upload</tt> objects
386
+ #
387
+ def uploads
388
+ unless @uploads
389
+ @uploads = []
390
+ @parsed_body["upload"].each do |upload|
391
+ @uploads << Upload.new(upload)
392
+ end unless @parsed_body["upload"].nil?
393
+ end
394
+
395
+ return @uploads
396
+ end
397
+
398
+ #
399
+ # Get the pending upload information for a specific file expressed by file path.
400
+ # The object returned from this method can be passed to <tt>Episodic::Platform::WriteMethods.upload_file_for_episode</tt>.
401
+ #
402
+ # ==== Parameters
403
+ #
404
+ # filepath<String>:: The path to the file to be uploaded.
405
+ #
406
+ # ==== Returns
407
+ #
408
+ # Hash:: A hash with the filepath (passed in) and the corresponding upload params
409
+ #
410
+ def upload_for_filepath filepath
411
+ filename = File.basename(filepath)
412
+
413
+ upload = uploads.detect{|u| u.filename == filename}
414
+
415
+ return upload ? {:filepath => filepath, :upload => upload} : nil
416
+ end
417
+ end
418
+
419
+ #
420
+ # Represents a pending upload. This includes the URL, filename as well as
421
+ # a list of params to be included in the POST.
422
+ #
423
+ class Upload
424
+
425
+ attr_reader :filename, :url, :params
426
+
427
+ #
428
+ # Constructor
429
+ #
430
+ # ==== Parameters
431
+ #
432
+ # upload<Hash>:: A hash for an upload element in the response.
433
+ #
434
+ def initialize(upload)
435
+ @filename = upload["filename"]
436
+ @url = upload["url"]
437
+ @params = {}
438
+ upload["param"].each do |param|
439
+ @params[param["name"]] = param["content"]
440
+ end
441
+ end
442
+ end
443
+
444
+ end
445
+
446
+ end