postrunner 0.12.0 → 1.0.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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
- SHA1:
3
- metadata.gz: 351aade063aa03b50f3563a29ff77549c336970b
4
- data.tar.gz: 9850c9ea5c0a0b59697423a7a70851d7292647ec
2
+ SHA256:
3
+ metadata.gz: bb8a80028ee50a8f543d589b44fd41523208483a1743d29ec86e37525ff84a13
4
+ data.tar.gz: 9834c6dc556e9e118fca144a8edd33e9cecc51eb9bc4487d43218973f5dae719
5
5
  SHA512:
6
- metadata.gz: b55b1de8af615a5213e5b9a185d8b3c4e93219a64ea2fefbd394ebd6e4a3e1c83a5ea39c28d76cc4d9528409ea0ba754d718418e413f8f23170bff620f40ba88
7
- data.tar.gz: fdb2911f5ef016648f9d426a98de321ad5e46387ed6500348d86bdd34a00bb49900dae6012dab25611fca08fdcdfa5c3aea15368d77e34bf9a00cd51eadec690
6
+ metadata.gz: 8544b0aca1e86a9ca4e90aa437684a9fc56ac925cefe115ac9453816d1373242fcf6aa94d86ea779ea947ebb982b318141c45df43197d77a78b2375aeb5f6724
7
+ data.tar.gz: d5354f1f321685cb7181c75cee537d36695bddb0cecea175cd703de678af9055db10e089740f6719f1c6097e3fc2a2713951ae396b558b3ffc39a5bbfbe9f722
@@ -380,7 +380,7 @@ module PostRunner
380
380
  end
381
381
 
382
382
  def activity_sub_type
383
- ActivitySubTypes[@sub_sport] || 'Undefined "#{@sub_sport}"'
383
+ ActivitySubTypes[@sub_sport] || "Undefined #{@sub_sport}"
384
384
  end
385
385
 
386
386
  def distance(timestamp, unit_system)
@@ -316,7 +316,7 @@ module PostRunner
316
316
  if zone.type == 18
317
317
  total_time = 0.0
318
318
  if zone.time_in_hr_zone
319
- zone.time_in_hr_zone.each { |tiz| total_time += tiz }
319
+ zone.time_in_hr_zone.each { |tiz| total_time += tiz if tiz }
320
320
  end
321
321
  break if total_time <= 0.0
322
322
  if zone.heart_rate_zones
@@ -354,12 +354,22 @@ module PostRunner
354
354
  def local_value(fdr, field, format, units)
355
355
  unit = units[@unit_system]
356
356
  value = fdr.get_as(field, unit)
357
+ if value.nil? && field == 'avg_speed'
358
+ # New fit files used 'enhanced_avg_speed' instead of the older
359
+ # 'avg_speed'.
360
+ value = fdr.get_as('enhanced_avg_speed', unit)
361
+ end
357
362
  return '-' unless value
358
363
  "#{format % [value, unit]}"
359
364
  end
360
365
 
361
366
  def pace(fdr, field, show_unit = true)
362
367
  speed = fdr.get(field)
368
+ if speed.nil? && field == 'avg_speed'
369
+ # New fit files used 'enhanced_avg_speed' instead of the older
370
+ # 'avg_speed'.
371
+ speed = fdr.get('enhanced_avg_speed')
372
+ end
363
373
  case @unit_system
364
374
  when :metric
365
375
  "#{speedToPace(speed)}#{show_unit ? ' min/km' : ''}"
@@ -73,6 +73,14 @@ module PostRunner
73
73
  :colors => '#900000',
74
74
  :show => false
75
75
  },
76
+ {
77
+ :id => 'respiration_rate',
78
+ :label => 'Respiration Rate',
79
+ :unit => 'brpm',
80
+ :graph => :line_graph,
81
+ :colors => '#9cd6ef',
82
+ :show => true
83
+ },
76
84
  {
77
85
  :id => 'performance_condition',
78
86
  :label => 'Performance Condition',
@@ -253,7 +253,7 @@ module PostRunner
253
253
  end
254
254
 
255
255
  def activity_sub_type
256
- ActivitySubTypes[@sub_sport] || 'Undefined'
256
+ ActivitySubTypes[@sub_sport] || "Undefined #{@sub_sport}"
257
257
  end
258
258
 
259
259
  def distance(timestamp, unit_system)
@@ -3,7 +3,7 @@
3
3
  #
4
4
  # = FFS_Device.rb -- PostRunner - Manage the data from your Garmin sport devices.
5
5
  #
6
- # Copyright (c) 2015, 2016, 2018 by Chris Schlaeger <cs@taskjuggler.org>
6
+ # Copyright (c) 2015, 2016, 2018, 2020 by Chris Schlaeger <cs@taskjuggler.org>
7
7
  #
8
8
  # This program is free software; you can redistribute it and/or modify
9
9
  # it under the terms of version 2 of the GNU General Public License as
@@ -83,7 +83,8 @@ module PostRunner
83
83
  return nil
84
84
  end
85
85
  else
86
- # Don't add the entity if has deleted before and overwrite isn't true.
86
+ # Don't add the entity if it has deleted before and overwrite isn't
87
+ # true.
87
88
  path = @store['file_store'].fit_file_dir(File.basename(fit_file_name),
88
89
  long_uid, type)
89
90
  fq_fit_file_name = File.join(path, File.basename(fit_file_name))
@@ -44,7 +44,7 @@ module PostRunner
44
44
  # Setup non-persistent variables.
45
45
  def restore
46
46
  @data_dir = @store['config']['data_dir']
47
- # Ensure that we have an Array in the store to hold all known devices.
47
+ # Ensure that we have a Hash in the store to hold all known devices.
48
48
  @store['devices'] = @store.new(PEROBS::Hash) unless @store['devices']
49
49
 
50
50
  @devices_dir = File.join(@data_dir, 'devices')
@@ -69,8 +69,58 @@ module PostRunner
69
69
  end
70
70
 
71
71
  # Version upgrade logic.
72
- def handle_version_update
73
- # Nothing here so far.
72
+ def handle_version_update(from_version, to_version)
73
+ if from_version <= Gem::Version.new('0.12.0')
74
+ # PostRunner up until version 0.12.0 was using a long_uid with
75
+ # manufacturer name and product name. This was a bad idea since unknown
76
+ # devices were resolved to their numerical ID. In case the unknown ID
77
+ # was later added to the dictionary in fit4ruby version update, it
78
+ # resolved to its name and the device was recognized as a new device.
79
+ # Versions after 0.12.0 only use the numerical versions for the device
80
+ # long_uid and directory names.
81
+ uid_remap = {}
82
+ @store['devices'].each do |uid, device|
83
+ old_uid = uid
84
+
85
+ if (first_activity = device.activities.first)
86
+ first_activity.load_fit_file
87
+ if (fit_activity = first_activity.fit_activity)
88
+ if (device_info = fit_activity.device_infos.first)
89
+ new_uid = "#{device_info.numeric_manufacturer}-" +
90
+ "#{device_info.numeric_product}-#{device_info.serial_number}"
91
+
92
+ uid_remap[old_uid] = new_uid
93
+ puts first_activity.fit_file_name
94
+ end
95
+ end
96
+ end
97
+ end
98
+
99
+ @store.transaction do
100
+ pwd = Dir.pwd
101
+ base_dir_name = @store['config']['devices_dir']
102
+ Dir.chdir(base_dir_name)
103
+
104
+ uid_remap.each do |old_uid, new_uid|
105
+ if Dir.exist?(old_uid) && !Dir.exist?(new_uid) &&
106
+ !File.symlink?(old_uid)
107
+ # Rename the directory from the old (string) scheme to the
108
+ # new numeric scheme.
109
+ FileUtils.mv(old_uid, new_uid)
110
+ # Create a symbolic link with that points the old name to
111
+ # the new name.
112
+ File.symlink(new_uid, old_uid)
113
+ end
114
+
115
+ # Now update the long_uid in the FFS_Device object
116
+ @store['devices'][new_uid] = device = @store['devices'][old_uid]
117
+ device.long_uid = new_uid
118
+ @store['devices'].delete(old_uid)
119
+ end
120
+
121
+ Dir.chdir(pwd)
122
+ end
123
+ end
74
124
  end
75
125
 
76
126
  # Add a file to the store.
@@ -95,7 +145,8 @@ module PostRunner
95
145
  # Generate a String that uniquely identifies the device that generated
96
146
  # the FIT file.
97
147
  id = extract_fit_file_id(fit_entity)
98
- long_uid = "#{id[:manufacturer]}-#{id[:product]}-#{id[:serial_number]}"
148
+ long_uid = "#{id[:numeric_manufacturer]}-" +
149
+ "#{id[:numeric_product]}-#{id[:serial_number]}"
99
150
 
100
151
  # Make sure the device that created the FIT file is properly registered.
101
152
  device = register_device(long_uid)
@@ -187,6 +238,7 @@ module PostRunner
187
238
  @store['records'].generate_html_reports
188
239
  generate_html_index_pages
189
240
  end
241
+
190
242
  # Determine the right directory for the given FIT file. The resulting path
191
243
  # looks something like /home/user/.postrunner/devices/garmin-fenix3-1234/
192
244
  # activity/5A.
@@ -462,6 +514,8 @@ module PostRunner
462
514
  return {
463
515
  :manufacturer => di.manufacturer,
464
516
  :product => di.garmin_product || di.product,
517
+ :numeric_manufacturer => di.numeric_manufacturer,
518
+ :numeric_product => di.numeric_product,
465
519
  :serial_number => di.serial_number
466
520
  }
467
521
  end
@@ -483,6 +537,8 @@ module PostRunner
483
537
  return {
484
538
  :manufacturer => fid.manufacturer,
485
539
  :product => fid.garmin_product || fid.product,
540
+ :numeric_manufacturer => di.numeric_manufacturer,
541
+ :numeric_product => di.numeric_product,
486
542
  :serial_number => fid.serial_number
487
543
  }
488
544
  end
@@ -502,7 +558,8 @@ module PostRunner
502
558
  @store.new(FFS_Device, short_uid, long_uid)
503
559
 
504
560
  # Create the directory to store the FIT files of this device.
505
- create_directory(File.join(@devices_dir, long_uid), long_uid)
561
+ create_directory(File.join(@devices_dir, long_uid),
562
+ long_uid)
506
563
  end
507
564
 
508
565
  @store['devices'][long_uid]
@@ -107,7 +107,7 @@ module PostRunner
107
107
 
108
108
  opts.separator <<"EOT"
109
109
 
110
- Copyright (c) 2014, 2015, 2016, 2017 by Chris Schlaeger
110
+ Copyright (c) 2014, 2015, 2016, 2017, 2018, 2019, 2020 by Chris Schlaeger
111
111
 
112
112
  This program is free software; you can redistribute it and/or modify it under
113
113
  the terms of version 2 of the GNU General Public License as published by the
@@ -597,10 +597,17 @@ EOT
597
597
 
598
598
  def handle_version_update
599
599
  if @db['config']['version'] != VERSION
600
- Log.warn "PostRunner version upgrade detected."
601
- @ffs.handle_version_update
600
+ puts "Work needed"
601
+ from_version = Gem::Version.new(@db['config']['version'])
602
+ to_version = Gem::Version.new(VERSION)
603
+
604
+ Log.warn "PostRunner version upgrade from #{from_version} to " +
605
+ "#{to_version} started."
606
+ @ffs.handle_version_update(from_version, to_version)
607
+
602
608
  @db['config']['version'] = VERSION
603
- Log.info "Version upgrade completed."
609
+ Log.warn "PostRunner version upgrade from #{from_version} to " +
610
+ "#{to_version} completed."
604
611
  end
605
612
  end
606
613
 
@@ -18,6 +18,8 @@ module PostRunner
18
18
 
19
19
  class UserProfileView
20
20
 
21
+ include Fit4Ruby::Converters
22
+
21
23
  def initialize(fit_activity, unit_system)
22
24
  @fit_activity = fit_activity
23
25
  @unit_system = unit_system
@@ -44,16 +46,18 @@ module PostRunner
44
46
  user_profile = @fit_activity.user_profiles.first
45
47
  hr_zones = @fit_activity.heart_rate_zones.first
46
48
 
47
- if user_data.height
49
+ if user_data && user_data.height
48
50
  unit = { :metric => 'm', :statute => 'ft' }[@unit_system]
49
51
  height = user_data.get_as('height', unit)
50
52
  t.cell('Height:', { :width => '40%' })
51
53
  t.cell("#{'%.2f' % height} #{unit}", { :width => '60%' })
52
54
  t.new_row
53
55
  end
54
- if user_data.weight
56
+ if (user_data && user_data.weight) ||
57
+ (user_profile && user_profile.weight)
55
58
  unit = { :metric => 'kg', :statute => 'lbs' }[@unit_system]
56
- weight = user_data.get_as('weight', unit)
59
+ weight = (user_profile && user_profile.get_as('weight', unit)) ||
60
+ (user_data && user_data.get_as('weight', unit))
57
61
  t.row([ 'Weight:', "#{'%.1f' % weight} #{unit}" ])
58
62
  end
59
63
  t.row([ 'Gender:', user_data.gender ]) if user_data.gender
@@ -66,15 +70,23 @@ module PostRunner
66
70
  (max_hr = hr_zones.max_heart_rate)
67
71
  t.row([ 'Max. Heart Rate:', "#{max_hr} bpm" ])
68
72
  end
69
- if (lthr = user_data.running_lactate_threshold_heart_rate)
70
- t.row([ 'Running LTHR:', "#{lthr} bpm" ])
73
+ if user_profile && (date = user_profile.time_last_lthr_update)
74
+ t.row([ 'Last Lactate Threshold Update:', date ])
75
+ end
76
+ if user_data && (lthr = user_data.running_lactate_threshold_heart_rate)
77
+ t.row([ 'Running LT Heart Rate:', "#{lthr} bpm" ])
78
+ end
79
+ if user_profile && (speed = user_profile.functional_threshold_speed)
80
+ unit = { :metric => 'min/km', :statute => 'min/mile' }[@unit_system]
81
+ t.row([ 'Running LT Pace:', "#{speedToPace(speed)} #{unit}" ])
71
82
  end
72
83
  if (activity_class = user_data.activity_class)
73
84
  t.row([ 'Activity Class:', activity_class ])
74
85
  end
75
- if (metmax = user_data.metmax)
76
- t.row([ 'METmax:', "#{metmax} MET" ])
77
- end
86
+ # It's unlikely that anybody ever cares about the METmax value.
87
+ #if (metmax = user_data.metmax)
88
+ # t.row([ 'METmax:', "#{metmax} MET" ])
89
+ #end
78
90
  if (vo2max = @fit_activity.vo2max)
79
91
  t.row([ 'VO2max:', "#{'%.1f' % vo2max} ml/kg/min" ])
80
92
  end
@@ -3,7 +3,7 @@
3
3
  #
4
4
  # = version.rb -- PostRunner - Manage the data from your Garmin sport devices.
5
5
  #
6
- # Copyright (c) 2014, 2015, 2016, 2017 by Chris Schlaeger <cs@taskjuggler.org>
6
+ # Copyright (c) 2014, 2015, 2016, 2017, 2020 by Chris Schlaeger <cs@taskjuggler.org>
7
7
  #
8
8
  # This program is free software; you can redistribute it and/or modify
9
9
  # it under the terms of version 2 of the GNU General Public License as
@@ -11,5 +11,5 @@
11
11
  #
12
12
 
13
13
  module PostRunner
14
- VERSION = '0.12.0'
14
+ VERSION = '1.0.0'
15
15
  end
@@ -28,8 +28,8 @@ operating systems as well.}
28
28
  spec.require_paths = ["lib"]
29
29
  spec.required_ruby_version = '>=2.4'
30
30
 
31
- spec.add_dependency 'fit4ruby', '~> 3.3.0'
32
- spec.add_dependency 'perobs', '~> 4.1.0'
31
+ spec.add_dependency 'fit4ruby', '~> 3.6.0'
32
+ spec.add_dependency 'perobs', '~> 4.2.0'
33
33
  spec.add_dependency 'nokogiri', '~> 1.6'
34
34
 
35
35
  spec.add_development_dependency 'bundler', '~> 1.6'
@@ -3,7 +3,7 @@
3
3
  #
4
4
  # = PostRunner_spec.rb -- PostRunner - Manage the data from your Garmin sport devices.
5
5
  #
6
- # Copyright (c) 2014, 2015, 2016 by Chris Schlaeger <cs@taskjuggler.org>
6
+ # Copyright (c) 2014, 2015, 2016, 2020 by Chris Schlaeger <cs@taskjuggler.org>
7
7
  #
8
8
  # This program is free software; you can redistribute it and/or modify
9
9
  # it under the terms of version 2 of the GNU General Public License as
@@ -51,7 +51,7 @@ describe PostRunner::FitFileStore do
51
51
  expect(@activities[-1]).not_to be_nil
52
52
 
53
53
  expect(@ffs.devices.length).to eq(1)
54
- expect(@ffs.devices.include?('garmin-fenix3-123456790')).to be true
54
+ expect(@ffs.devices.include?('1-2050-123456790')).to be true
55
55
  expect(@ffs.activities.length).to eq(1)
56
56
  expect(@ffs.ref_by_activity(@activities[0])).to eq(1)
57
57
  end
@@ -60,7 +60,7 @@ describe PostRunner::FitFileStore do
60
60
  expect(@ffs.add_fit_file(@fit_file_names[0])).to be_nil
61
61
 
62
62
  expect(@ffs.devices.length).to eq(1)
63
- expect(@ffs.devices.include?('garmin-fenix3-123456790')).to be true
63
+ expect(@ffs.devices.include?('1-2050-123456790')).to be true
64
64
  expect(@ffs.activities.length).to eq(1)
65
65
  end
66
66
 
@@ -69,8 +69,8 @@ describe PostRunner::FitFileStore do
69
69
  expect(@activities[-1]).not_to be_nil
70
70
 
71
71
  expect(@ffs.devices.length).to eq(2)
72
- expect(@ffs.devices.include?('garmin-fenix3-123456790')).to be true
73
- expect(@ffs.devices.include?('garmin-fenix3-123456791')).to be true
72
+ expect(@ffs.devices.include?('1-2050-123456790')).to be true
73
+ expect(@ffs.devices.include?('1-2050-123456791')).to be true
74
74
  expect(@ffs.activities.length).to eq(2)
75
75
  expect(@ffs.ref_by_activity(@activities[1])).to eq(1)
76
76
  end
@@ -80,8 +80,8 @@ describe PostRunner::FitFileStore do
80
80
  expect(@activities[-1]).not_to be_nil
81
81
 
82
82
  expect(@ffs.devices.length).to eq(2)
83
- expect(@ffs.devices.include?('garmin-fenix3-123456790')).to be true
84
- expect(@ffs.devices.include?('garmin-fenix3-123456791')).to be true
83
+ expect(@ffs.devices.include?('1-2050-123456790')).to be true
84
+ expect(@ffs.devices.include?('1-2050-123456791')).to be true
85
85
  expect(@ffs.activities.length).to eq(3)
86
86
  expect(@ffs.ref_by_activity(@activities[2])).to eq(1)
87
87
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: postrunner
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.12.0
4
+ version: 1.0.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Chris Schlaeger
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2019-09-03 00:00:00.000000000 Z
11
+ date: 2020-07-26 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: fit4ruby
@@ -16,28 +16,28 @@ dependencies:
16
16
  requirements:
17
17
  - - "~>"
18
18
  - !ruby/object:Gem::Version
19
- version: 3.3.0
19
+ version: 3.6.0
20
20
  type: :runtime
21
21
  prerelease: false
22
22
  version_requirements: !ruby/object:Gem::Requirement
23
23
  requirements:
24
24
  - - "~>"
25
25
  - !ruby/object:Gem::Version
26
- version: 3.3.0
26
+ version: 3.6.0
27
27
  - !ruby/object:Gem::Dependency
28
28
  name: perobs
29
29
  requirement: !ruby/object:Gem::Requirement
30
30
  requirements:
31
31
  - - "~>"
32
32
  - !ruby/object:Gem::Version
33
- version: 4.1.0
33
+ version: 4.2.0
34
34
  type: :runtime
35
35
  prerelease: false
36
36
  version_requirements: !ruby/object:Gem::Requirement
37
37
  requirements:
38
38
  - - "~>"
39
39
  - !ruby/object:Gem::Version
40
- version: 4.1.0
40
+ version: 4.2.0
41
41
  - !ruby/object:Gem::Dependency
42
42
  name: nokogiri
43
43
  requirement: !ruby/object:Gem::Requirement
@@ -342,7 +342,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
342
342
  version: '0'
343
343
  requirements: []
344
344
  rubyforge_project:
345
- rubygems_version: 2.2.5
345
+ rubygems_version: 2.7.6.2
346
346
  signing_key:
347
347
  specification_version: 4
348
348
  summary: Application to manage and analyze Garmin FIT files.