git-fit 0.9.5 → 0.9.7

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: 28eab6fd810e6ed7b29e2b04ae7c08dc37ff619aa37b87e21e73f139fc3b147c
4
- data.tar.gz: 6e4554743b020986de0803ba39f86044f3f607f8efbf81e57dd51ff174eb1080
3
+ metadata.gz: b08e4aac65236128444a75e0890cdaf1e8b25f08a1db325833c7e6c240019b98
4
+ data.tar.gz: b028cc241a518348434988519e5602cbe624d73e80f0b3e5bfbfb0bbe13660ab
5
5
  SHA512:
6
- metadata.gz: 432140f8a5aeff892215acacde7ffa4184c9bcc156754234f676a407b9466aef84b8df70987b380339ca2e2ad33c96882f9a5508f7929e1ccab184de30bef9de
7
- data.tar.gz: 1bf2814bc40fd83b253aa4424d371c3fcb0318dcc655ae34974bb284e45ad737d9f8eb00e130f14203ea3713b6869859e287a2535f5100f03d9a7603d68fa82e
6
+ metadata.gz: 62d0a2b6b23b44c8d56254fa695fcd5735052319261cb6b146143b88910f704c2eb2df77544bcb23b490409ed271e495d7bade5fff25d6c32e8a9dbb9d717e63
7
+ data.tar.gz: 6ab10823f7a8d558de35ef6eb51d11d7aa88d03a9d61d175623355d5a1f159cfd6a23c7985836b8edf26ce0284e61d99f1506792c1f3017a62852dfbdc493d1c
@@ -10,7 +10,7 @@ module GitFit
10
10
 
11
11
  sync:
12
12
  # workers: 3 # env: GIT_FIT_SYNC_WORKERS
13
- # time_budget: 300 # env: GIT_FIT_SYNC_TIME_BUDGET (seconds)
13
+ # time_budget: 90 # env: GIT_FIT_SYNC_TIME_BUDGET (seconds)
14
14
  # total_timeout: 600 # env: GIT_FIT_SYNC_TOTAL_TIMEOUT (seconds)
15
15
 
16
16
  # strava: # env: GIT_FIT_STRAVA_CLIENT_ID
@@ -42,6 +42,14 @@ module GitFit
42
42
  true
43
43
  end
44
44
 
45
+ def auth_error?
46
+ false
47
+ end
48
+
49
+ def last_auth_code
50
+ nil
51
+ end
52
+
45
53
  def pending_ids
46
54
  raise NotImplementedError, "#{self.class} must implement #pending_ids"
47
55
  end
@@ -53,9 +53,14 @@ module GitFit
53
53
  'Content-Type' => 'application/x-www-form-urlencoded;charset=utf-8',
54
54
  }
55
55
  @session = nil
56
+ @cached_pending_ids = nil
57
+ @last_http_code = nil
58
+ @token_issued_at = nil
59
+ @requests_since_login = 0
56
60
  end
57
61
 
58
62
  def before_call
63
+ @cached_pending_ids = nil
59
64
  return false if @phone.nil? || @password.nil?
60
65
  super
61
66
  end
@@ -67,6 +72,7 @@ module GitFit
67
72
  end
68
73
 
69
74
  def pending_ids
75
+ return @cached_pending_ids if @cached_pending_ids
70
76
  existing = existing_run_ids
71
77
  ids = []
72
78
  KEEP_TYPES.each do |sport|
@@ -80,6 +86,7 @@ module GitFit
80
86
  ids << { id: cid, keep_id: keep_id, sport: sport, has_raw: has_raw }
81
87
  end
82
88
  end
89
+ @cached_pending_ids = ids
83
90
  ids
84
91
  end
85
92
 
@@ -91,6 +98,14 @@ module GitFit
91
98
  end
92
99
  end
93
100
 
101
+ def auth_error?
102
+ [401, 403].include?(@last_http_code)
103
+ end
104
+
105
+ def last_auth_code
106
+ @last_http_code
107
+ end
108
+
94
109
  private
95
110
 
96
111
  def http_post(url, body, headers)
@@ -102,7 +117,9 @@ module GitFit
102
117
  http.use_ssl = uri.scheme == 'https'
103
118
  http.open_timeout = 30
104
119
  http.read_timeout = 60
105
- http.start { |h| h.request(req) }
120
+ resp = http.start { |h| h.request(req) }
121
+ @last_http_code = resp.code.to_i
122
+ resp
106
123
  end
107
124
 
108
125
  def http_get(url, headers)
@@ -113,7 +130,10 @@ module GitFit
113
130
  http.use_ssl = uri.scheme == 'https'
114
131
  http.open_timeout = 30
115
132
  http.read_timeout = 60
116
- http.start { |h| h.request(req) }
133
+ resp = http.start { |h| h.request(req) }
134
+ @last_http_code = resp.code.to_i
135
+ @requests_since_login += 1
136
+ resp
117
137
  end
118
138
 
119
139
  def login
@@ -126,6 +146,8 @@ module GitFit
126
146
 
127
147
  @headers['Authorization'] = "Bearer #{token}"
128
148
  @session = true
149
+ @token_issued_at = Time.now
150
+ @requests_since_login = 0
129
151
  rescue JSON::ParserError
130
152
  nil
131
153
  end
@@ -147,7 +169,12 @@ module GitFit
147
169
  loop do
148
170
  url = STATS_URL % { sport: sport, last_date: last_date }
149
171
  resp = @session ? http_get(url, @headers) : nil
150
- break unless resp&.code&.to_i == 200
172
+ break unless resp
173
+ if [401, 403].include?(resp.code.to_i)
174
+ report_token_expiry
175
+ break
176
+ end
177
+ break unless resp.code.to_i == 200
151
178
 
152
179
  data = JSON.parse(resp.body)['data']
153
180
  break unless data
@@ -175,6 +202,10 @@ module GitFit
175
202
  def sync_single_run_with_raw(compound_id, sport)
176
203
  url = LOG_URL % { sport: sport, run_id: compound_id }
177
204
  resp = http_get(url, @headers)
205
+ if [401, 403].include?(resp.code.to_i)
206
+ report_token_expiry
207
+ return false
208
+ end
178
209
  return false unless resp.code.to_i == 200
179
210
 
180
211
  data = JSON.parse(resp.body)
@@ -213,6 +244,7 @@ module GitFit
213
244
  end
214
245
 
215
246
  def upsert_from_raw(compound_id)
247
+ @last_http_code = nil
216
248
  keep_id = extract_id(compound_id) || compound_id
217
249
  raw = JSON.parse(File.read(raw_path(keep_id)))
218
250
  run_data = raw['meta'].merge('id' => compound_id)
@@ -497,6 +529,11 @@ module GitFit
497
529
  def spider_sleep
498
530
  sleep 0.1
499
531
  end
532
+
533
+ def report_token_expiry
534
+ lifetime = @token_issued_at ? (Time.now - @token_issued_at).round(1) : nil
535
+ warn "Keep: token expired after #{@requests_since_login} requests#{lifetime ? " (#{lifetime}s)" : ''}"
536
+ end
500
537
  end
501
538
  end
502
539
  end
@@ -6,7 +6,7 @@ module GitFit
6
6
  module Sync
7
7
  class Runner
8
8
  POISON = Object.new.freeze
9
- BATCH_SIZE = 5
9
+ BATCH_SIZE = 10
10
10
 
11
11
  def initialize(sources:, db:, config:, activity_filter: nil,
12
12
  privacy: nil, num_workers: 3, time_budget: nil, total_timeout: nil)
@@ -15,7 +15,7 @@ module GitFit
15
15
  @config = config
16
16
  @activity_filter = activity_filter
17
17
  @privacy = privacy
18
- @num_workers = [@num_workers.to_i, 1].max
18
+ @num_workers = [num_workers.to_i, 1].max
19
19
  @time_budget = time_budget.is_a?(Numeric) ? time_budget.to_f : nil
20
20
  @total_timeout = total_timeout.is_a?(Numeric) ? total_timeout.to_f : nil
21
21
  @sync_start = Time.now
@@ -32,6 +32,8 @@ module GitFit
32
32
  @shutdown = false
33
33
  @source_start_times = {}
34
34
  @source_activated = {}
35
+ @run_totals = {}
36
+ @run_processed = {}
35
37
  end
36
38
 
37
39
  def run
@@ -83,36 +85,43 @@ module GitFit
83
85
  return false
84
86
  end
85
87
 
86
- output_queue << { type: :banner, source:, text: 'authenticating' }
87
- adapter = klass.new(config: cfg, db: @db, activity_filter: @activity_filter, privacy: @privacy,
88
- time_budget: @time_budget)
89
- unless adapter.before_call
90
- output_queue << { type: :banner, source:, text: 'before_call failed' }
91
- clear_source(source)
92
- return false
93
- end
88
+ unless @adapters[source]
89
+ output_queue << { type: :banner, source:, text: 'authenticating' }
90
+ adapter = klass.new(config: cfg, db: @db, activity_filter: @activity_filter, privacy: @privacy,
91
+ time_budget: @time_budget)
92
+ unless adapter.before_call
93
+ output_queue << { type: :banner, source:, text: 'before_call failed' }
94
+ clear_source(source)
95
+ return false
96
+ end
94
97
 
95
- unless adapter.authenticate
96
- output_queue << { type: :banner, source:, text: 'auth failed' }
97
- clear_source(source)
98
- return false
99
- end
98
+ unless adapter.authenticate
99
+ output_queue << { type: :banner, source:, text: 'auth failed' }
100
+ clear_source(source)
101
+ return false
102
+ end
100
103
 
101
- method = adapter.auth_method || 'unknown'
102
- output_queue << { type: :banner, source:, text: "authenticated (#{method})" }
103
- output_queue << { type: :banner, source:, text: 'fetching activity list' }
104
- ids = adapter.pending_ids
105
- if ids.nil? || ids.empty?
106
- output_queue << { type: :banner, source:, text: 'no new activities' }
107
- clear_source(source)
108
- return false
104
+ method = adapter.auth_method || 'unknown'
105
+ output_queue << { type: :banner, source:, text: "authenticated (#{method})" }
106
+ @adapters[source] = adapter
107
+
108
+ output_queue << { type: :banner, source:, text: 'fetching activity list' }
109
+ ids = adapter.pending_ids
110
+ if adapter.auth_error?
111
+ output_queue << { type: :banner, source:, text: "auth failed (#{adapter.last_auth_code})" }
112
+ clear_source(source)
113
+ return false
114
+ end
115
+ if ids.nil? || ids.empty?
116
+ output_queue << { type: :banner, source:, text: 'no new activities' }
117
+ clear_source(source)
118
+ return false
119
+ end
120
+ @pending_ids[source] = ids
121
+ output_queue << { type: :banner, source:, text: "#{ids.size} pending" }
109
122
  end
110
123
 
111
- @adapters[source] = adapter
112
- @pending_ids[source] = ids
113
- @source_start_times[source] = Time.now
114
124
  @source_activated[source] = true
115
- output_queue << { type: :banner, source:, text: "#{ids.size} pending" }
116
125
  true
117
126
  end
118
127
 
@@ -121,6 +130,8 @@ module GitFit
121
130
  @adapters.delete(source)
122
131
  @source_start_times.delete(source)
123
132
  @source_activated.delete(source)
133
+ @run_totals.delete(source)
134
+ @run_processed.delete(source)
124
135
  end
125
136
 
126
137
  def worker_loop
@@ -153,10 +164,12 @@ module GitFit
153
164
  finish_activity(source)
154
165
  return
155
166
  end
167
+ @stats_lock.synchronize { @source_start_times[source] ||= Time.now }
156
168
 
157
- total = @pending_ids[source].size
169
+ total = @run_totals[source] ||= @pending_ids[source].size
158
170
  processed = 0
159
171
  timed_out_local = false
172
+ auth_failed_local = false
160
173
 
161
174
  loop do
162
175
  activity = nil
@@ -187,33 +200,76 @@ module GitFit
187
200
  begin
188
201
  result = @adapters[source].process_one(activity)
189
202
  processed += 1
190
- if result
191
- info = @adapters[source].build_progress_info(result)
192
- @output_queue << {
193
- type: :progress, source:, id: activity[:id],
194
- info: info, done: processed, total: total
195
- }
196
- else
197
- @output_queue << {
198
- type: :progress, source:, id: activity[:id],
199
- info: nil, done: processed, total: total
200
- }
201
- end
203
+ outcome = handle_result(source, activity, result, processed, total)
202
204
  rescue StandardError => e
203
205
  @output_queue << { type: :error, source:, message: e.message }
206
+ outcome = :ok
204
207
  end
205
208
 
209
+ if outcome == :auth_failed
210
+ auth_failed_local = true
211
+ break
212
+ end
206
213
  break if processed >= BATCH_SIZE
207
214
  end
208
215
 
209
- finish_activity(source, timed_out_local)
216
+ @run_processed[source] = (@run_processed[source] || 0) + processed
217
+
218
+ if auth_failed_local
219
+ finish_activity(source, true, 're-auth failed')
220
+ else
221
+ finish_activity(source, timed_out_local)
222
+ end
223
+ end
224
+
225
+ def handle_result(source, activity, result, processed, total)
226
+ if result
227
+ report_activity_progress(source, activity, result, processed, total)
228
+ :ok
229
+ elsif @adapters[source].auth_error?
230
+ code = @adapters[source].last_auth_code
231
+ @output_queue << { type: :banner, source:, text: "auth error (#{code}), re-authenticating" }
232
+ if @adapters[source].authenticate
233
+ result = @adapters[source].process_one(activity)
234
+ if result
235
+ report_activity_progress(source, activity, result, processed, total)
236
+ elsif @adapters[source].auth_error?
237
+ still_code = @adapters[source].last_auth_code
238
+ msg = "#{activity[:id]} still #{still_code} after re-auth"
239
+ @output_queue << { type: :error, source:, message: msg }
240
+ report_activity_progress(source, activity, nil, processed, total)
241
+ else
242
+ report_activity_progress(source, activity, nil, processed, total)
243
+ end
244
+ :ok
245
+ else
246
+ :auth_failed
247
+ end
248
+ else
249
+ report_activity_progress(source, activity, nil, processed, total)
250
+ :ok
251
+ end
252
+ end
253
+
254
+ def report_activity_progress(source, activity, result, processed, total)
255
+ acc = (@run_processed[source] || 0) + processed
256
+ info = result ? @adapters[source].build_progress_info(result) : nil
257
+ @output_queue << {
258
+ type: :progress, source:, id: activity[:id],
259
+ info:, done: acc, total:
260
+ }
210
261
  end
211
262
 
212
- def finish_activity(source, timed_out = false)
263
+ def finish_activity(source, timed_out = false, timeout_note = nil)
213
264
  @stats_lock.synchronize do
214
265
  if timed_out
215
266
  remaining = @pending_ids[source]&.size.to_i
216
- @output_queue << { type: :timeout, source:, remaining: }
267
+ msg = if timeout_note
268
+ { type: :banner, source:, text: "#{timeout_note} — #{remaining} remaining, ending source" }
269
+ else
270
+ { type: :timeout, source:, remaining: }
271
+ end
272
+ @output_queue << msg
217
273
  @pending_ids[source]&.clear
218
274
  end
219
275
 
@@ -277,11 +333,12 @@ module GitFit
277
333
  ps[:done] = msg[:done].nil? ? ps.fetch(:done, 0) + 1 : msg[:done]
278
334
  total = msg[:total] || ps[:total]
279
335
  next unless show_progress?(msg[:done], total)
280
- line = if msg[:info]
281
- " [#{msg[:done]}/#{total}] #{msg[:info]}"
282
- else
283
- " [#{msg[:done]}/#{total}] (skipped)"
284
- end
336
+ line = " [#{msg[:done]}/#{total}] #{msg[:id]}"
337
+ line += if msg[:info]
338
+ " | #{msg[:info]}"
339
+ else
340
+ ' | (skipped)'
341
+ end
285
342
  $stdout.puts " #{s.rjust(10)} |#{line}"
286
343
  when :error
287
344
  ps[:errors] = (ps[:errors] || 0) + 1
@@ -297,13 +354,15 @@ module GitFit
297
354
  @sources.each do |s|
298
355
  info = per_source[s] || {}
299
356
  err = info[:errors] || 0
300
- if info[:auth_failed]
301
- $stdout.puts " done: #{s}: 0 synced (auth failed)"
302
- elsif info[:up_to_date]
357
+ if info[:up_to_date]
303
358
  $stdout.puts " done: #{s}: up to date"
304
359
  elsif (cnt = info[:done]) && cnt > 0
305
- label = err > 0 ? "#{cnt} synced, #{err} errors" : "#{cnt} synced"
360
+ label = "#{cnt} synced"
361
+ label += ", #{err} errors" if err > 0
362
+ label += ' (auth failed)' if info[:auth_failed]
306
363
  $stdout.puts " done: #{s}: #{label}"
364
+ elsif info[:auth_failed]
365
+ $stdout.puts " done: #{s}: 0 synced (auth failed)"
307
366
  elsif info.key?(:done)
308
367
  $stdout.puts " done: #{s}: 0 synced"
309
368
  else
@@ -317,10 +376,9 @@ module GitFit
317
376
  end
318
377
 
319
378
  def show_progress?(done, total)
320
- return true if done.nil? || done <= 3
379
+ return true if done.nil?
321
380
  return true if total && (total - done) <= 5
322
- step = total && total > 0 ? [1, (total / 15.0).ceil].max : 1
323
- step <= 1 || done % step == 0
381
+ done % BATCH_SIZE == 1
324
382
  end
325
383
  end
326
384
  end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module GitFit
4
- VERSION = '0.9.5'
4
+ VERSION = '0.9.7'
5
5
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: git-fit
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.9.5
4
+ version: 0.9.7
5
5
  platform: ruby
6
6
  authors:
7
7
  - Lax