smartfm 0.3.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (40) hide show
  1. data/ChangeLog +38 -0
  2. data/README +45 -0
  3. data/Rakefile +155 -0
  4. data/examples/pure_ruby.rb +118 -0
  5. data/lib/ext/hash.rb +52 -0
  6. data/lib/smartfm.rb +16 -0
  7. data/lib/smartfm/core.rb +3 -0
  8. data/lib/smartfm/core/auth.rb +39 -0
  9. data/lib/smartfm/core/config.rb +51 -0
  10. data/lib/smartfm/core/version.rb +14 -0
  11. data/lib/smartfm/model.rb +5 -0
  12. data/lib/smartfm/model/base.rb +26 -0
  13. data/lib/smartfm/model/item.rb +174 -0
  14. data/lib/smartfm/model/list.rb +138 -0
  15. data/lib/smartfm/model/sentence.rb +118 -0
  16. data/lib/smartfm/model/user.rb +108 -0
  17. data/lib/smartfm/rest_client.rb +8 -0
  18. data/lib/smartfm/rest_client/base.rb +173 -0
  19. data/lib/smartfm/rest_client/item.rb +14 -0
  20. data/lib/smartfm/rest_client/list.rb +15 -0
  21. data/lib/smartfm/rest_client/sentence.rb +12 -0
  22. data/lib/smartfm/rest_client/user.rb +13 -0
  23. data/spec/ext/hash_spec.rb +11 -0
  24. data/spec/smartfm/core/auth_spec.rb +39 -0
  25. data/spec/smartfm/core/config_spec.rb +34 -0
  26. data/spec/smartfm/core/version_spec.rb +19 -0
  27. data/spec/smartfm/model/base_spec.rb +40 -0
  28. data/spec/smartfm/model/item_spec.rb +41 -0
  29. data/spec/smartfm/model/list_spec.rb +7 -0
  30. data/spec/smartfm/model/sentence_spec.rb +7 -0
  31. data/spec/smartfm/model/user_spec.rb +90 -0
  32. data/spec/smartfm/rest_client/base_spec.rb +9 -0
  33. data/spec/smartfm/rest_client/item_spec.rb +7 -0
  34. data/spec/smartfm/rest_client/list_spec.rb +7 -0
  35. data/spec/smartfm/rest_client/sentence_spec.rb +7 -0
  36. data/spec/smartfm/rest_client/user_spec.rb +7 -0
  37. data/spec/spec_helper.rb +18 -0
  38. data/test/smartfm_test.rb +8 -0
  39. data/test/test_helper.rb +3 -0
  40. metadata +132 -0
@@ -0,0 +1,108 @@
1
+ class Smartfm::User < Smartfm::Base
2
+ ATTRIBUTES = [:username, :profile]
3
+ attr_reader *ATTRIBUTES
4
+
5
+ class Profile < Smartfm::Base
6
+ ATTRIBUTES = [:name, :gender, :birthday, :description, :blog_url, :profile_url, :foaf_url, :icon_url]
7
+ attr_reader *ATTRIBUTES
8
+
9
+ def initialize(params = {})
10
+ @name = params[:name]
11
+ @gender = params[:gender]
12
+ @birthday = (Date.parse(params[:birthday]) rescue nil)
13
+ @description = params[:description]
14
+ @blog_url = params[:blog_url]
15
+ @profile_url = params[:profile_url]
16
+ @foaf_url = params[:foaf_url]
17
+ @icon_url = params[:icon_url]
18
+ end
19
+ end
20
+
21
+ class Study < Smartfm::Base
22
+ ATTRIBUTES = [:today, :results, :total_summary]
23
+ attr_reader *ATTRIBUTES
24
+
25
+ class Result < Smartfm::Base
26
+ ATTRIBUTES = [:timestamp, :seconds, :totals, :seen, :completed, :date]
27
+ attr_reader *ATTRIBUTES
28
+
29
+ def initialize(params = {})
30
+ @timestamp = (params[:timestamp].to_i rescue nil)
31
+ @seconds = (params[:seconds].to_i rescue nil)
32
+ @totals = {
33
+ :seconds => (params[:totals][:seconds].to_i rescue nil),
34
+ :seen => (params[:totals][:seen].to_i rescue nil),
35
+ :completed => (params[:totals][:completed].to_i rescue nil)
36
+ }
37
+ @seen = (params[:seen].to_i rescue nil)
38
+ @completed = (params[:completed].to_i rescue nil)
39
+ @date = (Date.parse(params[:date]) rescue nil)
40
+ end
41
+ end
42
+
43
+ class TotalSummary < Smartfm::Base
44
+ ATTRIBUTES = [:studied, :completed, :performance, :best_speed, :best_score]
45
+ attr_reader *ATTRIBUTES
46
+
47
+ def initialize(params = {})
48
+ @studied = params[:studied]
49
+ @completed = params[:completed]
50
+ @performance = params[:performance]
51
+ @best_speed = params[:best_speed]
52
+ @best_score = params[:best_score]
53
+ end
54
+ end
55
+
56
+ def initialize(params = {})
57
+ @today = (Date.parse(params[:today]) rescue nil)
58
+ @results = self.deserialize(params[:study_results], :as => Smartfm::User::Study::Result)
59
+ @total_summary = self.deserialize(params[:total_summary], :as => Smartfm::User::Study::TotalSummary)
60
+ end
61
+
62
+ end
63
+
64
+ def self.find(username, params = {})
65
+ params[:username] = username
66
+ hash = Smartfm::RestClient::User.find(params)
67
+ self.deserialize(hash)
68
+ end
69
+
70
+ def self.matching(keyword, params = {})
71
+ params[:keyword] = keyword
72
+ hash = Smartfm::RestClient::User.matching(params)
73
+ self.deserialize(hash) || []
74
+ end
75
+
76
+ def initialize(params)
77
+ @profile = Profile.new(params[:profile])
78
+ @username = params[:username]
79
+ end
80
+
81
+ def items(params = {})
82
+ hash = Smartfm::RestClient::User.items(params.merge(:username => self.username))
83
+ self.deserialize(hash, :as => Smartfm::Item) || []
84
+ end
85
+
86
+ def lists(params = {})
87
+ hash = Smartfm::RestClient::User.lists(params.merge(:username => self.username))
88
+ self.deserialize(hash, :as => Smartfm::List) || []
89
+ end
90
+
91
+ def friends(params = {})
92
+ hash = Smartfm::RestClient::User.friends(params.merge(:username => self.username))
93
+ self.deserialize(hash) || []
94
+ end
95
+
96
+ def followers(params = {})
97
+ hash = Smartfm::RestClient::User.followers(params.merge(:username => self.username))
98
+ self.deserialize(hash) || []
99
+ end
100
+
101
+ def study(params = {})
102
+ params[:application] ||= 'iknow'
103
+ return nil unless ['iknow', 'dictation', 'brainspeed', ].include?(params[:application])
104
+ hash = Smartfm::RestClient::User.study_results(params.merge(:username => self.username))
105
+ self.deserialize(hash, :as => Smartfm::User::Study)
106
+ end
107
+
108
+ end
@@ -0,0 +1,8 @@
1
+ module Smartfm::RestClient
2
+ end
3
+
4
+ require 'smartfm/rest_client/base'
5
+ require 'smartfm/rest_client/user'
6
+ require 'smartfm/rest_client/list'
7
+ require 'smartfm/rest_client/item'
8
+ require 'smartfm/rest_client/sentence'
@@ -0,0 +1,173 @@
1
+ require 'timeout'
2
+
3
+ class Smartfm::RestClient::Base
4
+
5
+ class RESTError < Exception
6
+ attr_accessor :code, :message
7
+
8
+ def initialize(params = {})
9
+ self.code = params[:code]
10
+ self.message = params[:message]
11
+ end
12
+
13
+ def to_s
14
+ "HTTP #{@code}: #{@message}"
15
+ end
16
+ end
17
+
18
+ def self.valid_action?(action) ; self::ACTIONS.keys.include? action.to_sym end
19
+ def self.path(action) ; self::ACTIONS[action.to_sym][:path] end
20
+ def self.http_method(action) ; self::ACTIONS[action.to_sym][:http_method] || :get end
21
+
22
+ def self.method_missing(action, *args)
23
+ # GET methods are handled here
24
+ # POST and DELETE methods should be implemented in each class
25
+ super unless self.valid_action?(action)
26
+ case self.http_method(action)
27
+ when :get
28
+ path, params = path_with_params(self.path(action), args[0])
29
+ http_get(path, params)
30
+ when :post
31
+ path, params = path_with_params(self.path(action), args[1])
32
+ http_post(auth(args[0]), path, params)
33
+ when :delete
34
+ path, params = path_with_params(self.path(action), args[1])
35
+ http_delete(auth(args[0]), path, params)
36
+ end
37
+ end
38
+
39
+ private
40
+
41
+ def self.config; Smartfm::Config end
42
+
43
+ def self.auth(auth)
44
+ if auth.is_a?(Smartfm::Auth)
45
+ auth
46
+ else
47
+ raise ArgumentError.new("Authorized Smartfm::Auth instance is required")
48
+ end
49
+ end
50
+
51
+ def self.api_key_required
52
+ raise ArgumentError.new("smart.fm API key is required") if self.config.api_key == ''
53
+ end
54
+
55
+ def self.raise_rest_error(response)
56
+ raise RESTError.new(:code => response.code, :message => response.message)
57
+ end
58
+
59
+ def self.handle_rest_response(response, format)
60
+ raise_rest_error(response) unless response.is_a?(Net::HTTPSuccess)
61
+ case format
62
+ when :json
63
+ handle_json_response(response.body)
64
+ when :nothing
65
+ # success => nothing / failure => json error
66
+ begin
67
+ handle_json_response(response.body)
68
+ rescue Exception => e
69
+ e.is_a?(RESTError) ? raise(e) : :success
70
+ end
71
+ else
72
+ begin
73
+ handle_json_response(response.body)
74
+ rescue Exception => e
75
+ e.is_a?(RESTError) ? raise(e) : response.body
76
+ end
77
+ end
78
+ end
79
+
80
+ def self.handle_json_response(json_response)
81
+ hash = JSON.parse(json_response)
82
+ unless (hash['error'].nil? rescue :success) # success response may be Array, not Hash.
83
+ if hash['error']['code'] == 404
84
+ return nil
85
+ else
86
+ raise RESTError.new(:code => hash['error']['code'], :message => hash['error']['message'])
87
+ end
88
+ end
89
+ hash
90
+ end
91
+
92
+ def self.http_header
93
+ @@http_header ||= {
94
+ 'User-Agent' => "#{self.config.application_name} v#{Smartfm::Version.to_version} [#{self.config.user_agent}]",
95
+ 'Accept' => 'text/x-json',
96
+ 'X-smartfm-Gem-Client' => self.config.application_name,
97
+ 'X-smartfm-Gem-Client-Version' => self.config.application_version,
98
+ 'X-smartfm-Gem-Client-URL' => self.config.application_url,
99
+ }
100
+ end
101
+
102
+ def self.http_connect
103
+ http = Net::HTTP.new(self.config.api_host, self.config.api_port)
104
+ http.start do |conn|
105
+ request, format = yield
106
+ begin
107
+ timeout(self.config.timeout) do
108
+ response = conn.request(request)
109
+ handle_rest_response(response, format)
110
+ end
111
+ rescue
112
+ raise RESTError.new(:code => 408, :message => "smart.fm Gem Timeout (#{self.config.timeout} [sec])")
113
+ end
114
+ end
115
+ end
116
+
117
+ def self.path_with_params(path, params = {})
118
+ path_with_params = path.clone
119
+ unless params.empty?
120
+ params.each do |key, value|
121
+ if path_with_params=~/__#{key}__/
122
+ path_with_params.sub!(/__#{key}__/, URI.encode(value.to_s))
123
+ params.delete(key)
124
+ end
125
+ end
126
+ end
127
+ return path_with_params, params
128
+ end
129
+
130
+ def self.http_get(path, params = {})
131
+ http_connect do
132
+ params.merge!(:api_key => self.config.api_key) unless self.config.api_key == ''
133
+ path = (params.size > 0) ? "#{path}?#{params.to_http_str}" : path
134
+ get_req = Net::HTTP::Get.new(path, http_header)
135
+ [get_req, :json]
136
+ end
137
+ end
138
+
139
+ def self.http_post(auth, path, params = {})
140
+ self.api_key_required
141
+ params.merge!(:api_key => self.config.api_key)
142
+ case auth.mode
143
+ when :oauth
144
+ response = auth.auth_token.post(path, params, http_header)
145
+ handle_rest_response(response, :text)
146
+ when :basic_auth
147
+ http_connect do
148
+ post_req = Net::HTTP::Post.new(path, http_header)
149
+ post_req.body = params.to_http_str
150
+ post_req.basic_auth(auth.account.username, auth.account.password)
151
+ [post_req, :text]
152
+ end
153
+ end
154
+ end
155
+
156
+ def self.http_delete(auth, path, params = {})
157
+ self.api_key_required
158
+ params.merge!(:api_key => self.config.api_key)
159
+ case auth.mode
160
+ when :oauth
161
+ response = auth.auth_token.delete(path, params.stringfy_keys!.stringfy_values!)
162
+ handle_rest_response(response, :nothing)
163
+ when :basic_auth
164
+ http_connect do
165
+ delete_req = Net::HTTP::Post.new(path, http_header)
166
+ delete_req.body = params.merge(:_method => 'DELETE').to_http_str
167
+ delete_req.basic_auth(auth.account.username, auth.account.password)
168
+ [delete_req, :nothing]
169
+ end
170
+ end
171
+ end
172
+
173
+ end
@@ -0,0 +1,14 @@
1
+ class Smartfm::RestClient::Item < Smartfm::RestClient::Base
2
+
3
+ ACTIONS = {
4
+ :recent => { :path => '/items' },
5
+ :find => { :path => '/items/__id__' },
6
+ :matching => { :path => '/items/matching/__keyword__' },
7
+ :extract => { :path => '/items/extract', },
8
+ :create => { :path => '/items', :http_method => :post },
9
+ :add_image => { :path => '/items/__id__/images', :http_method => :post },
10
+ :add_sound => { :path => '/items/__id__/sounds', :http_method => :post },
11
+ :add_tags => { :path => '/items/__id__/tags', :http_method => :post }
12
+ }
13
+
14
+ end
@@ -0,0 +1,15 @@
1
+ class Smartfm::RestClient::List < Smartfm::RestClient::Base
2
+
3
+ ACTIONS = {
4
+ :recent => { :path => '/lists' },
5
+ :find => { :path => '/lists/__id__' },
6
+ :items => { :path => '/lists/__id__/items' },
7
+ :sentences => { :path => '/lists/__id__/sentences' },
8
+ :matching => { :path => '/lists/matching/__keyword__' },
9
+ :create => { :path => '/lists', :http_method => :post },
10
+ :delete => { :path => '/lists/__id__', :http_method => :delete },
11
+ :add_item => { :path => '/lists/__list_id__/items', :http_method => :post },
12
+ :delete_item => { :path => '/lists/__list_id__/items/__id__', :http_method => :delete }
13
+ }
14
+
15
+ end
@@ -0,0 +1,12 @@
1
+ class Smartfm::RestClient::Sentence < Smartfm::RestClient::Base
2
+
3
+ ACTIONS = {
4
+ :recent => { :path => '/sentences' },
5
+ :find => { :path => '/sentences/__id__' },
6
+ :matching => { :path => '/sentences/matching/__keyword__' },
7
+ :create => { :path => '/sentences', :http_method => :post },
8
+ :add_image => { :path => '/sentences/__id__/images', :http_method => :post },
9
+ :add_sound => { :path => '/sentences/__id__/sounds', :http_method => :post }
10
+ }
11
+
12
+ end
@@ -0,0 +1,13 @@
1
+ class Smartfm::RestClient::User < Smartfm::RestClient::Base
2
+
3
+ ACTIONS = {
4
+ :find => { :path => '/users/__username__' },
5
+ :lists => { :path => '/users/__username__/lists' },
6
+ :items => { :path => '/users/__username__/items' },
7
+ :friends => { :path => '/users/__username__/friends' },
8
+ :followers => { :path => '/users/__username__/followers' },
9
+ :study_results => { :path => '/users/__username__/study_results/__application__' },
10
+ :matching => { :path => '/users/matching/__keyword__' }
11
+ }
12
+
13
+ end
@@ -0,0 +1,11 @@
1
+ require File.join(File.dirname(__FILE__), '..', 'spec_helper')
2
+
3
+ describe Hash, "#to_http_str" do
4
+ before do
5
+ @params = {:page => 3, :per_page => 100}
6
+ end
7
+ it "should return http_parameter as String" do
8
+ @params.to_http_str.should be_a(String)
9
+ @params.to_http_str.should match(/^(page=3&per_page=100|per_page=100&page=3)$/)
10
+ end
11
+ end
@@ -0,0 +1,39 @@
1
+ require File.join(File.dirname(__FILE__), '..', '..', 'spec_helper')
2
+
3
+ basic_auth_params = { :username => 'matake', :password => 'password' }
4
+ oauth_params = { :token => '1a2b3c4d5', :secret => 'z0y9x8w7v6' }
5
+
6
+ describe Smartfm::Auth, '.new' do
7
+ before do
8
+ @basic_auth = Smartfm::Auth.new(basic_auth_params)
9
+ @oauth = Smartfm::Auth.new(oauth_params)
10
+ end
11
+
12
+ it "should choose #{:basic_auth} mode" do
13
+ @basic_auth.mode.should equal(:basic_auth)
14
+ @basic_auth.should respond_to(:account)
15
+ @basic_auth.account.should respond_to(:username)
16
+ @basic_auth.account.should respond_to(:password)
17
+ @basic_auth.account.should_not respond_to(:token)
18
+ @basic_auth.account.should_not respond_to(:secret)
19
+ end
20
+
21
+ it "should choose #{:oauth} mode" do
22
+ @oauth.mode.should equal(:oauth)
23
+ @oauth.should respond_to(:auth_token)
24
+ @oauth.auth_token.should respond_to(:token)
25
+ @oauth.auth_token.should respond_to(:secret)
26
+ @oauth.auth_token.should_not respond_to(:username)
27
+ @oauth.auth_token.should_not respond_to(:password)
28
+ end
29
+ end
30
+
31
+ describe Smartfm::Auth, '.consumer' do
32
+ it "should be an instance of OAuth::Consumer" do
33
+ Smartfm::Auth.consumer.should be_a(OAuth::Consumer)
34
+ end
35
+
36
+ it "should be for http://api.smart.fm" do
37
+ Smartfm::Auth.consumer.site.should eql("http://api.smart.fm")
38
+ end
39
+ end
@@ -0,0 +1,34 @@
1
+ require File.join(File.dirname(__FILE__), '..', '..', 'spec_helper')
2
+
3
+ describe Smartfm::Config, ".init" do
4
+ before do
5
+ Smartfm::Config.init
6
+ end
7
+
8
+ it "should return init config" do
9
+ Smartfm::Config.init.should equal(Smartfm::Config.instance)
10
+ end
11
+
12
+ it "should change config with block" do
13
+ Smartfm::Config.init do |conf|
14
+ Smartfm::Config::ATTRIBUTES.each do |method|
15
+ conf.send("#{method}=", "fuckin_windows!")
16
+ end
17
+ end
18
+ Smartfm::Config::ATTRIBUTES.each do |method|
19
+ Smartfm::Config.send("#{method}").should eql("fuckin_windows!")
20
+ end
21
+ end
22
+
23
+ after do
24
+ Smartfm::Config.init
25
+ end
26
+ end
27
+
28
+ (Smartfm::Config::ATTRIBUTES + [:base_url, :api_base_url]).each do |method|
29
+ describe Smartfm::Config, ".#{method}" do
30
+ it "should be same with Smartfm::Config.instance.#{method}" do
31
+ Smartfm::Config.send(method).should eql(Smartfm::Config.instance.send(method))
32
+ end
33
+ end
34
+ end
@@ -0,0 +1,19 @@
1
+ require File.join(File.dirname(__FILE__), '..', '..', 'spec_helper')
2
+
3
+ version_params = [Smartfm::Version::MAJOR, Smartfm::Version::MINOR, Smartfm::Version::REVISION]
4
+ expected = {
5
+ :version => version_params.join('.'),
6
+ :name => version_params.join('_')
7
+ }
8
+
9
+ describe Smartfm::Version, ".to_version" do
10
+ it "should return #{expected[:version]}" do
11
+ Smartfm::Version.to_version.should eql(expected[:version])
12
+ end
13
+ end
14
+
15
+ describe Smartfm::Version, ".to_name" do
16
+ it "should return #{expected[:name]}" do
17
+ Smartfm::Version.to_name.should eql(expected[:name])
18
+ end
19
+ end