postrunner 1.0.0 → 1.0.5

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: bb8a80028ee50a8f543d589b44fd41523208483a1743d29ec86e37525ff84a13
4
- data.tar.gz: 9834c6dc556e9e118fca144a8edd33e9cecc51eb9bc4487d43218973f5dae719
3
+ metadata.gz: a3573b66dc63e6e6e26ed20c4389a9c4da0ea2aa3a6bba64e5cce363ddbe1531
4
+ data.tar.gz: 26e721d0ddc3cae1852c7135de1f24d3dc03e32be871c0894c958591ec538f11
5
5
  SHA512:
6
- metadata.gz: 8544b0aca1e86a9ca4e90aa437684a9fc56ac925cefe115ac9453816d1373242fcf6aa94d86ea779ea947ebb982b318141c45df43197d77a78b2375aeb5f6724
7
- data.tar.gz: d5354f1f321685cb7181c75cee537d36695bddb0cecea175cd703de678af9055db10e089740f6719f1c6097e3fc2a2713951ae396b558b3ffc39a5bbfbe9f722
6
+ metadata.gz: eac508a3916d92e2843e3ff34a454fd357ad35dbcf798178e7bdb555ef9b25f9010d5f647567e1907982bf11adb24c71592d3e3e3af1b3fe46d9c921cb36ea31
7
+ data.tar.gz: 51994f58aefd6354123655260a45772cc4d7e301d236b76628d27ce2af5370297aecd3c669e2147f8bf0ae91ffcbd5c7c2fbd171bf4fa6eff0a4015f7705a2ef
@@ -0,0 +1,33 @@
1
+ # This workflow uses actions that are not certified by GitHub.
2
+ # They are provided by a third-party and are governed by
3
+ # separate terms of service, privacy policy, and support
4
+ # documentation.
5
+ # This workflow will download a prebuilt Ruby version, install dependencies and run tests with Rake
6
+ # For more information see: https://github.com/marketplace/actions/setup-ruby-jruby-and-truffleruby
7
+
8
+ name: Ruby
9
+
10
+ on:
11
+ push:
12
+ branches: [ master ]
13
+ pull_request:
14
+ branches: [ master ]
15
+
16
+ jobs:
17
+ test:
18
+ runs-on: ubuntu-latest
19
+ strategy:
20
+ fail-fast: false
21
+ matrix:
22
+ os: [ ubuntu]
23
+ ruby: [2.4, 2.5, 2.6, 2.7, 3.0, head]
24
+ continue-on-error: ${{ endsWith(matrix.ruby, 'head') }}
25
+ steps:
26
+ - uses: actions/checkout@v2
27
+ - uses: ruby/setup-ruby@v1
28
+ with:
29
+ ruby-version: ${{ matrix.ruby }}
30
+ bundler-cache: true
31
+
32
+ - name: Test with RSpec
33
+ run: bundle exec rspec
data/README.md CHANGED
@@ -2,45 +2,93 @@
2
2
 
3
3
  PostRunner is an application to manage FIT files such as those
4
4
  produced by Garmin products like the Forerunner 620 (FR620),
5
- Forerunner 25 (FR25), Fenix 3, Fenix 3HR and Fenix 5. It allows you to
6
- import the files from the device and analyze the data. In addition to
7
- the common features like plotting pace, heart rates, elevation and
8
- other captured values it also provides a heart rate variability (HRV)
9
- analysis. It can also update satellite orbit prediction (EPO) data on
10
- the device to speed-up GPS fix times. It is an offline alternative to
11
- Garmin Connect. The software has been developed and tested on Linux
12
- but should work on other operating systems as well.
5
+ Forerunner 25 (FR25), Fenix 3, Fenix 3HR, Fenix 5, Fenix 5+ or Fenix6.
6
+ It allows you to import the files from the device and analyze the
7
+ data. In addition to the common features like plotting pace, heart
8
+ rates, elevation and other captured values it also provides a heart
9
+ rate variability (HRV) analysis. It can also update satellite orbit
10
+ prediction (EPO) data on the device to speed-up GPS fix times.
11
+ Unfortunately, the download mechanism for CPE files used by the
12
+ devices with GPS chipsets from Sony is still unknown and hence
13
+ unsupported. Postrunner is an offline alternative to Garmin Connect.
14
+ The software has been developed and tested on Linux but should work on
15
+ other operating systems as well.
13
16
 
14
17
  ## Installation
15
18
 
16
19
  PostRunner is a [http://www.ruby-lang.org](Ruby) application. You need
17
- to have a Ruby 2.0 or later runtime environment installed. This
20
+ to have a Ruby 2.4 or later runtime environment installed. This
18
21
  application was developed and tested on Linux but may work on other
19
- operating systems as well.
22
+ operating systems as well. You can either install it as root for all
23
+ users on the computer or as a particular user for just this user.
24
+
25
+ ### System-wide installation as root user
20
26
 
21
27
  ```
22
28
  $ gem install postrunner
23
29
  ```
24
30
 
31
+ On some Linux distributions using sudo might resolve in permission
32
+ problems as the installed packages are not readable for normal users.
33
+ This typically results in 'cannot load such file' type error messages.
34
+
35
+ ### Installation as non-privileged user
36
+
37
+ ```
38
+ gem install --user-install postrunner
39
+ ```
40
+
41
+ This will install PostRunner and all dependency packages in your .gem
42
+ directory. You then need to add the binary path to your PATH variable
43
+ in your .profile or .bashrc or .whatever file. The path is typically
44
+ .gem/ruby/<version>/bin. Watch out, on some Linux distributions the
45
+ version number of ruby gets added to the binary name, e. g.
46
+ postrunner.ruby2.7. You can use a symbolic link or alias to safe some
47
+ typing.
48
+
25
49
  ## Usage
26
50
 
51
+ ### Mounting the watch
52
+
53
+ Watches that expose their data as FAT file system. Typically after connecting
54
+ your garmin watch, they are mount automatically if you have installed the
55
+ udev package.
56
+
57
+ For watches that expose their data via MTP (Media Transfer Procotol). For
58
+ Debian buster running `sudo apt install jmtpfs mtp-tools` will install the
59
+ needed packages.
60
+
61
+ `mkdir /tmp/forerunner; jmtpfs /tmp/forerunner`
62
+
63
+ This has been tested with a Garmin Forerunner 945.
64
+
65
+ For more information about MTP under Windows have a look at the
66
+ [Garmin FAQ](https://support.garmin.com/?faq=Itl8M6ARrh4gBQMHFqdqK8)
67
+
27
68
  ### Importing FIT files
28
69
 
29
70
  To get started you need to connect your device to your computer and
30
- mount it as a disk drive. Only devices that expose their data as FAT file
71
+ mount it as a disk drive. Only devices that expose their data as FAT or MTP file
31
72
  system are supported. Older devices use proprietary drivers and are
32
- not supported by postrunner. Once the device is mounted find out the
73
+ not supported by PostRunner. Once the device is mounted find out the
33
74
  full path to the directory that contains your FIT files. You can then
34
75
  import all files on the device.
35
76
 
77
+ * for USB-FAT
36
78
  ```
37
79
  $ postrunner import /run/media/$USER/GARMIN/GARMIN/ACTIVITY/
38
80
  ```
39
-
40
- The above command assumes that your device is mounted as
41
- /run/media/$USER. Please replace $USER with your login name and the
42
- path with the path to your device. Files that have been imported
43
- previously will not be imported again.
81
+ The above command assumes that your device is mounted as /run/media/$USER.
82
+ Please replace $USER with your login name and the path with the path to
83
+ your device.
84
+
85
+ * for MTP (assuming you mounted it as described above)
86
+ ```
87
+ $ postrunner import /tmp/forerunner/Primary/GARMIN/Activity
88
+ ```
89
+
90
+ * Note
91
+ Files that have been imported previously will not be imported again.
44
92
 
45
93
  ### Viewing FIT file data on the console
46
94
 
@@ -128,7 +176,7 @@ $ postrunner show :1
128
176
  ## Contributing
129
177
 
130
178
  PostRunner is currently work in progress. It does some things I want
131
- with files from my Garmin FR620. It's certainly possible to do more
179
+ with files from my Garmin devices. It's certainly possible to do more
132
180
  things and support more devices. Patches are welcome!
133
181
 
134
182
  1. Fork it ( https://github.com/scrapper/postrunner/fork )
data/Rakefile CHANGED
@@ -1,24 +1,9 @@
1
- $:.unshift File.join(File.dirname(__FILE__))
2
-
3
- # Add the lib directory to the search path if it isn't included already
4
- lib = File.expand_path('../lib', __FILE__)
5
- $:.unshift lib unless $:.include?(lib)
6
-
1
+ #!/usr/bin/env ruby
2
+ require_relative 'lib/postrunner/version'
3
+ require 'rake'
4
+ require 'rspec'
7
5
  require "bundler/gem_tasks"
8
- require "rspec/core/rake_task"
9
- require 'rake/clean'
10
- require 'yard'
11
- YARD::Rake::YardocTask.new
12
-
13
- Dir.glob( 'tasks/*.rake').each do |fn|
14
- begin
15
- load fn;
16
- rescue LoadError
17
- puts "#{fn.split('/')[1]} tasks unavailable: #{$!}"
18
- end
19
- end
20
-
21
- task :default => :spec
22
- task :test => :spec
6
+ require 'rspec/core/rake_task'
23
7
 
24
- desc 'Run all unit and spec tests'
8
+ RSpec::Core::RakeTask.new(:spec)
9
+
@@ -65,6 +65,37 @@ module PostRunner
65
65
  'hiking' => 'Hiking',
66
66
  'multisport' => 'Multisport',
67
67
  'paddling' => 'Paddling',
68
+ 'training' => 'Training',
69
+ 'flying' => 'Flying',
70
+ 'e_biking' => 'E-Biking',
71
+ 'motorcycling' => 'Motor Cycling',
72
+ 'boating' => 'Boating',
73
+ 'driving' => 'Driving',
74
+ 'golf' => 'Golf',
75
+ 'hang_gliding' => 'Hang Gliding',
76
+ 'horseback_riding' => 'Horseback Riding',
77
+ 'hunting' => 'Hunting',
78
+ 'fishing' => 'Fishing',
79
+ 'inline_skating' => 'Inline Skating',
80
+ 'rock_climbing' => 'Rock Climbing',
81
+ 'sailing' => 'Sailing',
82
+ 'ice_skating' => 'Ice Skating',
83
+ 'sky_diving' => 'Sky Diving',
84
+ 'snowshoeing' => 'Snowshoeing',
85
+ 'snowmobiling' => 'Snowmobiling',
86
+ 'stand_up_paddleboarding' => 'Stand Up Paddleboarding',
87
+ 'surfing' => 'Surfing',
88
+ 'wakeboarding' => 'Wakeboarding',
89
+ 'water_skiing' => 'Water Skiing',
90
+ 'kayaking' => 'Kayaking',
91
+ 'rafting' => 'Rafting',
92
+ 'windsurfing' => 'Windsurfing',
93
+ 'kitesurfing' => 'Kitesurfing',
94
+ 'tactical' => 'Tactical',
95
+ 'jumpmaster' => 'Jumpmaster',
96
+ 'boxing' => 'Boxing',
97
+ 'floor_climbing' => 'Floor Climbing',
98
+ 'diving' => 'Diving',
68
99
  'all' => 'All'
69
100
  }
70
101
  ActivitySubTypes = {
@@ -95,7 +126,43 @@ module PostRunner
95
126
  'challenge' => 'Challenge',
96
127
  'indoor_skiing' => 'Indoor Skiing',
97
128
  'cardio_training' => 'Cardio Training',
129
+ 'e_bike_fitness' => 'E Bike Fittness',
130
+ 'bmx' => 'BMX',
131
+ 'casual_walking' => 'Casual Walking',
132
+ 'speed_walking' => 'Speed Walking',
133
+ 'bike_to_run_transition' => 'Bike to Run Transition',
134
+ 'run_to_bike_transition' => 'Run to Bike Transition',
135
+ 'swim_to_bike_transition' => 'Swim to Bike Transition',
136
+ 'atv' => 'ATV',
137
+ 'motocross' => 'Motocross',
138
+ 'backcountry' => 'Backcountry',
139
+ 'resort' => 'Resort',
140
+ 'rc_drone' => 'RC Drone',
141
+ 'wingsuit' => 'Wingsuite',
142
+ 'whitewater' => 'Whitemaster',
143
+ 'skate_skiing' => 'Skate Skiing',
144
+ 'yoga' => 'Yoga',
145
+ 'pilates' => 'Pilates',
146
+ 'indoor_running' => 'Indoor Running',
147
+ 'gravel_cycling' => 'Gravel Cycling',
148
+ 'e_bike_mountain' => 'E-Bike Mountain',
149
+ 'commuting' => 'Commuting',
150
+ 'mixed_surface' => 'Mixed Surface',
151
+ 'navigate' => 'Navigate',
152
+ 'track_me' => 'Track Me',
153
+ 'map' => 'Map',
154
+ 'single_gas_diving' => 'Single Gas Diving',
155
+ 'multi_gas_diving' => 'Multi Gas Diving',
156
+ 'gauge_diving' => 'Gauge Diving',
157
+ 'apnea_diving' => 'Apnea Diving',
158
+ 'apnea_hunting' => 'Apnea Hunting',
98
159
  'virtual_activity' => 'Virtual Activity',
160
+ 'obstacle' => 'Obstacle',
161
+ 'breathing' => 'Breating',
162
+ 'sail_race' => 'Sail Race',
163
+ 'ultra' => 'Ultra',
164
+ 'indoor_climbing' => 'Indoor Climbing',
165
+ 'bouldering' => 'Bouldering',
99
166
  'all' => 'All'
100
167
  }
101
168
 
@@ -53,7 +53,8 @@ module PostRunner
53
53
  def generate_html(doc)
54
54
  doc.unique(:activityview_style) {
55
55
  doc.head {
56
- [ 'jquery/jquery-2.1.1.min.js', 'flot/jquery.flot.js',
56
+ [ 'jquery/jquery-3.5.1.min.js', 'flot/jquery.flot.js',
57
+ #'flot/jquery.flot.time.js' ].each do |js|
57
58
  'flot/jquery.flot.time.js' ].each do |js|
58
59
  doc.script({ 'language' => 'javascript',
59
60
  'type' => 'text/javascript', 'src' => js })
@@ -30,7 +30,7 @@ module PostRunner
30
30
  :unit => select_unit('min/km'),
31
31
  :graph => :line_graph,
32
32
  :colors => '#0A7BEE',
33
- :show => @sport == 'running' || @sport == 'multisport',
33
+ :show => @sport == 'running' || @sport == 'multisport'
34
34
  },
35
35
  {
36
36
  :id => 'speed',
@@ -40,9 +40,17 @@ module PostRunner
40
40
  :colors => '#0A7BEE',
41
41
  :show => @sport != 'running'
42
42
  },
43
+ {
44
+ :id => 'Power_18FB2CF01A4B430DAD66988C847421F4',
45
+ :label => 'Power',
46
+ :unit => select_unit('Watts'),
47
+ :graph => :line_graph,
48
+ :colors => '#FFAC2E',
49
+ :show => @sport == 'running' || @sport == 'multisport'
50
+ },
43
51
  {
44
52
  :id => 'altitude',
45
- :label => 'Elevation',
53
+ :label => 'Altitude',
46
54
  :unit => select_unit('m'),
47
55
  :graph => :line_graph,
48
56
  :colors => '#5AAA44',
@@ -127,6 +135,22 @@ module PostRunner
127
135
  [ '#FF5558', nil ] ],
128
136
  :show => @sport == 'running' || @sport == 'multisport'
129
137
  },
138
+ {
139
+ :id => 'Form_Power_18FB2CF01A4B430DAD66988C847421F4',
140
+ :label => 'Form Power',
141
+ :unit => select_unit('Watts'),
142
+ :graph => :line_graph,
143
+ :colors => '#CBBB58',
144
+ :show => @sport == 'running' || @sport == 'multisport'
145
+ },
146
+ {
147
+ :id => 'Leg_Spring_Stiffness_18FB2CF01A4B430DAD66988C847421F4',
148
+ :label => 'Leg Spring Stiffness',
149
+ :unit => select_unit('kN/m'),
150
+ :graph => :line_graph,
151
+ :colors => '#358C88',
152
+ :show => @sport == 'running' || @sport == 'multisport'
153
+ },
130
154
  {
131
155
  :id => 'stance_time',
132
156
  :label => 'Ground Contact Time',
@@ -157,6 +181,14 @@ module PostRunner
157
181
  :colors => '#A88BBB',
158
182
  :show => @sport == 'cycling'
159
183
  },
184
+ {
185
+ :id => "Air_Power_18FB2CF01A4B430DAD66988C847421F4",
186
+ :label => 'Air Power',
187
+ :unit => select_unit('Watts'),
188
+ :graph => :line_graph,
189
+ :colors => '#919498',
190
+ :show => @sport == 'running' || @sport == 'multisport'
191
+ },
160
192
  {
161
193
  :id => 'temperature',
162
194
  :label => 'Temperature',
@@ -229,7 +261,7 @@ EOT
229
261
  end
230
262
 
231
263
  def java_script
232
- s = "$(function() {\n"
264
+ s = "$(document).ready(function() {\n"
233
265
 
234
266
  s << tooltip_div
235
267
  @charts.each do |chart|
@@ -44,7 +44,7 @@ module PostRunner
44
44
  def generate_html(doc)
45
45
  doc.unique(:dailymonitoringview_style) {
46
46
  doc.head {
47
- [ 'jquery/jquery-2.1.1.min.js', 'flot/jquery.flot.js',
47
+ [ 'jquery/jquery-3.5.1.min.js', 'flot/jquery.flot.js',
48
48
  'flot/jquery.flot.time.js' ].each do |js|
49
49
  doc.script({ 'language' => 'javascript',
50
50
  'type' => 'text/javascript', 'src' => js })
@@ -126,6 +126,19 @@ module PostRunner
126
126
  t.new_row
127
127
  end
128
128
 
129
+ if (ant_id = device.ant_id)
130
+ t.cell('ANT ID:')
131
+ t.cell(ant_id)
132
+ t.new_row
133
+ end
134
+
135
+ if ant_id && (sensor_settings = find_settings_by_ant_id(ant_id)) &&
136
+ (calibration_factor = sensor_settings.calibration_factor)
137
+ t.cell('Calibration Factor')
138
+ t.cell('%.1f' % calibration_factor)
139
+ t.new_row
140
+ end
141
+
129
142
  if (rx_ok = device.rx_packets_ok) && (rx_err = device.rx_packets_err)
130
143
  t.cell('Packet Errors:')
131
144
  t.cell('%d%%' % ((rx_err.to_f / (rx_ok + rx_err)) * 100).to_i)
@@ -148,6 +161,14 @@ module PostRunner
148
161
  tables
149
162
  end
150
163
 
164
+ private
165
+
166
+ def find_settings_by_ant_id(ant_id)
167
+ @fit_activity.sensor_settings.find do |sensor|
168
+ sensor.ant_id == ant_id
169
+ end
170
+ end
171
+
151
172
  end
152
173
 
153
174
  end
@@ -75,7 +75,7 @@ module PostRunner
75
75
  case event.event
76
76
  when 'timer'
77
77
  name = "Timer (#{event.event_type.gsub(/_/, ' ')})"
78
- value = event.timer_trigger
78
+ value = '-'
79
79
  when 'course_point'
80
80
  name = 'Course Point'
81
81
  value = event.message_index
@@ -121,12 +121,19 @@ module PostRunner
121
121
  when 'comm_timeout'
122
122
  name 'Communication timeout'
123
123
  value = event.comm_timeout
124
+ when 'off_course'
125
+ name = 'Off Course'
126
+ value = '-'
124
127
  when 'recovery_hr'
125
128
  name = 'Recovery heart rate'
126
129
  value = "#{event.recovery_hr} bpm"
127
130
  when 'recovery_time'
128
131
  name = 'Recovery time'
129
- value = "#{secsToDHMS(event.recovery_time * 60)}"
132
+ if event.recovery_time
133
+ value = "#{secsToDHMS(event.recovery_time * 60)}"
134
+ else
135
+ value = '-'
136
+ end
130
137
  when 'recovery_info'
131
138
  name = 'Recovery info'
132
139
  mins = event.recovery_info
@@ -140,6 +147,9 @@ module PostRunner
140
147
  when 'lactate_threshold_speed'
141
148
  name = 'Lactate Threshold Pace'
142
149
  value = pace(event, 'lactate_threshold_speed')
150
+ when 'functional_threshold_power'
151
+ name = 'Functional Threshold Power'
152
+ value = "#{event.functional_threshold_power} W"
143
153
  else
144
154
  name = event.event
145
155
  value = event.data
@@ -144,7 +144,9 @@ module PostRunner
144
144
 
145
145
  # Generate a String that uniquely identifies the device that generated
146
146
  # the FIT file.
147
- id = extract_fit_file_id(fit_entity)
147
+ unless (id = extract_fit_file_id(fit_entity))
148
+ return nil
149
+ end
148
150
  long_uid = "#{id[:numeric_manufacturer]}-" +
149
151
  "#{id[:numeric_product]}-#{id[:serial_number]}"
150
152
 
@@ -502,46 +504,23 @@ module PostRunner
502
504
  return nil
503
505
  end
504
506
 
505
- if fid.manufacturer == 'garmin' &&
506
- fid.garmin_product == 'fr920xt'
507
- # Garmin Fenix3 with firmware before 6.80 is reporting 'fr920xt' in
508
- # the file_id section but 'fenix3' in the first device_info section.
509
- # To tell the Fenix3 apart from the FR920XT we need to look into the
510
- # device_info section for all devices with a garmin_product of
511
- # 'fr920xt'.
512
- fit_entity.device_infos.each do |di|
513
- if di.device_index == 0
514
- return {
515
- :manufacturer => di.manufacturer,
516
- :product => di.garmin_product || di.product,
517
- :numeric_manufacturer => di.numeric_manufacturer,
518
- :numeric_product => di.numeric_product,
519
- :serial_number => di.serial_number
520
- }
521
- end
507
+ fit_entity.device_infos.each do |di|
508
+ # Not all FIT file have indexed device sections. In case the device
509
+ # index is nil we'll take the first entry.
510
+ if (di.device_index.nil? || di.device_index == 0) &&
511
+ di.numeric_manufacturer && di.numeric_product
512
+ return {
513
+ :manufacturer => di.manufacturer,
514
+ :product => di.garmin_product || di.product,
515
+ :numeric_manufacturer => di.numeric_manufacturer,
516
+ :numeric_product => di.numeric_product,
517
+ :serial_number => di.serial_number || 0
518
+ }
522
519
  end
523
- Log.error "Fit entity has no device info for 0"
524
- return nil
525
- else
526
- # And for all properly developed devices we can just look at the
527
- # file_id section.
528
- if fid.manufacturer.nil? ||
529
- fid.manufacturer[0..'Undocumented value'.length - 1] ==
530
- 'Undocumented value'
531
- Log.error "Cannot store FIT files for unknown manufacturer " +
532
- fid.manufacturer
533
- return nil
534
- end
535
- fid.serial_number ||= 0
536
-
537
- return {
538
- :manufacturer => fid.manufacturer,
539
- :product => fid.garmin_product || fid.product,
540
- :numeric_manufacturer => di.numeric_manufacturer,
541
- :numeric_product => di.numeric_product,
542
- :serial_number => fid.serial_number
543
- }
544
520
  end
521
+
522
+ Log.error "Fit entity has no device info section"
523
+ return nil
545
524
  end
546
525
 
547
526
  def register_device(long_uid)
@@ -310,6 +310,7 @@ EOT
310
310
  handle_version_update
311
311
  import_legacy_archive
312
312
 
313
+ retval = 0
313
314
  case (cmd = args.shift)
314
315
  when 'check'
315
316
  if args.empty?
@@ -334,7 +335,9 @@ EOT
334
335
  # is given, use the current date.
335
336
  @ffs.monthly_report(day_in_localtime(args, '%Y-%m-01'))
336
337
  when 'delete'
337
- process_activities(args, :delete)
338
+ unless process_activities(args, :delete)
339
+ retval = 1
340
+ end
338
341
  when 'dump'
339
342
  @filter = Fit4Ruby::FitFilter.new unless @filter
340
343
  process_files_or_activities(args, :dump)
@@ -344,9 +347,13 @@ EOT
344
347
  if args.empty?
345
348
  # If we have no file or directory for the import command, we get the
346
349
  # most recently used directory from the runtime config.
347
- process_files([ @db['config']['import_dir'] ], :import)
350
+ unless process_files([ @db['config']['import_dir'] ], :import)
351
+ retval = 1
352
+ end
348
353
  else
349
- process_files(args, :import)
354
+ unless process_files(args, :import)
355
+ retval = 1
356
+ end
350
357
  if args.length == 1 && Dir.exists?(args[0])
351
358
  # If only one directory was specified as argument we store the
352
359
  # directory for future use.
@@ -361,7 +368,9 @@ EOT
361
368
  unless (@name = args.shift)
362
369
  Log.abort 'You must provide a new name for the activity'
363
370
  end
364
- process_activities(args, :rename)
371
+ unless process_activities(args, :rename)
372
+ retval = 1
373
+ end
365
374
  when 'set'
366
375
  unless (@attribute = args.shift)
367
376
  Log.abort 'You must specify the attribute you want to change'
@@ -369,7 +378,9 @@ EOT
369
378
  unless (@value = args.shift)
370
379
  Log.abort 'You must specify the new value for the attribute'
371
380
  end
372
- process_activities(args, :set)
381
+ unless process_activities(args, :set)
382
+ retval = 1
383
+ end
373
384
  when 'show'
374
385
  if args.empty?
375
386
  @ffs.show_list_in_browser
@@ -378,12 +389,18 @@ EOT
378
389
  # given day in a browser.
379
390
  @ffs.show_monitoring(args[0])
380
391
  else
381
- process_activities(args, :show)
392
+ unless process_activities(args, :show)
393
+ retval = 1
394
+ end
382
395
  end
383
396
  when 'sources'
384
- process_activities(args, :sources)
397
+ unless process_activities(args, :sources)
398
+ retval = 1
399
+ end
385
400
  when 'summary'
386
- process_activities(args, :summary)
401
+ unless process_activities(args, :summary)
402
+ retval = 1
403
+ end
387
404
  when 'units'
388
405
  change_unit_system(args)
389
406
  when 'htmldir'
@@ -399,7 +416,7 @@ EOT
399
416
  # Ensure that all updates are written to the database.
400
417
  @db.sync
401
418
 
402
- 0
419
+ retval
403
420
  end
404
421
 
405
422
  def help
@@ -409,11 +426,13 @@ EOT
409
426
  def process_files_or_activities(files_or_activities, command)
410
427
  files_or_activities.each do |foa|
411
428
  if foa[0] == ':'
412
- process_activities([ foa ], command)
429
+ return false unless process_activities([ foa ], command)
413
430
  else
414
- process_files([ foa ], command)
431
+ return false unless process_files([ foa ], command)
415
432
  end
416
433
  end
434
+
435
+ true
417
436
  end
418
437
 
419
438
  def process_activities(activity_refs, command)
@@ -426,15 +445,19 @@ EOT
426
445
  activities = @ffs.find(a_ref[1..-1])
427
446
  if activities.empty?
428
447
  Log.warn "No matching activities found for '#{a_ref}'"
429
- return
448
+ return false
449
+ end
450
+ activities.each do |a|
451
+ unless process_activity(a, command)
452
+ return false
453
+ end
430
454
  end
431
- activities.each { |a| process_activity(a, command) }
432
455
  else
433
456
  Log.abort "Activity references must start with ':': #{a_ref}"
434
457
  end
435
458
  end
436
459
 
437
- nil
460
+ true
438
461
  end
439
462
 
440
463
  def process_files(files_or_dirs, command)
@@ -445,12 +468,14 @@ EOT
445
468
  files_or_dirs.each do |fod|
446
469
  if File.directory?(fod)
447
470
  Dir.glob(File.join(fod, '*.FIT'), File::FNM_CASEFOLD).each do |file|
448
- process_file(file, command)
471
+ return false unless process_file(file, command)
449
472
  end
450
473
  else
451
- process_file(fod, command)
474
+ return false unless process_file(fod, command)
452
475
  end
453
476
  end
477
+
478
+ true
454
479
  end
455
480
 
456
481
  # Process a single FIT file according to the given command.
@@ -597,7 +622,6 @@ EOT
597
622
 
598
623
  def handle_version_update
599
624
  if @db['config']['version'] != VERSION
600
- puts "Work needed"
601
625
  from_version = Gem::Version.new(@db['config']['version'])
602
626
  to_version = Gem::Version.new(VERSION)
603
627