diff options
-rwxr-xr-x | bin/postrunner | 5 | ||||
-rw-r--r-- | lib/postrunner/ActivitiesDB.rb | 35 | ||||
-rw-r--r-- | lib/postrunner/Activity.rb | 53 | ||||
-rw-r--r-- | lib/postrunner/Main.rb | 43 | ||||
-rw-r--r-- | lib/postrunner/RuntimeConfig.rb | 20 | ||||
-rw-r--r-- | postrunner.gemspec | 8 |
6 files changed, 148 insertions, 16 deletions
diff --git a/bin/postrunner b/bin/postrunner new file mode 100755 index 0000000..988c5c5 --- /dev/null +++ b/bin/postrunner @@ -0,0 +1,5 @@ +#!/usr/bin/env ruby + +$:.unshift File.join(File.dirname(__FILE__), '..', 'lib') +require File.basename(__FILE__) + diff --git a/lib/postrunner/ActivitiesDB.rb b/lib/postrunner/ActivitiesDB.rb index df0c105..c8785fb 100644 --- a/lib/postrunner/ActivitiesDB.rb +++ b/lib/postrunner/ActivitiesDB.rb @@ -8,6 +8,8 @@ module PostRunner class ActivitiesDB + include Fit4Ruby::Converters + def initialize(db_dir) @db_dir = db_dir @fit_dir = File.join(@db_dir, 'fit') @@ -21,24 +23,28 @@ module PostRunner @activities = [] end rescue - Log.fatal "Cannot load archive file #{@archive_file}: #{$!}" + Log.fatal "Cannot load archive file '#{@archive_file}': #{$!}" end else create_directories @activities = [] end + + unless @activities.is_a?(Array) + Log.fatal "The archive file '#{@archive_file}' is corrupted" + end end def add(fit_file) base_fit_file = File.basename(fit_file) if @activities.find { |a| a.fit_file == base_fit_file } - Log.warn "Activity #{fit_file} is already included in the archive" + Log.debug "Activity #{fit_file} is already included in the archive" return false end begin fit_activity = Fit4Ruby.read(fit_file) - rescue + rescue Fit4Ruby::Error Log.error "Cannot read #{fit_file}: #{$!}" return false end @@ -50,12 +56,27 @@ module PostRunner end @activities << Activity.new(base_fit_file, fit_activity) + @activities.sort! do |a1, a2| + a2.start_time <=> a1.start_time + end sync Log.info "#{fit_file} successfully added to archive" true end + + def rename(fit_file, name) + base_fit_file = File.basename(fit_file) + @activities.each do |a| + if a.fit_file == base_fit_file + a.name = name + sync + break + end + end + end + def map_to_files(query) case query when /\A-?\d+$\z/ @@ -74,7 +95,6 @@ module PostRunner # (N-1). sidx -= 1 if sidx > 0 eidx -= 1 if eidx > 0 - puts "iv: #{sidx} - #{eidx}" unless (as = @activities[sidx..eidx]).empty? files = [] as.each do |a| @@ -93,7 +113,12 @@ module PostRunner i = 0 @activities.each do |a| i += 1 - puts "#{"%4d" % i} #{"%12s" % a.fit_file} #{a.start_time}" + puts "#{'%4d' % i} " + + "#{'%-20s' % a.name[0..19]} " + + "#{'%22s' % a.start_time.strftime("%a, %Y %b %d %H:%M")} " + + "#{'%7.2f' % (a.distance / 1000)} " + + "#{'%8s' % secsToHMS(a.duration)} " + + "#{'%5s' % speedToPace(a.avg_speed)} " end end diff --git a/lib/postrunner/Activity.rb b/lib/postrunner/Activity.rb index 96c9887..964a702 100644 --- a/lib/postrunner/Activity.rb +++ b/lib/postrunner/Activity.rb @@ -1,14 +1,61 @@ require 'fit4ruby' +require 'postrunner/RuntimeConfig' module PostRunner class Activity - attr_reader :fit_file, :start_time + attr_reader :fit_file + attr_accessor :name - def initialize(fit_file, fit_activity) + # This is a list of variables that provide data from the fit file. To + # speed up access to it, we cache the data in the activity database. + @@CachedVariables = %w( start_time distance duration avg_speed ) + + def initialize(fit_file, fit_activity, name = nil) @fit_file = fit_file - @start_time = fit_activity.start_time + @fit_activity = fit_activity + @name = name || fit_file + + @@CachedVariables.each do |v| + v_str = "@#{v}" + instance_variable_set(v_str, fit_activity.send(v)) + self.class.send(:attr_reader, v.to_sym) + end + end + + def yaml_initialize(tag, value) + # Create attr_readers for cached variables. + @@CachedVariables.each { |v| self.class.send(:attr_reader, v.to_sym) } + + # Load all attributes and assign them to instance variables. + value.each do |a, v| + instance_variable_set("@" + a, v) + end + # Use the FIT file name as activity name if none has been set yet. + @name = @fit_file unless @name + end + + def encode_with(coder) + attr_ignore = %w( @fit_activity ) + + instance_variables.each do |v| + v = v.to_s + next if attr_ignore.include?(v) + + coder[v[1..-1]] = instance_variable_get(v) + end + end + + def method_missing(method_name, *args, &block) + fit_file = File.join(Config['fit_dir'], @fit_file) + begin + @fit_activity = Fit4Ruby.read(fit_file) unless @fit_activity + rescue Fit4Ruby::Error + Log.error "Cannot read #{fit_file}: #{$!}" + return false + end + @fit_activity.send(method_name, *args, &block) end end diff --git a/lib/postrunner/Main.rb b/lib/postrunner/Main.rb index 26e1005..4e2f020 100644 --- a/lib/postrunner/Main.rb +++ b/lib/postrunner/Main.rb @@ -1,6 +1,7 @@ require 'optparse' require 'logger' require 'fit4ruby' +require 'postrunner/RuntimeConfig' require 'postrunner/ActivitiesDB' module PostRunner @@ -11,6 +12,7 @@ module PostRunner def initialize(args) @filter = nil + @name = nil @activities = ActivitiesDB.new(File.join(ENV['HOME'], '.postrunner')) execute_command(parse_options(args)) @@ -42,6 +44,18 @@ module PostRunner @filter.field_names = [] unless @filter.field_names @filter.field_names << n end + opts.on('--filter-undef', + "Don't show fields with undefined values") do + @filter = Fit4Ruby::FitFilter.new unless @filter + @filter.ignore_undef = true + end + + opts.separator "Options for the 'import' and 'rename' command:" + + opts.on('--name name', String, + 'Name or rename the activity to the specified name') do |n| + @name = n + end end parser.parse!(args) @@ -50,7 +64,7 @@ module PostRunner def execute_command(args) case args.shift when 'check' - process_files(args, :check) + process_files_or_activities(args, :check) when 'dump' @filter = Fit4Ruby::FitFilter.new unless @filter process_files_or_activities(args, :dump) @@ -58,8 +72,10 @@ module PostRunner process_files(args, :import) when 'list' @activities.list + when 'rename' + process_activities(args, :rename) when 'summary' - process_files(args, :summary) + process_files_or_activities(args, :summary) else Log.fatal("No command provided") end @@ -76,11 +92,27 @@ module PostRunner process_files(files, command) else - process_files(foa, command) + process_files([ foa ], command) end end end + def process_activities(activity_files, command) + activity_files.each do |a| + if a[0] == ':' + files = @activities.map_to_files(a[1..-1]) + if files.empty? + Log.warn "No matching activities found for '#{a}'" + return + end + process_files(files, command) + else + Log.fatal "Activity references must start with ':': #{a}" + end + end + + end + def process_files(files_or_dirs, command) if files_or_dirs.empty? Log.fatal("You must provide at least one .FIT file name") @@ -98,8 +130,11 @@ module PostRunner end def process_file(file, command) - if command == :import + case command + when :import @activities.add(file) + when :rename + @activities.rename(file, @name) else begin activity = Fit4Ruby::read(file, @filter) diff --git a/lib/postrunner/RuntimeConfig.rb b/lib/postrunner/RuntimeConfig.rb new file mode 100644 index 0000000..2d5dccb --- /dev/null +++ b/lib/postrunner/RuntimeConfig.rb @@ -0,0 +1,20 @@ +module PostRunner + + class RuntimeConfig + + def initialize + @settings = {} + @settings['data_dir'] = File.join(ENV['HOME'], '.postrunner') + @settings['fit_dir'] = File.join(@settings['data_dir'], 'fit') + end + + def [](key) + @settings[key] + end + + end + + Config = RuntimeConfig.new + +end + diff --git a/postrunner.gemspec b/postrunner.gemspec index cc6c537..a63343e 100644 --- a/postrunner.gemspec +++ b/postrunner.gemspec @@ -5,20 +5,20 @@ require 'postrunner/version' Gem::Specification.new do |spec| spec.name = "postrunner" - spec.version = Postrunner::VERSION + spec.version = PostRunner::VERSION spec.authors = ["Chris Schlaeger"] spec.email = ["chris@linux.com"] spec.summary = %q{Application to manage and analyze Garmin FIT files.} 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 or may not work. Only devices that expose themselves as USB Mass Storage devices are supported.} - spec.homepage = "TBD" - spec.license = "MIT" + spec.homepage = 'https://github.com/scrapper/fit4ruby' + spec.license = "GNU GPL version 2" spec.files = `git ls-files -z`.split("\x0") spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) } spec.test_files = spec.files.grep(%r{^(test|spec|features)/}) spec.require_paths = ["lib"] + spec.required_ruby_version = '>=2.0' spec.add_development_dependency "bundler", "~> 1.6" spec.add_development_dependency "rake" - spec.add_development_dependency "fit4ruby" end |