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 +4 -4
- data/.rubocop.yml +14 -1
- data/.ruby-version +1 -1
- data/CHANGELOG.md +44 -1
- data/README.md +24 -1
- data/data/whitelist.example +5 -0
- data/db/001_create_unit_table.rb +2 -0
- data/exe/{start_scribe → tac_scribe} +9 -3
- data/lib/tac_scribe/cache.rb +151 -0
- data/lib/tac_scribe/daemon.rb +87 -23
- data/lib/tac_scribe/datastore.rb +22 -68
- data/lib/tac_scribe/event_processor.rb +53 -24
- data/lib/tac_scribe/event_queue.rb +12 -22
- data/lib/tac_scribe/version.rb +1 -1
- data/tac_scribe.gemspec +7 -2
- metadata +49 -5
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 9dc052878565aa0a92dca2eaf35fceec802af0ee74b7f316ea4efb2d243cef81
|
4
|
+
data.tar.gz: 601360e067fbf0812f9e4e77af1eb1ed724544a01db91a8df81e4c0e903922d9
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: ba986646a11ca3315dd2a6b3e54b0c1ff6766f239d73540779645055ddd2710e32e28a7409dffea1482a5b50d88f08f9ed4a80e9b65ed5152daa90e9965d457f
|
7
|
+
data.tar.gz: 519a7874ef8a1f9ec48f34626e5b34e05850c0a532228dd8dd175a96c5fae39ae531792bea547ecb98530df96c5c9745dc714a834cbed712c4ae9dfe5e26ff09
|
data/.rubocop.yml
CHANGED
@@ -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/
|
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
|
data/.ruby-version
CHANGED
@@ -1 +1 @@
|
|
1
|
-
ruby-2.
|
1
|
+
ruby-2.7.0
|
data/CHANGELOG.md
CHANGED
@@ -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
|
-
## [
|
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 `
|
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,
|
data/db/001_create_unit_table.rb
CHANGED
@@ -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-
|
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
|
data/lib/tac_scribe/daemon.rb
CHANGED
@@ -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
|
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
|
-
@
|
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
|
-
|
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
|
-
|
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
|
67
|
-
|
68
|
-
|
69
|
-
|
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
|
-
@
|
75
|
-
|
76
|
-
|
77
|
-
|
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__),
|
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
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
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
|
data/lib/tac_scribe/datastore.rb
CHANGED
@@ -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
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
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
|
-
|
71
|
-
|
72
|
-
|
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
|
-
|
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
|
-
|
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.
|
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
|
56
|
-
|
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
|
-
@
|
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
|
65
|
-
|
66
|
-
|
67
|
-
|
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
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
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 :
|
9
|
+
attr_accessor :events_written
|
10
10
|
|
11
|
-
def initialize
|
12
|
-
@verbose_logging = verbose_logging
|
11
|
+
def initialize
|
13
12
|
@events = Queue.new
|
14
|
-
|
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
|
34
|
-
@
|
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
|
-
|
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
|
-
|
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
|
-
|
74
|
+
@events << { type: :set_latitude, value: BigDecimal(value) }
|
85
75
|
when 'ReferenceLongitude'
|
86
|
-
|
76
|
+
@events << { type: :set_longitude, value: BigDecimal(value) }
|
87
77
|
when 'ReferenceTime'
|
88
78
|
@reference_time = @time = Time.parse(value)
|
89
79
|
end
|
data/lib/tac_scribe/version.rb
CHANGED
data/tac_scribe.gemspec
CHANGED
@@ -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.
|
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:
|
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
|
-
-
|
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/
|
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.
|
266
|
+
rubygems_version: 2.7.10
|
223
267
|
signing_key:
|
224
268
|
specification_version: 4
|
225
269
|
summary: Write Tacview data to PostGIS database
|