# Downloads and installs browser drivers. class DriverDownloader require 'zlib' def initialize(verbose = true) @verbose = verbose config_file_path = "#{Dir.pwd}/drivers/config.yml" @data = File.open(config_file_path) { |f| YAML.safe_load(f) } @installed_versions = @data['installed_versions'] end # This must be implemented in all child classes! # # Returns a string for the driver name in file structure. def browser_name raise 'This class should not be initialized! Please use subclass.' end # This must be implemented in all child classes! # # returns the string url where all downloads can be located. def driver_url raise 'This class should not be initialized! Please use subclass.' end # This must be implemented in all child classes! # # returns an array of all available platforms for the driver. def all_platforms raise 'This class should not be initialized! Please use subclass.' end # This must be implemented in all child classes! # # Returns the most recent version of chromedriver for the # desired platform. # platform must be one of: linux32, linux64, mac32, win32 def latest_driver_version(_platform) raise 'This class should not be initialized! Please use subclass.' end # Returns all available versions of Chromedriver def all_driver_versions raise 'This class should not be initialized! Please use subclass.' end # This must be implemented in all child classes! # # Returns the url for the desired version of driver # version: string - must match exactly the version in the download URL # platform: string - must be in all_platforms def driver_download_url(_version, _platform) raise 'This class should not be initialized! Please use subclass.' end # Returns the destination folder for the browser driver for the # desired platform. # platform: string - must match the appropriate platform for # the particular driver def path_for(platform, version) raise unknown_platform_error(platform) unless valid_platform?(platform) last_part = instance_of?(EdgedriverDownloader) ? version : platform "#{Dir.pwd}/drivers/#{browser_name}/#{last_part}/" end # Downloads and installs driver # version: string - the exact version number for the download # platform: string - must match the appropriate platform for # the particular driver def install_driver(version, platform) raise unknown_platform_error(platform) unless valid_platform?(platform) if @installed_versions[browser_name][platform].eql?(version.to_s) puts "Driver version #{version} already installed for #{platform}" if @verbose return false end driver_path = path_for(platform, version) download_url = driver_download_url(version, platform) puts "installing '#{download_url}' into '#{driver_path}'" if @verbose download_and_unzip(driver_path, download_url) update_browser_version(platform, version) end # Installs the latest version of chromedriver for the specified platform. # platform: string - must match the appropriate platform for # the particular driver def upgrade_driver(platform) raise unknown_platform_error(platform) unless valid_platform?(platform) latest_version = latest_driver_version(platform) install_driver(latest_version, platform) end # Installs the latest version of chromedriver for all platforms. def upgrade_driver_all_platforms all_platforms.each { |platform| upgrade_driver(platform) } end # Installs the specified version of specified driver for all platforms. def install_driver_all_platforms(version) all_platforms.each { |platform| install_driver(version, platform) } end # Downloads and installs driver to appropriate path # driver_path: string - path to install driver (must end with '/') # download_url: string - URL of the driver desired def download_and_unzip(driver_path, download_url) FileUtils.mkdir_p(driver_path) destination_file_name = "#{driver_path}#{File.basename(download_url)}" FileUtils.rm_f destination_file_name File.open(destination_file_name, 'wb') do |saved_file| saved_file.write(HTTParty.get(download_url, verify: false).parsed_response) end raise "Could not download #{download_url}" unless File.exist?(destination_file_name) extract_and_delete(destination_file_name, driver_path) unless instance_of?(EdgedriverDownloader) FileUtils.rm_f "#{destination_file_name}.zip" end private # Gem::Version lets versions get compared in a way that makes sense for # semantic versions. For example it makes 2.38 greater than 2.5 def version_of(num_str) Gem::Version.new(num_str) end # Updates the hash of the installed versions and then saves the Yaml file def update_browser_version(platform, version) @installed_versions[browser_name][platform] = version.to_s save_data end # Saves @data to config.yml file def save_data File.open("#{Dir.pwd}/drivers/config.yml", 'w') { |f| f.write(@data.to_yaml) } end # Checks to see if the passed in platform is valid for the particular browser # # Requires all_platforms to be defined in the instantiated class. def valid_platform?(platform) all_platforms.include?(platform) end # Returns instance of an UnknownPlatform Error to be thrown # # Requires all_platforms to be defined in the instantiated class. def unknown_platform_error(platform) msg = "Unknown platform #{platform}, valid options: #{all_platforms}." raise UnknownPlatformError.new(platform, msg) end def unknown_version_error(version) msg = "Unknown version '#{version}', valid options: #{@all_driver_versions}." raise UnknownVersionError.new(version, msg) end # Extracts a file and deletes the compressed file # # destination_file_name: string - full file path to the compressed file. # driver_path: string - the full path to the destination folder. def extract_and_delete(destination_file_name, driver_path) if destination_file_name.end_with?('.tar.gz') File.open(destination_file_name, 'rb') do |f| gz = Zlib::GzipReader.new(f) File.open("#{driver_path}geckodriver", 'wb', 0o0755) { |new_file| new_file.puts(gz.read) } end else Archive::Zip.extract(destination_file_name, driver_path, overwrite: :all) end files_to_delete = Dir.glob("#{driver_path}*.{zip,gz}") FileUtils.rm_rf(files_to_delete) if files_to_delete.size.eql?(1) end end