panda 0.6.4 → 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
data/Rakefile CHANGED
@@ -10,10 +10,7 @@ begin
10
10
  gem.email = "info@pandastream.com"
11
11
  gem.homepage = "http://github.com/newbamboo/panda_gem"
12
12
  gem.authors = ["New Bamboo"]
13
- gem.add_development_dependency "rspec", ">= 1.2.9"
14
- gem.add_development_dependency "webmock"
15
- # gem is a Gem::Specification... see http://www.rubygems.org/read/chapter/20 for additional settings
16
- gem.add_dependency "ruby-hmac", ">= 0.3.2"
13
+ gem.add_dependency "ruby-hmac", ">= 0.3.2"
17
14
  gem.add_dependency "rest-client", ">= 1.4"
18
15
  gem.add_dependency "json", ">= 1.2"
19
16
  end
@@ -22,19 +19,9 @@ rescue LoadError
22
19
  puts "Jeweler (or a dependency) not available. Install it with: sudo gem install jeweler"
23
20
  end
24
21
 
25
- require 'spec/rake/spectask'
26
- Spec::Rake::SpecTask.new(:spec) do |spec|
27
- spec.libs << 'lib' << 'spec'
28
- spec.spec_files = FileList['spec/**/*_spec.rb']
29
- spec.ruby_opts = ['-rrubygems']
22
+ desc "Run all the specs"
23
+ task :spec do
24
+ system "bundle exec spec -O spec/spec.opts spec"
30
25
  end
31
26
 
32
- Spec::Rake::SpecTask.new(:rcov) do |spec|
33
- spec.libs << 'lib' << 'spec'
34
- spec.pattern = 'spec/**/*_spec.rb'
35
- spec.rcov = true
36
- end
37
-
38
- task :spec => :check_dependencies
39
-
40
27
  task :default => :spec
data/VERSION CHANGED
@@ -1 +1 @@
1
- 0.6.4
1
+ 1.0.0
data/lib/panda.rb CHANGED
@@ -1,2 +1,22 @@
1
1
  require 'panda/api_authentication'
2
- require 'panda/panda'
2
+ require 'panda/connection'
3
+ require 'panda/modules/router'
4
+ require 'panda/modules/finders'
5
+ require 'panda/modules/builders'
6
+ require 'panda/modules/associations'
7
+ require 'panda/modules/updatable'
8
+ require 'panda/modules/short_status'
9
+ require 'panda/modules/cloud_connection'
10
+ require 'panda/proxies/proxy'
11
+ require 'panda/proxies/scope'
12
+ require 'panda/proxies/encoding_scope'
13
+ require 'panda/proxies/video_scope'
14
+ require 'panda/proxies/profile_scope'
15
+ require 'panda/error'
16
+ require 'panda/base'
17
+ require 'panda/resources/resource'
18
+ require 'panda/resources/cloud'
19
+ require 'panda/resources/encoding'
20
+ require 'panda/resources/profile'
21
+ require 'panda/resources/video'
22
+ require 'panda/panda'
@@ -5,19 +5,19 @@ require 'hmac'
5
5
  require 'hmac-sha2'
6
6
  require 'base64'
7
7
 
8
- class Panda
8
+ module Panda
9
9
  class ApiAuthentication
10
10
  def self.generate_signature(verb, request_uri, host, secret_key, params_given={})
11
11
  # Ensure all param keys are strings
12
12
  params = {}; params_given.each {|k,v| params[k.to_s] = v }
13
-
13
+
14
14
  query_string = canonical_querystring(params)
15
-
15
+
16
16
  string_to_sign = verb.to_s.upcase + "\n" +
17
17
  host.downcase + "\n" +
18
18
  request_uri + "\n" +
19
19
  query_string
20
-
20
+
21
21
  hmac = HMAC::SHA256.new( secret_key )
22
22
  hmac.update( string_to_sign )
23
23
  # chomp is important! the base64 encoded version will have a newline at the end
data/lib/panda/base.rb ADDED
@@ -0,0 +1,102 @@
1
+ module Panda
2
+ class Base
3
+ attr_accessor :attributes, :errors
4
+ include Panda::Router
5
+
6
+ def initialize(attributes = {})
7
+ init_load
8
+ load(attributes)
9
+ end
10
+
11
+ class << self
12
+ def id(this_id)
13
+ find(this_id)
14
+ end
15
+
16
+ def end_class_name
17
+ "#{name.split('::').last}"
18
+ end
19
+ end
20
+
21
+ def changed?
22
+ !@changed_attributes.empty?
23
+ end
24
+
25
+ def new?
26
+ id.nil?
27
+ end
28
+
29
+ def delete
30
+ response = connection.delete(object_url_map(self.class.one_path))
31
+ !!response['deleted']
32
+ end
33
+
34
+ def id
35
+ attributes['id']
36
+ end
37
+
38
+ def id=(id)
39
+ attributes['id'] = id
40
+ end
41
+
42
+ def reload
43
+ perform_reload
44
+ self
45
+ end
46
+
47
+ def to_json
48
+ attributes.to_json
49
+ end
50
+
51
+ private
52
+
53
+ def perform_reload(args={})
54
+ raise "RecordNotFound" if new?
55
+
56
+ url = self.class.object_url(self.class.one_path, :id => id)
57
+ response = connection.get(url)
58
+ init_load
59
+ load_response(response.merge(args))
60
+ end
61
+
62
+ def init_load
63
+ @attributes = {}
64
+ @changed_attributes = {}
65
+ @errors = []
66
+ end
67
+
68
+ def load(attributes)
69
+ attributes.each do |key, value|
70
+ @attributes[key.to_s] = value
71
+ @changed_attributes[key.to_s] = value if !(attributes['id'] || attributes[:id])
72
+ end
73
+ true
74
+ end
75
+
76
+ def load_response(response)
77
+ if response['error'] || response['id'].nil?
78
+ !(@errors << Error.new(response))
79
+ else
80
+ @errors=[]
81
+ load(response)
82
+ end
83
+ end
84
+
85
+ def method_missing(method_symbol, *arguments)
86
+ method_name = method_symbol.to_s
87
+ if method_name =~ /(=|\?)$/
88
+ case $1
89
+ when '='
90
+ attributes[$`] = arguments.first
91
+ @changed_attributes[$`] = arguments.first
92
+ when '?'
93
+ !! attributes[$`]
94
+ end
95
+ else
96
+ return attributes[method_name] if attributes.include?(method_name)
97
+ super
98
+ end
99
+ end
100
+
101
+ end
102
+ end
@@ -0,0 +1,189 @@
1
+ module Panda
2
+ class Connection
3
+ attr_accessor :api_host, :api_port, :access_key, :secret_key, :api_version, :cloud_id, :format
4
+
5
+ API_PORT=80
6
+ US_API_HOST="api.pandastream.com"
7
+ EU_API_HOST="api.eu.pandastream.com"
8
+
9
+ def initialize(auth_params={}, options={})
10
+ @raise_error = false
11
+ @api_version = 2
12
+ @format = "hash"
13
+
14
+ if auth_params.class == String
15
+ self.format = options[:format] || options["format"]
16
+ init_from_uri(auth_params)
17
+ else
18
+ self.format = auth_params[:format] || auth_params["format"]
19
+ init_from_hash(auth_params)
20
+ end
21
+ end
22
+
23
+ # Set the correct api_host for US/EU
24
+ def region=(region)
25
+ if(region.to_s == "us")
26
+ self.api_host = US_API_HOST
27
+ elsif(region.to_s == "eu")
28
+ self.api_host = EU_API_HOST
29
+ else
30
+ raise "Region Unknown"
31
+ end
32
+ end
33
+
34
+ # Setup connection for Heroku
35
+ def heroku=(url)
36
+ init_from_uri(url)
37
+ end
38
+
39
+ # Raise exception on non JSON parsable response if set
40
+ def raise_error=(bool)
41
+ @raise_error = bool
42
+ end
43
+
44
+ # Setup respond type JSON / Hash
45
+ def format=(ret_format)
46
+ if ret_format
47
+ raise "Format unknown" if !["json", "hash"].include?(ret_format.to_s)
48
+ @format = ret_format.to_s
49
+ end
50
+ end
51
+
52
+ # Authenticated requests
53
+
54
+ def get(request_uri, params={})
55
+ @connection = RestClient::Resource.new(api_url)
56
+ rescue_restclient_exception do
57
+ query = signed_query("GET", request_uri, params)
58
+ body_of @connection[request_uri + '?' + query].get
59
+ end
60
+ end
61
+
62
+ def post(request_uri, params={})
63
+ @connection = RestClient::Resource.new(api_url)
64
+ rescue_restclient_exception do
65
+ body_of @connection[request_uri].post(signed_params("POST", request_uri, params))
66
+ end
67
+ end
68
+
69
+ def put(request_uri, params={})
70
+ @connection = RestClient::Resource.new(api_url)
71
+ rescue_restclient_exception do
72
+ body_of @connection[request_uri].put(signed_params("PUT", request_uri, params))
73
+ end
74
+ end
75
+
76
+ def delete(request_uri, params={})
77
+ @connection = RestClient::Resource.new(api_url)
78
+ rescue_restclient_exception do
79
+ query = signed_query("DELETE", request_uri, params)
80
+ body_of @connection[request_uri + '?' + query].delete
81
+ end
82
+ end
83
+
84
+ # Signing methods
85
+ def signed_query(*args)
86
+ ApiAuthentication.hash_to_query(signed_params(*args))
87
+ end
88
+
89
+ def signed_params(verb, request_uri, params = {}, timestamp_str = nil)
90
+ auth_params = stringify_keys(params)
91
+ auth_params['cloud_id'] = @cloud_id
92
+ auth_params['access_key'] = @access_key
93
+ auth_params['timestamp'] = timestamp_str || Time.now.iso8601(6)
94
+
95
+ params_to_sign = auth_params.reject{|k,v| ['file'].include?(k.to_s)}
96
+ auth_params['signature'] = ApiAuthentication.generate_signature(verb, request_uri, @api_host, @secret_key, params_to_sign)
97
+ auth_params
98
+ end
99
+
100
+ def api_url
101
+ "http://#{@api_host}:#{@api_port}/#{@prefix}"
102
+ end
103
+
104
+ # Shortcut to setup your bucket
105
+ def setup_bucket(params={})
106
+ granting_params = {
107
+ :s3_videos_bucket => params[:bucket],
108
+ :user_aws_key => params[:access_key],
109
+ :user_aws_secret => params[:secret_key]
110
+ }
111
+
112
+ put("/clouds/#{@cloud_id}.json", granting_params)
113
+ end
114
+
115
+ def to_hash
116
+ hash = {}
117
+ [:api_host, :api_port, :access_key, :secret_key, :api_version, :cloud_id].each do |a|
118
+ hash[a] = send(a)
119
+ end
120
+ hash
121
+ end
122
+
123
+ private
124
+ def stringify_keys(params)
125
+ params.inject({}) do |options, (key, value)|
126
+ options[key.to_s] = value
127
+ options
128
+ end
129
+ end
130
+
131
+ def rescue_restclient_exception(&block)
132
+ begin
133
+ yield
134
+ rescue RestClient::Exception => e
135
+ format_to(e.http_body)
136
+ end
137
+ end
138
+
139
+ # API change on rest-client 1.4
140
+ def body_of(response)
141
+ json_response = response.respond_to?(:body) ? response.body : response
142
+ format_to(json_response)
143
+ end
144
+
145
+ def format_to(response)
146
+ begin
147
+ if self.format == "json"
148
+ response
149
+ elsif defined?(ActiveSupport::JSON)
150
+ ActiveSupport::JSON.decode(response)
151
+ else
152
+ JSON.parse(response)
153
+ end
154
+ rescue JSON::ParserError => e
155
+ # if not used with PandaResources
156
+ # don't raise Service Not Available because
157
+ # maybe the host, the url, or anything is wrongly setup
158
+ if @raise_error
159
+ raise ServiceNotAvailable.new
160
+ else
161
+ raise e
162
+ end
163
+ end
164
+ end
165
+
166
+ def init_from_uri(uri)
167
+ heroku_uri = URI.parse(uri)
168
+
169
+ @access_key = heroku_uri.user
170
+ @secret_key = heroku_uri.password
171
+ @cloud_id = heroku_uri.path[1..-1]
172
+ @api_host = heroku_uri.host
173
+ @api_port = heroku_uri.port
174
+ @prefix = "v#{@api_version}"
175
+ end
176
+
177
+ def init_from_hash(hash_params)
178
+ params = { :api_host => US_API_HOST, :api_port => API_PORT }.merge!(hash_params)
179
+
180
+ @cloud_id = params["cloud_id"] || params[:cloud_id]
181
+ @access_key = params["access_key"] || params[:access_key]
182
+ @secret_key = params["secret_key"] || params[:secret_key]
183
+ @api_host = params["api_host"] || params[:api_host]
184
+ @api_port = params["api_port"] || params[:api_port]
185
+ @prefix = params["prefix_url"] || "v#{@api_version}"
186
+ end
187
+ end
188
+ end
189
+
@@ -0,0 +1,29 @@
1
+ module Panda
2
+ class Error
3
+ attr_reader :message
4
+ attr_reader :error_class
5
+ attr_reader :original_hash
6
+
7
+ def initialize(options)
8
+ @original_hash = options
9
+ @message = options['message']
10
+ @error_class = options['error']
11
+ end
12
+
13
+ def raise!
14
+ raise(self.to_s)
15
+ end
16
+
17
+ def to_s
18
+ "#{@error_class}: #{@message}"
19
+ end
20
+
21
+ end
22
+
23
+ class ServiceNotAvailable < StandardError
24
+ def initialize
25
+ super("ServiceNotAvailable")
26
+ end
27
+ end
28
+
29
+ end
@@ -0,0 +1,55 @@
1
+ module Panda
2
+ module Associations
3
+ def self.included(base)
4
+ base.extend(ClassMethods)
5
+ end
6
+
7
+ module ClassMethods
8
+
9
+ def has_one(relation_name)
10
+ # for example creates : @video ||= VideoScope.new(self)
11
+
12
+ define_method relation_name do
13
+ param_id = "#{relation_name}_id"
14
+ if instance_var = instance_variable_get("@#{relation_name}")
15
+ instance_var
16
+ else
17
+ @associations ||= []
18
+ @associations << relation_name
19
+ instance_variable_set("@#{relation_name}",
20
+ Panda::const_get(relation_name.to_s.capitalize).find(send(param_id.to_sym)))
21
+ end
22
+ end
23
+ end
24
+
25
+ def has_many(relation_name)
26
+ # for example creates : @encodings ||= EncodingScope.new(self)
27
+
28
+ define_method relation_name do
29
+ model_name = "#{relation_name.to_s[0..-2].capitalize}"
30
+ if instance_var = instance_variable_get("@#{relation_name}")
31
+ instance_var
32
+ else
33
+ @associations ||= []
34
+ @associations << relation_name
35
+ instance_variable_set("@#{relation_name}",
36
+ Panda::const_get("#{model_name}Scope").new(self))
37
+ end
38
+ end
39
+ end
40
+
41
+ alias :belongs_to :has_one
42
+
43
+ end
44
+
45
+ private
46
+ def reset_associations
47
+ if @associations
48
+ @associations.each do |a|
49
+ instance_variable_set("@#{a}",nil)
50
+ end
51
+ end
52
+ end
53
+
54
+ end
55
+ end