tac_scribe 0.5.0 → 0.6.0

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: 485c9e87c957fd0e66db7cc762a36283baa2c4852546008b2d4dbd5e2372a53a
4
- data.tar.gz: 95576f042c7455ac1e5b87bcab21d6a5ff555d8f67a83b4c7cd2531b56f696c1
3
+ metadata.gz: 3f357b6fb0a4a45accdc07e20ff29fe6a93c66d7b88665ea51a652ca28a2b1d4
4
+ data.tar.gz: b01e4759b8e523fee4541b71b43102fcb62d228a15b3c87bb6b969fc33e6e206
5
5
  SHA512:
6
- metadata.gz: 1aefe11c62a16ac70377bc48f262b699f2aebb4c38480bbe677b1490e5d14320c0e213d1976e0b4a477e1687e24b8bccae1b9a7f111bdaaeca1c0ebba63f9d62
7
- data.tar.gz: 2d54201ba482399aafd29a4633b3b7361220457e85e8aae0447be8920191fc708e3582617affe5176d6bacd6682688c73e315772e094a71ada60af1101b6a2f5
6
+ metadata.gz: fc97730d7e4b521a46987f2ea01d2680715933571ce33218e8ad69033a35df0b9722fcc123af082d71cd70487d7caab60765d171c5b29301445d0ec6c3178ea8
7
+ data.tar.gz: 3e27a7a3957fab068f168dad158a6ae555faab3a40fa8535c0ee65629fe8d4915255c41890ff0d0ea79e3a36b24e8642d4df4f3747a891fd90b175e4883a9a59
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/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
data/CHANGELOG.md CHANGED
@@ -4,7 +4,12 @@ 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.6.0]
8
+ ### Added
9
+ - Calculates and stores speed of units
10
+
11
+ ### Fixed
12
+ - Made more robust against failures and more logging messages
8
13
 
9
14
  ## [0.5.0]
10
15
  ### Changed
@@ -12,6 +12,7 @@ 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
16
17
  Boolean :deleted
17
18
  # TODO: GIST Index on the position
@@ -5,8 +5,11 @@ require 'sequel'
5
5
  require 'sequel-postgis-georuby'
6
6
  require 'singleton'
7
7
  require 'concurrent-ruby'
8
+ require 'haversine'
8
9
 
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
10
13
  class Cache
11
14
  include Singleton
12
15
  include GeoRuby::SimpleFeatures
@@ -48,9 +51,11 @@ module TacScribe
48
51
 
49
52
  if cache_object
50
53
  object[:heading] = calculate_heading(cache_object, object)
54
+ object[:speed] = calculate_speed(cache_object, object)
51
55
  cache_object.merge!(object)
52
56
  else
53
57
  object[:heading] = -1
58
+ object[:speed] = 0
54
59
  cache_object = object
55
60
  end
56
61
 
@@ -75,7 +80,7 @@ module TacScribe
75
80
  def delete_object(id)
76
81
  @@cache[id][:deleted] = true if @@cache[id]
77
82
  end
78
-
83
+
79
84
  def clear
80
85
  @@cache.clear
81
86
  end
@@ -104,11 +109,16 @@ module TacScribe
104
109
  cache_object[:position].x
105
110
  end
106
111
 
112
+ # This "Point" class is not lat/long aware which is why we are
113
+ # flipping things up here because when it converts to SQL we need
114
+ # the long to be first since that is what postgresql expects.
107
115
  Point.from_x_y(longitude, latitude)
108
116
  end
109
117
 
110
118
  def calculate_heading(cache_object, object)
111
- return cache_object[:heading] if cache_object[:position] == object[:position]
119
+ if cache_object[:position] == object[:position]
120
+ return cache_object[:heading]
121
+ end
112
122
 
113
123
  begin
114
124
  cache_object[:position].bearing_to(object[:position]).to_i
@@ -118,5 +128,21 @@ module TacScribe
118
128
  puts 'New Position: ' + object[:position].inspect
119
129
  end
120
130
  end
131
+
132
+ def calculate_speed(cache_object, object)
133
+ time = object[:game_time] - cache_object[:game_time]
134
+
135
+ start_point = cache_object[:position]
136
+ end_point = object[:position]
137
+
138
+ # Because of the above issues with Point and Lat/Lon
139
+ # the values are reverse here :(
140
+ distance = Haversine.distance(start_point.y, start_point.x,
141
+ end_point.y, end_point.x).to_meters
142
+
143
+ speed = distance / time
144
+
145
+ speed.to_i
146
+ end
121
147
  end
122
148
  end
@@ -31,7 +31,7 @@ module TacScribe
31
31
 
32
32
  @populate_airfields = populate_airfields
33
33
  @thread_count = thread_count
34
- @threads = []
34
+ @threads = {}
35
35
  @whitelist = Set.new(IO.read(whitelist).split) if whitelist
36
36
 
37
37
  @client = TacviewClient::Client.new(
@@ -50,7 +50,7 @@ module TacScribe
50
50
  # for example
51
51
  def start_processing
52
52
  loop do
53
- kill_threads
53
+ puts 'Starting processing loop'
54
54
  @event_queue.clear
55
55
  Datastore.instance.truncate_table
56
56
  Cache.instance.clear
@@ -58,23 +58,34 @@ module TacScribe
58
58
  start_db_sync_thread
59
59
  start_reporting_thread
60
60
  populate_airfields if @populate_airfields
61
+ @threads.each_pair do |key, _value|
62
+ puts "#{key} thread started"
63
+ end
61
64
  @client.connect
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
+ sleep 10
62
69
  # Rescuing reliably from Net::HTTP is a complete bear so rescue
63
70
  # StandardError. It ain't pretty but it is reliable. We will puts
64
71
  # the exception just in case
65
72
  # https://stackoverflow.com/questions/5370697/what-s-the-best-way-to-handle-exceptions-from-nethttp
66
73
  rescue StandardError => e
74
+ puts 'Exception in processing loop'
67
75
  puts e.message
68
76
  puts e.backtrace
77
+ kill_threads
69
78
  sleep 30
70
79
  next
71
80
  end
72
81
  end
73
82
 
74
83
  def kill_threads
75
- @threads.each do |thr|
76
- thr.kill
77
- thr.join
84
+ puts 'Killing Threads'
85
+ @threads.each_pair do |key, thread|
86
+ puts "Killing #{key} thread"
87
+ thread.kill
88
+ thread.join
78
89
  end
79
90
  end
80
91
 
@@ -87,7 +98,7 @@ module TacScribe
87
98
  @event_processor.start
88
99
  end
89
100
  event_processor_thread.name = 'Event Processor'
90
- @threads << event_processor_thread
101
+ @threads[:processing] = event_processor_thread
91
102
  end
92
103
 
93
104
  def start_db_sync_thread
@@ -96,16 +107,19 @@ module TacScribe
96
107
  sleep 1
97
108
  deleted = Datastore.instance.write_objects(Cache.instance.data.values)
98
109
  deleted.each { |id| Cache.instance.data.delete(id) }
110
+ rescue StandardError
111
+ next
99
112
  end
100
113
  end
101
114
  db_write_thread.name = 'Database Writing'
102
- @threads << db_write_thread
115
+ @threads[:database] = db_write_thread
103
116
  end
104
117
 
105
118
  def start_reporting_thread
106
119
  return unless @verbose_logging
107
120
 
108
121
  reporting_thread = Thread.new do
122
+ sleep 5
109
123
  loop do
110
124
  puts "#{Time.now.strftime('%FT%T')}\t" \
111
125
  "Events Incoming: #{@event_queue.events_written}\t" \
@@ -123,11 +137,12 @@ module TacScribe
123
137
  end
124
138
  end
125
139
  reporting_thread.name = 'Reporting'
126
- @threads << reporting_thread
140
+ @threads[:reporting] = reporting_thread
127
141
  end
128
142
 
129
143
  def populate_airfields
130
- json = File.read(File.join(File.dirname(__FILE__), '../../data/airfields.json'))
144
+ json = File.read(File.join(File.dirname(__FILE__),
145
+ '../../data/airfields.json'))
131
146
  airfields = JSON.parse(json)
132
147
  airfields.each_with_index do |airfield, i|
133
148
  @event_queue.update_object(
@@ -53,11 +53,18 @@ module TacScribe
53
53
  end
54
54
 
55
55
  def write_objects(objects)
56
+ objects = objects.map do |object|
57
+ obj = object.clone
58
+ obj.delete(:game_time)
59
+ obj
60
+ end
61
+
56
62
  @db[:units].insert_conflict(
57
63
  constraint: :units_pkey,
58
64
  update: { position: Sequel[:excluded][:position],
59
65
  altitude: Sequel[:excluded][:altitude],
60
66
  heading: Sequel[:excluded][:heading],
67
+ speed: Sequel[:excluded][:speed],
61
68
  updated_at: Sequel[:excluded][:updated_at],
62
69
  deleted: Sequel[:excluded][:deleted] }
63
70
  )
@@ -23,7 +23,7 @@ module TacScribe
23
23
 
24
24
  def start
25
25
  loop do
26
- wrapped_event = @event_queue.get_event
26
+ wrapped_event = @event_queue.shift
27
27
  process_event(wrapped_event)
28
28
  rescue StandardError => e
29
29
  puts wrapped_event
@@ -38,6 +38,10 @@ module TacScribe
38
38
  update_object(wrapped_event[:event], wrapped_event[:time])
39
39
  when :delete_object
40
40
  delete_object(wrapped_event[:object_id])
41
+ when :set_latitude
42
+ update_latitude wrapped_event[:value]
43
+ when :set_longitude
44
+ update_longitude wrapped_event[:value]
41
45
  end
42
46
  end
43
47
 
@@ -60,20 +64,28 @@ module TacScribe
60
64
  # format.
61
65
  # @option event [BigDecimal] :altitude The object altitude above sea level
62
66
  # in meters to 1 decimal place.
63
- def update_object(event, _time)
67
+ def update_object(event, time)
68
+ return if ignore_unit?(even)
69
+
70
+ event[:game_time] = time
71
+
72
+ self.events_processed += 1
73
+ @cache.write_object(event)
74
+ end
75
+
76
+ def ignore_unit?(event)
64
77
  if @@ignored_units.include?(event[:object_id])
65
78
  self.events_ignored += 1
66
- return
79
+ return true
67
80
  end
68
81
 
69
82
  if @whitelist && event[:type] && !@whitelist.include?(event[:type])
70
83
  @@ignored_units << event[:object_id]
71
84
  self.events_ignored += 1
72
- return
85
+ return true
73
86
  end
74
87
 
75
- self.events_processed += 1
76
- @cache.write_object(event)
88
+ false
77
89
  end
78
90
 
79
91
  # Process a delete event for an object
@@ -81,12 +93,19 @@ module TacScribe
81
93
  # @param id [String] A hexadecimal number representing the object ID
82
94
  def delete_object(id)
83
95
  if @@ignored_units.delete?(id)
84
- self.events_processed += 1
85
96
  nil
86
97
  else
87
- self.events_processed += 1
88
98
  @cache.delete_object(id)
89
99
  end
100
+ self.events_processed += 1
101
+ end
102
+
103
+ def update_latitude(value)
104
+ Cache.instance.reference_latitude = value
105
+ end
106
+
107
+ def update_longitude(value)
108
+ Cache.instance.reference_longitude = value
90
109
  end
91
110
  end
92
111
  end
@@ -21,7 +21,7 @@ module TacScribe
21
21
  @events.size
22
22
  end
23
23
 
24
- def get_event
24
+ def shift
25
25
  @events.shift
26
26
  end
27
27
 
@@ -71,9 +71,9 @@ module TacScribe
71
71
  def set_property(property:, value:)
72
72
  case property
73
73
  when 'ReferenceLatitude'
74
- Cache.instance.reference_latitude = BigDecimal(value)
74
+ @events << { type: :set_latitude, value: BigDecimal(value) }
75
75
  when 'ReferenceLongitude'
76
- Cache.instance.reference_longitude = BigDecimal(value)
76
+ @events << { type: :set_longitude, value: BigDecimal(value) }
77
77
  when 'ReferenceTime'
78
78
  @reference_time = @time = Time.parse(value)
79
79
  end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module TacScribe
4
- VERSION = '0.5.0'
4
+ VERSION = '0.6.0'
5
5
  end
data/tac_scribe.gemspec CHANGED
@@ -36,6 +36,7 @@ Gem::Specification.new do |spec|
36
36
  # of an API breaking change are higher then normal. Therefore lock the
37
37
  # version
38
38
  spec.add_dependency 'concurrent-ruby', '~>1.1.5'
39
+ spec.add_dependency 'haversine', '~>0.3'
39
40
  spec.add_dependency 'sequel-postgis-georuby', '0.1.2'
40
41
  spec.add_dependency 'tacview_client', '~>0.1'
41
42
 
metadata CHANGED
@@ -1,178 +1,192 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: tac_scribe
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.5.0
4
+ version: 0.6.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Jeffrey Jones
8
- autorequire:
8
+ autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2020-03-17 00:00:00.000000000 Z
11
+ date: 2020-04-19 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
+ name: pg
14
15
  requirement: !ruby/object:Gem::Requirement
15
16
  requirements:
16
- - - ">="
17
+ - - "~>"
17
18
  - !ruby/object:Gem::Version
18
- version: '0'
19
- name: activerecord-jdbcpostgresql-adapter
20
- prerelease: false
19
+ version: '1.1'
21
20
  type: :runtime
21
+ prerelease: false
22
22
  version_requirements: !ruby/object:Gem::Requirement
23
23
  requirements:
24
- - - ">="
24
+ - - "~>"
25
25
  - !ruby/object:Gem::Version
26
- version: '0'
26
+ version: '1.1'
27
27
  - !ruby/object:Gem::Dependency
28
+ name: georuby
28
29
  requirement: !ruby/object:Gem::Requirement
29
30
  requirements:
30
31
  - - "~>"
31
32
  - !ruby/object:Gem::Version
32
33
  version: '2.5'
33
- name: georuby
34
- prerelease: false
35
34
  type: :runtime
35
+ prerelease: false
36
36
  version_requirements: !ruby/object:Gem::Requirement
37
37
  requirements:
38
38
  - - "~>"
39
39
  - !ruby/object:Gem::Version
40
40
  version: '2.5'
41
41
  - !ruby/object:Gem::Dependency
42
+ name: sequel
42
43
  requirement: !ruby/object:Gem::Requirement
43
44
  requirements:
44
45
  - - "~>"
45
46
  - !ruby/object:Gem::Version
46
47
  version: '5.22'
47
- name: sequel
48
- prerelease: false
49
48
  type: :runtime
49
+ prerelease: false
50
50
  version_requirements: !ruby/object:Gem::Requirement
51
51
  requirements:
52
52
  - - "~>"
53
53
  - !ruby/object:Gem::Version
54
54
  version: '5.22'
55
55
  - !ruby/object:Gem::Dependency
56
+ name: concurrent-ruby
56
57
  requirement: !ruby/object:Gem::Requirement
57
58
  requirements:
58
59
  - - "~>"
59
60
  - !ruby/object:Gem::Version
60
61
  version: 1.1.5
61
- name: concurrent-ruby
62
- prerelease: false
63
62
  type: :runtime
63
+ prerelease: false
64
64
  version_requirements: !ruby/object:Gem::Requirement
65
65
  requirements:
66
66
  - - "~>"
67
67
  - !ruby/object:Gem::Version
68
68
  version: 1.1.5
69
69
  - !ruby/object:Gem::Dependency
70
+ name: haversine
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - "~>"
74
+ - !ruby/object:Gem::Version
75
+ version: '0.3'
76
+ type: :runtime
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - "~>"
81
+ - !ruby/object:Gem::Version
82
+ version: '0.3'
83
+ - !ruby/object:Gem::Dependency
84
+ name: sequel-postgis-georuby
70
85
  requirement: !ruby/object:Gem::Requirement
71
86
  requirements:
72
87
  - - '='
73
88
  - !ruby/object:Gem::Version
74
89
  version: 0.1.2
75
- name: sequel-postgis-georuby
76
- prerelease: false
77
90
  type: :runtime
91
+ prerelease: false
78
92
  version_requirements: !ruby/object:Gem::Requirement
79
93
  requirements:
80
94
  - - '='
81
95
  - !ruby/object:Gem::Version
82
96
  version: 0.1.2
83
97
  - !ruby/object:Gem::Dependency
98
+ name: tacview_client
84
99
  requirement: !ruby/object:Gem::Requirement
85
100
  requirements:
86
101
  - - "~>"
87
102
  - !ruby/object:Gem::Version
88
103
  version: '0.1'
89
- name: tacview_client
90
- prerelease: false
91
104
  type: :runtime
105
+ prerelease: false
92
106
  version_requirements: !ruby/object:Gem::Requirement
93
107
  requirements:
94
108
  - - "~>"
95
109
  - !ruby/object:Gem::Version
96
110
  version: '0.1'
97
111
  - !ruby/object:Gem::Dependency
112
+ name: bundler
98
113
  requirement: !ruby/object:Gem::Requirement
99
114
  requirements:
100
115
  - - "~>"
101
116
  - !ruby/object:Gem::Version
102
117
  version: '2.0'
103
- name: bundler
104
- prerelease: false
105
118
  type: :development
119
+ prerelease: false
106
120
  version_requirements: !ruby/object:Gem::Requirement
107
121
  requirements:
108
122
  - - "~>"
109
123
  - !ruby/object:Gem::Version
110
124
  version: '2.0'
111
125
  - !ruby/object:Gem::Dependency
126
+ name: rake
112
127
  requirement: !ruby/object:Gem::Requirement
113
128
  requirements:
114
129
  - - "~>"
115
130
  - !ruby/object:Gem::Version
116
131
  version: '10.0'
117
- name: rake
118
- prerelease: false
119
132
  type: :development
133
+ prerelease: false
120
134
  version_requirements: !ruby/object:Gem::Requirement
121
135
  requirements:
122
136
  - - "~>"
123
137
  - !ruby/object:Gem::Version
124
138
  version: '10.0'
125
139
  - !ruby/object:Gem::Dependency
140
+ name: rspec
126
141
  requirement: !ruby/object:Gem::Requirement
127
142
  requirements:
128
143
  - - "~>"
129
144
  - !ruby/object:Gem::Version
130
145
  version: '3.8'
131
- name: rspec
132
- prerelease: false
133
146
  type: :development
147
+ prerelease: false
134
148
  version_requirements: !ruby/object:Gem::Requirement
135
149
  requirements:
136
150
  - - "~>"
137
151
  - !ruby/object:Gem::Version
138
152
  version: '3.8'
139
153
  - !ruby/object:Gem::Dependency
154
+ name: rubocop
140
155
  requirement: !ruby/object:Gem::Requirement
141
156
  requirements:
142
157
  - - "~>"
143
158
  - !ruby/object:Gem::Version
144
159
  version: '0.73'
145
- name: rubocop
146
- prerelease: false
147
160
  type: :development
161
+ prerelease: false
148
162
  version_requirements: !ruby/object:Gem::Requirement
149
163
  requirements:
150
164
  - - "~>"
151
165
  - !ruby/object:Gem::Version
152
166
  version: '0.73'
153
167
  - !ruby/object:Gem::Dependency
168
+ name: simplecov
154
169
  requirement: !ruby/object:Gem::Requirement
155
170
  requirements:
156
171
  - - "~>"
157
172
  - !ruby/object:Gem::Version
158
173
  version: '0.17'
159
- name: simplecov
160
- prerelease: false
161
174
  type: :development
175
+ prerelease: false
162
176
  version_requirements: !ruby/object:Gem::Requirement
163
177
  requirements:
164
178
  - - "~>"
165
179
  - !ruby/object:Gem::Version
166
180
  version: '0.17'
167
181
  - !ruby/object:Gem::Dependency
182
+ name: yard
168
183
  requirement: !ruby/object:Gem::Requirement
169
184
  requirements:
170
185
  - - "~>"
171
186
  - !ruby/object:Gem::Version
172
187
  version: '0.9'
173
- name: yard
174
- prerelease: false
175
188
  type: :development
189
+ prerelease: false
176
190
  version_requirements: !ruby/object:Gem::Requirement
177
191
  requirements:
178
192
  - - "~>"
@@ -219,7 +233,7 @@ licenses:
219
233
  - AGPL-3.0-or-later
220
234
  metadata:
221
235
  yard.run: yri
222
- post_install_message:
236
+ post_install_message:
223
237
  rdoc_options: []
224
238
  require_paths:
225
239
  - lib
@@ -234,9 +248,8 @@ required_rubygems_version: !ruby/object:Gem::Requirement
234
248
  - !ruby/object:Gem::Version
235
249
  version: '0'
236
250
  requirements: []
237
- rubyforge_project:
238
- rubygems_version: 2.7.6
239
- signing_key:
251
+ rubygems_version: 3.0.3
252
+ signing_key:
240
253
  specification_version: 4
241
254
  summary: Write Tacview data to PostGIS database
242
255
  test_files: []