tac_scribe 0.2.1-java → 0.7.1-java

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: ad06d7c2f742a8d35c641bf120a6f95391902ea57719669cc53cd80186a14493
4
- data.tar.gz: e35b648c32b18fe4628ee4a3a46adb6a378f2639d2e85a9cd6743b5d57624f7d
3
+ metadata.gz: 9dc052878565aa0a92dca2eaf35fceec802af0ee74b7f316ea4efb2d243cef81
4
+ data.tar.gz: 601360e067fbf0812f9e4e77af1eb1ed724544a01db91a8df81e4c0e903922d9
5
5
  SHA512:
6
- metadata.gz: 9a0fb2a75b2d1c66ff44aca4bbcdb3bf905032c20f631230c4491cfc8c696de3059b947e89c245b30c624d6c89a37f20bb9f24a38e0acc876bfefb409b04a442
7
- data.tar.gz: 3502da2ea575e6a40a87599c2a117ccdbf12417b59feef9dd92628c9f56c46a1b642795203437d7b81321eda4c4c64e67e23d5203cb8ec4cf1c63bf6529c5f97
6
+ metadata.gz: ba986646a11ca3315dd2a6b3e54b0c1ff6766f239d73540779645055ddd2710e32e28a7409dffea1482a5b50d88f08f9ed4a80e9b65ed5152daa90e9965d457f
7
+ data.tar.gz: 519a7874ef8a1f9ec48f34626e5b34e05850c0a532228dd8dd175a96c5fae39ae531792bea547ecb98530df96c5c9745dc714a834cbed712c4ae9dfe5e26ff09
@@ -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
- ruby-2.6.3
1
+ ruby-2.7.0
@@ -4,7 +4,50 @@ All notable changes to this project will be documented in this file.
4
4
  The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
5
5
  and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
6
6
 
7
- ## [Unreleased]
7
+ ## [0.7.1]
8
+ ### Changed
9
+ - Fixed issue where "name" fields were not being populated
10
+ if airfields were not being loaded first
11
+
12
+ ## [0.7.0]
13
+ ### Changed
14
+ - Synced Lat/Lon calculations with DCS and fixed issue where
15
+ calculations were incorrect if only one value was updated
16
+
17
+ ## [0.6.2]
18
+ ### Changed
19
+ - Fixed typo causing app to fail
20
+
21
+ ## [0.6.1]
22
+ ### Changed
23
+ - Test build for gem issue
24
+
25
+ ## [0.6.0]
26
+ ### Added
27
+ - Calculates and stores speed of units
28
+
29
+ ### Fixed
30
+ - Made more robust against failures and more logging messages
31
+
32
+ ## [0.5.0]
33
+ ### Changed
34
+ - Added missing db column to migration file
35
+ - Clear the cache on server restart
36
+ - Add more info to logging
37
+
38
+ ## [0.4.0]
39
+ ### Changed
40
+ - Switch to periodic writing to the database
41
+
42
+ ## [0.3.0] - 2019-10-27
43
+ ### Changed
44
+ - Changed the command-line name to match gem
45
+
46
+ ### Fixed
47
+ - Sleep between connection attempts
48
+
49
+ ### Added
50
+ - Unit type filtering
8
51
 
9
52
  ## [0.2.1] - 2019-10-13
10
53
  ### Changed
data/README.md CHANGED
@@ -21,9 +21,32 @@ Or install it yourself as:
21
21
 
22
22
  ## Usage
23
23
 
24
- You can run the tool using the `start_scribe` command. Use the `--help`
24
+ You can run the tool using the `tac_scribe` command. Use the `--help`
25
25
  option for information on required arguments
26
26
 
27
+ ### Whitelist
28
+
29
+ If you want to restrict what units get written to the database (e.g. for
30
+ performance reasons) then create a whitelist file and pass that in with
31
+ the whitelist option. For example if you only care about air units
32
+ and landing areas create a file like the following:
33
+
34
+ `whitelist.txt`
35
+
36
+ With the following content
37
+
38
+ ```
39
+ Sea+Watercraft+AircraftCarrier
40
+ Air+Rotorcraft
41
+ Air+FixedWing
42
+ Ground+Static+Aerodrome
43
+ Navaid+Static+Bullseye
44
+ ```
45
+
46
+ and add `-w whitelist.txt` to the command-line.
47
+
48
+ The unit type is the type provided by Tacview and it must be an exact match
49
+
27
50
  ## Development
28
51
 
29
52
  After checking out the repo, run `bin/setup` to install dependencies. Then,
@@ -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
@@ -15,7 +15,8 @@ options = {
15
15
  db_name: 'tac_scribe',
16
16
  verbose_logging: false,
17
17
  threads: 1,
18
- populate_airfields: false
18
+ populate_airfields: false,
19
+ whitelist: nil
19
20
  }
20
21
 
21
22
  OptionParser.new do |opts|
@@ -51,7 +52,7 @@ OptionParser.new do |opts|
51
52
  'Postgresql server port (Default: 5432)') do |v|
52
53
  options[:db_port] = v
53
54
  end
54
- opts.on('-u', '--db-username=username',
55
+ opts.on('-u', '--db-user=username',
55
56
  'Postgresql username (Required)') do |v|
56
57
  options[:db_user] = v
57
58
  end
@@ -76,6 +77,10 @@ OptionParser.new do |opts|
76
77
  'Verbose logging') do |_v|
77
78
  options[:verbose] = true
78
79
  end
80
+ opts.on('-w', '--whitelist=whitelist',
81
+ 'Whitelist filename') do |v|
82
+ options[:whitelist] = v
83
+ end
79
84
  end.parse!
80
85
 
81
86
  %i[db_user db_password].each do |parameter|
@@ -99,5 +104,6 @@ TacScribe::Daemon.new(
99
104
  db_password: options[:db_password],
100
105
  verbose_logging: options[:verbose],
101
106
  thread_count: options[:threads].to_i,
102
- populate_airfields: options[:populate_airfields]
107
+ populate_airfields: options[:populate_airfields],
108
+ whitelist: options[:whitelist]
103
109
  ).start_processing
@@ -0,0 +1,151 @@
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
27
+ localize_position(object)
28
+ end
29
+
30
+ id = object[:object_id]
31
+ object[:id] = id
32
+
33
+ cache_object = @@cache[id]
34
+
35
+ object[:position] = set_position(object, cache_object)
36
+
37
+ %i[object_id latitude longitude color country].each do |key|
38
+ object.delete(key)
39
+ end
40
+
41
+ # https://wiki.hoggitworld.com/view/DCS_singleton_coalition
42
+ if object[:coalition]
43
+ object[:coalition] = case object[:coalition]
44
+ when 'Enemies' # Enemies is Bluefor
45
+ 2
46
+ when 'Allies' # Allies is Redfor
47
+ 1
48
+ else # Neutral
49
+ 0
50
+ end
51
+ end
52
+
53
+ if cache_object
54
+ object[:heading] = calculate_heading(cache_object, object)
55
+ object[:speed] = calculate_speed(cache_object, object)
56
+ cache_object.merge!(object)
57
+ else
58
+ object[:heading] = -1
59
+ object[:speed] = 0
60
+ cache_object = object
61
+ end
62
+
63
+ if !cache_object.key?(:altitude) || !cache_object[:altitude]
64
+ cache_object[:altitude] = 0
65
+ end
66
+ if !cache_object.key?(:coalition) || !cache_object[:coalition]
67
+ cache_object[:coalition] = 2
68
+ end
69
+
70
+ cache_object[:pilot] = nil unless cache_object.key?(:pilot)
71
+
72
+ cache_object[:group] = nil unless cache_object.key?(:group)
73
+
74
+ cache_object[:deleted] = false unless cache_object.key?(:deleted)
75
+
76
+ cache_object[:updated_at] = Time.now
77
+
78
+ @@cache[id] = cache_object
79
+ end
80
+
81
+ def delete_object(id)
82
+ @@cache[id][:deleted] = true if @@cache[id]
83
+ end
84
+
85
+ def clear
86
+ @@cache.clear
87
+ self.reference_latitude = nil
88
+ self.reference_longitude = nil
89
+ end
90
+
91
+ private
92
+
93
+ def localize_position(object)
94
+ if reference_latitude && object.key?(:latitude)
95
+ object[:latitude] = reference_latitude + object[:latitude]
96
+ end
97
+ if reference_longitude && object.key?(:longitude)
98
+ object[:longitude] = reference_longitude + object[:longitude]
99
+ end
100
+ end
101
+
102
+ def set_position(object, cache_object)
103
+ longitude = if object.key?(:longitude)
104
+ object[:longitude]
105
+ else
106
+ cache_object[:position].x
107
+ end
108
+
109
+ latitude = if object.key?(:latitude)
110
+ object[:latitude]
111
+ else
112
+ cache_object[:position].y
113
+ end
114
+
115
+ # This "Point" class is not lat/long aware which is why we are
116
+ # flipping things up here because when it converts to SQL we need
117
+ # the long to be first since that is what postgresql expects.
118
+ Point.from_x_y(longitude, latitude)
119
+ end
120
+
121
+ def calculate_heading(cache_object, object)
122
+ if cache_object[:position] == object[:position]
123
+ return cache_object[:heading]
124
+ end
125
+
126
+ begin
127
+ cache_object[:position].bearing_to(object[:position]).to_i
128
+ rescue Math::DomainError => e
129
+ puts 'Could not calculate heading: ' + e.message
130
+ puts 'Old Position: ' + cache_object[:position].inspect
131
+ puts 'New Position: ' + object[:position].inspect
132
+ end
133
+ end
134
+
135
+ def calculate_speed(cache_object, object)
136
+ time = object[:game_time] - cache_object[:game_time]
137
+
138
+ start_point = cache_object[:position]
139
+ end_point = object[:position]
140
+
141
+ # Because of the above issues with Point and Lat/Lon
142
+ # the values are reverse here :(
143
+ distance = Haversine.distance(start_point.y, start_point.x,
144
+ end_point.y, end_point.x).to_meters
145
+
146
+ speed = distance / time
147
+
148
+ speed.to_i
149
+ end
150
+ end
151
+ end
@@ -2,7 +2,9 @@
2
2
 
3
3
  require 'json'
4
4
  require 'tacview_client'
5
+ require 'set'
5
6
  require_relative 'datastore'
7
+ require_relative 'cache'
6
8
  require_relative 'event_queue'
7
9
  require_relative 'event_processor'
8
10
 
@@ -13,7 +15,7 @@ module TacScribe
13
15
  def initialize(db_host:, db_port:, db_name:, db_user:, db_password:,
14
16
  tacview_host:, tacview_port:, tacview_password:,
15
17
  tacview_client_name:, verbose_logging:, thread_count:,
16
- populate_airfields:)
18
+ populate_airfields:, whitelist: nil)
17
19
  Datastore.instance.configure do |config|
18
20
  config.host = db_host
19
21
  config.port = db_port
@@ -23,11 +25,14 @@ module TacScribe
23
25
  end
24
26
  Datastore.instance.connect
25
27
 
26
- @event_queue = EventQueue.new verbose_logging: verbose_logging
28
+ @event_queue = EventQueue.new
29
+
30
+ @verbose_logging = verbose_logging
27
31
 
28
32
  @populate_airfields = populate_airfields
29
33
  @thread_count = thread_count
30
- @processing_threads = []
34
+ @threads = {}
35
+ @whitelist = Set.new(IO.read(whitelist).split) if whitelist
31
36
 
32
37
  @client = TacviewClient::Client.new(
33
38
  host: tacview_host,
@@ -45,53 +50,112 @@ module TacScribe
45
50
  # for example
46
51
  def start_processing
47
52
  loop do
48
- kill_processing_threads
53
+ puts 'Starting processing loop'
49
54
  @event_queue.clear
50
55
  Datastore.instance.truncate_table
56
+ Cache.instance.clear
51
57
  start_processing_threads
58
+ start_db_sync_thread
59
+ start_reporting_thread
52
60
  populate_airfields if @populate_airfields
61
+ @threads.each_pair do |key, _value|
62
+ puts "#{key} thread started"
63
+ end
53
64
  @client.connect
54
- sleep 30
65
+ # If this code is executed it means we have been disconnected without
66
+ # exceptions. We will still sleep to stop a retry storm. Just not as
67
+ # long.
68
+ puts "Disconnected from Tacview"
69
+ kill_threads
70
+ sleep 10
55
71
  # Rescuing reliably from Net::HTTP is a complete bear so rescue
56
72
  # StandardError. It ain't pretty but it is reliable. We will puts
57
73
  # the exception just in case
58
74
  # https://stackoverflow.com/questions/5370697/what-s-the-best-way-to-handle-exceptions-from-nethttp
59
75
  rescue StandardError => e
76
+ puts 'Exception in processing loop'
60
77
  puts e.message
61
78
  puts e.backtrace
79
+ kill_threads
80
+ sleep 30
62
81
  next
63
82
  end
64
83
  end
65
84
 
66
- def kill_processing_threads
67
- @processing_threads.each do |thr|
68
- thr.kill
69
- thr.join
85
+ def kill_threads
86
+ puts 'Killing Threads'
87
+ @threads.each_pair do |key, thread|
88
+ puts "Killing #{key} thread"
89
+ thread.kill
90
+ thread.join
70
91
  end
71
92
  end
72
93
 
73
94
  def start_processing_threads
74
- @thread_count.times do
75
- @processing_threads << Thread.new do
76
- EventProcessor.new(datastore: Datastore.instance,
77
- event_queue: @event_queue).start
95
+ @event_processor = EventProcessor.new(cache: Cache.instance,
96
+ datastore: Datastore.instance,
97
+ event_queue: @event_queue,
98
+ whitelist: @whitelist)
99
+ event_processor_thread = Thread.new do
100
+ @event_processor.start
101
+ end
102
+ event_processor_thread.name = 'Event Processor'
103
+ @threads[:processing] = event_processor_thread
104
+ end
105
+
106
+ def start_db_sync_thread
107
+ db_write_thread = Thread.new do
108
+ loop do
109
+ sleep 1
110
+ deleted = Datastore.instance.write_objects(Cache.instance.data.values)
111
+ deleted.each { |id| Cache.instance.data.delete(id) }
112
+ rescue StandardError
113
+ next
114
+ end
115
+ end
116
+ db_write_thread.name = 'Database Writing'
117
+ @threads[:database] = db_write_thread
118
+ end
119
+
120
+ def start_reporting_thread
121
+ return unless @verbose_logging
122
+
123
+ reporting_thread = Thread.new do
124
+ sleep 5
125
+ loop do
126
+ puts "#{Time.now.strftime('%FT%T')}\t" \
127
+ "Events Incoming: #{@event_queue.events_written}\t" \
128
+ "Processed: #{@event_processor.events_processed}\t" \
129
+ "Ignored: #{@event_processor.events_ignored}\t" \
130
+ "Queue Size: #{@event_queue.size}\t" \
131
+ "Objects Written: #{Datastore.instance.written}\t" \
132
+ "Deleted: #{Datastore.instance.deleted}"
133
+ @event_queue.events_written = 0
134
+ @event_processor.events_processed = 0
135
+ @event_processor.events_ignored = 0
136
+ Datastore.instance.written = 0
137
+ Datastore.instance.deleted = 0
138
+ sleep 1
78
139
  end
79
140
  end
141
+ reporting_thread.name = 'Reporting'
142
+ @threads[:reporting] = reporting_thread
80
143
  end
81
144
 
82
145
  def populate_airfields
83
- json = File.read(File.join(File.dirname(__FILE__), '../../data/airfields.json'))
146
+ json = File.read(File.join(File.dirname(__FILE__),
147
+ '../../data/airfields.json'))
84
148
  airfields = JSON.parse(json)
85
149
  airfields.each_with_index do |airfield, i|
86
- @event_queue.update_object({
87
- object_id: (45_000_000 + i).to_s,
88
- latitude: BigDecimal(airfield['lat'].to_s),
89
- longitude: BigDecimal(airfield['lon'].to_s),
90
- altitude: BigDecimal(airfield['alt'].to_s),
91
- type: 'Ground+Static+Aerodrome',
92
- name: airfield['name'],
93
- coalition: 2
94
- })
150
+ @event_queue.update_object(
151
+ object_id: (45_000_000 + i).to_s,
152
+ latitude: BigDecimal(airfield['lat'].to_s),
153
+ longitude: BigDecimal(airfield['lon'].to_s),
154
+ altitude: BigDecimal(airfield['alt'].to_s),
155
+ type: 'Ground+Static+Aerodrome',
156
+ name: airfield['name'],
157
+ coalition: 0 # Neutral
158
+ )
95
159
  end
96
160
  end
97
161
  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
@@ -2,23 +2,33 @@
2
2
 
3
3
  require 'tacview_client/base_processor'
4
4
  require 'time'
5
- require_relative 'datastore'
5
+ require 'concurrent-ruby'
6
+ require_relative 'cache'
6
7
 
7
8
  module TacScribe
8
9
  # Processes the events emitted by the Ruby Tacview Client
9
10
  class EventProcessor
10
- def initialize(datastore:, event_queue:)
11
+ @@ignored_units = Concurrent::Set.new
12
+
13
+ attr_accessor :events_processed, :events_ignored
14
+
15
+ def initialize(cache:, datastore:, event_queue:, whitelist: nil)
16
+ @cache = cache
11
17
  @datastore = datastore
12
18
  @event_queue = event_queue
19
+ @whitelist = whitelist
20
+ self.events_processed = 0
21
+ self.events_ignored = 0
13
22
  end
14
23
 
15
24
  def start
16
25
  loop do
17
- wrapped_event = @event_queue.get_event
26
+ wrapped_event = @event_queue.shift
18
27
  process_event(wrapped_event)
19
28
  rescue StandardError => e
20
29
  puts wrapped_event
21
30
  puts e.inspect
31
+ puts e.backtrace
22
32
  next
23
33
  end
24
34
  end
@@ -29,6 +39,10 @@ module TacScribe
29
39
  update_object(wrapped_event[:event], wrapped_event[:time])
30
40
  when :delete_object
31
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]
32
46
  end
33
47
  end
34
48
 
@@ -52,35 +66,50 @@ module TacScribe
52
66
  # @option event [BigDecimal] :altitude The object altitude above sea level
53
67
  # in meters to 1 decimal place.
54
68
  def update_object(event, time)
55
- if @event_queue.reference_latitude || @event_queue.reference_longitude
56
- localize_position(event)
69
+ return if ignore_unit?(event)
70
+
71
+ # Hack to make sure the :name field is present so that it is included
72
+ # in the SQL
73
+ event[:name] = "" 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
57
84
  end
58
85
 
59
- @datastore.write_object(event, time)
86
+ if @whitelist && event[:type] && !@whitelist.include?(event[:type])
87
+ @@ignored_units << event[:object_id]
88
+ self.events_ignored += 1
89
+ return true
90
+ end
91
+
92
+ false
60
93
  end
61
94
 
62
95
  # Process a delete event for an object
63
96
  #
64
- # @param object_id [String] A hexadecimal number representing the object
65
- # ID
66
- def delete_object(object_id)
67
- @datastore.delete_object(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
68
105
  end
69
106
 
70
- # If we have reference lat/long then use that as a base and update the event
71
- # position
72
- def localize_position(event)
73
- # There is no combination of layouts for these lines that doesn' trip
74
- # at least one rubocop cop so just ignore the guard clause. The best
75
- # one is using an inline if but that breaks the line length.
76
- # rubocop:disable Style/GuardClause
77
- if event[:latitude]
78
- event[:latitude] = @event_queue.reference_latitude + event[:latitude]
79
- end
80
- if event[:longitude]
81
- event[:longitude] = @event_queue.reference_longitude + event[:longitude]
82
- end
83
- # rubocop:enable Style/GuardClause
107
+ def update_latitude(value)
108
+ Cache.instance.reference_latitude = value
109
+ end
110
+
111
+ def update_longitude(value)
112
+ Cache.instance.reference_longitude = value
84
113
  end
85
114
  end
86
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.2.1'
4
+ VERSION = '0.7.1'
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'
@@ -37,6 +39,8 @@ Gem::Specification.new do |spec|
37
39
  # The following gem is newish and not heavily updated so the chances
38
40
  # of an API breaking change are higher then normal. Therefore lock the
39
41
  # version
42
+ spec.add_dependency 'concurrent-ruby', '~>1.1.5'
43
+ spec.add_dependency 'haversine', '~>0.3'
40
44
  spec.add_dependency 'sequel-postgis-georuby', '0.1.2'
41
45
  spec.add_dependency 'tacview_client', '~>0.1'
42
46
 
@@ -46,4 +50,5 @@ Gem::Specification.new do |spec|
46
50
  spec.add_development_dependency 'rubocop', '~>0.73'
47
51
  spec.add_development_dependency 'simplecov', '~>0.17'
48
52
  spec.add_development_dependency 'yard', '~>0.9'
53
+ spec.add_development_dependency 'pry-byebug', '~>3.9'
49
54
  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.2.1
4
+ version: 0.7.1
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-12 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
@@ -52,6 +52,34 @@ dependencies:
52
52
  - - "~>"
53
53
  - !ruby/object:Gem::Version
54
54
  version: '5.22'
55
+ - !ruby/object:Gem::Dependency
56
+ requirement: !ruby/object:Gem::Requirement
57
+ requirements:
58
+ - - "~>"
59
+ - !ruby/object:Gem::Version
60
+ version: 1.1.5
61
+ name: concurrent-ruby
62
+ prerelease: false
63
+ type: :runtime
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - "~>"
67
+ - !ruby/object:Gem::Version
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'
55
83
  - !ruby/object:Gem::Dependency
56
84
  requirement: !ruby/object:Gem::Requirement
57
85
  requirements:
@@ -164,11 +192,25 @@ dependencies:
164
192
  - - "~>"
165
193
  - !ruby/object:Gem::Version
166
194
  version: '0.9'
195
+ - !ruby/object:Gem::Dependency
196
+ requirement: !ruby/object:Gem::Requirement
197
+ requirements:
198
+ - - "~>"
199
+ - !ruby/object:Gem::Version
200
+ version: '3.9'
201
+ name: pry-byebug
202
+ prerelease: false
203
+ type: :development
204
+ version_requirements: !ruby/object:Gem::Requirement
205
+ requirements:
206
+ - - "~>"
207
+ - !ruby/object:Gem::Version
208
+ version: '3.9'
167
209
  description: Write Tacview data to PostGIS database
168
210
  email:
169
211
  - jeff@jones.be
170
212
  executables:
171
- - start_scribe
213
+ - tac_scribe
172
214
  extensions: []
173
215
  extra_rdoc_files: []
174
216
  files:
@@ -187,10 +229,12 @@ files:
187
229
  - bin/console
188
230
  - bin/setup
189
231
  - data/airfields.json
232
+ - data/whitelist.example
190
233
  - db/001_create_unit_table.rb
191
234
  - db/README.md
192
- - exe/start_scribe
235
+ - exe/tac_scribe
193
236
  - lib/tac_scribe.rb
237
+ - lib/tac_scribe/cache.rb
194
238
  - lib/tac_scribe/daemon.rb
195
239
  - lib/tac_scribe/datastore.rb
196
240
  - lib/tac_scribe/event_processor.rb
@@ -219,7 +263,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
219
263
  version: '0'
220
264
  requirements: []
221
265
  rubyforge_project:
222
- rubygems_version: 2.7.6
266
+ rubygems_version: 2.7.10
223
267
  signing_key:
224
268
  specification_version: 4
225
269
  summary: Write Tacview data to PostGIS database