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