synaccess_connect 0.2.2 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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