episodic-platform 0.9

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.
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