tac_scribe 0.3.0-java → 0.7.3-java

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: f53013c85197970f92c1b1d77608bbb6c0f6d7d70b09640a4d566282ecd2ecd0
4
- data.tar.gz: e05a92c6f3da05c2f2925bb0ab18740ec31e3629906c56472e3fa94b5c1891c8
3
+ metadata.gz: c39a4f860637756b22a7ce9fd09d74e2c7190478a94dbc779373ba0d213d0b35
4
+ data.tar.gz: 5abb08ea5e750d3e460ade56d89924866badb2a842b62b20672df2f0c349eb53
5
5
  SHA512:
6
- metadata.gz: 54e42d58f65f1d4e49f1affc4211d8829d4b26a1dfc810c5656c37346e552fad692371c3e0738f7a7629d378d88e481aad570a1a8ab47e329cc2c91aac601f20
7
- data.tar.gz: 71d84cd363227f4ffd2684e00c04ae97e714b74f25339f1cf004d5741c33bf770131d7fb51b2b346f112d575135549ef001c0cb6c71b914cf0a72310659c926b
6
+ metadata.gz: 0d0ba804851aa3143c0f5b563d7401706dd129b826dc5398174ead33b982a22d48d0871d0d5498eb922c4ca08c83598a7c3928e3f1a69a7cd2094a3a5acf8801
7
+ data.tar.gz: 641fe1a451dff87f915c69fdf6ea36a4053f4924d77141a3617d7019dcf27ff9d9f884f65894d20ae75414d3b18c0b2e40642d2248d9a022cc8061455855afd3
@@ -14,7 +14,10 @@ Metrics/BlockLength:
14
14
  # There isn't much sense breaking up the OptionParser block since splitting
15
15
  # into db and tacview option methods will just break the method length cop
16
16
  # and splitting further doesn't aid readability
17
- - exe/start_scribe
17
+ - exe/tac_scribe
18
+ # Cannot really avoid this
19
+ - tac_scribe.gemspec
20
+
18
21
  Metrics/MethodLength:
19
22
  Exclude:
20
23
  # Breaking up the initializer doesn't really do much for readability
@@ -29,3 +32,13 @@ Style/GlobalVars:
29
32
  Exclude:
30
33
  # Using a global variable makes it available to the Pry console
31
34
  - bin/console
35
+ AllCops:
36
+ Exclude:
37
+ # These files will need a MAJOR refactoring to get rid of these warnings
38
+ - lib/tac_scribe/cache.rb
39
+ - lib/tac_scribe/datastore.rb
40
+ - lib/tac_scribe/daemon.rb
41
+ ClassVars:
42
+ Exclude:
43
+ # We are fine with this and understand the risks. We are not inheriting.
44
+ - lib/tac_scribe/event_processor.rb
@@ -1 +1 @@
1
- jruby
1
+ ruby-2.7.0
@@ -6,6 +6,49 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
6
6
 
7
7
  ## [Unreleased]
8
8
 
9
+ ## [0.7.3] - 2020-08-10
10
+ ### Changed
11
+ - Bug fixes after adding CinC support.
12
+
13
+ ## [0.7.2] - 2020-08-10
14
+ ### Added
15
+ - Support for "Commander-In-Chief" to get airbase information
16
+
17
+ ## [0.7.1] - 2020-08-10
18
+ ### Changed
19
+ - Fixed issue where "name" fields were not being populated
20
+ if airfields were not being loaded first
21
+
22
+ ## [0.7.0] - 2020-07-5
23
+ ### Changed
24
+ - Synced Lat/Lon calculations with DCS and fixed issue where
25
+ calculations were incorrect if only one value was updated
26
+
27
+ ## [0.6.2] - 2020-04-19
28
+ ### Changed
29
+ - Fixed typo causing app to fail
30
+
31
+ ## [0.6.1] - 2020-04-19
32
+ ### Changed
33
+ - Test build for gem issue
34
+
35
+ ## [0.6.0] - 2020-04-19
36
+ ### Added
37
+ - Calculates and stores speed of units
38
+
39
+ ### Fixed
40
+ - Made more robust against failures and more logging messages
41
+
42
+ ## [0.5.0] - 2020-03-17
43
+ ### Changed
44
+ - Added missing db column to migration file
45
+ - Clear the cache on server restart
46
+ - Add more info to logging
47
+
48
+ ## [0.4.0] - 2020-03-13
49
+ ### Changed
50
+ - Switch to periodic writing to the database
51
+
9
52
  ## [0.3.0] - 2019-10-27
10
53
  ### Changed
11
54
  - Changed the command-line name to match gem
@@ -0,0 +1,5 @@
1
+ Sea+Watercraft+AircraftCarrier
2
+ Air+Rotorcraft
3
+ Air+FixedWing
4
+ Ground+Static+Aerodrome
5
+ Navaid+Static+Bullseye
@@ -12,7 +12,9 @@ Sequel.migration do
12
12
  String :group, null: true
13
13
  Integer :coalition
14
14
  Integer :heading
15
+ Integer :speed
15
16
  Time :updated_at
17
+ Boolean :deleted
16
18
  # TODO: GIST Index on the position
17
19
  end
18
20
  end
@@ -10,6 +10,8 @@ options = {
10
10
  tacview_port: 42_674,
11
11
  tacview_password: nil,
12
12
  tacview_client_name: 'TacScribe',
13
+ cinc_enabled: false,
14
+ cinc_port: 9000,
13
15
  db_host: 'localhost',
14
16
  db_port: 5432,
15
17
  db_name: 'tac_scribe',
@@ -43,6 +45,17 @@ OptionParser.new do |opts|
43
45
  options[:tacview_client_name] = v
44
46
  end
45
47
 
48
+ opts.separator "\nCommander-In-Chief Options"
49
+ opts.on('-b', '--enable-cinc',
50
+ 'Is Cinc enabled? (Default: false)') do |_v|
51
+ options[:cinc_enabled] = true
52
+ end
53
+ opts.on('-m', '--cinc-port=port',
54
+ 'Cinc server port (Default: 9001)') do |v|
55
+ options[:cinc_port] = v
56
+ end
57
+
58
+
46
59
  opts.separator "\nDatabase Options"
47
60
  opts.on('-o', '--db-host=host',
48
61
  'Postgresql server hostname / IP (Default: localhost)') do |v|
@@ -52,7 +65,7 @@ OptionParser.new do |opts|
52
65
  'Postgresql server port (Default: 5432)') do |v|
53
66
  options[:db_port] = v
54
67
  end
55
- opts.on('-u', '--db-username=username',
68
+ opts.on('-u', '--db-user=username',
56
69
  'Postgresql username (Required)') do |v|
57
70
  options[:db_user] = v
58
71
  end
@@ -92,6 +105,11 @@ end.parse!
92
105
  exit(1)
93
106
  end
94
107
 
108
+ if(options[:populate_airfields] && options[:cinc_enabled])
109
+ puts "You cannot populate airfields AND enable Cinc at the same time"
110
+ exit(1)
111
+ end
112
+
95
113
  TacScribe::Daemon.new(
96
114
  tacview_host: options[:tacview_host],
97
115
  tacview_port: options[:tacview_port],
@@ -105,5 +123,7 @@ TacScribe::Daemon.new(
105
123
  verbose_logging: options[:verbose],
106
124
  thread_count: options[:threads].to_i,
107
125
  populate_airfields: options[:populate_airfields],
108
- whitelist: options[:whitelist]
126
+ whitelist: options[:whitelist],
127
+ cinc_enabled: options[:cinc_enabled],
128
+ cinc_port: options[:cinc_port]
109
129
  ).start_processing
@@ -0,0 +1,162 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'georuby'
4
+ require 'sequel'
5
+ require 'sequel-postgis-georuby'
6
+ require 'singleton'
7
+ require 'concurrent-ruby'
8
+ require 'haversine'
9
+
10
+ module TacScribe
11
+ # The in-memory cache that is updated by events in real-time before the data
12
+ # is synced to the DB
13
+ class Cache
14
+ include Singleton
15
+ include GeoRuby::SimpleFeatures
16
+
17
+ @@cache = Concurrent::Hash.new
18
+
19
+ attr_accessor :reference_latitude, :reference_longitude
20
+
21
+ def data
22
+ @@cache
23
+ end
24
+
25
+ def write_object(object)
26
+ if (reference_latitude != 0 || reference_longitude != 0) && object[:type] != "Ground+Static+Aerodrome"
27
+ localize_position(object)
28
+ end
29
+
30
+ id = object[:object_id]
31
+ id ||= object[:id].to_s
32
+ object[:id] = id
33
+
34
+ cache_object = @@cache[id]
35
+
36
+ object[:position] = set_position(object, cache_object)
37
+
38
+ %i[object_id latitude longitude color country].each do |key|
39
+ object.delete(key)
40
+ end
41
+
42
+ # https://wiki.hoggitworld.com/view/DCS_singleton_coalition
43
+ # Tacview returns a string, CinC returns an integer so we
44
+ # only do the conversion if there is a string.
45
+ if object[:coalition] && object[:coalition].is_a?(String)
46
+ object[:coalition] = case object[:coalition]
47
+ when 'Enemies' # Enemies is Bluefor
48
+ 2
49
+ when 'Allies' # Allies is Redfor
50
+ 1
51
+ else # Neutral
52
+ 0
53
+ end
54
+ end
55
+
56
+ if object[:type] = "Ground+Static+Aerodrome"
57
+ object[:heading] = object[:wind_heading] ? object[:wind_heading] : 0
58
+ object[:speed] = object[:wind_speed] ? object[:wind_speec] : 0
59
+ object.delete(:wind_heading)
60
+ object.delete(:wind_speed)
61
+ object.delete(:category)
62
+ cache_object = object
63
+ # No-op
64
+ elsif cache_object
65
+ object[:heading] = calculate_heading(cache_object, object)
66
+ object[:speed] = calculate_speed(cache_object, object)
67
+ cache_object.merge!(object)
68
+ else
69
+ object[:heading] = -1
70
+ object[:speed] = 0
71
+ cache_object = object
72
+ end
73
+
74
+ if !cache_object.key?(:altitude) || !cache_object[:altitude]
75
+ cache_object[:altitude] = 0
76
+ end
77
+ if !cache_object.key?(:coalition) || !cache_object[:coalition]
78
+ cache_object[:coalition] = 2
79
+ end
80
+
81
+ cache_object[:pilot] = nil unless cache_object.key?(:pilot)
82
+
83
+ cache_object[:group] = nil unless cache_object.key?(:group)
84
+
85
+ cache_object[:deleted] = false unless cache_object.key?(:deleted)
86
+
87
+ cache_object[:updated_at] = Time.now
88
+
89
+ @@cache[id] = cache_object
90
+ end
91
+
92
+ def delete_object(id)
93
+ @@cache[id][:deleted] = true if @@cache[id]
94
+ end
95
+
96
+ def clear
97
+ @@cache.clear
98
+ self.reference_latitude = nil
99
+ self.reference_longitude = nil
100
+ end
101
+
102
+ private
103
+
104
+ def localize_position(object)
105
+ if reference_latitude && object.key?(:latitude)
106
+ object[:latitude] = reference_latitude + object[:latitude]
107
+ end
108
+ if reference_longitude && object.key?(:longitude)
109
+ object[:longitude] = reference_longitude + object[:longitude]
110
+ end
111
+ end
112
+
113
+ def set_position(object, cache_object)
114
+ longitude = if object.key?(:longitude)
115
+ object[:longitude]
116
+ else
117
+ cache_object[:position].x
118
+ end
119
+
120
+ latitude = if object.key?(:latitude)
121
+ object[:latitude]
122
+ else
123
+ cache_object[:position].y
124
+ end
125
+
126
+ # This "Point" class is not lat/long aware which is why we are
127
+ # flipping things up here because when it converts to SQL we need
128
+ # the long to be first since that is what postgresql expects.
129
+ Point.from_x_y(longitude, latitude)
130
+ end
131
+
132
+ def calculate_heading(cache_object, object)
133
+ if cache_object[:position] == object[:position]
134
+ return cache_object[:heading]
135
+ end
136
+
137
+ begin
138
+ cache_object[:position].bearing_to(object[:position]).to_i
139
+ rescue Math::DomainError => e
140
+ puts 'Could not calculate heading: ' + e.message
141
+ puts 'Old Position: ' + cache_object[:position].inspect
142
+ puts 'New Position: ' + object[:position].inspect
143
+ end
144
+ end
145
+
146
+ def calculate_speed(cache_object, object)
147
+ time = object[:game_time] - cache_object[:game_time]
148
+
149
+ start_point = cache_object[:position]
150
+ end_point = object[:position]
151
+
152
+ # Because of the above issues with Point and Lat/Lon
153
+ # the values are reverse here :(
154
+ distance = Haversine.distance(start_point.y, start_point.x,
155
+ end_point.y, end_point.x).to_meters
156
+
157
+ speed = distance / time
158
+
159
+ speed.to_i
160
+ end
161
+ end
162
+ end
@@ -3,7 +3,9 @@
3
3
  require 'json'
4
4
  require 'tacview_client'
5
5
  require 'set'
6
+ require 'cinc'
6
7
  require_relative 'datastore'
8
+ require_relative 'cache'
7
9
  require_relative 'event_queue'
8
10
  require_relative 'event_processor'
9
11
 
@@ -14,7 +16,7 @@ module TacScribe
14
16
  def initialize(db_host:, db_port:, db_name:, db_user:, db_password:,
15
17
  tacview_host:, tacview_port:, tacview_password:,
16
18
  tacview_client_name:, verbose_logging:, thread_count:,
17
- populate_airfields:, whitelist: nil)
19
+ populate_airfields:, whitelist: nil, cinc_enabled:, cinc_port:)
18
20
  Datastore.instance.configure do |config|
19
21
  config.host = db_host
20
22
  config.port = db_port
@@ -24,20 +26,29 @@ module TacScribe
24
26
  end
25
27
  Datastore.instance.connect
26
28
 
27
- @event_queue = EventQueue.new verbose_logging: verbose_logging
29
+ @event_queue = EventQueue.new
30
+
31
+ @verbose_logging = verbose_logging
28
32
 
29
33
  @populate_airfields = populate_airfields
30
34
  @thread_count = thread_count
31
- @processing_threads = []
35
+ @threads = {}
32
36
  @whitelist = Set.new(IO.read(whitelist).split) if whitelist
33
37
 
34
- @client = TacviewClient::Client.new(
38
+ @tacview_client = TacviewClient::Client.new(
35
39
  host: tacview_host,
36
40
  port: tacview_port,
37
41
  password: tacview_password,
38
42
  processor: @event_queue,
39
43
  client_name: tacview_client_name
40
44
  )
45
+
46
+ if cinc_enabled
47
+ @cinc_client = Cinc::Client.new(
48
+ host: tacview_host,
49
+ port: cinc_port
50
+ )
51
+ end
41
52
  end
42
53
 
43
54
  # Starts processing and reconnects if the client was disconnected.
@@ -47,43 +58,126 @@ module TacScribe
47
58
  # for example
48
59
  def start_processing
49
60
  loop do
50
- kill_processing_threads
61
+ puts 'Starting processing loop'
51
62
  @event_queue.clear
52
63
  Datastore.instance.truncate_table
64
+ Cache.instance.clear
53
65
  start_processing_threads
66
+ start_cinc_thread
67
+ start_db_sync_thread
68
+ start_reporting_thread
54
69
  populate_airfields if @populate_airfields
55
- @client.connect
70
+ @threads.each_pair do |key, _value|
71
+ puts "#{key} thread started"
72
+ end
73
+ @tacview_client.connect
74
+ # If this code is executed it means we have been disconnected without
75
+ # exceptions. We will still sleep to stop a retry storm. Just not as
76
+ # long.
77
+ puts "Disconnected from Tacview"
78
+ kill_threads
79
+ sleep 10
56
80
  # Rescuing reliably from Net::HTTP is a complete bear so rescue
57
81
  # StandardError. It ain't pretty but it is reliable. We will puts
58
82
  # the exception just in case
59
83
  # https://stackoverflow.com/questions/5370697/what-s-the-best-way-to-handle-exceptions-from-nethttp
60
84
  rescue StandardError => e
85
+ puts 'Exception in processing loop'
61
86
  puts e.message
62
87
  puts e.backtrace
88
+ kill_threads
63
89
  sleep 30
64
90
  next
65
91
  end
66
92
  end
67
93
 
68
- def kill_processing_threads
69
- @processing_threads.each do |thr|
70
- thr.kill
71
- thr.join
94
+ def kill_threads
95
+ puts 'Killing Threads'
96
+ @threads.each_pair do |key, thread|
97
+ puts "Killing #{key} thread"
98
+ thread.kill
99
+ thread.join
72
100
  end
73
101
  end
74
102
 
75
103
  def start_processing_threads
76
- @thread_count.times do
77
- @processing_threads << Thread.new do
78
- EventProcessor.new(datastore: Datastore.instance,
79
- event_queue: @event_queue,
80
- whitelist: @whitelist).start
104
+ @event_processor = EventProcessor.new(cache: Cache.instance,
105
+ datastore: Datastore.instance,
106
+ event_queue: @event_queue,
107
+ whitelist: @whitelist)
108
+ event_processor_thread = Thread.new do
109
+ @event_processor.start
110
+ end
111
+ event_processor_thread.name = 'Event Processor'
112
+ @threads[:processing] = event_processor_thread
113
+ end
114
+
115
+ def start_cinc_thread
116
+ return unless @cinc_client
117
+ cinc_thread = Thread.new do
118
+ loop do
119
+ @cinc_client.connect
120
+ while true
121
+ @cinc_client.get_airbases.each do |airbase|
122
+ @event_queue.update_object Hash[airbase.map{ |k, v| [k.to_sym, v] }]
123
+ end
124
+ sleep 60
125
+ end
126
+ rescue StandardError => e
127
+ puts 'Exception in cinc thread'
128
+ puts e.message
129
+ puts e.backtrace
130
+ sleep 30
131
+ next
132
+ end
133
+ end
134
+
135
+ cinc_thread.name = 'Cinc Processor'
136
+ @threads[:cinc] = cinc_thread
137
+ end
138
+
139
+ def start_db_sync_thread
140
+ db_write_thread = Thread.new do
141
+ loop do
142
+ sleep 1
143
+ deleted = Datastore.instance.write_objects(Cache.instance.data.values)
144
+ deleted.each { |id| Cache.instance.data.delete(id) }
145
+ rescue StandardError
146
+ next
147
+ end
148
+ end
149
+ db_write_thread.name = 'Database Writing'
150
+ @threads[:database] = db_write_thread
151
+ end
152
+
153
+ def start_reporting_thread
154
+ return unless @verbose_logging
155
+
156
+ reporting_thread = Thread.new do
157
+ sleep 5
158
+ loop do
159
+ puts "#{Time.now.strftime('%FT%T')}\t" \
160
+ "Events Incoming: #{@event_queue.events_written}\t" \
161
+ "Processed: #{@event_processor.events_processed}\t" \
162
+ "Ignored: #{@event_processor.events_ignored}\t" \
163
+ "Queue Size: #{@event_queue.size}\t" \
164
+ "Objects Written: #{Datastore.instance.written}\t" \
165
+ "Deleted: #{Datastore.instance.deleted}"
166
+ @event_queue.events_written = 0
167
+ @event_processor.events_processed = 0
168
+ @event_processor.events_ignored = 0
169
+ Datastore.instance.written = 0
170
+ Datastore.instance.deleted = 0
171
+ sleep 1
81
172
  end
82
173
  end
174
+ reporting_thread.name = 'Reporting'
175
+ @threads[:reporting] = reporting_thread
83
176
  end
84
177
 
85
178
  def populate_airfields
86
- json = File.read(File.join(File.dirname(__FILE__), '../../data/airfields.json'))
179
+ json = File.read(File.join(File.dirname(__FILE__),
180
+ '../../data/airfields.json'))
87
181
  airfields = JSON.parse(json)
88
182
  airfields.each_with_index do |airfield, i|
89
183
  @event_queue.update_object(
@@ -93,7 +187,7 @@ module TacScribe
93
187
  altitude: BigDecimal(airfield['alt'].to_s),
94
188
  type: 'Ground+Static+Aerodrome',
95
189
  name: airfield['name'],
96
- coalition: 2
190
+ coalition: 0 # Neutral
97
191
  )
98
192
  end
99
193
  end
@@ -14,7 +14,7 @@ module TacScribe
14
14
  include Singleton
15
15
  include GeoRuby::SimpleFeatures
16
16
 
17
- attr_accessor :db
17
+ attr_accessor :db, :written, :deleted
18
18
 
19
19
  @configuration = nil
20
20
  @db = nil
@@ -52,24 +52,29 @@ module TacScribe
52
52
  @db[:units].truncate
53
53
  end
54
54
 
55
- def write_object(event, timestamp)
56
- unit = get_unit(event[:object_id])
57
-
58
- # Tacview omits values that don't change to save
59
- # data bandwidth and storage. If something has not changed then
60
- # use the old value
61
- current_position = get_position(event, unit)
62
-
63
- if unit
64
- update_unit(event, unit, current_position, timestamp)
65
- else
66
- insert_unit(event, current_position, timestamp)
55
+ def write_objects(objects)
56
+ objects = objects.map do |object|
57
+ obj = object.clone
58
+ obj.delete(:game_time)
59
+ obj
67
60
  end
68
- end
69
61
 
70
- def delete_object(object_id)
71
- count = @db[:units].where(id: object_id).delete
72
- "Deleted #{object_id} #{object_id.class} - #{count}"
62
+ @db[:units].insert_conflict(
63
+ constraint: :units_pkey,
64
+ update: { position: Sequel[:excluded][:position],
65
+ altitude: Sequel[:excluded][:altitude],
66
+ heading: Sequel[:excluded][:heading],
67
+ speed: Sequel[:excluded][:speed],
68
+ updated_at: Sequel[:excluded][:updated_at],
69
+ deleted: Sequel[:excluded][:deleted] }
70
+ )
71
+ .multi_insert(objects)
72
+ deleted_ids = @db[:units].where(deleted: true).select_map(:id)
73
+ @db[:units].where(deleted: true).delete
74
+ self.written = objects.size
75
+ self.deleted = deleted_ids.size
76
+
77
+ deleted_ids
73
78
  end
74
79
 
75
80
  private
@@ -85,56 +90,5 @@ module TacScribe
85
90
  "/#{@configuration.database}"
86
91
  end
87
92
  end
88
-
89
- def get_unit(id)
90
- @db[:units].where(id: id).first
91
- end
92
-
93
- def update_unit(event, unit, current_position, timestamp)
94
- heading = calculate_heading(unit[:position],
95
- current_position,
96
- unit[:heading])
97
-
98
- @db[:units].where(id: event[:object_id]).update(
99
- position: current_position[:lat_lon],
100
- altitude: current_position[:altitude],
101
- heading: heading,
102
- updated_at: timestamp
103
- )
104
- end
105
-
106
- def insert_unit(event, current_position, timestamp)
107
- @db[:units].insert(id: event[:object_id],
108
- position: current_position[:lat_lon],
109
- altitude: current_position[:altitude],
110
- type: event[:type],
111
- name: event[:name],
112
- group: event[:group],
113
- pilot: event[:pilot],
114
- coalition: event[:coalition] == 'Allies' ? 0 : 1,
115
- updated_at: timestamp)
116
- end
117
-
118
- def get_position(event, unit)
119
- {
120
- lat_lon: Point.from_x_y(
121
- event.fetch(:longitude) { unit ? unit[:position].y : nil },
122
- event.fetch(:latitude) { unit ? unit[:position].x : nil }
123
- ),
124
- altitude: event.fetch(:altitude) { unit ? unit[:altitude] : nil }
125
- }
126
- end
127
-
128
- def calculate_heading(old_position, current_position, current_heading)
129
- return current_heading if old_position == current_position[:lat_lon]
130
-
131
- begin
132
- old_position.bearing_to(current_position[:lat_lon]).to_i
133
- rescue Math::DomainError => e
134
- puts 'Could not calculate heading: ' + e.message
135
- puts 'Old Position: ' + old_position.inspect
136
- puts 'New Position: ' + current_position.inspect
137
- end
138
- end
139
93
  end
140
94
  end
@@ -3,26 +3,32 @@
3
3
  require 'tacview_client/base_processor'
4
4
  require 'time'
5
5
  require 'concurrent-ruby'
6
- require_relative 'datastore'
6
+ require_relative 'cache'
7
7
 
8
8
  module TacScribe
9
9
  # Processes the events emitted by the Ruby Tacview Client
10
10
  class EventProcessor
11
11
  @@ignored_units = Concurrent::Set.new
12
12
 
13
- def initialize(datastore:, event_queue:, whitelist: nil)
13
+ attr_accessor :events_processed, :events_ignored
14
+
15
+ def initialize(cache:, datastore:, event_queue:, whitelist: nil)
16
+ @cache = cache
14
17
  @datastore = datastore
15
18
  @event_queue = event_queue
16
19
  @whitelist = whitelist
20
+ self.events_processed = 0
21
+ self.events_ignored = 0
17
22
  end
18
23
 
19
24
  def start
20
25
  loop do
21
- wrapped_event = @event_queue.get_event
26
+ wrapped_event = @event_queue.shift
22
27
  process_event(wrapped_event)
23
28
  rescue StandardError => e
24
29
  puts wrapped_event
25
30
  puts e.inspect
31
+ puts e.backtrace
26
32
  next
27
33
  end
28
34
  end
@@ -33,6 +39,10 @@ module TacScribe
33
39
  update_object(wrapped_event[:event], wrapped_event[:time])
34
40
  when :delete_object
35
41
  delete_object(wrapped_event[:object_id])
42
+ when :set_latitude
43
+ update_latitude wrapped_event[:value]
44
+ when :set_longitude
45
+ update_longitude wrapped_event[:value]
36
46
  end
37
47
  end
38
48
 
@@ -56,44 +66,50 @@ module TacScribe
56
66
  # @option event [BigDecimal] :altitude The object altitude above sea level
57
67
  # in meters to 1 decimal place.
58
68
  def update_object(event, time)
59
- return if @@ignored_units.include?(event[:object_id])
69
+ return if ignore_unit?(event)
60
70
 
61
- if @whitelist && event[:type] && !@whitelist.include?(event[:type])
62
- @@ignored_units << event[:object_id]
63
- return
71
+ # Hack to make sure the :name field is present so that it is included
72
+ # in the SQL
73
+ event[:name] = nil unless event.has_key?(:name)
74
+ event[:game_time] = time
75
+
76
+ self.events_processed += 1
77
+ @cache.write_object(event)
78
+ end
79
+
80
+ def ignore_unit?(event)
81
+ if @@ignored_units.include?(event[:object_id])
82
+ self.events_ignored += 1
83
+ return true
64
84
  end
65
85
 
66
- if @event_queue.reference_latitude || @event_queue.reference_longitude
67
- localize_position(event)
86
+ if @whitelist && event[:type] && !@whitelist.include?(event[:type])
87
+ @@ignored_units << event[:object_id]
88
+ self.events_ignored += 1
89
+ return true
68
90
  end
69
91
 
70
- @datastore.write_object(event, time)
92
+ false
71
93
  end
72
94
 
73
95
  # Process a delete event for an object
74
96
  #
75
- # @param object_id [String] A hexadecimal number representing the object
76
- # ID
77
- def delete_object(object_id)
78
- return if @@ignored_units.delete?(object_id)
97
+ # @param id [String] A hexadecimal number representing the object ID
98
+ def delete_object(id)
99
+ if @@ignored_units.delete?(id)
100
+ nil
101
+ else
102
+ @cache.delete_object(id)
103
+ end
104
+ self.events_processed += 1
105
+ end
79
106
 
80
- @datastore.delete_object(object_id)
107
+ def update_latitude(value)
108
+ Cache.instance.reference_latitude = value
81
109
  end
82
110
 
83
- # If we have reference lat/long then use that as a base and update the event
84
- # position
85
- def localize_position(event)
86
- # There is no combination of layouts for these lines that doesn' trip
87
- # at least one rubocop cop so just ignore the guard clause. The best
88
- # one is using an inline if but that breaks the line length.
89
- # rubocop:disable Style/GuardClause
90
- if event[:latitude]
91
- event[:latitude] = @event_queue.reference_latitude + event[:latitude]
92
- end
93
- if event[:longitude]
94
- event[:longitude] = @event_queue.reference_longitude + event[:longitude]
95
- end
96
- # rubocop:enable Style/GuardClause
111
+ def update_longitude(value)
112
+ Cache.instance.reference_longitude = value
97
113
  end
98
114
  end
99
115
  end
@@ -6,32 +6,22 @@ require 'time'
6
6
  module TacScribe
7
7
  # Processes the events emitted by the Ruby Tacview Client
8
8
  class EventQueue < TacviewClient::BaseProcessor
9
- attr_accessor :reference_latitude, :reference_longitude
9
+ attr_accessor :events_written
10
10
 
11
- def initialize(verbose_logging:)
12
- @verbose_logging = verbose_logging
11
+ def initialize
13
12
  @events = Queue.new
14
- @event_write = 0
15
- @event_read = 0
16
-
17
- return unless verbose_logging == true
18
-
19
- Thread.new do
20
- loop do
21
- puts "#{Time.now.strftime('%FT%T')} - Queue Size: #{@events.size} \t Events Added: #{@event_write} \t Events Processed: #{@event_read}"
22
- @event_write = 0
23
- @event_read = 0
24
- sleep 1
25
- end
26
- end
13
+ self.events_written = 0
27
14
  end
28
15
 
29
16
  def clear
30
17
  @events.clear
31
18
  end
32
19
 
33
- def get_event
34
- @event_read += 1
20
+ def size
21
+ @events.size
22
+ end
23
+
24
+ def shift
35
25
  @events.shift
36
26
  end
37
27
 
@@ -53,7 +43,7 @@ module TacScribe
53
43
  # @option event [BigDecimal] :altitude The object altitude above sea level
54
44
  # in meters to 1 decimal place.
55
45
  def update_object(event)
56
- @event_write += 1
46
+ self.events_written += 1
57
47
  @events << { type: :update_object, event: event, time: @time }
58
48
  end
59
49
 
@@ -62,7 +52,7 @@ module TacScribe
62
52
  # @param object_id [String] A hexadecimal number representing the object
63
53
  # ID
64
54
  def delete_object(object_id)
65
- @event_write += 1
55
+ self.events_written += 1
66
56
  @events << { type: :delete_object, object_id: object_id }
67
57
  end
68
58
 
@@ -81,9 +71,9 @@ module TacScribe
81
71
  def set_property(property:, value:)
82
72
  case property
83
73
  when 'ReferenceLatitude'
84
- self.reference_latitude = BigDecimal(value)
74
+ @events << { type: :set_latitude, value: BigDecimal(value) }
85
75
  when 'ReferenceLongitude'
86
- self.reference_longitude = BigDecimal(value)
76
+ @events << { type: :set_longitude, value: BigDecimal(value) }
87
77
  when 'ReferenceTime'
88
78
  @reference_time = @time = Time.parse(value)
89
79
  end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module TacScribe
4
- VERSION = '0.3.0'
4
+ VERSION = '0.7.3'
5
5
  end
@@ -4,6 +4,8 @@ lib = File.expand_path('lib', __dir__)
4
4
  $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
5
5
  require 'tac_scribe/version'
6
6
 
7
+ puts "RUBY_PLATFORM = #{RUBY_PLATFORM}"
8
+
7
9
  Gem::Specification.new do |spec|
8
10
  spec.name = 'tac_scribe'
9
11
  spec.version = TacScribe::VERSION
@@ -24,9 +26,9 @@ Gem::Specification.new do |spec|
24
26
 
25
27
  spec.metadata['yard.run'] = 'yri'
26
28
 
29
+ spec.platform = RUBY_PLATFORM == 'java' ? 'java' : 'ruby'
30
+
27
31
  if RUBY_PLATFORM == 'java'
28
- # TODO: Specifying a verison chokes bundler for some reason
29
- spec.platform = 'java'
30
32
  spec.add_dependency 'activerecord-jdbcpostgresql-adapter'
31
33
  else
32
34
  spec.add_dependency 'pg', '~>1.1'
@@ -38,8 +40,10 @@ Gem::Specification.new do |spec|
38
40
  # of an API breaking change are higher then normal. Therefore lock the
39
41
  # version
40
42
  spec.add_dependency 'concurrent-ruby', '~>1.1.5'
43
+ spec.add_dependency 'haversine', '~>0.3'
41
44
  spec.add_dependency 'sequel-postgis-georuby', '0.1.2'
42
45
  spec.add_dependency 'tacview_client', '~>0.1'
46
+ spec.add_dependency 'cinc', '~>0.1'
43
47
 
44
48
  spec.add_development_dependency 'bundler', '~> 2.0'
45
49
  spec.add_development_dependency 'rake', '~> 10.0'
@@ -47,4 +51,5 @@ Gem::Specification.new do |spec|
47
51
  spec.add_development_dependency 'rubocop', '~>0.73'
48
52
  spec.add_development_dependency 'simplecov', '~>0.17'
49
53
  spec.add_development_dependency 'yard', '~>0.9'
54
+ spec.add_development_dependency 'pry-byebug', '~>3.9'
50
55
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: tac_scribe
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.3.0
4
+ version: 0.7.3
5
5
  platform: java
6
6
  authors:
7
7
  - Jeffrey Jones
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2019-10-26 00:00:00.000000000 Z
11
+ date: 2020-08-09 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  requirement: !ruby/object:Gem::Requirement
@@ -66,6 +66,20 @@ dependencies:
66
66
  - - "~>"
67
67
  - !ruby/object:Gem::Version
68
68
  version: 1.1.5
69
+ - !ruby/object:Gem::Dependency
70
+ requirement: !ruby/object:Gem::Requirement
71
+ requirements:
72
+ - - "~>"
73
+ - !ruby/object:Gem::Version
74
+ version: '0.3'
75
+ name: haversine
76
+ prerelease: false
77
+ type: :runtime
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - "~>"
81
+ - !ruby/object:Gem::Version
82
+ version: '0.3'
69
83
  - !ruby/object:Gem::Dependency
70
84
  requirement: !ruby/object:Gem::Requirement
71
85
  requirements:
@@ -94,6 +108,20 @@ dependencies:
94
108
  - - "~>"
95
109
  - !ruby/object:Gem::Version
96
110
  version: '0.1'
111
+ - !ruby/object:Gem::Dependency
112
+ requirement: !ruby/object:Gem::Requirement
113
+ requirements:
114
+ - - "~>"
115
+ - !ruby/object:Gem::Version
116
+ version: '0.1'
117
+ name: cinc
118
+ prerelease: false
119
+ type: :runtime
120
+ version_requirements: !ruby/object:Gem::Requirement
121
+ requirements:
122
+ - - "~>"
123
+ - !ruby/object:Gem::Version
124
+ version: '0.1'
97
125
  - !ruby/object:Gem::Dependency
98
126
  requirement: !ruby/object:Gem::Requirement
99
127
  requirements:
@@ -178,6 +206,20 @@ dependencies:
178
206
  - - "~>"
179
207
  - !ruby/object:Gem::Version
180
208
  version: '0.9'
209
+ - !ruby/object:Gem::Dependency
210
+ requirement: !ruby/object:Gem::Requirement
211
+ requirements:
212
+ - - "~>"
213
+ - !ruby/object:Gem::Version
214
+ version: '3.9'
215
+ name: pry-byebug
216
+ prerelease: false
217
+ type: :development
218
+ version_requirements: !ruby/object:Gem::Requirement
219
+ requirements:
220
+ - - "~>"
221
+ - !ruby/object:Gem::Version
222
+ version: '3.9'
181
223
  description: Write Tacview data to PostGIS database
182
224
  email:
183
225
  - jeff@jones.be
@@ -201,10 +243,12 @@ files:
201
243
  - bin/console
202
244
  - bin/setup
203
245
  - data/airfields.json
246
+ - data/whitelist.example
204
247
  - db/001_create_unit_table.rb
205
248
  - db/README.md
206
249
  - exe/tac_scribe
207
250
  - lib/tac_scribe.rb
251
+ - lib/tac_scribe/cache.rb
208
252
  - lib/tac_scribe/daemon.rb
209
253
  - lib/tac_scribe/datastore.rb
210
254
  - lib/tac_scribe/event_processor.rb
@@ -233,7 +277,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
233
277
  version: '0'
234
278
  requirements: []
235
279
  rubyforge_project:
236
- rubygems_version: 2.7.6
280
+ rubygems_version: 2.7.10
237
281
  signing_key:
238
282
  specification_version: 4
239
283
  summary: Write Tacview data to PostGIS database