git-fit 0.16.1 → 0.17.1

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: b6d2e554e3fbd61794daf4d15fe4a934d20e1bf8f988fbe13778e925eb26bb91
4
- data.tar.gz: 83550dc8b0bb9ada27501fab66a5d9fd0e8f5dd5aadb846de9dfdbb46e5ccedb
3
+ metadata.gz: 1091af70dde8e2b640208f49633b3572ddf64e41c5063ec4541349a873829988
4
+ data.tar.gz: aa450db53cfe1eacc076d32f35c78da9bb0bfe86ab014d73554127d9467db876
5
5
  SHA512:
6
- metadata.gz: 5f288c5a834bff0877a6949d2b79a642f6599b51188f653b9fed9f6699d5d5fab63a48a725c5088935d26d6fb2a90ae1984032add551cb303cacbd3ef6851aae
7
- data.tar.gz: 54494ec90b395bf6417c9a437beb128661522b4ebed0421d1129c0a3e9b9ad17e5acda8ec1524643b66dae70028bc6ba22e5686ef51ab57b8f839b4aab528a39
6
+ metadata.gz: 122b1ecaac183df2215020f86fdd9f534aed6f356e9bee3e0e53c281f88a6d0c519007aa9809f89c99cfbaeb2e563c712b597065373280160f655188c633749a
7
+ data.tar.gz: 5c4e2a6f3832390138c1d6ea4d96ede15e8119fb85d67829da6801b81ff0c810d1e42c072bfcd647586b67c5482ec7209168015d0c97bc870326a5b6302916e2
data/lib/git-fit.rb CHANGED
@@ -87,6 +87,7 @@ require_relative 'git_fit/export'
87
87
  require_relative 'git_fit/db/cli'
88
88
  require_relative 'git_fit/install/actions'
89
89
  require_relative 'git_fit/cli/install_cli'
90
+ require_relative 'git_fit/cli/purge_cli'
90
91
  require_relative 'git_fit/cli/gh_cli'
91
92
  require_relative 'git_fit/cli/export'
92
93
  require_relative 'git_fit/cli/import_cli'
@@ -0,0 +1,118 @@
1
+ # frozen_string_literal: true
2
+
3
+ module GitFit
4
+ class PurgeCLI < Thor
5
+ include Cli::Checkpointable
6
+
7
+ class RecordNotFound < StandardError; end
8
+ class PurgeFailed < StandardError; end
9
+
10
+ def self.exit_on_failure?
11
+ false
12
+ end
13
+
14
+ no_commands do
15
+ def git_fit_config
16
+ @git_fit_config ||= GitFit::Config.new(options[:config])
17
+ end
18
+ end
19
+
20
+ desc 'activity RUN_ID', 'Delete activity by run_id (e.g. keep_9223370467120855807)'
21
+ option :dry_run, type: :boolean, default: false, desc: 'Preview without deleting'
22
+ option :checkpoint, type: :boolean, default: true, desc: 'Run WAL checkpoint after delete'
23
+ option :delete_raw, type: :boolean, default: false, desc: 'Also delete raw source file(s)'
24
+ option :delete_std, type: :boolean, default: false, desc: 'Also delete standardized JSON file'
25
+
26
+ def activity(run_id)
27
+ db = connect_db
28
+
29
+ record = db[:activities].where(run_id: run_id).first
30
+
31
+ raise RecordNotFound, "Not found: #{run_id}" unless record
32
+
33
+ if options[:dry_run]
34
+ say_status :dry_run, 'Would delete activity:', :yellow
35
+ say " run_id: #{record[:run_id]}"
36
+ say " name: #{record[:name]}"
37
+ say " source: #{record[:source]}"
38
+ say " sport_category: #{record[:sport_category]}"
39
+ say " start_date: #{record[:start_date]}"
40
+ say " distance: #{record[:distance]}m" if record[:distance]
41
+ say " moving_time: #{record[:moving_time]}s" if record[:moving_time]
42
+ list_raw_files(run_id) if options[:delete_raw]
43
+ list_std_file(run_id) if options[:delete_std]
44
+ return
45
+ end
46
+
47
+ db[:dedup_group_members].where(run_id: run_id).delete
48
+ deleted = db[:activities].where(run_id: run_id).delete
49
+
50
+ say_status :deleted, "Removed #{deleted} activity: #{run_id}", :green
51
+
52
+ delete_raw_files(run_id) if options[:delete_raw]
53
+ delete_std_file(run_id) if options[:delete_std]
54
+
55
+ checkpoint_db(db) if options[:checkpoint]
56
+ rescue RecordNotFound
57
+ raise
58
+ rescue StandardError => e
59
+ raise PurgeFailed, "Purge failed: #{e.message}"
60
+ end
61
+
62
+ private
63
+
64
+ def connect_db
65
+ conn = GitFit::DB::Connection.new(git_fit_config.db_path)
66
+ conn.migrate!
67
+ conn.db
68
+ end
69
+
70
+ def delete_raw_files(run_id)
71
+ source, natural_id = GitFit::Std::Resolver.decompose(run_id)
72
+ return unless source && natural_id
73
+
74
+ raw_base = File.join('data', 'raw', source, natural_id)
75
+ files = Dir.glob("#{raw_base}.*")
76
+ if files.any?
77
+ files.each { |f| File.delete(f) }
78
+ elsif File.directory?(raw_base)
79
+ FileUtils.rm_rf(raw_base)
80
+ end
81
+ end
82
+
83
+ def list_raw_files(run_id)
84
+ source, natural_id = GitFit::Std::Resolver.decompose(run_id)
85
+ return unless source && natural_id
86
+
87
+ raw_base = File.join('data', 'raw', source, natural_id)
88
+ files = Dir.glob("#{raw_base}.*")
89
+ if files.any?
90
+ files.each { |f| say "Would delete: #{f}" }
91
+ elsif File.directory?(raw_base)
92
+ say "Would delete: #{raw_base}/ (directory)"
93
+ else
94
+ say "No raw files found for #{run_id}"
95
+ end
96
+ end
97
+
98
+ def delete_std_file(run_id)
99
+ source, natural_id = GitFit::Std::Resolver.decompose(run_id)
100
+ return unless source && natural_id
101
+
102
+ std_path = File.join('data', 'std', source, "#{natural_id}.json")
103
+ File.delete(std_path) if File.exist?(std_path)
104
+ end
105
+
106
+ def list_std_file(run_id)
107
+ source, natural_id = GitFit::Std::Resolver.decompose(run_id)
108
+ return unless source && natural_id
109
+
110
+ std_path = File.join('data', 'std', source, "#{natural_id}.json")
111
+ if File.exist?(std_path)
112
+ say "Would delete: #{std_path}"
113
+ else
114
+ say "No std file found for #{run_id}"
115
+ end
116
+ end
117
+ end
118
+ end
data/lib/git_fit/cli.rb CHANGED
@@ -120,6 +120,9 @@ module GitFit
120
120
  desc 'strava SUBCOMMAND', 'Strava web operations'
121
121
  subcommand 'strava', StravaCLI
122
122
 
123
+ desc 'purge SUBCOMMAND', 'Purge activities from database'
124
+ subcommand 'purge', PurgeCLI
125
+
123
126
  desc 'install SUBCOMMAND', 'Install project assets (actions)'
124
127
  subcommand 'install', InstallCLI
125
128
 
@@ -100,46 +100,41 @@ module GitFit
100
100
  max_power_peak: 0.0, total_max_power: 0.0, max_power_count: 0 }
101
101
  end
102
102
 
103
- rows.each do |r|
104
- next unless r[:start_date_local] || r[:start_date]
103
+ rows.each { |row| accumulate_monthly_row(monthly, row) }
105
104
 
106
- ym = (r[:start_date_local] || r[:start_date])[0..6]
107
- d = r[:distance] || 0
108
- t = r[:moving_time] || 0
109
- hr = r[:average_heartrate]
110
- e = r[:elevation_gain] || 0
111
- l = r[:elevation_loss] || 0
112
- s = r[:steps] || 0
105
+ format_monthly(monthly.sort)
106
+ end
113
107
 
114
- monthly[ym][:count] += 1
115
- monthly[ym][:distance_km] += d / 1000.0
116
- monthly[ym][:moving_time_h] += t / 3600.0
117
- monthly[ym][:total_elev] += e
118
- monthly[ym][:total_loss] += l
119
- monthly[ym][:total_steps] += s
120
- if hr && hr > 0
121
- monthly[ym][:total_hr] += hr
122
- monthly[ym][:hr_count] += 1
123
- end
124
- cad = r[:average_cadence]
125
- pow = r[:average_power]
126
- if cad && cad > 0
127
- monthly[ym][:total_cadence] += cad
128
- monthly[ym][:cadence_count] += 1
129
- end
130
- if pow && pow > 0
131
- monthly[ym][:total_power] += pow
132
- monthly[ym][:power_count] += 1
133
- end
134
- max_pow = r[:max_power]
135
- if max_pow && max_pow > 0
136
- monthly[ym][:max_power_peak] = max_pow if max_pow > monthly[ym][:max_power_peak]
137
- monthly[ym][:total_max_power] += max_pow
138
- monthly[ym][:max_power_count] += 1
139
- end
140
- end
108
+ def accumulate_monthly_row(monthly, row)
109
+ return unless row[:start_date_local] || row[:start_date]
110
+
111
+ ym = (row[:start_date_local] || row[:start_date])[0..6]
112
+ acc = monthly[ym]
113
+ acc[:count] += 1
114
+ acc[:distance_km] += (row[:distance] || 0) / 1000.0
115
+ acc[:moving_time_h] += (row[:moving_time] || 0) / 3600.0
116
+ acc[:total_elev] += row[:elevation_gain] || 0
117
+ acc[:total_loss] += row[:elevation_loss] || 0
118
+ acc[:total_steps] += row[:steps] || 0
119
+ add_if_positive(acc, :total_hr, :hr_count, row[:average_heartrate])
120
+ add_if_positive(acc, :total_cadence, :cadence_count, row[:average_cadence])
121
+ add_if_positive(acc, :total_power, :power_count, row[:average_power])
122
+ max_pow = row[:max_power]
123
+ return unless max_pow && max_pow > 0
124
+ acc[:max_power_peak] = max_pow if max_pow > acc[:max_power_peak]
125
+ acc[:total_max_power] += max_pow
126
+ acc[:max_power_count] += 1
127
+ end
128
+
129
+ def add_if_positive(acc, sum_key, count_key, value)
130
+ return unless value && value > 0
131
+
132
+ acc[sum_key] += value
133
+ acc[count_key] += 1
134
+ end
141
135
 
142
- monthly.sort.map do |ym, v|
136
+ def format_monthly(sorted)
137
+ sorted.map do |ym, v|
143
138
  pace = if v[:moving_time_h] > 0 && v[:distance_km] > 0
144
139
  format_pace(v[:moving_time_h] * 3600 / v[:distance_km])
145
140
  end
@@ -73,7 +73,9 @@ module GitFit
73
73
  next if skip_type?(type)
74
74
  seen[act_id] = seen.fetch(act_id, 0) + 1
75
75
  if seen[act_id] > 3
76
- raise SyncError, "XOSS: activity list returned #{seen[act_id]} duplicate records for id #{act_id} — server-side degradation. Run: git fit sync --source xoss"
76
+ raise SyncError,
77
+ "XOSS: activity list returned #{seen[act_id]} duplicate records for id #{act_id} " \
78
+ '— server-side degradation. Run: git fit sync --source xoss'
77
79
  end
78
80
  next if seen[act_id] > 1
79
81
  ids << { id: act_id, type: type, summary: act }
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module GitFit
4
- VERSION = '0.16.1'
4
+ VERSION = '0.17.1'
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.16.1
4
+ version: 0.17.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Lax
@@ -262,6 +262,7 @@ files:
262
262
  - lib/git_fit/cli/gh_cli.rb
263
263
  - lib/git_fit/cli/import_cli.rb
264
264
  - lib/git_fit/cli/install_cli.rb
265
+ - lib/git_fit/cli/purge_cli.rb
265
266
  - lib/git_fit/cli/strava.rb
266
267
  - lib/git_fit/cli/sync.rb
267
268
  - lib/git_fit/config.rb