make pool.setarch() do the right thing
[platform/upstream/libsolv.git] / examples / rbsolv
1 #!/usr/bin/ruby
2
3 require 'solv'
4 require 'rubygems'
5 require 'inifile'
6 require 'tempfile'
7
8 class Repo_generic
9   def initialize(name, type, attribs = {})
10     @name = name
11     @type = type
12     @attribs = attribs.dup
13     @incomplete = false
14   end
15
16   def enabled?
17     return @attribs['enabled'].to_i != 0
18   end
19
20   def autorefresh?
21     return @attribs['autorefresh'].to_i != 0
22   end
23
24   def id
25     return @handle ? @handle.id : 0
26   end
27
28   def calc_cookie_fp(f)
29     chksum = Solv::Chksum.new(Solv::REPOKEY_TYPE_SHA256)
30     chksum.add("1.1")
31     chksum.add_fp(f)
32     return chksum.raw
33   end
34
35   def calc_cookie_file(filename)
36     chksum = Solv::Chksum.new(Solv::REPOKEY_TYPE_SHA256)
37     chksum.add("1.1")
38     chksum.add_stat(filename)
39     return chksum.raw
40   end
41
42   def calc_cookie_ext(f, cookie)
43     chksum = Solv::Chksum.new(Solv::REPOKEY_TYPE_SHA256)
44     chksum.add("1.1")
45     chksum.add(cookie)
46     chksum.add_fstat(f.fileno)
47     extcookie = chksum.raw()
48     extcookie[0] = 1 if extcookie[0] == 0
49     return extcookie
50   end
51
52   def cachepath(ext = nil)
53     path = @name.sub(/^\./, '_')
54     path += ext ? "_#{ext}.solvx" : '.solv'
55     return '/var/cache/solv/' + path.gsub(/\//, '_')
56   end
57
58   def load(pool)
59     @handle = pool.add_repo(@name)
60     @handle.appdata = self
61     @handle.priority = 99 - @attribs['priority'].to_i if @attribs['priority']
62     dorefresh = autorefresh?
63     if dorefresh
64       begin
65         s = File.stat(cachepath)
66         dorefresh = false if s && (@attribs['metadata_expire'].to_i == -1 || Time.now - s.mtime < @attribs['metadata_expire'].to_i)
67       rescue SystemCallError
68       end
69     end
70     @cookie = nil
71     if !dorefresh && usecachedrepo(nil)
72       puts "repo: '#{@name}' cached"
73       return true
74     end
75     return false
76   end
77
78   def load_ext(repodata)
79     return false
80   end
81
82   def download(file, uncompress, chksum, markincomplete = false)
83     url = @attribs['baseurl']
84     if !url
85       puts "%{@name}: no baseurl"
86       return nil
87     end
88     url = url.sub(/\/$/, '') + "/#{file}"
89     f =  Tempfile.new('rbsolv')
90     f.unlink
91     st = system('curl', '-f', '-s', '-L', '-o', "/dev/fd/" + f.fileno.to_s, '--', url)
92     return nil if f.stat.size == 0 && (st || !chksum)
93     if !st
94         puts "#{file}: download error #{$? >> 8}"
95         @incomplete = true if markincomplete
96         return nil
97     end
98     if chksum
99       fchksum = Solv::Chksum.new(chksum.type)
100       fchksum.add_fd(f.fileno)
101       if !fchksum == chksum
102         puts "#{file}: checksum error"
103         @incomplete = true if markincomplete
104         return nil
105       end
106     end
107     if uncompress
108       return Solv::xfopen_dup(file, f.fileno)
109     else
110       return Solv::xfopen_dup('', f.fileno)
111     end
112   end
113
114   def usecachedrepo(ext, mark = false)
115     cookie = ext ? @extcookie : @cookie
116     begin
117       repopath = cachepath(ext)
118       f = File.new(repopath, "r")
119       f.sysseek(-32, IO::SEEK_END)
120       fcookie = f.sysread(32)
121       return false if fcookie.length != 32
122       return false if cookie && fcookie != cookie
123       if !ext && @type != 'system'
124         f.sysseek(-32 * 2, IO::SEEK_END)
125         fextcookie = f.sysread(32)
126         return false if fextcookie.length != 32
127       end
128       f.sysseek(0, IO::SEEK_SET)
129       f = Solv::xfopen_dup('', f.fileno)
130       flags = ext ? Solv::Repo::REPO_USE_LOADING|Solv::Repo::REPO_EXTEND_SOLVABLES : 0
131       flags |= Solv::Repo::REPO_LOCALPOOL if ext && ext != 'DL'
132       if ! @handle.add_solv(f, flags)
133         Solv::xfclose(f)
134         return false
135       end
136       Solv::xfclose(f)
137       @cookie = fcookie unless ext
138       @extcookie = fextcookie if !ext && @type != 'system'
139       now = Time.now
140       begin
141         File::utime(now, now, repopath) if mark
142       rescue SystemCallError
143       end
144       return true
145     rescue SystemCallError
146       return false
147     end
148     return true
149   end
150
151   def writecachedrepo(ext, info = nil)
152     return if @incomplete && !ext
153     begin
154       Dir::mkdir("/var/cache/solv", 0755) unless FileTest.directory?("/var/cache/solv")
155       f =  Tempfile.new('.newsolv-', '/var/cache/solv')
156       f.chmod(0444)
157       sf = Solv::xfopen_dup('', f.fileno)
158       if !info
159         @handle.write(sf)
160       elsif ext
161         info.write(sf)
162       else
163         @handle.write_first_repodata(sf)
164       end
165       Solv::xfclose(sf)
166       f.sysseek(0, IO::SEEK_END)
167       if @type != 'system' && !ext
168         @extcookie = calc_cookie_ext(f, @cookie) unless @extcookie
169         f.syswrite(@extcookie)
170       end
171       f.syswrite(ext ? @extcookie : @cookie)
172       f.close(false)
173       if @handle.iscontiguous?
174         sf = Solv::xfopen(f.path)
175         if sf
176           if !ext
177             @handle.empty()
178             abort("internal error, cannot reload solv file") unless @handle.add_solv(sf, Solv::Repo::SOLV_ADD_NO_STUBS)
179           else
180             info.extend_to_repo()
181             info.add_solv(sf, Solv::Repo::REPO_EXTEND_SOLVABLES)
182           end
183           Solv::xfclose(sf)
184         end
185       end
186       File.rename(f.path, cachepath(ext))
187       f.unlink
188       return true
189     rescue SystemCallError
190       return false
191     end
192   end
193
194   def updateaddedprovides(addedprovides)
195     return if @incomplete
196     return unless @handle && !@handle.isempty?
197     repodata = @handle.first_repodata()
198     return unless repodata
199     oldaddedprovides = repodata.lookup_idarray(Solv::SOLVID_META, Solv::REPOSITORY_ADDEDFILEPROVIDES)
200     return if (oldaddedprovides | addedprovides) == oldaddedprovides
201     for id in addedprovides
202       repodata.add_idarray(Solv::SOLVID_META, Solv::REPOSITORY_ADDEDFILEPROVIDES, id)
203     end
204     repodata.internalize()
205     writecachedrepo(nil, repodata)
206   end
207
208   def packagespath()
209     return ''
210   end
211 end
212
213 class Repo_rpmmd < Repo_generic
214
215   def find(what)
216     di = @handle.Dataiterator(Solv::SOLVID_META, Solv::REPOSITORY_REPOMD_TYPE, what, Solv::Dataiterator::SEARCH_STRING)
217     di.prepend_keyname(Solv::REPOSITORY_REPOMD)
218     for d in di
219       dp = d.parentpos()
220       filename = dp.lookup_str(Solv::REPOSITORY_REPOMD_LOCATION)
221       next unless filename
222       checksum = dp.lookup_checksum(Solv::REPOSITORY_REPOMD_CHECKSUM)
223       if !checksum
224         puts "no #{filename} checksum!"
225         return nil, nil
226       end
227       return filename, checksum
228     end
229     return nil, nil
230   end
231
232   def load(pool)
233     return true if super(pool)
234     print "rpmmd repo '#{@name}: "
235     f = download("repodata/repomd.xml", false, nil, nil)
236     if !f
237       puts "no repomd.xml file, skipped"
238       @handle.free(true)
239       @handle = nil
240       return false
241     end
242     @cookie = calc_cookie_fp(f)
243     if usecachedrepo(nil, true)
244       puts "cached"
245       Solv.xfclose(f)
246       return true
247     end
248     @handle.add_repomdxml(f, 0)
249     Solv::xfclose(f)
250     puts "fetching"
251     filename, filechksum = find('primary')
252     if filename
253       f = download(filename, true, filechksum, true)
254       if f
255         @handle.add_rpmmd(f, nil, 0)
256         Solv::xfclose(f)
257       end
258       return false if @incomplete
259     end
260     filename, filechksum = find('updateinfo')
261     if filename
262       f = download(filename, true, filechksum, true)
263       if f
264         @handle.add_updateinfoxml(f, 0)
265         Solv::xfclose(f)
266       end
267     end
268     add_exts()
269     writecachedrepo(nil)
270     @handle.create_stubs()
271     return true
272   end
273
274   def add_ext(repodata, what, ext)
275     filename, filechksum = find(what)
276     filename, filechksum = find('prestodelta') if !filename && what == 'deltainfo'
277     return unless filename
278     h = repodata.new_handle()
279     repodata.set_poolstr(h, Solv::REPOSITORY_REPOMD_TYPE, what)
280     repodata.set_str(h, Solv::REPOSITORY_REPOMD_LOCATION, filename)
281     repodata.set_checksum(h, Solv::REPOSITORY_REPOMD_CHECKSUM, filechksum)
282     if ext == 'DL'
283       repodata.add_idarray(h, Solv::REPOSITORY_KEYS, Solv::REPOSITORY_DELTAINFO)
284       repodata.add_idarray(h, Solv::REPOSITORY_KEYS, Solv::REPOKEY_TYPE_FLEXARRAY)
285     elsif ext == 'FL'
286       repodata.add_idarray(h, Solv::REPOSITORY_KEYS, Solv::SOLVABLE_FILELIST)
287       repodata.add_idarray(h, Solv::REPOSITORY_KEYS, Solv::REPOKEY_TYPE_DIRSTRARRAY)
288     end
289     repodata.add_flexarray(Solv::SOLVID_META, Solv::REPOSITORY_EXTERNAL, h)
290   end
291
292   def add_exts
293     repodata = @handle.add_repodata(0)
294     add_ext(repodata, 'deltainfo', 'DL')
295     add_ext(repodata, 'filelists', 'FL')
296     repodata.internalize()
297   end
298
299   def load_ext(repodata)
300     repomdtype = repodata.lookup_str(Solv::SOLVID_META, Solv::REPOSITORY_REPOMD_TYPE)
301     if repomdtype == 'filelists'
302       ext = 'FL'
303     elsif repomdtype == 'deltainfo'
304       ext = 'DL'
305     else
306       return false
307     end
308     print "[#{@name}:#{ext}: "
309     STDOUT.flush
310     if usecachedrepo(ext)
311       puts "cached]\n"
312       return true
313     end
314     puts "fetching]\n"
315     filename = repodata.lookup_str(Solv::SOLVID_META, Solv::REPOSITORY_REPOMD_LOCATION)
316     filechksum = repodata.lookup_checksum(Solv::SOLVID_META, Solv::REPOSITORY_REPOMD_CHECKSUM)
317     f = download(filename, true, filechksum)
318     return false unless f
319     if ext == 'FL'
320       @handle.add_rpmmd(f, 'FL', Solv::Repo::REPO_USE_LOADING|Solv::Repo::REPO_EXTEND_SOLVABLES)
321     elsif ext == 'DL'
322       @handle.add_deltainfoxml(f, Solv::Repo::REPO_USE_LOADING)
323     end
324     Solv::xfclose(f)
325     writecachedrepo(ext, repodata)
326     return true
327   end
328
329 end
330
331 class Repo_susetags < Repo_generic
332
333   def find(what)
334     di = @handle.Dataiterator(Solv::SOLVID_META, Solv::SUSETAGS_FILE_NAME, what, Solv::Dataiterator::SEARCH_STRING)
335     di.prepend_keyname(Solv::SUSETAGS_FILE)
336     for d in di
337       dp = d.parentpos()
338       checksum = dp.lookup_checksum(Solv::SUSETAGS_FILE_CHECKSUM)
339       return what, checksum
340     end
341     return nil, nil
342   end
343
344   def load(pool)
345     return true if super(pool)
346     print "susetags repo '#{@name}: "
347     f = download("content", false, nil, nil)
348     if !f
349       puts "no content file, skipped"
350       @handle.free(true)
351       @handle = nil
352       return false
353     end
354     @cookie = calc_cookie_fp(f)
355     if usecachedrepo(nil, true)
356       puts "cached"
357       Solv.xfclose(f)
358       return true
359     end
360     @handle.add_content(f, 0)
361     Solv::xfclose(f)
362     puts "fetching"
363     defvendorid = @handle.lookup_id(Solv::SOLVID_META, Solv::SUSETAGS_DEFAULTVENDOR)
364     descrdir = @handle.lookup_str(Solv::SOLVID_META, Solv::SUSETAGS_DESCRDIR)
365     descrdir = "suse/setup/descr" unless descrdir
366     (filename, filechksum) = find('packages.gz')
367     (filename, filechksum) = find('packages') unless filename
368     if filename
369       f = download("#{descrdir}/#{filename}", true, filechksum, true)
370       if f
371         @handle.add_susetags(f, defvendorid, nil, Solv::Repo::REPO_NO_INTERNALIZE|Solv::Repo::SUSETAGS_RECORD_SHARES)
372         Solv::xfclose(f)
373         (filename, filechksum) = find('packages.en.gz')
374         (filename, filechksum) = find('packages.en') unless filename
375         if filename
376           f = download("#{descrdir}/#{filename}", true, filechksum, true)
377           if f
378             @handle.add_susetags(f, defvendorid, nil, Solv::Repo::REPO_NO_INTERNALIZE|Solv::Repo::REPO_REUSE_REPODATA|Solv::Repo::REPO_EXTEND_SOLVABLES)
379             Solv::xfclose(f)
380           end
381         end
382         @handle.internalize()
383       end
384     end
385     add_exts()
386     writecachedrepo(nil)
387     @handle.create_stubs()
388     return true
389   end
390
391   @@langtags = {
392     Solv::SOLVABLE_SUMMARY     => Solv::REPOKEY_TYPE_STR,
393     Solv::SOLVABLE_DESCRIPTION => Solv::REPOKEY_TYPE_STR,
394     Solv::SOLVABLE_EULA        => Solv::REPOKEY_TYPE_STR,
395     Solv::SOLVABLE_MESSAGEINS  => Solv::REPOKEY_TYPE_STR,
396     Solv::SOLVABLE_MESSAGEDEL  => Solv::REPOKEY_TYPE_STR,
397     Solv::SOLVABLE_CATEGORY    => Solv::REPOKEY_TYPE_ID,
398   }
399
400   def add_ext(repodata, what, ext)
401     (filename, filechksum) = find(what)
402     h = repodata.new_handle()
403     repodata.set_str(h, Solv::SUSETAGS_FILE_NAME, filename)
404     repodata.set_checksum(h, Solv::SUSETAGS_FILE_CHECKSUM, filechksum)
405     if ext == 'DL'
406       repodata.add_idarray(h, Solv::REPOSITORY_KEYS, Solv::REPOSITORY_DELTAINFO)
407       repodata.add_idarray(h, Solv::REPOSITORY_KEYS, Solv::REPOKEY_TYPE_FLEXARRAY)
408     elsif ext == 'DU'
409       repodata.add_idarray(h, Solv::REPOSITORY_KEYS, Solv::SOLVABLE_DISKUSAGE)
410       repodata.add_idarray(h, Solv::REPOSITORY_KEYS, Solv::REPOKEY_TYPE_DIRNUMNUMARRAY)
411     elsif ext == 'FL'
412       repodata.add_idarray(h, Solv::REPOSITORY_KEYS, Solv::SOLVABLE_FILELIST)
413       repodata.add_idarray(h, Solv::REPOSITORY_KEYS, Solv::REPOKEY_TYPE_DIRSTRARRAY)
414     else
415       @@langtags.sort.each do |langid, langtype|
416         repodata.add_idarray(h, Solv::REPOSITORY_KEYS, @handle.pool.id2langid(langid, ext, true))
417         repodata.add_idarray(h, Solv::REPOSITORY_KEYS, langtype)
418       end
419     end
420     repodata.add_flexarray(Solv::SOLVID_META, Solv::REPOSITORY_EXTERNAL, h)
421   end
422
423   def add_exts
424     repodata = @handle.add_repodata(0)
425     di = @handle.Dataiterator(Solv::SOLVID_META, Solv::SUSETAGS_FILE_NAME, nil, 0)
426     di.prepend_keyname(Solv::SUSETAGS_FILE)
427     for d in di
428       filename = d.str
429       next unless filename && filename =~ /^packages\.(..)(?:\..*)$/
430       next if $1 == 'en' || $1 == 'gz'
431       add_ext(repodata, filename, $1)
432     end
433     repodata.internalize()
434   end
435
436   def load_ext(repodata)
437     filename = repodata.lookup_str(Solv::SOLVID_META, Solv::SUSETAGS_FILE_NAME)
438     ext = filename[9,2]
439     print "[#{@name}:#{ext}: "
440     STDOUT.flush
441     if usecachedrepo(ext)
442       puts "cached]\n"
443       return true
444     end
445     puts "fetching]\n"
446     defvendorid = @handle.lookup_id(Solv::SOLVID_META, Solv::SUSETAGS_DEFAULTVENDOR)
447     descrdir = @handle.lookup_str(Solv::SOLVID_META, Solv::SUSETAGS_DESCRDIR)
448     descrdir = "suse/setup/descr" unless descrdir
449     filechksum = repodata.lookup_checksum(Solv::SOLVID_META, Solv::SUSETAGS_FILE_CHECKSUM)
450     f = download("#{descrdir}/#{filename}", true, filechksum)
451     return false unless f
452     @handle.add_susetags(f, defvendorid, ext, Solv::Repo::REPO_USE_LOADING|Solv::Repo::REPO_EXTEND_SOLVABLES)
453     Solv::xfclose(f)
454     writecachedrepo(ext, repodata)
455     return true
456   end
457
458   def packagespath()
459     datadir = @handle.lookup_str(Solv::SOLVID_META, Solv::SUSETAGS_DATADIR)
460     datadir = "suse" unless datadir
461     return datadir + '/'
462   end
463 end
464
465 class Repo_unknown < Repo_generic
466   def load(pool)
467     puts "unsupported repo '#{@name}: skipped"
468     return false
469   end
470 end
471
472 class Repo_system < Repo_generic
473   def load(pool)
474     @handle = pool.add_repo(@name)
475     @handle.appdata = self
476     pool.installed = @handle
477     print "rpm database: "
478     @cookie = calc_cookie_file("/var/lib/rpm/Packages")
479     if usecachedrepo(nil)
480       puts "cached"
481       return true
482     end
483     puts "reading"
484     if @handle.respond_to? :add_products
485       @handle.add_products("/etc/products.d", Solv::Repo::REPO_NO_INTERNALIZE)
486     end
487     @handle.add_rpmdb(nil, Solv::Repo::REPO_REUSE_REPODATA)
488     writecachedrepo(nil)
489     return true
490   end
491 end
492
493 args = ARGV
494 cmd = args.shift
495 cmd = 'list' if cmd == 'li'
496 cmd = 'install' if cmd == 'in'
497 cmd = 'erase' if cmd == 'rm'
498 cmd = 'verify' if cmd == 've'
499 cmd = 'search' if cmd == 'se'
500
501 repos = []
502 reposdirs = []
503 if FileTest.directory?('/etc/zypp/repos.d')
504   reposdirs = [ '/etc/zypp/repos.d' ]
505 else
506   reposdirs = [ '/etc/yum/repos.d' ]
507 end
508 for reposdir in reposdirs do
509   next unless FileTest.directory?(reposdir)
510   for reponame in Dir["#{reposdir}/*.repo"].sort do
511     cfg = IniFile.new(reponame)
512     cfg.each_section do |ali|
513       repoattr = { 'alias' => ali, 'enabled' => 0, 'priority' => 99, 'autorefresh' => 1, 'type' => 'rpm-md', 'metadata_expire' => 900}
514       repoattr.update(cfg[ali])
515       if repoattr['type'] == 'rpm-md'
516         repo = Repo_rpmmd.new(ali, 'repomd', repoattr)
517       elsif repoattr['type'] == 'yast2'
518         repo = Repo_susetags.new(ali, 'susetags', repoattr)
519       else
520         repo = Repo_unknown.new(ali, 'unknown', repoattr)
521       end
522       repos.push(repo)
523     end
524   end
525 end
526
527 pool = Solv::Pool.new()
528 pool.setarch()
529
530 pool.set_loadcallback { |repodata|
531   repo = repodata.repo.appdata
532   repo ? repo.load_ext(repodata) : false
533 }
534
535 sysrepo = Repo_system.new('@System', 'system')
536 sysrepo.load(pool)
537 for repo in repos
538   repo.load(pool) if repo.enabled?
539 end
540
541 if cmd == 'search'
542   matches = {}
543   for di in pool.Dataiterator(0, Solv::SOLVABLE_NAME, args[0], Solv::Dataiterator::SEARCH_SUBSTRING | Solv::Dataiterator::SEARCH_NOCASE)
544     matches[di.solvid] = true
545   end
546   for solvid in matches.keys.sort
547     s = pool.solvables[solvid]
548     puts "- #{s.str} [#{s.repo.name}]"
549   end
550   exit
551 end
552
553 addedprovides = pool.addfileprovides_queue()
554 if !addedprovides.empty?
555   sysrepo.updateaddedprovides(addedprovides)
556   for repo in repos
557     repo.updateaddedprovides(addedprovides)
558   end
559 end
560 pool.createwhatprovides()
561
562 jobs = []
563 for arg in args
564   flags = Solv::Selection::SELECTION_NAME | Solv::Selection::SELECTION_PROVIDES|Solv::Selection::SELECTION_GLOB
565   if arg =~ /^\//
566     flags |= Solv::Selection::SELECTION_FILELIST
567     flags |= Solv::Selection::SELECTION_INSTALLED_ONLY if cmd == 'erase'
568   end
569   sel = pool.select(arg, flags)
570   if sel.isempty?
571     sel = pool.select(arg, flags |  Solv::Selection::SELECTION_NOCASE)
572     puts "[ignoring case for '#{arg}']" unless sel.isempty?
573   end
574   puts "[using file list match for '#{arg}']" if sel.flags & Solv::Selection::SELECTION_FILELIST != 0
575   puts "[using capability match for '#{arg}']" if sel.flags & Solv::Selection::SELECTION_PROVIDES != 0
576   jobs += sel.jobs(0)
577 end
578
579 if jobs.empty? && (cmd == 'up' || cmd == 'dup' || cmd == 'verify')
580   sel = pool.Selection()
581   sel.addsimple(Solv::Job::SOLVER_SOLVABLE_ALL, 0)
582   jobs += sel.jobs(0)
583 end
584
585 if cmd == 'list' || cmd == 'info'
586   abort("no package matched.") if jobs.empty?
587     for job in jobs
588       for s in job.solvables()
589         if cmd == 'info'
590           puts "Name:        #{s.str}"
591           puts "Repo:        #{s.repo.name}"
592           puts "Summary:     #{s.lookup_str(Solv::SOLVABLE_SUMMARY)}"
593           str = s.lookup_str(Solv::SOLVABLE_URL)
594           puts "Url:         #{str}" if str
595           str = s.lookup_str(Solv::SOLVABLE_LICENSE)
596           puts "License:     #{str}" if str
597           puts "Description:\n#{s.lookup_str(Solv::SOLVABLE_DESCRIPTION)}"
598           puts
599         else
600           puts "  - #{s.str} [#{s.repo.name}]"
601           puts "    #{s.lookup_str(Solv::SOLVABLE_SUMMARY)}"
602         end
603       end
604     end
605   exit
606 end
607
608 if cmd == 'install' || cmd == 'erase' || cmd == 'up' || cmd == 'dup' || cmd == 'verify'
609   abort("no package matched.") if jobs.empty?
610   for job in jobs
611     if cmd == 'up'
612       if job.how == Solv::Job::SOLVER_SOLVABLE_ALL || job.solvables.any? {|s| s.isinstalled?}
613         job.how |= Solv::Job::SOLVER_UPDATE
614       else
615         job.how |= Solv::Job::SOLVER_INSTALL
616       end
617     elsif cmd == 'install'
618       job.how |= Solv::Job::SOLVER_INSTALL
619     elsif cmd == 'erase'
620       job.how |= Solv::Job::SOLVER_ERASE
621     elsif cmd == 'dup'
622       job.how |= Solv::Job::SOLVER_DISTUPGRADE
623     elsif cmd == 'verify'
624       job.how |= Solv::Job::SOLVER_VERIFY
625     end
626   end
627
628   solver = nil
629   #pool.set_debuglevel(1)
630   while true
631     solver = pool.Solver
632     solver.set_flag(Solv::Solver::SOLVER_FLAG_SPLITPROVIDES, 1)
633     solver.set_flag(Solv::Solver::SOLVER_FLAG_ALLOW_UNINSTALL, 1) if cmd == 'erase'
634     problems = solver.solve(jobs)
635     break if problems.empty?
636     for problem in problems
637       puts "Problem #{problem.id}:"
638       puts problem.findproblemrule.info.problemstr
639       solutions = problem.solutions
640       for solution in solutions
641         puts "  Solution #{solution.id}:"
642         elements = solution.elements(true)
643         for element in elements
644           puts "  - #{element.str}"
645         end
646         puts
647       end
648       sol = nil
649       while true
650         print "Please choose a solution: "
651         STDOUT.flush
652         sol = STDIN.gets.strip
653         break if sol == 's' || sol == 'q'
654         break if sol =~ /^\d+$/ && sol.to_i >= 1 && sol.to_i <= solutions.length
655       end
656       next if sol == 's'
657       abort if sol == 'q'
658       solution = solutions[sol.to_i - 1]
659       for element in solution.elements
660         newjob = element.Job()
661         if element.type == Solv::Solver::SOLVER_SOLUTION_JOB
662           jobs[element.jobidx] = newjob
663         else
664           jobs.push(newjob) if newjob && !jobs.include?(newjob)
665         end
666       end
667     end
668   end
669   trans = solver.transaction
670   solver = nil
671   if trans.isempty?
672     puts "Nothing to do."
673     exit
674   end
675   puts "\nTransaction summary:\n"
676   for cl in trans.classify()
677     if cl.type == Solv::Transaction::SOLVER_TRANSACTION_ERASE
678       puts "#{cl.count} erased packages:"
679     elsif cl.type == Solv::Transaction::SOLVER_TRANSACTION_INSTALL
680       puts "#{cl.count} installed packages:"
681     elsif cl.type == Solv::Transaction::SOLVER_TRANSACTION_REINSTALLED
682       puts "#{cl.count} reinstalled packages:"
683     elsif cl.type == Solv::Transaction::SOLVER_TRANSACTION_DOWNGRADED
684       puts "#{cl.count} downgraded packages:"
685     elsif cl.type == Solv::Transaction::SOLVER_TRANSACTION_CHANGED
686       puts "#{cl.count} changed packages:"
687     elsif cl.type == Solv::Transaction::SOLVER_TRANSACTION_UPGRADED
688       puts "#{cl.count} upgraded packages:"
689     elsif cl.type == Solv::Transaction::SOLVER_TRANSACTION_VENDORCHANGE
690       puts "#{cl.count} vendor changes from '#{pool.id2str(cl.fromid)}' to '#{pool.id2str(cl.toid)}':"
691     elsif cl.type == Solv::Transaction::SOLVER_TRANSACTION_ARCHCHANGE
692       puts "#{cl.count} arch changes from '#{pool.id2str(cl.fromid)}' to '#{pool.id2str(cl.toid)}':"
693     else
694       next
695     end
696     for p in cl.solvables
697       if cl.type == Solv::Transaction::SOLVER_TRANSACTION_UPGRADED || cl.type == Solv::Transaction::SOLVER_TRANSACTION_DOWNGRADED
698         puts "  - #{p.str} -> #{trans.othersolvable(p).str}"
699       else
700         puts "  - #{p.str}"
701       end
702     end
703     puts
704   end
705   puts "install size change: #{trans.calc_installsizechange()} K\n\n"
706   while true:
707     print("OK to continue (y/n)? ")
708     STDOUT.flush
709     yn = STDIN.gets.strip
710     break if yn == 'y'
711     abort if yn == 'n'
712   end
713   newpkgs = trans.newpackages()
714   newpkgsfp = {}
715   if !newpkgs.empty?
716     downloadsize = 0
717     for p in newpkgs
718       downloadsize += p.lookup_num(Solv::SOLVABLE_DOWNLOADSIZE)
719     end
720     puts "Downloading #{newpkgs.length} packages, #{downloadsize} K"
721     for p in newpkgs
722       repo = p.repo.appdata
723       location, medianr = p.lookup_location()
724       next unless location
725       location = repo.packagespath + location
726       chksum = p.lookup_checksum(Solv::SOLVABLE_CHECKSUM)
727       f = repo.download(location, false, chksum)
728       abort("\n#{@name}: #{location} not found in repository\n") unless f
729       newpkgsfp[p.id] = f
730       print "."
731       STDOUT.flush()
732     end
733     puts
734   end
735   puts "Committing transaction:"
736   puts
737   trans.order(0)
738   for p in trans.steps
739     steptype = trans.steptype(p, Solv::Transaction::SOLVER_TRANSACTION_RPM_ONLY)
740     if steptype == Solv::Transaction::SOLVER_TRANSACTION_ERASE
741       puts "erase #{p.str}"
742       next unless p.lookup_num(Solv::RPM_RPMDBID)
743       evr = p.evr.sub(/^[0-9]+:/, '')
744       system('rpm', '-e', '--nodeps', '--nodigest', '--nosignature', "#{p.name}-#{evr}.#{p.arch}") || abort("rpm failed: #{$? >> 8}") 
745     elsif (steptype == Solv::Transaction::SOLVER_TRANSACTION_INSTALL || steptype == Solv::Transaction::SOLVER_TRANSACTION_MULTIINSTALL)
746       puts "install #{p.str}"
747       f = newpkgsfp.delete(p.id)
748       next unless f
749       mode = steptype == Solv::Transaction::SOLVER_TRANSACTION_INSTALL ? '-U' : '-i'
750       system('rpm', mode, '--force', '--nodeps', '--nodigest', '--nosignature', "/dev/fd/#{Solv::xfileno(f).to_s}") || abort("rpm failed: #{$? >> 8}")
751       solv::xfclose(f)
752     end
753   end
754 end