small cleanup: move incomplete check into writecachedrepo
[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 load_if_changed()
76   end
77
78   def load_ext(repodata)
79     return false
80   end
81
82   def load_if_changed
83     return false
84   end
85
86   def download(file, uncompress, chksum, markincomplete = false)
87     url = @attribs['baseurl']
88     if !url
89       puts "%{@name}: no baseurl"
90       return nil
91     end
92     url = url.sub(/\/$/, '') + "/#{file}"
93     f =  Tempfile.new('rbsolv')
94     f.unlink
95     st = system('curl', '-f', '-s', '-L', '-o', "/dev/fd/" + f.fileno.to_s, '--', url)
96     return nil if f.stat.size == 0 && (st || !chksum)
97     if !st
98         puts "#{file}: download error #{$? >> 8}"
99         @incomplete = true if markincomplete
100         return nil
101     end
102     if chksum
103       fchksum = Solv::Chksum.new(chksum.type)
104       fchksum.add_fd(f.fileno)
105       if !fchksum == chksum
106         puts "#{file}: checksum error"
107         @incomplete = true if markincomplete
108         return nil
109       end
110     end
111     if uncompress
112       return Solv::xfopen_dup(file, f.fileno)
113     else
114       return Solv::xfopen_dup('', f.fileno)
115     end
116   end
117
118   def usecachedrepo(ext, mark = false)
119     cookie = ext ? @extcookie : @cookie
120     begin
121       repopath = cachepath(ext)
122       f = File.new(repopath, "r")
123       f.sysseek(-32, IO::SEEK_END)
124       fcookie = f.sysread(32)
125       return false if fcookie.length != 32
126       return false if cookie && fcookie != cookie
127       if !ext && @type != 'system'
128         f.sysseek(-32 * 2, IO::SEEK_END)
129         fextcookie = f.sysread(32)
130         return false if fextcookie.length != 32
131       end
132       f.sysseek(0, IO::SEEK_SET)
133       f = Solv::xfopen_dup('', f.fileno)
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(f, flags)
137         Solv::xfclose(f)
138         return false
139       end
140       Solv::xfclose(f)
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 && !ext
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_dup('', 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       Solv::xfclose(sf)
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(false)
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           Solv::xfclose(sf)
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_if_changed
237     print "rpmmd repo '#{@name}: "
238     f = download("repodata/repomd.xml", false, nil, nil)
239     if !f
240       puts "no repomd.xml file, skipped"
241       @handle.free(true)
242       @handle = nil
243       return false
244     end
245     @cookie = calc_cookie_fp(f)
246     if usecachedrepo(nil, true)
247       puts "cached"
248       Solv.xfclose(f)
249       return true
250     end
251     @handle.add_repomdxml(f, 0)
252     Solv::xfclose(f)
253     puts "fetching"
254     filename, filechksum = find('primary')
255     if filename
256       f = download(filename, true, filechksum, true)
257       if f
258         @handle.add_rpmmd(f, nil, 0)
259         Solv::xfclose(f)
260       end
261       return false if @incomplete
262     end
263     filename, filechksum = find('updateinfo')
264     if filename
265       f = download(filename, true, filechksum, true)
266       if f
267         @handle.add_updateinfoxml(f, 0)
268         Solv::xfclose(f)
269       end
270     end
271     add_exts()
272     writecachedrepo(nil)
273     @handle.create_stubs()
274     return true
275   end
276
277   def add_ext(repodata, what, ext)
278     filename, filechksum = find(what)
279     filename, filechksum = find('prestodelta') if !filename && what == 'deltainfo'
280     return unless filename
281     h = repodata.new_handle()
282     repodata.set_poolstr(h, Solv::REPOSITORY_REPOMD_TYPE, what)
283     repodata.set_str(h, Solv::REPOSITORY_REPOMD_LOCATION, filename)
284     repodata.set_checksum(h, Solv::REPOSITORY_REPOMD_CHECKSUM, filechksum)
285     if ext == 'DL'
286       repodata.add_idarray(h, Solv::REPOSITORY_KEYS, Solv::REPOSITORY_DELTAINFO)
287       repodata.add_idarray(h, Solv::REPOSITORY_KEYS, Solv::REPOKEY_TYPE_FLEXARRAY)
288     elsif ext == 'FL'
289       repodata.add_idarray(h, Solv::REPOSITORY_KEYS, Solv::SOLVABLE_FILELIST)
290       repodata.add_idarray(h, Solv::REPOSITORY_KEYS, Solv::REPOKEY_TYPE_DIRSTRARRAY)
291     end
292     repodata.add_flexarray(Solv::SOLVID_META, Solv::REPOSITORY_EXTERNAL, h)
293   end
294
295   def add_exts
296     repodata = @handle.add_repodata(0)
297     add_ext(repodata, 'deltainfo', 'DL')
298     add_ext(repodata, 'filelists', 'FL')
299     repodata.internalize()
300   end
301
302   def load_ext(repodata)
303     repomdtype = repodata.lookup_str(Solv::SOLVID_META, Solv::REPOSITORY_REPOMD_TYPE)
304     if repomdtype == 'filelists'
305       ext = 'FL'
306     elsif repomdtype == 'deltainfo'
307       ext = 'DL'
308     else
309       return false
310     end
311     print "[#{@name}:#{ext}: "
312     STDOUT.flush
313     if usecachedrepo(ext)
314       puts "cached]\n"
315       return true
316     end
317     puts "fetching]\n"
318     filename = repodata.lookup_str(Solv::SOLVID_META, Solv::REPOSITORY_REPOMD_LOCATION)
319     filechksum = repodata.lookup_checksum(Solv::SOLVID_META, Solv::REPOSITORY_REPOMD_CHECKSUM)
320     f = download(filename, true, filechksum)
321     return false unless f
322     if ext == 'FL'
323       @handle.add_rpmmd(f, 'FL', Solv::Repo::REPO_USE_LOADING|Solv::Repo::REPO_EXTEND_SOLVABLES)
324     elsif ext == 'DL'
325       @handle.add_deltainfoxml(f, Solv::Repo::REPO_USE_LOADING)
326     end
327     Solv::xfclose(f)
328     writecachedrepo(ext, repodata)
329     return true
330   end
331
332 end
333
334 class Repo_susetags < Repo_generic
335
336   def find(what)
337     di = @handle.Dataiterator(Solv::SOLVID_META, Solv::SUSETAGS_FILE_NAME, what, Solv::Dataiterator::SEARCH_STRING)
338     di.prepend_keyname(Solv::SUSETAGS_FILE)
339     for d in di
340       dp = d.parentpos()
341       checksum = dp.lookup_checksum(Solv::SUSETAGS_FILE_CHECKSUM)
342       return what, checksum
343     end
344     return nil, nil
345   end
346
347   def load_if_changed
348     print "susetags repo '#{@name}: "
349     f = download("content", false, nil, nil)
350     if !f
351       puts "no content file, skipped"
352       @handle.free(true)
353       @handle = nil
354       return false
355     end
356     @cookie = calc_cookie_fp(f)
357     if usecachedrepo(nil, true)
358       puts "cached"
359       Solv.xfclose(f)
360       return true
361     end
362     @handle.add_content(f, 0)
363     Solv::xfclose(f)
364     puts "fetching"
365     defvendorid = @handle.lookup_id(Solv::SOLVID_META, Solv::SUSETAGS_DEFAULTVENDOR)
366     descrdir = @handle.lookup_str(Solv::SOLVID_META, Solv::SUSETAGS_DESCRDIR)
367     descrdir = "suse/setup/descr" unless descrdir
368     (filename, filechksum) = find('packages.gz')
369     (filename, filechksum) = find('packages') unless filename
370     if filename
371       f = download("#{descrdir}/#{filename}", true, filechksum, true)
372       if f
373         @handle.add_susetags(f, defvendorid, nil, Solv::Repo::REPO_NO_INTERNALIZE|Solv::Repo::SUSETAGS_RECORD_SHARES)
374         Solv::xfclose(f)
375         (filename, filechksum) = find('packages.en.gz')
376         (filename, filechksum) = find('packages.en') unless filename
377         if filename
378           f = download("#{descrdir}/#{filename}", true, filechksum, true)
379           if f
380             @handle.add_susetags(f, defvendorid, nil, Solv::Repo::REPO_NO_INTERNALIZE|Solv::Repo::REPO_REUSE_REPODATA|Solv::Repo::REPO_EXTEND_SOLVABLES)
381             Solv::xfclose(f)
382           end
383         end
384         @handle.internalize()
385       end
386     end
387     add_exts()
388     writecachedrepo(nil)
389     @handle.create_stubs()
390     return true
391   end
392
393   @@langtags = {
394     Solv::SOLVABLE_SUMMARY     => Solv::REPOKEY_TYPE_STR,
395     Solv::SOLVABLE_DESCRIPTION => Solv::REPOKEY_TYPE_STR,
396     Solv::SOLVABLE_EULA        => Solv::REPOKEY_TYPE_STR,
397     Solv::SOLVABLE_MESSAGEINS  => Solv::REPOKEY_TYPE_STR,
398     Solv::SOLVABLE_MESSAGEDEL  => Solv::REPOKEY_TYPE_STR,
399     Solv::SOLVABLE_CATEGORY    => Solv::REPOKEY_TYPE_ID,
400   }
401
402   def add_ext(repodata, what, ext)
403     (filename, filechksum) = find(what)
404     h = repodata.new_handle()
405     repodata.set_str(h, Solv::SUSETAGS_FILE_NAME, filename)
406     repodata.set_checksum(h, Solv::SUSETAGS_FILE_CHECKSUM, filechksum)
407     if ext == 'DL'
408       repodata.add_idarray(h, Solv::REPOSITORY_KEYS, Solv::REPOSITORY_DELTAINFO)
409       repodata.add_idarray(h, Solv::REPOSITORY_KEYS, Solv::REPOKEY_TYPE_FLEXARRAY)
410     elsif ext == 'DU'
411       repodata.add_idarray(h, Solv::REPOSITORY_KEYS, Solv::SOLVABLE_DISKUSAGE)
412       repodata.add_idarray(h, Solv::REPOSITORY_KEYS, Solv::REPOKEY_TYPE_DIRNUMNUMARRAY)
413     elsif ext == 'FL'
414       repodata.add_idarray(h, Solv::REPOSITORY_KEYS, Solv::SOLVABLE_FILELIST)
415       repodata.add_idarray(h, Solv::REPOSITORY_KEYS, Solv::REPOKEY_TYPE_DIRSTRARRAY)
416     else
417       @@langtags.sort.each do |langid, langtype|
418         repodata.add_idarray(h, Solv::REPOSITORY_KEYS, @handle.pool.id2langid(langid, ext, true))
419         repodata.add_idarray(h, Solv::REPOSITORY_KEYS, langtype)
420       end
421     end
422     repodata.add_flexarray(Solv::SOLVID_META, Solv::REPOSITORY_EXTERNAL, h)
423   end
424
425   def add_exts
426     repodata = @handle.add_repodata(0)
427     di = @handle.Dataiterator(Solv::SOLVID_META, Solv::SUSETAGS_FILE_NAME, nil, 0)
428     di.prepend_keyname(Solv::SUSETAGS_FILE)
429     for d in di
430       filename = d.str
431       next unless filename && filename =~ /^packages\.(..)(?:\..*)$/
432       next if $1 == 'en' || $1 == 'gz'
433       add_ext(repodata, filename, $1)
434     end
435     repodata.internalize()
436   end
437
438   def load_ext(repodata)
439     filename = repodata.lookup_str(Solv::SOLVID_META, Solv::SUSETAGS_FILE_NAME)
440     ext = filename[9,2]
441     print "[#{@name}:#{ext}: "
442     STDOUT.flush
443     if usecachedrepo(ext)
444       puts "cached]\n"
445       return true
446     end
447     puts "fetching]\n"
448     defvendorid = @handle.lookup_id(Solv::SOLVID_META, Solv::SUSETAGS_DEFAULTVENDOR)
449     descrdir = @handle.lookup_str(Solv::SOLVID_META, Solv::SUSETAGS_DESCRDIR)
450     descrdir = "suse/setup/descr" unless descrdir
451     filechksum = repodata.lookup_checksum(Solv::SOLVID_META, Solv::SUSETAGS_FILE_CHECKSUM)
452     f = download("#{descrdir}/#{filename}", true, filechksum)
453     return false unless f
454     @handle.add_susetags(f, defvendorid, ext, Solv::Repo::REPO_USE_LOADING|Solv::Repo::REPO_EXTEND_SOLVABLES)
455     Solv::xfclose(f)
456     writecachedrepo(ext, repodata)
457     return true
458   end
459
460   def packagespath()
461     datadir = @handle.lookup_str(Solv::SOLVID_META, Solv::SUSETAGS_DATADIR)
462     datadir = "suse" unless datadir
463     return datadir + '/'
464   end
465 end
466
467 class Repo_unknown < Repo_generic
468   def load(pool)
469     puts "unsupported repo '#{@name}: skipped"
470     return false
471   end
472 end
473
474 class Repo_system < Repo_generic
475   def load(pool)
476     @handle = pool.add_repo(@name)
477     @handle.appdata = self
478     pool.installed = @handle
479     print "rpm database: "
480     @cookie = calc_cookie_file("/var/lib/rpm/Packages")
481     if usecachedrepo(nil)
482       puts "cached"
483       return true
484     end
485     puts "reading"
486     if @handle.respond_to? :add_products
487       @handle.add_products("/etc/products.d", Solv::Repo::REPO_NO_INTERNALIZE)
488     end
489     @handle.add_rpmdb(nil, Solv::Repo::REPO_REUSE_REPODATA)
490     writecachedrepo(nil)
491     return true
492   end
493 end
494
495 args = ARGV
496 cmd = args.shift
497 cmd = 'list' if cmd == 'li'
498 cmd = 'install' if cmd == 'in'
499 cmd = 'erase' if cmd == 'rm'
500 cmd = 'verify' if cmd == 've'
501 cmd = 'search' if cmd == 'se'
502
503 repos = []
504 reposdirs = []
505 if FileTest.directory?('/etc/zypp/repos.d')
506   reposdirs = [ '/etc/zypp/repos.d' ]
507 else
508   reposdirs = [ '/etc/yum/repos.d' ]
509 end
510 for reposdir in reposdirs do
511   next unless FileTest.directory?(reposdir)
512   for reponame in Dir["#{reposdir}/*.repo"].sort do
513     cfg = IniFile.new(reponame)
514     cfg.each_section do |ali|
515       repoattr = { 'alias' => ali, 'enabled' => 0, 'priority' => 99, 'autorefresh' => 1, 'type' => 'rpm-md', 'metadata_expire' => 900}
516       repoattr.update(cfg[ali])
517       if repoattr['type'] == 'rpm-md'
518         repo = Repo_rpmmd.new(ali, 'repomd', repoattr)
519       elsif repoattr['type'] == 'yast2'
520         repo = Repo_susetags.new(ali, 'susetags', repoattr)
521       else
522         repo = Repo_unknown.new(ali, 'unknown', repoattr)
523       end
524       repos.push(repo)
525     end
526   end
527 end
528
529 pool = Solv::Pool.new()
530 # require 'sys/uname' ; sysarch = Sys::Uname.machine
531 sysarch = `uname -p`.strip
532 pool.setarch(sysarch)
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 '#{pool.id2str(cl.fromid)}' to '#{pool.id2str(cl.toid)}':"
695     elsif cl.type == Solv::Transaction::SOLVER_TRANSACTION_ARCHCHANGE
696       puts "#{cl.count} arch changes from '#{pool.id2str(cl.fromid)}' to '#{pool.id2str(cl.toid)}':"
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/#{Solv::xfileno(f).to_s}") || abort("rpm failed: #{$? >> 8}")
755       solv::xfclose(f)
756     end
757   end
758 end