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 +4 -4
- data/lib/git_fit/config_template.rb +1 -1
- data/lib/git_fit/sync/base.rb +8 -0
- data/lib/git_fit/sync/keep.rb +40 -3
- data/lib/git_fit/sync/runner.rb +113 -55
- data/lib/git_fit/version.rb +1 -1
- metadata +1 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: b08e4aac65236128444a75e0890cdaf1e8b25f08a1db325833c7e6c240019b98
|
|
4
|
+
data.tar.gz: b028cc241a518348434988519e5602cbe624d73e80f0b3e5bfbfb0bbe13660ab
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
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:
|
|
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
|
data/lib/git_fit/sync/base.rb
CHANGED
data/lib/git_fit/sync/keep.rb
CHANGED
|
@@ -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
|
|
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
|
data/lib/git_fit/sync/runner.rb
CHANGED
|
@@ -6,7 +6,7 @@ module GitFit
|
|
|
6
6
|
module Sync
|
|
7
7
|
class Runner
|
|
8
8
|
POISON = Object.new.freeze
|
|
9
|
-
BATCH_SIZE =
|
|
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 = [
|
|
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
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
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
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
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
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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 =
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
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[:
|
|
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 =
|
|
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?
|
|
379
|
+
return true if done.nil?
|
|
321
380
|
return true if total && (total - done) <= 5
|
|
322
|
-
|
|
323
|
-
step <= 1 || done % step == 0
|
|
381
|
+
done % BATCH_SIZE == 1
|
|
324
382
|
end
|
|
325
383
|
end
|
|
326
384
|
end
|
data/lib/git_fit/version.rb
CHANGED