postrunner 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: a7ba557bbd6fb695863134d4c073cbec45d13e50
4
+ data.tar.gz: 77f131a6e793fc273ab45faab2638ee6091b6322
5
+ SHA512:
6
+ metadata.gz: 97662af5296d68665fb4062cc20df3e76ff44f42dc5b9e755e80d8db7bdbc33c2282ead70d32dc5f9695f94cdd8399468d50b3683e9fdae3c3612e14e98543ae
7
+ data.tar.gz: b0b83f2b6c4e9a462b83097916221bf886c9bbe87a2a371882445dab0b21e0d4f44e85addda82072ec74cc263d6f225f1800664ab14be9b0cf7869ab2fe38a1d
data/.gitignore ADDED
@@ -0,0 +1,22 @@
1
+ *.gem
2
+ *.rbc
3
+ .bundle
4
+ .config
5
+ .yardoc
6
+ Gemfile.lock
7
+ InstalledFiles
8
+ _yardoc
9
+ coverage
10
+ doc/
11
+ lib/bundler/man
12
+ pkg
13
+ rdoc
14
+ spec/reports
15
+ test/tmp
16
+ test/version_tmp
17
+ tmp
18
+ *.bundle
19
+ *.so
20
+ *.o
21
+ *.a
22
+ mkmf.log
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in postrunner.gemspec
4
+ gemspec
data/LICENSE.txt ADDED
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2014 Chris Schlaeger
2
+
3
+ MIT License
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining
6
+ a copy of this software and associated documentation files (the
7
+ "Software"), to deal in the Software without restriction, including
8
+ without limitation the rights to use, copy, modify, merge, publish,
9
+ distribute, sublicense, and/or sell copies of the Software, and to
10
+ permit persons to whom the Software is furnished to do so, subject to
11
+ the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be
14
+ included in all copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,29 @@
1
+ # Postrunner
2
+
3
+ TODO: Write a gem description
4
+
5
+ ## Installation
6
+
7
+ Add this line to your application's Gemfile:
8
+
9
+ gem 'postrunner'
10
+
11
+ And then execute:
12
+
13
+ $ bundle
14
+
15
+ Or install it yourself as:
16
+
17
+ $ gem install postrunner
18
+
19
+ ## Usage
20
+
21
+ TODO: Write usage instructions here
22
+
23
+ ## Contributing
24
+
25
+ 1. Fork it ( https://github.com/[my-github-username]/postrunner/fork )
26
+ 2. Create your feature branch (`git checkout -b my-new-feature`)
27
+ 3. Commit your changes (`git commit -am 'Add some feature'`)
28
+ 4. Push to the branch (`git push origin my-new-feature`)
29
+ 5. Create a new Pull Request
data/Rakefile ADDED
@@ -0,0 +1,2 @@
1
+ require "bundler/gem_tasks"
2
+
data/bin/postrunner ADDED
@@ -0,0 +1,5 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ $:.unshift File.join(File.dirname(__FILE__), '..', 'lib')
4
+ require File.basename(__FILE__)
5
+
data/lib/postrunner.rb ADDED
@@ -0,0 +1,11 @@
1
+ $:.unshift(File.join(File.dirname(__FILE__), '..', '..', 'fit4ruby', 'lib'))
2
+ $:.unshift(File.dirname(__FILE__))
3
+
4
+ require 'postrunner/version'
5
+ require 'postrunner/Main'
6
+
7
+ module PostRunner
8
+
9
+ Main.new(ARGV)
10
+
11
+ end
@@ -0,0 +1,148 @@
1
+ require 'fileutils'
2
+ require 'yaml'
3
+
4
+ require 'fit4ruby'
5
+ require 'postrunner/Activity'
6
+
7
+ module PostRunner
8
+
9
+ class ActivitiesDB
10
+
11
+ include Fit4Ruby::Converters
12
+
13
+ def initialize(db_dir)
14
+ @db_dir = db_dir
15
+ @fit_dir = File.join(@db_dir, 'fit')
16
+ @archive_file = File.join(@db_dir, 'archive.yml')
17
+
18
+ if Dir.exists?(@db_dir)
19
+ begin
20
+ if File.exists?(@archive_file)
21
+ @activities = YAML.load_file(@archive_file)
22
+ else
23
+ @activities = []
24
+ end
25
+ rescue
26
+ Log.fatal "Cannot load archive file '#{@archive_file}': #{$!}"
27
+ end
28
+ else
29
+ create_directories
30
+ @activities = []
31
+ end
32
+
33
+ unless @activities.is_a?(Array)
34
+ Log.fatal "The archive file '#{@archive_file}' is corrupted"
35
+ end
36
+ end
37
+
38
+ def add(fit_file)
39
+ base_fit_file = File.basename(fit_file)
40
+ if @activities.find { |a| a.fit_file == base_fit_file }
41
+ Log.debug "Activity #{fit_file} is already included in the archive"
42
+ return false
43
+ end
44
+
45
+ begin
46
+ fit_activity = Fit4Ruby.read(fit_file)
47
+ rescue Fit4Ruby::Error
48
+ Log.error "Cannot read #{fit_file}: #{$!}"
49
+ return false
50
+ end
51
+
52
+ begin
53
+ FileUtils.cp(fit_file, @fit_dir)
54
+ rescue
55
+ Log.fatal "Cannot copy #{fit_file} into #{@fit_dir}: #{$!}"
56
+ end
57
+
58
+ @activities << Activity.new(base_fit_file, fit_activity)
59
+ @activities.sort! do |a1, a2|
60
+ a2.start_time <=> a1.start_time
61
+ end
62
+ sync
63
+ Log.info "#{fit_file} successfully added to archive"
64
+
65
+ true
66
+ end
67
+
68
+
69
+ def rename(fit_file, name)
70
+ base_fit_file = File.basename(fit_file)
71
+ @activities.each do |a|
72
+ if a.fit_file == base_fit_file
73
+ a.name = name
74
+ sync
75
+ break
76
+ end
77
+ end
78
+ end
79
+
80
+ def map_to_files(query)
81
+ case query
82
+ when /\A-?\d+$\z/
83
+ index = query.to_i
84
+ # The UI counts the activities from 1 to N. Ruby counts from 0 -
85
+ # (N-1).
86
+ index -= 1 if index > 0
87
+ if (a = @activities[index])
88
+ return [ File.join(@fit_dir, a.fit_file) ]
89
+ end
90
+ when /\A-?\d+--?\d+\z/
91
+ idxs = query.match(/(?<sidx>-?\d+)-(?<eidx>-?[0-9]+)/)
92
+ sidx = idxs['sidx'].to_i
93
+ eidx = idxs['eidx'].to_i
94
+ # The UI counts the activities from 1 to N. Ruby counts from 0 -
95
+ # (N-1).
96
+ sidx -= 1 if sidx > 0
97
+ eidx -= 1 if eidx > 0
98
+ unless (as = @activities[sidx..eidx]).empty?
99
+ files = []
100
+ as.each do |a|
101
+ files << File.join(@fit_dir, a.fit_file)
102
+ end
103
+ return files
104
+ end
105
+ else
106
+ Log.error "Invalid activity query: #{query}"
107
+ end
108
+
109
+ []
110
+ end
111
+
112
+ def list
113
+ i = 0
114
+ @activities.each do |a|
115
+ i += 1
116
+ puts "#{'%4d' % i} " +
117
+ "#{'%-20s' % a.name[0..19]} " +
118
+ "#{'%22s' % a.start_time.strftime("%a, %Y %b %d %H:%M")} " +
119
+ "#{'%7.2f' % (a.distance / 1000)} " +
120
+ "#{'%8s' % secsToHMS(a.duration)} " +
121
+ "#{'%5s' % speedToPace(a.avg_speed)} "
122
+ end
123
+ end
124
+
125
+ private
126
+
127
+ def sync
128
+ File.open(@archive_file, 'w') { |f| f.write(@activities.to_yaml) }
129
+ end
130
+
131
+ def create_directories
132
+ Log.info "Creating data directory #{@db_dir}"
133
+ begin
134
+ Dir.mkdir(@db_dir)
135
+ rescue
136
+ Log.fatal "Cannot create data directory #{@db_dir}: #{$!}"
137
+ end
138
+ begin
139
+ Dir.mkdir(@fit_dir)
140
+ rescue
141
+ Log.fatal "Cannot create fit directory #{@fit_dir}: #{$!}"
142
+ end
143
+ end
144
+
145
+ end
146
+
147
+ end
148
+
@@ -0,0 +1,64 @@
1
+ require 'fit4ruby'
2
+ require 'postrunner/RuntimeConfig'
3
+
4
+ module PostRunner
5
+
6
+ class Activity
7
+
8
+ attr_reader :fit_file
9
+ attr_accessor :name
10
+
11
+ # This is a list of variables that provide data from the fit file. To
12
+ # speed up access to it, we cache the data in the activity database.
13
+ @@CachedVariables = %w( start_time distance duration avg_speed )
14
+
15
+ def initialize(fit_file, fit_activity, name = nil)
16
+ @fit_file = fit_file
17
+ @fit_activity = fit_activity
18
+ @name = name || fit_file
19
+
20
+ @@CachedVariables.each do |v|
21
+ v_str = "@#{v}"
22
+ instance_variable_set(v_str, fit_activity.send(v))
23
+ self.class.send(:attr_reader, v.to_sym)
24
+ end
25
+ end
26
+
27
+ def yaml_initialize(tag, value)
28
+ # Create attr_readers for cached variables.
29
+ @@CachedVariables.each { |v| self.class.send(:attr_reader, v.to_sym) }
30
+
31
+ # Load all attributes and assign them to instance variables.
32
+ value.each do |a, v|
33
+ instance_variable_set("@" + a, v)
34
+ end
35
+ # Use the FIT file name as activity name if none has been set yet.
36
+ @name = @fit_file unless @name
37
+ end
38
+
39
+ def encode_with(coder)
40
+ attr_ignore = %w( @fit_activity )
41
+
42
+ instance_variables.each do |v|
43
+ v = v.to_s
44
+ next if attr_ignore.include?(v)
45
+
46
+ coder[v[1..-1]] = instance_variable_get(v)
47
+ end
48
+ end
49
+
50
+ def method_missing(method_name, *args, &block)
51
+ fit_file = File.join(Config['fit_dir'], @fit_file)
52
+ begin
53
+ @fit_activity = Fit4Ruby.read(fit_file) unless @fit_activity
54
+ rescue Fit4Ruby::Error
55
+ Log.error "Cannot read #{fit_file}: #{$!}"
56
+ return false
57
+ end
58
+ @fit_activity.send(method_name, *args, &block)
59
+ end
60
+
61
+ end
62
+
63
+ end
64
+
@@ -0,0 +1,151 @@
1
+ require 'optparse'
2
+ require 'logger'
3
+ require 'fit4ruby'
4
+ require 'postrunner/RuntimeConfig'
5
+ require 'postrunner/ActivitiesDB'
6
+
7
+ module PostRunner
8
+
9
+ Log = Fit4Ruby::ILogger.new(STDOUT)
10
+
11
+ class Main
12
+
13
+ def initialize(args)
14
+ @filter = nil
15
+ @name = nil
16
+ @activities = ActivitiesDB.new(File.join(ENV['HOME'], '.postrunner'))
17
+
18
+ execute_command(parse_options(args))
19
+ end
20
+
21
+ private
22
+
23
+ def parse_options(args)
24
+ parser = OptionParser.new do |opts|
25
+ opts.banner = "Usage postrunner <command> [options]"
26
+ opts.separator ""
27
+ opts.separator "Options for the 'dump' command:"
28
+
29
+ opts.on('--filter-msg N', Integer,
30
+ 'Only dump messages of type number N') do |n|
31
+ @filter = Fit4Ruby::FitFilter.new unless @filter
32
+ @filter.record_numbers = [] unless @filter.record_numbers
33
+ @filter.record_numbers << n.to_i
34
+ end
35
+ opts.on('--filter-msg-idx N', Integer,
36
+ 'Only dump the N-th message of the specified types') do |n|
37
+ @filter = Fit4Ruby::FitFilter.new unless @filter
38
+ @filter.record_indexes = [] unless @filter.record_indexes
39
+ @filter.record_indexes << n.to_i
40
+ end
41
+ opts.on('--filter-field name', String,
42
+ 'Only dump the field \'name\' of the selected messages') do |n|
43
+ @filter = Fit4Ruby::FitFilter.new unless @filter
44
+ @filter.field_names = [] unless @filter.field_names
45
+ @filter.field_names << n
46
+ end
47
+ opts.on('--filter-undef',
48
+ "Don't show fields with undefined values") do
49
+ @filter = Fit4Ruby::FitFilter.new unless @filter
50
+ @filter.ignore_undef = true
51
+ end
52
+
53
+ opts.separator "Options for the 'import' and 'rename' command:"
54
+
55
+ opts.on('--name name', String,
56
+ 'Name or rename the activity to the specified name') do |n|
57
+ @name = n
58
+ end
59
+ end
60
+
61
+ parser.parse!(args)
62
+ end
63
+
64
+ def execute_command(args)
65
+ case args.shift
66
+ when 'check'
67
+ process_files_or_activities(args, :check)
68
+ when 'dump'
69
+ @filter = Fit4Ruby::FitFilter.new unless @filter
70
+ process_files_or_activities(args, :dump)
71
+ when 'import'
72
+ process_files(args, :import)
73
+ when 'list'
74
+ @activities.list
75
+ when 'rename'
76
+ process_activities(args, :rename)
77
+ when 'summary'
78
+ process_files_or_activities(args, :summary)
79
+ else
80
+ Log.fatal("No command provided")
81
+ end
82
+ end
83
+
84
+ def process_files_or_activities(files_or_activities, command)
85
+ files_or_activities.each do |foa|
86
+ if foa[0] == ':'
87
+ files = @activities.map_to_files(foa[1..-1])
88
+ if files.empty?
89
+ Log.warn "No matching activities found for '#{foa}'"
90
+ return
91
+ end
92
+
93
+ process_files(files, command)
94
+ else
95
+ process_files([ foa ], command)
96
+ end
97
+ end
98
+ end
99
+
100
+ def process_activities(activity_files, command)
101
+ activity_files.each do |a|
102
+ if a[0] == ':'
103
+ files = @activities.map_to_files(a[1..-1])
104
+ if files.empty?
105
+ Log.warn "No matching activities found for '#{a}'"
106
+ return
107
+ end
108
+ process_files(files, command)
109
+ else
110
+ Log.fatal "Activity references must start with ':': #{a}"
111
+ end
112
+ end
113
+
114
+ end
115
+
116
+ def process_files(files_or_dirs, command)
117
+ if files_or_dirs.empty?
118
+ Log.fatal("You must provide at least one .FIT file name")
119
+ end
120
+
121
+ files_or_dirs.each do |fod|
122
+ if File.directory?(fod)
123
+ Dir.glob(File.join(fod, '*.FIT')).each do |file|
124
+ process_file(file, command)
125
+ end
126
+ else
127
+ process_file(fod, command)
128
+ end
129
+ end
130
+ end
131
+
132
+ def process_file(file, command)
133
+ case command
134
+ when :import
135
+ @activities.add(file)
136
+ when :rename
137
+ @activities.rename(file, @name)
138
+ else
139
+ begin
140
+ activity = Fit4Ruby::read(file, @filter)
141
+ #rescue
142
+ # Log.error("File '#{file}' is corrupted!: #{$!}")
143
+ end
144
+ puts activity.to_s if command == :summary
145
+ end
146
+ end
147
+
148
+ end
149
+
150
+ end
151
+
@@ -0,0 +1,20 @@
1
+ module PostRunner
2
+
3
+ class RuntimeConfig
4
+
5
+ def initialize
6
+ @settings = {}
7
+ @settings['data_dir'] = File.join(ENV['HOME'], '.postrunner')
8
+ @settings['fit_dir'] = File.join(@settings['data_dir'], 'fit')
9
+ end
10
+
11
+ def [](key)
12
+ @settings[key]
13
+ end
14
+
15
+ end
16
+
17
+ Config = RuntimeConfig.new
18
+
19
+ end
20
+
@@ -0,0 +1,3 @@
1
+ module PostRunner
2
+ VERSION = "0.0.1"
3
+ end
@@ -0,0 +1,24 @@
1
+ # coding: utf-8
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'postrunner/version'
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = "postrunner"
8
+ spec.version = PostRunner::VERSION
9
+ spec.authors = ["Chris Schlaeger"]
10
+ spec.email = ["chris@linux.com"]
11
+ spec.summary = %q{Application to manage and analyze Garmin FIT files.}
12
+ 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.}
13
+ spec.homepage = 'https://github.com/scrapper/fit4ruby'
14
+ spec.license = "GNU GPL version 2"
15
+
16
+ spec.files = `git ls-files -z`.split("\x0")
17
+ spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
18
+ spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
19
+ spec.require_paths = ["lib"]
20
+ spec.required_ruby_version = '>=2.0'
21
+
22
+ spec.add_development_dependency "bundler", "~> 1.6"
23
+ spec.add_development_dependency "rake"
24
+ end
metadata ADDED
@@ -0,0 +1,90 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: postrunner
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.1
5
+ platform: ruby
6
+ authors:
7
+ - Chris Schlaeger
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2014-07-20 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: bundler
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - ~>
18
+ - !ruby/object:Gem::Version
19
+ version: '1.6'
20
+ type: :development
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - ~>
25
+ - !ruby/object:Gem::Version
26
+ version: '1.6'
27
+ - !ruby/object:Gem::Dependency
28
+ name: rake
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - '>='
32
+ - !ruby/object:Gem::Version
33
+ version: '0'
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - '>='
39
+ - !ruby/object:Gem::Version
40
+ version: '0'
41
+ description: This application will allow you to manage and analyze .FIT files as generated
42
+ by Garmin GPS devices. The application was developed for the Garmin Forerunner 620.
43
+ Other devices may or may not work. Only devices that expose themselves as USB Mass
44
+ Storage devices are supported.
45
+ email:
46
+ - chris@linux.com
47
+ executables:
48
+ - postrunner
49
+ extensions: []
50
+ extra_rdoc_files: []
51
+ files:
52
+ - .gitignore
53
+ - Gemfile
54
+ - LICENSE.txt
55
+ - README.md
56
+ - Rakefile
57
+ - bin/postrunner
58
+ - lib/postrunner.rb
59
+ - lib/postrunner/ActivitiesDB.rb
60
+ - lib/postrunner/Activity.rb
61
+ - lib/postrunner/Main.rb
62
+ - lib/postrunner/RuntimeConfig.rb
63
+ - lib/postrunner/version.rb
64
+ - postrunner.gemspec
65
+ homepage: https://github.com/scrapper/fit4ruby
66
+ licenses:
67
+ - GNU GPL version 2
68
+ metadata: {}
69
+ post_install_message:
70
+ rdoc_options: []
71
+ require_paths:
72
+ - lib
73
+ required_ruby_version: !ruby/object:Gem::Requirement
74
+ requirements:
75
+ - - '>='
76
+ - !ruby/object:Gem::Version
77
+ version: '2.0'
78
+ required_rubygems_version: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - '>='
81
+ - !ruby/object:Gem::Version
82
+ version: '0'
83
+ requirements: []
84
+ rubyforge_project:
85
+ rubygems_version: 2.0.3
86
+ signing_key:
87
+ specification_version: 4
88
+ summary: Application to manage and analyze Garmin FIT files.
89
+ test_files: []
90
+ has_rdoc: