smartfm 0.3.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/ChangeLog +38 -0
- data/README +45 -0
- data/Rakefile +155 -0
- data/examples/pure_ruby.rb +118 -0
- data/lib/ext/hash.rb +52 -0
- data/lib/smartfm.rb +16 -0
- data/lib/smartfm/core.rb +3 -0
- data/lib/smartfm/core/auth.rb +39 -0
- data/lib/smartfm/core/config.rb +51 -0
- data/lib/smartfm/core/version.rb +14 -0
- data/lib/smartfm/model.rb +5 -0
- data/lib/smartfm/model/base.rb +26 -0
- data/lib/smartfm/model/item.rb +174 -0
- data/lib/smartfm/model/list.rb +138 -0
- data/lib/smartfm/model/sentence.rb +118 -0
- data/lib/smartfm/model/user.rb +108 -0
- data/lib/smartfm/rest_client.rb +8 -0
- data/lib/smartfm/rest_client/base.rb +173 -0
- data/lib/smartfm/rest_client/item.rb +14 -0
- data/lib/smartfm/rest_client/list.rb +15 -0
- data/lib/smartfm/rest_client/sentence.rb +12 -0
- data/lib/smartfm/rest_client/user.rb +13 -0
- data/spec/ext/hash_spec.rb +11 -0
- data/spec/smartfm/core/auth_spec.rb +39 -0
- data/spec/smartfm/core/config_spec.rb +34 -0
- data/spec/smartfm/core/version_spec.rb +19 -0
- data/spec/smartfm/model/base_spec.rb +40 -0
- data/spec/smartfm/model/item_spec.rb +41 -0
- data/spec/smartfm/model/list_spec.rb +7 -0
- data/spec/smartfm/model/sentence_spec.rb +7 -0
- data/spec/smartfm/model/user_spec.rb +90 -0
- data/spec/smartfm/rest_client/base_spec.rb +9 -0
- data/spec/smartfm/rest_client/item_spec.rb +7 -0
- data/spec/smartfm/rest_client/list_spec.rb +7 -0
- data/spec/smartfm/rest_client/sentence_spec.rb +7 -0
- data/spec/smartfm/rest_client/user_spec.rb +7 -0
- data/spec/spec_helper.rb +18 -0
- data/test/smartfm_test.rb +8 -0
- data/test/test_helper.rb +3 -0
- 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,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
|