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