Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Ruby downloader #77

Closed
wants to merge 29 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
29 commits
Select commit Hold shift + click to select a range
88775c4
Update gem information and development dependencies
agross Dec 9, 2015
37cd46d
Use rubyzip to extract zip files, as unzip is likely not installed on…
agross Dec 9, 2015
350b465
Support Windows x64
agross Dec 9, 2015
19d6063
Downloading the same phantomjs version as the gem version
agross Dec 9, 2015
71b5441
Use Dir.mktmpdir instead of manually creating and cleaning the downlo…
agross Dec 9, 2015
651b5ff
Refactoring
agross Dec 9, 2015
df24d98
Download using Ruby with percentage
agross Dec 9, 2015
a50ed94
Fix missing parenthesis
agross Dec 9, 2015
76a544d
Run travis on linux and OSX
agross Dec 9, 2015
d2a0962
Remove Ruby 1.9 from travis builds as mime-types-data requires Ruby >…
agross Dec 9, 2015
a17c434
Use multi-platform which implementation
agross Dec 9, 2015
7e32baa
Log download URL
agross Dec 9, 2015
fa32364
On Windows, define phantomjs.exe as the executable
agross Dec 9, 2015
e2a0092
Fix phantomjs runner spec for phantomjs 2.0
agross Dec 9, 2015
81cd034
Fix specs
agross Dec 9, 2015
c2ff828
rspec 3 syntax
agross Dec 9, 2015
70caca9
Make sure phantomjs is executable
agross Dec 9, 2015
3f62377
Remove info about cURL, wget and unzip from readme
agross Dec 9, 2015
f810b46
Exclude Ruby 2.2 from MacOS build
agross Dec 11, 2015
5d74626
Report errors for bunzip2 and tar
agross Dec 11, 2015
6ebe3a3
Platform#temp_path is no longer needed
agross Dec 11, 2015
2b12601
Use version that works on MacOS El Capitan
agross Dec 11, 2015
83bd604
Add note about WebMock, closes #1
agross Feb 18, 2016
8b82bc9
Update to 2.1.1
agross Mar 14, 2016
a567b5f
Tweak Travis configuration to use more Rubies and caching
agross Mar 14, 2016
3017182
Add download retry
agross Mar 14, 2016
6f8629b
Work around Net::HTTP errors on Ruby 1.9
agross Mar 14, 2016
2bd661f
Do not verify SSL certificates on Windows only (where CAcerts are gen…
agross Mar 14, 2016
1238ab0
Exclude failing Travis builds and be more specific about Ruby 2.2
agross Mar 14, 2016
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 20 additions & 3 deletions .travis.yml
Original file line number Diff line number Diff line change
@@ -1,15 +1,32 @@
# Use the new container-based Travis infrastructure.
sudo: false

language: ruby
cache:
- bundler
before_install:
- gem install bundler
script:
- bundle
- bundle exec rspec
os:
- linux
- osx
rvm:
- 1.9
- 2.0
- 2.1
- 2.2
- 2.2.3
- ruby-head
- jruby-19mode
matrix:
exclude:
- rvm: ruby-head
os: osx
- rvm: jruby-19mode
os: osx

notifications:
email:
on_success: always
on_failure: always
before_install:
- gem install bundler
17 changes: 15 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,11 @@
I am lazy as hell, and wanted to be able to install [PhantomJS](http://phantomjs.org) via Rubygems/Bundler when using [poltergeist](https://github.com/jonleighton/poltergeist).

It keeps installations of phantomjs in `$HOME/.phantomjs/VERSION/PLATFORM`. When you call `Phantomjs.path`, it will return the path to the phantomjs executable in there. If that is not present, it will first fetch and
install the prebuilt packages suitable for the current plattform (currently Linux 32/64 or OS X supported).
install the prebuilt packages suitable for the current plattform (currently Linux 32/64, OS X or Windows supported).

If there is a phantomjs executable in your `$PATH` that matches the version number packaged in this gem, this one will be used instead of installing one in your `$HOME/.phantomjs`.

You will need `cURL` or `wget` on your system. For extraction, `bunzip2` and `tar` are required on Linux, and `unzip` on OS X. They should be installed already.
For extraction, `bunzip2` and `tar` are required on Linux. They should be installed already.

**TL;DR:** Instead of manually installing phantomjs on your machines, use this gem. It will take care of it.

Expand Down Expand Up @@ -56,6 +56,19 @@ end

Check out [the poltergeist docs](https://www.ruby-toolbox.com/gems/phantomjs) for all the options you can pass in there.

## Usage with WebMock

WebMock will intercept the download of phantomjs. You may want to disable WebMock while the phantomjs downloader runs, e.g. by adding the following lines to `spec/support/phantomjs.rb`:

```ruby
begin
WebMock.disable!
Phantomjs.platform
ensure
WebMock.enable!
end
```

## A note about versions.

The gem version consists of 4 digits: The first 3 indicate the phantomjs release installed via this gem, the last one is the internal version of this gem, in case I screw things up and need to push another release in the interim.
Expand Down
4 changes: 2 additions & 2 deletions lib/phantomjs.rb
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
require "phantomjs/version"
require 'phantomjs/version'
require 'fileutils'

module Phantomjs
Expand Down Expand Up @@ -30,7 +30,7 @@ def platform
platform.ensure_installed!
platform
else
raise UnknownPlatform, "Could not find an appropriate PhantomJS library for your platform (#{RUBY_PLATFORM} :( Please install manually."
raise UnknownPlatform, "Could not find an appropriate PhantomJS library for your platform (#{RUBY_PLATFORM}) :( Please install manually."
end
end

Expand Down
187 changes: 140 additions & 47 deletions lib/phantomjs/platform.rb
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
require 'net/http'
require 'tmpdir'
require 'zip'

module Phantomjs
class Platform
class << self
Expand All @@ -9,21 +13,20 @@ def architecture
RbConfig::CONFIG['host_cpu']
end

def temp_path
ENV['TMPDIR'] || ENV['TEMP'] || '/tmp'
end

def phantomjs_path
if system_phantomjs_installed?
system_phantomjs_path
else
File.expand_path File.join(Phantomjs.base_dir, platform, 'bin/phantomjs')
File.expand_path(File.join(Phantomjs.base_dir, platform, 'bin', phantomjs_executable))
end
end

def phantomjs_executable
'phantomjs'
end

def system_phantomjs_path
`which phantomjs`.delete("\n")
rescue
which(phantomjs_executable)
end

def system_phantomjs_version
Expand All @@ -39,51 +42,145 @@ def installed?
File.exist?(phantomjs_path) || system_phantomjs_installed?
end

# TODO: Clean this up, it looks like a pile of...
def install!
STDERR.puts "Phantomjs does not appear to be installed in #{phantomjs_path}, installing!"
FileUtils.mkdir_p Phantomjs.base_dir

# Purge temporary directory if it is still hanging around from previous installs,
# then re-create it.
temp_dir = File.join(temp_path, 'phantomjs_install')
FileUtils.rm_rf temp_dir
FileUtils.mkdir_p temp_dir

Dir.chdir temp_dir do
unless system "curl -L --retry 5 -O #{package_url}" or system "wget -t 5 #{package_url}"
raise "\n\nFailed to load phantomjs! :(\nYou need to have cURL or wget installed on your system.\nIf you have, the source of phantomjs might be unavailable: #{package_url}\n\n"
in_tmp do
file = with_retry(5) do
download(package_url)
end

case package_url.split('.').last
when 'bz2'
system "bunzip2 #{File.basename(package_url)}"
system "tar xf #{File.basename(package_url).sub(/\.bz2$/, '')}"
when 'zip'
system "unzip #{File.basename(package_url)}"
case File.extname(file)
when '.bz2'
bunzip(file)
when '.zip'
unzip(file)
else
raise "Unknown compression format for #{File.basename(package_url)}"
raise "Unknown compression format for #{file}"
end

# Find the phantomjs build we just extracted
extracted_dir = Dir['phantomjs*'].find { |path| File.directory?(path) }
move_to_local_directory
end
end

# Move the extracted phantomjs build to $HOME/.phantomjs/version/platform
if FileUtils.mv extracted_dir, File.join(Phantomjs.base_dir, platform)
STDOUT.puts "\nSuccessfully installed phantomjs. Yay!"
def ensure_installed!
install! unless installed?
end

private
def which(executable)
ENV['PATH']
.split(File::PATH_SEPARATOR)
.map { |path| File.join(path, executable) }
.select { |path| File.file?(path) }
.first
end

def in_tmp
Dir.mktmpdir('phantomjs_install') do |dir|
Dir.chdir(dir) do
yield if block_given?
end
end
end

def with_retry(tries = 5)
yield if block_given?
# http://tammersaleh.com/posts/rescuing-net-http-exceptions/
rescue Timeout::Error,
Errno::EINVAL,
Errno::ECONNRESET,
EOFError,
Net::HTTPBadResponse,
Net::HTTPHeaderSyntaxError,
Net::ProtocolError => e
warn('Retrying download...')
retry unless (tries -= 1).zero?
raise e
end

def download(uri, redirect_limit = 10)
fail ArgumentError, 'Too many HTTP redirects' if redirect_limit <= 0

uri = URI(uri)
file = File.basename(uri.path)

opts = { use_ssl: uri.scheme == 'https' }
opts[:verify_mode] = OpenSSL::SSL::VERIFY_NONE if self == Win32

Net::HTTP.start(uri.host, uri.port, opts) do |http|
request = Net::HTTP::Get.new(uri.request_uri)

# Clean up remaining files in tmp
if FileUtils.rm_rf temp_dir
STDOUT.puts "Removed temporarily downloaded files."
http.request(request) do |response|
case response
when Net::HTTPSuccess then
STDOUT.puts("Downloading from #{uri}")

File.open(file, 'wb') do |io|
downloaded = 0

response.read_body do |chunk|
downloaded += chunk.length
STDOUT.print(sprintf("\r%5.1f%", downloaded.to_f / response.content_length * 100))
STDOUT.flush

io.write(chunk)
end
end
when Net::HTTPRedirection then
location = response['Location']
return download(location, redirect_limit - 1)
else
fail "Unknown HTTP response #{response}"
end
end
end

raise "Failed to install phantomjs. Sorry :(" unless File.exist?(phantomjs_path)
file
end

def ensure_installed!
install! unless installed?
def bunzip(file)
bunzip = %W(bunzip2 #{file})
unless system(*bunzip)
fail "Failed to execute \"#{bunzip.join(' ')}\", exit status #{$?.exitstatus}"
end

tarfile = file.sub(/\.bz2$/, '')
tar = %W(tar xf #{tarfile})
unless system(*tar)
fail "Failed to execute \"#{tar.join(' ')}\", exit status #{$?.exitstatus}"
end
end

def unzip(file)
# Overwrite existing files.
Zip.on_exists_proc = true

Zip::File.open(file) do |zip|
zip.each do |entry|
entry.extract
end
end
end

def move_to_local_directory
extracted_dir = Dir['phantomjs*'].find { |path| File.directory?(path) }

fail "Could not find extracted phantomjs directory in #{File.join(Dir.pwd, 'phantomjs*')}" if extracted_dir.nil?

# Move the extracted phantomjs build to $HOME/.phantomjs/version/platform
target = File.join(Phantomjs.base_dir, platform)

FileUtils.mkdir_p(File.dirname(target))
FileUtils.mv(extracted_dir, target)

if File.exist?(phantomjs_path)
FileUtils.chmod(0755, phantomjs_path)
STDOUT.puts "\nSuccessfully installed phantomjs in #{phantomjs_path}. Yay!"
return
end

fail "Failed to install phantomjs. Could not find #{phantomjs_path}. Sorry :("
end
end

Expand All @@ -98,7 +195,7 @@ def platform
end

def package_url
'https://bitbucket.org/ariya/phantomjs/downloads/phantomjs-2.1.1-linux-x86_64.tar.bz2'
"https://bitbucket.org/ariya/phantomjs/downloads/phantomjs-#{Phantomjs.version}-linux-x86_64.tar.bz2"
end
end
end
Expand All @@ -114,7 +211,7 @@ def platform
end

def package_url
'https://bitbucket.org/ariya/phantomjs/downloads/phantomjs-2.1.1-linux-i686.tar.bz2'
"https://bitbucket.org/ariya/phantomjs/downloads/phantomjs-#{Phantomjs.version}-linux-i686.tar.bz2"
end
end
end
Expand All @@ -130,31 +227,27 @@ def platform
end

def package_url
'https://bitbucket.org/ariya/phantomjs/downloads/phantomjs-2.1.1-macosx.zip'
"https://bitbucket.org/ariya/phantomjs/downloads/phantomjs-#{Phantomjs.version}-macosx.zip"
end
end
end

class Win32 < Platform
class << self
def useable?
host_os.include?('mingw32') and architecture.include?('i686')
host_os.include?('mingw32') && (architecture.include?('i686') || architecture.include?('x86_64'))
end

def platform
'win32'
end

def phantomjs_path
if system_phantomjs_installed?
system_phantomjs_path
else
File.expand_path File.join(Phantomjs.base_dir, platform, 'bin', 'phantomjs.exe')
end
def phantomjs_executable
'phantomjs.exe'
end

def package_url
'https://bitbucket.org/ariya/phantomjs/downloads/phantomjs-2.1.1-windows.zip'
"https://bitbucket.org/ariya/phantomjs/downloads/phantomjs-#{Phantomjs.version}-windows.zip"
end
end
end
Expand Down
8 changes: 5 additions & 3 deletions phantomjs.gemspec
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,11 @@ Gem::Specification.new do |gem|
gem.homepage = "https://github.com/colszowka/phantomjs-gem"
gem.license = 'MIT'

gem.add_dependency 'rubyzip', '~> 1.0'

gem.add_development_dependency 'poltergeist', '~> 1.5'
gem.add_development_dependency 'capybara', '~> 2.4'
gem.add_development_dependency 'rspec', "~> 2.99"
gem.add_development_dependency 'rspec', '~> 3.4'
gem.add_development_dependency 'simplecov'
gem.add_development_dependency 'rake'
if RUBY_VERSION < '2'
Expand All @@ -21,7 +23,7 @@ Gem::Specification.new do |gem|
gem.files = `git ls-files`.split($\)
gem.executables = gem.files.grep(%r{^bin/}).map{ |f| File.basename(f) }
gem.test_files = gem.files.grep(%r{^(test|spec|features)/})
gem.name = "phantomjs"
gem.require_paths = ["lib"]
gem.name = 'phantomjs'
gem.require_paths = ['lib']
gem.version = Phantomjs::VERSION
end
Loading