omt-cli 1.6.3 → 1.6.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/.codeclimate.yml +8 -0
- data/.gitignore +24 -0
- data/.travis.yml +31 -0
- data/CHANGELOG +188 -0
- data/Gemfile +11 -0
- data/LICENSE.txt +22 -0
- data/README.md +35 -0
- data/Rakefile +10 -0
- data/bin/console +11 -0
- data/bin/fir +14 -0
- data/bin/setup +7 -0
- data/doc/build_apk.md +42 -0
- data/doc/build_ipa.md +66 -0
- data/doc/help.md +34 -0
- data/doc/info.md +44 -0
- data/doc/install.md +65 -0
- data/doc/login.md +19 -0
- data/doc/mapping.md +22 -0
- data/doc/publish.md +35 -0
- data/doc/upgrade.md +7 -0
- data/lib/fir.rb +28 -0
- data/lib/fir/api.yml +13 -0
- data/lib/fir/api.yml.bak +13 -0
- data/lib/fir/cli.rb +195 -0
- data/lib/fir/patches.rb +10 -0
- data/lib/fir/patches/blank.rb +131 -0
- data/lib/fir/patches/concern.rb +146 -0
- data/lib/fir/patches/default_headers.rb +9 -0
- data/lib/fir/patches/hash.rb +79 -0
- data/lib/fir/patches/instance_variables.rb +30 -0
- data/lib/fir/patches/native_patch.rb +28 -0
- data/lib/fir/patches/os_patch.rb +28 -0
- data/lib/fir/patches/try.rb +102 -0
- data/lib/fir/util.rb +87 -0
- data/lib/fir/util/build_apk.rb +76 -0
- data/lib/fir/util/build_common.rb +93 -0
- data/lib/fir/util/build_ipa.rb +240 -0
- data/lib/fir/util/config.rb +42 -0
- data/lib/fir/util/http.rb +30 -0
- data/lib/fir/util/info.rb +39 -0
- data/lib/fir/util/login.rb +18 -0
- data/lib/fir/util/mapping.rb +98 -0
- data/lib/fir/util/me.rb +19 -0
- data/lib/fir/util/parser/apk.rb +43 -0
- data/lib/fir/util/parser/bin/pngcrush +0 -0
- data/lib/fir/util/parser/common.rb +24 -0
- data/lib/fir/util/parser/ipa.rb +188 -0
- data/lib/fir/util/parser/pngcrush.rb +23 -0
- data/lib/fir/util/publish.rb +106 -0
- data/lib/fir/util/publish.rb.bak +185 -0
- data/lib/fir/version.rb +5 -0
- data/lib/fir/xcode_wrapper.sh +29 -0
- data/lib/omt-cli.rb +3 -0
- data/lib/omt_cli.rb +3 -0
- data/omt-cli.gemspec +48 -0
- data/test/build_ipa_test.rb +17 -0
- data/test/cases/test_apk.apk +0 -0
- data/test/cases/test_apk_txt +1 -0
- data/test/cases/test_ipa.ipa +0 -0
- data/test/cases/test_ipa_dsym +0 -0
- data/test/info_test.rb +36 -0
- data/test/login_test.rb +12 -0
- data/test/mapping_test.rb +18 -0
- data/test/me_test.rb +17 -0
- data/test/publish_test.rb +44 -0
- data/test/test_helper.rb +98 -0
- metadata +84 -4
| @@ -0,0 +1,42 @@ | |
| 1 | 
            +
            # encoding: utf-8
         | 
| 2 | 
            +
             | 
| 3 | 
            +
            module FIR
         | 
| 4 | 
            +
              module Config
         | 
| 5 | 
            +
                CONFIG_PATH   = "#{ENV['HOME']}/.omt-cli"
         | 
| 6 | 
            +
                APP_INFO_PATH = "#{ENV['HOME']}/.omt-cli-app"
         | 
| 7 | 
            +
                API_YML_PATH  = File.expand_path('../../', __FILE__) + '/api.yml'
         | 
| 8 | 
            +
                XCODE_WRAPPER_PATH  = File.expand_path('../../', __FILE__) + '/xcode_wrapper.sh'
         | 
| 9 | 
            +
                APP_FILE_TYPE = %w(.ipa .apk).freeze
         | 
| 10 | 
            +
             | 
| 11 | 
            +
                def fir_api
         | 
| 12 | 
            +
                  @fir_api ||= YAML.load_file(API_YML_PATH).deep_symbolize_keys[:fir]
         | 
| 13 | 
            +
                end
         | 
| 14 | 
            +
             | 
| 15 | 
            +
                def bughd_api
         | 
| 16 | 
            +
                  @bughd_api ||= YAML.load_file(API_YML_PATH).deep_symbolize_keys[:bughd]
         | 
| 17 | 
            +
                end
         | 
| 18 | 
            +
             | 
| 19 | 
            +
                def config
         | 
| 20 | 
            +
                  return unless File.exist?(CONFIG_PATH)
         | 
| 21 | 
            +
                  @config ||= YAML.load_file(CONFIG_PATH).deep_symbolize_keys
         | 
| 22 | 
            +
                end
         | 
| 23 | 
            +
             | 
| 24 | 
            +
                def reload_config
         | 
| 25 | 
            +
                  @config = YAML.load_file(CONFIG_PATH).deep_symbolize_keys
         | 
| 26 | 
            +
                end
         | 
| 27 | 
            +
             | 
| 28 | 
            +
                def write_config(hash)
         | 
| 29 | 
            +
                  File.open(CONFIG_PATH, 'w+') { |f| f << YAML.dump(hash) }
         | 
| 30 | 
            +
                end
         | 
| 31 | 
            +
             | 
| 32 | 
            +
                def write_app_info(hash)
         | 
| 33 | 
            +
                  File.open(APP_INFO_PATH, 'w+') { |f| f << YAML.dump(hash) }
         | 
| 34 | 
            +
                end
         | 
| 35 | 
            +
             | 
| 36 | 
            +
                def current_token
         | 
| 37 | 
            +
                  @token ||= config[:token] if config
         | 
| 38 | 
            +
                end
         | 
| 39 | 
            +
             | 
| 40 | 
            +
                alias_method :☠, :exit
         | 
| 41 | 
            +
              end
         | 
| 42 | 
            +
            end
         | 
| @@ -0,0 +1,30 @@ | |
| 1 | 
            +
            # encoding: utf-8
         | 
| 2 | 
            +
             | 
| 3 | 
            +
            module FIR
         | 
| 4 | 
            +
              module Http
         | 
| 5 | 
            +
                MAX_RETRIES = 5
         | 
| 6 | 
            +
             | 
| 7 | 
            +
                %w(get post patch put).each do |_m|
         | 
| 8 | 
            +
                  class_eval <<-METHOD, __FILE__, __LINE__ + 1
         | 
| 9 | 
            +
                    def #{_m}(url, params = {})
         | 
| 10 | 
            +
                      query = :#{_m} == :get ? { params: params } : params
         | 
| 11 | 
            +
                      begin
         | 
| 12 | 
            +
                        res = ::RestClient.#{_m}(url, query)
         | 
| 13 | 
            +
                      rescue => e
         | 
| 14 | 
            +
                        @retries ||= 0
         | 
| 15 | 
            +
                        logger.error(e.message.to_s)
         | 
| 16 | 
            +
                        if @retries < MAX_RETRIES
         | 
| 17 | 
            +
                          @retries += 1
         | 
| 18 | 
            +
                          logger.info("Retry \#{@retries} times......")
         | 
| 19 | 
            +
                          sleep 2
         | 
| 20 | 
            +
                          retry
         | 
| 21 | 
            +
                        else
         | 
| 22 | 
            +
                          exit 1
         | 
| 23 | 
            +
                        end
         | 
| 24 | 
            +
                      end
         | 
| 25 | 
            +
                      JSON.parse(res.body.force_encoding('UTF-8'), symbolize_names: true)
         | 
| 26 | 
            +
                    end
         | 
| 27 | 
            +
                  METHOD
         | 
| 28 | 
            +
                end
         | 
| 29 | 
            +
              end
         | 
| 30 | 
            +
            end
         | 
| @@ -0,0 +1,39 @@ | |
| 1 | 
            +
            # encoding: utf-8
         | 
| 2 | 
            +
             | 
| 3 | 
            +
            module FIR
         | 
| 4 | 
            +
              module Info
         | 
| 5 | 
            +
             | 
| 6 | 
            +
                def info(*args, options)
         | 
| 7 | 
            +
                  file_path = File.absolute_path(args.first.to_s)
         | 
| 8 | 
            +
                  is_all    = !options[:all].blank?
         | 
| 9 | 
            +
             | 
| 10 | 
            +
                  check_file_exist file_path
         | 
| 11 | 
            +
                  check_supported_file file_path
         | 
| 12 | 
            +
             | 
| 13 | 
            +
                  file_type = File.extname(file_path).delete('.')
         | 
| 14 | 
            +
             | 
| 15 | 
            +
                  logger.info "Analyzing #{file_type} file......"
         | 
| 16 | 
            +
                  logger_info_dividing_line
         | 
| 17 | 
            +
             | 
| 18 | 
            +
                  app_info = send("#{file_type}_info", file_path, full_info: is_all)
         | 
| 19 | 
            +
                  app_info.each { |k, v| logger.info "#{k}: #{v}" }
         | 
| 20 | 
            +
             | 
| 21 | 
            +
                  logger_info_blank_line
         | 
| 22 | 
            +
                end
         | 
| 23 | 
            +
             | 
| 24 | 
            +
                def ipa_info(ipa_path, options = {})
         | 
| 25 | 
            +
                  ipa  = FIR::Parser::Ipa.new(ipa_path)
         | 
| 26 | 
            +
                  app  = ipa.app
         | 
| 27 | 
            +
                  info = app.full_info(options)
         | 
| 28 | 
            +
             | 
| 29 | 
            +
                  ipa.cleanup
         | 
| 30 | 
            +
                  info
         | 
| 31 | 
            +
                end
         | 
| 32 | 
            +
             | 
| 33 | 
            +
                def apk_info(apk_path, options = {})
         | 
| 34 | 
            +
                  apk  = FIR::Parser::Apk.new(apk_path)
         | 
| 35 | 
            +
                  info = apk.full_info(options)
         | 
| 36 | 
            +
                  info
         | 
| 37 | 
            +
                end
         | 
| 38 | 
            +
              end
         | 
| 39 | 
            +
            end
         | 
| @@ -0,0 +1,18 @@ | |
| 1 | 
            +
            # encoding: utf-8
         | 
| 2 | 
            +
             | 
| 3 | 
            +
            module FIR
         | 
| 4 | 
            +
              module Login
         | 
| 5 | 
            +
             | 
| 6 | 
            +
                def login(token)
         | 
| 7 | 
            +
                  check_token_cannot_be_blank token
         | 
| 8 | 
            +
             | 
| 9 | 
            +
                  user_info = fetch_user_info(token)
         | 
| 10 | 
            +
             | 
| 11 | 
            +
                  logger.info "Login succeed, previous user's email: #{config[:email]}" unless config.blank?
         | 
| 12 | 
            +
                  write_config(email: user_info.fetch(:email, ''), token: token)
         | 
| 13 | 
            +
                  reload_config
         | 
| 14 | 
            +
                  logger.info "Login succeed, current  user's email: #{config[:email]}"
         | 
| 15 | 
            +
                  logger_info_blank_line
         | 
| 16 | 
            +
                end
         | 
| 17 | 
            +
              end
         | 
| 18 | 
            +
            end
         | 
| @@ -0,0 +1,98 @@ | |
| 1 | 
            +
            # encoding: utf-8
         | 
| 2 | 
            +
             | 
| 3 | 
            +
            module FIR
         | 
| 4 | 
            +
              module Mapping
         | 
| 5 | 
            +
             | 
| 6 | 
            +
                def mapping(*args, options)
         | 
| 7 | 
            +
                  initialize_and_check_mapping_options(args, options)
         | 
| 8 | 
            +
                  check_file_and_token
         | 
| 9 | 
            +
             | 
| 10 | 
            +
                  logger.info "Creating bughd project's version......."
         | 
| 11 | 
            +
                  logger_info_dividing_line
         | 
| 12 | 
            +
             | 
| 13 | 
            +
                  @full_version = find_or_create_bughd_full_version
         | 
| 14 | 
            +
             | 
| 15 | 
            +
                  logger.info 'Uploading mapping file.......'
         | 
| 16 | 
            +
             | 
| 17 | 
            +
                  upload_mapping_file
         | 
| 18 | 
            +
                  logger_info_dividing_line
         | 
| 19 | 
            +
             | 
| 20 | 
            +
                  logger.info "Uploaded succeed: #{bughd_api[:domain]}/project/#{@proj}/settings"
         | 
| 21 | 
            +
                  logger_info_blank_line
         | 
| 22 | 
            +
                end
         | 
| 23 | 
            +
             | 
| 24 | 
            +
                def find_or_create_bughd_full_version
         | 
| 25 | 
            +
                  url = bughd_api[:project_url] + "/#{@proj}/full_versions"
         | 
| 26 | 
            +
                  post url, version: @version, build: @build, uuid: uuid
         | 
| 27 | 
            +
                end
         | 
| 28 | 
            +
             | 
| 29 | 
            +
                def upload_mapping_file
         | 
| 30 | 
            +
                  tmp_file_path = generate_temp_mapping_file
         | 
| 31 | 
            +
             | 
| 32 | 
            +
                  url = bughd_api[:full_version_url] + "/#{@full_version[:id]}"
         | 
| 33 | 
            +
                  patch url, file: File.new(tmp_file_path, 'rb'), project_id: @proj, uuid: uuid
         | 
| 34 | 
            +
                end
         | 
| 35 | 
            +
             | 
| 36 | 
            +
                private
         | 
| 37 | 
            +
             | 
| 38 | 
            +
                def initialize_and_check_mapping_options(args, options)
         | 
| 39 | 
            +
                  @file_path = File.absolute_path(args.first.to_s)
         | 
| 40 | 
            +
                  @token     = options[:token] || current_token
         | 
| 41 | 
            +
                  @proj      = options[:proj].to_s
         | 
| 42 | 
            +
                  @version   = options[:version].to_s
         | 
| 43 | 
            +
                  @build     = options[:build].to_s
         | 
| 44 | 
            +
                end
         | 
| 45 | 
            +
             | 
| 46 | 
            +
                def check_file_and_token
         | 
| 47 | 
            +
                  check_file_exist(@file_path)
         | 
| 48 | 
            +
                  check_token_cannot_be_blank(@token)
         | 
| 49 | 
            +
                  check_project_id_cannot_be_blank
         | 
| 50 | 
            +
                end
         | 
| 51 | 
            +
             | 
| 52 | 
            +
                def check_project_id_cannot_be_blank
         | 
| 53 | 
            +
                  return unless @proj.blank?
         | 
| 54 | 
            +
             | 
| 55 | 
            +
                  logger.error "Project id can't be blank"
         | 
| 56 | 
            +
                  exit 1
         | 
| 57 | 
            +
                end
         | 
| 58 | 
            +
             | 
| 59 | 
            +
                def uuid
         | 
| 60 | 
            +
                  @uuid ||= fetch_user_uuid(@token)
         | 
| 61 | 
            +
                end
         | 
| 62 | 
            +
             | 
| 63 | 
            +
                def generate_temp_mapping_file
         | 
| 64 | 
            +
                  tmp_file_path = "#{Dir.tmpdir}/fircli-#{File.basename(@file_path)}"
         | 
| 65 | 
            +
                  FileUtils.cp(@file_path, tmp_file_path)
         | 
| 66 | 
            +
             | 
| 67 | 
            +
                  tmp_file_path = zip_mapping_file(tmp_file_path)
         | 
| 68 | 
            +
                  tmp_file_path = dsym_or_txt_file(tmp_file_path)
         | 
| 69 | 
            +
             | 
| 70 | 
            +
                  tmp_file_path
         | 
| 71 | 
            +
                end
         | 
| 72 | 
            +
             | 
| 73 | 
            +
                def zip_mapping_file(tmp_file_path)
         | 
| 74 | 
            +
                  if File.size?(tmp_file_path) > 50 * 1000 * 1000
         | 
| 75 | 
            +
                    logger.info 'Zipping mapping file.......'
         | 
| 76 | 
            +
             | 
| 77 | 
            +
                    system("zip -qr #{tmp_file_path}.zip #{tmp_file_path}")
         | 
| 78 | 
            +
                    tmp_file_path += '.zip'
         | 
| 79 | 
            +
             | 
| 80 | 
            +
                    logger.info "Zipped Mapping file size - #{File.size?(tmp_file_path)}"
         | 
| 81 | 
            +
                  end
         | 
| 82 | 
            +
             | 
| 83 | 
            +
                  tmp_file_path
         | 
| 84 | 
            +
                end
         | 
| 85 | 
            +
             | 
| 86 | 
            +
                def dsym_or_txt_file(tmp_file_path)
         | 
| 87 | 
            +
                  if File.dsym?(@file_path)
         | 
| 88 | 
            +
                    FileUtils.mv(tmp_file_path, tmp_file_path + '.dSYM')
         | 
| 89 | 
            +
                    tmp_file_path += '.dSYM'
         | 
| 90 | 
            +
                  elsif File.text?(@file_path)
         | 
| 91 | 
            +
                    FileUtils.mv(tmp_file_path, tmp_file_path + '.txt')
         | 
| 92 | 
            +
                    tmp_file_path += '.txt'
         | 
| 93 | 
            +
                  end
         | 
| 94 | 
            +
             | 
| 95 | 
            +
                  tmp_file_path
         | 
| 96 | 
            +
                end
         | 
| 97 | 
            +
              end
         | 
| 98 | 
            +
            end
         | 
    
        data/lib/fir/util/me.rb
    ADDED
    
    | @@ -0,0 +1,19 @@ | |
| 1 | 
            +
            # encoding: utf-8
         | 
| 2 | 
            +
             | 
| 3 | 
            +
            module FIR
         | 
| 4 | 
            +
              module Me
         | 
| 5 | 
            +
             | 
| 6 | 
            +
                def me
         | 
| 7 | 
            +
                  check_logined
         | 
| 8 | 
            +
             | 
| 9 | 
            +
                  user_info = fetch_user_info(current_token)
         | 
| 10 | 
            +
             | 
| 11 | 
            +
                  email = user_info.fetch(:email, '')
         | 
| 12 | 
            +
                  name  = user_info.fetch(:name, '')
         | 
| 13 | 
            +
             | 
| 14 | 
            +
                  logger.info "Login succeed, current user's email: #{email}"
         | 
| 15 | 
            +
                  logger.info "Login succeed, current user's name:  #{name}"
         | 
| 16 | 
            +
                  logger_info_blank_line
         | 
| 17 | 
            +
                end
         | 
| 18 | 
            +
              end
         | 
| 19 | 
            +
            end
         | 
| @@ -0,0 +1,43 @@ | |
| 1 | 
            +
            # encoding: utf-8
         | 
| 2 | 
            +
             | 
| 3 | 
            +
            require_relative './common'
         | 
| 4 | 
            +
             | 
| 5 | 
            +
            module FIR
         | 
| 6 | 
            +
              module Parser
         | 
| 7 | 
            +
                class Apk
         | 
| 8 | 
            +
                  include Parser::Common
         | 
| 9 | 
            +
             | 
| 10 | 
            +
                  def initialize(path)
         | 
| 11 | 
            +
                    Zip.warn_invalid_date = false
         | 
| 12 | 
            +
                    @apk = ::Android::Apk.new(path)
         | 
| 13 | 
            +
                  end
         | 
| 14 | 
            +
             | 
| 15 | 
            +
                  def full_info(options)
         | 
| 16 | 
            +
                    if options.fetch(:full_info, false)
         | 
| 17 | 
            +
                      basic_info.merge!(icons: tmp_icons)
         | 
| 18 | 
            +
                    end
         | 
| 19 | 
            +
             | 
| 20 | 
            +
                    basic_info
         | 
| 21 | 
            +
                  end
         | 
| 22 | 
            +
             | 
| 23 | 
            +
                  def basic_info
         | 
| 24 | 
            +
                    @basic_info ||= {
         | 
| 25 | 
            +
                      type:       'android',
         | 
| 26 | 
            +
                      identifier: @apk.manifest.package_name,
         | 
| 27 | 
            +
                      name:       @apk.label,
         | 
| 28 | 
            +
                      build:      @apk.manifest.version_code.to_s,
         | 
| 29 | 
            +
                      version:    @apk.manifest.version_name.to_s
         | 
| 30 | 
            +
                    }
         | 
| 31 | 
            +
                  end
         | 
| 32 | 
            +
             | 
| 33 | 
            +
                  # @apk.icon is a hash, { icon_name: icon_binary_data }
         | 
| 34 | 
            +
                  def tmp_icons
         | 
| 35 | 
            +
                    begin
         | 
| 36 | 
            +
                      @apk.icon.map { |_, data| generate_tmp_icon(data, :apk) }
         | 
| 37 | 
            +
                    rescue
         | 
| 38 | 
            +
                      []
         | 
| 39 | 
            +
                    end
         | 
| 40 | 
            +
                  end
         | 
| 41 | 
            +
                end
         | 
| 42 | 
            +
              end
         | 
| 43 | 
            +
            end
         | 
| Binary file | 
| @@ -0,0 +1,24 @@ | |
| 1 | 
            +
            # encoding: utf-8
         | 
| 2 | 
            +
             | 
| 3 | 
            +
            module FIR
         | 
| 4 | 
            +
              module Parser
         | 
| 5 | 
            +
                module Common
         | 
| 6 | 
            +
             | 
| 7 | 
            +
                  # when type is ipa, the icon data is a png file.
         | 
| 8 | 
            +
                  # when type is apk, the icon data is a binary data.
         | 
| 9 | 
            +
                  def generate_tmp_icon data, type
         | 
| 10 | 
            +
                    tmp_icon_path = "#{Dir.tmpdir}/icon-#{SecureRandom.hex[4..9]}.png"
         | 
| 11 | 
            +
             | 
| 12 | 
            +
                    if type == :ipa
         | 
| 13 | 
            +
                      FileUtils.cp(data, tmp_icon_path)
         | 
| 14 | 
            +
                    elsif type == :apk
         | 
| 15 | 
            +
                      File.open(tmp_icon_path, 'w+') { |f| f << data }
         | 
| 16 | 
            +
                    else
         | 
| 17 | 
            +
                      return
         | 
| 18 | 
            +
                    end
         | 
| 19 | 
            +
             | 
| 20 | 
            +
                    tmp_icon_path
         | 
| 21 | 
            +
                  end
         | 
| 22 | 
            +
                end
         | 
| 23 | 
            +
              end
         | 
| 24 | 
            +
            end
         | 
| @@ -0,0 +1,188 @@ | |
| 1 | 
            +
            # encoding: utf-8
         | 
| 2 | 
            +
             | 
| 3 | 
            +
            require_relative './common'
         | 
| 4 | 
            +
             | 
| 5 | 
            +
            module FIR
         | 
| 6 | 
            +
              module Parser
         | 
| 7 | 
            +
                class Ipa
         | 
| 8 | 
            +
                  include Parser::Common
         | 
| 9 | 
            +
             | 
| 10 | 
            +
                  def initialize(path)
         | 
| 11 | 
            +
                    @path = path
         | 
| 12 | 
            +
                  end
         | 
| 13 | 
            +
             | 
| 14 | 
            +
                  def app
         | 
| 15 | 
            +
                    @app ||= App.new(app_path, is_stored)
         | 
| 16 | 
            +
                  end
         | 
| 17 | 
            +
             | 
| 18 | 
            +
                  def app_path
         | 
| 19 | 
            +
                    @app_path ||= Dir.glob(File.join(contents, 'Payload', '*.app')).first
         | 
| 20 | 
            +
                  end
         | 
| 21 | 
            +
             | 
| 22 | 
            +
                  def cleanup
         | 
| 23 | 
            +
                    return unless @contents
         | 
| 24 | 
            +
                    FileUtils.rm_rf(@contents)
         | 
| 25 | 
            +
                    @contents = nil
         | 
| 26 | 
            +
                  end
         | 
| 27 | 
            +
             | 
| 28 | 
            +
                  def metadata
         | 
| 29 | 
            +
                    return unless has_metadata?
         | 
| 30 | 
            +
                    @metadata ||= CFPropertyList.native_types(CFPropertyList::List.new(file: metadata_path).value)
         | 
| 31 | 
            +
                  end
         | 
| 32 | 
            +
             | 
| 33 | 
            +
                  def has_metadata?
         | 
| 34 | 
            +
                    File.file?(metadata_path)
         | 
| 35 | 
            +
                  end
         | 
| 36 | 
            +
             | 
| 37 | 
            +
                  def metadata_path
         | 
| 38 | 
            +
                    @metadata_path ||= File.join(@contents, 'iTunesMetadata.plist')
         | 
| 39 | 
            +
                  end
         | 
| 40 | 
            +
             | 
| 41 | 
            +
                  def is_stored
         | 
| 42 | 
            +
                    has_metadata? ? true : false
         | 
| 43 | 
            +
                  end
         | 
| 44 | 
            +
             | 
| 45 | 
            +
                  def contents
         | 
| 46 | 
            +
                    return if @contents
         | 
| 47 | 
            +
                    @contents = "#{Dir.tmpdir}/ipa_files-#{Time.now.to_i}"
         | 
| 48 | 
            +
             | 
| 49 | 
            +
                    Zip::File.open(@path) do |zip_file|
         | 
| 50 | 
            +
                      zip_file.each do |f|
         | 
| 51 | 
            +
                        f_path = File.join(@contents, f.name)
         | 
| 52 | 
            +
                        FileUtils.mkdir_p(File.dirname(f_path))
         | 
| 53 | 
            +
                        zip_file.extract(f, f_path) unless File.exist?(f_path)
         | 
| 54 | 
            +
                      end
         | 
| 55 | 
            +
                    end
         | 
| 56 | 
            +
             | 
| 57 | 
            +
                    @contents
         | 
| 58 | 
            +
                  end
         | 
| 59 | 
            +
             | 
| 60 | 
            +
                  class App
         | 
| 61 | 
            +
                    include Parser::Common
         | 
| 62 | 
            +
             | 
| 63 | 
            +
                    def initialize(path, is_stored = false)
         | 
| 64 | 
            +
                      @path      = path
         | 
| 65 | 
            +
                      @is_stored = is_stored
         | 
| 66 | 
            +
                    end
         | 
| 67 | 
            +
             | 
| 68 | 
            +
                    def full_info(options)
         | 
| 69 | 
            +
                      if options.fetch(:full_info, false)
         | 
| 70 | 
            +
                        basic_info.merge!(icons: tmp_icons)
         | 
| 71 | 
            +
                      end
         | 
| 72 | 
            +
             | 
| 73 | 
            +
                      basic_info
         | 
| 74 | 
            +
                    end
         | 
| 75 | 
            +
             | 
| 76 | 
            +
                    def basic_info
         | 
| 77 | 
            +
                      @basic_info ||= {
         | 
| 78 | 
            +
                        type:              'ios',
         | 
| 79 | 
            +
                        identifier:        identifier,
         | 
| 80 | 
            +
                        name:              name,
         | 
| 81 | 
            +
                        display_name:      display_name,
         | 
| 82 | 
            +
                        build:             version.to_s,
         | 
| 83 | 
            +
                        version:           short_version.to_s,
         | 
| 84 | 
            +
                        devices:           devices,
         | 
| 85 | 
            +
                        release_type:      release_type,
         | 
| 86 | 
            +
                        distribution_name: distribution_name
         | 
| 87 | 
            +
                      }
         | 
| 88 | 
            +
                    end
         | 
| 89 | 
            +
             | 
| 90 | 
            +
                    def info
         | 
| 91 | 
            +
                      @info ||= CFPropertyList.native_types(
         | 
| 92 | 
            +
                        CFPropertyList::List.new(file: File.join(@path, 'Info.plist')).value)
         | 
| 93 | 
            +
                    end
         | 
| 94 | 
            +
             | 
| 95 | 
            +
                    def name
         | 
| 96 | 
            +
                      info['CFBundleName']
         | 
| 97 | 
            +
                    end
         | 
| 98 | 
            +
             | 
| 99 | 
            +
                    def identifier
         | 
| 100 | 
            +
                      info['CFBundleIdentifier']
         | 
| 101 | 
            +
                    end
         | 
| 102 | 
            +
             | 
| 103 | 
            +
                    def display_name
         | 
| 104 | 
            +
                      info['CFBundleDisplayName']
         | 
| 105 | 
            +
                    end
         | 
| 106 | 
            +
             | 
| 107 | 
            +
                    def version
         | 
| 108 | 
            +
                      info['CFBundleVersion']
         | 
| 109 | 
            +
                    end
         | 
| 110 | 
            +
             | 
| 111 | 
            +
                    def short_version
         | 
| 112 | 
            +
                      info['CFBundleShortVersionString']
         | 
| 113 | 
            +
                    end
         | 
| 114 | 
            +
             | 
| 115 | 
            +
                    def tmp_icons
         | 
| 116 | 
            +
                      icons.map { |data| generate_tmp_icon(data, :ipa) }
         | 
| 117 | 
            +
                    end
         | 
| 118 | 
            +
             | 
| 119 | 
            +
                    def icons
         | 
| 120 | 
            +
                      @icons ||= begin
         | 
| 121 | 
            +
                        icons = []
         | 
| 122 | 
            +
                        info['CFBundleIcons']['CFBundlePrimaryIcon']['CFBundleIconFiles'].each do |name|
         | 
| 123 | 
            +
                          icons << get_image(name)
         | 
| 124 | 
            +
                          icons << get_image("#{name}@2x")
         | 
| 125 | 
            +
                        end
         | 
| 126 | 
            +
                        icons.delete_if &:!
         | 
| 127 | 
            +
                      rescue NoMethodError
         | 
| 128 | 
            +
                        []
         | 
| 129 | 
            +
                      end
         | 
| 130 | 
            +
                    end
         | 
| 131 | 
            +
             | 
| 132 | 
            +
                    def mobileprovision
         | 
| 133 | 
            +
                      return unless has_mobileprovision?
         | 
| 134 | 
            +
                      return @mobileprovision if @mobileprovision
         | 
| 135 | 
            +
             | 
| 136 | 
            +
                      cmd = "security cms -D -i \"#{mobileprovision_path}\""
         | 
| 137 | 
            +
                      begin
         | 
| 138 | 
            +
                        @mobileprovision = CFPropertyList.native_types(CFPropertyList::List.new(data: `#{cmd}`).value)
         | 
| 139 | 
            +
                      rescue CFFormatError
         | 
| 140 | 
            +
                        @mobileprovision = {}
         | 
| 141 | 
            +
                      end
         | 
| 142 | 
            +
                    end
         | 
| 143 | 
            +
             | 
| 144 | 
            +
                    def has_mobileprovision?
         | 
| 145 | 
            +
                      File.file? mobileprovision_path
         | 
| 146 | 
            +
                    end
         | 
| 147 | 
            +
             | 
| 148 | 
            +
                    def mobileprovision_path
         | 
| 149 | 
            +
                      @mobileprovision_path ||= File.join(@path, 'embedded.mobileprovision')
         | 
| 150 | 
            +
                    end
         | 
| 151 | 
            +
             | 
| 152 | 
            +
                    def hide_developer_certificates
         | 
| 153 | 
            +
                      mobileprovision.delete('DeveloperCertificates') if has_mobileprovision?
         | 
| 154 | 
            +
                    end
         | 
| 155 | 
            +
             | 
| 156 | 
            +
                    def devices
         | 
| 157 | 
            +
                      mobileprovision['ProvisionedDevices'] if has_mobileprovision?
         | 
| 158 | 
            +
                    end
         | 
| 159 | 
            +
             | 
| 160 | 
            +
                    def distribution_name
         | 
| 161 | 
            +
                      "#{mobileprovision['Name']} - #{mobileprovision['TeamName']}" if has_mobileprovision?
         | 
| 162 | 
            +
                    end
         | 
| 163 | 
            +
             | 
| 164 | 
            +
                    def release_type
         | 
| 165 | 
            +
                      if @is_stored
         | 
| 166 | 
            +
                        'store'
         | 
| 167 | 
            +
                      else
         | 
| 168 | 
            +
                        if has_mobileprovision?
         | 
| 169 | 
            +
                          if devices
         | 
| 170 | 
            +
                            'adhoc'
         | 
| 171 | 
            +
                          else
         | 
| 172 | 
            +
                            'inhouse'
         | 
| 173 | 
            +
                          end
         | 
| 174 | 
            +
                        end
         | 
| 175 | 
            +
                      end
         | 
| 176 | 
            +
                    end
         | 
| 177 | 
            +
             | 
| 178 | 
            +
                    private
         | 
| 179 | 
            +
             | 
| 180 | 
            +
                    def get_image(name)
         | 
| 181 | 
            +
                      path = File.join(@path, "#{name}.png")
         | 
| 182 | 
            +
                      return nil unless File.exist?(path)
         | 
| 183 | 
            +
                      path
         | 
| 184 | 
            +
                    end
         | 
| 185 | 
            +
                  end
         | 
| 186 | 
            +
                end
         | 
| 187 | 
            +
              end
         | 
| 188 | 
            +
            end
         |