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.



30
31
32
33
34
35
36
# File 'lib/wifi-wand/models/base_model.rb', line 30

def initialize(options)
  @verbose_mode = options.verbose
  if options.wifi_port && (! is_wifi_port?(options.wifi_port))
    raise "#{options.wifi_port} is not a Wi-Fi interface."
  end
  @wifi_port = options.wifi_port
end

Instance Attribute Details

#verbose_modeObject

Returns the value of attribute verbose_mode.



9
10
11
# File 'lib/wifi-wand/models/base_model.rb', line 9

def verbose_mode
  @verbose_mode
end

#wifi_portObject

Returns the value of attribute wifi_port.



9
10
11
# File 'lib/wifi-wand/models/base_model.rb', line 9

def wifi_port
  @wifi_port
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().



146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
# File 'lib/wifi-wand/models/base_model.rb', line 146

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 "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
      message << %Q{connected to "#{connected_network_name}" instead.}
    else
      message << "unable to connect to any network. Did you "
    end
    message << (password ? "provide the correct password?" : "need to provide a password?")
    raise message
  end
  nil
end

#connected_to?(network_name) ⇒ Boolean

Returns:

  • (Boolean)


138
139
140
# File 'lib/wifi-wand/models/base_model.rb', line 138

def connected_to?(network_name)
  network_name == connected_network_name
end

#connected_to_internet?Boolean

TODO Investigate using Curl options: –connect-timeout 1 –max-time 2 –retry 0 to greatly simplify this method.

Returns:

  • (Boolean)


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
110
111
112
113
114
115
116
117
118
119
120
121
122
123
# File 'lib/wifi-wand/models/base_model.rb', line 74

def connected_to_internet?

  tempfile = Tempfile.open('wifi-wand-')

  begin
    start_status_script = -> do
      script = "curl --silent --head http://www.google.com/ > /dev/null ; echo $? > #{tempfile.path} &"
      pid = Process.spawn(script)
      Process.detach(pid)
      pid
    end

    process_is_running = ->(pid) do
      script = %Q{ps -p #{pid} > /dev/null; echo $?}
      output = `#{script}`.chomp
      output == "0"
    end

    get_connected_state_from_curl = -> do
      tempfile.close
      File.read(tempfile.path).chomp == '0'
    end

    # Do one run, iterating during the timeout period to see if the command has completed
    do_one_run = -> do
      end_time = Time.now + 3
      pid = start_status_script.()
      while Time.now < end_time
        if process_is_running.(pid)
          sleep 0.5
        else
          return get_connected_state_from_curl.()
        end
      end
      Process.kill('KILL', pid) if process_is_running.(pid)
      :hung
    end

    3.times do
      connected = do_one_run.()
      return connected if connected != :hung
    end

    raise "Could not determine Internet status."

  ensure
    tempfile.unlink
  end

end

#cycle_networkObject

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



127
128
129
130
131
132
133
134
135
# File 'lib/wifi-wand/models/base_model.rb', line 127

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

#nameservers_using_resolv_confObject

Though this is strictly not OS-agnostic, it will be used by most OS’s, and can be overridden by subclasses (e.g. Windows).

Returns:

  • array of nameserver IP addresses from /etc/resolv.conf, or nil if not found



252
253
254
255
256
257
258
# File 'lib/wifi-wand/models/base_model.rb', line 252

def nameservers_using_resolv_conf
  begin
    File.readlines('/etc/resolv.conf').grep(/^nameserver /).map { |line| line.split.last }
  rescue Errno::ENOENT
    nil
  end
end

#nameservers_using_scutilObject



261
262
263
264
265
266
267
# File 'lib/wifi-wand/models/base_model.rb', line 261

def nameservers_using_scutil
  output = run_os_command('scutil --dns')
  nameserver_lines_scoped_and_unscoped = output.split("\n").grep(/^\s*nameserver\[/)
  unique_nameserver_lines = nameserver_lines_scoped_and_unscoped.uniq # take the union
  nameservers = unique_nameserver_lines.map { |line| line.split(' : ').last.strip }
  nameservers
end

#preferred_network_password(preferred_network_name) ⇒ Object



182
183
184
185
186
187
188
189
# File 'lib/wifi-wand/models/base_model.rb', line 182

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 "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.



244
245
246
# File 'lib/wifi-wand/models/base_model.rb', line 244

def public_ip_address_info
  JSON.parse(`curl -s ipinfo.io`)
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

Returns:

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



176
177
178
179
# File 'lib/wifi-wand/models/base_model.rb', line 176

def remove_preferred_networks(*network_names)
  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



39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
# File 'lib/wifi-wand/models/base_model.rb', line 39

def run_os_command(command, raise_on_error = true)

  output = `#{command} 2>&1` # join stderr with stdout

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

  if @verbose_mode
    puts "\n\n#{'-' * 79}\nCommand: #{command}\n\n"
    puts "#{output}#{'-' * 79}\n\n"
  end

  output
end

#till(target_status, wait_interval_in_secs = nil) ⇒ Object

Waits for the Internet connection to be in the desired state.

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



197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
# File 'lib/wifi-wand/models/base_model.rb', line 197

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



230
231
232
233
234
235
236
237
238
# File 'lib/wifi-wand/models/base_model.rb', line 230

def try_os_command_until(command, stop_condition, max_tries = 100)
  max_tries.times do
    stdout = run_os_command(command)
    if stop_condition.(stdout)
      return stdout
    end
  end
  nil
end