From 6c1eb2b0070af99bc31908ea1478384947b15951 Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Sun, 17 Mar 2013 11:25:22 -0700 Subject: [PATCH] Download boxes using CURL! --- lib/vagrant/action/builtin/box_add.rb | 40 ++++++++------------------- lib/vagrant/errors.rb | 8 +++--- lib/vagrant/util/downloader.rb | 31 +++++++++++++++------ templates/locales/en.yml | 7 +++-- 4 files changed, 44 insertions(+), 42 deletions(-) diff --git a/lib/vagrant/action/builtin/box_add.rb b/lib/vagrant/action/builtin/box_add.rb index 3192c389b..06ee41482 100644 --- a/lib/vagrant/action/builtin/box_add.rb +++ b/lib/vagrant/action/builtin/box_add.rb @@ -1,5 +1,6 @@ require "log4r" +require "vagrant/util/downloader" require "vagrant/util/platform" module Vagrant @@ -14,18 +15,21 @@ module Vagrant end def call(env) - # Instantiate the downloader - downloader = download_klass(env[:box_url]).new(env[:ui]) - env[:ui].info I18n.t("vagrant.actions.box.download.with", - :class => downloader.class.to_s) + @temp_path = env[:tmp_path].join("box" + Time.now.to_i.to_s) + @logger.info("Downloading box to: #{@temp_path}") # Download the box to a temporary path. We store the temporary # path as an instance variable so that the `#recover` method can # access it. - @temp_path = env[:tmp_path].join("box" + Time.now.to_i.to_s) - @logger.info("Downloading box to: #{@temp_path}") - File.open(@temp_path, Vagrant::Util::Platform.tar_file_options) do |f| - downloader.download!(env[:box_url], f) + env[:ui].info(I18n.t("vagrant.actions.box.download.downloading")) + begin + downloader = Util::Downloader.new(env[:box_url], @temp_path, :ui => env[:ui]) + downloader.download! + rescue Errors::DownloaderInterrupted + # The downloader was interrupted, so just return, because that + # means we were interrupted as well. + env[:ui].info(I18n.t("vagrant.actions.box.download.interrupted")) + return end # Add the box @@ -54,28 +58,8 @@ module Vagrant @app.call(env) end - def download_klass(url) - # This is hardcoded for now. In the future I'd like to make this - # pluginnable as well. - classes = [Downloaders::HTTP, Downloaders::File] - - # Find the class to use. - classes.each_index do |i| - klass = classes[i] - - # Use the class if it matches the given URI or if this - # is the last class... - return klass if classes.length == (i + 1) || klass.match?(url) - end - - # If no downloader knows how to download this file, then we - # raise an exception. - raise Errors::BoxDownloadUnknownType - end - def recover(env) if @temp_path && File.exist?(@temp_path) - env[:ui].info I18n.t("vagrant.actions.box.download.cleaning") File.unlink(@temp_path) end end diff --git a/lib/vagrant/errors.rb b/lib/vagrant/errors.rb index 1bc02b020..82ce26b38 100644 --- a/lib/vagrant/errors.rb +++ b/lib/vagrant/errors.rb @@ -95,10 +95,6 @@ module Vagrant error_key(:already_exists, "vagrant.actions.box.unpackage") end - class BoxDownloadUnknownType < VagrantError - error_key(:unknown_type, "vagrant.actions.box.download") - end - class BoxMetadataFileNotFound < VagrantError error_key(:box_metadata_file_not_found) end @@ -167,6 +163,10 @@ module Vagrant error_key(:downloader_error) end + class DownloaderInterrupted < DownloaderError + error_key(:downloader_interrupted) + end + class DownloaderFileDoesntExist < VagrantError error_key(:file_missing, "vagrant.downloaders.file") end diff --git a/lib/vagrant/util/downloader.rb b/lib/vagrant/util/downloader.rb index f67f61929..8f39b9a56 100644 --- a/lib/vagrant/util/downloader.rb +++ b/lib/vagrant/util/downloader.rb @@ -1,5 +1,6 @@ require "log4r" +require "vagrant/util/busy" require "vagrant/util/subprocess" module Vagrant @@ -10,12 +11,12 @@ module Vagrant class Downloader def initialize(source, destination, options=nil) @logger = Log4r::Logger.new("vagrant::util::downloader") - @source = source - @destination = destination + @source = source.to_s + @destination = destination.to_s # Get the various optional values - @options ||= {} - @ui = @options[:ui] + options ||= {} + @ui = options[:ui] end # This executes the actual download, downloading the source file @@ -70,24 +71,38 @@ module Vagrant # 11 - Time left # 12 - Current speed - output = "Progress: #{columns[1]}% (Rate: #{columns[12]}/s, Estimated time remaining: #{columns[11]}" + output = "Progress: #{columns[1]}% (Rate: #{columns[12]}/s, Estimated time remaining: #{columns[11]})" @ui.clear_line @ui.info(output, :new_line => false) end end + # Create the callback that is called if we are interrupted + interrupted = false + int_callback = Proc.new do + @logger.info("Downloader interrupted!") + interrupted = true + end + @logger.info("Downloader starting download: ") @logger.info(" -- Source: #{@source}") @logger.info(" -- Destination: #{@destination}") # Execute! - result = Subprocess.execute("curl", *options, &data_proc) + result = Busy.busy(int_callback) do + Subprocess.execute("curl", *options, &data_proc) + end + + # If the download was interrupted, then raise a specific error + raise Errors::DownloaderInterrupted if interrupted # If it didn't exit successfully, we need to parse the data and # show an error message. if result.exit_code != 0 - parts = result.stderr.split(/\ncurl:\s+\(\d+\)\s*/, 2) - raise Errors::DownloaderError, :message => parts[1] + @logger.warn("Downloader exit code: #{result.exit_code}") + parts = result.stderr.split(/\n*curl:\s+\(\d+\)\s*/, 2) + parts[1] ||= "" + raise Errors::DownloaderError, :message => parts[1].chomp end # Everything succeeded diff --git a/templates/locales/en.yml b/templates/locales/en.yml index e5a727c85..093ee21c8 100644 --- a/templates/locales/en.yml +++ b/templates/locales/en.yml @@ -136,6 +136,9 @@ en: again. %{message} + downloader_interrupted: |- + The download was interrupted by an external signal. It did not + complete. environment_locked: |- An instance of Vagrant is already running. Only one instance of Vagrant may run at any given time to avoid problems with VirtualBox inconsistencies @@ -853,9 +856,9 @@ en: destroy: destroying: "Deleting box '%{name}'..." download: - with: "Downloading with %{class}..." cleaning: "Cleaning up downloaded box..." - unknown_type: "Unknown or unsupported URI type given for box download." + downloading: "Downloading or copying the box..." + interrupted: "Box download was interrupted. Exiting." verify: verifying: "Verifying box..." failed: |-