postrunner 0.0.6 → 0.0.7
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/postrunner/ActivitiesDB.rb +86 -31
- data/lib/postrunner/Activity.rb +222 -30
- data/lib/postrunner/ActivityLink.rb +59 -0
- data/lib/postrunner/ActivityListView.rb +37 -90
- data/lib/postrunner/ActivitySummary.rb +12 -12
- data/lib/postrunner/ActivityView.rb +49 -72
- data/lib/postrunner/BackedUpFile.rb +56 -0
- data/lib/postrunner/ChartView.rb +14 -16
- data/lib/postrunner/DeviceList.rb +3 -7
- data/lib/postrunner/FlexiTable.rb +28 -1
- data/lib/postrunner/HTMLBuilder.rb +64 -16
- data/lib/postrunner/Main.rb +63 -19
- data/lib/postrunner/NavButtonRow.rb +103 -0
- data/lib/postrunner/PagingButtons.rb +77 -0
- data/lib/postrunner/PersonalRecords.rb +338 -79
- data/lib/postrunner/RecordListPageView.rb +69 -0
- data/lib/postrunner/RuntimeConfig.rb +5 -3
- data/lib/postrunner/TrackView.rb +14 -16
- data/lib/postrunner/UserProfileView.rb +3 -7
- data/lib/postrunner/View.rb +97 -0
- data/lib/postrunner/ViewBottom.rb +54 -0
- data/lib/postrunner/ViewButtons.rb +68 -0
- data/lib/postrunner/ViewFrame.rb +93 -0
- data/lib/postrunner/ViewTop.rb +80 -0
- data/lib/postrunner/version.rb +1 -1
- data/misc/icons/activities.png +0 -0
- data/misc/icons/activities.svg +1582 -0
- data/misc/icons/record-small.png +0 -0
- data/misc/icons/record.png +0 -0
- data/misc/icons/record.svg +15712 -0
- data/spec/ActivitySummary_spec.rb +3 -1
- data/spec/PostRunner_spec.rb +45 -0
- data/spec/View_spec.rb +61 -0
- data/spec/spec_helper.rb +21 -7
- metadata +19 -3
- data/lib/postrunner/ViewWidgets.rb +0 -153
@@ -0,0 +1,103 @@
|
|
1
|
+
#!/usr/bin/env ruby -w
|
2
|
+
# encoding: UTF-8
|
3
|
+
#
|
4
|
+
# = NavButtonRow.rb -- PostRunner - Manage the data from your Garmin sport devices.
|
5
|
+
#
|
6
|
+
# Copyright (c) 2015 by Chris Schlaeger <cs@taskjuggler.org>
|
7
|
+
#
|
8
|
+
# This program is free software; you can redistribute it and/or modify
|
9
|
+
# it under the terms of version 2 of the GNU General Public License as
|
10
|
+
# published by the Free Software Foundation.
|
11
|
+
#
|
12
|
+
|
13
|
+
require 'postrunner/HTMLBuilder'
|
14
|
+
|
15
|
+
module PostRunner
|
16
|
+
|
17
|
+
# Auxilliary class that stores the name of an icon file and a URL as a
|
18
|
+
# String. It is used to describe a NavButtonRow button.
|
19
|
+
class NavButtonDef < Struct.new(:icon, :url)
|
20
|
+
end
|
21
|
+
|
22
|
+
# A NavButtonRow is a row of buttons used to navigate between HTML pages.
|
23
|
+
class NavButtonRow
|
24
|
+
|
25
|
+
# A class to store the icon and URL of a button in the NavButtonRow
|
26
|
+
# objects.
|
27
|
+
class Button
|
28
|
+
|
29
|
+
# Create a Button object.
|
30
|
+
# @param icon [String] File name of the icon file
|
31
|
+
# @param url [String] URL of the page to change to
|
32
|
+
def initialize(icon, url = nil)
|
33
|
+
@icon = icon
|
34
|
+
@url = url
|
35
|
+
end
|
36
|
+
|
37
|
+
# Add the object as HTML Elements to the document.
|
38
|
+
# @param doc [HTMLBuilder] XML Document
|
39
|
+
def to_html(doc)
|
40
|
+
if @url
|
41
|
+
doc.a({ :href => @url }) {
|
42
|
+
doc.img({ :src => "icons/#{@icon}", :class => 'active_button' })
|
43
|
+
}
|
44
|
+
else
|
45
|
+
doc.img({ :src => "icons/#{@icon}", :class => 'inactive_button' })
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
end
|
50
|
+
|
51
|
+
# Create a new NavButtonRow object.
|
52
|
+
# @param float [String, Nil] specifies if the HTML representation should
|
53
|
+
# be a floating object that floats left or right.
|
54
|
+
def initialize(float = nil)
|
55
|
+
unless float.nil? || %w( left right ).include?(float)
|
56
|
+
raise ArgumentError "float argument must be nil, 'left' or 'right'"
|
57
|
+
end
|
58
|
+
|
59
|
+
@float = float
|
60
|
+
@buttons = []
|
61
|
+
end
|
62
|
+
|
63
|
+
# Add a new button to the NavButtonRow object.
|
64
|
+
# @param icon [String] File name of the icon file
|
65
|
+
# @param url [String] URL of the page to change to
|
66
|
+
def addButton(icon, url = nil)
|
67
|
+
@buttons << Button.new(icon, url)
|
68
|
+
end
|
69
|
+
|
70
|
+
# Add the object as HTML Elements to the document.
|
71
|
+
# @param doc [HTMLBuilder] XML Document
|
72
|
+
def to_html(doc)
|
73
|
+
doc.unique(:nav_button_row_style) {
|
74
|
+
doc.head { doc.style(style) }
|
75
|
+
}
|
76
|
+
doc.div({ :class => 'nav_button_row',
|
77
|
+
:style => "width: #{@buttons.length * (32 + 10)}px; " +
|
78
|
+
"#{@float ? "float: #{@float};" :
|
79
|
+
'margin-left: auto; margin-right: auto'}"}) {
|
80
|
+
@buttons.each { |btn| btn.to_html(doc) }
|
81
|
+
}
|
82
|
+
end
|
83
|
+
|
84
|
+
private
|
85
|
+
|
86
|
+
def style
|
87
|
+
<<"EOT"
|
88
|
+
.nav_button_row {
|
89
|
+
padding: 3px 30px;
|
90
|
+
}
|
91
|
+
.active_button {
|
92
|
+
padding: 5px;
|
93
|
+
}
|
94
|
+
.inactive_button {
|
95
|
+
padding: 5px;
|
96
|
+
opacity: 0.4;
|
97
|
+
}
|
98
|
+
EOT
|
99
|
+
end
|
100
|
+
|
101
|
+
end
|
102
|
+
|
103
|
+
end
|
@@ -0,0 +1,77 @@
|
|
1
|
+
#!/usr/bin/env ruby -w
|
2
|
+
# encoding: UTF-8
|
3
|
+
#
|
4
|
+
# = PagingButtons.rb -- PostRunner - Manage the data from your Garmin sport devices.
|
5
|
+
#
|
6
|
+
# Copyright (c) 2015 by Chris Schlaeger <cs@taskjuggler.org>
|
7
|
+
#
|
8
|
+
# This program is free software; you can redistribute it and/or modify
|
9
|
+
# it under the terms of version 2 of the GNU General Public License as
|
10
|
+
# published by the Free Software Foundation.
|
11
|
+
#
|
12
|
+
|
13
|
+
require 'postrunner/NavButtonRow'
|
14
|
+
|
15
|
+
module PostRunner
|
16
|
+
|
17
|
+
# A class to generate a set of forward/backward buttons for an HTML page. It
|
18
|
+
# can also include jump to first/last buttons.
|
19
|
+
class PagingButtons
|
20
|
+
|
21
|
+
# Create a new PagingButtons object.
|
22
|
+
# @param page_urls [Array of String] Sorted list of all possible pages
|
23
|
+
# @param end_buttons [Boolean] If true jump to first/last buttons are
|
24
|
+
# included
|
25
|
+
def initialize(page_urls, end_buttons = true)
|
26
|
+
if page_urls.empty?
|
27
|
+
raise ArgumentError.new("'page_urls' must not be empty")
|
28
|
+
end
|
29
|
+
@pages = page_urls
|
30
|
+
@current_page_index = 0
|
31
|
+
@end_buttons = end_buttons
|
32
|
+
end
|
33
|
+
|
34
|
+
# Return the URL of the current page
|
35
|
+
def current_page
|
36
|
+
@pages[@current_page_index]
|
37
|
+
end
|
38
|
+
|
39
|
+
# Set the URL for the current page. It must be included in the URL set
|
40
|
+
# passed at creation time. The forward/backward links will be derived from
|
41
|
+
# the setting of the current page.
|
42
|
+
# @param page_url [String] URL of the page
|
43
|
+
def current_page=(page_url)
|
44
|
+
unless (@current_page_index = @pages.index(page_url))
|
45
|
+
raise ArgumentError.new("URL #{page_url} is not a known page URL")
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
# Iterate over all buttons. A NavButtonDef object is passed to the block
|
50
|
+
# that contains the icon and URL for the button. If no URL is set, the
|
51
|
+
# button is inactive.
|
52
|
+
def each
|
53
|
+
%w( first back forward last ).each do |button_name|
|
54
|
+
button = NavButtonDef.new
|
55
|
+
button.icon = button_name + '.png'
|
56
|
+
button.url =
|
57
|
+
case button_name
|
58
|
+
when 'first'
|
59
|
+
@current_page_index == 0 || !@end_buttons ? nil : @pages.first
|
60
|
+
when 'back'
|
61
|
+
@current_page_index == 0 ? nil :
|
62
|
+
@pages[@current_page_index - 1]
|
63
|
+
when 'forward'
|
64
|
+
@current_page_index == @pages.length - 1 ? nil :
|
65
|
+
@pages[@current_page_index + 1]
|
66
|
+
when 'last'
|
67
|
+
@current_page_index == @pages.length - 1 ||
|
68
|
+
!@end_buttons ? nil : @pages.last
|
69
|
+
end
|
70
|
+
|
71
|
+
yield(button)
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
75
|
+
end
|
76
|
+
|
77
|
+
end
|
@@ -3,7 +3,7 @@
|
|
3
3
|
#
|
4
4
|
# = PersonalRecords.rb -- PostRunner - Manage the data from your Garmin sport devices.
|
5
5
|
#
|
6
|
-
# Copyright (c) 2014 by Chris Schlaeger <cs@taskjuggler.org>
|
6
|
+
# Copyright (c) 2014, 2015 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
|
@@ -14,115 +14,349 @@ require 'fileutils'
|
|
14
14
|
require 'yaml'
|
15
15
|
|
16
16
|
require 'fit4ruby'
|
17
|
+
require 'postrunner/BackedUpFile'
|
18
|
+
require 'postrunner/RecordListPageView'
|
19
|
+
require 'postrunner/ActivityLink'
|
17
20
|
|
18
21
|
module PostRunner
|
19
22
|
|
20
23
|
class PersonalRecords
|
21
24
|
|
25
|
+
include Fit4Ruby::Converters
|
26
|
+
|
27
|
+
SpeedRecordDistances = {
|
28
|
+
'cycling' => {
|
29
|
+
5000.0 => '5 km',
|
30
|
+
8000.0 => '8 km',
|
31
|
+
9000.0 => '9 km',
|
32
|
+
10000.0 => '10 km',
|
33
|
+
20000.0 => '20 km',
|
34
|
+
40000.0 => '40 km',
|
35
|
+
80000.0 => '80 km',
|
36
|
+
90000.0 => '90 km',
|
37
|
+
12000.0 => '120 km',
|
38
|
+
18000.0 => '180 km',
|
39
|
+
},
|
40
|
+
'running' => {
|
41
|
+
1000.0 => '1 km',
|
42
|
+
1609.0 => '1 mi',
|
43
|
+
2000.0 => '2 km',
|
44
|
+
3000.0 => '3 km',
|
45
|
+
5000.0 => '5 km',
|
46
|
+
10000.0 => '10 km',
|
47
|
+
20000.0 => '20 km',
|
48
|
+
30000.0 => '30 km',
|
49
|
+
21097.5 => 'Half Marathon',
|
50
|
+
42195.0 => 'Marathon'
|
51
|
+
},
|
52
|
+
'swimming' => {
|
53
|
+
100.0 => '100 m',
|
54
|
+
300.0 => '300 m',
|
55
|
+
400.0 => '400 m',
|
56
|
+
750.0 => '750 m',
|
57
|
+
1500.0 => '1.5 km',
|
58
|
+
1930.0 => '1.2 mi',
|
59
|
+
3000.0 => '3 km',
|
60
|
+
4000.0 => '4 km',
|
61
|
+
3860.0 => '2.4 mi'
|
62
|
+
},
|
63
|
+
'walking' => {
|
64
|
+
1000.0 => '1 km',
|
65
|
+
1609.0 => '1 mi',
|
66
|
+
5000.0 => '5 km',
|
67
|
+
10000.0 => '10 km',
|
68
|
+
21097.5 => 'Half Marathon',
|
69
|
+
42195.0 => 'Marathon'
|
70
|
+
}
|
71
|
+
}
|
72
|
+
|
22
73
|
class Record
|
23
74
|
|
24
|
-
|
75
|
+
include Fit4Ruby::Converters
|
76
|
+
|
77
|
+
attr_accessor :activity, :sport, :distance, :duration, :start_time
|
25
78
|
|
26
|
-
def initialize(distance, duration, start_time
|
79
|
+
def initialize(activity, sport, distance, duration, start_time)
|
80
|
+
@activity = activity
|
81
|
+
@sport = sport
|
27
82
|
@distance = distance
|
28
83
|
@duration = duration
|
29
84
|
@start_time = start_time
|
30
|
-
|
85
|
+
end
|
86
|
+
|
87
|
+
def to_table_row(t)
|
88
|
+
t.row((@duration.nil? ?
|
89
|
+
[ 'Longest Run', '%.1f m' % @distance, '-' ] :
|
90
|
+
[ PersonalRecords::SpeedRecordDistances[@sport][@distance],
|
91
|
+
secsToHMS(@duration),
|
92
|
+
speedToPace(@distance / @duration) ]) +
|
93
|
+
[ @activity.db.ref_by_fit_file(@activity.fit_file),
|
94
|
+
ActivityLink.new(@activity, false),
|
95
|
+
@start_time.strftime("%Y-%m-%d") ])
|
31
96
|
end
|
32
97
|
|
33
98
|
end
|
34
99
|
|
35
|
-
|
100
|
+
class RecordSet
|
36
101
|
|
37
|
-
|
38
|
-
@activities = activities
|
39
|
-
@db_dir = activities.db_dir
|
40
|
-
@records_file = File.join(@db_dir, 'records.yml')
|
41
|
-
@records = []
|
102
|
+
include Fit4Ruby::Converters
|
42
103
|
|
43
|
-
|
44
|
-
end
|
104
|
+
attr_reader :year
|
45
105
|
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
106
|
+
def initialize(sport, year)
|
107
|
+
@sport = sport
|
108
|
+
@year = year
|
109
|
+
@distance = nil
|
110
|
+
@speed_records = {}
|
111
|
+
PersonalRecords::SpeedRecordDistances[@sport].each_key do |dist|
|
112
|
+
@speed_records[dist] = nil
|
113
|
+
end
|
114
|
+
end
|
115
|
+
|
116
|
+
def register_result(result)
|
117
|
+
if result.duration
|
118
|
+
# We have a potential speed record for a known distance.
|
119
|
+
unless PersonalRecords::SpeedRecordDistances[@sport].
|
120
|
+
include?(result.distance)
|
121
|
+
Log.fatal "Unknown record distance #{result.distance}"
|
122
|
+
end
|
123
|
+
|
124
|
+
old_record = @speed_records[result.distance]
|
125
|
+
if old_record.nil? || old_record.duration > result.duration
|
126
|
+
@speed_records[result.distance] = result
|
127
|
+
Log.info "New #{@year ? @year.to_s : 'all-time'} " +
|
128
|
+
"#{result.sport} speed record for " +
|
129
|
+
"#{PersonalRecords::SpeedRecordDistances[@sport][
|
130
|
+
result.distance]}: " +
|
131
|
+
"#{secsToHMS(result.duration)}"
|
132
|
+
return true
|
64
133
|
end
|
65
134
|
else
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
record.fit_file = fit_file
|
72
|
-
Log.info "New distance record #{distance} m"
|
135
|
+
# We have a potential distance record.
|
136
|
+
if @distance.nil? || result.distance > @distance.distance
|
137
|
+
@distance = result
|
138
|
+
Log.info "New #{@year ? @year.to_s : 'all-time'} " +
|
139
|
+
"#{result.sport} distance record: #{result.distance} m"
|
73
140
|
return true
|
74
|
-
else
|
75
|
-
# No new distance record.
|
76
|
-
return false
|
77
141
|
end
|
78
142
|
end
|
143
|
+
|
144
|
+
false
|
145
|
+
end
|
146
|
+
|
147
|
+
def delete_activity(activity)
|
148
|
+
if @distance && @distance.activity == activity
|
149
|
+
@distance = nil
|
150
|
+
end
|
151
|
+
PersonalRecords::SpeedRecordDistances[@sport].each_key do |dist|
|
152
|
+
if @speed_records[dist] && @speed_records[dist].activity == activity
|
153
|
+
@speed_records[dist] = nil
|
154
|
+
end
|
155
|
+
end
|
156
|
+
end
|
157
|
+
|
158
|
+
# Return true if no Record is stored in this RecordSet object.
|
159
|
+
def empty?
|
160
|
+
return false if @distance
|
161
|
+
@speed_records.each_value { |r| return false if r }
|
162
|
+
|
163
|
+
true
|
164
|
+
end
|
165
|
+
|
166
|
+
# Iterator for all Record objects that are stored in this data structure.
|
167
|
+
def each(&block)
|
168
|
+
yield(@distance) if @distance
|
169
|
+
@speed_records.each_value do |record|
|
170
|
+
yield(record) if record
|
171
|
+
end
|
172
|
+
end
|
173
|
+
|
174
|
+
def to_s
|
175
|
+
return '' if empty?
|
176
|
+
|
177
|
+
generate_table.to_s + "\n"
|
178
|
+
end
|
179
|
+
|
180
|
+
def to_html(doc)
|
181
|
+
generate_table.to_html(doc)
|
182
|
+
end
|
183
|
+
|
184
|
+
private
|
185
|
+
|
186
|
+
def generate_table
|
187
|
+
t = FlexiTable.new
|
188
|
+
t.head
|
189
|
+
t.row([ 'Record', 'Time/Dist.', 'Avg. Pace', 'Ref.', 'Activity',
|
190
|
+
'Date' ],
|
191
|
+
{ :halign => :center })
|
192
|
+
t.set_column_attributes([
|
193
|
+
{},
|
194
|
+
{ :halign => :right },
|
195
|
+
{ :halign => :right },
|
196
|
+
{ :halign => :right },
|
197
|
+
{ :halign => :left },
|
198
|
+
{ :halign => :left }
|
199
|
+
])
|
200
|
+
t.body
|
201
|
+
|
202
|
+
records = @speed_records.values.delete_if { |r| r.nil? }
|
203
|
+
records << @distance if @distance
|
204
|
+
|
205
|
+
records.sort { |r1, r2| r1.distance <=> r2.distance }.each do |r|
|
206
|
+
r.to_table_row(t)
|
207
|
+
end
|
208
|
+
|
209
|
+
t
|
210
|
+
end
|
211
|
+
|
212
|
+
|
213
|
+
end
|
214
|
+
|
215
|
+
class SportRecords
|
216
|
+
|
217
|
+
attr_reader :sport, :all_time, :yearly
|
218
|
+
|
219
|
+
def initialize(sport)
|
220
|
+
@sport = sport
|
221
|
+
@all_time = RecordSet.new(@sport, nil)
|
222
|
+
@yearly = {}
|
223
|
+
end
|
224
|
+
|
225
|
+
def register_result(result)
|
226
|
+
year = result.start_time.year
|
227
|
+
unless @yearly[year]
|
228
|
+
@yearly[year] = RecordSet.new(@sport, year)
|
229
|
+
end
|
230
|
+
|
231
|
+
new_at = @all_time.register_result(result)
|
232
|
+
new_yr = @yearly[year].register_result(result)
|
233
|
+
|
234
|
+
new_at || new_yr
|
235
|
+
end
|
236
|
+
|
237
|
+
def delete_activity(activity)
|
238
|
+
([ @all_time ] + @yearly.values).each do |r|
|
239
|
+
r.delete_activity(activity)
|
240
|
+
end
|
241
|
+
end
|
242
|
+
|
243
|
+
# Return true if no record is stored in this SportRecords object.
|
244
|
+
def empty?
|
245
|
+
return false unless @all_time.empty?
|
246
|
+
@yearly.each_value { |r| return false unless r.empty? }
|
247
|
+
|
248
|
+
true
|
249
|
+
end
|
250
|
+
|
251
|
+
# Iterator for all Record objects that are stored in this data structure.
|
252
|
+
def each(&block)
|
253
|
+
records = @yearly.values
|
254
|
+
records << @all_time if @all_time
|
255
|
+
records.each { |r| r.each(&block) }
|
256
|
+
end
|
257
|
+
|
258
|
+
def to_s
|
259
|
+
return '' if empty?
|
260
|
+
|
261
|
+
str = "All-time records:\n\n#{@all_time.to_s}" unless @all_time.empty?
|
262
|
+
@yearly.values.sort{ |r1, r2| r2.year <=> r1.year }.each do |record|
|
263
|
+
unless record.empty?
|
264
|
+
str += "Records of #{record.year}:\n\n#{record.to_s}"
|
265
|
+
end
|
266
|
+
end
|
267
|
+
|
268
|
+
str
|
269
|
+
end
|
270
|
+
|
271
|
+
def to_html(doc)
|
272
|
+
return nil if empty?
|
273
|
+
|
274
|
+
doc.div {
|
275
|
+
doc.h3('All-time records')
|
276
|
+
@all_time.to_html(doc)
|
277
|
+
@yearly.values.sort{ |r1, r2| r2.year <=> r1.year }.each do |record|
|
278
|
+
puts record.year
|
279
|
+
unless record.empty?
|
280
|
+
doc.h3("Records of #{record.year}")
|
281
|
+
record.to_html(doc)
|
282
|
+
end
|
283
|
+
end
|
284
|
+
}
|
79
285
|
end
|
80
286
|
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
287
|
+
end
|
288
|
+
|
289
|
+
def initialize(activities)
|
290
|
+
@activities = activities
|
291
|
+
@db_dir = activities.db_dir
|
292
|
+
@records_file = File.join(@db_dir, 'records.yml')
|
293
|
+
delete_all_records
|
294
|
+
|
295
|
+
load_records
|
296
|
+
end
|
297
|
+
|
298
|
+
def register_result(activity, sport, distance, duration, start_time)
|
299
|
+
unless @sport_records.include?(sport)
|
300
|
+
Log.info "Ignoring records for activity type '#{sport}' in " +
|
301
|
+
"#{activity.fit_file}"
|
302
|
+
return false
|
87
303
|
end
|
88
304
|
|
89
|
-
|
305
|
+
result = Record.new(activity, sport, distance, duration, start_time)
|
306
|
+
@sport_records[sport].register_result(result)
|
307
|
+
end
|
308
|
+
|
309
|
+
def delete_all_records
|
310
|
+
@sport_records = {}
|
311
|
+
SpeedRecordDistances.keys.each do |sport|
|
312
|
+
@sport_records[sport] = SportRecords.new(sport)
|
313
|
+
end
|
90
314
|
end
|
91
315
|
|
92
|
-
def delete_activity(
|
93
|
-
@
|
316
|
+
def delete_activity(activity)
|
317
|
+
@sport_records.each_value { |r| r.delete_activity(activity) }
|
94
318
|
end
|
95
319
|
|
96
320
|
def sync
|
97
321
|
save_records
|
322
|
+
|
323
|
+
non_empty_records = @sport_records.select { |s, r| !r.empty? }
|
324
|
+
max = non_empty_records.length
|
325
|
+
i = 0
|
326
|
+
non_empty_records.each do |sport, record|
|
327
|
+
output_file = File.join(@activities.cfg[:html_dir],
|
328
|
+
"records-#{i}.html")
|
329
|
+
RecordListPageView.new(@activities, record, max, i).
|
330
|
+
write(output_file)
|
331
|
+
end
|
98
332
|
end
|
99
333
|
|
100
334
|
def to_s
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
|
110
|
-
|
111
|
-
|
112
|
-
|
113
|
-
|
114
|
-
|
115
|
-
|
116
|
-
|
117
|
-
|
118
|
-
|
119
|
-
|
120
|
-
|
121
|
-
|
122
|
-
|
123
|
-
|
124
|
-
|
125
|
-
|
335
|
+
str = ''
|
336
|
+
@sport_records.each do |sport, record|
|
337
|
+
next if record.empty?
|
338
|
+
str += "Records for activity type #{sport}:\n\n#{record.to_s}"
|
339
|
+
end
|
340
|
+
|
341
|
+
str
|
342
|
+
end
|
343
|
+
|
344
|
+
# Iterator for all Record objects that are stored in this data structure.
|
345
|
+
def each(&block)
|
346
|
+
@sport_records.each_value { |r| r.each(&block) }
|
347
|
+
end
|
348
|
+
|
349
|
+
# Return an Array of all the records associated with the given Activity.
|
350
|
+
def activity_records(activity)
|
351
|
+
records = []
|
352
|
+
each do |record|
|
353
|
+
# puts record.activity
|
354
|
+
if record.activity.equal?(activity) && !records.include?(record)
|
355
|
+
records << record
|
356
|
+
end
|
357
|
+
end
|
358
|
+
|
359
|
+
records
|
126
360
|
end
|
127
361
|
|
128
362
|
private
|
@@ -130,25 +364,50 @@ module PostRunner
|
|
130
364
|
def load_records
|
131
365
|
begin
|
132
366
|
if File.exists?(@records_file)
|
133
|
-
@
|
367
|
+
@sport_records = YAML.load_file(@records_file)
|
134
368
|
else
|
135
369
|
Log.info "No records file found at '#{@records_file}'"
|
136
370
|
end
|
137
|
-
rescue
|
371
|
+
rescue IOError
|
138
372
|
Log.fatal "Cannot load records file '#{@records_file}': #{$!}"
|
139
373
|
end
|
140
374
|
|
141
|
-
unless @
|
375
|
+
unless @sport_records.is_a?(Hash)
|
142
376
|
Log.fatal "The personal records file '#{@records_file}' is corrupted"
|
143
377
|
end
|
378
|
+
fit_file_names_to_activity_refs
|
144
379
|
end
|
145
380
|
|
146
381
|
def save_records
|
382
|
+
activity_refs_to_fit_file_names
|
147
383
|
begin
|
148
|
-
|
149
|
-
|
384
|
+
BackedUpFile.open(@records_file, 'w') do |f|
|
385
|
+
f.write(@sport_records.to_yaml)
|
386
|
+
end
|
387
|
+
rescue IOError
|
150
388
|
Log.fatal "Cannot write records file '#{@records_file}': #{$!}"
|
151
389
|
end
|
390
|
+
fit_file_names_to_activity_refs
|
391
|
+
end
|
392
|
+
|
393
|
+
# Convert FIT file names in all Record objects into Activity references.
|
394
|
+
def fit_file_names_to_activity_refs
|
395
|
+
each do |record|
|
396
|
+
# Record objects can be referenced multiple times.
|
397
|
+
if record.activity.is_a?(String)
|
398
|
+
record.activity = @activities.activity_by_fit_file(record.activity)
|
399
|
+
end
|
400
|
+
end
|
401
|
+
end
|
402
|
+
|
403
|
+
# Convert Activity references in all Record objects into FIT file names.
|
404
|
+
def activity_refs_to_fit_file_names
|
405
|
+
each do |record|
|
406
|
+
# Record objects can be referenced multiple times.
|
407
|
+
unless record.activity.is_a?(String)
|
408
|
+
record.activity = record.activity.fit_file
|
409
|
+
end
|
410
|
+
end
|
152
411
|
end
|
153
412
|
|
154
413
|
end
|