git-fit 0.9.5 → 0.9.6

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: ca129e8a18a66ca1cf48deaaffac3c5a7b84b752737bb52e96906a207be7eb19
4
+ data.tar.gz: aba8f6fd23d5d6cc89b174b0de160a590dd9a74c20836e4986303e9d2ab5be2b
5
5
  SHA512:
6
- metadata.gz: 432140f8a5aeff892215acacde7ffa4184c9bcc156754234f676a407b9466aef84b8df70987b380339ca2e2ad33c96882f9a5508f7929e1ccab184de30bef9de
7
- data.tar.gz: 1bf2814bc40fd83b253aa4424d371c3fcb0318dcc655ae34974bb284e45ad737d9f8eb00e130f14203ea3713b6869859e287a2535f5100f03d9a7603d68fa82e
6
+ metadata.gz: 848a12e3fe926fe889c3672d75b0929756ee036622d6c09db8306f6b7590eb63298243d3d90733dcff18b253a285120cf26896546f000f05d7dbe57bc8b28c65
7
+ data.tar.gz: bbc19cdda1ceee49217d287cc07073cdbee17ecdf6a41774963670dbf377e2933250ef23a21fdfc9beb75624b86212289b6424408f40409d202c0a0873dc62c7
@@ -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
@@ -83,36 +83,43 @@ module GitFit
83
83
  return false
84
84
  end
85
85
 
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
86
+ unless @adapters[source]
87
+ output_queue << { type: :banner, source:, text: 'authenticating' }
88
+ adapter = klass.new(config: cfg, db: @db, activity_filter: @activity_filter, privacy: @privacy,
89
+ time_budget: @time_budget)
90
+ unless adapter.before_call
91
+ output_queue << { type: :banner, source:, text: 'before_call failed' }
92
+ clear_source(source)
93
+ return false
94
+ end
94
95
 
95
- unless adapter.authenticate
96
- output_queue << { type: :banner, source:, text: 'auth failed' }
97
- clear_source(source)
98
- return false
99
- end
96
+ unless adapter.authenticate
97
+ output_queue << { type: :banner, source:, text: 'auth failed' }
98
+ clear_source(source)
99
+ return false
100
+ end
100
101
 
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
102
+ method = adapter.auth_method || 'unknown'
103
+ output_queue << { type: :banner, source:, text: "authenticated (#{method})" }
104
+ @adapters[source] = adapter
105
+
106
+ output_queue << { type: :banner, source:, text: 'fetching activity list' }
107
+ ids = adapter.pending_ids
108
+ if adapter.auth_error?
109
+ output_queue << { type: :banner, source:, text: "auth failed (#{adapter.last_auth_code})" }
110
+ clear_source(source)
111
+ return false
112
+ end
113
+ if ids.nil? || ids.empty?
114
+ output_queue << { type: :banner, source:, text: 'no new activities' }
115
+ clear_source(source)
116
+ return false
117
+ end
118
+ @pending_ids[source] = ids
119
+ output_queue << { type: :banner, source:, text: "#{ids.size} pending" }
109
120
  end
110
121
 
111
- @adapters[source] = adapter
112
- @pending_ids[source] = ids
113
- @source_start_times[source] = Time.now
114
122
  @source_activated[source] = true
115
- output_queue << { type: :banner, source:, text: "#{ids.size} pending" }
116
123
  true
117
124
  end
118
125
 
@@ -149,6 +156,7 @@ module GitFit
149
156
  end
150
157
 
151
158
  def process_source(source)
159
+ @stats_lock.synchronize { @source_start_times[source] ||= Time.now }
152
160
  unless init_source(source)
153
161
  finish_activity(source)
154
162
  return
@@ -157,6 +165,7 @@ module GitFit
157
165
  total = @pending_ids[source].size
158
166
  processed = 0
159
167
  timed_out_local = false
168
+ auth_failed_local = false
160
169
 
161
170
  loop do
162
171
  activity = nil
@@ -187,33 +196,73 @@ module GitFit
187
196
  begin
188
197
  result = @adapters[source].process_one(activity)
189
198
  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
199
+ outcome = handle_result(source, activity, result, processed, total)
202
200
  rescue StandardError => e
203
201
  @output_queue << { type: :error, source:, message: e.message }
202
+ outcome = :ok
204
203
  end
205
204
 
205
+ if outcome == :auth_failed
206
+ auth_failed_local = true
207
+ break
208
+ end
206
209
  break if processed >= BATCH_SIZE
207
210
  end
208
211
 
209
- finish_activity(source, timed_out_local)
212
+ if auth_failed_local
213
+ finish_activity(source, true, 're-auth failed')
214
+ else
215
+ finish_activity(source, timed_out_local)
216
+ end
217
+ end
218
+
219
+ def handle_result(source, activity, result, processed, total)
220
+ if result
221
+ report_activity_progress(source, activity, result, processed, total)
222
+ :ok
223
+ elsif @adapters[source].auth_error?
224
+ code = @adapters[source].last_auth_code
225
+ @output_queue << { type: :banner, source:, text: "auth error (#{code}), re-authenticating" }
226
+ if @adapters[source].authenticate
227
+ result = @adapters[source].process_one(activity)
228
+ if result
229
+ report_activity_progress(source, activity, result, processed, total)
230
+ elsif @adapters[source].auth_error?
231
+ still_code = @adapters[source].last_auth_code
232
+ msg = "#{activity[:id]} still #{still_code} after re-auth"
233
+ @output_queue << { type: :error, source:, message: msg }
234
+ report_activity_progress(source, activity, nil, processed, total)
235
+ else
236
+ report_activity_progress(source, activity, nil, processed, total)
237
+ end
238
+ :ok
239
+ else
240
+ :auth_failed
241
+ end
242
+ else
243
+ report_activity_progress(source, activity, nil, processed, total)
244
+ :ok
245
+ end
246
+ end
247
+
248
+ def report_activity_progress(source, activity, result, processed, total)
249
+ info = result ? @adapters[source].build_progress_info(result) : nil
250
+ @output_queue << {
251
+ type: :progress, source:, id: activity[:id],
252
+ info:, done: processed, total:
253
+ }
210
254
  end
211
255
 
212
- def finish_activity(source, timed_out = false)
256
+ def finish_activity(source, timed_out = false, timeout_note = nil)
213
257
  @stats_lock.synchronize do
214
258
  if timed_out
215
259
  remaining = @pending_ids[source]&.size.to_i
216
- @output_queue << { type: :timeout, source:, remaining: }
260
+ msg = if timeout_note
261
+ { type: :banner, source:, text: "#{timeout_note} — #{remaining} remaining, ending source" }
262
+ else
263
+ { type: :timeout, source:, remaining: }
264
+ end
265
+ @output_queue << msg
217
266
  @pending_ids[source]&.clear
218
267
  end
219
268
 
@@ -277,11 +326,12 @@ module GitFit
277
326
  ps[:done] = msg[:done].nil? ? ps.fetch(:done, 0) + 1 : msg[:done]
278
327
  total = msg[:total] || ps[:total]
279
328
  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
329
+ line = " [#{msg[:done]}/#{total}] #{msg[:id]}"
330
+ line += if msg[:info]
331
+ " | #{msg[:info]}"
332
+ else
333
+ ' | (skipped)'
334
+ end
285
335
  $stdout.puts " #{s.rjust(10)} |#{line}"
286
336
  when :error
287
337
  ps[:errors] = (ps[:errors] || 0) + 1
@@ -297,13 +347,15 @@ module GitFit
297
347
  @sources.each do |s|
298
348
  info = per_source[s] || {}
299
349
  err = info[:errors] || 0
300
- if info[:auth_failed]
301
- $stdout.puts " done: #{s}: 0 synced (auth failed)"
302
- elsif info[:up_to_date]
350
+ if info[:up_to_date]
303
351
  $stdout.puts " done: #{s}: up to date"
304
352
  elsif (cnt = info[:done]) && cnt > 0
305
- label = err > 0 ? "#{cnt} synced, #{err} errors" : "#{cnt} synced"
353
+ label = "#{cnt} synced"
354
+ label += ", #{err} errors" if err > 0
355
+ label += " (auth failed)" if info[:auth_failed]
306
356
  $stdout.puts " done: #{s}: #{label}"
357
+ elsif info[:auth_failed]
358
+ $stdout.puts " done: #{s}: 0 synced (auth failed)"
307
359
  elsif info.key?(:done)
308
360
  $stdout.puts " done: #{s}: 0 synced"
309
361
  else
@@ -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.6'
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.6
5
5
  platform: ruby
6
6
  authors:
7
7
  - Lax