<commit_before>require 'cfpropertylist'
require 'json'
require 'net/http'
require 'pathname'
require 'zip/zip'
require 'zip/zipfilesystem'

module AppStore; end

module AppStore::Emigrant
  
  # Represents a single iTunes mobile application
  class App
    
    # List of valid extensions for an application
    VALID_EXTENSIONS = [
      '.ipa'
    ]
    
    # Regular expression to match a version number in filenames
    FILENAME_VERSION_REGEX = Regexp.new('([0-9.]+)(?:' + VALID_EXTENSIONS.join('|').gsub!('.', '\.') + ')')
    
    attr_reader :path
    
    # Initializes application from given path
    def initialize path
      @path = Pathname.new path
      @path = @path.expand_path unless @path.absolute?
      
      # Ensure application exists
      unless @path.file?
        raise DoesNotExist, "Given path is not a valid application: #{@path}"
      end
      
      # Ensure application has valid extension
      unless VALID_EXTENSIONS.include? @path.extname
        raise Invalid, "Given application does not have a valid extension: #{@path}"
      end
      
      @metadata = nil
      @clouddata = nil
    end
    
    # Filename of this application (including extension)
    def filename
      unless @filename
        @filename = @path.basename().to_s
      end
      
      @filename
    end
    
    # Unique application id
    def id
      metadata['itemId'] rescue nil
    end
    
    # Name of this application
    def name
      metadata['itemName'] rescue nil
    end
    
    # Whether this application is valid
    def valid?
      metadata rescue false
    end
    
    # Version of this application
    def version
      unless @version
        
        # Extract version information (if available)
        @version = metadata['bundleShortVersionString'] || metadata['bundleVersion'] rescue nil
        
        # Otherwise, use the filename
        unless @version
          @version = filename[FILENAME_VERSION_REGEX, 1]
        end
        
      end
      
      @version
    end
    
    # Lazily loads local metadata for this application from its iTunesMetadata.plist
    def metadata
      unless @metadata
        begin
          Zip::ZipFile.open(@path) do |zip|
            data = zip.file.read('iTunesMetadata.plist')
            plist = CFPropertyList::List.new(:data => data)
            @metadata = CFPropertyList.native_types(plist.value)
          end
        rescue Zip::ZipError => e
          raise Invalid, e.message
        end
      end
      
      @metadata
    end
    
    # Lazily queries Apple's iTunes Store API for latest cloud data
    # Note: Clouddata may be nil if the application was removed from the store
    def clouddata
      unless @clouddata
        response = Net::HTTP.get('itunes.apple.com', '/lookup?id=' + id.to_s)
        @clouddata = JSON.parse(response)['results'].first
      end
      
      @clouddata
    end
    
    # Version available in the cloud
    def cloudversion
      clouddata && clouddata['version']
    end
    
    # Whether this application is outdated
    def outdated?
      return cloudversion && version != cloudversion
    end
  
    # Raised when an application does not exist
    class DoesNotExist < StandardError; end<commit_msg>Implemented metadata caching; Enhanced App.valid?<commit_after>require 'cfpropertylist'
require 'json'
require 'net/http'
require 'pathname'
require 'zip/zip'
require 'zip/zipfilesystem'

module AppStore; end

module AppStore::Emigrant
  
  # Represents a single iTunes mobile application
  class App
    
    # List of valid extensions for an application
    VALID_EXTENSIONS = [
      '.ipa'
    ]
    
    # Regular expression to match a version number in filenames
    FILENAME_VERSION_REGEX = Regexp.new('([0-9.]+)(?:' + VALID_EXTENSIONS.join('|').gsub!('.', '\.') + ')')
    
    attr_reader :path
    attr_writer :clouddata
    
    # Initializes application from given path
    def initialize path
      @path = Pathname.new path
      @path = @path.expand_path unless @path.absolute?
      
      # Ensure application exists
      unless @path.file?
        raise DoesNotExist, "Given path is not a valid application: #{@path}"
      end
      
      # Ensure application has valid extension
      unless VALID_EXTENSIONS.include? @path.extname
        raise Invalid, "Given application does not have a valid extension: #{@path}"
      end
      
      @metadata = nil
      @clouddata = nil
    end
    
    # Filename of this application (including extension)
    def filename
      unless @filename
        @filename = @path.basename.to_s
      end
      
      @filename
    end
    
    # Unique application id
    def id
      metadata['itemId'] rescue nil
    end
    
    # Name of this application
    def name
      metadata['itemName'] rescue nil
    end
    
    # Whether this application is valid (validates metadata, id and name)
    def valid?
      metadata && id && name rescue false
    end
    
    # Version of this application
    def version
      unless @version
        
        # Extract version information (if available)
        @version = metadata['bundleShortVersionString'] || metadata['bundleVersion'] rescue nil
        
        # Otherwise, use the filename
        unless @version
          @version = filename[FILENAME_VERSION_REGEX, 1]
        end
        
      end
      
      @version
    end
    
    # Lazily loads local metadata for this application from its iTunesMetadata.plist
    def metadata
      unless @metadata
        
        # Determine plist name
        plist = Pathname.new(filename).basename('.ipa').to_s + '.plist'
        
        unless Cache.has?(plist)
          begin
            Zip::ZipFile.open(@path) do |zip|
              zip.extract('iTunesMetadata.plist', Cache.path_to(plist))
            end
          rescue Zip::ZipError => e
            raise Invalid, e.message
          end
        end
        
        @metadata = CFPropertyList.native_types(CFPropertyList::List.new(:file => Cache.path_to(plist)).value)
      end
      
      @metadata
    end
    
    # Lazily queries Apple's iTunes Store API for latest cloud data
    # Note: Clouddata may be nil if the application was removed from the store
    def clouddata
      unless @clouddata
        response = Net::HTTP.get('itunes.apple.com', '/lookup?id=' + id.to_s)
        @clouddata = JSON.parse(response)['results'].first
      end
      
      @clouddata
    end
    
    # Version available in the cloud
    def cloudversion
      clouddata && clouddata['version']
    end
    
    # Whether this application is outdated
    def outdated?
      return cloudversion && version != cloudversion
    end
  
    # Raised when an application does not exist
    class DoesNotExist < StandardError; end
{'fraction_non_alphanumeric': 0.07882716958892, 'fraction_numerical': 0.0008840430234271402, 'mean_word_length': 2.1704810836057917, 'pattern_counts': {'":': 0, '<': 5, '<?xml version=': 0, '>': 7, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '3803459', 'n_tokens_mistral': 1924, 'n_tokens_neox': 1865, 'n_words': 589}