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 +4 -4
- data/lib/git-fit.rb +1 -0
- data/lib/git_fit/cli/checkpointable.rb +16 -0
- data/lib/git_fit/cli/geo_cli.rb +2 -0
- data/lib/git_fit/cli/import_cli.rb +1 -0
- data/lib/git_fit/cli.rb +1 -9
- data/lib/git_fit/config_template.rb +2 -0
- data/lib/git_fit/sync/xingzhe.rb +90 -8
- data/lib/git_fit/version.rb +1 -1
- metadata +2 -2
- data/lib/git_fit/source/tally.rb +0 -57
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 6128a0ade228a52d4521a69f4cf036c2edd0507db418bdab7a27439c7675463e
|
|
4
|
+
data.tar.gz: 889d4c1a82660bd67a5e00344e66bd05ce36071926cde030dda1a88f6272008c
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
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
|
data/lib/git_fit/cli/geo_cli.rb
CHANGED
|
@@ -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
|
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) }
|
data/lib/git_fit/sync/xingzhe.rb
CHANGED
|
@@ -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
|
-
|
|
42
|
-
|
|
43
|
-
|
|
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 =
|
|
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 =
|
|
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')
|
data/lib/git_fit/version.rb
CHANGED
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.
|
|
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
|
data/lib/git_fit/source/tally.rb
DELETED
|
@@ -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
|