module IOStreams
  module Zip
    class Reader
      # Read from a zip file or stream, decompressing the contents as it is read
      # The input stream from the first file found in the zip file is passed
      # to the supplied block
      #
      # Example:
      #   IOStreams::Zip::Reader.open('abc.zip') do |io_stream|
      #     # Read 256 bytes at a time
      #     while data = io_stream.read(256)
      #       puts data
      #     end
      #   end
      def self.open(file_name_or_io, buffer_size: 65536, &block)
        if !defined?(JRuby) && !defined?(::Zip)
          # MRI needs Ruby Zip, since it only has native support for GZip
          begin
            require 'zip'
          rescue LoadError => exc
            raise(LoadError, "Install gem 'rubyzip' to read and write Zip files: #{exc.message}")
          end
        end

        # File name supplied
        return read_file(file_name_or_io, &block) unless IOStreams.reader_stream?(file_name_or_io)

        # Stream supplied
        begin
          # Since ZIP cannot be streamed, download un-zipped data to a local file before streaming
          temp_file = Tempfile.new('rocket_job')
          file_name = temp_file.to_path

          # Stream zip stream into temp file
          ::File.open(file_name, 'wb') do |file|
            IOStreams.copy(file_name_or_io, file, buffer_size: buffer_size)
          end

          read_file(file_name, &block)
        ensure
          temp_file.delete if temp_file
        end
      end

      if defined?(JRuby)
        # Java has built-in support for Zip files
        def self.read_file(file_name, &block)
          fin = Java::JavaIo::FileInputStream.new(file_name)
          zin = Java::JavaUtilZip::ZipInputStream.new(fin)
          zin.get_next_entry
          block.call(zin.to_io)
        ensure
          zin.close if zin
          fin.close if fin
        end

      else

        # Read from a zip file or stream, decompressing the contents as it is read
        # The input stream from the first file found in the zip file is passed
        # to the supplied block
        def self.read_file(file_name, &block)
          begin
            zin = ::Zip::InputStream.new(file_name)
            zin.get_next_entry
            block.call(zin)
          ensure
            begin
              zin.close if zin
            rescue IOError
              # Ignore file already closed errors since Zip::InputStream
              # does not have a #closed? method
            end
          end
        end

      end
    end
  end
end
