You can not select more than 25 topics
			Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
		
		
		
		
		
			
		
			
				
					
					
						
							271 lines
						
					
					
						
							8.2 KiB
						
					
					
				
			
		
		
	
	
							271 lines
						
					
					
						
							8.2 KiB
						
					
					
				| 
 | |
| module RPM
 | |
|   CallbackData = Struct.new(:type, :key, :package, :amount, :total) do
 | |
|     def to_s
 | |
|       "#{type} #{key} #{package} #{amount} #{total}"
 | |
|     end
 | |
|   end
 | |
| 
 | |
|   class Transaction
 | |
|     def self.release(ptr)
 | |
|       RPM::C.rpmtsFree(ptr)
 | |
|     end
 | |
| 
 | |
|     def initialize(opts = {})
 | |
|       # http://markmail.org/message/ypsiqxop442p7rzz
 | |
|       # The key pointer needs to stay valid during commit
 | |
|       # so we keep a reference to them mapping from
 | |
|       # object_id to ruby object.
 | |
|       @keys = {}
 | |
|       opts[:root] ||= '/'
 | |
| 
 | |
|       @ptr = ::FFI::AutoPointer.new(RPM::C.rpmtsCreate, Transaction.method(:release))
 | |
|       RPM::C.rpmtsSetRootDir(@ptr, opts[:root])
 | |
|     end
 | |
| 
 | |
|     # @return [RPM::MatchIterator] Creates an iterator for +tag+ and +val+
 | |
|     def init_iterator(tag, val)
 | |
|       raise TypeError if val && !val.is_a?(String)
 | |
| 
 | |
|       it_ptr = RPM::C.rpmtsInitIterator(@ptr, tag.nil? ? 0 : tag, val, 0)
 | |
| 
 | |
|       raise "Can't init iterator for [#{tag}] -> '#{val}'" if it_ptr.null?
 | |
|       MatchIterator.from_ptr(it_ptr)
 | |
|     end
 | |
| 
 | |
|     # @visibility private
 | |
|     attr_reader :ptr
 | |
| 
 | |
|     #
 | |
|     # @yield [Package] Called for each match
 | |
|     # @param [Number] key RPM tag key
 | |
|     # @param [String] val Value to match
 | |
|     # @example
 | |
|     #   RPM.transaction do |t|
 | |
|     #     t.each_match(RPM::TAG_ARCH, "x86_64") do |pkg|
 | |
|     #       puts pkg.name
 | |
|     #     end
 | |
|     #   end
 | |
|     #
 | |
|     def each_match(key, val, &block)
 | |
|       it = init_iterator(key, val)
 | |
| 
 | |
|       return it unless block_given?
 | |
| 
 | |
|       it.each(&block)
 | |
|     end
 | |
| 
 | |
|     #
 | |
|     # @yield [Package] Called for each package in the database
 | |
|     # @example
 | |
|     #   db.each do |pkg|
 | |
|     #     puts pkg.name
 | |
|     #   end
 | |
|     #
 | |
|     def each(&block)
 | |
|       each_match(0, nil, &block)
 | |
|     end
 | |
| 
 | |
|     # Add a install operation to the transaction
 | |
|     # @param [Package] pkg Package to install
 | |
|     # @param [String] key e.g. filename where to install from
 | |
|     def install(pkg, key)
 | |
|       install_element(pkg, key, upgrade: false)
 | |
|     end
 | |
| 
 | |
|     # Add an upgrade operation to the transaction
 | |
|     # @param [Package] pkg Package to upgrade
 | |
|     # @param [String] key e.g. filename where to install from
 | |
|     def upgrade(pkg, key)
 | |
|       install_element(pkg, key, upgrade: true)
 | |
|     end
 | |
| 
 | |
|     # Add a delete operation to the transaction
 | |
|     # @param [String, Package, Dependency] pkg Package to delete
 | |
|     def delete(pkg)
 | |
|       iterator = case pkg
 | |
|                  when Package
 | |
|                    pkg[:sigmd5] ? each_match(:sigmd5, pkg[:sigmd5]) : each_match(:label, pkg[:label])
 | |
|                  when String
 | |
|                    each_match(:label, pkg)
 | |
|                  when Dependency
 | |
|                    each_match(:label, pkg.name).set_iterator_version(pkg.version)
 | |
|                  else
 | |
|                    raise TypeError, 'illegal argument type'
 | |
|                  end
 | |
| 
 | |
|       iterator.each do |header|
 | |
|         ret = RPM::C.rpmtsAddEraseElement(@ptr, header.ptr, iterator.offset)
 | |
|         raise "Error while adding erase/#{pkg} to transaction" if ret != 0
 | |
|       end
 | |
|     end
 | |
| 
 | |
|     # Sets the root directory for this transaction
 | |
|     # @param [String] root directory
 | |
|     def root_dir=(dir)
 | |
|       rc = RPM::C.rpmtsSetRootDir(@ptr, dir)
 | |
|       raise "Can't set #{dir} as root directory" if rc < 0
 | |
|     end
 | |
| 
 | |
|     # @return [String ] the root directory for this transaction
 | |
|     def root_dir
 | |
|       RPM::C.rpmtsRootDir(@ptr)
 | |
|     end
 | |
| 
 | |
|     def flags=(fl)
 | |
|       RPM::C.rpmtsSetFlags(@ptr, fl)
 | |
|     end
 | |
| 
 | |
|     def flags
 | |
|       RPM::C.rpmtsFlags(@ptr)
 | |
|     end
 | |
| 
 | |
|     # Determine package order in the transaction according to dependencies
 | |
|     #
 | |
|     # The final order ends up as installed packages followed by removed
 | |
|     # packages, with packages removed for upgrades immediately following
 | |
|     # the new package to be installed.
 | |
|     #
 | |
|     # @returns [Fixnum] no. of (added) packages that could not be ordered
 | |
|     def order
 | |
|       RPM::C.rpmtsOrder(@ptr)
 | |
|     end
 | |
| 
 | |
|     # Free memory needed only for dependency checks and ordering.
 | |
|     def clean
 | |
|       RPM::C.rpmtsClean(@ptr)
 | |
|     end
 | |
| 
 | |
|     def check
 | |
|       rc = RPM::C.rpmtsCheck(@ptr)
 | |
|       probs = RPM::C.rpmtsProblems(@ptr)
 | |
| 
 | |
|       return if rc < 0
 | |
|       begin
 | |
|         psi = RPM::C.rpmpsInitIterator(probs)
 | |
|         while RPM::C.rpmpsNextIterator(psi) >= 0
 | |
|           problem = Problem.from_ptr(RPM::C.rpmpsGetProblem(psi))
 | |
|           yield problem
 | |
|         end
 | |
|       ensure
 | |
|         RPM::C.rpmpsFree(probs)
 | |
|       end
 | |
|     end
 | |
| 
 | |
|     # Performs the transaction.
 | |
|     # @param [Number] flag Transaction flags, default +RPM::TRANS_FLAG_NONE+
 | |
|     # @param [Number] filter Transaction filter, default +RPM::PROB_FILTER_NONE+
 | |
|     # @example
 | |
|     #   transaction.commit
 | |
|     # You can supply your own callback
 | |
|     # @example
 | |
|     #   transaction.commit do |data|
 | |
|     #   end
 | |
|     # end
 | |
|     # @yield [CallbackData] sig Transaction progress
 | |
|     def commit
 | |
|       flags = RPM::C::TransFlags[:none]
 | |
| 
 | |
|       callback = proc do |hdr, type, amount, total, key_ptr, data_ignored|
 | |
|         key_id = key_ptr.address
 | |
|         key = @keys.include?(key_id) ? @keys[key_id] : nil
 | |
| 
 | |
|         if block_given?
 | |
|           package = hdr.null? ? nil : Package.new(hdr)
 | |
|           data = CallbackData.new(type, key, package, amount, total)
 | |
|           yield(data)
 | |
|         else
 | |
|           RPM::C.rpmShowProgress(hdr, type, amount, total, key, data_ignored)
 | |
|         end
 | |
|       end
 | |
|       # We create a callback to pass to the C method and we
 | |
|       # call the user supplied callback from there
 | |
|       #
 | |
|       # The C callback expects you to return a file handle,
 | |
|       # We expect from the user to get a File, which we
 | |
|       # then convert to a file handle to return.
 | |
|       callback = proc do |hdr, type, amount, total, key_ptr, data_ignored|
 | |
|         key_id = key_ptr.address
 | |
|         key = @keys.include?(key_id) ? @keys[key_id] : nil
 | |
| 
 | |
|         if block_given?
 | |
|           package = hdr.null? ? nil : Package.new(hdr)
 | |
|           data = CallbackData.new(type, key, package, amount, total)
 | |
|           ret = yield(data)
 | |
| 
 | |
|           # For OPEN_FILE we need to do some type conversion
 | |
|           # for certain callback types we need to do some
 | |
|           case type
 | |
|           when :inst_open_file
 | |
|             # For :inst_open_file the user callback has to
 | |
|             # return the open file
 | |
|             unless ret.is_a?(::File)
 | |
|               raise TypeError, "illegal return value type #{ret.class}. Expected File."
 | |
|             end
 | |
|             fdt = RPM::C.fdDup(ret.to_i)
 | |
|             if fdt.null? || RPM::C.Ferror(fdt) != 0
 | |
|               raise "Can't use opened file #{data.key}: #{RPM::C.Fstrerror(fdt)}"
 | |
|               RPM::C.Fclose(fdt) unless fdt.nil?
 | |
|             else
 | |
|               fdt = RPM::C.fdLink(fdt)
 | |
|               @fdt = fdt
 | |
|             end
 | |
|             # return the (RPM type) file handle
 | |
|             fdt
 | |
|           when :inst_close_file
 | |
|             fdt = @fdt
 | |
|             RPM::C.Fclose(fdt)
 | |
|             @fdt = nil
 | |
|           else
 | |
|             ret
 | |
|           end
 | |
|         else
 | |
|           # No custom callback given, use the default to show progress
 | |
|           RPM::C.rpmShowProgress(hdr, type, amount, total, key, data_ignored)
 | |
|         end
 | |
|       end
 | |
| 
 | |
|       rc = RPM::C.rpmtsSetNotifyCallback(@ptr, callback, nil)
 | |
|       raise "Can't set commit callback" if rc != 0
 | |
| 
 | |
|       rc = RPM::C.rpmtsRun(@ptr, nil, :none)
 | |
| 
 | |
|       raise "#{self}: #{RPM::C.rpmlogMessage}" if rc < 0
 | |
| 
 | |
|       if rc > 0
 | |
|         ps = RPM::C.rpmtsProblems(@ptr)
 | |
|         psi = RPM::C.rpmpsInitIterator(ps)
 | |
|         while RPM::C.rpmpsNextIterator(psi) >= 0
 | |
|           problem = Problem.from_ptr(RPM::C.rpmpsGetProblem(psi))
 | |
|           STDERR.puts problem
 | |
|         end
 | |
|         RPM::C.rpmpsFree(ps)
 | |
|       end
 | |
|     end
 | |
| 
 | |
|     # @return [DB] the database associated with this transaction
 | |
|     def db
 | |
|       RPM::DB.new(self)
 | |
|     end
 | |
| 
 | |
|     private
 | |
| 
 | |
|     # @param [Package] pkg package to install
 | |
|     # @param [String] key e.g. filename where to install from
 | |
|     # @param opts options
 | |
|     #   @option :upgrade Upgrade packages if true
 | |
|     def install_element(pkg, key, opts = {})
 | |
|       raise TypeError, 'illegal argument type' unless pkg.is_a?(RPM::Package)
 | |
|       raise ArgumentError, "#{self}: key '#{key}' must be unique" if @keys.include?(key.object_id)
 | |
| 
 | |
|       # keep a reference to the key as rpmtsAddInstallElement will keep a copy
 | |
|       # of the passed pointer (we pass the object_id)
 | |
|       @keys[key.object_id] = key
 | |
| 
 | |
|       ret = RPM::C.rpmtsAddInstallElement(@ptr, pkg.ptr, FFI::Pointer.new(key.object_id), opts[:upgrade] ? 1 : 0, nil)
 | |
|       raise RuntimeError if ret != 0
 | |
|       nil
 | |
|     end
 | |
|   end
 | |
| end
 |