diff options
author | Chris Schlaeger <chris@linux.com> | 2016-01-17 05:46:08 +0100 |
---|---|---|
committer | Chris Schlaeger <chris@linux.com> | 2016-01-17 05:46:08 +0100 |
commit | ed1d5cde7f92d9d7c7de28aa4b516da3e0baf8e5 (patch) | |
tree | c6d9eb0720c260cad354d152d7e8c6633458773c | |
parent | 998bc46bdf995963a237fccf51e75317d08cd608 (diff) | |
download | postrunner-ed1d5cde7f92d9d7c7de28aa4b516da3e0baf8e5.zip |
Wrapping up switch to PEROBS as database back-end.
-rw-r--r-- | lib/postrunner/FFS_Activity.rb | 49 | ||||
-rw-r--r-- | lib/postrunner/FFS_Device.rb | 36 | ||||
-rw-r--r-- | lib/postrunner/FitFileStore.rb | 32 | ||||
-rw-r--r-- | lib/postrunner/Main.rb | 13 | ||||
-rw-r--r-- | lib/postrunner/PersonalRecords.rb | 53 | ||||
-rw-r--r-- | postrunner.gemspec | 6 | ||||
-rw-r--r-- | spec/PostRunner_spec.rb | 59 | ||||
-rw-r--r-- | spec/spec_helper.rb | 7 |
8 files changed, 160 insertions, 95 deletions
diff --git a/lib/postrunner/FFS_Activity.rb b/lib/postrunner/FFS_Activity.rb index 309db1e..3921301 100644 --- a/lib/postrunner/FFS_Activity.rb +++ b/lib/postrunner/FFS_Activity.rb @@ -101,37 +101,34 @@ module PostRunner attr_reader :fit_activity # Create a new FFS_Activity object. - # @param store [PEROBS::Store] The data base + # @param p [PEROBS::Handle] PEROBS handle # @param fit_file_name [String] The fully qualified file name of the FIT # file to add # @param fit_entity [Fit4Ruby::FitEntity] The content of the loaded FIT # file - def initialize(store, device = nil, fit_file_name = nil, fit_entity = nil) - super(store) - init_attr(:device, device) - init_attr(:fit_file_name, fit_file_name ? - File.basename(fit_file_name) : nil) - init_attr(:name, fit_file_name ? File.basename(fit_file_name) : nil) - init_attr(:norecord, false) + def initialize(p, device, fit_file_name, fit_entity) + super(p) + + self.device = device + self.fit_file_name = fit_file_name ? File.basename(fit_file_name) : nil + self.name = fit_file_name ? File.basename(fit_file_name) : nil + self.norecord = false if (@fit_activity = fit_entity) - init_attr(:timestamp, fit_entity.timestamp) - init_attr(:total_timer_time, fit_entity.total_timer_time) - init_attr(:sport, fit_entity.sport) - init_attr(:sub_sport, fit_entity.sub_sport) - init_attr(:total_distance, fit_entity.total_distance) - init_attr(:avg_speed, fit_entity.avg_speed) + self.timestamp = fit_entity.timestamp + self.total_timer_time = fit_entity.total_timer_time + self.sport = fit_entity.sport + self.sub_sport = fit_entity.sub_sport + self.total_distance = fit_entity.total_distance + self.avg_speed = fit_entity.avg_speed end end - def post_restore - raise RuntimeError unless @device.is_a?(PEROBS::ObjectBase) - end - # Store a copy of the given FIT file in the corresponding directory. # @param fit_file_name [String] Fully qualified name of the FIT file. def store_fit_file(fit_file_name) # Get the right target directory for this particular FIT file. - dir = fit_file_dir(File.basename(fit_file_name)) + dir = @store['file_store'].fit_file_dir(File.basename(fit_file_name), + @device.long_uid, 'activity') # Create the necessary directories if they don't exist yet. create_directory(dir, 'Device activity diretory') @@ -278,7 +275,9 @@ module PostRunner def load_fit_file(filter = nil) return if @fit_activity - fit_file = File.join(fit_file_dir(@fit_file_name), @fit_file_name) + dir = @store['file_store'].fit_file_dir(@fit_file_name, + @device.long_uid, 'activity') + fit_file = File.join(dir, @fit_file_name) begin @fit_activity = Fit4Ruby.read(fit_file, filter) rescue Fit4Ruby::Error @@ -292,16 +291,6 @@ module PostRunner private - # Determine the right directory for the given FIT file. The resulting path - # looks something like /home/user/.postrunner/devices/garmin-fenix3-1234/ - # activity/5A. - def fit_file_dir(fit_file_base_name) - # The first letter of the FIT file specifies the creation year. - # The second letter of the FIT file specifies the creation month. - dir = File.join(@store['config']['devices_dir'], @device.long_uid, - 'activity', fit_file_base_name[0..1]) - end - end end diff --git a/lib/postrunner/FFS_Device.rb b/lib/postrunner/FFS_Device.rb index 47c473b..5cde333 100644 --- a/lib/postrunner/FFS_Device.rb +++ b/lib/postrunner/FFS_Device.rb @@ -22,19 +22,24 @@ module PostRunner # dashes. All objects are transparently stored in the PEROBS::Store. class FFS_Device < PEROBS::Object - po_attr :activities, :monitors, :short_uid, :long_uid + po_attr :activities, :monitorings, :short_uid, :long_uid # Create a new FFS_Device object. - # @param store [PEROBS::Store] The store to persist the data + # @param cf [PEROBS::ConstructorForm] cf # @param short_uid [Fixnum] A random number used a unique ID # @param long_uid [String] A string consisting of the manufacturer and # product name and the serial number. - def initialize(store, short_uid = nil, long_uid = nil) - super(store) - init_attr(:short_uid, short_uid) - init_attr(:long_uid, long_uid) - init_attr(:activities, @store.new(PEROBS::Array)) - init_attr(:monitorings, @store.new(PEROBS::Array)) + def initialize(cf, short_uid, long_uid) + super(cf) + self.short_uid = short_uid + self.long_uid = long_uid + restore + end + + # Handle initialization of persistent attributes. + def restore + attr_init(:activities) { @store.new(PEROBS::Array) } + attr_init(:monitorings) { @store.new(PEROBS::Array) } end # Add a new FIT file for this device. @@ -49,10 +54,12 @@ module PostRunner when Fit4Ruby::Activity.class entity = activity_by_file_name(File.basename(fit_file_name)) entities = @activities + type = 'activity' new_entity_class = FFS_Activity when Fit4Ruby::Monitoring.class entity = monitoring_by_file_name(File.basename(fit_file_name)) entities = @monitorings + type = 'monitoring' new_entity_class = FFS_Monitoring else Log.fatal "Unsupported FIT entity #{fit_entity.class}" @@ -69,6 +76,13 @@ module PostRunner return nil end else + # Don't add the entity if has deleted before and overwrite isn't true. + path = @store['file_store'].fit_file_dir(File.basename(fit_file_name), + long_uid, type) + fq_fit_file_name = File.join(path, File.basename(fit_file_name)) + if File.exists?(fq_fit_file_name) && !overwrite + return nil + end # Add the new file to the list. entity = @store.new(new_entity_class, myself, fit_file_name, fit_entity) end @@ -86,6 +100,12 @@ module PostRunner entity end + # Delete the given activity from the activity list. + # @param activity [FFS_Activity] activity to delete + def delete_activity(activity) + @activities.delete(activity) + end + # Return the activity with the given file name. # @param file_name [String] Base name of the fit file. # @return [FFS_Activity] Corresponding FFS_Activity or nil. diff --git a/lib/postrunner/FitFileStore.rb b/lib/postrunner/FitFileStore.rb index c52d5a5..09bf3ca 100644 --- a/lib/postrunner/FitFileStore.rb +++ b/lib/postrunner/FitFileStore.rb @@ -32,11 +32,15 @@ module PostRunner attr_reader :store, :views # Create a new FIT file store. - # @param store [PEROBS::Store] Data store - # @param cfg [RuntimeConfig] Runtime configuration data - def initialize(store) - super - @data_dir = store['config']['data_dir'] + # @param p [PEROBS::Handle] PEROBS handle + def initialize(p) + super(p) + restore + end + + # Setup non-persistent variables. + 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'] @@ -123,7 +127,7 @@ module PostRunner pred = predecessor(activity) succ = successor(activity) - activity.device.activities.delete(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 @@ -170,6 +174,20 @@ module PostRunner @store['records'].generate_html_reports generate_html_index_pages end + # Determine the right directory for the given FIT file. The resulting path + # looks something like /home/user/.postrunner/devices/garmin-fenix3-1234/ + # activity/5A. + # @param fit_file_base_name [String] The base name of the fit file + # @param long_uid [String] the long UID of the device + # @param type [String] 'activity' or 'monitoring' + # @return [String] the full path name of the archived FIT file + 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 + # @return [Array of FFS_Device] List of registered devices. @@ -340,7 +358,7 @@ module PostRunner def generate_html_index_pages # Ensure that HTML index is up-to-date. - ActivityListView.new(self).update_index_pages + ActivityListView.new(myself).update_index_pages end end diff --git a/lib/postrunner/Main.rb b/lib/postrunner/Main.rb index 29b8e47..75af05b 100644 --- a/lib/postrunner/Main.rb +++ b/lib/postrunner/Main.rb @@ -33,6 +33,7 @@ module PostRunner def initialize(args) @filter = nil @name = nil + @force = false @attribute = nil @value = nil @db_dir = File.join(ENV['HOME'], '.postrunner') @@ -41,6 +42,9 @@ module PostRunner create_directory(@db_dir, 'PostRunner data') @db = PEROBS::Store.new(File.join(@db_dir, 'database')) + if (errors = @db.check) != 0 + Log.fatal "Postrunner DB is contains #{errors} errors" + end # Create a hash to store configuration data in the store unless it # exists already. unless @db['config'] @@ -97,6 +101,11 @@ EOT @filter = Fit4Ruby::FitFilter.new unless @filter @filter.ignore_undef = true end + opts.on('--force', + 'Import files even if they have been deleted from the ' + + 'database before.') do + @force = true + end opts.separator "" opts.separator "Options for the 'import' command:" @@ -276,7 +285,7 @@ EOT when 'list' @ffs.list_activities when 'records' - @ffs.show_records + puts @records.to_s when 'rename' unless (@name = args.shift) Log.fatal 'You must provide a new name for the activity' @@ -391,7 +400,7 @@ EOT end if fit_entity.is_a?(Fit4Ruby::Activity) - return @ffs.add_fit_file(fit_file_name, fit_entity) + return @ffs.add_fit_file(fit_file_name, fit_entity, @force) #elsif fit_entity.is_a?(Fit4Ruby::Monitoring_B) # return @monitoring.add(fit_file_name, fit_entity) else diff --git a/lib/postrunner/PersonalRecords.rb b/lib/postrunner/PersonalRecords.rb index a509abb..098badd 100644 --- a/lib/postrunner/PersonalRecords.rb +++ b/lib/postrunner/PersonalRecords.rb @@ -78,8 +78,7 @@ module PostRunner attr_reader :activity, :sport, :distance, :duration, :start_time - def initialize(activity = nil, sport = nil, distance = nil, - duration = nil, start_time = nil) + def initialize(activity, sport, distance, duration, start_time) @activity = activity @sport = sport @distance = distance @@ -97,15 +96,14 @@ module PostRunner po_attr :activity, :sport, :distance, :duration, :start_time - def initialize(store, result = nil) - super(store) - if result - init_attr(:activity, result.activity) - init_attr(:sport, result.sport) - init_attr(:distance, result.distance) - init_attr(:duration, result.duration) - init_attr(:start_time, result.start_time) - end + def initialize(p, result) + super(p) + + self.activity = result.activity + self.sport = result.sport + self.distance = result.distance + self.duration = result.duration + self.start_time = result.start_time end def to_table_row(t) @@ -127,12 +125,13 @@ module PostRunner po_attr :sport, :year, :distance_record, :speed_records - def initialize(store, sport = nil, year = nil) - super(store) - init_attr(:sport, sport) - init_attr(:year, year) - init_attr(:distance_record, nil) - init_attr(:speed_records, @store.new(PEROBS::Hash)) + def initialize(p, sport, year) + super(p) + + self.sport = sport + self.year = year + self.distance_record = nil + self.speed_records = @store.new(PEROBS::Hash) if sport PersonalRecords::SpeedRecordDistances[sport].each_key do |dist| @speed_records[dist.to_s] = nil @@ -249,11 +248,12 @@ module PostRunner po_attr :sport, :all_time, :yearly - def initialize(store, sport = nil) - super(store) - init_attr(:sport, sport) - init_attr(:all_time, @store.new(RecordSet, sport, nil)) - init_attr(:yearly, @store.new(PEROBS::Hash)) + def initialize(p, sport) + super(p) + + self.sport = sport + self.all_time = @store.new(RecordSet, sport, nil) + self.yearly = @store.new(PEROBS::Hash) end def register_result(result) @@ -324,10 +324,11 @@ module PostRunner end - def initialize(store) - super - init_attr(:sport_records, @store.new(PEROBS::Hash)) - delete_all_records if @sport_records.empty? + def initialize(p) + super(p) + + self.sport_records = @store.new(PEROBS::Hash) + delete_all_records end def scan_activity_for_records(activity, report_update_requested = false) diff --git a/postrunner.gemspec b/postrunner.gemspec index f23524a..555852f 100644 --- a/postrunner.gemspec +++ b/postrunner.gemspec @@ -7,7 +7,7 @@ Gem::Specification.new do |spec| spec.name = "postrunner" spec.version = PostRunner::VERSION spec.authors = ["Chris Schlaeger"] - spec.email = ["chris@taskjuggler.org"] + spec.email = ["cs@taskjuggler.org"] 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 such as those generated by Garmin GPS devices. The application was developed and tested with the Garmin Forerunner 620 and Fenix 3. Other devices may work as well. They need to export the data as USB Mass Storage devices. It is an offline alternative to Garmin Connect.} spec.homepage = 'https://github.com/scrapper/postrunner' @@ -20,11 +20,11 @@ Gem::Specification.new do |spec| spec.required_ruby_version = '>=2.0' spec.add_dependency 'fit4ruby', '~> 0.0.8' - spec.add_dependency 'perobs', '~> 1.0.0' + spec.add_dependency 'perobs', '~> 2.0.1' spec.add_dependency 'nokogiri', '~> 1.6' spec.add_development_dependency 'bundler', '~> 1.6' spec.add_development_dependency 'rake', '~> 0.9.6' - spec.add_development_dependency 'rspec', '~> 2.14.1' + spec.add_development_dependency 'rspec', '~> 3.4.1' spec.add_development_dependency 'yard', '~> 0.8.7' end diff --git a/spec/PostRunner_spec.rb b/spec/PostRunner_spec.rb index 24ae0fa..437438e 100644 --- a/spec/PostRunner_spec.rb +++ b/spec/PostRunner_spec.rb @@ -21,6 +21,8 @@ describe PostRunner::Main do args = [ '--dbdir', @db_dir ] + args old_stdout = $stdout $stdout = (stdout = StringIO.new) + @postrunner = nil + GC.start @postrunner = PostRunner::Main.new(args) $stdout = old_stdout stdout.string @@ -31,10 +33,13 @@ describe PostRunner::Main do create_working_dirs @db_dir = File.join(@work_dir, '.postrunner') - @file1 = File.join(@work_dir, 'FILE1.FIT') - @file2 = File.join(@work_dir, 'FILE2.FIT') - create_fit_file(@file1, '2014-07-01-8:00') - create_fit_file(@file2, '2014-07-02-8:00') + @opts = { :t => '2014-07-01-8:00', :speed => 11.0 } + @file1 = create_fit_activity_file(@work_dir, @opts) + @opts[:t] = '2014-07-02-8:00' + @file2 = create_fit_activity_file(@work_dir, @opts) + @opts[:t] = '2014-07-03-8:00' + @opts[:speed] = 12.5 + @file3 = create_fit_activity_file(@work_dir, @opts) end after(:all) do @@ -74,34 +79,34 @@ describe PostRunner::Main do end it 'should list the imported file' do - expect(postrunner(%w( list )).index('FILE1')).to be_a(Fixnum) + expect(postrunner(%w( list )).index(File.basename(@file1))).to be_a(Fixnum) end - it 'should import the other FIT file' do - postrunner([ 'import', @work_dir ]) + it 'should import the 2nd FIT file' do + postrunner([ 'import', @file2 ]) list = postrunner(%w( list )) - expect(list.index('FILE1.FIT')).to be_a(Fixnum) - expect(list.index('FILE2.FIT')).to be_a(Fixnum) + expect(list.index(File.basename(@file1))).to be_a(Fixnum) + expect(list.index(File.basename(@file2))).to be_a(Fixnum) end it 'should delete the first file' do postrunner(%w( delete :2 )) list = postrunner(%w( list )) - expect(list.index('FILE1.FIT')).to be_nil - expect(list.index('FILE2.FIT')).to be_a(Fixnum) + expect(list.index(File.basename(@file1))).to be_nil + expect(list.index(File.basename(@file2))).to be_a(Fixnum) end it 'should not import the deleted file again' do - postrunner(%w( import . )) + postrunner([ 'import', @file1 ]) list = postrunner(%w( list )) - expect(list.index('FILE1.FIT')).to be_nil - expect(list.index('FILE2.FIT')).to be_a(Fixnum) + expect(list.index(File.basename(@file1))).to be_nil + expect(list.index(File.basename(@file2))).to be_a(Fixnum) end it 'should rename FILE2.FIT activity' do postrunner(%w( rename foobar :1 )) list = postrunner(%w( list )) - expect(list.index('FILE2.FIT')).to be_nil + expect(list.index(File.basename(@file2))).to be_nil expect(list.index('foobar')).to be_a(Fixnum) end @@ -109,14 +114,14 @@ describe PostRunner::Main do expect { postrunner(%w( set foo bar :1)) }.to raise_error(Fit4Ruby::Error) end - it 'should set name for FILE2.FIT activity' do + it 'should set name for 2nd activity' do postrunner(%w( set name foobar :1 )) list = postrunner(%w( list )) expect(list.index(@file2)).to be_nil expect(list.index('foobar')).to be_a(Fixnum) end - it 'should set activity type for FILE2.FIT activity' do + it 'should set activity type for 2nd activity' do postrunner(%w( set type Cycling :1 )) list = postrunner(%w( summary :1 )) expect(list.index('Running')).to be_nil @@ -162,5 +167,25 @@ describe PostRunner::Main do postrunner(%w( units metric )) end + it 'should list records' do + # Add slow running activity + postrunner([ 'import', '--force', @file1 ]) + list = postrunner([ 'records' ]) + expect(list.index(File.basename(@file1))).to be_a(Fixnum) + + # Add fast running activity + postrunner([ 'import', @file3 ]) + list = postrunner([ 'records' ]) + expect(list.index(File.basename(@file3))).to be_a(Fixnum) + expect(list.index(File.basename(@file1))).to be_nil + end + + it 'should ignore records of an activity' do + postrunner(%w( set norecord true :1 )) + list = postrunner([ 'records' ]) + expect(list.index(File.basename(@file1))).to be_a(Fixnum) + expect(list.index(File.basename(@file3))).to be_nil + end + end diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb index dba6be6..2d93e12 100644 --- a/spec/spec_helper.rb +++ b/spec/spec_helper.rb @@ -96,14 +96,17 @@ def create_fit_activity(config) :garmin_product => 'sdm4', :device_index => 1, :battery_status => 'ok' }) laps = 0 + curr_speed = (config[:speed] ? config[:speed] : 10.0) / 3.6 # as m/s + curr_distance = 0.0 # as m 0.upto((a.total_timer_time / 60) - 1) do |mins| + curr_speed -= 0.05 if curr_speed > 2.5 a.new_record({ :timestamp => ts, :position_lat => 51.5512 - mins * 0.0008, :position_long => 11.647 + mins * 0.002, - :distance => 200.0 * mins, + :distance => curr_distance += curr_speed * 60, :altitude => 100 + mins * 3, - :speed => 3.1, + :speed => curr_speed, :vertical_oscillation => 90 + mins * 0.2, :stance_time => 235.0 * mins * 0.01, :stance_time_percent => 32.0, |