rarbg 1.1.1 → 1.2.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 875767e85f80c778779be897867dffad4731f9f8a4a7dca68cd76b47be386b5a
4
- data.tar.gz: 1a340ef687b05465ff2290fc8feec5324102d90514c657641b47411149cfd7eb
3
+ metadata.gz: 6ae0fc9cbd49bc0297737148b66e9c08164e1ebb4e0e1e7237d0e0c3385d4165
4
+ data.tar.gz: 76ae543821aa6204da813588d4020bfaf8d9f0462f88cea1c7e8299ee9b84fac
5
5
  SHA512:
6
- metadata.gz: 9a8fca5afbe015a0d4fdb33a2ff3fcd77090b680e519c064d9686c291f5aa3366aa9ea456ae770042c79b5bc8bb7ba7477dfa945c85ee8327befa2b1052adeb2
7
- data.tar.gz: 1d64bf8971f55b9bebaa8cd19cc45d0667f77b53fbba7fa896ad81d8099f40ffb649e5d32994fdd20cca0c11f8fdf849bfdb2661982d54a5f4d4f25d4918ca99
6
+ metadata.gz: c486d18f7621cfa1aa6305145dda201e60c5a05a4c53637efe68908efcb296adb7094af575ab77253c6286098269a1daaa5561ad140d9274a40786de9ebe2c50
7
+ data.tar.gz: f1974c700b3f66ec529e123a92ae8619fca9c9f014f9a7b7d6d3381b0aa95890e05f70706f3e20a62bacaa677a0b758cab3885a634e300a24fdc634310640f95
@@ -0,0 +1,4 @@
1
+ # These owners will be the default owners for everything in the repo.
2
+ # Unless a later match takes precedence, they will be requested for review
3
+ # when someone opens a pull request.
4
+ * @epistrephein
@@ -1,8 +1,12 @@
1
- ## Bug description
2
- A clear and concise description of what the bug is.
1
+ ---
2
+ name: Bug Report
3
+ about: Report something that isn't working as expected.
4
+
5
+ ---
3
6
 
4
- ## Expected behavior
5
- A clear and concise description of what you expected to happen.
7
+ ## Bug description
8
+ A clear and concise description of what the bug is, what you expected to happen
9
+ and what happened instead.
6
10
 
7
11
  ## Steps to reproduce
8
12
  1. Instantiate object...
@@ -10,9 +14,9 @@ A clear and concise description of what you expected to happen.
10
14
  3. Error is raised...
11
15
 
12
16
  ## Environment
13
- - Gem version:
14
- - Ruby version:
15
- - OS and version:
17
+ - Gem version:
18
+ - Ruby version:
19
+ - OS and version:
16
20
 
17
21
  ## Logs
18
22
  If applicable, add logs or screenshots to help explain your problem.
@@ -0,0 +1,18 @@
1
+ ---
2
+ name: Feature request
3
+ about: Suggest an idea for this project.
4
+
5
+ ---
6
+
7
+ ## Feature description
8
+ A clear and concise description of what problem you're trying to solve and what
9
+ you'd want to happen.
10
+
11
+ ## Possible implementations
12
+ If possible, suggest a way to add this feature.
13
+
14
+ ## Alternatives considered
15
+ A description of any alternative solutions or features you've considered.
16
+
17
+ ## Additional context
18
+ Add any other context about the feature here.
@@ -0,0 +1,14 @@
1
+ ## Pull Request description
2
+ A clear and concise explanation of what changes your PR introduces and why this
3
+ was needed.
4
+
5
+ ## Pull Request checklist
6
+
7
+ - [ ] My code is a **bug fix** (non-breaking change which fixes an issue)
8
+ - [ ] My code is a **new feature** (non-breaking change which adds functionality)
9
+ - [ ] My code introduces a **breaking change** (fix or feature that would cause existing functionality to change)
10
+ - [ ] My code follows the code style of this project
11
+ - [ ] My change requires a change to the documentation
12
+ - [ ] I have updated the documentation accordingly
13
+ - [ ] I have added tests to cover my changes
14
+ - [ ] All new and existing tests passed
data/.gitignore CHANGED
@@ -32,6 +32,10 @@ Gemfile.lock
32
32
  *.log
33
33
  *.log.[0-9]*
34
34
 
35
+ # Pry
36
+ .pryrc
37
+ .pry_history
38
+
35
39
  # RVM
36
40
  .rvmrc
37
41
 
data/.rspec CHANGED
@@ -1,4 +1,4 @@
1
+ --color
1
2
  --format documentation
2
3
  --order random
3
- --color
4
4
  --require spec_helper
@@ -0,0 +1,27 @@
1
+ AllCops:
2
+ TargetRubyVersion: 2.3
3
+ DisplayCopNames: true
4
+ DisplayStyleGuide: true
5
+
6
+ Gemspec/RequiredRubyVersion:
7
+ Enabled: false
8
+
9
+ Layout/AlignHash:
10
+ EnforcedHashRocketStyle: table
11
+ EnforcedColonStyle: table
12
+
13
+ Layout/FirstParameterIndentation:
14
+ EnforcedStyle: consistent
15
+
16
+ Metrics/BlockLength:
17
+ Exclude:
18
+ - '*.gemspec'
19
+ ExcludedMethods:
20
+ - 'describe'
21
+ - 'context'
22
+
23
+ Metrics/LineLength:
24
+ Max: 90
25
+
26
+ Style/BracesAroundHashParameters:
27
+ EnforcedStyle: context_dependent
@@ -3,6 +3,7 @@ language: ruby
3
3
  cache: bundler
4
4
  script: bundle exec rspec
5
5
  rvm:
6
+ - 2.6.0
6
7
  - 2.5.3
7
8
  - 2.4.5
8
9
  - 2.3.8
@@ -4,8 +4,23 @@ All notable changes to this project will be documented in this file.
4
4
  The format is based on [Keep a Changelog](https://keepachangelog.com/),
5
5
  and this project adheres to [Semantic Versioning](https://semver.org/).
6
6
 
7
- ## Unreleased
8
- [Diff](https://github.com/epistrephein/rarbg/compare/v1.1.1...master)
7
+ ## 1.2.0 - 2019-01-02
8
+ [RubyGems](https://rubygems.org/gems/rarbg/versions/1.2.0) |
9
+ [Release](https://github.com/epistrephein/rarbg/releases/tag/v1.2.0) |
10
+ [Diff](https://github.com/epistrephein/rarbg/compare/v1.1.1...v1.2.0)
11
+
12
+ #### Fixed
13
+ - Fixed gemspec to not include HTML documentation files.
14
+ - Fixed code style and typos.
15
+
16
+ #### Added
17
+ - Added rubocop as development dependency to enforce code style.
18
+ - Added GitHub templates for issues and pull requests.
19
+ - Added CONTRIBUTING document with contribution guidelines.
20
+
21
+ #### Changed
22
+ - Bumped all dependencies to stricter minor version constraint.
23
+ - Rate limit is now stubbed to 0.1 seconds in RSpec to speed up tests.
9
24
 
10
25
 
11
26
  ## 1.1.1 - 2018-10-19
@@ -13,7 +28,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/).
13
28
  [Release](https://github.com/epistrephein/rarbg/releases/tag/v1.1.1) |
14
29
  [Diff](https://github.com/epistrephein/rarbg/compare/v1.1.0...v1.1.1)
15
30
 
16
- ### Changed
31
+ #### Changed
17
32
  - Self-host documentation on GitHub pages.
18
33
 
19
34
 
@@ -33,13 +48,13 @@ and this project adheres to [Semantic Versioning](https://semver.org/).
33
48
  [Release](https://github.com/epistrephein/rarbg/releases/tag/v1.0.1) |
34
49
  [Diff](https://github.com/epistrephein/rarbg/compare/v1.0.0...v1.0.1)
35
50
 
36
- #### Added
37
- - Faraday now logs requests to stdout if in verbose mode (`-w` or `-v`).
38
-
39
51
  #### Fixed
40
52
  - Fixed code styling and some documentation errors.
41
53
  - Fixed IMDb id autocorrect in `search`.
42
54
 
55
+ #### Added
56
+ - Faraday now logs requests to stdout if in verbose mode (`-w` or `-v`).
57
+
43
58
 
44
59
  ## 1.0.0 – 2018-04-02
45
60
  [RubyGems](https://rubygems.org/gems/rarbg/versions/1.0.0) |
@@ -95,9 +110,11 @@ search type must be specified as a keyword argument upon `search`.
95
110
  [Diff](https://github.com/epistrephein/rarbg/compare/v0.1.0...v0.1.1)
96
111
 
97
112
  #### Fixed
98
- - Fix invalid gemspec
113
+ - Fix invalid gemspec.
99
114
 
100
115
 
101
116
  ## 0.1.0 – 2016-12-21
117
+ [RubyGems](https://rubygems.org/gems/rarbg/versions/0.1.0) |
118
+ [Release](https://github.com/epistrephein/rarbg/releases/tag/v0.1.0)
102
119
 
103
- Initial release. Yanked from [RubyGems](https://rubygems.org).
120
+ Initial release. Yanked from RubyGems.
@@ -0,0 +1,92 @@
1
+ # Contributing
2
+
3
+ First off, thank you for considering contributing to this project.
4
+
5
+ As part of the open source community, this project thrives thanks to the help
6
+ and the contributions of its users. There are many ways to contribute - from
7
+ submitting bug reports and feature requests, to improving the documentation, or
8
+ writing code which can be incorporated into the codebase.
9
+
10
+ In these guidelines, you will find the most common scenarios related to
11
+ contributing and information on how to proceed to make sure that your submission
12
+ can be evaluated swiftly and properly.
13
+
14
+ This project is intended to be a safe, welcoming space for collaboration,
15
+ and contributors are expected to adhere to the
16
+ [Code of Conduct](https://github.com/epistrephein/rarbg/blob/master/CODE_OF_CONDUCT.md).
17
+
18
+ ## Opening an issue
19
+
20
+ Opening an issue on GitHub is the fastest way to get in touch with the maintainers
21
+ of the project and start a discussion regarding a problem you're experiencing or
22
+ a missing feature you would like to suggest.
23
+
24
+ For this reason, upon opening a new issue, you'll be able to choose between
25
+ different templates for your submission, with some pre-allocated sections to fill.
26
+ Please try to follow the existing structure of the issue and to provide as much
27
+ information as possible regarding your situation. This will help the maintainers
28
+ to reproduce the bug or effectively consider the feature you'd like to see
29
+ implemented.
30
+
31
+ If you think your issue doesn't fit into any of the existing templates, or if you
32
+ just have a question regarding the existing functionalities of the project,
33
+ simply open a regular issue and describe the problem in the clearest way possible.
34
+
35
+ ## Contributing with code
36
+
37
+ If you would like to contribute to the project by writing code, you can open a
38
+ Pull Request on GitHub with your changes and ask a maintainer for a review.
39
+
40
+ As an open source Ruby gem, this projects uses the most common development tools
41
+ to test, validate, and document the code.
42
+
43
+ ### Tools and integrations
44
+
45
+ We use [RSpec](http://rspec.info/) as testing framework.
46
+ The test suite lives in the `spec` directory, and you can run it to test your
47
+ changes with `rake spec`.
48
+
49
+ We also use [RuboCop](https://docs.rubocop.org/en/latest/) to enforce code style.
50
+ The configuration file used for this project lives in `.rubocop.yml`. You can check
51
+ the code style of your changes with `rake rubocop`.
52
+
53
+ The default Rake task, runnable using `rake`, performs code linting via Rubocop
54
+ and then runs the RSpec tests.
55
+
56
+ Documentation is written as [YARD](https://yardoc.org/) docblocks in the Ruby code.
57
+ This is rendered as self-hosted Web pages on [GitHub pages](https://epistrephein.github.io/rarbg/).
58
+ Sources are stored in the repository under the `docs` folder and can be automatically
59
+ generated and updated via `rake yard`.
60
+ The completeness of the documentation is then measured via
61
+ [Inch CI](https://inch-ci.org/github/epistrephein/rarbg).
62
+
63
+ Continuous integration and automated tests are run on
64
+ [Travis CI](https://travis-ci.org/epistrephein/rarbg) and integrated with the
65
+ GitHub Pull Request flow.
66
+
67
+ Code quality and test coverage are then scored via
68
+ [CodeClimate](https://codeclimate.com/github/epistrephein/rarbg).
69
+
70
+ Finally, dependencies are kept up-to-date thanks to
71
+ [Depfu](https://depfu.com/github/epistrephein/rarbg) integration.
72
+
73
+ ### Submitting a PR
74
+
75
+ This project follows the [GitHub flow](https://guides.github.com/introduction/flow/)
76
+ for Pull Request submissions.
77
+
78
+ To submit a PR with your proposed changes, follow these steps:
79
+
80
+ 1. [Fork the repo](https://github.com/epistrephein/rarbg/fork) in your GitHub
81
+ userspace and clone it locally
82
+ 2. Install the dependencies with bundler (`bin/setup`)
83
+ 3. Create a feature branch (`git checkout -b my-new-feature`)
84
+ 4. Add your code to the branch and then commit the changes (`git commit -am 'Add some feature'`)
85
+ 5. Run the test suite (`rake spec`) and make sure that existing and newly introduced
86
+ tests pass. You can also run Rubocop linting with `rake rubocop`, or just use `rake`
87
+ to run both Rubocop and RSpec tests
88
+ 6. Push to your remote branch on GitHub (`git push origin my-new-feature`)
89
+ 7. Create a [new pull request](https://github.com/epistrephein/rarbg/pulls)
90
+
91
+ As for issues, when opening a new Pull Request, please fill out the template with
92
+ all the relevant information in order to reduce the review effort of the maintainers.
data/Gemfile CHANGED
@@ -1,5 +1,4 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  source 'https://rubygems.org'
4
-
5
4
  gemspec
data/LICENSE CHANGED
@@ -1,6 +1,6 @@
1
1
  MIT License
2
2
 
3
- Copyright (c) 2016-2018 Tommaso Barbato
3
+ Copyright (c) 2016-2019 Tommaso Barbato
4
4
 
5
5
  Permission is hereby granted, free of charge, to any person obtaining a copy
6
6
  of this software and associated documentation files (the "Software"), to deal
data/README.md CHANGED
@@ -20,7 +20,7 @@ $ gem install rarbg
20
20
  Or add it to your Gemfile and execute `bundle install`
21
21
 
22
22
  ```ruby
23
- gem 'rarbg', '~> 1.0'
23
+ gem 'rarbg', '~> 1.2'
24
24
  ```
25
25
 
26
26
  ## Usage
@@ -67,7 +67,6 @@ One search type parameter among `string`, `imdb`, `themoviedb` and `tvdb` is req
67
67
  rarbg.search(string: 'Force Awakens')
68
68
 
69
69
  # Search by IMDB id, in `Movies/x264/1080` and `Movies/x264/720`.
70
- # Note that 'tt' can be omitted when passing an IMDB id.
71
70
  rarbg.search(imdb: 'tt2488496', category: [44, 45])
72
71
 
73
72
  # Search unranked torrents by TheMovieDB id, sorted by last.
@@ -115,7 +114,14 @@ rarbg.list('a string instead of an hash')
115
114
  # => ArgumentError: Expected params hash
116
115
 
117
116
  rarbg.search(limit: 50)
118
- # => ArgumentError: At least one parameter required among string, imdb, tvdb, themoviedb for search mode.
117
+ # => ArgumentError: One search parameter required among: string, imdb, tvdb, themoviedb
118
+ ```
119
+
120
+ Lower level connection errors will raise `Faraday::Error` subclasses exceptions.
121
+
122
+ ```ruby
123
+ rarbg.search(string: 'a timeout error')
124
+ # => Faraday::ConnectionFailed: execution expired
119
125
  ```
120
126
 
121
127
  ## Contributing
@@ -135,6 +141,8 @@ You can contribute changes by forking the project and submitting a pull request.
135
141
  6. Push to the branch (`git push origin my-new-feature`)
136
142
  7. Create a new pull request
137
143
 
144
+ For more information about contributing to this project, check out [CONTRIBUTING](https://github.com/epistrephein/rarbg/blob/master/CONTRIBUTING.md).
145
+
138
146
  ## License
139
147
 
140
148
  This gem is released as open source under the terms of the [MIT License](https://github.com/epistrephein/rarbg/blob/master/LICENSE).
data/Rakefile CHANGED
@@ -2,9 +2,11 @@
2
2
 
3
3
  require 'bundler/gem_tasks'
4
4
  require 'rspec/core/rake_task'
5
+ require 'rubocop/rake_task'
5
6
  require 'yard'
6
7
 
7
8
  RSpec::Core::RakeTask.new(:spec)
9
+ RuboCop::RakeTask.new(:rubocop)
8
10
  YARD::Rake::YardocTask.new(:yard)
9
11
 
10
- task default: :spec
12
+ task default: %i[rubocop spec]
@@ -4,13 +4,13 @@ require 'rarbg/version'
4
4
  require 'rarbg/categories'
5
5
  require 'rarbg/api'
6
6
 
7
- # Module shortcut methods
7
+ # Module namespace shortcut methods.
8
8
  module RARBG
9
9
  class << self
10
10
  %i[list search].each do |m|
11
- define_method(m) do |*arg|
11
+ define_method(m) do |*args|
12
12
  @rarbg ||= RARBG::API.new
13
- @rarbg.send(m, *arg)
13
+ @rarbg.send(m, *args)
14
14
  end
15
15
  end
16
16
  end
@@ -3,7 +3,7 @@
3
3
  require 'faraday'
4
4
  require 'faraday_middleware'
5
5
 
6
- # Main namespace for RARBG
6
+ # Main namespace for RARBG.
7
7
  module RARBG
8
8
  # Default error class for the module.
9
9
  class APIError < StandardError; end
@@ -16,10 +16,10 @@ module RARBG
16
16
  # App name identifier.
17
17
  APP_ID = 'rarbg-rubygem'
18
18
 
19
- # Default token expiration time.
19
+ # Default token expiration time (seconds).
20
20
  TOKEN_EXPIRATION = 800
21
21
 
22
- # Default API rate limit.
22
+ # Default API rate limit (seconds).
23
23
  RATE_LIMIT = 2.1
24
24
 
25
25
  # @return [Faraday::Connection] the Faraday connection object.
@@ -34,7 +34,11 @@ module RARBG
34
34
  # @return [Integer] the monotonic timestamp of the last request performed.
35
35
  attr_reader :last_request
36
36
 
37
- # Initialize a new istance of `RARBG::API`.
37
+ # Supported search parameters.
38
+ SEARCH_KEYS = %w[string imdb tvdb themoviedb].freeze
39
+ private_constant :SEARCH_KEYS
40
+
41
+ # Initialize a new instance of `RARBG::API`.
38
42
  #
39
43
  # @example
40
44
  # rarbg = RARBG::API.new
@@ -72,9 +76,9 @@ module RARBG
72
76
  # @return [Array<Hash>] Return torrents that match the specified parameters.
73
77
  #
74
78
  # @raise [ArgumentError] Exception raised if `params` is not an `Hash`.
75
- #
76
79
  # @raise [RARBG::APIError] Exception raised when request fails or endpoint
77
80
  # responds with an error.
81
+ # @raise [Faraday::Error] Exception raised on low-level connection errors.
78
82
  #
79
83
  # @example List last 100 ranked torrents in `Movies/x264/1080`
80
84
  # rarbg = RARBG::API.new
@@ -87,8 +91,8 @@ module RARBG
87
91
  raise ArgumentError, 'Expected params hash' unless params.is_a?(Hash)
88
92
 
89
93
  params.update(
90
- mode: 'list',
91
- token: token?
94
+ mode: 'list',
95
+ token: token?
92
96
  )
93
97
  call(params)
94
98
  end
@@ -103,7 +107,7 @@ module RARBG
103
107
  # @option params [String] :themoviedb Search by The Movie DB id.
104
108
  # @option params [Array<Integer>] :category Filter results by category.
105
109
  # @option params [Symbol] :format Format results.
106
- # Valid values: `:json`, `:json_extended`. Default: `:json`
110
+ # Valid values: `:json`, `:json_extended`. Default: `:json`.
107
111
  # @option params [Integer] :limit Limit results number.
108
112
  # Valid values: `25`, `50`, `100`. Default: `25`.
109
113
  # @option params [Integer] :min_seeders Filter results by minimum seeders.
@@ -116,16 +120,15 @@ module RARBG
116
120
  # @return [Array<Hash>] Return torrents that match the specified parameters.
117
121
  #
118
122
  # @raise [ArgumentError] Exception raised if `params` is not an `Hash`.
119
- #
120
123
  # @raise [ArgumentError] Exception raised if no search type param is passed
121
124
  # (among `string`, `imdb`, `tvdb`, `themoviedb`).
122
- #
123
125
  # @raise [RARBG::APIError] Exception raised when request fails or endpoint
124
126
  # responds with an error.
127
+ # @raise [Faraday::Error] Exception raised on low-level connection errors.
125
128
  #
126
129
  # @example Search by IMDb ID, sorted by leechers and in extended format.
127
130
  # rarbg = RARBG::API.new
128
- # rarbg.search(imdb: 'tt012831', sort: :leechers, format: :json_extended)
131
+ # rarbg.search(imdb: 'tt2488496', sort: :leechers, format: :json_extended)
129
132
  #
130
133
  # @example Search unranked torrents by string, with at least 2 seeders.
131
134
  # rarbg = RARBG::API.new
@@ -134,20 +137,21 @@ module RARBG
134
137
  raise ArgumentError, 'Expected params hash' unless params.is_a?(Hash)
135
138
 
136
139
  params.update(
137
- mode: 'search',
138
- token: token?
140
+ mode: 'search',
141
+ token: token?
139
142
  )
140
143
  call(params)
141
144
  end
142
145
 
143
146
  private
144
147
 
145
- # Wrap request for error handling.
148
+ # Wrap requests for error handling.
146
149
  def call(params)
147
150
  response = request(validate(params))
148
151
 
149
152
  return [] if response['error'] == 'No results found'
150
153
  raise APIError, response['error'] if response.key?('error')
154
+
151
155
  response.fetch('torrent_results', [])
152
156
  end
153
157
 
@@ -167,23 +171,20 @@ module RARBG
167
171
  Hash[params.reject { |_k, v| v.nil? }.map { |k, v| [k.to_s, v] }]
168
172
  end
169
173
 
170
- # Validate search type parameter.
174
+ # Validate search type parameters.
171
175
  def validate_search!(params)
172
- search_keys = %w[string imdb tvdb themoviedb]
173
-
174
- raise(
175
- ArgumentError,
176
- "At least one parameter required among #{search_keys.join(', ')} " \
177
- 'for search mode.'
178
- ) if (params.keys & search_keys).none?
176
+ if (params.keys & SEARCH_KEYS).none?
177
+ raise(ArgumentError,
178
+ "One search parameter required among: #{SEARCH_KEYS.join(', ')}")
179
+ end
179
180
 
180
- search_keys.each do |k|
181
+ SEARCH_KEYS.each do |k|
181
182
  params["search_#{k}"] = params.delete(k) if params.key?(k)
182
183
  end
183
184
  params
184
185
  end
185
186
 
186
- # Convert ruby sugar to expected value style.
187
+ # Convert ruby syntax to expected value format.
187
188
  def normalize
188
189
  {
189
190
  'category' => (->(v) { v.join(';') }),
@@ -194,28 +195,28 @@ module RARBG
194
195
 
195
196
  # Return or renew auth token.
196
197
  def token?
197
- if @token.nil? || time >= (@token_time + TOKEN_EXPIRATION)
198
+ if token.nil? || time >= (token_time.to_f + TOKEN_EXPIRATION)
198
199
  response = request(get_token: 'get_token')
199
200
  @token = response.fetch('token')
200
201
  @token_time = time
201
202
  end
202
- @token
203
+ token
203
204
  end
204
205
 
205
206
  # Perform API request.
206
207
  def request(params)
207
208
  rate_limit!(RATE_LIMIT)
208
-
209
- response = @conn.get(nil, params)
209
+ response = conn.get(nil, params)
210
210
  @last_request = time
211
211
 
212
212
  return response.body if response.success?
213
+
213
214
  raise APIError, "#{response.reason_phrase} (#{response.status})"
214
215
  end
215
216
 
216
217
  # Rate-limit requests to comply with endpoint limits.
217
218
  def rate_limit!(seconds)
218
- sleep(0.3) until time >= ((@last_request || 0) + seconds)
219
+ sleep(0.1) until time >= (last_request.to_f + seconds)
219
220
  end
220
221
 
221
222
  # Monotonic clock for elapsed time calculations.