panda 0.6.4 → 1.0.0

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