opera-mobile-store-sdk 0.1.1 → 0.1.2
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.
- 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
|