get rid of the ugly load_if_changed method
[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 # require 'sys/uname' ; sysarch = Sys::Uname.machine
529 sysarch = `uname -p`.strip
530 pool.setarch(sysarch)
531
532 pool.set_loadcallback { |repodata|
533   repo = repodata.repo.appdata
534   repo ? repo.load_ext(repodata) : false
535 }
536
537 sysrepo = Repo_system.new('@System', 'system')
538 sysrepo.load(pool)
539 for repo in repos
540   repo.load(pool) if repo.enabled?
541 end
542
543 if cmd == 'search'
544   matches = {}
545   for di in pool.Dataiterator(0, Solv::SOLVABLE_NAME, args[0], Solv::Dataiterator::SEARCH_SUBSTRING | Solv::Dataiterator::SEARCH_NOCASE)
546     matches[di.solvid] = true
547   end
548   for solvid in matches.keys.sort
549     s = pool.solvables[solvid]
550     puts "- #{s.str} [#{s.repo.name}]"
551   end
552   exit
553 end
554
555 addedprovides = pool.addfileprovides_queue()
556 if !addedprovides.empty?
557   sysrepo.updateaddedprovides(addedprovides)
558   for repo in repos
559     repo.updateaddedprovides(addedprovides)
560   end
561 end
562 pool.createwhatprovides()
563
564 jobs = []
565 for arg in args
566   flags = Solv::Selection::SELECTION_NAME | Solv::Selection::SELECTION_PROVIDES|Solv::Selection::SELECTION_GLOB
567   if arg =~ /^\//
568     flags |= Solv::Selection::SELECTION_FILELIST
569     flags |= Solv::Selection::SELECTION_INSTALLED_ONLY if cmd == 'erase'
570   end
571   sel = pool.select(arg, flags)
572   if sel.isempty?
573     sel = pool.select(arg, flags |  Solv::Selection::SELECTION_NOCASE)
574     puts "[ignoring case for '#{arg}']" unless sel.isempty?
575   end
576   puts "[using file list match for '#{arg}']" if sel.flags & Solv::Selection::SELECTION_FILELIST != 0
577   puts "[using capability match for '#{arg}']" if sel.flags & Solv::Selection::SELECTION_PROVIDES != 0
578   jobs += sel.jobs(0)
579 end
580
581 if jobs.empty? && (cmd == 'up' || cmd == 'dup' || cmd == 'verify')
582   sel = pool.Selection()
583   sel.addsimple(Solv::Job::SOLVER_SOLVABLE_ALL, 0)
584   jobs += sel.jobs(0)
585 end
586
587 if cmd == 'list' || cmd == 'info'
588   abort("no package matched.") if jobs.empty?
589     for job in jobs
590       for s in job.solvables()
591         if cmd == 'info'
592           puts "Name:        #{s.str}"
593           puts "Repo:        #{s.repo.name}"
594           puts "Summary:     #{s.lookup_str(Solv::SOLVABLE_SUMMARY)}"
595           str = s.lookup_str(Solv::SOLVABLE_URL)
596           puts "Url:         #{str}" if str
597           str = s.lookup_str(Solv::SOLVABLE_LICENSE)
598           puts "License:     #{str}" if str
599           puts "Description:\n#{s.lookup_str(Solv::SOLVABLE_DESCRIPTION)}"
600           puts
601         else
602           puts "  - #{s.str} [#{s.repo.name}]"
603           puts "    #{s.lookup_str(Solv::SOLVABLE_SUMMARY)}"
604         end
605       end
606     end
607   exit
608 end
609
610 if cmd == 'install' || cmd == 'erase' || cmd == 'up' || cmd == 'dup' || cmd == 'verify'
611   abort("no package matched.") if jobs.empty?
612   for job in jobs
613     if cmd == 'up'
614       if job.how == Solv::Job::SOLVER_SOLVABLE_ALL || job.solvables.any? {|s| s.isinstalled?}
615         job.how |= Solv::Job::SOLVER_UPDATE
616       else
617         job.how |= Solv::Job::SOLVER_INSTALL
618       end
619     elsif cmd == 'install'
620       job.how |= Solv::Job::SOLVER_INSTALL
621     elsif cmd == 'erase'
622       job.how |= Solv::Job::SOLVER_ERASE
623     elsif cmd == 'dup'
624       job.how |= Solv::Job::SOLVER_DISTUPGRADE
625     elsif cmd == 'verify'
626       job.how |= Solv::Job::SOLVER_VERIFY
627     end
628   end
629
630   solver = nil
631   #pool.set_debuglevel(1)
632   while true
633     solver = pool.Solver
634     solver.set_flag(Solv::Solver::SOLVER_FLAG_SPLITPROVIDES, 1)
635     solver.set_flag(Solv::Solver::SOLVER_FLAG_ALLOW_UNINSTALL, 1) if cmd == 'erase'
636     problems = solver.solve(jobs)
637     break if problems.empty?
638     for problem in problems
639       puts "Problem #{problem.id}:"
640       puts problem.findproblemrule.info.problemstr
641       solutions = problem.solutions
642       for solution in solutions
643         puts "  Solution #{solution.id}:"
644         elements = solution.elements(true)
645         for element in elements
646           puts "  - #{element.str}"
647         end
648         puts
649       end
650       sol = nil
651       while true
652         print "Please choose a solution: "
653         STDOUT.flush
654         sol = STDIN.gets.strip
655         break if sol == 's' || sol == 'q'
656         break if sol =~ /^\d+$/ && sol.to_i >= 1 && sol.to_i <= solutions.length
657       end
658       next if sol == 's'
659       abort if sol == 'q'
660       solution = solutions[sol.to_i - 1]
661       for element in solution.elements
662         newjob = element.Job()
663         if element.type == Solv::Solver::SOLVER_SOLUTION_JOB
664           jobs[element.jobidx] = newjob
665         else
666           jobs.push(newjob) if newjob && !jobs.include?(newjob)
667         end
668       end
669     end
670   end
671   trans = solver.transaction
672   solver = nil
673   if trans.isempty?
674     puts "Nothing to do."
675     exit
676   end
677   puts "\nTransaction summary:\n"
678   for cl in trans.classify()
679     if cl.type == Solv::Transaction::SOLVER_TRANSACTION_ERASE
680       puts "#{cl.count} erased packages:"
681     elsif cl.type == Solv::Transaction::SOLVER_TRANSACTION_INSTALL
682       puts "#{cl.count} installed packages:"
683     elsif cl.type == Solv::Transaction::SOLVER_TRANSACTION_REINSTALLED
684       puts "#{cl.count} reinstalled packages:"
685     elsif cl.type == Solv::Transaction::SOLVER_TRANSACTION_DOWNGRADED
686       puts "#{cl.count} downgraded packages:"
687     elsif cl.type == Solv::Transaction::SOLVER_TRANSACTION_CHANGED
688       puts "#{cl.count} changed packages:"
689     elsif cl.type == Solv::Transaction::SOLVER_TRANSACTION_UPGRADED
690       puts "#{cl.count} upgraded packages:"
691     elsif cl.type == Solv::Transaction::SOLVER_TRANSACTION_VENDORCHANGE
692       puts "#{cl.count} vendor changes from '#{pool.id2str(cl.fromid)}' to '#{pool.id2str(cl.toid)}':"
693     elsif cl.type == Solv::Transaction::SOLVER_TRANSACTION_ARCHCHANGE
694       puts "#{cl.count} arch changes from '#{pool.id2str(cl.fromid)}' to '#{pool.id2str(cl.toid)}':"
695     else
696       next
697     end
698     for p in cl.solvables
699       if cl.type == Solv::Transaction::SOLVER_TRANSACTION_UPGRADED || cl.type == Solv::Transaction::SOLVER_TRANSACTION_DOWNGRADED
700         puts "  - #{p.str} -> #{trans.othersolvable(p).str}"
701       else
702         puts "  - #{p.str}"
703       end
704     end
705     puts
706   end
707   puts "install size change: #{trans.calc_installsizechange()} K\n\n"
708   while true:
709     print("OK to continue (y/n)? ")
710     STDOUT.flush
711     yn = STDIN.gets.strip
712     break if yn == 'y'
713     abort if yn == 'n'
714   end
715   newpkgs = trans.newpackages()
716   newpkgsfp = {}
717   if !newpkgs.empty?
718     downloadsize = 0
719     for p in newpkgs
720       downloadsize += p.lookup_num(Solv::SOLVABLE_DOWNLOADSIZE)
721     end
722     puts "Downloading #{newpkgs.length} packages, #{downloadsize} K"
723     for p in newpkgs
724       repo = p.repo.appdata
725       location, medianr = p.lookup_location()
726       next unless location
727       location = repo.packagespath + location
728       chksum = p.lookup_checksum(Solv::SOLVABLE_CHECKSUM)
729       f = repo.download(location, false, chksum)
730       abort("\n#{@name}: #{location} not found in repository\n") unless f
731       newpkgsfp[p.id] = f
732       print "."
733       STDOUT.flush()
734     end
735     puts
736   end
737   puts "Committing transaction:"
738   puts
739   trans.order(0)
740   for p in trans.steps
741     steptype = trans.steptype(p, Solv::Transaction::SOLVER_TRANSACTION_RPM_ONLY)
742     if steptype == Solv::Transaction::SOLVER_TRANSACTION_ERASE
743       puts "erase #{p.str}"
744       next unless p.lookup_num(Solv::RPM_RPMDBID)
745       evr = p.evr.sub(/^[0-9]+:/, '')
746       system('rpm', '-e', '--nodeps', '--nodigest', '--nosignature', "#{p.name}-#{evr}.#{p.arch}") || abort("rpm failed: #{$? >> 8}") 
747     elsif (steptype == Solv::Transaction::SOLVER_TRANSACTION_INSTALL || steptype == Solv::Transaction::SOLVER_TRANSACTION_MULTIINSTALL)
748       puts "install #{p.str}"
749       f = newpkgsfp.delete(p.id)
750       next unless f
751       mode = steptype == Solv::Transaction::SOLVER_TRANSACTION_INSTALL ? '-U' : '-i'
752       system('rpm', mode, '--force', '--nodeps', '--nodigest', '--nosignature', "/dev/fd/#{Solv::xfileno(f).to_s}") || abort("rpm failed: #{$? >> 8}")
753       solv::xfclose(f)
754     end
755   end
756 end