eve_app 0.1.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.
Files changed (46) hide show
  1. checksums.yaml +7 -0
  2. data/MIT-LICENSE +20 -0
  3. data/README.md +28 -0
  4. data/Rakefile +21 -0
  5. data/app/controllers/eve_app/application_controller.rb +5 -0
  6. data/app/controllers/eve_app/regions_controller.rb +3 -0
  7. data/app/controllers/eve_app/simple_resource_controller.rb +38 -0
  8. data/app/controllers/eve_app/solar_systems_controller.rb +2 -0
  9. data/app/controllers/eve_app/types_controller.rb +3 -0
  10. data/app/helpers/eve_app/entity_helper.rb +15 -0
  11. data/app/helpers/eve_app/output_helper.rb +25 -0
  12. data/app/models/eve_app/activity.rb +26 -0
  13. data/app/models/eve_app/activity_material.rb +11 -0
  14. data/app/models/eve_app/activity_product.rb +8 -0
  15. data/app/models/eve_app/activity_skill.rb +5 -0
  16. data/app/models/eve_app/application_record.rb +5 -0
  17. data/app/models/eve_app/category.rb +27 -0
  18. data/app/models/eve_app/concerns/activity_relation.rb +26 -0
  19. data/app/models/eve_app/group.rb +3 -0
  20. data/app/models/eve_app/market_group.rb +25 -0
  21. data/app/models/eve_app/region.rb +3 -0
  22. data/app/models/eve_app/solar_system.rb +14 -0
  23. data/app/models/eve_app/station.rb +9 -0
  24. data/app/models/eve_app/type.rb +57 -0
  25. data/app/serializers/eve_app/application_serializer.rb +2 -0
  26. data/app/serializers/eve_app/region_serializer.rb +5 -0
  27. data/app/serializers/eve_app/solar_system_serializer.rb +8 -0
  28. data/app/serializers/eve_app/type_serializer.rb +11 -0
  29. data/config/routes.rb +5 -0
  30. data/lib/eve_app/engine.rb +6 -0
  31. data/lib/eve_app/eve_central.rb +78 -0
  32. data/lib/eve_app/item_parser.rb +112 -0
  33. data/lib/eve_app/sde/data_importer.rb +43 -0
  34. data/lib/eve_app/sde/downloader.rb +53 -0
  35. data/lib/eve_app/sde/normalizer.rb +106 -0
  36. data/lib/eve_app/sde.rb +27 -0
  37. data/lib/eve_app/version.rb +3 -0
  38. data/lib/eve_app/xml_api/calls.rb +197 -0
  39. data/lib/eve_app/xml_api/classes.rb +260 -0
  40. data/lib/eve_app/xml_api/client.rb +86 -0
  41. data/lib/eve_app/xml_api.rb +10 -0
  42. data/lib/eve_app.rb +35 -0
  43. data/lib/table-list.yml +88 -0
  44. data/lib/table-map.yml +177 -0
  45. data/lib/tasks/eve_app.rake +30 -0
  46. metadata +186 -0
@@ -0,0 +1,43 @@
1
+ require "sshkit"
2
+
3
+ module EveApp
4
+ module SDE
5
+ class DataImporter < SSHKit::Backend::Local
6
+ include Downloader
7
+ include Normalizer
8
+
9
+ def initialize
10
+ super
11
+ execute :mkdir, '-p', SDE.config.tmp_path
12
+ end
13
+
14
+ private
15
+
16
+ def db_config
17
+ ActiveRecord::Base.connection_config
18
+ end
19
+
20
+ def sql(sql)
21
+ log "[SQL] #{sql}"
22
+ db.execute(sql)
23
+ end
24
+
25
+ def db
26
+ ActiveRecord::Base.connection
27
+ end
28
+
29
+ def table_list
30
+ @_table_list ||= begin
31
+ whitelist = Array[SDE.config.table_whitelist].flatten.compact.map(&:to_s)
32
+ tables = SDE.table_list
33
+ tables = whitelist.any? ? tables & whitelist : tables
34
+ Hash[tables.map { |name| [name, normalize_table_name(name)] }]
35
+ end
36
+ end
37
+
38
+ def normalize_table_name(name)
39
+ (SDE.config.table_prefix.to_s + name.gsub(/^#{SDE::PREFIXES.join('|')}/, '')).pluralize.underscore
40
+ end
41
+ end
42
+ end
43
+ end
@@ -0,0 +1,53 @@
1
+ module EveApp
2
+ module SDE
3
+ module Downloader
4
+ def download
5
+ within SDE.config.tmp_path do
6
+ execute :wget, '-q', download_uri + '{,.md5}'
7
+ verify!
8
+ execute :bunzip2, SDE.config.archive
9
+ end
10
+ end
11
+
12
+ def restore
13
+ table_list.each do |table_name,normalized_name|
14
+ sql %Q(DROP TABLE IF EXISTS "#{table_name}")
15
+ sql %Q(DROP TABLE IF EXISTS "#{normalized_name}")
16
+ end
17
+
18
+ options = ['-x -O']
19
+ options << "-h #{db_config[:host]}" if db_config[:host]
20
+ options << "-U #{db_config[:username]}" if db_config[:username]
21
+ options << "-d #{db_config[:database]}"
22
+ options += table_list.keys.map { |name| "-t #{name}" }
23
+ options << local_archive.gsub('.bz2', '')
24
+
25
+ execute :pg_restore, *options
26
+ end
27
+
28
+ private
29
+
30
+ def local_archive
31
+ @_local_archive ||= [SDE.config.tmp_path, SDE.config.archive].join('/')
32
+ end
33
+
34
+ def download_uri
35
+ @_download_url ||= begin
36
+ uri = URI(SDE.config.download_host)
37
+ uri.path = [uri.path, SDE.config.archive].join('/')
38
+ uri.to_s
39
+ end
40
+ end
41
+
42
+ def verify!
43
+ md5 = capture :md5, '-q', local_archive
44
+ md5_file = local_archive + '.md5'
45
+ if md5 == open(md5_file).read.split(' ').first
46
+ execute :rm, '-f', md5_file
47
+ else
48
+ raise "Downloaded data dump is invalid (MD5 hash verification failed)"
49
+ end
50
+ end
51
+ end
52
+ end
53
+ end
@@ -0,0 +1,106 @@
1
+ module EveApp
2
+ module SDE
3
+ module Normalizer
4
+ def normalize
5
+ table_names
6
+ column_names
7
+ missing_relations
8
+ indexes
9
+ end
10
+
11
+ def table_names
12
+ table_list.each do |table_name, normalized_name|
13
+ sql %Q(ALTER TABLE IF EXISTS "#{table_name}" RENAME TO "#{normalized_name}")
14
+ end
15
+ end
16
+
17
+ def column_names
18
+ columns.each do |row|
19
+ resource_name = row[:table_name].singularize.gsub('eve_', '')
20
+ row[:target_column_name] = row[:column_name].underscore
21
+ if row[:target_column_name].starts_with?(resource_name)
22
+ row[:target_column_name] = row[:target_column_name].gsub("#{resource_name}_", "")
23
+ end
24
+
25
+ if row[:column_name] != row[:target_column_name]
26
+ sql %Q(ALTER TABLE "#{row[:table_name]}" RENAME COLUMN "#{row[:column_name]}" TO "#{row[:target_column_name]}")
27
+ end
28
+ end
29
+ end
30
+
31
+ def indexes
32
+ columns.each do |row|
33
+ if (row[:column_name].ends_with?('id') || row[:column_name] == 'name') && !has_index?(db, row[:table_name], row[:column_name])
34
+ sql %Q(CREATE INDEX IF NOT EXISTS idx_#{row[:table_name]}_#{row[:column_name]} ON #{row[:table_name]} (#{row[:column_name]});)
35
+ end
36
+ if row[:column_name] == 'id'
37
+ sql %Q(ALTER TABLE #{row[:table_name]} DROP CONSTRAINT IF EXISTS #{row[:table_name]}_pkey)
38
+
39
+ if complex_id_index?(row[:table_name])
40
+ sql %Q(ALTER TABLE #{row[:table_name]} ADD PRIMARY KEY (id, type_id))
41
+ else
42
+ sql %Q(ALTER TABLE #{row[:table_name]} ADD PRIMARY KEY (id))
43
+ end
44
+ end
45
+ end
46
+ end
47
+
48
+ def missing_relations
49
+ sql %Q(ALTER TABLE #{table_list['invTypes']} ADD IF NOT EXISTS category_id integer)
50
+ sql %Q(ALTER TABLE #{table_list['invMarketGroups']} ADD root_group_id INTEGER DEFAULT NULL)
51
+ sql %Q(ALTER TABLE #{table_list['invTypes']} ADD market_group_root_id integer)
52
+ sql %Q(UPDATE #{table_list['invTypes']} SET category_id = (SELECT category_id FROM #{table_list['invGroups']} WHERE id = #{table_list['invTypes']}.group_id))
53
+ sql %Q(
54
+ WITH RECURSIVE mg_roots(id, root_id) AS (
55
+ SELECT mg.id, mg.id AS root_id FROM #{table_list['invMarketGroups']} AS mg WHERE mg.parent_group_id IS NULL
56
+ UNION ALL
57
+ SELECT c.id, p.root_id FROM mg_roots AS p, #{table_list['invMarketGroups']} AS c WHERE c.parent_group_id = p.id
58
+ )
59
+ UPDATE #{table_list['invMarketGroups']} SET root_group_id = mg_roots.root_id FROM (
60
+ SELECT id, root_id FROM mg_roots WHERE root_id != id
61
+ ) AS mg_roots WHERE #{table_list['invMarketGroups']}.id = mg_roots.id;
62
+ )
63
+ sql %Q(UPDATE #{table_list['invTypes']} SET market_group_root_id = (SELECT root_group_id FROM #{table_list['invMarketGroups']} WHERE id = #{table_list['invTypes']}.market_group_id))
64
+ end
65
+
66
+ private
67
+
68
+ def complex_id_index?(table_name)
69
+ SDE::ID_TYPE_INDEX.include?(table_name.gsub("#{SDE.config.table_prefix.to_s}_", ''))
70
+ end
71
+
72
+ def columns
73
+ query = %Q(
74
+ SELECT table_name, column_name, ordinal_position
75
+ FROM information_schema.columns
76
+ WHERE
77
+ table_catalog = '#{db_config[:database]}' AND
78
+ table_schema = 'public' AND
79
+ table_name IN(#{table_list.values.map { |n| "'#{n}'" }.join(', ')})
80
+ )
81
+ db.select_all(query).map(&:symbolize_keys)
82
+ end
83
+
84
+ def has_index?(db, table, column)
85
+ count = db.select_value %Q(
86
+ select
87
+ COUNT(*)
88
+ from
89
+ pg_class t,
90
+ pg_class i,
91
+ pg_index ix,
92
+ pg_attribute a
93
+ where
94
+ t.oid = ix.indrelid
95
+ and i.oid = ix.indexrelid
96
+ and a.attrelid = t.oid
97
+ and a.attnum = ANY(ix.indkey)
98
+ and t.relkind = 'r'
99
+ and t.relname = '#{table}'
100
+ and a.attname = '#{column}';
101
+ )
102
+ count.to_i > 0
103
+ end
104
+ end
105
+ end
106
+ end
@@ -0,0 +1,27 @@
1
+ module EveApp
2
+ module SDE
3
+ autoload :DataImporter, 'eve_app/sde/data_importer'
4
+ autoload :Downloader, 'eve_app/sde/downloader'
5
+ autoload :Normalizer, 'eve_app/sde/normalizer'
6
+
7
+ DEFAULT_CONFIG = {
8
+ table_prefix: :eve,
9
+ download_host: 'https://www.fuzzwork.co.uk/dump',
10
+ archive: 'postgres-latest.dmp.bz2',
11
+ tmp_path: Rails.root.join('tmp', 'eve-sde'),
12
+ table_list_file: EveApp.root.join('lib', 'table-list.yml')
13
+ }
14
+ PREFIXES = %w(agt dgm map trn inv sta industry)
15
+ ID_TYPE_INDEX = %w(activities)
16
+
17
+ class << self
18
+ def config
19
+ @_config ||= OpenStruct.new(DEFAULT_CONFIG)
20
+ end
21
+
22
+ def table_list
23
+ @_table_list ||= YAML::load_file(config.table_list_file)
24
+ end
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,3 @@
1
+ module EveApp
2
+ VERSION = '0.1.0'
3
+ end
@@ -0,0 +1,197 @@
1
+ module EveApp
2
+ module XmlApi
3
+ class Calls
4
+ class Base
5
+ class_attribute :endpoint
6
+ class_attribute :selector
7
+ class_attribute :class_name
8
+
9
+ self.selector = '//rowset/row'
10
+
11
+ attr_reader :xml, :results
12
+ attr_accessor :cached_until
13
+
14
+ delegate :blank?, :present?, :any?, to: :results
15
+
16
+ def initialize(xml=nil)
17
+ if xml
18
+ @xml = xml
19
+ @results = xml.search(selector).map { |row| class_name.new(row) }
20
+ @cached_until = (xml.search('cachedUntil').text + 'Z').to_time
21
+ else
22
+ @results = []
23
+ end
24
+ end
25
+
26
+ def merge(call)
27
+ @results += call.results
28
+ end
29
+
30
+ def ids
31
+ @results.map(&:id)
32
+ end
33
+
34
+ def error?
35
+ false
36
+ end
37
+ end
38
+
39
+ class Characters < Base
40
+ self.class_name = Classes::Characters
41
+ self.endpoint = '/account/Characters.xml.aspx'
42
+ end
43
+
44
+ class Sheet < Base
45
+ attr_reader :sheet
46
+
47
+ def initialize(xml)
48
+ @xml = xml
49
+ @sheet = class_name.new(xml)
50
+ end
51
+ end
52
+ class CharacterSheet < Sheet
53
+ self.class_name = Classes::CharacterSheet
54
+ self.endpoint = '/char/CharacterSheet.xml.aspx'
55
+ end
56
+ class CorporationSheet < Sheet
57
+ self.class_name = Classes::CorporationSheet
58
+ self.endpoint = '/corp/CorporationSheet.xml.aspx'
59
+ end
60
+
61
+ class AccountBalance < Base
62
+ self.class_name = Classes::AccountBalance
63
+ end
64
+ class PersonalAccountBalance < AccountBalance
65
+ self.endpoint = '/char/AccountBalance.xml.aspx'
66
+ end
67
+ class CorporateAccountBalance < AccountBalance
68
+ self.endpoint = '/corp/AccountBalance.xml.aspx'
69
+ end
70
+
71
+ class WalletTransactions < Base
72
+ self.class_name = Classes::WalletTransaction
73
+ end
74
+ class PersonalWalletTransactions < WalletTransactions
75
+ self.endpoint = '/char/WalletTransactions.xml.aspx'
76
+ end
77
+ class CorporateWalletTransactions < WalletTransactions
78
+ self.endpoint = '/corp/WalletTransactions.xml.aspx'
79
+ end
80
+
81
+ class WalletJournals < Base
82
+ self.class_name = Classes::WalletJournal
83
+ end
84
+ class PersonalWalletJournals < WalletJournals
85
+ self.endpoint = '/char/WalletJournal.xml.aspx'
86
+ end
87
+ class CorporateWalletJournals < WalletJournals
88
+ self.endpoint = '/corp/WalletJournal.xml.aspx'
89
+ end
90
+
91
+ class Blueprints < Base
92
+ self.class_name = Classes::Blueprint
93
+ end
94
+ class PersonalBlueprints < Blueprints
95
+ self.endpoint = '/char/Blueprints.xml.aspx'
96
+ end
97
+ class CorporateBlueprints < Blueprints
98
+ self.endpoint = '/corp/Blueprints.xml.aspx'
99
+ end
100
+
101
+ class MarketOrders < Base
102
+ self.class_name = Classes::MarketOrder
103
+ end
104
+ class PersonalMarketOrders < MarketOrders
105
+ self.endpoint = '/char/MarketOrders.xml.aspx'
106
+ end
107
+ class CorporateMarketOrders < MarketOrders
108
+ self.endpoint = '/corp/MarketOrders.xml.aspx'
109
+ end
110
+
111
+ class IndustryJobs < Base
112
+ self.class_name = Classes::IndustryJob
113
+ end
114
+ class PersonalIndustryJobs < IndustryJobs
115
+ self.endpoint = '/char/IndustryJobs.xml.aspx'
116
+ end
117
+ class CorporateIndustryJobs < IndustryJobs
118
+ self.endpoint = '/corp/IndustryJobs.xml.aspx'
119
+ end
120
+ class PersonalIndustryJobHistory < IndustryJobs
121
+ self.endpoint = '/char/IndustryJobsHistory.xml.aspx'
122
+ end
123
+ class CorporateIndustryJobHistory < IndustryJobs
124
+ self.endpoint = '/corp/IndustryJobsHistory.xml.aspx'
125
+ end
126
+
127
+ class AssetList < Base
128
+ attr_reader :assets
129
+
130
+ def initialize(xml)
131
+ @assets = []
132
+ parse_rows(xml.search("//rowset[@name='assets']/row"))
133
+ @cached_until = (xml.search('cachedUntil').text + 'Z').to_time
134
+ end
135
+
136
+ def parse_rows(rows, parent=nil)
137
+ rows.each do |container|
138
+ asset = Classes::Asset.new(container)
139
+ asset.container = container.children.any?
140
+ asset.parent = parent if parent
141
+ parse_rows(container.search("rowset/row"), asset) if asset.container?
142
+ @assets << asset
143
+ end
144
+ end
145
+ end
146
+ class PersonalAssetList < AssetList
147
+ self.endpoint = '/char/AssetList.xml.aspx'
148
+ end
149
+ class CorporateAssetList < AssetList
150
+ self.endpoint = '/corp/AssetList.xml.aspx'
151
+ end
152
+
153
+ class CorporateMemberTracking < Base
154
+ self.class_name = Classes::CorporateMemberTracking
155
+ self.endpoint = '/corp/MemberTracking.xml.aspx'
156
+ end
157
+
158
+ class CorporateMemberSecurity < Base
159
+ self.class_name = Classes::CorporateMemberSecurity
160
+ self.endpoint = '/corp/MemberSecurity.xml.aspx'
161
+ end
162
+
163
+ class CorporateTitles < Base
164
+ self.class_name = Classes::CorporateTitle
165
+ self.endpoint = '/corp/Titles.xml.aspx'
166
+ end
167
+
168
+ class ErrorResponse < Base
169
+ attr_reader :exception, :call, :params
170
+
171
+ def initialize(exception, call=nil, params={})
172
+ @exception = exception
173
+ @call = call
174
+ @params = params
175
+ end
176
+
177
+ def error?
178
+ true
179
+ end
180
+
181
+ def title
182
+ exception.http_body.scan(/<title>(.+?)<\/title>/).flatten[0]
183
+ end
184
+
185
+ def to_s
186
+ puts "================================"
187
+ puts "API Error: #{exception.class}"
188
+ puts "title: #{title}"
189
+ puts "body: #{exception.http_body}"
190
+ puts "call: #{call}"
191
+ puts "params: #{params.inspect}"
192
+ puts "================================"
193
+ end
194
+ end
195
+ end
196
+ end
197
+ end
@@ -0,0 +1,260 @@
1
+ module EveApp
2
+ module XmlApi
3
+ class Classes
4
+ class Base
5
+ protected
6
+
7
+ def parse_time(value)
8
+ !value || value.starts_with?('0001') ? nil : "#{value}Z".to_time
9
+ end
10
+ end
11
+
12
+ class Characters < Base
13
+ attr_reader :id, :name, :corporation_id, :corporation_name, :alliance_id, :alliance_name, :faction_id, :faction_name
14
+
15
+ def initialize(elem)
16
+ @id = elem['characterID'].to_i
17
+ @name = elem['name']
18
+ @corporation_id = elem['corporationID'].to_i
19
+ @corporation_name = elem['corporationName']
20
+ @alliance_id = elem['allianceID'].to_i
21
+ @alliance_name = elem['allianceName']
22
+ @faction_id = elem['factionID'].to_i
23
+ @faction_name = elem['factionName']
24
+ end
25
+ end
26
+
27
+ class AccountBalance < Base
28
+ attr_reader :id, :key, :balance
29
+
30
+ def initialize(elem)
31
+ @id = elem['accountID'].to_i
32
+ @key = elem['accountKey'].to_i
33
+ @balance = elem['balance'].to_f
34
+ end
35
+ end
36
+
37
+ class CharacterSheet < Base
38
+ attr_reader :id, :name, :corporation_id
39
+
40
+ def initialize(xml)
41
+ @id = xml.search('//result/characterID[1]').text.to_i
42
+ @name = xml.search('//result/name[1]').text
43
+ @corporation_id = xml.search('//result/corporationID[1]').text.to_i
44
+ end
45
+ end
46
+
47
+ class CorporationSheet < Base
48
+ attr_reader :id, :name, :alliance_id
49
+
50
+ def initialize(xml)
51
+ @id = xml.search('//result/corporationID[1]').text.to_i
52
+ @name = xml.search('//result/corporationName[1]').text
53
+ @alliance_id = xml.search('//result/allianceID[1]').text.to_i
54
+ end
55
+ end
56
+
57
+ class WalletTransaction < Base
58
+ attr_reader :created_at, :id, :quantity, :type_name, :type_id, :price,
59
+ :client_id, :client_name, :character_id, :station_id, :station_name, :type,
60
+ :transaction_for, :character_name
61
+
62
+ def initialize(elem)
63
+ @created_at = parse_time(elem['transactionDateTime'])
64
+ @id = elem['transactionID'].to_i
65
+ @quantity = elem['quantity'].to_i
66
+ @type_name = elem['typeName']
67
+ @type_id = elem['typeID'].to_i
68
+ @price = elem['price'].to_f
69
+ @client_id = elem['clientID'].to_i if elem['clientID']
70
+ @client_name = elem['clientName']
71
+ @character_name = elem['characterName']
72
+ @station_id = elem['stationID'].to_i
73
+ @station_name = elem['stationName']
74
+ @character_id = elem['characterID'].to_i if elem['characterID'] && elem['characterID'] != '0'
75
+ @type = elem['transactionType']
76
+ @transaction_for = elem['transactionFor']
77
+ end
78
+ end
79
+
80
+ class WalletJournal < Base
81
+ attr_reader :created_at, :id, :ref_type_id, :owner1_name, :owner1_id, :owner1_type_id,
82
+ :owner2_name, :owner2_id, :owner2_type_id, :arg_name, :arg_id, :amount, :balance, :reason
83
+
84
+ def initialize(elem)
85
+ @created_at = parse_time(elem['date'])
86
+ @id = elem['refID'].to_i
87
+ @ref_type_id = elem['refTypeID'].to_i
88
+ @owner1_id = elem['ownerID1'].to_i if elem['ownerID1']
89
+ @owner1_name = elem['ownerName1']
90
+ @owner1_type = elem['owner1TypeID']
91
+ @owner2_id = elem['ownerID2'].to_i if elem['ownerID2']
92
+ @owner2_name = elem['ownerName2']
93
+ @owner2_type = elem['owner2TypeID']
94
+ @arg_name = elem['argName1']
95
+ @arg_id = elem['argID1'].to_i if elem['argID1']
96
+ @amount = elem['amount'].to_f
97
+ @balance = elem['balance'].to_f
98
+ @reason = elem['reason']
99
+ end
100
+ end
101
+
102
+ class Blueprint < Base
103
+ attr_reader :item_id, :location_id, :type_id, :type_name, :flag, :quantity, :te, :me, :runs
104
+
105
+ def initialize(elem)
106
+ @item_id = elem['itemID'].to_i
107
+ @location_id = elem['locationID']
108
+ @type_id = elem['typeID'].to_i
109
+ @type_name = elem['typeName']
110
+ @flag = elem['flagID'].to_i
111
+ @quantity = elem['quantity'].to_i
112
+ @te = elem['timeEfficiency'].to_i
113
+ @me = elem['materialEfficiency'].to_i
114
+ @runs = elem['runs'].to_i
115
+ end
116
+ end
117
+
118
+ class Asset < Base
119
+ attr_accessor :container, :parent
120
+ attr_reader :item_id, :type_id, :location_id, :quantity, :flag, :repacked, :raw_quantity
121
+
122
+ def initialize(elem)
123
+ @container = false
124
+ @parent = nil
125
+ @item_id = elem['itemID'].to_i
126
+ @type_id = elem['typeID'].to_i
127
+ @location_id = elem['locationID'].to_i
128
+ @quantity = elem['quantity'].to_i
129
+ @flag = elem['flag'].to_i
130
+ @repacked = elem['singleton'].to_i
131
+ @raw_quantity = elem['rawQuantity'].to_i
132
+ end
133
+
134
+ def container?
135
+ !!container
136
+ end
137
+
138
+ def parent?
139
+ !!parent
140
+ end
141
+ end
142
+
143
+ class MarketOrder < Base
144
+ attr_reader :order_id, :character_id, :station_id, :volume_entered, :volume_remaining, :minimum_volume,
145
+ :state, :type_id, :range, :account_key, :duration, :escrow, :price, :bid, :issued_at
146
+
147
+ def initialize(elem) #:nodoc:
148
+ @order_id = elem['orderID'].to_i
149
+ @character_id = elem['charID'].to_i
150
+ @station_id = elem['stationID'].to_i
151
+ @volume_entered = elem['volEntered'].to_i
152
+ @volume_remaining = elem['volRemaining'].to_i
153
+ @minimum_volume = elem['minVolume'].to_i
154
+ @state = case elem['orderState'].to_i
155
+ when 0
156
+ 'Active'
157
+ when 1
158
+ 'Closed'
159
+ when 2
160
+ 'Expired'
161
+ when 3
162
+ 'Cancelled'
163
+ when 4
164
+ 'Pending'
165
+ when 5
166
+ 'Character Deleted'
167
+ end
168
+ @type_id = elem['typeID'].to_i
169
+ @range = elem['range'].to_i
170
+ @account_key = elem['accountKey'].to_i
171
+ @escrow = elem['escrow'].to_f
172
+ @price = elem['price'].to_f
173
+ @bid = elem['bid'] == '1'
174
+ @duration = elem['duration'].to_i
175
+ @issued_at = parse_time(elem['issued'])
176
+ end
177
+ end
178
+
179
+ class IndustryJob < Base
180
+ attr_reader :job_id, :installer_id, :installer_name, :facility_id, :system_id, :system_name,
181
+ :station_id, :activity_id, :blueprint_id, :blueprint_type_id, :blueprint_type_name,
182
+ :blueprint_location_id, :output_location_id, :runs, :cost, :team_id, :licensed_runs,
183
+ :probability, :product_type_id, :product_type_name, :status, :duration, :start_date,
184
+ :end_date, :pause_date, :completed_date, :completed_character_id, :successful_runs
185
+
186
+ def initialize(elem)
187
+ @job_id = elem['jobID'].to_i # "306488326"
188
+ @installer_id = elem['installerID'].to_i # "1663516939"
189
+ @installer_name = elem['installerName'] # "GaIIente Slave"
190
+ @facility_id = elem['facilityID'].to_i # "1022215707239"
191
+ @system_id = elem['solarSystemID'].to_i # "30001363"
192
+ @system_name = elem['solarSystemName'] # "Sobaseki"
193
+ @station_id = elem['stationID'].to_i # "1022182744039"
194
+ @activity_id = elem['activityID'].to_i # "1"
195
+ @blueprint_id = elem['blueprintID'].to_i # "1022215694764"
196
+ @blueprint_type_id = elem['blueprintTypeID'].to_i # "22545"
197
+ @blueprint_type_name = elem['blueprintTypeName'] # "Hulk Blueprint"
198
+ @blueprint_location_id = elem['blueprintLocationID'].to_i # "1022215707239"
199
+ @output_location_id = elem['outputLocationID'].to_i # "1022215707239"
200
+ @runs = elem['runs'].to_i # "1"
201
+ @cost = elem['cost'].to_f # "8920512.00"
202
+ @team_id = elem['teamID'].to_i # "0"
203
+ @licensed_runs = elem['licensedRuns'].to_i # "1"
204
+ @probability = elem['probability'].to_f # "1"
205
+ @product_type_id = elem['productTypeID'].to_i
206
+ @product_type_name = elem['productTypeName']
207
+ @status = elem['status'].to_i
208
+ @duration = elem['timeInSeconds'].to_i
209
+ @start_date = parse_time(elem['startDate'])
210
+ @end_date = parse_time(elem['endDate'])
211
+ @pause_date = parse_time(elem['pauseDate'])
212
+ @completed_date = parse_time(elem['completedDate'])
213
+ @completed_character_id = elem['completedCharacterID'].to_i
214
+ @successful_runs = elem['successfulRuns'].to_i
215
+ end
216
+ end
217
+
218
+ class CorporateMemberTracking < Base
219
+ attr_reader :character_id, :name, :start_date, :base_id, :base, :title, :logon_date, :logoff_date, :location_id, :location, :ship_type_id, :ship_type, :roles, :grantable_roles
220
+
221
+ def initialize(elem)
222
+ @character_id = elem['characterID'].to_i
223
+ @name = elem['name']
224
+ @start_date = parse_time(elem['startDateTime'])
225
+ @base_id = elem['baseID'].to_i
226
+ @base = elem['base']
227
+ @title = elem['title']
228
+ @logon_date = parse_time(elem['logonDateTime'])
229
+ @logoff_date = parse_time(elem['logoffDateTime'])
230
+ @location_id = elem['locationID'].to_i > 0 ? elem['locationID'].to_i : nil
231
+ @location = elem['location'].presence
232
+ @ship_type_id = elem['shipTypeID'].to_i
233
+ @ship_type = elem['shipType']
234
+ @roles = elem['roles']
235
+ @grantable_roles = elem['grantableRoles']
236
+ end
237
+ end
238
+
239
+ class CorporateMemberSecurity < Base
240
+ attr_reader :character_id, :name, :titles
241
+
242
+ def initialize(elem)
243
+ @character_id = elem['characterID'].to_i
244
+ @name = elem['name']
245
+ @titles = elem.search('rowset[@name="titles"]/row').map { |t| t['titleName'] }
246
+ end
247
+ end
248
+
249
+ class CorporateTitle < Base
250
+ attr_reader :id, :name, :description
251
+
252
+ def initialize(elem)
253
+ @id = elem['roleID'].to_i
254
+ @name = elem['roleName']
255
+ @description = elem['roleDescription']
256
+ end
257
+ end
258
+ end
259
+ end
260
+ end