class Quandl::Command::Tasks::Update < Quandl::Command::Task depends 'uri', 'net/http', 'quandl/utility/config', 'zlib', 'archive/tar/minitar' description "Update Quandl Toolbelt to the latest version." syntax %Q{quandl update [revision] EXAMPLES: $ quandl update Updating from a.b.c ... You are up to date! ( x.y.z ) $ quandl update prerelease Updating from x.y.z ... You are up to date! ( x.y.z-prerelease )} PACKAGE_URL = 'http://s3.amazonaws.com/quandl-command/' disable_in_gem! class << self def package_url(revision=nil) # are we on windows? platform = Quandl::Utility::Config.windows? ? 'windows' : nil # build filename filename = ['quandl-command', platform, revision, 'tar.gz'].compact.join(".") # append s3 url File.join(PACKAGE_URL, filename) end end def execute if args[0] == 'rollback' execute_rollback else execute_update end rescue => err # log error error(err) debug(err.backtrace.join("\n")) # report failure and rollback error("----\nAn error has occured! Rolling back to previous version ... ") error("If the problem persists reinstall with: #{installer_url}") rollback_update end def execute_update info "Updating from #{Quandl::Command::VERSION} ... " # wipe update/ and backup/ folders prepare_for_update download_tarball # ensure package was downloaded return error("'#{package_url}' not found") unless File.exists?(tarball_path) # install extract_tarball install_update install_executable # success version = %x{quandl -v}.to_s.strip.rstrip cleanup_releases info "You are up to date! ( #{version} )" end def execute_rollback info "Rolling back from #{current_timestamp} ... " previous_timestamp = find_timestamp_before(current_timestamp) if previous_timestamp.blank? info("nothing to rollback to!") return false end info "to: #{previous_timestamp}" install_timestamp( previous_timestamp ) version = %x{quandl -v}.to_s.strip.rstrip info "You have rolled back! ( #{version} )" end def installer_url return File.join(PACKAGE_URL, "Quandl Setup.exe") if Quandl::Utility::Config.windows? return File.join(PACKAGE_URL, "quandl-toolbelt.pkg") if Quandl::Utility::Config.macosx? end private delegate :chmod, :cp, :ln_sf, :mv, :rm_rf, :rm, :mkdir_p, :cp_r, to: :qu def prepare_for_update FileUtils.mkdir_p(releases_path) unless Dir.exists?(releases_path) end def download_tarball debug "Downloading '#{package_url}' to '#{tarball_path}'" # download new package uri = URI( package_url ) # open connection to storage host Net::HTTP.start( uri.host, uri.port ) do |http| # download file resp = http.get( uri.path ) # write tar file if it was found open( tarball_path, "wb"){|f| f.write(resp.body) } unless resp.code == '404' end end def extract_tarball debug "Archive::Tar::Minitar.unpack( '#{tarball_path}', '#{releases_path}' )" # extract into releases_path Archive::Tar::Minitar.unpack( Zlib::GzipReader.open(tarball_path), releases_path ) # rename quandl-command to release name mv( package_path, timestamp_path ) end def install_update install_timestamp(new_timestamp) end def rollback_update # remove the failed release rm_rf(timestamp_path) if Dir.exists?(timestamp_path) # install previous release install_timestamp(current_timestamp) end def install_executable new_executable = File.join(timestamp_path, 'pkg', 'quandl') return unless File.exists?(new_executable) rm executable_path if File.exists?(executable_path) cp new_executable, executable_path end def install_timestamp(stamp) # remove previous rm_rf(current_path) if File.exists?(current_path) # install timestamp path = File.join( releases_path, stamp.to_s ) # copy or link if Quandl::Utility::Config.windows? cp_r(path, current_path) else ln_sf(path, current_path) end update_current_timestamp(stamp) end def update_current_timestamp(stamp) File.open( timestamp_release_path, 'wb' ){|f| f.write(stamp.to_s) } end def delete_timestamp(stamp) path = File.join(releases_path, stamp.to_s) rm_rf(path) if Dir.exists?(path) end def cleanup_releases return unless release_timestamps.count > 5 release_timestamps.reverse[5..-1].each{|stamp| delete_timestamp(stamp) } end def executable_path @executable_path ||= File.join(root_path, 'bin', 'quandl') end def timestamp_release_path @timestamp_release_path ||= File.join(root_path, '.timestamp') end def timestamp_path @timestamp_path ||= File.join( releases_path, new_timestamp ) end def tarball_path @tarball_path ||= File.join(releases_path, "update.tar.gz") end def package_path @package_path ||= File.join(releases_path, "quandl-command") end def releases_path @releases_path ||= File.expand_path(File.join( root_path, 'releases' )) end def package_url @package_url ||= self.class.package_url(args.first) end def current_path @current_path ||= File.join(root_path, 'current') end def root_path return @root_path if defined?(@root_path) parent_root = File.expand_path(File.join(Quandl::Command::Tasks.root, '../')) if Dir.exists?(File.join(parent_root, 'releases')) && Dir.exists?(File.join(parent_root, 'bin')) @root_path = parent_root else @root_path = Quandl::Command::Tasks.root end @root_path end def new_timestamp @new_timestamp ||= (Time.now.getutc.to_f * 10000).to_i.to_s.gsub('.','') end def find_timestamp_before(stamp) release_timestamps.sort.each do |rstamp| return rstamp if rstamp.to_i < stamp.to_i end nil end def current_timestamp # largest to smallest @current_timestamp ||= File.read(timestamp_release_path).strip.rstrip.to_i if File.exists?(timestamp_release_path) @current_timestamp ||= release_timestamps.sort.reverse.detect{|t| t < new_timestamp.to_i } end def release_timestamps @release_timestamps ||= Dir["#{releases_path}/*"].select{|f| File.directory?(f) }.collect{|f| File.basename(f).to_i } end def qu @qu ||= QuandlUtils.new(self) end class QuandlUtils attr_accessor :task def initialize(task=nil) self.task = task end def method_missing(method_name, *args, &block) fu(method_name, *args, &block) end private def fu(method_name, *args, &block) task.debug("#{method_name} #{args}") unless task.nil? FileUtils.send(method_name, *args, &block) end end end