opera-mobile-store-sdk 0.1.1 → 0.1.2
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/lib/opera-mobile-store-sdk.rb +2 -1
- data/lib/opera-mobile-store-sdk/version.rb +1 -1
- data/lib/opera/mobile_store/build.rb +97 -101
- data/lib/opera/mobile_store/build_file.rb +24 -27
- data/lib/opera/mobile_store/category.rb +17 -19
- data/lib/opera/mobile_store/developer.rb +61 -59
- data/lib/opera/mobile_store/inspectable_attributes.rb +13 -0
- data/lib/opera/mobile_store/payment_info.rb +76 -82
- data/lib/opera/mobile_store/product.rb +172 -177
- data/lib/opera/mobile_store/product_image.rb +30 -32
- data/lib/opera/mobile_store/product_localization.rb +21 -23
- data/lib/opera/mobile_store_sdk/identity_mapable.rb +11 -3
- metadata +3 -3
- data/lib/opera/mobile_store/author.rb +0 -29
@@ -1,105 +1,99 @@
|
|
1
|
-
|
2
|
-
module MobileStore
|
3
|
-
class PaymentInfo
|
1
|
+
require "active_model"
|
4
2
|
|
5
|
-
|
3
|
+
module Opera::MobileStore
|
4
|
+
class PaymentInfo
|
6
5
|
|
7
|
-
|
8
|
-
|
9
|
-
type = node.xpath("string(@type)").strip.downcase
|
10
|
-
|
11
|
-
case type
|
12
|
-
when "check" then Check.build_from_nokogiri_node node
|
13
|
-
when "wired" then Wired.build_from_nokogiri_node node
|
14
|
-
when "paypal" then PayPal.build_from_nokogiri_node node
|
15
|
-
when "none" then nil
|
16
|
-
else raise "WTF?"
|
17
|
-
end
|
18
|
-
end
|
19
|
-
end
|
6
|
+
include ActiveModel::Model
|
7
|
+
include ActiveModel::Serialization
|
20
8
|
|
21
|
-
|
22
|
-
|
23
|
-
|
9
|
+
include Opera::MobileStore::InspectableAttributes
|
10
|
+
|
11
|
+
def type
|
12
|
+
self.class.name.demodulize.downcase
|
13
|
+
end
|
24
14
|
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
attributes + ["#{key}: \"#{val}\""]
|
29
|
-
end.join(", ")
|
15
|
+
def self.build_from_nokogiri_node(node)
|
16
|
+
if node.present?
|
17
|
+
type = node.xpath("string(@type)").strip.downcase
|
30
18
|
|
31
|
-
|
19
|
+
case type
|
20
|
+
when "check" then Check.build_from_nokogiri_node node
|
21
|
+
when "wired" then Wired.build_from_nokogiri_node node
|
22
|
+
when "paypal" then PayPal.build_from_nokogiri_node node
|
23
|
+
when "none" then nil
|
24
|
+
else raise "WTF?"
|
25
|
+
end
|
32
26
|
end
|
27
|
+
end
|
33
28
|
|
34
|
-
|
35
|
-
|
29
|
+
class Check < PaymentInfo
|
30
|
+
attr_accessor :name, :address
|
36
31
|
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
32
|
+
def self.build_from_nokogiri_node(node)
|
33
|
+
self.new(
|
34
|
+
name: node.xpath("string(payment_check_name)"),
|
35
|
+
address: node.xpath("string(payment_check_address)")
|
36
|
+
)
|
37
|
+
end
|
43
38
|
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
end
|
39
|
+
def attributes
|
40
|
+
[:name, :address].inject({}) do |hash, method|
|
41
|
+
value = self.public_send method
|
42
|
+
hash[method] = value unless value.nil?
|
43
|
+
hash
|
50
44
|
end
|
51
45
|
end
|
46
|
+
end
|
52
47
|
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
def self.build_from_nokogiri_node(node)
|
66
|
-
data = [
|
67
|
-
:bank_account, :bank_name, :bank_address, :bank_swiftbic, :bank_iban,
|
68
|
-
:bank_routing_number, :intermediary_bank_name, :intermediary_bank_address,
|
69
|
-
:intermediary_bank_swiftbic, :intermediary_bank_iban
|
70
|
-
].inject({}) do |hash, attribute_name|
|
71
|
-
value = node.xpath("string(payment_wired_#{attribute_name})").strip
|
72
|
-
hash[attribute_name] = value if value.present?
|
73
|
-
hash
|
74
|
-
end
|
75
|
-
|
76
|
-
self.new data
|
77
|
-
end
|
48
|
+
class Wired < PaymentInfo
|
49
|
+
attr_accessor :bank_account,
|
50
|
+
:bank_name,
|
51
|
+
:bank_address,
|
52
|
+
:bank_swiftbic,
|
53
|
+
:bank_iban,
|
54
|
+
:bank_routing_number,
|
55
|
+
:intermediary_bank_name,
|
56
|
+
:intermediary_bank_address,
|
57
|
+
:intermediary_bank_swiftbic,
|
58
|
+
:intermediary_bank_iban
|
78
59
|
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
end
|
60
|
+
def self.build_from_nokogiri_node(node)
|
61
|
+
data = [
|
62
|
+
:bank_account, :bank_name, :bank_address, :bank_swiftbic, :bank_iban,
|
63
|
+
:bank_routing_number, :intermediary_bank_name, :intermediary_bank_address,
|
64
|
+
:intermediary_bank_swiftbic, :intermediary_bank_iban
|
65
|
+
].inject({}) do |hash, attribute_name|
|
66
|
+
value = node.xpath("string(payment_wired_#{attribute_name})").strip
|
67
|
+
hash[attribute_name] = value if value.present?
|
68
|
+
hash
|
89
69
|
end
|
70
|
+
|
71
|
+
self.new data
|
90
72
|
end
|
91
73
|
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
74
|
+
def attributes
|
75
|
+
[
|
76
|
+
:bank_account, :bank_name, :bank_address, :bank_swiftbic, :bank_iban,
|
77
|
+
:bank_routing_number, :intermediary_bank_name, :intermediary_bank_address,
|
78
|
+
:intermediary_bank_swiftbic, :intermediary_bank_iban
|
79
|
+
].inject({}) do |hash, method|
|
80
|
+
value = self.public_send method
|
81
|
+
hash[method] = value unless value.nil?
|
82
|
+
hash
|
96
83
|
end
|
84
|
+
end
|
85
|
+
end
|
97
86
|
|
98
|
-
|
99
|
-
|
100
|
-
|
87
|
+
class PayPal < PaymentInfo
|
88
|
+
attr_accessor :account
|
89
|
+
def self.build_from_nokogiri_node(node)
|
90
|
+
self.new(account: node.xpath("string(account)").strip)
|
101
91
|
end
|
102
92
|
|
93
|
+
def attributes
|
94
|
+
{ account: account }
|
95
|
+
end
|
103
96
|
end
|
97
|
+
|
104
98
|
end
|
105
99
|
end
|
@@ -1,212 +1,207 @@
|
|
1
1
|
require "active_model"
|
2
2
|
|
3
|
-
module Opera
|
4
|
-
|
5
|
-
class Product
|
3
|
+
module Opera::MobileStore
|
4
|
+
class Product
|
6
5
|
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
6
|
+
FIELDS = [
|
7
|
+
:id, :code, :category_id, :cp_product_id, :app_type, :author_id,
|
8
|
+
:support_url, :version, :requirements, :price, :adult_content, :rating,
|
9
|
+
:currency, :product_type, :weight, :updated_at, :added_at, :keywords,
|
10
|
+
:rating, :images, :eula, :subsites, :builds, :i18n,
|
11
|
+
:released_at, # Release Date
|
12
|
+
:download_count # Download count at Opera Mobile Store
|
13
|
+
].freeze
|
15
14
|
|
16
|
-
|
17
|
-
|
15
|
+
include ActiveModel::Model
|
16
|
+
include ActiveModel::Serialization
|
18
17
|
|
19
|
-
|
18
|
+
include Opera::MobileStore::InspectableAttributes
|
20
19
|
|
21
|
-
|
22
|
-
attr_accessor *FIELDS
|
20
|
+
include Opera::MobileStoreSDK::APIAccessible
|
23
21
|
|
24
|
-
|
25
|
-
|
26
|
-
end
|
27
|
-
|
28
|
-
def category=(given_category)
|
29
|
-
@category_id = given_category.id
|
30
|
-
end
|
22
|
+
# All attributes are Read-Only...
|
23
|
+
attr_accessor *FIELDS
|
31
24
|
|
32
|
-
|
33
|
-
|
34
|
-
|
25
|
+
def category
|
26
|
+
Category.find category_id if category_id.present?
|
27
|
+
end
|
35
28
|
|
36
|
-
|
37
|
-
|
38
|
-
|
29
|
+
def category=(given_category)
|
30
|
+
@category_id = given_category.id
|
31
|
+
end
|
39
32
|
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
end.title
|
44
|
-
required_data.present? ? required_data : title('en')
|
45
|
-
end
|
33
|
+
def author
|
34
|
+
Developer.find author_id if author_id.present?
|
35
|
+
end
|
46
36
|
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
end.short_description
|
51
|
-
required_data.present? ? required_data : short_description('en')
|
52
|
-
end
|
37
|
+
def author=(given_author)
|
38
|
+
@author_id = given_author.id
|
39
|
+
end
|
53
40
|
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
41
|
+
def title(key = "en")
|
42
|
+
required_data = i18n.detect do |x|
|
43
|
+
x.language_code == key.to_s
|
44
|
+
end.title
|
45
|
+
required_data.present? ? required_data : title('en')
|
46
|
+
end
|
60
47
|
|
61
|
-
|
62
|
-
|
63
|
-
|
48
|
+
def short_description(key = "en")
|
49
|
+
required_data = i18n.detect do |x|
|
50
|
+
x.language_code == key.to_s
|
51
|
+
end.short_description
|
52
|
+
required_data.present? ? required_data : short_description('en')
|
53
|
+
end
|
64
54
|
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
end
|
55
|
+
def long_description(key = "en")
|
56
|
+
required_data = i18n.detect do |x|
|
57
|
+
x.language_code == key.to_s
|
58
|
+
end.long_description
|
59
|
+
required_data.present? ? required_data : long_description('en')
|
60
|
+
end
|
72
61
|
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
# query the OMS API for a single entity directly, we'll replace the entity
|
77
|
-
# id field (category_id, author_id) with the serializable hash of the
|
78
|
-
# actual object (category, author) as part of the serializable hash:
|
79
|
-
def serializable_hash(options = nil)
|
80
|
-
attributes.inject({}) do |shsh, keyval|
|
81
|
-
field_name, field_value = keyval
|
82
|
-
|
83
|
-
case field_name
|
84
|
-
when 'category_id', 'author_id'
|
85
|
-
field_name = field_name[0..-4]
|
86
|
-
field_value = self.send(field_name).serializable_hash
|
87
|
-
when 'i18n', 'images', 'builds' # Array of special objects
|
88
|
-
field_value = field_value.map(&:serializable_hash)
|
89
|
-
end
|
62
|
+
def available_language_codes
|
63
|
+
i18n.map(&:language_code)
|
64
|
+
end
|
90
65
|
|
91
|
-
|
92
|
-
|
93
|
-
|
66
|
+
def attributes
|
67
|
+
FIELDS.inject({}) do |hsh, field_name|
|
68
|
+
field_value = self.public_send field_name
|
69
|
+
hsh[field_name.to_s] = field_value if field_value.present?
|
70
|
+
hsh
|
94
71
|
end
|
72
|
+
end
|
95
73
|
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
|
110
|
-
|
111
|
-
|
112
|
-
end
|
113
|
-
when 'builds' # Array of special objects
|
114
|
-
field_value = field_value.map do |item_serializable_hash|
|
115
|
-
Build.deserialize item_serializable_hash
|
116
|
-
end
|
117
|
-
end
|
118
|
-
|
119
|
-
hsh[field_name] = field_value
|
120
|
-
hsh
|
74
|
+
# Override of serializable_hash:
|
75
|
+
#
|
76
|
+
# In the case of the category and author of a product, since we can't
|
77
|
+
# query the OMS API for a single entity directly, we'll replace the entity
|
78
|
+
# id field (category_id, author_id) with the serializable hash of the
|
79
|
+
# actual object (category, author) as part of the serializable hash:
|
80
|
+
def serializable_hash(options = nil)
|
81
|
+
attributes.inject({}) do |shsh, keyval|
|
82
|
+
field_name, field_value = keyval
|
83
|
+
|
84
|
+
case field_name
|
85
|
+
when 'category_id', 'author_id'
|
86
|
+
field_name = field_name[0..-4]
|
87
|
+
field_value = self.send(field_name).serializable_hash
|
88
|
+
when 'i18n', 'images', 'builds' # Array of special objects
|
89
|
+
field_value = field_value.map(&:serializable_hash)
|
121
90
|
end
|
122
91
|
|
123
|
-
|
92
|
+
shsh[field_name] = field_value
|
93
|
+
shsh
|
124
94
|
end
|
95
|
+
end
|
125
96
|
|
126
|
-
|
127
|
-
|
128
|
-
|
129
|
-
|
130
|
-
|
131
|
-
|
132
|
-
|
133
|
-
|
134
|
-
|
135
|
-
|
136
|
-
|
137
|
-
|
138
|
-
|
139
|
-
|
140
|
-
|
141
|
-
|
142
|
-
|
143
|
-
|
144
|
-
|
145
|
-
|
146
|
-
|
147
|
-
|
148
|
-
Author.new author_attributes
|
97
|
+
def self.deserialize(serializable_hash)
|
98
|
+
attributes_hash = serializable_hash.inject({}) do |hsh, keyval|
|
99
|
+
field_name, field_value = keyval
|
100
|
+
|
101
|
+
case field_name
|
102
|
+
when 'category'
|
103
|
+
field_value = Category.deserialize field_value
|
104
|
+
when 'author'
|
105
|
+
field_value = Author.deserialize field_value
|
106
|
+
when 'i18n'
|
107
|
+
field_value = field_value.map do |item_serializable_hash|
|
108
|
+
ProductLocalization.deserialize item_serializable_hash
|
109
|
+
end
|
110
|
+
when 'images'
|
111
|
+
field_value = field_value.map do |item_serializable_hash|
|
112
|
+
ProductImage.deserialize item_serializable_hash
|
113
|
+
end
|
114
|
+
when 'builds' # Array of special objects
|
115
|
+
field_value = field_value.map do |item_serializable_hash|
|
116
|
+
Build.deserialize item_serializable_hash
|
117
|
+
end
|
149
118
|
end
|
150
119
|
|
151
|
-
|
152
|
-
|
153
|
-
|
154
|
-
cp_product_id: node.xpath("number(cp_product_id)").to_i,
|
155
|
-
|
156
|
-
category_id: category_id,
|
157
|
-
author_id: author_id,
|
158
|
-
app_type: node.xpath("string(apptype)"),
|
159
|
-
released_at: DateTime.parse(node.xpath "string(release_date)"),
|
160
|
-
download_count: node.xpath("number(downloads_count)").to_i,
|
161
|
-
support_url: node.xpath("string(support_url)"),
|
162
|
-
version: node.xpath("string(version)"),
|
163
|
-
# TODO: process requirements node
|
164
|
-
|
165
|
-
# Product localization in English:
|
166
|
-
i18n: [
|
167
|
-
ProductLocalization.new({
|
168
|
-
language_code: "en",
|
169
|
-
title: node.xpath("string(product_name)").strip,
|
170
|
-
short_description: node.xpath("string(short_description)").strip,
|
171
|
-
long_description: node.xpath("string(long_description)").strip
|
172
|
-
}.select { |key, val| val.present? })
|
173
|
-
],
|
174
|
-
|
175
|
-
price: node.xpath("number(price)"),
|
176
|
-
currency: node.xpath("string(currency)"),
|
177
|
-
product_type: node.xpath("string(type)"),
|
178
|
-
weight: node.xpath("number(weight)").to_i,
|
179
|
-
updated_at: DateTime.parse(node.xpath "string(update_date)"),
|
180
|
-
added_at: DateTime.parse(node.xpath "string(add_date)"),
|
181
|
-
|
182
|
-
keywords: node.xpath("keywords/keyword").map do |x|
|
183
|
-
value = x.text.strip
|
184
|
-
value.present? ? Opera::MobileStoreSDK.html_entities.decode(value) : nil
|
185
|
-
end.compact,
|
186
|
-
|
187
|
-
images: node.xpath("images/*").map do |i|
|
188
|
-
ProductImage.build_from_nokogiri_node i
|
189
|
-
end,
|
190
|
-
|
191
|
-
builds: node.xpath("builds/build").map do |b|
|
192
|
-
Build.build_from_nokogiri_node b
|
193
|
-
end
|
120
|
+
hsh[field_name] = field_value
|
121
|
+
hsh
|
122
|
+
end
|
194
123
|
|
195
|
-
|
124
|
+
self.new attributes_hash
|
125
|
+
end
|
196
126
|
|
197
|
-
|
198
|
-
|
199
|
-
|
200
|
-
|
201
|
-
|
127
|
+
# TODO: Move this implementation to the SDK namespace
|
128
|
+
def self.build_from_nokogiri_node(node)
|
129
|
+
|
130
|
+
# Initialize the category unless it's already in it's Identity Map:
|
131
|
+
category_id = node.xpath("number(category/@id)").to_i
|
132
|
+
Category.new(
|
133
|
+
id: category_id,
|
134
|
+
code: node.xpath("string(category/@code)"),
|
135
|
+
name: node.xpath("string(category)")
|
136
|
+
) unless Category.identity_mapped? category_id
|
137
|
+
|
138
|
+
# Initialize the category unless it's already in it's Identity Map:
|
139
|
+
author_id = node.xpath("number(author/@id)").to_i
|
140
|
+
Developer.new(
|
141
|
+
id: author_id,
|
142
|
+
name: node.xpath("string(author)"),
|
143
|
+
email: node.xpath("string(author_email)")
|
144
|
+
) unless Developer.identity_mapped? author_id
|
145
|
+
|
146
|
+
data = {
|
147
|
+
id: node.xpath("number(@id)").to_i,
|
148
|
+
code: node.xpath("string(@code)"),
|
149
|
+
cp_product_id: node.xpath("number(cp_product_id)").to_i,
|
150
|
+
|
151
|
+
category_id: category_id,
|
152
|
+
author_id: author_id,
|
153
|
+
|
154
|
+
app_type: node.xpath("string(apptype)"),
|
155
|
+
released_at: DateTime.parse(node.xpath "string(release_date)"),
|
156
|
+
download_count: node.xpath("number(downloads_count)").to_i,
|
157
|
+
support_url: node.xpath("string(support_url)"),
|
158
|
+
version: node.xpath("string(version)"),
|
159
|
+
# TODO: process requirements node
|
160
|
+
|
161
|
+
# Product localization in English:
|
162
|
+
i18n: [
|
163
|
+
ProductLocalization.new({
|
164
|
+
language_code: "en",
|
165
|
+
title: node.xpath("string(product_name)").strip,
|
202
166
|
short_description: node.xpath("string(short_description)").strip,
|
203
|
-
long_description: node.xpath("string(
|
167
|
+
long_description: node.xpath("string(long_description)").strip
|
204
168
|
}.select { |key, val| val.present? })
|
169
|
+
],
|
170
|
+
|
171
|
+
price: node.xpath("number(price)"),
|
172
|
+
currency: node.xpath("string(currency)"),
|
173
|
+
product_type: node.xpath("string(type)"),
|
174
|
+
weight: node.xpath("number(weight)").to_i,
|
175
|
+
updated_at: DateTime.parse(node.xpath "string(update_date)"),
|
176
|
+
added_at: DateTime.parse(node.xpath "string(add_date)"),
|
177
|
+
|
178
|
+
keywords: node.xpath("keywords/keyword").map do |x|
|
179
|
+
value = x.text.strip
|
180
|
+
value.present? ? Opera::MobileStoreSDK.html_entities.decode(value) : nil
|
181
|
+
end.compact,
|
182
|
+
|
183
|
+
images: node.xpath("images/*").map do |i|
|
184
|
+
ProductImage.build_from_nokogiri_node i
|
185
|
+
end,
|
186
|
+
|
187
|
+
builds: node.xpath("builds/build").map do |b|
|
188
|
+
Build.build_from_nokogiri_node b
|
205
189
|
end
|
206
190
|
|
207
|
-
|
191
|
+
}
|
192
|
+
|
193
|
+
node.xpath("translates/language").each do |language_node|
|
194
|
+
data[:i18n] << ProductLocalization.new({
|
195
|
+
# We'll ignore the "@code" attribute... only Opera Devs knows WTF with it.
|
196
|
+
language_code: language_node.xpath("string(@iso)").strip,
|
197
|
+
title: node.xpath("string(title)").strip,
|
198
|
+
short_description: node.xpath("string(short_description)").strip,
|
199
|
+
long_description: node.xpath("string(description)").strip
|
200
|
+
}.select { |key, val| val.present? })
|
208
201
|
end
|
209
202
|
|
203
|
+
self.new data
|
210
204
|
end
|
205
|
+
|
211
206
|
end
|
212
207
|
end
|