git-fit 0.10.11 → 0.11.2

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: 49ad8e07b35c5dc5be5cd8e9067fdaa21dc1684a6d5d5bb14ed873243c926de5
4
- data.tar.gz: 1b4057796536c438ce997cccf15268fa121f5ad28f19f7ba0af30922c374d2c5
3
+ metadata.gz: 6128a0ade228a52d4521a69f4cf036c2edd0507db418bdab7a27439c7675463e
4
+ data.tar.gz: 889d4c1a82660bd67a5e00344e66bd05ce36071926cde030dda1a88f6272008c
5
5
  SHA512:
6
- metadata.gz: c835f47ca63aebfb8e7493fc522dca3f77768469d8b1b73d5293d882c1ccada07e03db64fae6063c5d2628ee579563e4842e75c6c5d1d2a4163a20801c337ec4
7
- data.tar.gz: d0858df7943c64dd1ca22089be6eb8bfb09194fe3a2b8124c4b95051315edc095518388e6d76fe9a8d3f5c75b357a0f02cf0a7a9e075b6c9d68bf83a1bf27650
6
+ metadata.gz: ca1456122456538768205397ff39ac128f1cdec115dcbeacd9c37874fa62bae20011ed4b98560c68d762f1a2fb80ab8ec5e42e8a5539713ed66f73a6f7eec079
7
+ data.tar.gz: 0edbddf20214183b02caa9dab86d17c20ae7f87a5cdfdf450e939c45a32bd23acc5c5a30209293b53c1def43e85ed090920804123aa21e5cdce9e604e6d54777
data/lib/git-fit.rb CHANGED
@@ -72,6 +72,7 @@ require_relative 'git_fit/sync/xoss'
72
72
  require_relative 'git_fit/sync/xingzhe'
73
73
  require_relative 'git_fit/sync/runner'
74
74
  require_relative 'git_fit/cli/sync'
75
+ require_relative 'git_fit/cli/checkpointable'
75
76
  require_relative 'git_fit/db/rebuild'
76
77
  require_relative 'git_fit/db/connection'
77
78
  require_relative 'git_fit/db/activity'
@@ -0,0 +1,16 @@
1
+ # frozen_string_literal: true
2
+
3
+ module GitFit
4
+ module Cli
5
+ module Checkpointable
6
+ def checkpoint_db(db)
7
+ result = db.fetch('PRAGMA wal_checkpoint(TRUNCATE)').first
8
+ say_status :checkpoint, "WAL checkpoint: #{result[:checkpointed]} pages", :green
9
+ result
10
+ rescue StandardError => e
11
+ say_status :error, "Checkpoint failed: #{e.message}", :red
12
+ nil
13
+ end
14
+ end
15
+ end
16
+ end
@@ -4,6 +4,8 @@ require 'thor'
4
4
 
5
5
  module GitFit
6
6
  class GeoCLI < Thor
7
+ include Cli::Checkpointable
8
+
7
9
  desc 'detect', 'Detect administrative divisions for activities with GPS data'
8
10
  option :limit, type: :numeric, aliases: '-l', desc: 'Max activities to process'
9
11
  option :batch, type: :numeric, aliases: '-b', desc: 'Batch size', default: 20
@@ -4,6 +4,7 @@ require 'thor'
4
4
 
5
5
  module GitFit
6
6
  class ImportCLI < Thor
7
+ include Cli::Checkpointable
7
8
  FILE_SOURCES = %w[gpx fit tcx].freeze
8
9
 
9
10
  desc 'all', 'Import from all sources (gpx, fit, tcx)'
data/lib/git_fit/cli.rb CHANGED
@@ -6,6 +6,7 @@ module GitFit
6
6
  class CLI < Thor
7
7
  package_name 'git-fit'
8
8
  include Cli::Sync
9
+ include Cli::Checkpointable
9
10
 
10
11
  class_option :config, type: :string, desc: 'Config file path', aliases: '-c'
11
12
 
@@ -162,15 +163,6 @@ module GitFit
162
163
  conn.db
163
164
  end
164
165
 
165
- def checkpoint_db(db)
166
- result = db.fetch('PRAGMA wal_checkpoint(TRUNCATE)').first
167
- say_status :checkpoint, "WAL checkpoint: #{result[:checkpointed]} pages", :green
168
- result
169
- rescue StandardError => e
170
- say_status :error, "Checkpoint failed: #{e.message}", :red
171
- nil
172
- end
173
-
174
166
  def deep_dup(obj)
175
167
  case obj
176
168
  when Hash then obj.each_with_object({}) { |(k, v), h| h[k] = deep_dup(v) }
@@ -48,6 +48,8 @@ module GitFit
48
48
  # xingzhe: # env: GIT_FIT_XINGZHE_EMAIL
49
49
  # email: "" # env: GIT_FIT_XINGZHE_PASSWORD
50
50
  # password: ""
51
+ # session_path: "" # env: GIT_FIT_XINGZHE_SESSION_PATH
52
+ # # default data/cache/xingzhe_session.json
51
53
 
52
54
  export:
53
55
  json:
@@ -22,14 +22,17 @@ module GitFit
22
22
 
23
23
  RAW_EXT = 'json'
24
24
 
25
+ SESSION_TTL = 30 * 24 * 60 * 60
26
+
25
27
  register_adapter
26
28
  config_key 'xingzhe'
27
- register_config :email, :password
29
+ register_config :email, :password, :session_path
28
30
 
29
31
  def initialize(...)
30
32
  super
31
33
  @email = @config['email']
32
34
  @password = @config['password']
35
+ @session_path = @config['session_path'] || File.join('data', 'cache', 'xingzhe_session.json')
33
36
  end
34
37
 
35
38
  def before_call
@@ -38,9 +41,15 @@ module GitFit
38
41
  end
39
42
 
40
43
  def authenticate
41
- result = login
42
- @auth_method = 'login' if result
43
- result
44
+ if load_session && session_valid?
45
+ @auth_method = 'cached'
46
+ return true
47
+ end
48
+ if login
49
+ @auth_method = 'login'
50
+ return true
51
+ end
52
+ false
44
53
  end
45
54
 
46
55
  def pending_ids
@@ -67,6 +76,14 @@ module GitFit
67
76
  process_activity(pending[:summary], pending[:type])
68
77
  end
69
78
 
79
+ def auth_error?
80
+ [302, 401, 403].include?(@last_http_code)
81
+ end
82
+
83
+ def last_auth_code
84
+ @last_http_code
85
+ end
86
+
70
87
  private
71
88
 
72
89
  def http_request(method, path, body: nil, params: nil, headers: {})
@@ -85,7 +102,26 @@ module GitFit
85
102
  http.use_ssl = true
86
103
  http.open_timeout = 30
87
104
  http.read_timeout = 60
88
- http.start { |h| h.request(req) }
105
+ resp = http.start { |h| h.request(req) }
106
+ @last_http_code = resp.code.to_i
107
+ resp
108
+ end
109
+
110
+ def authed_get(path, params: nil)
111
+ resp = http_request(:get, path, params: params, headers: @headers || {})
112
+ return resp unless auth_failure?(resp)
113
+
114
+ remove_stale_session
115
+ return resp unless login
116
+
117
+ http_request(:get, path, params: params, headers: @headers)
118
+ end
119
+
120
+ def auth_failure?(resp)
121
+ return false unless resp
122
+ c = resp.code.to_i
123
+ (c == 400 && resp.body.to_s.include?('not allowed')) ||
124
+ [302, 401, 403].include?(c)
89
125
  end
90
126
 
91
127
  def login
@@ -102,10 +138,57 @@ module GitFit
102
138
  cookie = extract_cookie(resp)
103
139
  return false unless cookie
104
140
 
141
+ @session_expires_at = parse_max_age(resp)
142
+ @headers = { 'Cookie' => cookie, 'Accept' => 'application/json' }
143
+ save_session(cookie)
144
+ true
145
+ end
146
+
147
+ def load_session
148
+ return false unless File.exist?(@session_path)
149
+
150
+ data = JSON.parse(File.read(@session_path))
151
+ cookie = data['cookie']
152
+ @session_expires_at = data['expires_at']
153
+ return false unless cookie
154
+
105
155
  @headers = { 'Cookie' => cookie, 'Accept' => 'application/json' }
106
156
  true
157
+ rescue StandardError => e
158
+ warn "XingZhe: failed to load cached session: #{e.message}"
159
+ remove_stale_session
160
+ false
161
+ end
162
+
163
+ def session_valid?
164
+ return false unless @session_expires_at
165
+ Time.now.to_i < @session_expires_at - 60
166
+ end
167
+
168
+ def save_session(cookie)
169
+ FileUtils.mkdir_p(File.dirname(@session_path))
170
+ File.write(@session_path, JSON.generate({
171
+ cookie: cookie,
172
+ expires_at: @session_expires_at,
173
+ }))
174
+ rescue StandardError => e
175
+ warn "XingZhe: failed to save session: #{e.message}"
176
+ end
177
+
178
+ def remove_stale_session
179
+ File.delete(@session_path) if File.exist?(@session_path)
180
+ rescue StandardError => e
181
+ warn "XingZhe: failed to remove stale session: #{e.message}"
182
+ end
183
+
184
+ def parse_max_age(resp)
185
+ set_cookie = resp['set-cookie'].to_s
186
+ ma = set_cookie[/Max-Age=(\d+)/, 1]
187
+ ma ? Time.now.to_i + ma.to_i : Time.now.to_i + SESSION_TTL
107
188
  end
108
189
 
190
+ attr_reader :session_path
191
+
109
192
  def rsa_encrypt(password)
110
193
  pem = "-----BEGIN PUBLIC KEY-----\n#{RSA_PUBKEY}\n-----END PUBLIC KEY-----"
111
194
  key = OpenSSL::PKey::RSA.new(pem)
@@ -124,9 +207,8 @@ module GitFit
124
207
  end
125
208
 
126
209
  def fetch_page(offset, limit)
127
- resp = http_request(:get, WORKOUTS_PATH,
210
+ resp = authed_get(WORKOUTS_PATH,
128
211
  params: { offset: offset, limit: limit },
129
- headers: @headers
130
212
  )
131
213
  return nil unless resp.code.to_i == 200
132
214
 
@@ -202,7 +284,7 @@ module GitFit
202
284
  end
203
285
 
204
286
  def fetch_stream(id)
205
- resp = http_request(:get, "#{STREAM_PATH}#{id}/stream/", headers: @headers || {})
287
+ resp = authed_get("#{STREAM_PATH}#{id}/stream/")
206
288
  return nil unless resp.code.to_i == 200
207
289
 
208
290
  JSON.parse(resp.body).dig('data')
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module GitFit
4
- VERSION = '0.10.11'
4
+ VERSION = '0.11.2'
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.10.11
4
+ version: 0.11.2
5
5
  platform: ruby
6
6
  authors:
7
7
  - Lax
@@ -254,6 +254,7 @@ files:
254
254
  - lib/git_fit/auth/garmin_token.rb
255
255
  - lib/git_fit/auth/strava.rb
256
256
  - lib/git_fit/cli.rb
257
+ - lib/git_fit/cli/checkpointable.rb
257
258
  - lib/git_fit/cli/export.rb
258
259
  - lib/git_fit/cli/geo_cli.rb
259
260
  - lib/git_fit/cli/gh_cli.rb
@@ -305,7 +306,6 @@ files:
305
306
  - lib/git_fit/source.rb
306
307
  - lib/git_fit/source/base.rb
307
308
  - lib/git_fit/source/keep.rb
308
- - lib/git_fit/source/tally.rb
309
309
  - lib/git_fit/sport_mapper.rb
310
310
  - lib/git_fit/std/resolver.rb
311
311
  - lib/git_fit/strava_web/file_check.rb
@@ -1,57 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module GitFit
4
- module Source
5
- class Tally
6
- DIGITS = %w[零 一 二 三 四 五 六 七 八 九 十]
7
-
8
- def initialize(count = 0)
9
- @count = count
10
- end
11
-
12
- def add(n = 1)
13
- @count += n
14
- end
15
-
16
- def to_s
17
- return @count.to_s if @count > 9999
18
- chinese_numeral
19
- rescue StandardError
20
- @count.to_s
21
- end
22
-
23
- private
24
-
25
- def chinese_numeral
26
- n = @count
27
- return DIGITS[0] if n == 0
28
- return DIGITS[n] if n <= 10
29
-
30
- result = ''
31
- thousands = n / 1000
32
- n %= 1000
33
- result += DIGITS[thousands] + '千' if thousands > 0
34
-
35
- hundreds = n / 100
36
- n %= 100
37
- if hundreds > 0
38
- result += DIGITS[hundreds] + '百'
39
- elsif thousands > 0 && n > 0
40
- result += '零'
41
- end
42
-
43
- tens = n / 10
44
- ones = n % 10
45
- if tens > 0
46
- join_ten = tens > 1 || !result.empty?
47
- result += (join_ten ? DIGITS[tens] : '') + '十'
48
- elsif !result.empty? && tens == 0 && ones > 0 && !result.end_with?('零')
49
- result += '零'
50
- end
51
-
52
- result += DIGITS[ones] if ones > 0
53
- result
54
- end
55
- end
56
- end
57
- end