Class: WifiWand::BaseModel

Inherits:
Object
  • Object
show all
Defined in:
lib/wifi-wand/models/base_model.rb

Direct Known Subclasses

MacOsModel

Defined Under Namespace

Classes: OsCommandError

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(options) ⇒ BaseModel

Returns a new instance of BaseModel.



14
15
16
17
18
19
20
21
22
23
24
# File 'lib/wifi-wand/models/base_model.rb', line 14

def initialize(options)
  @verbose_mode = options.verbose

  if options.wifi_interface
    if is_wifi_interface?(options.wifi_interface)
      @wifi_interface = options.wifi_interface
    else
      raise Error.new("#{options.wifi_interface} is not a Wi-Fi interface.")
    end
  end
end

Instance Attribute Details

#verbose_modeObject

Returns the value of attribute verbose_mode.



12
13
14
# File 'lib/wifi-wand/models/base_model.rb', line 12

def verbose_mode
  @verbose_mode
end

#wifi_interfaceObject

Returns the value of attribute wifi_interface.



12
13
14
# File 'lib/wifi-wand/models/base_model.rb', line 12

def wifi_interface
  @wifi_interface
end

Instance Method Details

#connect(network_name, password = nil) ⇒ Object

Connects to the passed network name, optionally with password. Turns wifi on first, in case it was turned off. Relies on subclass implementation of os_level_connect().



132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
# File 'lib/wifi-wand/models/base_model.rb', line 132

def connect(network_name, password = nil)
  # Allow symbols and anything responding to to_s for user convenience
  network_name = network_name.to_s if network_name
  password     = password.to_s     if password

  if network_name.nil? || network_name.empty?
    raise Error.new("A network name is required but was not provided.")
  end
  wifi_on
  os_level_connect(network_name, password)

  # Verify that the network is now connected:
  actual_network_name = connected_network_name

  unless actual_network_name == network_name
    message = %Q{Expected to connect to "#{network_name}" but }
    if actual_network_name.nil? || actual_network_name.empty?
      message << "unable to connect to any network."
    else
      message << %Q{connected to "#{connected_network_name}" instead.}
    end
    message << ' Did you ' << (password ? "provide the correct password?" : "need to provide a password?")
    raise Error.new(message)
  end
  nil
end

#connected_to?(network_name) ⇒ Boolean

Returns:

  • (Boolean)


124
125
126
# File 'lib/wifi-wand/models/base_model.rb', line 124

def connected_to?(network_name)
  network_name == connected_network_name
end

#connected_to_internet?Boolean

This method returns whether or not there is a working Internet connection, which is defined as success for both name resolution and an HTTP get. Domains attempted are google.com and baidu.com. Success is defined as either being successful. Commands for the multiple sites are run in parallel, in threads, to save time.

Returns:

  • (Boolean)


52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
# File 'lib/wifi-wand/models/base_model.rb', line 52

def connected_to_internet?
  return false unless wifi_on? # no need to try

  # We cannot use run_os_command for the running of external processes here,
  # because they are multithreaded, and the output will get mixed up.
  test_using_dig = -> do
    domains = %w(google.com  baidu.com)
    puts "Calling dig on domains #{domains}..." if verbose_mode

    threads = domains.map do |domain|
      Thread.new do
        output = `dig +short #{domain}`
        output.length > 0
      end
    end

    threads.each(&:join)
    values = threads.map(&:value)
    success = values.include?(true)
    puts "Results of dig: success == #{success}, values were #{values}." if verbose_mode
    success
  end

  test_using_http_get = -> do
    test_sites = %w{https://www.google.com  http://baidu.com}
    puts "Calling HTTP.get on sites #{test_sites}..." if verbose_mode

    threads = test_sites.map do |site|
      Thread.new do
        url = URI.parse(site)
        success = true
        start = Time.now

        begin
          Net::HTTP.start(url.host) do |http|
            http.read_timeout = 3 # seconds
            http.get('.')
            puts "Finished HTTP get #{url.host} in #{Time.now - start} seconds" if verbose_mode
          end
        rescue => e
          puts "Got error for host #{url.host} in #{Time.now - start} seconds:\n#{e.inspect}" if verbose_mode
          success = false
        end

        success
      end
    end

    threads.each(&:join)
    values = threads.map(&:value)
    success = values.include?(true)

    puts "Results of HTTP.get: success == #{success}, values were #{values}." if verbose_mode
    success
  end

  test_using_dig.() && test_using_http_get.()
end

#cycle_networkObject

Turns wifi off and then on, reconnecting to the originally connecting network.



113
114
115
116
117
118
119
120
121
# File 'lib/wifi-wand/models/base_model.rb', line 113

def cycle_network
  # TODO: Make this network name saving and restoring conditional on it not having a password.
  # If the disabled code below is enabled, an error will be raised if a password is required,
  # even though it is stored.
  # network_name = connected_network_name
  wifi_off
  wifi_on
  # connect(network_name) if network_name
end

#preferred_network_password(preferred_network_name) ⇒ Object



171
172
173
174
175
176
177
178
# File 'lib/wifi-wand/models/base_model.rb', line 171

def preferred_network_password(preferred_network_name)
  preferred_network_name = preferred_network_name.to_s
  if preferred_networks.include?(preferred_network_name)
    os_level_preferred_network_password(preferred_network_name)
  else
    raise Error.new("Network #{preferred_network_name} not in preferred networks list.")
  end
end

#public_ip_address_infoObject

Reaches out to ipinfo.io to get public IP address information in the form of a hash. You may need to enclose this call in a begin/rescue.



241
242
243
# File 'lib/wifi-wand/models/base_model.rb', line 241

def public_ip_address_info
  JSON.parse(`curl -s ipinfo.io`)
end

#random_mac_addressObject



246
247
248
249
250
# File 'lib/wifi-wand/models/base_model.rb', line 246

def random_mac_address
  bytes = Array.new(6) { rand(256) }
  chars = bytes.map { |b| "%02x" % b }
  chars.join(':')
end

#remove_preferred_networks(*network_names) ⇒ Object

Removes the specified network(s) from the preferred network list.

Parameters:

  • network_names

    names of networks to remove; may be empty or contain nonexistent networks can be a single arg which is an array of names or 1 name string per arg

Returns:

  • names of the networks that were removed (excludes non-preexisting networks)



164
165
166
167
168
# File 'lib/wifi-wand/models/base_model.rb', line 164

def remove_preferred_networks(*network_names)
  network_names = network_names.first if network_names.first.is_a?(Array) && network_names.size == 1
  networks_to_remove = network_names & preferred_networks # exclude any nonexistent networks
  networks_to_remove.each { |name| remove_preferred_network(name) }
end

#run_os_command(command, raise_on_error = true) ⇒ Object



26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
# File 'lib/wifi-wand/models/base_model.rb', line 26

def run_os_command(command, raise_on_error = true)
  if verbose_mode
    puts CommandOutputFormatter.command_attempt_as_string(command)
  end

  start_time = Time.now
  output = `#{command} 2>&1` # join stderr with stdout

  if verbose_mode
    puts "Duration: #{'%.4f' % [Time.now - start_time]} seconds"
    puts CommandOutputFormatter.command_result_as_string(output)
  end

  if $?.exitstatus != 0 && raise_on_error
    raise OsCommandError.new($?.exitstatus, command, output)
  end

  output
end

#till(target_status, wait_interval_in_secs = nil) ⇒ Object

Waits for the Internet connection to be in the desired state. Connected is defined as being able to connect to an external web site.

Parameters:

  • target_status

    must be in [:conn, :disc, :off, :on]; waits for that state

  • wait_interval_in_secs (defaults to: nil)

    sleeps this interval between retries; if nil or absent, a default will be provided



187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
# File 'lib/wifi-wand/models/base_model.rb', line 187

def till(target_status, wait_interval_in_secs = nil)
  # One might ask, why not just put the 0.5 up there as the default argument.
  # We could do that, but we'd still need the line below in case nil
  # was explicitly specified. The default argument of nil above emphasizes that
  # the absence of an argument and a specification of nil will behave identically.
  wait_interval_in_secs ||= 0.5

  finished_predicates = {
      conn: -> { connected_to_internet? },
      disc: -> { ! connected_to_internet? },
      on:   -> { wifi_on? },
      off:  -> { ! wifi_on? }
  }

  finished_predicate = finished_predicates[target_status]

  if finished_predicate.nil?
    raise ArgumentError.new(
        "Option must be one of #{finished_predicates.keys.inspect}. Was: #{target_status.inspect}")
  end

  loop do
    return if finished_predicate.()
    sleep(wait_interval_in_secs)
  end
end

#try_os_command_until(command, stop_condition, max_tries = 100) ⇒ Object

Tries an OS command until the stop condition is true.

Returns:

  • the stdout produced by the command, or nil if max_tries was reached



219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
# File 'lib/wifi-wand/models/base_model.rb', line 219

def try_os_command_until(command, stop_condition, max_tries = 100)

  report_attempt_count = ->(attempt_count) do
    puts "Command was executed #{attempt_count} time(s)." if verbose_mode
  end

  max_tries.times do |n|
    stdout_text = run_os_command(command)
    if stop_condition.(stdout_text)
      report_attempt_count.(n + 1)
      return stdout_text
    end
  end

  report_attempt_count.(max_tries)
  nil
end