synaccess_connect 0.2.2 → 0.3.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,15 +1,15 @@
1
1
  ---
2
2
  !binary "U0hBMQ==":
3
3
  metadata.gz: !binary |-
4
- ZmRjYmQ0NmU2ZTE2Y2IwZTZiOGNjZGUwYTgyM2M5ZTk2MTIxNGI1OQ==
4
+ ZmIyY2NiZjNjYjU0OGJkN2ZiYzQwOTMzODQwNWFjODczN2I4Zjc0Yw==
5
5
  data.tar.gz: !binary |-
6
- YTkyZGExYmZiMjA3ZDcxMTRlZjk4NjA0YzRlMTU5NThlYTU2YjUwNg==
6
+ ODYwYzRjM2E2ODdhNjRlOWM2OGU4MWE1MTFkYjBhMmYwMWVhMDVkMQ==
7
7
  SHA512:
8
8
  metadata.gz: !binary |-
9
- MmU2NWFhMGVjMDg2MTdkMWZiMTA0MThlZmY2MTU4OGE1OGMzNGY0Y2ZmZjQ0
10
- NzRhMGMzZTExNGRjYjZmMjBhZDdiNGZkNDA0MDEyZWU5OWRjYzI0NGVhNDQ0
11
- MjM1YWJlNWYwODQxZTUyZDVjNTNiZWM4ZjZkNjMzMjBhZDI2ZGE=
9
+ NTlmZWJhOWM3ZDJmMmY0NDRiZjM3YTgwNWQ4NmZmMmZjYTlkNzcwODM0NjY4
10
+ OWM0MDY2ZjIxNjM0MzYyZGQ1MDExNGVhZjNmMDk5MGJhNzZlNDEwNDZlMGQz
11
+ MWE3YTI3YmY2Y2Q1MTRlMzU4OTA5YzczMzNhYjMzNDhlOWQ1MGU=
12
12
  data.tar.gz: !binary |-
13
- ODg5NGEwNjdjYTIyMzU2YzgwNjQwNmY1NTVmZDdlOGMyZDk4MTAyODcwOTE2
14
- ZDcyOTQzZjE0MWIxY2YxN2VjMzgzYTgzYzJhYmZmNzhmY2E0YWMwNzIxOGU5
15
- MjJkNDE4OGM2OWZmMzJiNDQ0YWNkMzNiN2VjNGU5MGRkYWNjNjg=
13
+ YTBjZjdiODU0NDU0MWY1OGEwYzUyYmY3M2JlYWZhNTQ4N2M2YmRkMzc5ZTZk
14
+ ZDJiNTM4YWI4NjIyMTliNDMwN2Q2NGE1NTE3ZGJmMjA1MTcyNTMzMmEwZTcw
15
+ NGFjYzRmMzllOTg3NzY3NDQyYTMzN2FjMDQ4YjMxY2ZjN2Q4MGE=
data/README.md CHANGED
@@ -12,19 +12,25 @@ Communication wrappers for Synaccess netBooter power relays.
12
12
  Http
13
13
  RevA
14
14
  RevB
15
- Telnet
16
- RevA
17
- RevB
18
15
  ```
19
16
 
20
- ## New connections
17
+ The Telnet interface was removed in version 0.3.0 because it was unreliable. You
18
+ should switch to using the Http interface.
21
19
 
22
- `connection = NetBooter::PROTOCOL::RevX.new('XXX.XXX.XXX.XXX', options)`
20
+ ## Usage
23
21
 
24
- Options:
22
+ * Add `gem "synaccess_connect", "~> 0.3.0" to your Gemfile.
25
23
 
26
- * username
27
- * password
24
+ ## Example
25
+
26
+ ```ruby
27
+ connection = NetBooter::Http::RevA.new('XXX.XXX.XXX.XXX', username: "admin", password: "admin")
28
+ connection.status(1)
29
+ => true
30
+ connection.toggle(false, 1)
31
+ connection.status(1)
32
+ => false
33
+ ```
28
34
 
29
35
  ## Interface
30
36
 
@@ -1,34 +1,5 @@
1
- module NetBooter
2
- module Telnet
3
- end
4
- module Http
5
- end
6
- end
7
-
8
1
  require 'synaccess_connect/net_booter/error'
9
- require 'synaccess_connect/net_booter/telnet/telnet_connection'
10
- require 'synaccess_connect/net_booter/telnet/rev_a'
11
- require 'synaccess_connect/net_booter/telnet/rev_b'
12
-
13
- require 'synaccess_connect/net_booter/http/http_connection'
14
- require 'synaccess_connect/net_booter/http/rev_a'
15
- require 'synaccess_connect/net_booter/http/rev_b'
16
-
2
+ require 'synaccess_connect/net_booter/http'
17
3
 
18
-
19
- # if defined?(Rails)
20
- # module SynaccessConnect
21
- # class Railtie < Rails::Railtie
22
- # # initializer "carrierwave.setup_paths" do
23
- # # CarrierWave.root = Rails.root.join(Rails.public_path).to_s
24
- # # CarrierWave.base_path = ENV['RAILS_RELATIVE_URL_ROOT']
25
- # # end
26
-
27
- # # initializer "carrierwave.active_record" do
28
- # # ActiveSupport.on_load :active_record do
29
- # # require 'carrierwave/orm/activerecord'
30
- # # end
31
- # # end
32
- # end
33
- # end
34
- # end
4
+ module NetBooter
5
+ end
@@ -0,0 +1,8 @@
1
+ require 'synaccess_connect/net_booter/http/http_connection'
2
+ require 'synaccess_connect/net_booter/http/rev_a'
3
+ require 'synaccess_connect/net_booter/http/rev_b'
4
+
5
+ module NetBooter
6
+ module Http
7
+ end
8
+ end
@@ -1,128 +1,133 @@
1
- require "net/http"
1
+ require 'net/http'
2
2
  require 'nokogiri'
3
3
 
4
- class NetBooter::HttpConnection
5
- def initialize(host, options = {})
6
- @host = host
7
- @options = {
8
- :port => 80
9
- }.merge(options)
10
- end
4
+ module NetBooter
5
+
6
+ class HttpConnection
11
7
 
12
- # Get the status of an outlet
13
- #
14
- # Example:
15
- # >> netbooter.status(2)
16
- # => false
17
- #
18
- # Arguments:
19
- # +outlet+ Outlet number you want to check (1-based)
20
- #
21
- # Returns:
22
- # boolean - on/off status of the outlet
23
- def status(outlet = 1)
24
- statuses.fetch outlet do
25
- raise NetBooter::Error.new('Error communicating with relay')
8
+ def initialize(host, options = {})
9
+ @host = host
10
+ @options = {
11
+ :port => 80
12
+ }.merge(options)
26
13
  end
27
- end
28
14
 
29
- # Turn on the specified outlet
30
- # Example:
31
- # >> netbooter.toggle_on(2)
32
- # => true
33
- #
34
- # Arguments:
35
- # +outlet+ Outlet you want to turn on (1-based)
36
- #
37
- # Returns:
38
- # boolean - The new status of the outlet (should be true)
39
- def toggle_on(outlet = 1)
40
- toggle(outlet, true)
41
- end
15
+ # Get the status of an outlet
16
+ #
17
+ # Example:
18
+ # >> netbooter.status(2)
19
+ # => false
20
+ #
21
+ # Arguments:
22
+ # +outlet+ Outlet number you want to check (1-based)
23
+ #
24
+ # Returns:
25
+ # boolean - on/off status of the outlet
26
+ def status(outlet = 1)
27
+ statuses.fetch outlet do
28
+ raise NetBooter::Error.new('Error communicating with relay')
29
+ end
30
+ end
42
31
 
43
- # Turn off the specified outlet
44
- # Example:
45
- # >> netbooter.toggle_off(2)
46
- # => true
47
- #
48
- # Arguments:
49
- # +outlet+ Outlet you want to turn off (1-based)
50
- #
51
- # Returns:
52
- # boolean - The new status of the outlet (should be false)
53
- def toggle_off(outlet = 1)
54
- toggle(outlet, false)
55
- end
32
+ # Turn on the specified outlet
33
+ # Example:
34
+ # >> netbooter.toggle_on(2)
35
+ # => true
36
+ #
37
+ # Arguments:
38
+ # +outlet+ Outlet you want to turn on (1-based)
39
+ #
40
+ # Returns:
41
+ # boolean - The new status of the outlet (should be true)
42
+ def toggle_on(outlet = 1)
43
+ toggle(outlet, true)
44
+ end
56
45
 
57
- # Toggle the status of an outlet
58
- #
59
- # Example:
60
- # >> netbooter.toggle(1, false)
61
- # => false
62
- #
63
- # Arguments:
64
- # +outlet+ The outlet you want to toggle
65
- # +status+ Boolean. true to turn on, false to turn off
66
- #
67
- # Returns:
68
- # boolean - The new status of the outlet
69
- def toggle(outlet, status)
70
- current_status = status(outlet)
71
- toggle_relay(outlet) if current_status != status
72
- status
73
- end
46
+ # Turn off the specified outlet
47
+ # Example:
48
+ # >> netbooter.toggle_off(2)
49
+ # => true
50
+ #
51
+ # Arguments:
52
+ # +outlet+ Outlet you want to turn off (1-based)
53
+ #
54
+ # Returns:
55
+ # boolean - The new status of the outlet (should be false)
56
+ def toggle_off(outlet = 1)
57
+ toggle(outlet, false)
58
+ end
74
59
 
75
- # Get the status of all outlets on the device. Needs to be defined
76
- # in a subclass.
77
- #
78
- # Example:
79
- # >> netbooter.statuses
80
- # => { 1 => true, 2 => false }
81
- #
82
- # Arguments: none
83
- #
84
- # Returns:
85
- # A hash containing the outlet numbers and their respective status
86
- def statuses
87
- raise NotImplementedError.new
88
- end
60
+ # Toggle the status of an outlet
61
+ #
62
+ # Example:
63
+ # >> netbooter.toggle(1, false)
64
+ # => false
65
+ #
66
+ # Arguments:
67
+ # +outlet+ The outlet you want to toggle
68
+ # +status+ Boolean. true to turn on, false to turn off
69
+ #
70
+ # Returns:
71
+ # boolean - The new status of the outlet
72
+ def toggle(outlet, status)
73
+ current_status = status(outlet)
74
+ toggle_relay(outlet) if current_status != status
75
+ status
76
+ end
89
77
 
90
- # The method that contains the network command the relay. Needs to be defined
91
- # in a subclass.
92
- def toggle_relay(outlet)
93
- raise NotImplementedError.new('Must implement toggle_relay in subclass')
94
- end
78
+ # Get the status of all outlets on the device. Needs to be defined
79
+ # in a subclass.
80
+ #
81
+ # Example:
82
+ # >> netbooter.statuses
83
+ # => { 1 => true, 2 => false }
84
+ #
85
+ # Arguments: none
86
+ #
87
+ # Returns:
88
+ # A hash containing the outlet numbers and their respective status
89
+ def statuses
90
+ raise NotImplementedError.new
91
+ end
95
92
 
96
- private
93
+ # The method that contains the network command the relay. Needs to be defined
94
+ # in a subclass.
95
+ def toggle_relay(outlet)
96
+ raise NotImplementedError.new('Must implement toggle_relay in subclass')
97
+ end
98
+
99
+ private
97
100
 
98
- # Make an http request and return the result.
99
- def get_request(path)
100
- resp = nil
101
- begin
102
- Timeout::timeout(5) do
103
- resp = do_http_request(path)
101
+ # Make an http request and return the result.
102
+ def get_request(path)
103
+ resp = nil
104
+ begin
105
+ Timeout::timeout(5) do
106
+ resp = do_http_request(path)
107
+ end
108
+ rescue => e
109
+ raise NetBooter::Error.new("Error connecting to relay: #{e.message}")
104
110
  end
105
- rescue => e
106
- raise NetBooter::Error.new("Error connecting to relay: #{e.message}")
111
+ resp
107
112
  end
108
- resp
109
- end
110
113
 
111
- def do_http_request(path)
112
- resp = nil
113
- Net::HTTP.start(@host) do |http|
114
- req = Net::HTTP::Get.new(path)
114
+ def do_http_request(path)
115
+ resp = nil
116
+ Net::HTTP.start(@host) do |http|
117
+ req = Net::HTTP::Get.new(path)
115
118
 
116
- req.basic_auth @options[:username], @options[:password] if @options[:username] && @options[:password]
119
+ req.basic_auth @options[:username], @options[:password] if @options[:username] && @options[:password]
117
120
 
118
- resp = http.request(req)
121
+ resp = http.request(req)
119
122
 
120
- # Error checking. Allow 200s and 302s
121
- unless ['200', '302'].include? resp.code
122
- raise NetBooter::Error.new "Relay responded with #{resp.code}. Perhaps you have the wrong relay type specified."
123
+ # Error checking. Allow 200s and 302s
124
+ unless ['200', '302'].include? resp.code
125
+ raise NetBooter::Error.new "Relay responded with #{resp.code}. Perhaps you have the wrong relay type specified."
126
+ end
123
127
  end
128
+ resp
124
129
  end
125
- resp
130
+
126
131
  end
127
132
 
128
133
  end
@@ -1,24 +1,32 @@
1
- class NetBooter::Http::RevA < NetBooter::HttpConnection
2
- # So here's the deal... When you hit the switch page (pwrSw1.cgi) nothing happens.
3
- # It's only when hitting the status page AFTER visiting a swith page that
4
- # the relay is toggled.
5
- def toggle_relay(outlet)
6
- get_request("/pwrSw#{outlet}.cgi")
7
- get_request("/synOpStatus.shtml")
8
- end
1
+ module NetBooter
2
+
3
+ module Http
4
+
5
+ class RevA < HttpConnection
6
+ # So here's the deal... When you hit the switch page (pwrSw1.cgi) nothing happens.
7
+ # It's only when hitting the status page AFTER visiting a swith page that
8
+ # the relay is toggled.
9
+ def toggle_relay(outlet)
10
+ get_request("/pwrSw#{outlet}.cgi")
11
+ get_request("/synOpStatus.shtml")
12
+ end
9
13
 
10
- def statuses
11
- resp = get_request('/synOpStatus.shtml')
14
+ def statuses
15
+ resp = get_request('/synOpStatus.shtml')
12
16
 
13
- doc = Nokogiri::HTML(resp.body)
14
- nodes = doc.xpath('//img')
15
- status = {}
17
+ doc = Nokogiri::HTML(resp.body)
18
+ nodes = doc.xpath('//img')
19
+ status = {}
16
20
 
17
- nodes.each do |node|
18
- if node.values[0].match(/^led(off|on).gif$/)
19
- status[node.parent.parent.xpath('th').first.content.to_i] = $1 == 'on' ? true : false
21
+ nodes.each do |node|
22
+ if node.values[0].match(/^led(off|on).gif$/)
23
+ status[node.parent.parent.xpath('th').first.content.to_i] = $1 == 'on' ? true : false
24
+ end
25
+ end
26
+ status
20
27
  end
21
28
  end
22
- status
29
+
23
30
  end
31
+
24
32
  end
@@ -1,20 +1,30 @@
1
- class NetBooter::Http::RevB < NetBooter::HttpConnection
2
- def toggle_relay(outlet)
3
- get_request("/cmd.cgi?rly=#{outlet - 1}")
4
- end
1
+ module NetBooter
2
+
3
+ module Http
4
+
5
+ class RevB < HttpConnection
6
+
7
+ def toggle_relay(outlet)
8
+ get_request("/cmd.cgi?rly=#{outlet - 1}")
9
+ end
5
10
 
6
- def statuses
7
- resp = get_request('/status.xml')
11
+ def statuses
12
+ resp = get_request('/status.xml')
8
13
 
9
- doc = Nokogiri::XML(resp.body)
10
- nodes = doc.xpath('/response/*')
14
+ doc = Nokogiri::XML(resp.body)
15
+ nodes = doc.xpath('/response/*')
11
16
 
12
- status = {}
13
- nodes.each do |node|
14
- if node.name.match(/^rly(\d+)$/)
15
- status[$1.to_i + 1] = node.content == '1' ? true : false
17
+ status = {}
18
+ nodes.each do |node|
19
+ if node.name.match(/^rly(\d+)$/)
20
+ status[$1.to_i + 1] = node.content == '1' ? true : false
21
+ end
22
+ end
23
+ status
16
24
  end
25
+
17
26
  end
18
- status
27
+
19
28
  end
20
- end
29
+
30
+ end
@@ -0,0 +1,3 @@
1
+ module SynaccessConnect
2
+ VERSION = "0.3.0".freeze
3
+ end
metadata CHANGED
@@ -1,57 +1,57 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: synaccess_connect
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.2
4
+ version: 0.3.0
5
5
  platform: ruby
6
6
  authors:
7
- - Jason Hanggi
7
+ - Table XI
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2014-03-12 00:00:00.000000000 Z
11
+ date: 2018-01-22 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: nokogiri
15
15
  requirement: !ruby/object:Gem::Requirement
16
16
  requirements:
17
- - - ~>
17
+ - - ! '>='
18
18
  - !ruby/object:Gem::Version
19
- version: '0'
19
+ version: '1.6'
20
20
  type: :runtime
21
21
  prerelease: false
22
22
  version_requirements: !ruby/object:Gem::Requirement
23
23
  requirements:
24
- - - ~>
24
+ - - ! '>='
25
25
  - !ruby/object:Gem::Version
26
- version: '0'
26
+ version: '1.6'
27
27
  - !ruby/object:Gem::Dependency
28
28
  name: rspec
29
29
  requirement: !ruby/object:Gem::Requirement
30
30
  requirements:
31
31
  - - ~>
32
32
  - !ruby/object:Gem::Version
33
- version: '0'
33
+ version: '3.6'
34
34
  type: :development
35
35
  prerelease: false
36
36
  version_requirements: !ruby/object:Gem::Requirement
37
37
  requirements:
38
38
  - - ~>
39
39
  - !ruby/object:Gem::Version
40
- version: '0'
40
+ version: '3.6'
41
41
  - !ruby/object:Gem::Dependency
42
42
  name: vcr
43
43
  requirement: !ruby/object:Gem::Requirement
44
44
  requirements:
45
45
  - - ~>
46
46
  - !ruby/object:Gem::Version
47
- version: '0'
47
+ version: '2.8'
48
48
  type: :development
49
49
  prerelease: false
50
50
  version_requirements: !ruby/object:Gem::Requirement
51
51
  requirements:
52
52
  - - ~>
53
53
  - !ruby/object:Gem::Version
54
- version: '0'
54
+ version: '2.8'
55
55
  - !ruby/object:Gem::Dependency
56
56
  name: webmock
57
57
  requirement: !ruby/object:Gem::Requirement
@@ -68,7 +68,7 @@ dependencies:
68
68
  version: '1.16'
69
69
  description: Ruby interface to connecting to Synaccess netBooter power relays
70
70
  email:
71
- - jason@tablexi.com
71
+ - devs@tablexi.com
72
72
  executables: []
73
73
  extensions: []
74
74
  extra_rdoc_files: []
@@ -76,12 +76,11 @@ files:
76
76
  - README.md
77
77
  - lib/synaccess_connect.rb
78
78
  - lib/synaccess_connect/net_booter/error.rb
79
+ - lib/synaccess_connect/net_booter/http.rb
79
80
  - lib/synaccess_connect/net_booter/http/http_connection.rb
80
81
  - lib/synaccess_connect/net_booter/http/rev_a.rb
81
82
  - lib/synaccess_connect/net_booter/http/rev_b.rb
82
- - lib/synaccess_connect/net_booter/telnet/rev_a.rb
83
- - lib/synaccess_connect/net_booter/telnet/rev_b.rb
84
- - lib/synaccess_connect/net_booter/telnet/telnet_connection.rb
83
+ - lib/synaccess_connect/version.rb
85
84
  homepage: https://github.com/tablexi/synaccess
86
85
  licenses: []
87
86
  metadata: {}
@@ -101,7 +100,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
101
100
  version: '0'
102
101
  requirements: []
103
102
  rubyforge_project:
104
- rubygems_version: 2.2.2
103
+ rubygems_version: 2.4.8
105
104
  signing_key:
106
105
  specification_version: 4
107
106
  summary: ''
@@ -1,23 +0,0 @@
1
- class NetBooter::Telnet::RevA < NetBooter::TelnetConnection
2
-
3
- def override_options
4
- {
5
- :prompt => /^\r>$/n,
6
- :status_string => 'On'
7
- }
8
- end
9
-
10
- def local_statuses
11
- parse_status @connection.cmd("pshow")
12
- end
13
-
14
- private
15
- def authenticate
16
- sleep 0.1
17
- @connection.puts(@options[:username])
18
- sleep 0.1
19
- @connection.puts(@options[:password])
20
- @connection.cmd('')
21
- sleep 0.1
22
- end
23
- end
@@ -1,17 +0,0 @@
1
- class NetBooter::Telnet::RevB < NetBooter::TelnetConnection
2
- def local_statuses
3
- @connection.cmd("")
4
- sleep 0.25
5
- parse_status @connection.cmd("pshow")
6
- end
7
-
8
- private
9
-
10
- def authenticate
11
- @connection.cmd('login')
12
- sleep 0.25
13
- @connection.puts(@options[:username])
14
- sleep 0.25
15
- @connection.puts(@options[:password])
16
- end
17
- end
@@ -1,119 +0,0 @@
1
- require 'net/telnet'
2
-
3
- class NetBooter::TelnetConnection
4
- def initialize(host, options = {})
5
- @host = host
6
- @options = { :port => 23,
7
- :binmode => false,
8
- :prompt => /^[>]/n,
9
- :timeout => 1,
10
- :status_string => 'ON'
11
- }.merge(override_options).merge(options)
12
- end
13
-
14
- def override_options
15
- {}
16
- end
17
-
18
- def status(outlet = 1)
19
- statuses.fetch outlet do
20
- raise NetBooter::Error.new('Error communicating with relay')
21
- end
22
- end
23
-
24
- def statuses
25
- with_connection :default => {} do
26
- local_statuses
27
- end
28
- end
29
-
30
- def toggle_on(outlet = 1)
31
- toggle(true, outlet)
32
- end
33
-
34
- def toggle_off(outlet = 1)
35
- toggle(false, outlet)
36
- end
37
-
38
- def toggle(outlet, status)
39
- status_string = status ? '1' : '0'
40
- with_connection do
41
- @connection.cmd("pset #{outlet} #{status_string}")
42
- end
43
- status
44
- end
45
-
46
- private
47
-
48
- def authenticate
49
- raise NotImplementedException.new('Must implement in subclass')
50
- end
51
-
52
- # Parse the text that we get back from pshow
53
- # returns hash of { channel => boolean_status }
54
- def parse_status(response)
55
- statuses = {}
56
- return statuses unless response
57
-
58
- response.split("\n").each do |line|
59
- parts = line.split('|').map &:strip
60
- next unless parts[0] =~ /^\d+/
61
- statuses[parts[0].to_i] = parts[2] == @options[:status_string]
62
- end
63
- statuses
64
- end
65
-
66
- def connect
67
- retry_block 5 do
68
- @connection = Net::Telnet::new(
69
- 'Host' => @host,
70
- 'Port' => @options[:port],
71
- 'Binmode' => @options[:binmode],
72
- 'Prompt' => @options[:prompt],
73
- 'Timeout' => @options[:timeout]
74
- )
75
- authenticate if @options[:username] && @options[:password]
76
- end
77
- end
78
-
79
- def disconnect
80
- begin
81
- @connection.close if @connection
82
- rescue
83
- # do nothing
84
- end
85
- # needs a little bit of delay after disconnecting
86
- # if so the next connection doesn't get refused while
87
- # this one is closing. Number arrived at by experimentation
88
- sleep 0.02
89
- end
90
-
91
- def with_connection options = {}
92
- begin
93
- connect
94
- raise NetBooter::Error.new('Unable to connect') if @connection.nil?
95
- output = yield
96
- rescue => e
97
- puts "ERROR! #{e.message}"
98
- # puts e.backtrace.join("\n")
99
- output = options[:default] if options[:default]
100
- ensure
101
- disconnect
102
- end
103
-
104
- output
105
- end
106
-
107
- def retry_block max_attempts, options = {}
108
- max_attempts.times do |i|
109
- begin
110
- yield
111
- break # if we made it through the yield without an error we can break
112
- rescue => e
113
- puts e.message
114
- raise e if i + 1 >= max_attempts
115
- end
116
- end
117
- end
118
-
119
- end