./lib/animoto/client.rb in animoto-0.0.0.alpha9 vs ./lib/animoto/client.rb in animoto-0.1.0.beta0

- old
+ new

@@ -1,43 +1,98 @@ require 'yaml' require 'uri' require 'logger' -require 'animoto/errors' -require 'animoto/content_type' -require 'animoto/standard_envelope' -require 'animoto/resource' -require 'animoto/asset' -require 'animoto/visual' -require 'animoto/coverable' -require 'animoto/footage' -require 'animoto/image' -require 'animoto/song' -require 'animoto/title_card' -require 'animoto/manifest' -require 'animoto/directing_manifest' -require 'animoto/rendering_manifest' -require 'animoto/directing_and_rendering_manifest' -require 'animoto/storyboard' -require 'animoto/video' -require 'animoto/job' -require 'animoto/directing_and_rendering_job' -require 'animoto/directing_job' -require 'animoto/rendering_job' -require 'animoto/dynamic_class_loader' -require 'animoto/http_engine' -require 'animoto/response_parser' +require 'animoto/support/content_type' +require 'animoto/support/coverable' +require 'animoto/support/dynamic_class_loader' +require 'animoto/support/errors' +require 'animoto/support/standard_envelope' +require 'animoto/support/visual' +require 'animoto/resources/base' +require 'animoto/resources/storyboard' +require 'animoto/resources/video' +require 'animoto/resources/jobs/base' +require 'animoto/resources/jobs/directing_and_rendering' +require 'animoto/resources/jobs/directing' +require 'animoto/resources/jobs/rendering' + +require 'animoto/assets/base' +require 'animoto/assets/footage' +require 'animoto/assets/image' +require 'animoto/assets/song' +require 'animoto/assets/title_card' + +require 'animoto/manifests/base' +require 'animoto/manifests/directing' +require 'animoto/manifests/directing_and_rendering' +require 'animoto/manifests/rendering' + +require 'animoto/callbacks/base' +require 'animoto/callbacks/directing' +require 'animoto/callbacks/directing_and_rendering' +require 'animoto/callbacks/rendering' + +require 'animoto/http_engines/base' +require 'animoto/response_parsers/base' + module Animoto class Client API_ENDPOINT = "https://api2-sandbox.animoto.com/" API_VERSION = 1 BASE_CONTENT_TYPE = "application/vnd.animoto" - attr_accessor :key, :secret, :endpoint, :logger - attr_reader :http_engine, :response_parser + # Your API key. + # @return [String] + attr_accessor :key + # Your API secret. + # @return [String] + attr_accessor :secret + + # The base URL where all requests will be sent. + # @return [String] + attr_accessor :endpoint + + # A logger. + # @return [Logger] + attr_accessor :logger + + # The engine to handle HTTP requests. + # @return [HTTPEngines::Base] + # @overload http_engine + # Returns the HTTP engine. + # @return [HTTPEngines::Base] + # @overload http_engine=(engine) + # Sets the HTTP engine. + # + # @param [HTTPEngines::Base,Symbol,Class] engine you may pass a + # HTTPEngine instance to use, or the symbolic name of an adapter to use, + # or a Class whose instances respond to #request and return a String of + # the response body + # @see Animoto::HTTPEngines::Base + # @return [HTTPEngines::Base] the engine instance + # @raise [ArgumentError] if given a class without the correct interface + attr_reader :http_engine + + # The engine to handle parsing XML or JSON responses. + # @return [ResponseParsers::Base] + # @overload response_parser + # Returns the parser. + # @return [ResponseParsers::Base] + # @overload response_parser=(parser) + # Sets the parser. + # + # @param [ResponseParsers::Base,Symbol,Class] parser you may pass a + # ResponseParser instance to use, or the symbolic name of an adapter to use, + # or a Class whose instances respond to #parse, #unparse, and #format. + # @see Animoto::ResponseParsers::Base + # @return [ResponseParsers::Base] the parser instance + # @raise [ArgumentError] if given a class without the correct interface + attr_reader :response_parser + # Creates a new Client object which handles credentials, versioning, making requests, and # parsing responses. # # If you have your key and secret in ~/.animotorc or /etc/.animotorc, those credentials will # be read from those files (in that order) whenever you make a new Client if you don't specify @@ -58,109 +113,102 @@ @endpoint ||= API_ENDPOINT __send__ :http_engine=, options[:http_engine] || :net_http __send__ :response_parser=, options[:response_parser] || :json end - # Set the HTTP engine this client will use. - # - # @param [HTTPEngine, Symbol, Class] engine you may pass a - # HTTPEngine instance to use, or the symbolic name of a adapter to use, - # or a Class whose instances respond to #request and return a String of - # the response body - # @see Animoto::HTTPEngine - # @return [HTTPEngine] the engine instance - # @raise [ArgumentError] if given a class without the correct interface def http_engine= engine @http_engine = case engine - when Animoto::HTTPEngine + when Animoto::HTTPEngines::Base engine when Class if engine.instance_methods.include?('request') engine.new else raise ArgumentError end else - Animoto::HTTPEngine[engine].new + Animoto::HTTPEngines[engine].new end end - # Set the response parser this client will use. - # - # @param [ResponseParser, Symbol, Class] parser you may pass a - # ResponseParser instance to use, or the symbolic name of a adapter to use, - # or a Class whose instances respond to #parse, #unparse, and #format. - # @see Animoto::ResponseParser - # @return [ResponseParser] the parser instance - # @raise [ArgumentError] if given a class without the correct interface def response_parser= parser @response_parser = case parser - when Animoto::ResponseParser + when Animoto::ResponseParsers::Base parser when Class if %{format parse unparse}.all? { |m| parser.instance_methods.include? m } parser.new else raise ArgumentError end else - Animoto::ResponseParser[parser].new + Animoto::ResponseParsers[parser].new end end # Finds a resource by its URL. # - # @param [Class] klass the Resource class you're finding + # @param [Class] klass the resource class you're finding # @param [String] url the URL of the resource you want - # @param [Hash] options - # @return [Resource] the Resource object found + # @param [Hash<Symbol,Object>] options + # @return [Resources::Base] the resource object found def find klass, url, options = {} klass.load(find_request(klass, url, options)) end + # Returns a callback object of the specified type given the callback body. + # + # @param [Class] klass the callback class + # @param [String] body the HTTP body of the callback + # @return [Callbacks::Base] the callback object + def process_callback klass, body + klass.new(response_parser.parse(body)) + end + # Sends a request to start directing a storyboard. # - # @param [DirectingManifest] manifest the manifest to direct - # @param [Hash] options - # @return [DirectingJob] a job to monitor the status of the directing + # @param [Manifests::Directing] manifest the manifest to direct + # @param [Hash<Symbol,Object>] options + # @return [Jobs::Directing] a job to monitor the status of the directing def direct! manifest, options = {} - DirectingJob.load(send_manifest(manifest, DirectingJob.endpoint, options)) + Resources::Jobs::Directing.load(send_manifest(manifest, Resources::Jobs::Directing.endpoint, options)) end # Sends a request to start rendering a video. # - # @param [RenderingManifest] manifest the manifest to render - # @param [Hash] options - # @return [RenderingJob] a job to monitor the status of the rendering + # @param [Manifests::Rendering] manifest the manifest to render + # @param [Hash<Symbol,Object>] options + # @return [Jobs::Rendering] a job to monitor the status of the rendering def render! manifest, options = {} - RenderingJob.load(send_manifest(manifest, RenderingJob.endpoint, options)) + Resources::Jobs::Rendering.load(send_manifest(manifest, Resources::Jobs::Rendering.endpoint, options)) end # Sends a request to start directing and rendering a video. # - # @param [DirectingAndRenderingManifest] manifest the manifest to direct and render - # @param [Hash] options - # @return [DirectingAndRenderingJob] a job to monitor the status of the directing and rendering + # @param [Manifests::DirectingAndRendering] manifest the manifest to direct and render + # @param [Hash<Symbol,Object>] options + # @return [Jobs::DirectingAndRendering] a job to monitor the status of the directing and rendering def direct_and_render! manifest, options = {} - DirectingAndRenderingJob.load(send_manifest(manifest, DirectingAndRenderingJob.endpoint, options)) + Resources::Jobs::DirectingAndRendering.load(send_manifest(manifest, Resources::Jobs::DirectingAndRendering.endpoint, options)) end # Update a resource with the latest attributes. Useful to update the state of a Job to # see if it's ready if you are not using HTTP callbacks. # - # @param [Resource] resource the resource to update - # @param [Hash] options - # @return [Resource] the given resource with the latest attributes + # @param [Resources::Base] resource the resource to update + # @param [Hash<Symbol,Object>] options + # @return [Resources::Base] the given resource with the latest attributes def reload! resource, options = {} resource.load(find_request(resource.class, resource.url, options)) end private # Sets the API credentials from an .animotorc file. First looks for one in the current # directory, then checks ~/.animotorc, then finally /etc/.animotorc. # + # @return [void] # @raise [ArgumentError] if none of the files are found def configure_from_rc_file current_path = Dir.pwd + '/.animotorc' home_path = File.expand_path('~/.animotorc') config = if File.exist?(current_path) @@ -181,22 +229,22 @@ # Builds a request to find a resource. # # @param [Class] klass the Resource class you're looking for # @param [String] url the URL of the resource - # @param [Hash] options - # @return [Hash] deserialized response body + # @param [Hash<Symbol,Object>] options + # @return [Hash<String,Object>] deserialized response body def find_request klass, url, options = {} request(:get, url, nil, { "Accept" => content_type_of(klass) }, options) end # Builds a request requiring a manifest. # - # @param [Manifest] manifest the manifest being acted on + # @param [Manifests::Base] manifest the manifest being acted on # @param [String] endpoint the endpoint to send the request to - # @param [Hash] options - # @return [Hash] deserialized response body + # @param [Hash<Symbol,Object>] options + # @return [Hash<String,Object>] deserialized response body def send_manifest manifest, endpoint, options = {} u = URI.parse(self.endpoint) u.path = endpoint request( :post, @@ -209,39 +257,44 @@ # Makes a request and parses the response. # # @param [Symbol] method which HTTP method to use (should be lowercase, i.e. :get instead of :GET) # @param [String] url the URL of the request - # @param [String, nil] body the request body + # @param [String,nil] body the request body # @param [Hash<String,String>] headers the request headers (will be sent as-is, which means you should # specify "Content-Type" => "..." instead of, say, :content_type => "...") - # @param [Hash] options - # @return [Hash] deserialized response body + # @param [Hash<Symbol,Object>] options + # @return [Hash<String,Object>] deserialized response body # @raise [Error] def request method, url, body, headers = {}, options = {} - error = catch(:fail) do + code, body = catch(:fail) do options = { :username => @key, :password => @secret }.merge(options) @logger.info "Sending request to #{url.inspect} with body #{body}" response = http_engine.request(method, url, body, headers, options) @logger.info "Received response #{response}" return response_parser.parse(response) end - if error - errors = response_parser.parse(error)['response']['status']['errors'] - err_string = errors.collect { |e| e['message'] }.join(', ') - @logger.error "Error response from server: #{err_string}" - raise Animoto::Error.new(err_string) + if code + if body.empty? + @logger.error "HTTP error (#{code})" + raise Animoto::Error.new("HTTP error (#{code})") + else + errors = response_parser.parse(body)['response']['status']['errors'] + err_string = errors.collect { |e| e['message'] }.join(', ') + @logger.error "Error response from server: #{err_string}" + raise Animoto::Error.new(err_string) + end else @logger.error "Error sending request to #{url.inspect}" raise Animoto::Error end rescue NoMethodError => e @logger.error e.inspect raise Animoto::Error.new("Invalid response (#{e.inspect})") end # Creates the full content type string given a Resource class or instance - # @param [Class,ContentType] klass_or_instance the class or instance to build the + # @param [Class,Support::ContentType] klass_or_instance the class or instance to build the # content type for # @return [String] the full content type with the version and format included (i.e. # "application/vnd.animoto.storyboard-v1+json") def content_type_of klass_or_instance klass = klass_or_instance.is_a?(Class) ? klass_or_instance : klass_or_instance.class \ No newline at end of file