postrunner 0.0.5 → 0.0.6
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/README.md +21 -6
- data/lib/postrunner/ActivitiesDB.rb +6 -0
- data/lib/postrunner/Activity.rb +42 -18
- data/lib/postrunner/ActivityListView.rb +4 -2
- data/lib/postrunner/ActivitySummary.rb +24 -12
- data/lib/postrunner/ChartView.rb +86 -17
- data/lib/postrunner/Main.rb +11 -2
- data/lib/postrunner/RuntimeConfig.rb +1 -0
- data/lib/postrunner/TrackView.rb +11 -11
- data/lib/postrunner/version.rb +1 -1
- data/postrunner.gemspec +8 -8
- data/spec/PostRunner_spec.rb +17 -1
- data/spec/spec_helper.rb +1 -1
- metadata +21 -21
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 5c193dbf151045522cfe8f47b729d244eb87f957
|
4
|
+
data.tar.gz: fb92f5b286258cac2c17e96dae821d858face419
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 2a01b57478175e684b1253e30174e46ee5b3119dd8a2941a8cf6141255e7955c6628fff6a20167c3421feaee0ca7c857370ae8c05a4d7ae114fb6730df909953
|
7
|
+
data.tar.gz: b3f8c2ad6fa0effd9958a4102b6280e373c5bf11a3c90982bfdaaffaeb14d90f03fff37460963d51e6600cd9432f36b11338a86e3b5eb19536252fcd37e4c048
|
data/README.md
CHANGED
@@ -1,10 +1,15 @@
|
|
1
1
|
# PostRunner
|
2
2
|
|
3
|
-
PostRunner is an application to manage FIT files such as those
|
3
|
+
PostRunner is an application to manage FIT files such as those
|
4
|
+
produced by Garmin products like the Forerunner 620 (FR620). It allows you to
|
5
|
+
import the files from the device and inspect them.
|
4
6
|
|
5
7
|
## Installation
|
6
8
|
|
7
|
-
PostRunner is a Ruby application. You need
|
9
|
+
PostRunner is a [http://www.ruby-lang.org](Ruby) application. You need
|
10
|
+
to have a Ruby 2.0 or later runtime environment installed. This
|
11
|
+
application was developed and tested on Linux but may work on other
|
12
|
+
operating systems as well.
|
8
13
|
|
9
14
|
```
|
10
15
|
$ gem install postrunner
|
@@ -14,13 +19,20 @@ $ gem install postrunner
|
|
14
19
|
|
15
20
|
### Importing FIT files
|
16
21
|
|
17
|
-
To get started you need to connect your device to your computer and
|
22
|
+
To get started you need to connect your device to your computer and
|
23
|
+
mount it as a disk drive. Only devices that expose their data as FAT file
|
24
|
+
system are supported. Older devices use proprietary drivers and are
|
25
|
+
not supported by postrunner. Once the device is mounted find out the
|
26
|
+
full path to the directory that contains your FIT files. You can then
|
27
|
+
import all files on the device.
|
18
28
|
|
19
29
|
```
|
20
30
|
$ postrunner import /var/run/media/user/GARMIN/GARMIN/ACTIVITY/
|
21
31
|
```
|
22
32
|
|
23
|
-
The above command assumes that your device is mounted as
|
33
|
+
The above command assumes that your device is mounted as
|
34
|
+
/var/run/media/user. Please replace this with the path to your device.
|
35
|
+
Files that have been imported previously will not be imported again.
|
24
36
|
|
25
37
|
### Viewing FIT file data on the console
|
26
38
|
|
@@ -57,13 +69,16 @@ You can also get a full dump of the content of a FIT file.
|
|
57
69
|
$ postrunner dump 1234568.FIT
|
58
70
|
```
|
59
71
|
|
60
|
-
If the file is already in the data base you can also use the reference
|
72
|
+
If the file is already in the data base you can also use the reference
|
73
|
+
notation.
|
61
74
|
|
62
75
|
```
|
63
76
|
$ postrunner dump :1
|
64
77
|
```
|
65
78
|
|
66
|
-
This will provide you with a lot more information contained in the FIT
|
79
|
+
This will provide you with a lot more information contained in the FIT
|
80
|
+
files that is not available through Garmin Connect or most other
|
81
|
+
tools.
|
67
82
|
|
68
83
|
### Viewing FIT file data in your web browser
|
69
84
|
|
@@ -49,11 +49,17 @@ module PostRunner
|
|
49
49
|
# Not all instance variables of Activity are stored in the file. The
|
50
50
|
# normal constructor is not run during YAML::load_file. We have to
|
51
51
|
# initialize those instance variables in a secondary step.
|
52
|
+
sync_needed = false
|
52
53
|
@activities.each do |a|
|
53
54
|
a.late_init(self)
|
55
|
+
# If the Activity has the data from the FIT file loaded, a value was
|
56
|
+
# missing in the YAML file. Set the sync flag so we can update the
|
57
|
+
# YAML file once we have checked all Activities.
|
58
|
+
sync_needed |= !a.fit_activity.nil?
|
54
59
|
end
|
55
60
|
|
56
61
|
@records = PersonalRecords.new(self)
|
62
|
+
sync if sync_needed
|
57
63
|
end
|
58
64
|
|
59
65
|
# Add a new FIT file to the database.
|
data/lib/postrunner/Activity.rb
CHANGED
@@ -23,8 +23,10 @@ module PostRunner
|
|
23
23
|
|
24
24
|
# This is a list of variables that provide data from the fit file. To
|
25
25
|
# speed up access to it, we cache the data in the activity database.
|
26
|
-
@@
|
27
|
-
avg_speed )
|
26
|
+
@@CachedActivityValues = %w( sport timestamp total_distance
|
27
|
+
total_timer_time avg_speed )
|
28
|
+
# We also store some additional information in the archive index.
|
29
|
+
@@CachedAttributes = @@CachedActivityValues + %w( fit_file name )
|
28
30
|
|
29
31
|
def initialize(db, fit_file, fit_activity, name = nil)
|
30
32
|
@fit_file = fit_file
|
@@ -32,7 +34,7 @@ module PostRunner
|
|
32
34
|
@name = name || fit_file
|
33
35
|
late_init(db)
|
34
36
|
|
35
|
-
@@
|
37
|
+
@@CachedActivityValues.each do |v|
|
36
38
|
v_str = "@#{v}"
|
37
39
|
instance_variable_set(v_str, fit_activity.send(v))
|
38
40
|
self.class.send(:attr_reader, v.to_sym)
|
@@ -41,6 +43,9 @@ module PostRunner
|
|
41
43
|
generate_html_view
|
42
44
|
end
|
43
45
|
|
46
|
+
# YAML::load() does not call initialize(). We don't have all attributes
|
47
|
+
# stored in the YAML file, so we need to make sure these are properly set
|
48
|
+
# after a YAML::load().
|
44
49
|
def late_init(db)
|
45
50
|
@db = db
|
46
51
|
@html_dir = File.join(@db.db_dir, 'html')
|
@@ -56,26 +61,45 @@ module PostRunner
|
|
56
61
|
@fit_activity = load_fit_file(filter)
|
57
62
|
end
|
58
63
|
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
64
|
+
# This method is called during YAML::load() to initialize the class
|
65
|
+
# objects. The initialize() is NOT called during YAML::load(). Any
|
66
|
+
# additional initialization work is done in late_init().
|
67
|
+
def init_with(coder)
|
68
|
+
@@CachedAttributes.each do |name_without_at|
|
69
|
+
name_with_at = '@' + name_without_at
|
70
|
+
# Create attr_readers for cached variables.
|
71
|
+
self.class.send(:attr_reader, name_without_at.to_sym)
|
72
|
+
|
73
|
+
if coder.map.include?(name_without_at)
|
74
|
+
# The YAML file has a value for the instance variable. So just set
|
75
|
+
# it.
|
76
|
+
instance_variable_set(name_with_at, coder[name_without_at])
|
77
|
+
else
|
78
|
+
if @@CachedActivityValues.include?(name_without_at)
|
79
|
+
# The YAML file does not yet have the instance variable cached.
|
80
|
+
# Load the Activity data and extract the value to set the instance
|
81
|
+
# variable.
|
82
|
+
@fit_activity = load_fit_file unless @fit_activity
|
83
|
+
instance_variable_set(name_with_at,
|
84
|
+
@fit_activity.send(name_without_at))
|
85
|
+
else
|
86
|
+
Log.fatal "Don't know how to initialize the instance variable " +
|
87
|
+
"#{name_with_at}."
|
88
|
+
end
|
89
|
+
end
|
66
90
|
end
|
67
|
-
# Use the FIT file name as activity name if none has been set yet.
|
68
|
-
@name = @fit_file unless @name
|
69
91
|
end
|
70
92
|
|
93
|
+
# This method is called during Activity::to_yaml() calls. It's being used
|
94
|
+
# to prevent some instance variables from being saved in the YAML file.
|
95
|
+
# Only attributes that are listed in @@CachedAttributes are being saved.
|
71
96
|
def encode_with(coder)
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
next if attr_ignore.include?(v)
|
97
|
+
instance_variables.each do |a|
|
98
|
+
name_with_at = a.to_s
|
99
|
+
name_without_at = name_with_at[1..-1]
|
100
|
+
next unless @@CachedAttributes.include?(name_without_at)
|
77
101
|
|
78
|
-
coder[
|
102
|
+
coder[name_without_at] = instance_variable_get(name_with_at)
|
79
103
|
end
|
80
104
|
end
|
81
105
|
|
@@ -134,7 +134,7 @@ EOT
|
|
134
134
|
i = @page_no < 0 ? 0 : @page_no * @page_size
|
135
135
|
t = FlexiTable.new
|
136
136
|
t.head
|
137
|
-
t.row(%w( Ref. Activity Start Distance Duration Pace ),
|
137
|
+
t.row(%w( Ref. Activity Start Distance Duration Speed/Pace ),
|
138
138
|
{ :halign => :left })
|
139
139
|
t.set_column_attributes([
|
140
140
|
{ :halign => :right },
|
@@ -155,7 +155,9 @@ EOT
|
|
155
155
|
local_value(a.total_distance, 'm', '%.2f',
|
156
156
|
{ :metric => 'km', :statute => 'mi' }),
|
157
157
|
secsToHMS(a.total_timer_time),
|
158
|
-
pace(a.avg_speed)
|
158
|
+
a.sport == 'running' ? pace(a.avg_speed) :
|
159
|
+
local_value(a.avg_speed, 'm/s', '%.1f',
|
160
|
+
{ :metric => 'km/h', :statute => 'mph' }) ])
|
159
161
|
end
|
160
162
|
|
161
163
|
t
|
@@ -29,16 +29,12 @@ module PostRunner
|
|
29
29
|
end
|
30
30
|
|
31
31
|
def to_s
|
32
|
-
|
33
|
-
|
34
|
-
summary(session).to_s + "\n" + laps.to_s
|
32
|
+
summary.to_s + "\n" + laps.to_s
|
35
33
|
end
|
36
34
|
|
37
35
|
def to_html(doc)
|
38
|
-
session = @fit_activity.sessions[0]
|
39
|
-
|
40
36
|
frame(doc, "Activity: #{@name}") {
|
41
|
-
summary
|
37
|
+
summary.to_html(doc)
|
42
38
|
}
|
43
39
|
frame(doc, 'Laps') {
|
44
40
|
laps.to_html(doc)
|
@@ -47,7 +43,9 @@ module PostRunner
|
|
47
43
|
|
48
44
|
private
|
49
45
|
|
50
|
-
def summary
|
46
|
+
def summary
|
47
|
+
session = @fit_activity.sessions[0]
|
48
|
+
|
51
49
|
t = FlexiTable.new
|
52
50
|
t.enable_frame(false)
|
53
51
|
t.body
|
@@ -56,7 +54,13 @@ module PostRunner
|
|
56
54
|
local_value(session, 'total_distance', '%.2f %s',
|
57
55
|
{ :metric => 'km', :statute => 'mi'}) ])
|
58
56
|
t.row([ 'Time:', secsToHMS(session.total_timer_time) ])
|
59
|
-
|
57
|
+
if session.sport == 'running'
|
58
|
+
t.row([ 'Avg. Pace:', pace(session, 'avg_speed') ])
|
59
|
+
else
|
60
|
+
t.row([ 'Avg. Speed:',
|
61
|
+
local_value(session, 'avg_speed', '%.1f %s',
|
62
|
+
{ :metric => 'km/h', :statute => 'mph' }) ])
|
63
|
+
end
|
60
64
|
t.row([ 'Total Ascent:',
|
61
65
|
local_value(session, 'total_ascent', '%.0f %s',
|
62
66
|
{ :metric => 'm', :statute => 'ft' }) ])
|
@@ -91,18 +95,26 @@ module PostRunner
|
|
91
95
|
end
|
92
96
|
|
93
97
|
def laps
|
98
|
+
session = @fit_activity.sessions[0]
|
99
|
+
|
94
100
|
t = FlexiTable.new
|
95
101
|
t.head
|
96
|
-
t.row([ 'Lap', 'Duration', 'Distance',
|
97
|
-
'Avg.
|
102
|
+
t.row([ 'Lap', 'Duration', 'Distance',
|
103
|
+
session.sport == 'running' ? 'Avg. Pace' : 'Avg. Speed',
|
104
|
+
'Stride', 'Cadence', 'Avg. HR', 'Max. HR' ])
|
98
105
|
t.set_column_attributes(Array.new(8, { :halign => :right }))
|
99
106
|
t.body
|
100
|
-
|
107
|
+
session.laps.each.with_index do |lap, index|
|
101
108
|
t.cell(index + 1)
|
102
109
|
t.cell(secsToHMS(lap.total_timer_time))
|
103
110
|
t.cell(local_value(lap, 'total_distance', '%.2f',
|
104
111
|
{ :metric => 'km', :statute => 'mi' }))
|
105
|
-
|
112
|
+
if session.sport == 'running'
|
113
|
+
t.cell(pace(lap, 'avg_speed', false))
|
114
|
+
else
|
115
|
+
t.cell(local_value(lap, 'avg_speed', '%.1f',
|
116
|
+
{ :metric => 'km/h', :statute => 'mph' }))
|
117
|
+
end
|
106
118
|
t.cell(local_value(lap, 'avg_stride_length', '%.2f',
|
107
119
|
{ :metric => 'm', :statute => 'ft' }))
|
108
120
|
t.cell(lap.avg_running_cadence && lap.avg_fractional_cadence ?
|
data/lib/postrunner/ChartView.rb
CHANGED
@@ -1,5 +1,4 @@
|
|
1
|
-
#!/usr/bin/env ruby -w
|
2
|
-
# encoding: UTF-8
|
1
|
+
#!/usr/bin/env ruby -w # encoding: UTF-8
|
3
2
|
#
|
4
3
|
# = ChartView.rb -- PostRunner - Manage the data from your Garmin sport devices.
|
5
4
|
#
|
@@ -20,6 +19,7 @@ module PostRunner
|
|
20
19
|
|
21
20
|
def initialize(activity, unit_system)
|
22
21
|
@activity = activity
|
22
|
+
@sport = activity.fit_activity.sessions[0].sport
|
23
23
|
@unit_system = unit_system
|
24
24
|
@empty_charts = {}
|
25
25
|
end
|
@@ -35,7 +35,11 @@ module PostRunner
|
|
35
35
|
end
|
36
36
|
|
37
37
|
def div(doc)
|
38
|
-
|
38
|
+
if @sport == 'running'
|
39
|
+
chart_div(doc, 'pace', "Pace (#{select_unit('min/km')})")
|
40
|
+
else
|
41
|
+
chart_div(doc, 'speed', "Speed (#{select_unit('km/h')})")
|
42
|
+
end
|
39
43
|
chart_div(doc, 'altitude', "Elevation (#{select_unit('m')})")
|
40
44
|
chart_div(doc, 'heart_rate', 'Heart Rate (bpm)')
|
41
45
|
chart_div(doc, 'run_cadence', 'Run Cadence (spm)')
|
@@ -51,7 +55,7 @@ module PostRunner
|
|
51
55
|
when :metric
|
52
56
|
metric_unit
|
53
57
|
when :statute
|
54
|
-
{ 'min/km' => 'min/mi', 'm' => 'ft', 'cm' => 'in',
|
58
|
+
{ 'min/km' => 'min/mi', 'm' => 'ft', 'cm' => 'in', 'km/h' => 'mph',
|
55
59
|
'bpm' => 'bpm', 'spm' => 'spm', 'ms' => 'ms' }[metric_unit]
|
56
60
|
else
|
57
61
|
Log.fatal "Unknown unit system #{@unit_system}"
|
@@ -91,22 +95,27 @@ EOT
|
|
91
95
|
def java_script
|
92
96
|
s = "$(function() {\n"
|
93
97
|
|
94
|
-
s <<
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
+
s << tooltip_div
|
99
|
+
if @sport == 'running'
|
100
|
+
s << line_graph('pace', 'Pace', 'min/km', '#0A7BEE' )
|
101
|
+
else
|
102
|
+
s << line_graph('speed', 'Speed', 'km/h', '#0A7BEE' )
|
103
|
+
end
|
104
|
+
s << line_graph('altitude', 'Elevation', 'm', '#5AAA44')
|
105
|
+
s << line_graph('heart_rate', 'Heart Rate', 'bpm', '#900000')
|
106
|
+
s << point_graph('run_cadence', 'Run Cadence', 'spm',
|
98
107
|
[ [ '#EE3F2D', 151 ],
|
99
108
|
[ '#F79666', 163 ],
|
100
109
|
[ '#A0D488', 174 ],
|
101
110
|
[ '#96D7DE', 185 ],
|
102
111
|
[ '#A88BBB', nil ] ])
|
103
|
-
s << point_graph('vertical_oscillation', 'cm',
|
112
|
+
s << point_graph('vertical_oscillation', 'Vertical Oscillation', 'cm',
|
104
113
|
[ [ '#A88BBB', 67 ],
|
105
114
|
[ '#96D7DE', 84 ],
|
106
115
|
[ '#A0D488', 101 ],
|
107
116
|
[ '#F79666', 118 ],
|
108
117
|
[ '#EE3F2D', nil ] ])
|
109
|
-
s << point_graph('stance_time', 'ms',
|
118
|
+
s << point_graph('stance_time', 'Ground Contact Time', 'ms',
|
110
119
|
[ [ '#A88BBB', 208 ],
|
111
120
|
[ '#96D7DE', 241 ],
|
112
121
|
[ '#A0D488', 273 ],
|
@@ -118,19 +127,52 @@ EOT
|
|
118
127
|
s
|
119
128
|
end
|
120
129
|
|
121
|
-
def
|
130
|
+
def tooltip_div
|
131
|
+
<<"EOT"
|
132
|
+
function timeToHMS(usecs) {
|
133
|
+
var secs = parseInt(usecs / 1000.0);
|
134
|
+
var s = secs % 60;
|
135
|
+
var mins = parseInt(secs / 60);
|
136
|
+
var m = mins % 60;
|
137
|
+
var h = parseInt(mins / 60);
|
138
|
+
s = (s < 10) ? "0" + s : s;
|
139
|
+
if (h == 0) {
|
140
|
+
return ("" + m + ":" + s);
|
141
|
+
} else {
|
142
|
+
m = (m < 10) ? "0" + m : m;
|
143
|
+
return ("" + h + ":" + m + ":" + s);
|
144
|
+
}
|
145
|
+
};
|
146
|
+
$("<div id='tooltip'></div>").css({
|
147
|
+
position: "absolute",
|
148
|
+
display: "none",
|
149
|
+
border: "1px solid #888",
|
150
|
+
padding: "2px",
|
151
|
+
"background-color": "#EEE",
|
152
|
+
opacity: 0.90,
|
153
|
+
"font-size": "8pt"
|
154
|
+
}).appendTo("body");
|
155
|
+
EOT
|
156
|
+
end
|
157
|
+
|
158
|
+
def line_graph(field, y_label, unit, color = nil)
|
122
159
|
s = "var #{field}_data = [\n"
|
123
160
|
|
124
161
|
data_set = []
|
125
162
|
start_time = @activity.fit_activity.sessions[0].start_time.to_i
|
163
|
+
min_value = nil
|
126
164
|
@activity.fit_activity.records.each do |r|
|
127
165
|
value = r.get_as(field, select_unit(unit))
|
128
166
|
if field == 'pace'
|
129
|
-
|
167
|
+
# Slow speeds lead to very large pace values that make the graph
|
168
|
+
# hard to read. We cap the pace at 20.0 min/km to keep it readable.
|
169
|
+
if value > (@unit_system == :metric ? 20.0 : 36.0 )
|
130
170
|
value = nil
|
131
171
|
else
|
132
172
|
value = (value * 3600.0 * 1000).to_i
|
133
173
|
end
|
174
|
+
else
|
175
|
+
min_value = value if value && (min_value.nil? || min_value > value)
|
134
176
|
end
|
135
177
|
data_set << [ ((r.timestamp.to_i - start_time) * 1000).to_i, value ]
|
136
178
|
end
|
@@ -144,25 +186,31 @@ EOT
|
|
144
186
|
"[ #{set[0]}, #{set[1] ? set[1] : 'null'} ]"
|
145
187
|
end.join(', ')
|
146
188
|
|
189
|
+
chart_id = "#{field}_chart"
|
147
190
|
s << <<"EOT"
|
148
191
|
];
|
149
192
|
|
150
|
-
$.plot("##{
|
193
|
+
$.plot(\"##{chart_id}\",
|
151
194
|
[ { data: #{field}_data,
|
152
195
|
#{color ? "color: \"#{color}\"," : ''}
|
153
196
|
lines: { show: true#{field == 'pace' ? '' :
|
154
197
|
', fill: true'} } } ],
|
155
|
-
{ xaxis: { mode: "time" }
|
198
|
+
{ xaxis: { mode: "time" },
|
199
|
+
grid: { hoverable: true }
|
156
200
|
EOT
|
157
201
|
if field == 'pace'
|
158
202
|
s << ", yaxis: { mode: \"time\",\n" +
|
159
203
|
" transform: function (v) { return -v; },\n" +
|
160
204
|
" inverseTransform: function (v) { return -v; } }"
|
205
|
+
else
|
206
|
+
# Set the minimum slightly below the lowest found value.
|
207
|
+
s << ", yaxis: { min: #{0.9 * min_value} }"
|
161
208
|
end
|
162
209
|
s << "});\n"
|
210
|
+
s << hover_function(chart_id, y_label, select_unit(unit)) + "\n"
|
163
211
|
end
|
164
212
|
|
165
|
-
def point_graph(field, unit, colors)
|
213
|
+
def point_graph(field, y_label, unit, colors)
|
166
214
|
# We need to split the field values into separate data sets for each
|
167
215
|
# color. The max value for each color determines which set a data point
|
168
216
|
# ends up in.
|
@@ -207,14 +255,17 @@ EOT
|
|
207
255
|
s << " ];\n"
|
208
256
|
end
|
209
257
|
|
210
|
-
|
258
|
+
chart_id = "#{field}_chart"
|
259
|
+
s << "$.plot(\"##{chart_id}\", [\n"
|
211
260
|
s << data_sets.map do |index, ds|
|
212
261
|
"{ data: #{field}_data_#{index},\n" +
|
213
262
|
" color: \"#{colors[index][0]}\",\n" +
|
214
263
|
" points: { show: true, fillColor: \"#{colors[index][0]}\", " +
|
215
264
|
" fill: true, radius: 2 } }"
|
216
265
|
end.join(', ')
|
217
|
-
s << "], { xaxis: { mode: \"time\" }
|
266
|
+
s << "], { xaxis: { mode: \"time\" },
|
267
|
+
grid: { hoverable: true } });\n"
|
268
|
+
s << hover_function(chart_id, y_label, select_unit(unit))
|
218
269
|
|
219
270
|
s
|
220
271
|
end
|
@@ -228,6 +279,24 @@ EOT
|
|
228
279
|
}
|
229
280
|
end
|
230
281
|
|
282
|
+
def hover_function(chart_id, y_label, y_unit)
|
283
|
+
<<"EOT"
|
284
|
+
$("##{chart_id}").bind("plothover", function (event, pos, item) {
|
285
|
+
if (item) {
|
286
|
+
var x = timeToHMS(item.datapoint[0]);
|
287
|
+
var y = #{y_label == 'Pace' ? 'timeToHMS(item.datapoint[1] / 60)' :
|
288
|
+
'item.datapoint[1].toFixed(0)'};
|
289
|
+
$("#tooltip").html("<b>#{y_label}:</b> " + y + " #{y_unit}<br/>" +
|
290
|
+
"<b>Time:</b> " + x + " h:m:s")
|
291
|
+
.css({top: item.pageY-20, left: item.pageX+15})
|
292
|
+
.fadeIn(200);
|
293
|
+
} else {
|
294
|
+
$("#tooltip").hide();
|
295
|
+
}
|
296
|
+
});
|
297
|
+
EOT
|
298
|
+
end
|
299
|
+
|
231
300
|
end
|
232
301
|
|
233
302
|
end
|
data/lib/postrunner/Main.rb
CHANGED
@@ -83,9 +83,9 @@ EOT
|
|
83
83
|
end
|
84
84
|
|
85
85
|
opts.separator ""
|
86
|
-
opts.separator "Options for the 'import'
|
86
|
+
opts.separator "Options for the 'import' command:"
|
87
87
|
opts.on('--name name', String,
|
88
|
-
'Name
|
88
|
+
'Name the activity to the specified name') do |n|
|
89
89
|
@name = n
|
90
90
|
end
|
91
91
|
|
@@ -163,6 +163,12 @@ EOT
|
|
163
163
|
|
164
164
|
def execute_command(args)
|
165
165
|
@activities = ActivitiesDB.new(@db_dir, @cfg)
|
166
|
+
if @cfg.get_option(:version) != VERSION
|
167
|
+
Log.warn "Version upgrade detected. Database conversion started..."
|
168
|
+
@activities.generate_all_html_reports
|
169
|
+
@cfg.set_option(:version, VERSION)
|
170
|
+
Log.info "Version upgrade completed."
|
171
|
+
end
|
166
172
|
|
167
173
|
case (cmd = args.shift)
|
168
174
|
when 'check'
|
@@ -194,6 +200,9 @@ EOT
|
|
194
200
|
when 'records'
|
195
201
|
@activities.show_records
|
196
202
|
when 'rename'
|
203
|
+
unless (@name = args.shift)
|
204
|
+
Log.fatal "You must provide a new name for the activity"
|
205
|
+
end
|
197
206
|
process_activities(args, :rename)
|
198
207
|
when 'show'
|
199
208
|
if args.empty?
|
data/lib/postrunner/TrackView.rb
CHANGED
@@ -117,19 +117,19 @@ EOT
|
|
117
117
|
def track_points(script)
|
118
118
|
first = true
|
119
119
|
@activity.fit_activity.sessions.each do |session|
|
120
|
-
session.
|
121
|
-
|
122
|
-
|
123
|
-
|
124
|
-
|
125
|
-
|
126
|
-
|
127
|
-
|
128
|
-
|
129
|
-
|
120
|
+
session.records.each do |record|
|
121
|
+
long = record.position_long
|
122
|
+
lat = record.position_lat
|
123
|
+
next unless long && lat
|
124
|
+
|
125
|
+
if first
|
126
|
+
first = false
|
127
|
+
else
|
128
|
+
script << ","
|
129
|
+
end
|
130
|
+
script << <<"EOT"
|
130
131
|
new OpenLayers.Feature.Vector(new OpenLayers.Geometry.Point(#{long}, #{lat}).transform(geographic, mercator))
|
131
132
|
EOT
|
132
|
-
end
|
133
133
|
end
|
134
134
|
end
|
135
135
|
end
|
data/lib/postrunner/version.rb
CHANGED
data/postrunner.gemspec
CHANGED
@@ -7,9 +7,9 @@ Gem::Specification.new do |spec|
|
|
7
7
|
spec.name = "postrunner"
|
8
8
|
spec.version = PostRunner::VERSION
|
9
9
|
spec.authors = ["Chris Schlaeger"]
|
10
|
-
spec.email = ["chris@
|
10
|
+
spec.email = ["chris@taskjuggler.org"]
|
11
11
|
spec.summary = %q{Application to manage and analyze Garmin FIT files.}
|
12
|
-
spec.description = %q{This application will allow you to manage and analyze .FIT files as generated by Garmin GPS devices. The application was developed for the Garmin Forerunner 620. Other devices may
|
12
|
+
spec.description = %q{This application will allow you to manage and analyze .FIT files such as those generated by Garmin GPS devices. The application was developed for the Garmin Forerunner 620. Other devices may work as well. They need to export the data as USB Mass Storage devices.}
|
13
13
|
spec.homepage = 'https://github.com/scrapper/postrunner'
|
14
14
|
spec.license = "GNU GPL version 2"
|
15
15
|
|
@@ -19,11 +19,11 @@ Gem::Specification.new do |spec|
|
|
19
19
|
spec.require_paths = ["lib"]
|
20
20
|
spec.required_ruby_version = '>=2.0'
|
21
21
|
|
22
|
-
spec.add_dependency
|
23
|
-
spec.add_dependency
|
22
|
+
spec.add_dependency 'fit4ruby', '~> 0.0.4'
|
23
|
+
spec.add_dependency 'nokogiri', '~> 1.6'
|
24
24
|
|
25
|
-
spec.add_development_dependency
|
26
|
-
spec.add_development_dependency
|
27
|
-
spec.add_development_dependency
|
28
|
-
spec.add_development_dependency
|
25
|
+
spec.add_development_dependency 'bundler', '~> 1.6'
|
26
|
+
spec.add_development_dependency 'rake', '~> 0.9.6'
|
27
|
+
spec.add_development_dependency 'rspec', '~> 2.14.1'
|
28
|
+
spec.add_development_dependency 'yard', '~> 0.8.7'
|
29
29
|
end
|
data/spec/PostRunner_spec.rb
CHANGED
@@ -100,7 +100,7 @@ describe PostRunner::Main do
|
|
100
100
|
end
|
101
101
|
|
102
102
|
it 'should rename FILE2.FIT activity' do
|
103
|
-
postrunner(%w( rename :1
|
103
|
+
postrunner(%w( rename foobar :1 ))
|
104
104
|
list = postrunner(%w( list ))
|
105
105
|
list.index('FILE2.FIT').should be_nil
|
106
106
|
list.index('foobar').should be_a(Fixnum)
|
@@ -122,5 +122,21 @@ describe PostRunner::Main do
|
|
122
122
|
postrunner(%w( units metric ))
|
123
123
|
end
|
124
124
|
|
125
|
+
it 'should properly upgrade to a new version' do
|
126
|
+
# Change version in config file to 0.0.0.
|
127
|
+
rc = PostRunner::RuntimeConfig.new(@db_dir)
|
128
|
+
rc.set_option(:version, '0.0.0')
|
129
|
+
# Check that the config file really was changed.
|
130
|
+
rc = PostRunner::RuntimeConfig.new(@db_dir)
|
131
|
+
rc.get_option(:version).should == '0.0.0'
|
132
|
+
|
133
|
+
# Run some command.
|
134
|
+
postrunner(%w( list ))
|
135
|
+
|
136
|
+
# Check that version matches the current version again.
|
137
|
+
rc = PostRunner::RuntimeConfig.new(@db_dir)
|
138
|
+
rc.get_option(:version).should == PostRunner::VERSION
|
139
|
+
end
|
140
|
+
|
125
141
|
end
|
126
142
|
|
data/spec/spec_helper.rb
CHANGED
@@ -38,7 +38,7 @@ def create_fit_activity(date, duration_minutes)
|
|
38
38
|
end
|
39
39
|
ts += 60
|
40
40
|
end
|
41
|
-
a.new_session({ :timestamp => ts })
|
41
|
+
a.new_session({ :timestamp => ts, :sport => 'running' })
|
42
42
|
a.new_event({ :timestamp => ts, :event => 'recovery_time',
|
43
43
|
:event_type => 'marker',
|
44
44
|
:data => 2160 })
|
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.0.
|
4
|
+
version: 0.0.6
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Chris Schlaeger
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2014-
|
11
|
+
date: 2014-09-03 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: fit4ruby
|
@@ -16,14 +16,14 @@ dependencies:
|
|
16
16
|
requirements:
|
17
17
|
- - ~>
|
18
18
|
- !ruby/object:Gem::Version
|
19
|
-
version: 0.0.
|
19
|
+
version: 0.0.4
|
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: 0.0.
|
26
|
+
version: 0.0.4
|
27
27
|
- !ruby/object:Gem::Dependency
|
28
28
|
name: nokogiri
|
29
29
|
requirement: !ruby/object:Gem::Requirement
|
@@ -56,50 +56,50 @@ dependencies:
|
|
56
56
|
name: rake
|
57
57
|
requirement: !ruby/object:Gem::Requirement
|
58
58
|
requirements:
|
59
|
-
- -
|
59
|
+
- - ~>
|
60
60
|
- !ruby/object:Gem::Version
|
61
|
-
version:
|
61
|
+
version: 0.9.6
|
62
62
|
type: :development
|
63
63
|
prerelease: false
|
64
64
|
version_requirements: !ruby/object:Gem::Requirement
|
65
65
|
requirements:
|
66
|
-
- -
|
66
|
+
- - ~>
|
67
67
|
- !ruby/object:Gem::Version
|
68
|
-
version:
|
68
|
+
version: 0.9.6
|
69
69
|
- !ruby/object:Gem::Dependency
|
70
70
|
name: rspec
|
71
71
|
requirement: !ruby/object:Gem::Requirement
|
72
72
|
requirements:
|
73
|
-
- -
|
73
|
+
- - ~>
|
74
74
|
- !ruby/object:Gem::Version
|
75
|
-
version:
|
75
|
+
version: 2.14.1
|
76
76
|
type: :development
|
77
77
|
prerelease: false
|
78
78
|
version_requirements: !ruby/object:Gem::Requirement
|
79
79
|
requirements:
|
80
|
-
- -
|
80
|
+
- - ~>
|
81
81
|
- !ruby/object:Gem::Version
|
82
|
-
version:
|
82
|
+
version: 2.14.1
|
83
83
|
- !ruby/object:Gem::Dependency
|
84
84
|
name: yard
|
85
85
|
requirement: !ruby/object:Gem::Requirement
|
86
86
|
requirements:
|
87
|
-
- -
|
87
|
+
- - ~>
|
88
88
|
- !ruby/object:Gem::Version
|
89
|
-
version:
|
89
|
+
version: 0.8.7
|
90
90
|
type: :development
|
91
91
|
prerelease: false
|
92
92
|
version_requirements: !ruby/object:Gem::Requirement
|
93
93
|
requirements:
|
94
|
-
- -
|
94
|
+
- - ~>
|
95
95
|
- !ruby/object:Gem::Version
|
96
|
-
version:
|
97
|
-
description: This application will allow you to manage and analyze .FIT files
|
98
|
-
by Garmin GPS devices. The application was developed for the
|
99
|
-
Other devices may
|
100
|
-
Storage devices
|
96
|
+
version: 0.8.7
|
97
|
+
description: This application will allow you to manage and analyze .FIT files such
|
98
|
+
as those generated by Garmin GPS devices. The application was developed for the
|
99
|
+
Garmin Forerunner 620. Other devices may work as well. They need to export the data
|
100
|
+
as USB Mass Storage devices.
|
101
101
|
email:
|
102
|
-
- chris@
|
102
|
+
- chris@taskjuggler.org
|
103
103
|
executables:
|
104
104
|
- postrunner
|
105
105
|
extensions: []
|