git-fit 0.7.9 → 0.8.0

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.
Files changed (58) hide show
  1. checksums.yaml +4 -4
  2. data/lib/git-fit.rb +44 -42
  3. data/lib/git_fit/cli/export.rb +8 -6
  4. data/lib/git_fit/cli/geo_cli.rb +10 -8
  5. data/lib/git_fit/cli/gh_cli.rb +24 -22
  6. data/lib/git_fit/cli/import_cli.rb +40 -38
  7. data/lib/git_fit/cli/install_cli.rb +10 -8
  8. data/lib/git_fit/cli/sync.rb +9 -7
  9. data/lib/git_fit/cli.rb +29 -27
  10. data/lib/git_fit/config.rb +42 -28
  11. data/lib/git_fit/config_template.rb +2 -0
  12. data/lib/git_fit/db/activity.rb +2 -0
  13. data/lib/git_fit/db/cli.rb +11 -9
  14. data/lib/git_fit/db/connection.rb +8 -6
  15. data/lib/git_fit/db/rebuild.rb +5 -3
  16. data/lib/git_fit/export/defaults.rb +14 -12
  17. data/lib/git_fit/export/json.rb +5 -3
  18. data/lib/git_fit/export.rb +4 -2
  19. data/lib/git_fit/fit/decoder.rb +9 -7
  20. data/lib/git_fit/fit.rb +3 -1
  21. data/lib/git_fit/geo/amap_client.rb +15 -13
  22. data/lib/git_fit/geo/coord_transform.rb +2 -0
  23. data/lib/git_fit/geo/division_detector.rb +31 -26
  24. data/lib/git_fit/geo/nominatim_client.rb +14 -12
  25. data/lib/git_fit/geo/polyline.rb +4 -2
  26. data/lib/git_fit/geo/reverse_geocode.rb +11 -9
  27. data/lib/git_fit/geo.rb +8 -6
  28. data/lib/git_fit/import/apple_health.rb +84 -82
  29. data/lib/git_fit/import/local_file.rb +45 -42
  30. data/lib/git_fit/import.rb +4 -2
  31. data/lib/git_fit/install/actions.rb +15 -13
  32. data/lib/git_fit/parser/base.rb +12 -10
  33. data/lib/git_fit/parser/fit.rb +11 -9
  34. data/lib/git_fit/parser/gpx.rb +17 -15
  35. data/lib/git_fit/parser/tcx.rb +22 -20
  36. data/lib/git_fit/parser.rb +6 -4
  37. data/lib/git_fit/privacy/polyline_filter.rb +6 -4
  38. data/lib/git_fit/source/base.rb +25 -23
  39. data/lib/git_fit/source/tally.rb +9 -7
  40. data/lib/git_fit/source.rb +4 -2
  41. data/lib/git_fit/sport_mapper.rb +23 -21
  42. data/lib/git_fit/sync/base.rb +29 -27
  43. data/lib/git_fit/sync/garmin.rb +5 -3
  44. data/lib/git_fit/sync/garmin_base.rb +145 -143
  45. data/lib/git_fit/sync/garmin_base_di.rb +42 -40
  46. data/lib/git_fit/sync/garmin_cn.rb +8 -6
  47. data/lib/git_fit/sync/igpsport.rb +43 -41
  48. data/lib/git_fit/sync/keep.rb +98 -96
  49. data/lib/git_fit/sync/lorem.rb +31 -29
  50. data/lib/git_fit/sync/runner.rb +14 -11
  51. data/lib/git_fit/sync/strava.rb +61 -52
  52. data/lib/git_fit/sync/xingzhe.rb +52 -50
  53. data/lib/git_fit/sync/xoss.rb +68 -66
  54. data/lib/git_fit/timezone/resolver.rb +5 -3
  55. data/lib/git_fit/util/tally.rb +10 -8
  56. data/lib/git_fit/util.rb +2 -0
  57. data/lib/git_fit/version.rb +3 -1
  58. metadata +1 -1
@@ -1,39 +1,41 @@
1
- require_relative "base"
2
- require "base64"
3
- require "zlib"
4
- require "openssl"
5
- require "time"
6
- require "tzinfo"
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'base'
4
+ require 'base64'
5
+ require 'zlib'
6
+ require 'openssl'
7
+ require 'time'
8
+ require 'tzinfo'
7
9
 
8
10
  module GitFit
9
11
  module Sync
10
12
  class Keep < Base
11
- LOGIN_URL = "https://api.gotokeep.com/v1.1/users/login"
12
- STATS_URL = "https://api.gotokeep.com/pd/v3/stats/detail?dateUnit=all&type=%{sport}&lastDate=%{last_date}"
13
- LOG_URL = "https://api.gotokeep.com/pd/v3/%{sport}log/%{run_id}"
13
+ LOGIN_URL = 'https://api.gotokeep.com/v1.1/users/login'
14
+ STATS_URL = 'https://api.gotokeep.com/pd/v3/stats/detail?dateUnit=all&type=%{sport}&lastDate=%{last_date}'
15
+ LOG_URL = 'https://api.gotokeep.com/pd/v3/%{sport}log/%{run_id}'
14
16
 
15
- AES_KEY = Base64.decode64("NTZmZTU5OzgyZzpkODczYw==")
16
- AES_IV = Base64.decode64("MjM0Njg5MjQzMjkyMDMwMA==")
17
+ AES_KEY = Base64.decode64('NTZmZTU5OzgyZzpkODczYw==')
18
+ AES_IV = Base64.decode64('MjM0Njg5MjQzMjkyMDMwMA==')
17
19
 
18
- PLACEHOLDER_POLYLINE = "gqqrFurkeU??".freeze
20
+ PLACEHOLDER_POLYLINE = 'gqqrFurkeU??'
19
21
 
20
22
  KEEP_TYPES = %w[running hiking cycling].freeze
21
- KEEP_RAW_EXT = "json".freeze
23
+ KEEP_RAW_EXT = 'json'
22
24
 
23
25
  TYPE_MAP = {
24
- "outdoorWalking" => "walk",
25
- "outdoorRunning" => "run",
26
- "outdoorCycling" => "ride",
27
- "indoorRunning" => "run",
28
- "mountaineering" => "hike"
26
+ 'outdoorWalking' => 'walk',
27
+ 'outdoorRunning' => 'run',
28
+ 'outdoorCycling' => 'ride',
29
+ 'indoorRunning' => 'run',
30
+ 'mountaineering' => 'hike',
29
31
  }.freeze
30
32
 
31
33
  STRAVA_TYPE_MAP = {
32
- "outdoorWalking" => "Walk",
33
- "outdoorRunning" => "Run",
34
- "outdoorCycling" => "Ride",
35
- "indoorRunning" => "VirtualRun",
36
- "mountaineering" => "Hiking"
34
+ 'outdoorWalking' => 'Walk',
35
+ 'outdoorRunning' => 'Run',
36
+ 'outdoorCycling' => 'Ride',
37
+ 'indoorRunning' => 'VirtualRun',
38
+ 'mountaineering' => 'Hiking',
37
39
  }.freeze
38
40
 
39
41
  TIMESTAMP_THRESHOLD = 3_600_000
@@ -44,11 +46,11 @@ module GitFit
44
46
 
45
47
  def initialize(config:, db:, activity_filter: nil, privacy: nil, time_budget: nil)
46
48
  super
47
- @phone = @config["phone"] || @config["mobile"]
48
- @password = @config["password"]
49
+ @phone = @config['phone'] || @config['mobile']
50
+ @password = @config['password']
49
51
  @headers = {
50
- "User-Agent" => "Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:78.0) Gecko/20100101 Firefox/78.0",
51
- "Content-Type" => "application/x-www-form-urlencoded;charset=utf-8"
52
+ 'User-Agent' => 'Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:78.0) Gecko/20100101 Firefox/78.0',
53
+ 'Content-Type' => 'application/x-www-form-urlencoded;charset=utf-8',
52
54
  }
53
55
  @session = nil
54
56
  end
@@ -96,7 +98,7 @@ module GitFit
96
98
  headers.each { |k, v| req[k] = v }
97
99
  req.body = body.is_a?(Hash) ? URI.encode_www_form(body) : body
98
100
  http = Net::HTTP.new(uri.host, uri.port)
99
- http.use_ssl = uri.scheme == "https"
101
+ http.use_ssl = uri.scheme == 'https'
100
102
  http.open_timeout = 30
101
103
  http.read_timeout = 60
102
104
  http.start { |h| h.request(req) }
@@ -107,7 +109,7 @@ module GitFit
107
109
  req = Net::HTTP::Get.new(uri)
108
110
  headers.each { |k, v| req[k] = v }
109
111
  http = Net::HTTP.new(uri.host, uri.port)
110
- http.use_ssl = uri.scheme == "https"
112
+ http.use_ssl = uri.scheme == 'https'
111
113
  http.open_timeout = 30
112
114
  http.read_timeout = 60
113
115
  http.start { |h| h.request(req) }
@@ -118,10 +120,10 @@ module GitFit
118
120
  return nil unless resp.code.to_i == 200
119
121
 
120
122
  data = JSON.parse(resp.body)
121
- token = data.dig("data", "token")
123
+ token = data.dig('data', 'token')
122
124
  return nil unless token
123
125
 
124
- @headers["Authorization"] = "Bearer #{token}"
126
+ @headers['Authorization'] = "Bearer #{token}"
125
127
  @session = true
126
128
  rescue JSON::ParserError
127
129
  nil
@@ -134,7 +136,7 @@ module GitFit
134
136
  end
135
137
 
136
138
  def existing_run_ids
137
- @db[:activities].where(source: "keep").select_map(:run_id).map(&:to_s)
139
+ @db[:activities].where(source: 'keep').select_map(:run_id).map(&:to_s)
138
140
  end
139
141
 
140
142
  def fetch_run_ids(sport)
@@ -146,19 +148,19 @@ module GitFit
146
148
  resp = @session ? http_get(url, @headers) : nil
147
149
  break unless resp&.code&.to_i == 200
148
150
 
149
- data = JSON.parse(resp.body)["data"]
151
+ data = JSON.parse(resp.body)['data']
150
152
  break unless data
151
153
 
152
- records = data["records"] || []
154
+ records = data['records'] || []
153
155
  records.each do |r|
154
- logs = r["logs"] || []
156
+ logs = r['logs'] || []
155
157
  logs.each do |log|
156
- stats = log["stats"] || log
157
- ids << stats["id"] unless stats["isDoubtful"]
158
+ stats = log['stats'] || log
159
+ ids << stats['id'] unless stats['isDoubtful']
158
160
  end
159
161
  end
160
162
 
161
- last_date = data["lastTimestamp"]
163
+ last_date = data['lastTimestamp']
162
164
  break if last_date.nil? || last_date.zero?
163
165
 
164
166
  spider_sleep
@@ -175,26 +177,26 @@ module GitFit
175
177
  return false unless resp.code.to_i == 200
176
178
 
177
179
  data = JSON.parse(resp.body)
178
- run_data = data["data"]
180
+ run_data = data['data']
179
181
  return false unless run_data
180
182
 
181
183
  payload = decrypt_raw_payload(run_data)
182
- keep_id = extract_id(run_data["id"])
184
+ keep_id = extract_id(run_data['id'])
183
185
  return false unless keep_id
184
186
 
185
187
  write_source_archive({
186
- source: "keep",
187
- decodedAt: Time.now.utc.strftime("%Y-%m-%dT%H:%M:%SZ"),
188
+ source: 'keep',
189
+ decodedAt: Time.now.utc.strftime('%Y-%m-%dT%H:%M:%SZ'),
188
190
  meta: {
189
- startTime: run_data["startTime"],
190
- endTime: run_data["endTime"],
191
- duration: run_data["duration"],
192
- dataType: run_data["dataType"],
193
- distance: run_data["distance"],
194
- region: run_data["region"]
191
+ startTime: run_data['startTime'],
192
+ endTime: run_data['endTime'],
193
+ duration: run_data['duration'],
194
+ dataType: run_data['dataType'],
195
+ distance: run_data['distance'],
196
+ region: run_data['region'],
195
197
  },
196
198
  geoPoints: payload[:geo_points],
197
- heartRates: payload[:hr_data]
199
+ heartRates: payload[:hr_data],
198
200
  }, keep_id)
199
201
 
200
202
  wgs_points = compute_wgs_points(payload, run_data)
@@ -212,14 +214,14 @@ module GitFit
212
214
  def upsert_from_raw(compound_id)
213
215
  keep_id = extract_id(compound_id) || compound_id
214
216
  raw = JSON.parse(File.read(raw_path(keep_id)))
215
- run_data = raw["meta"].merge("id" => compound_id)
217
+ run_data = raw['meta'].merge('id' => compound_id)
216
218
 
217
219
  wgs = load_wgs_points(keep_id, raw)
218
220
  return false unless wgs&.any?
219
221
 
220
- if raw["heartRates"]&.any?
221
- vals = raw["heartRates"].map { |h| h["value"] }.compact
222
- run_data["heartRate"] = { "averageHeartRate" => vals.sum / vals.size } if vals.any?
222
+ if raw['heartRates']&.any?
223
+ vals = raw['heartRates'].map { |h| h['value'] }.compact
224
+ run_data['heartRate'] = { 'averageHeartRate' => vals.sum / vals.size } if vals.any?
223
225
  end
224
226
 
225
227
  attrs = build_activity_from_wgs(run_data, wgs)
@@ -233,19 +235,19 @@ module GitFit
233
235
  std_path = File.join(std_dir, "#{keep_id}.json")
234
236
  if File.exist?(std_path)
235
237
  pts = JSON.parse(File.read(std_path))
236
- pts.map { |pt| [pt["latitude"], pt["longitude"], pt["altitude"],
237
- pt["timestamp"] ? Time.parse(pt["timestamp"]) : nil, pt["heartrate"]] }
238
+ pts.map { |pt| [pt['latitude'], pt['longitude'], pt['altitude'],
239
+ pt['timestamp'] ? Time.parse(pt['timestamp']) : nil, pt['heartrate']] }
238
240
  else
239
- payload = { geo_points: raw["geoPoints"], hr_data: raw["heartRates"] }
240
- run_data = raw["meta"]
241
+ payload = { geo_points: raw['geoPoints'], hr_data: raw['heartRates'] }
242
+ run_data = raw['meta']
241
243
  compute_wgs_points(payload, run_data)
242
244
  end
243
245
  end
244
246
 
245
247
  def decrypt_raw_payload(run_data)
246
248
  {
247
- geo_points: decode_geo_points(run_data["geoPoints"]),
248
- hr_data: decode_hr_data(run_data.dig("heartRate", "heartRates"))
249
+ geo_points: decode_geo_points(run_data['geoPoints']),
250
+ hr_data: decode_hr_data(run_data.dig('heartRate', 'heartRates')),
249
251
  }
250
252
  end
251
253
 
@@ -254,16 +256,16 @@ module GitFit
254
256
  hr_data = payload[:hr_data]
255
257
  return [] unless geo_points&.any?
256
258
 
257
- start_time = run_data["startTime"]
258
- use_absolute_ts = geo_points.first["timestamp"].to_i > TIMESTAMP_THRESHOLD
259
+ start_time = run_data['startTime']
260
+ use_absolute_ts = geo_points.first['timestamp'].to_i > TIMESTAMP_THRESHOLD
259
261
  effective_start = use_absolute_ts ? 0 : start_time
260
262
 
261
263
  geo_points.map do |p|
262
- lat, lng = Geo::CoordTransform.gcj02_to_wgs84_exact(p["latitude"], p["longitude"])
263
- ts = p["timestamp"] || p["unixTimestamp"]
264
+ lat, lng = Geo::CoordTransform.gcj02_to_wgs84_exact(p['latitude'], p['longitude'])
265
+ ts = p['timestamp'] || p['unixTimestamp']
264
266
  pt_time = ts ? Time.at(effective_start / 1000.0 + ts.to_i / 10.0).utc : nil
265
267
  hr = find_nearest_hr(hr_data, ts, start_time)
266
- [lat, lng, p["altitude"], pt_time, hr]
268
+ [lat, lng, p['altitude'], pt_time, hr]
267
269
  end
268
270
  end
269
271
 
@@ -278,29 +280,29 @@ module GitFit
278
280
  tz_string = tz_resolver.resolve(lat, lng)
279
281
  tz = TZInfo::Timezone.get(tz_string)
280
282
  tz.utc_to_local(start_utc).iso8601
281
- rescue => e
283
+ rescue StandardError => e
282
284
  warn "Keep: timezone resolution failed: #{e.message}"
283
285
  start_utc.iso8601
284
286
  end
285
287
 
286
288
  def build_activity_from_wgs(run_data, wgs_points)
287
- keep_id = extract_id(run_data["id"])
289
+ keep_id = extract_id(run_data['id'])
288
290
  return nil unless keep_id
289
291
 
290
- start_time = run_data["startTime"]
291
- end_time = run_data["endTime"]
292
- duration = run_data["duration"]
292
+ start_time = run_data['startTime']
293
+ end_time = run_data['endTime']
294
+ duration = run_data['duration']
293
295
  return nil unless duration
294
296
 
295
297
  start_date = Time.at(start_time / 1000.0).utc
296
- data_type = run_data["dataType"]
298
+ data_type = run_data['dataType']
297
299
 
298
- avg_hr = run_data.dig("heartRate", "averageHeartRate")
300
+ avg_hr = run_data.dig('heartRate', 'averageHeartRate')
299
301
  avg_hr = nil if avg_hr && avg_hr < 0
300
302
 
301
303
  polyline_str = nil
302
- total_elevation = run_data["elevation"]
303
- computed_distance = run_data["distance"]
304
+ total_elevation = run_data['elevation']
305
+ computed_distance = run_data['distance']
304
306
 
305
307
  if wgs_points.any?
306
308
  if wgs_points.any? { |pt| pt[3] }
@@ -321,44 +323,44 @@ module GitFit
321
323
  distance: computed_distance&.round(2),
322
324
  moving_time: moving_time,
323
325
  elapsed_time: elapsed_time,
324
- sport_category: TYPE_MAP[data_type] || "other",
326
+ sport_category: TYPE_MAP[data_type] || 'other',
325
327
  sport_type: data_type,
326
328
  start_date: start_date.iso8601,
327
329
  start_date_local: local_time_from_gps(start_date, wgs_points),
328
- location_country: run_data["region"],
330
+ location_country: run_data['region'],
329
331
  summary_polyline: polyline_str,
330
332
  average_heartrate: avg_hr&.round(1),
331
333
  average_speed: computed_distance && moving_time&.positive? ? (computed_distance.to_f / moving_time).round(2) : nil,
332
334
  elevation_gain: total_elevation&.round(1),
333
- source: "keep"
335
+ source: 'keep',
334
336
  }
335
337
  end
336
338
 
337
339
  def write_standardized_json(wgs_points, payload, run_data, keep_id)
338
340
  geo_points = payload[:geo_points] || []
339
341
  hr_data = payload[:hr_data]
340
- start_time = run_data["startTime"]
342
+ start_time = run_data['startTime']
341
343
 
342
344
  extended = wgs_points.each_with_index.map do |pt, i|
343
345
  orig = geo_points[i] || {}
344
346
  ts = pt[3]
345
347
  {
346
348
  latitude: pt[0], longitude: pt[1], altitude: pt[2],
347
- timestamp: ts&.strftime("%Y-%m-%dT%H:%M:%SZ"),
349
+ timestamp: ts&.strftime('%Y-%m-%dT%H:%M:%SZ'),
348
350
  heartrate: pt[4],
349
- speed: orig["speed"],
350
- currentTotalDistance: orig["currentTotalDistance"],
351
- currentTotalDuration: orig["currentTotalDuration"],
352
- currentPace: orig["currentPace"],
353
- pressure: orig["pressure"],
354
- accuracyRadius: orig["accuracyRadius"],
355
- verticalAccuracy: orig["verticalAccuracy"],
356
- locationType: orig["locationType"],
357
- isPause: orig["isPause"],
358
- flags: orig["flags"],
359
- processLabel: orig["processLabel"],
360
- crossKmMark: orig["crossKmMark"],
361
- currentTotalSteps: orig["currentTotalSteps"]
351
+ speed: orig['speed'],
352
+ currentTotalDistance: orig['currentTotalDistance'],
353
+ currentTotalDuration: orig['currentTotalDuration'],
354
+ currentPace: orig['currentPace'],
355
+ pressure: orig['pressure'],
356
+ accuracyRadius: orig['accuracyRadius'],
357
+ verticalAccuracy: orig['verticalAccuracy'],
358
+ locationType: orig['locationType'],
359
+ isPause: orig['isPause'],
360
+ flags: orig['flags'],
361
+ processLabel: orig['processLabel'],
362
+ crossKmMark: orig['crossKmMark'],
363
+ currentTotalSteps: orig['currentTotalSteps']
362
364
  }
363
365
  end
364
366
 
@@ -376,7 +378,7 @@ module GitFit
376
378
 
377
379
  def extract_id(id_str)
378
380
  return nil unless id_str
379
- id_str.split("_")[1]
381
+ id_str.split('_')[1]
380
382
  end
381
383
 
382
384
  def decode_geo_points(geo_data)
@@ -399,7 +401,7 @@ module GitFit
399
401
  end
400
402
 
401
403
  def aes_decrypt(data)
402
- cipher = OpenSSL::Cipher.new("AES-128-CBC")
404
+ cipher = OpenSSL::Cipher.new('AES-128-CBC')
403
405
  cipher.decrypt
404
406
  cipher.key = AES_KEY
405
407
  cipher.iv = AES_IV
@@ -418,7 +420,7 @@ module GitFit
418
420
  next
419
421
  end
420
422
  end
421
- raise Zlib::DataError, "cannot inflate data"
423
+ raise Zlib::DataError, 'cannot inflate data'
422
424
  end
423
425
 
424
426
  def find_nearest_hr(hr_data, geo_ts, start_time, threshold: HR_THRESHOLD)
@@ -438,7 +440,7 @@ module GitFit
438
440
  min_diff = Float::INFINITY
439
441
 
440
442
  hr_data.each do |item|
441
- ts = item["timestamp"]
443
+ ts = item['timestamp']
442
444
  next unless ts
443
445
 
444
446
  diff = (normalize.call(ts) - target).abs
@@ -448,7 +450,7 @@ module GitFit
448
450
  end
449
451
  end
450
452
 
451
- hr = nearest&.dig("beatsPerMinute")
453
+ hr = nearest&.dig('beatsPerMinute')
452
454
  hr&.positive? ? hr : nil
453
455
  end
454
456
 
@@ -1,6 +1,8 @@
1
- require "json"
2
- require "fileutils"
3
- require "time"
1
+ # frozen_string_literal: true
2
+
3
+ require 'json'
4
+ require 'fileutils'
5
+ require 'time'
4
6
 
5
7
  module GitFit
6
8
  module Sync
@@ -9,41 +11,41 @@ module GitFit
9
11
  register_test_adapter
10
12
 
11
13
  DEMO_ACTIVITIES = [
12
- { id: "alpha_001", name: "Lorem Morning Run", sport_category: "run",
14
+ { id: 'alpha_001', name: 'Lorem Morning Run', sport_category: 'run',
13
15
  distance: 5200.0, moving_time: 1800, elapsed_time: 2000,
14
- start_date: "2025-01-15T06:30:00Z", start_date_local: "2025-01-15T14:30:00+08:00",
15
- location_country: "CN", average_heartrate: 148.0, max_heartrate: 165.0,
16
+ start_date: '2025-01-15T06:30:00Z', start_date_local: '2025-01-15T14:30:00+08:00',
17
+ location_country: 'CN', average_heartrate: 148.0, max_heartrate: 165.0,
16
18
  average_cadence: 172.0, max_cadence: 185.0, average_power: nil, max_power: nil,
17
19
  average_temperature: 22.0, calories: 380,
18
- average_speed: 5200.0 / 1800, elevation_gain: 125.0, external_id: "lorem_001" },
19
- { id: "beta_002", name: "Ipsum Cycling Session", sport_category: "ride",
20
+ average_speed: 5200.0 / 1800, elevation_gain: 125.0, external_id: 'lorem_001' },
21
+ { id: 'beta_002', name: 'Ipsum Cycling Session', sport_category: 'ride',
20
22
  distance: 35000.0, moving_time: 5400, elapsed_time: 6000,
21
- start_date: "2025-01-14T08:00:00Z", start_date_local: "2025-01-14T16:00:00+08:00",
22
- location_country: "CN", average_heartrate: 138.0, max_heartrate: 155.0,
23
+ start_date: '2025-01-14T08:00:00Z', start_date_local: '2025-01-14T16:00:00+08:00',
24
+ location_country: 'CN', average_heartrate: 138.0, max_heartrate: 155.0,
23
25
  average_cadence: 85.0, max_cadence: 105.0, average_power: 185.0, max_power: 420.0,
24
26
  average_temperature: 18.0, calories: 850,
25
- average_speed: 35000.0 / 5400, elevation_gain: 450.0, external_id: "lorem_002" },
26
- { id: "gamma_003", name: "Dolor Hike", sport_category: "hike",
27
+ average_speed: 35000.0 / 5400, elevation_gain: 450.0, external_id: 'lorem_002' },
28
+ { id: 'gamma_003', name: 'Dolor Hike', sport_category: 'hike',
27
29
  distance: 12000.0, moving_time: 7200, elapsed_time: 8100,
28
- start_date: "2025-01-13T09:00:00Z", start_date_local: "2025-01-13T17:00:00+08:00",
29
- location_country: "CN", average_heartrate: 122.0, max_heartrate: 142.0,
30
+ start_date: '2025-01-13T09:00:00Z', start_date_local: '2025-01-13T17:00:00+08:00',
31
+ location_country: 'CN', average_heartrate: 122.0, max_heartrate: 142.0,
30
32
  average_cadence: 110.0, max_cadence: 135.0, average_power: nil, max_power: nil,
31
33
  average_temperature: 15.0, calories: 620,
32
- average_speed: 12000.0 / 7200, elevation_gain: 820.0, external_id: "lorem_003" },
33
- { id: "delta_004", name: "Sit Amet Walk", sport_category: "walk",
34
+ average_speed: 12000.0 / 7200, elevation_gain: 820.0, external_id: 'lorem_003' },
35
+ { id: 'delta_004', name: 'Sit Amet Walk', sport_category: 'walk',
34
36
  distance: 5000.0, moving_time: 3600, elapsed_time: 3900,
35
- start_date: "2025-01-12T18:00:00Z", start_date_local: "2025-01-12T18:00:00+08:00",
36
- location_country: "CN", average_heartrate: 108.0, max_heartrate: 125.0,
37
+ start_date: '2025-01-12T18:00:00Z', start_date_local: '2025-01-12T18:00:00+08:00',
38
+ location_country: 'CN', average_heartrate: 108.0, max_heartrate: 125.0,
37
39
  average_cadence: 105.0, max_cadence: 118.0, average_power: nil, max_power: nil,
38
40
  average_temperature: 20.0, calories: 280,
39
- average_speed: 5000.0 / 3600, elevation_gain: 35.0, external_id: "lorem_004" },
40
- { id: "epsilon_005", name: "Consectetur Swim", sport_category: "swim",
41
+ average_speed: 5000.0 / 3600, elevation_gain: 35.0, external_id: 'lorem_004' },
42
+ { id: 'epsilon_005', name: 'Consectetur Swim', sport_category: 'swim',
41
43
  distance: 2000.0, moving_time: 2400, elapsed_time: 2700,
42
- start_date: "2025-01-11T07:00:00Z", start_date_local: "2025-01-11T15:00:00+08:00",
43
- location_country: "CN", average_heartrate: 135.0, max_heartrate: 150.0,
44
+ start_date: '2025-01-11T07:00:00Z', start_date_local: '2025-01-11T15:00:00+08:00',
45
+ location_country: 'CN', average_heartrate: 135.0, max_heartrate: 150.0,
44
46
  average_cadence: 28.0, max_cadence: 32.0, average_power: nil, max_power: nil,
45
47
  average_temperature: 26.0, calories: 520,
46
- average_speed: 2000.0 / 2400, elevation_gain: 0.0, external_id: "lorem_005" }
48
+ average_speed: 2000.0 / 2400, elevation_gain: 0.0, external_id: 'lorem_005' }
47
49
  ].freeze
48
50
 
49
51
  def authenticate
@@ -97,8 +99,8 @@ module GitFit
97
99
  calories: act[:calories],
98
100
  average_speed: act[:average_speed],
99
101
  elevation_gain: act[:elevation_gain],
100
- source: "lorem",
101
- external_id: act[:external_id]
102
+ source: 'lorem',
103
+ external_id: act[:external_id],
102
104
  }
103
105
  end
104
106
 
@@ -108,13 +110,13 @@ module GitFit
108
110
  num_points = [(distance / 500.0).ceil, 20].max
109
111
 
110
112
  case category
111
- when "run"
113
+ when 'run'
112
114
  (0...num_points).map { |i| [base_lat + i * 0.0002, base_lng + i * 0.0003] }
113
- when "ride"
115
+ when 'ride'
114
116
  (0...num_points).map { |i| [base_lat + i * 0.0005, base_lng + i * 0.0008] }
115
- when "hike"
117
+ when 'hike'
116
118
  (0...num_points).map { |i| [base_lat + i * 0.0003 + Math.sin(i * 0.5) * 0.0001, base_lng + i * 0.0004] }
117
- when "swim"
119
+ when 'swim'
118
120
  [[base_lat, base_lng]]
119
121
  else
120
122
  (0...num_points).map { |i| [base_lat + i * 0.0002, base_lng + i * 0.0002] }
@@ -1,4 +1,6 @@
1
- require "thread"
1
+ # frozen_string_literal: true
2
+
3
+ require 'thread'
2
4
 
3
5
  module GitFit
4
6
  module Sync
@@ -76,29 +78,30 @@ module GitFit
76
78
  actual_key = klass&.config_key || source
77
79
  cfg = @config.sync_config(actual_key)
78
80
  unless klass
79
- output_queue << { type: :banner, source:, text: "no adapter" }
81
+ output_queue << { type: :banner, source:, text: 'no adapter' }
80
82
  clear_source(source)
81
83
  return false
82
84
  end
83
85
 
84
- output_queue << { type: :banner, source:, text: "authenticating" }
85
- adapter = klass.new(config: cfg, db: @db, activity_filter: @activity_filter, privacy: @privacy, time_budget: @time_budget)
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)
86
89
  unless adapter.before_call
87
- output_queue << { type: :banner, source:, text: "before_call failed" }
90
+ output_queue << { type: :banner, source:, text: 'before_call failed' }
88
91
  clear_source(source)
89
92
  return false
90
93
  end
91
94
 
92
95
  unless adapter.authenticate
93
- output_queue << { type: :banner, source:, text: "auth failed" }
96
+ output_queue << { type: :banner, source:, text: 'auth failed' }
94
97
  clear_source(source)
95
98
  return false
96
99
  end
97
100
 
98
- output_queue << { type: :banner, source:, text: "fetching activity list" }
101
+ output_queue << { type: :banner, source:, text: 'fetching activity list' }
99
102
  ids = adapter.pending_ids
100
103
  if ids.nil? || ids.empty?
101
- output_queue << { type: :banner, source:, text: "no new activities" }
104
+ output_queue << { type: :banner, source:, text: 'no new activities' }
102
105
  clear_source(source)
103
106
  return false
104
107
  end
@@ -125,7 +128,7 @@ module GitFit
125
128
 
126
129
  begin
127
130
  process_source(source)
128
- rescue => e
131
+ rescue StandardError => e
129
132
  @output_queue << { type: :error, source:, message: "Worker crashed: #{e.message}" }
130
133
  @stats_lock.synchronize do
131
134
  @pending_ids.delete(source)
@@ -194,7 +197,7 @@ module GitFit
194
197
  info: nil, done: processed, total: total
195
198
  }
196
199
  end
197
- rescue => e
200
+ rescue StandardError => e
198
201
  @output_queue << { type: :error, source:, message: e.message }
199
202
  end
200
203
 
@@ -306,7 +309,7 @@ module GitFit
306
309
  end
307
310
  end
308
311
  $stdout.flush
309
- rescue => e
312
+ rescue StandardError => e
310
313
  $stdout.puts " Runner output error: #{e.message}"
311
314
  $stdout.flush
312
315
  end