class PostRunner::ActivitiesDB
Attributes
Public Class Methods
# File lib/postrunner/ActivitiesDB.rb, line 29 def initialize(db_dir, cfg) @db_dir = db_dir @cfg = cfg @fit_dir = File.join(@db_dir, 'fit') @archive_file = File.join(@db_dir, 'archive.yml') @auxilliary_dirs = %w( icons jquery flot openlayers postrunner ) create_directories begin if File.exist?(@archive_file) if RUBY_VERSION >= '3.1.0' # Since Ruby 3.1.0 YAML does not load unknown classes unless # explicitely listed. @activities = YAML.load_file( @archive_file, permitted_classes: [ PostRunner::Activity, Time ]) else @activities = YAML.load_file(@archive_file) end else @activities = [] end rescue RuntimeError Log.fatal "Cannot load archive file '#{@archive_file}': #{$!}" end unless @activities.is_a?(Array) Log.fatal "The archive file '#{@archive_file}' is corrupted" end # Not all instance variables of Activity are stored in the file. The # normal constructor is not run during YAML::load_file. We have to # initialize those instance variables in a secondary step. sync_needed = false @activities.each do |a| a.late_init(self) # If the Activity has the data from the FIT file loaded, a value was # missing in the YAML file. Set the sync flag so we can update the # YAML file once we have checked all Activities. sync_needed |= !a.fit_activity.nil? end # Define which View objects the HTML output will contain off. This # doesn't really belong in ActivitiesDB 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") ]) sync if sync_needed end
Public Instance Methods
# File lib/postrunner/ActivitiesDB.rb, line 200 def activity_by_fit_file(fit_file) @activities.find { |a| a.fit_file == fit_file } end
Add a new FIT file to the database. @param fit_file_name [String] Name of the FIT file. @param fit_activity [Activity] Activity
to add @return [TrueClass or FalseClass] True if the file could be added. False otherwise.
# File lib/postrunner/ActivitiesDB.rb, line 113 def add(fit_file_name, fit_activity) base_fit_file_name = File.basename(fit_file_name) if @activities.find { |a| a.fit_file == base_fit_file_name } Log.debug "Activity #{fit_file_name} is already included in the archive" return false end if File.exist?(File.join(@fit_dir, base_fit_file_name)) Log.debug "Activity #{fit_file_name} has been deleted before" return false end begin FileUtils.cp(fit_file_name, @fit_dir) rescue StandardError Log.fatal "Cannot copy #{fit_file_name} into #{@fit_dir}: #{$!}" end @activities << (activity = Activity.new(self, base_fit_file_name, fit_activity)) @activities.sort! do |a1, a2| a2.timestamp <=> a1.timestamp end activity.register_records # Generate HTML file for this activity. activity.generate_html_view # 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(activity)) pred.generate_html_view end if (succ = successor(activity)) succ.generate_html_view end sync Log.info "#{fit_file_name} successfully added to archive" true end
# File lib/postrunner/ActivitiesDB.rb, line 94 def backup_archive begin if File.exist?(@archive_file) backup_file = @archive_file + '.bak' if File.exist?(backup_file) File.delete(backup_file) end File.rename(@archive_file, backup_file) end rescue IOError Log.error "Cannot backup #{@archive_file}: #{$!}" end end
Ensure that all necessary directories are present to store the output files. This method is idempotent and can be called even when directories exist already.
# File lib/postrunner/ActivitiesDB.rb, line 84 def create_directories @cfg.create_directory(@db_dir, 'data') @cfg.create_directory(@fit_dir, 'fit') @cfg.create_directory(@cfg[:html_dir], 'html') @auxilliary_dirs.each do |dir| create_auxdir(dir) end end
# File lib/postrunner/ActivitiesDB.rb, line 159 def delete(activity) pred = predecessor(activity) succ = successor(activity) @activities.delete(activity) # The HTML activity views contain links to their predecessors and # successors. After deleting an activity, we need to re-generate these # views as well. pred.generate_html_view if pred succ.generate_html_view if succ sync end
# File lib/postrunner/ActivitiesDB.rb, line 204 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). index -= 1 if index > 0 if (a = @activities[index]) return [ a ] end when /\A-?\d+--?\d+\z/ idxs = query.match(/(?<sidx>-?\d+)-(?<eidx>-?[0-9]+)/) sidx = idxs['sidx'].to_i eidx = idxs['eidx'].to_i # The UI counts the activities from 1 to N. Ruby counts from 0 - # (N-1). sidx -= 1 if sidx > 0 eidx -= 1 if eidx > 0 unless (as = @activities[sidx..eidx]).empty? return as end else Log.error "Invalid activity query: #{query}" end [] end
This method can be called to re-generate all HTML reports and all HTML index files.
# File lib/postrunner/ActivitiesDB.rb, line 304 def generate_all_html_reports Log.info "Re-generating all HTML report files..." # Generate HTML views for all activities in the DB. @activities.each { |a| a.generate_html_view } Log.info "All HTML report files have been re-generated." # (Re-)generate index files. ActivityListView.new(self).update_index_pages Log.info "HTML index files have been updated." end
Take all necessary steps to convert user data to match an updated PostRunner
version.
# File lib/postrunner/ActivitiesDB.rb, line 316 def handle_version_update # An updated version may bring new auxilliary directories. We remove the # old directories and create new copies. Log.warn('Removing old HTML auxilliary directories') @auxilliary_dirs.each do |dir| auxdir = File.join(@cfg[:html_dir], dir) FileUtils.rm_rf(auxdir) end create_directories Log.warn('Updating HTML files...') generate_all_html_reports end
# File lib/postrunner/ActivitiesDB.rb, line 287 def list puts ActivityListView.new(self).to_s end
# File lib/postrunner/ActivitiesDB.rb, line 249 def map_to_files(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). index -= 1 if index > 0 if (a = @activities[index]) return [ File.join(@fit_dir, a.fit_file) ] end when /\A-?\d+--?\d+\z/ idxs = query.match(/(?<sidx>-?\d+)-(?<eidx>-?[0-9]+)/) sidx = idxs['sidx'].to_i eidx = idxs['eidx'].to_i # The UI counts the activities from 1 to N. Ruby counts from 0 - # (N-1). sidx -= 1 if sidx > 0 eidx -= 1 if eidx > 0 unless (as = @activities[sidx..eidx]).empty? files = [] as.each do |a| files << File.join(@fit_dir, a.fit_file) end return files end else Log.error "Invalid activity query: #{query}" end [] end
Return the previous Activity
before the provided activity. If none is found, return nil.
# File lib/postrunner/ActivitiesDB.rb, line 242 def predecessor(activity) idx = @activities.index(activity) return nil if idx.nil? # Activities indexes are reversed. The predecessor has a higher index. @activities[idx + 1] end
# File lib/postrunner/ActivitiesDB.rb, line 190 def ref_by_fit_file(fit_file) i = 1 @activities.each do |activity| return i if activity.fit_file == fit_file i += 1 end nil end
# File lib/postrunner/ActivitiesDB.rb, line 174 def rename(activity, name) activity.rename(name) sync end
# File lib/postrunner/ActivitiesDB.rb, line 179 def set(activity, attribute, value) activity.set(attribute, value) if %w( norecord type ).include?(attribute) # 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 sync end
Launch a web browser and show an HTML file. @param html_file [String] file name of the HTML file to show
# File lib/postrunner/ActivitiesDB.rb, line 293 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 the activity list in a web browser.
# File lib/postrunner/ActivitiesDB.rb, line 282 def show_list_in_browser ActivityListView.new(self).update_index_pages show_in_browser(File.join(@cfg[:html_dir], 'index.html')) end
Return the next Activity
after the provided activity. Note that this has a lower index. If none is found, return nil.
# File lib/postrunner/ActivitiesDB.rb, line 234 def successor(activity) idx = @activities.index(activity) return nil if idx.nil? || idx == 0 @activities[idx - 1] end
Private Instance Methods
# File lib/postrunner/ActivitiesDB.rb, line 344 def create_auxdir(dir) # This file should be in lib/postrunner. The 'misc' directory should be # found in '../../misc'. misc_dir = File.realpath(File.join(File.dirname(__FILE__), '..', '..', 'misc')) unless Dir.exist?(misc_dir) Log.fatal "Cannot find 'misc' directory under '#{misc_dir}': #{$!}" end src_dir = File.join(misc_dir, dir) unless Dir.exist?(src_dir) Log.fatal "Cannot find '#{src_dir}': #{$!}" end dst_dir = @cfg[:html_dir] begin #FileUtils.ln_s(src_dir, dst_dir) FileUtils.cp_r(src_dir, dst_dir) rescue IOError Log.fatal "Cannot copy auxilliary data directory '#{dst_dir}': #{$!}" end end
# File lib/postrunner/ActivitiesDB.rb, line 332 def sync begin BackedUpFile.open(@archive_file, 'w') do |f| f.write(@activities.to_yaml) end rescue StandardError Log.fatal "Cannot write archive file '#{@archive_file}': #{$!}" end ActivityListView.new(self).update_index_pages end