lib/quandl/command/tasks/update.rb in quandl-0.3.7 vs lib/quandl/command/tasks/update.rb in quandl-0.4.0

- old
+ new

@@ -1,23 +1,21 @@ class Quandl::Command::Tasks::Update < Quandl::Command::Task depends 'uri', 'net/http', 'quandl/utility/config', 'zlib', 'archive/tar/minitar' - - # curl -s https://s3.amazonaws.com/quandl-command/install.sh | bash description "Update Quandl Toolbelt to the latest version." syntax %Q{quandl update [revision] EXAMPLES: $ quandl update - Updating from 0.2.18 ... - You are up to date! ( 0.3.1 ) + Updating from a.b.c ... + You are up to date! ( x.y.z ) - $ quandl update beta1 - Updating from 0.3.1 ... - You are up to date! ( 0.3.1-beta1 )} + $ 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! @@ -31,75 +29,67 @@ 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 - copy_windows_specific_files if Quandl::Utility::Config.windows? install_update - configure_update - ensure_correct_permissions + install_executable # success version = %x{quandl -v}.to_s.strip.rstrip - info "You are up to date! ( #{version} )" + cleanup_releases - rescue => err - # log error - info(err) - debug(err.backtrace.join("\n")) - # report failure and rollback - info("----\nAn error has occured! Rolling back to previous version ... ") - info("If the problem persists reinstall with: #{installer_url}") - rollback_update + 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 - def package_path - @package_path ||= File.join(update_path, "quandl-command") - end + private - def tarball_path - @tarball_path ||= File.join(update_path, "update.tar.gz") - end + delegate :chmod, :cp, :ln_sf, :mv, :rm_rf, :rm, :mkdir_p, :cp_r, to: :qu - def package_url - @package_url ||= self.class.package_url(args.first) - end - - def update_path - @update_path ||= File.join( root_path, 'update' ) - end - - def backup_path - @backup_path ||= File.join( root_path, 'backup' ) - end - - def root_path - Quandl::Command::Tasks.root - end - - - private - def prepare_for_update - [backup_path, update_path].each do |path| - # remove previous directory if present - rm_rf(path) if Dir.exists?(path) - # create new directory - mkdir_p(path) unless Dir.exists?(path) - end + FileUtils.mkdir_p(releases_path) unless Dir.exists?(releases_path) end def download_tarball debug "Downloading '#{package_url}' to '#{tarball_path}'" # download new package @@ -112,84 +102,148 @@ 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}', '#{update_path}' )" - # extract into update_path - Archive::Tar::Minitar.unpack( Zlib::GzipReader.open(tarball_path), update_path ) + 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 copy_windows_specific_files - files = ['bin/quandl.bat'] - files.each do |file| - source_path = File.join(root_path, file) - cp source_path, File.join(package_path, file) if File.exists?(source_path) - end + def install_update + install_timestamp(new_timestamp) end - def install_update - debug "Installing '#{update_path}' to '#{root_path}'" - # install each folder into the live directory - Dir.glob(File.join(package_path, "/*")) do |update_dir| - # current folder - folder_name = File.basename(update_dir) - # get live dir - live_dir = File.join(root_path, folder_name ) - # move live to backup (skip this step if it doesn't exist since it might be a new folder) - mv live_dir, File.join(backup_path, folder_name) if File.exists?(live_dir) - # move update to live - mv update_dir, live_dir - 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 configure_update - if Dir.exists?("#{root_path}/ruby") - bin_file = File.read("#{root_path}/bin/quandl") - bin_file.gsub!("#!/usr/bin/env ruby", "#!/#{root_path}/ruby/bin/ruby") - File.write("#{root_path}/bin/quandl", bin_file) - 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 rollback_update - Dir.glob(File.join(backup_path, "/*")) do |backup_dir| - # current folder - folder_name = File.basename(backup_dir) - # get live dir - live_dir = File.join(root_path, folder_name ) - # move live to update - mv live_dir, File.join(package_path, folder_name) - # move backup to live - mv backup_dir, live_dir + 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 ensure_correct_permissions - chmod("+x", File.join(root_path, 'bin/quandl')) + def update_current_timestamp(stamp) + File.open( timestamp_release_path, 'wb' ){|f| f.write(stamp.to_s) } end - def chmod(*args) - debug("FileUtils.chmod #{args.to_a.join(' ')}") - FileUtils.chmod(*args) + 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 cp(old_path, new_path) - debug("FileUtils.cp #{old_path} #{new_path}") - FileUtils.cp( old_path, new_path ) + def new_timestamp + @new_timestamp ||= (Time.now.getutc.to_f * 10000).to_i.to_s.gsub('.','') end - def mv(old_path, new_path) - debug("FileUtils.mv #{old_path} #{new_path}") - FileUtils.mv( old_path, new_path ) + def find_timestamp_before(stamp) + release_timestamps.sort.each do |rstamp| + return rstamp if rstamp.to_i < stamp.to_i + end + nil end - def rm_rf(*args) - debug( "FileUtils.rm_rf #{args.to_a.join(" ")}") - FileUtils.rm_rf(*args) + 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 mkdir_p(*args) - debug( "FileUtils.mkdir_p #{args.to_a.join(" ")}") - FileUtils.mkdir_p(*args) + 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 \ No newline at end of file