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