Class: PostRunner::FitFileStore
- Inherits:
-
PEROBS::Object
- Object
- PEROBS::Object
- PostRunner::FitFileStore
- Includes:
- DirUtils
- Defined in:
- lib/postrunner/FitFileStore.rb
Overview
The FitFileStore stores all FIT file and provides access to the contained data.
Instance Attribute Summary collapse
-
#store ⇒ Object
readonly
Returns the value of attribute store.
-
#views ⇒ Object
readonly
Returns the value of attribute views.
Instance Method Summary collapse
-
#activities ⇒ Array of FFS_Activity
List of stored activities.
-
#add_fit_file(fit_file_name, fit_entity = nil, overwrite = false) ⇒ FFS_Activity or FFS_Monitoring
Add a file to the store.
-
#change_unit_system ⇒ Object
Perform the necessary report updates after the unit system has been changed.
-
#check ⇒ Object
This methods checks all stored FIT files for correctness, updates all indexes and re-generates all HTML reports.
- #daily_report(day) ⇒ Object
-
#delete_activity(activity) ⇒ Object
Delete an activity from the database.
-
#devices ⇒ Array of FFS_Device
List of registered devices.
-
#find(query) ⇒ Object
Find a specific subset of the activities based on their index.
-
#fit_file_dir(fit_file_base_name, long_uid, type) ⇒ String
Determine the right directory for the given FIT file.
-
#handle_version_update ⇒ Object
Version upgrade logic.
-
#initialize(p) ⇒ FitFileStore
constructor
Create a new FIT file store.
- #list_activities ⇒ Object
-
#monitorings(start_date, end_date) ⇒ Array of Monitoring_B
Read in all Monitoring_B FIT files that overlap with the given interval.
- #monthly_report(day) ⇒ Object
-
#predecessor(activity) ⇒ Object
Return the previous Activity before the provided activity.
-
#ref_by_activity(activity) ⇒ Fixnum
Return the reference index of the given FFS_Activity.
-
#rename_activity(activity, name) ⇒ Object
Rename the specified activity and update all HTML pages that contain the name.
-
#restore ⇒ Object
Setup non-persistent variables.
-
#set_activity_attribute(activity, attribute, value) ⇒ Object
Set the specified attribute of the given activity to a new value.
-
#show_in_browser(html_file) ⇒ Object
Launch a web browser and show an HTML file.
-
#show_list_in_browser ⇒ Object
Show the activity list in a web browser.
-
#successor(activity) ⇒ Object
Return the next Activity after the provided activity.
Methods included from DirUtils
Constructor Details
#initialize(p) ⇒ FitFileStore
Create a new FIT file store.
37 38 39 40 |
# File 'lib/postrunner/FitFileStore.rb', line 37 def initialize(p) super(p) restore end |
Instance Attribute Details
#store ⇒ Object (readonly)
Returns the value of attribute store.
33 34 35 |
# File 'lib/postrunner/FitFileStore.rb', line 33 def store @store end |
#views ⇒ Object (readonly)
Returns the value of attribute views.
33 34 35 |
# File 'lib/postrunner/FitFileStore.rb', line 33 def views @views end |
Instance Method Details
#activities ⇒ Array of FFS_Activity
Returns List of stored activities.
206 207 208 209 210 211 212 |
# File 'lib/postrunner/FitFileStore.rb', line 206 def activities list = [] @store['devices'].each do |id, device| list += device.activities end list.sort end |
#add_fit_file(fit_file_name, fit_entity = nil, overwrite = false) ⇒ FFS_Activity or FFS_Monitoring
Add a file to the store.
77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 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 126 |
# File 'lib/postrunner/FitFileStore.rb', line 77 def add_fit_file(fit_file_name, fit_entity = nil, overwrite = false) # If we the file hasn't been read yet, read it in as a # Fit4Ruby::Activity or Fit4Ruby::Monitoring entity. unless fit_entity return nil unless (fit_entity = read_fit_file(fit_file_name)) end unless [ Fit4Ruby::Activity, Fit4Ruby::Monitoring_B ].include?(fit_entity.class) Log.fatal "Unsupported FIT file type #{fit_entity.class}" end # Generate a String that uniquely identifies the device that generated # the FIT file. id = extract_fit_file_id(fit_entity) long_uid = "#{id[:manufacturer]}-#{id[:product]}-#{id[:serial_number]}" # Make sure the device that created the FIT file is properly registered. device = register_device(long_uid) # Store the FIT entity with the device. entity = device.add_fit_file(fit_file_name, fit_entity, overwrite) # The FIT file might be already stored or invalid. In that case we # abort this method. return nil unless entity if fit_entity.is_a?(Fit4Ruby::Activity) @store['records'].scan_activity_for_records(entity) # Generate HTML file for this activity. entity.generate_html_report # The HTML activity views contain links to their predecessors and # successors. After inserting a new activity, we need to re-generate # these views as well. if (pred = predecessor(entity)) pred.generate_html_report end if (succ = successor(entity)) succ.generate_html_report end # And update the index pages generate_html_index_pages end Log.info "#{File.basename(fit_file_name)} " + 'has been successfully added to archive' entity end |
#change_unit_system ⇒ Object
Perform the necessary report updates after the unit system has been changed.
175 176 177 178 179 180 181 182 183 |
# File 'lib/postrunner/FitFileStore.rb', line 175 def change_unit_system # If we have changed the unit system we need to re-generate all HTML # reports. activities.each do |activity| activity.generate_html_report end @store['records'].generate_html_reports generate_html_index_pages end |
#check ⇒ Object
This methods checks all stored FIT files for correctness, updates all indexes and re-generates all HTML reports.
304 305 306 307 308 309 310 311 312 313 314 315 |
# File 'lib/postrunner/FitFileStore.rb', line 304 def check records = @store['records'] records.delete_all_records activities.sort do |a1, a2| a1. <=> a2. end.each do |a| a.check records.scan_activity_for_records(a) end records.generate_html_reports generate_html_index_pages end |
#daily_report(day) ⇒ Object
339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 |
# File 'lib/postrunner/FitFileStore.rb', line 339 def daily_report(day) # 'day' specifies the current day. But we don't know what timezone the # watch was set to for a given date. The files are always named after # the moment of finishing the recording expressed as GMT time. # Each file contains information about the time zone for the specific # file. Recording is always flipped to a new file at midnight GMT but # there are usually multiple files per GMT day. day_as_time = Time.parse(day).gmtime # To get weekly intensity minutes we need 7 days of data prior to the # current date and 1 day after to include the following night. We add # at least 12 extra hours to accomodate time zone changes. monitoring_files = monitorings(day_as_time - 8 * 24 * 60 * 60, day_as_time + 36 * 60 * 60) puts MonitoringStatistics.new(monitoring_files).daily(day) end |
#delete_activity(activity) ⇒ Object
Delete an activity from the database. It will only delete the entry in the database. The original activity file will not be deleted from the file system.
132 133 134 135 136 137 138 139 140 141 142 143 144 145 |
# File 'lib/postrunner/FitFileStore.rb', line 132 def delete_activity(activity) pred = predecessor(activity) succ = successor(activity) activity.device.delete_activity(activity) # The HTML activity views contain links to their predecessors and # successors. After deleting an activity, we need to re-generate these # views. pred.generate_html_report if pred succ.generate_html_report if succ generate_html_index_pages end |
#devices ⇒ Array of FFS_Device
Returns List of registered devices.
201 202 203 |
# File 'lib/postrunner/FitFileStore.rb', line 201 def devices @store['devices'] end |
#find(query) ⇒ Object
Find a specific subset of the activities based on their index.
261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 |
# File 'lib/postrunner/FitFileStore.rb', line 261 def find(query) case query when /\A-?\d+$\z/ index = query.to_i # The UI counts the activities from 1 to N. Ruby counts from 0 - # (N-1). if index <= 0 Log.error 'Index must be larger than 0' return [] end # The UI counts the activities from 1 to N. Ruby counts from 0 - # (N-1). if (a = activities[index - 1]) return [ a ] end when /\A-?\d+--?\d+\z/ idxs = query.match(/(?<sidx>-?\d+)-(?<eidx>-?[0-9]+)/) if (sidx = idxs['sidx'].to_i) <= 0 Log.error 'Start index must be larger than 0' return [] end if (eidx = idxs['eidx'].to_i) <= 0 Log.error 'End index must be larger than 0' return [] end if eidx < sidx Log.error 'Start index must be smaller than end index' return [] end # The UI counts the activities from 1 to N. Ruby counts from 0 - # (N-1). unless (as = activities[(sidx - 1)..(eidx - 1)]).empty? return as end else Log.error "Invalid activity query: #{query}" end [] end |
#fit_file_dir(fit_file_base_name, long_uid, type) ⇒ String
Determine the right directory for the given FIT file. The resulting path looks something like /home/user/.postrunner/devices/garmin-fenix3-1234/ activity/5A.
191 192 193 194 195 196 |
# File 'lib/postrunner/FitFileStore.rb', line 191 def fit_file_dir(fit_file_base_name, long_uid, type) # The first letter of the FIT file specifies the creation year. # The second letter of the FIT file specifies the creation month. File.join(@store['config']['devices_dir'], long_uid, type, fit_file_base_name[0..1]) end |
#handle_version_update ⇒ Object
Version upgrade logic.
67 68 69 |
# File 'lib/postrunner/FitFileStore.rb', line 67 def handle_version_update # Nothing here so far. end |
#list_activities ⇒ Object
324 325 326 |
# File 'lib/postrunner/FitFileStore.rb', line 324 def list_activities puts ActivityListView.new(self).to_s end |
#monitorings(start_date, end_date) ⇒ Array of Monitoring_B
Read in all Monitoring_B FIT files that overlap with the given interval.
218 219 220 221 222 223 224 225 226 227 228 |
# File 'lib/postrunner/FitFileStore.rb', line 218 def monitorings(start_date, end_date) monitorings = [] @store['devices'].each do |id, device| monitorings += device.monitorings(start_date.gmtime, end_date.gmtime) end monitorings.reverse.map do |m| read_fit_file(File.join(fit_file_dir(m.fit_file_name, m.device.long_uid, 'monitor'), m.fit_file_name)) end end |
#monthly_report(day) ⇒ Object
356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 |
# File 'lib/postrunner/FitFileStore.rb', line 356 def monthly_report(day) monitorings = [] # 'day' specifies the current month. It must be in the form of # YYYY-MM-01. But we don't know what timezone the watch was set to for a # given date. The files are always named after the moment of finishing # the recording expressed as GMT time. Each file contains information # about the time zone for the specific file. Recording is always flipped # to a new file at midnight GMT but there are usually multiple files per # GMT day. day_as_time = Time.parse(day).gmtime # To get weekly intensity minutes we need 7 days of data prior to the # current month start and 1 after to inclide the following night. We add # at least 12 extra hours to accomondate time zone changes. monitoring_files = monitorings(day_as_time - 8 * 24 * 60 * 60, day_as_time + 32 * 24 * 60 * 60) puts MonitoringStatistics.new(monitoring_files).monthly(day) end |
#predecessor(activity) ⇒ Object
Return the previous Activity before the provided activity. If none is found, return nil.
251 252 253 254 255 256 257 |
# File 'lib/postrunner/FitFileStore.rb', line 251 def predecessor(activity) all_activities = activities idx = all_activities.index(activity) return nil if idx.nil? # Activities indexes are reversed. The predecessor has a higher index. all_activities[idx + 1] end |
#ref_by_activity(activity) ⇒ Fixnum
Return the reference index of the given FFS_Activity.
234 235 236 237 238 |
# File 'lib/postrunner/FitFileStore.rb', line 234 def ref_by_activity(activity) return nil unless (idx = activities.index(activity)) idx + 1 end |
#rename_activity(activity, name) ⇒ Object
Rename the specified activity and update all HTML pages that contain the name.
151 152 153 154 155 |
# File 'lib/postrunner/FitFileStore.rb', line 151 def rename_activity(activity, name) activity.set('name', name) generate_html_index_pages @store['records'].generate_html_reports if activity.has_records? end |
#restore ⇒ Object
Setup non-persistent variables.
43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 |
# File 'lib/postrunner/FitFileStore.rb', line 43 def restore @data_dir = @store['config']['data_dir'] # Ensure that we have an Array in the store to hold all known devices. @store['devices'] = @store.new(PEROBS::Hash) unless @store['devices'] @devices_dir = File.join(@data_dir, 'devices') # It's generally not a good idea to store absolute file names in the # database. We'll make an exception here as this is the only way to # propagate this path to FFS_Activity or FFS_Monitoring objects. The # store entry is updated on each program run, so the DB can be moved # safely to another directory. @store['config']['devices_dir'] = @devices_dir create_directory(@devices_dir, 'devices') # Define which View objects the HTML output will consist of. This # doesn't really belong in this class but for now it's the best place # to put it. @views = ViewButtons.new([ NavButtonDef.new('activities.png', 'index.html'), NavButtonDef.new('record.png', "records-0.html") ]) end |
#set_activity_attribute(activity, attribute, value) ⇒ Object
Set the specified attribute of the given activity to a new value.
161 162 163 164 165 166 167 168 169 170 171 |
# File 'lib/postrunner/FitFileStore.rb', line 161 def set_activity_attribute(activity, attribute, value) activity.set(attribute, value) case attribute when 'norecord', 'type' # If we have changed a norecord setting or an activity type, we need # to regenerate all reports and re-collect the record list since we # don't know which Activity needs to replace the changed one. check end generate_html_index_pages end |
#show_in_browser(html_file) ⇒ Object
Launch a web browser and show an HTML file.
330 331 332 333 334 335 336 337 |
# File 'lib/postrunner/FitFileStore.rb', line 330 def show_in_browser(html_file) cmd = "#{ENV['BROWSER'] || 'firefox'} \"#{html_file}\" &" unless system(cmd) Log.fatal "Failed to execute the following shell command: #{$cmd}\n" + "#{$!}" end end |
#show_list_in_browser ⇒ Object
Show the activity list in a web browser.
318 319 320 321 322 |
# File 'lib/postrunner/FitFileStore.rb', line 318 def show_list_in_browser generate_html_index_pages @store['records'].generate_html_reports show_in_browser(File.join(@store['config']['html_dir'], 'index.html')) end |
#successor(activity) ⇒ Object
Return the next Activity after the provided activity. Note that this has a lower index. If none is found, return nil.
242 243 244 245 246 247 |
# File 'lib/postrunner/FitFileStore.rb', line 242 def successor(activity) all_activities = activities idx = all_activities.index(activity) return nil if idx.nil? || idx == 0 all_activities[idx - 1] end |