<commit_before>require 'digest/md5'

module Dister

  # Core functionality
  class Core

    attr_reader :options, :shell

    # Absolute path to the root of the current application
    APP_ROOT = File.expand_path('.')

    # Connect to SUSE Studio and verify the user's credentials.
    # Sets +@options+, +@shell+ and +@connection+ for further use.
    def initialize
      @options ||= Options.new
      @shell = Thor::Shell::Basic.new
      @connection = StudioApi::Connection.new(
        @options.username,
        @options.api_key,
        @options.api_path,
        :proxy   => @options.proxy,           # proxy can be nil
        :timeout => (@options.timeout || 60)  # default to 60s
      )
      # Try the connection once to determine whether credentials are correct.
      @connection.api_version
      StudioApi::Util.configure_studio_connection @connection

      # Ensure app_name is stored for further use.
      if @options.app_name.nil?
        @options.app_name = APP_ROOT.split(/(\/|\\)/).last
      end

      true
    rescue ActiveResource::UnauthorizedAccess
      puts 'A connection to SUSE Studio could not be established.'
      keep_trying = @shell.ask(
        'Would you like to re-enter your credentials and try again? (y/n)'
      )
      if keep_trying == 'y'
        update_credentials
        retry
      else
        abort('Exiting dister.')
      end
    end

    # Creates a new appliance.
    # @return [StudioApi::Appliance] the new appliance
    def create_appliance(name, template, basesystem, arch)
      match = check_template_and_basesystem_availability(template, basesystem)
      exit 1 if match.nil?

      @db_adapter = get_db_adapter
      app = Utils::execute_printing_progress "Cloning appliance" do
        StudioApi::Appliance.clone(match.appliance_id, {:name => name,
                                                        :arch => arch})
      end
      @options.appliance_id = app.id
      ensure_devel_languages_ruby_extensions_repo_is_added
      self.add_package "devel_C_C++"
      self.add_package "devel_ruby"
      self.add_package 'rubygem-bundler'
      self.add_package 'rubygem-passenger-apache2'
      unless @db_adapter.nil?
        @db_adapter.packages.each do |p|
          self.add_package p
        end
      end

      Utils::execute_printing_progress "Uploading build scripts" do
        upload_configurations_scripts
      end
      puts "SUSE Studio appliance successfull created:"
      puts "  #{app.edit_url}"
      app
    end

    # Builds the appliance
    def build
      verify_status
      #TODO: build using another format
      build = StudioApi::RunningBuild.create(
        :appliance_id => @options.appliance_id,
        :image_type => "oem"
      )

      build.reload
      if build.state == "queued"
        puts "Your build is queued. It will be automatically processed by "\
             "SUSE Studio. You can keep waiting or you can exit from dister."
        puts "Exiting from dister won't remove your build from the queue."
        shell = Thor::Shell::Basic.new
        keep_waiting = @shell.ask('Do you want to keep waiting (y/n)')
        if keep_waiting == 'n'
          exit 0
        end

        Utils::execute_printing_progress "Build queued..." do
          while build.state == 'queued' do
            sleep 5
            build.reload
          end
        end
      end

      # build is no longer queued
      pbar = ProgressBar.new "Building", 100

      while not ['finished', 'error', 'failed', 'cancelled'].include?(build.state)
        pbar.set build.percent.to_i
        sleep 5
        build.reload
      end
      pbar.finish
      build.state == 'finished'
    end

    # Finds the appliance for the current app
    # @return [StudioApi::Appliance] the app's appliance (or nil if none exist).
    def appliance
      if @appliance.nil?
        begin
          appliance_id = self.options.appliance_id
          return nil if appliance_id.nil?
          @appliance = StudioApi::Appliance.find(appliance_id.to_i)
        rescue ActiveResource::BadRequest
          self.options.appliance_id = nil
          nil
        end
      else
        @appliance
      end
    end

    # Finds all builds
    # @return [Array<StudioApi::Build>]
    def builds
      StudioApi::Build.find(:all, :params => {:appliance_id => @options.appliance_id})
    end

    def templates
      reply = StudioApi::TemplateSet.find(:first, :conditions => {:name => "default"})
      if reply.nil?
        STDERR.puts "There is no default template set named 'default'"
        STDERR.puts "Please contact SUSE Studio admin"
        exit 1
      else
        return reply.template
      end
    end

    def basesystems
      templates.collect(&:basesystem).uniq
    end

    def check_template_and_basesystem_availability template, basesystem
      available_templates = self.templates
      match = available_templates.find do |t|
        t.basesystem == basesystem && t.name.downcase.include?(template.downcase)
      end

      if match.nil?
        STDERR.puts "The #{basesystem} doesn't have the #{template} template."
        STDERR.puts "Available templates are:"
        available_templates.find_all do |t|
          t.basesystem.downcase == basesystem.downcase
        end.each do |t|
          STDERR.puts "  - #{t.name}"
        end
      end
      match
    end

    # Uploads a file identified by filename to a SuSE Studio Appliance
    #
    # @param [String] filename name of file to upload
    # @param [Hash] upload_options the following keys can be used:
    #   - filename (optional) - The name of the file in the filesystem.
    #   - path (optional) - The path where the file will be stored.
    #   - owner (optional) - The owner of the file.
    #   - group (optional) - The group of the file.
    #   - permissions (optional) - The permissions of the file.
    #   - enabled (optional) - Used to enable/disable this file for the builds.
    #   - url (optional) - The url of the file to add from the internet (HTTP and FTP are supported) when using the web upload method
    #
    # @return [Boolean] true if the file has been successfully uploaded
    def file_upload filename, upload_options={}
      if File.exists? filename
        # Delete existing (obsolete) file.<commit_msg>Specify upload_options paramater more idiomatically<commit_after>require 'digest/md5'

module Dister

  # Core functionality
  class Core

    attr_reader :options, :shell

    # Absolute path to the root of the current application
    APP_ROOT = File.expand_path('.')

    # Connect to SUSE Studio and verify the user's credentials.
    # Sets +@options+, +@shell+ and +@connection+ for further use.
    def initialize
      @options ||= Options.new
      @shell = Thor::Shell::Basic.new
      @connection = StudioApi::Connection.new(
        @options.username,
        @options.api_key,
        @options.api_path,
        :proxy   => @options.proxy,           # proxy can be nil
        :timeout => (@options.timeout || 60)  # default to 60s
      )
      # Try the connection once to determine whether credentials are correct.
      @connection.api_version
      StudioApi::Util.configure_studio_connection @connection

      # Ensure app_name is stored for further use.
      if @options.app_name.nil?
        @options.app_name = APP_ROOT.split(/(\/|\\)/).last
      end

      true
    rescue ActiveResource::UnauthorizedAccess
      puts 'A connection to SUSE Studio could not be established.'
      keep_trying = @shell.ask(
        'Would you like to re-enter your credentials and try again? (y/n)'
      )
      if keep_trying == 'y'
        update_credentials
        retry
      else
        abort('Exiting dister.')
      end
    end

    # Creates a new appliance.
    # @return [StudioApi::Appliance] the new appliance
    def create_appliance(name, template, basesystem, arch)
      match = check_template_and_basesystem_availability(template, basesystem)
      exit 1 if match.nil?

      @db_adapter = get_db_adapter
      app = Utils::execute_printing_progress "Cloning appliance" do
        StudioApi::Appliance.clone(match.appliance_id, {:name => name,
                                                        :arch => arch})
      end
      @options.appliance_id = app.id
      ensure_devel_languages_ruby_extensions_repo_is_added
      self.add_package "devel_C_C++"
      self.add_package "devel_ruby"
      self.add_package 'rubygem-bundler'
      self.add_package 'rubygem-passenger-apache2'
      unless @db_adapter.nil?
        @db_adapter.packages.each do |p|
          self.add_package p
        end
      end

      Utils::execute_printing_progress "Uploading build scripts" do
        upload_configurations_scripts
      end
      puts "SUSE Studio appliance successfull created:"
      puts "  #{app.edit_url}"
      app
    end

    # Builds the appliance
    def build
      verify_status
      #TODO: build using another format
      build = StudioApi::RunningBuild.create(
        :appliance_id => @options.appliance_id,
        :image_type => "oem"
      )

      build.reload
      if build.state == "queued"
        puts "Your build is queued. It will be automatically processed by "\
             "SUSE Studio. You can keep waiting or you can exit from dister."
        puts "Exiting from dister won't remove your build from the queue."
        shell = Thor::Shell::Basic.new
        keep_waiting = @shell.ask('Do you want to keep waiting (y/n)')
        if keep_waiting == 'n'
          exit 0
        end

        Utils::execute_printing_progress "Build queued..." do
          while build.state == 'queued' do
            sleep 5
            build.reload
          end
        end
      end

      # build is no longer queued
      pbar = ProgressBar.new "Building", 100

      while not ['finished', 'error', 'failed', 'cancelled'].include?(build.state)
        pbar.set build.percent.to_i
        sleep 5
        build.reload
      end
      pbar.finish
      build.state == 'finished'
    end

    # Finds the appliance for the current app
    # @return [StudioApi::Appliance] the app's appliance (or nil if none exist).
    def appliance
      if @appliance.nil?
        begin
          appliance_id = self.options.appliance_id
          return nil if appliance_id.nil?
          @appliance = StudioApi::Appliance.find(appliance_id.to_i)
        rescue ActiveResource::BadRequest
          self.options.appliance_id = nil
          nil
        end
      else
        @appliance
      end
    end

    # Finds all builds
    # @return [Array<StudioApi::Build>]
    def builds
      StudioApi::Build.find(:all, :params => {:appliance_id => @options.appliance_id})
    end

    def templates
      reply = StudioApi::TemplateSet.find(:first, :conditions => {:name => "default"})
      if reply.nil?
        STDERR.puts "There is no default template set named 'default'"
        STDERR.puts "Please contact SUSE Studio admin"
        exit 1
      else
        return reply.template
      end
    end

    def basesystems
      templates.collect(&:basesystem).uniq
    end

    def check_template_and_basesystem_availability template, basesystem
      available_templates = self.templates
      match = available_templates.find do |t|
        t.basesystem == basesystem && t.name.downcase.include?(template.downcase)
      end

      if match.nil?
        STDERR.puts "The #{basesystem} doesn't have the #{template} template."
        STDERR.puts "Available templates are:"
        available_templates.find_all do |t|
          t.basesystem.downcase == basesystem.downcase
        end.each do |t|
          STDERR.puts "  - #{t.name}"
        end
      end
      match
    end

    # Uploads a file identified by filename to a SuSE Studio Appliance
    #
    # @param [String] filename name of file to upload
    # @param [Hash] upload_options upload options (all parameters are optional)
    # @option upload_options [String] filename The name of the file in the
    #   filesystem
    # @option upload_options [String] path The path where the file will be stored
    # @option upload_options [String] owner The owner of the file
    # @option upload_options [String] group The group of the file
    # @option upload_options [String] permissions The permissions of the file
    # @option upload_options [String] enabled Used to enable/disable this file
    #   for the builds
    # @option upload_options [String] url The url of the file to add from the
    #   internet (HTTP and FTP are supported) when using the web upload method
    #
    # @return [Boolean] true if the file has been successfully uploaded
    def file_upload filename, upload_options={}
      if File.exists? filename
        # Delete existing (obsolete) file.
{'fraction_non_alphanumeric': 0.08431188351338656, 'fraction_numerical': 0.002191952403319242, 'mean_word_length': 2.31301867219917, 'pattern_counts': {'":': 0, '<': 5, '<?xml version=': 0, '>': 25, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 4, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '6299453', 'n_tokens_mistral': 3731, 'n_tokens_neox': 3570, 'n_words': 1129}