rename selection_limit to selection_filter, add flags to make selection_make more...
[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             flags = Solv::Repo::REPO_EXTEND_SOLVABLES
186             flags |= Solv::Repo::REPO_LOCALPOOL if ext != 'DL'
187             info.add_solv(sf, flags)
188           end
189           sf.close
190         end
191       end
192       File.rename(f.path, cachepath(ext))
193       f.unlink
194       return true
195     rescue SystemCallError
196       return false
197     end
198   end
199
200   def updateaddedprovides(addedprovides)
201     return if @incomplete
202     return unless @handle && !@handle.isempty?
203     repodata = @handle.first_repodata()
204     return unless repodata
205     oldaddedprovides = repodata.lookup_idarray(Solv::SOLVID_META, Solv::REPOSITORY_ADDEDFILEPROVIDES)
206     return if (oldaddedprovides | addedprovides) == oldaddedprovides
207     for id in addedprovides
208       repodata.add_idarray(Solv::SOLVID_META, Solv::REPOSITORY_ADDEDFILEPROVIDES, id)
209     end
210     repodata.internalize()
211     writecachedrepo(nil, repodata)
212   end
213
214   def packagespath()
215     return ''
216   end
217 end
218
219 class Repo_rpmmd < Repo_generic
220
221   def find(what)
222     di = @handle.Dataiterator(Solv::SOLVID_META, Solv::REPOSITORY_REPOMD_TYPE, what, Solv::Dataiterator::SEARCH_STRING)
223     di.prepend_keyname(Solv::REPOSITORY_REPOMD)
224     for d in di
225       dp = d.parentpos()
226       filename = dp.lookup_str(Solv::REPOSITORY_REPOMD_LOCATION)
227       next unless filename
228       checksum = dp.lookup_checksum(Solv::REPOSITORY_REPOMD_CHECKSUM)
229       if !checksum
230         puts "no #{filename} checksum!"
231         return nil, nil
232       end
233       return filename, checksum
234     end
235     return nil, nil
236   end
237
238   def load(pool)
239     return true if super(pool)
240     print "rpmmd repo '#{@name}: "
241     f = download("repodata/repomd.xml", false, nil, nil)
242     if !f
243       puts "no repomd.xml file, skipped"
244       @handle.free(true)
245       @handle = nil
246       return false
247     end
248     @cookie = calc_cookie_fp(f)
249     if usecachedrepo(nil, true)
250       puts "cached"
251       f.close
252       return true
253     end
254     @handle.add_repomdxml(f, 0)
255     f.close
256     puts "fetching"
257     filename, filechksum = find('primary')
258     if filename
259       f = download(filename, true, filechksum, true)
260       if f
261         @handle.add_rpmmd(f, nil, 0)
262         f.close
263       end
264       return false if @incomplete
265     end
266     filename, filechksum = find('updateinfo')
267     if filename
268       f = download(filename, true, filechksum, true)
269       if f
270         @handle.add_updateinfoxml(f, 0)
271         f.close
272       end
273     end
274     add_exts()
275     writecachedrepo(nil)
276     @handle.create_stubs()
277     return true
278   end
279
280   def add_ext(repodata, what, ext)
281     filename, filechksum = find(what)
282     filename, filechksum = find('prestodelta') if !filename && what == 'deltainfo'
283     return unless filename
284     h = repodata.new_handle()
285     repodata.set_poolstr(h, Solv::REPOSITORY_REPOMD_TYPE, what)
286     repodata.set_str(h, Solv::REPOSITORY_REPOMD_LOCATION, filename)
287     repodata.set_checksum(h, Solv::REPOSITORY_REPOMD_CHECKSUM, filechksum)
288     if ext == 'DL'
289       repodata.add_idarray(h, Solv::REPOSITORY_KEYS, Solv::REPOSITORY_DELTAINFO)
290       repodata.add_idarray(h, Solv::REPOSITORY_KEYS, Solv::REPOKEY_TYPE_FLEXARRAY)
291     elsif ext == 'FL'
292       repodata.add_idarray(h, Solv::REPOSITORY_KEYS, Solv::SOLVABLE_FILELIST)
293       repodata.add_idarray(h, Solv::REPOSITORY_KEYS, Solv::REPOKEY_TYPE_DIRSTRARRAY)
294     end
295     repodata.add_flexarray(Solv::SOLVID_META, Solv::REPOSITORY_EXTERNAL, h)
296   end
297
298   def add_exts
299     repodata = @handle.add_repodata(0)
300     add_ext(repodata, 'deltainfo', 'DL')
301     add_ext(repodata, 'filelists', 'FL')
302     repodata.internalize()
303   end
304
305   def load_ext(repodata)
306     repomdtype = repodata.lookup_str(Solv::SOLVID_META, Solv::REPOSITORY_REPOMD_TYPE)
307     if repomdtype == 'filelists'
308       ext = 'FL'
309     elsif repomdtype == 'deltainfo'
310       ext = 'DL'
311     else
312       return false
313     end
314     print "[#{@name}:#{ext}: "
315     STDOUT.flush
316     if usecachedrepo(ext)
317       puts "cached]\n"
318       return true
319     end
320     puts "fetching]\n"
321     filename = repodata.lookup_str(Solv::SOLVID_META, Solv::REPOSITORY_REPOMD_LOCATION)
322     filechksum = repodata.lookup_checksum(Solv::SOLVID_META, Solv::REPOSITORY_REPOMD_CHECKSUM)
323     f = download(filename, true, filechksum)
324     return false unless f
325     if ext == 'FL'
326       @handle.add_rpmmd(f, 'FL', Solv::Repo::REPO_USE_LOADING|Solv::Repo::REPO_EXTEND_SOLVABLES|Solv::Repo::REPO_LOCALPOOL)
327     elsif ext == 'DL'
328       @handle.add_deltainfoxml(f, Solv::Repo::REPO_USE_LOADING)
329     end
330     f.close
331     writecachedrepo(ext, repodata)
332     return true
333   end
334
335 end
336
337 class Repo_susetags < Repo_generic
338
339   def find(what)
340     di = @handle.Dataiterator(Solv::SOLVID_META, Solv::SUSETAGS_FILE_NAME, what, Solv::Dataiterator::SEARCH_STRING)
341     di.prepend_keyname(Solv::SUSETAGS_FILE)
342     for d in di
343       dp = d.parentpos()
344       checksum = dp.lookup_checksum(Solv::SUSETAGS_FILE_CHECKSUM)
345       return what, checksum
346     end
347     return nil, nil
348   end
349
350   def load(pool)
351     return true if super(pool)
352     print "susetags repo '#{@name}: "
353     f = download("content", false, nil, nil)
354     if !f
355       puts "no content file, skipped"
356       @handle.free(true)
357       @handle = nil
358       return false
359     end
360     @cookie = calc_cookie_fp(f)
361     if usecachedrepo(nil, true)
362       puts "cached"
363       f.close
364       return true
365     end
366     @handle.add_content(f, 0)
367     f.close
368     puts "fetching"
369     defvendorid = @handle.lookup_id(Solv::SOLVID_META, Solv::SUSETAGS_DEFAULTVENDOR)
370     descrdir = @handle.lookup_str(Solv::SOLVID_META, Solv::SUSETAGS_DESCRDIR)
371     descrdir = "suse/setup/descr" unless descrdir
372     (filename, filechksum) = find('packages.gz')
373     (filename, filechksum) = find('packages') unless filename
374     if filename
375       f = download("#{descrdir}/#{filename}", true, filechksum, true)
376       if f
377         @handle.add_susetags(f, defvendorid, nil, Solv::Repo::REPO_NO_INTERNALIZE|Solv::Repo::SUSETAGS_RECORD_SHARES)
378         f.close
379         (filename, filechksum) = find('packages.en.gz')
380         (filename, filechksum) = find('packages.en') unless filename
381         if filename
382           f = download("#{descrdir}/#{filename}", true, filechksum, true)
383           if f
384             @handle.add_susetags(f, defvendorid, nil, Solv::Repo::REPO_NO_INTERNALIZE|Solv::Repo::REPO_REUSE_REPODATA|Solv::Repo::REPO_EXTEND_SOLVABLES)
385             f.close
386           end
387         end
388         @handle.internalize()
389       end
390     end
391     add_exts()
392     writecachedrepo(nil)
393     @handle.create_stubs()
394     return true
395   end
396
397   @@langtags = {
398     Solv::SOLVABLE_SUMMARY     => Solv::REPOKEY_TYPE_STR,
399     Solv::SOLVABLE_DESCRIPTION => Solv::REPOKEY_TYPE_STR,
400     Solv::SOLVABLE_EULA        => Solv::REPOKEY_TYPE_STR,
401     Solv::SOLVABLE_MESSAGEINS  => Solv::REPOKEY_TYPE_STR,
402     Solv::SOLVABLE_MESSAGEDEL  => Solv::REPOKEY_TYPE_STR,
403     Solv::SOLVABLE_CATEGORY    => Solv::REPOKEY_TYPE_ID,
404   }
405
406   def add_ext(repodata, what, ext)
407     (filename, filechksum) = find(what)
408     h = repodata.new_handle()
409     repodata.set_str(h, Solv::SUSETAGS_FILE_NAME, filename)
410     repodata.set_checksum(h, Solv::SUSETAGS_FILE_CHECKSUM, filechksum)
411     if ext == 'DL'
412       repodata.add_idarray(h, Solv::REPOSITORY_KEYS, Solv::REPOSITORY_DELTAINFO)
413       repodata.add_idarray(h, Solv::REPOSITORY_KEYS, Solv::REPOKEY_TYPE_FLEXARRAY)
414     elsif ext == 'DU'
415       repodata.add_idarray(h, Solv::REPOSITORY_KEYS, Solv::SOLVABLE_DISKUSAGE)
416       repodata.add_idarray(h, Solv::REPOSITORY_KEYS, Solv::REPOKEY_TYPE_DIRNUMNUMARRAY)
417     elsif ext == 'FL'
418       repodata.add_idarray(h, Solv::REPOSITORY_KEYS, Solv::SOLVABLE_FILELIST)
419       repodata.add_idarray(h, Solv::REPOSITORY_KEYS, Solv::REPOKEY_TYPE_DIRSTRARRAY)
420     else
421       @@langtags.sort.each do |langid, langtype|
422         repodata.add_idarray(h, Solv::REPOSITORY_KEYS, @handle.pool.id2langid(langid, ext, true))
423         repodata.add_idarray(h, Solv::REPOSITORY_KEYS, langtype)
424       end
425     end
426     repodata.add_flexarray(Solv::SOLVID_META, Solv::REPOSITORY_EXTERNAL, h)
427   end
428
429   def add_exts
430     repodata = @handle.add_repodata(0)
431     di = @handle.Dataiterator(Solv::SOLVID_META, Solv::SUSETAGS_FILE_NAME, nil, 0)
432     di.prepend_keyname(Solv::SUSETAGS_FILE)
433     for d in di
434       filename = d.str
435       next unless filename && filename =~ /^packages\.(..)(?:\..*)$/
436       next if $1 == 'en' || $1 == 'gz'
437       add_ext(repodata, filename, $1)
438     end
439     repodata.internalize()
440   end
441
442   def load_ext(repodata)
443     filename = repodata.lookup_str(Solv::SOLVID_META, Solv::SUSETAGS_FILE_NAME)
444     ext = filename[9,2]
445     print "[#{@name}:#{ext}: "
446     STDOUT.flush
447     if usecachedrepo(ext)
448       puts "cached]\n"
449       return true
450     end
451     puts "fetching]\n"
452     defvendorid = @handle.lookup_id(Solv::SOLVID_META, Solv::SUSETAGS_DEFAULTVENDOR)
453     descrdir = @handle.lookup_str(Solv::SOLVID_META, Solv::SUSETAGS_DESCRDIR)
454     descrdir = "suse/setup/descr" unless descrdir
455     filechksum = repodata.lookup_checksum(Solv::SOLVID_META, Solv::SUSETAGS_FILE_CHECKSUM)
456     f = download("#{descrdir}/#{filename}", true, filechksum)
457     return false unless f
458     flags = Solv::Repo::REPO_USE_LOADING|Solv::Repo::REPO_EXTEND_SOLVABLES
459     flags |= Solv::Repo::REPO_LOCALPOOL if ext != 'DL'
460     @handle.add_susetags(f, defvendorid, ext, flags)
461     f.close
462     writecachedrepo(ext, repodata)
463     return true
464   end
465
466   def packagespath()
467     datadir = @handle.lookup_str(Solv::SOLVID_META, Solv::SUSETAGS_DATADIR)
468     datadir = "suse" unless datadir
469     return datadir + '/'
470   end
471 end
472
473 class Repo_unknown < Repo_generic
474   def load(pool)
475     puts "unsupported repo '#{@name}: skipped"
476     return false
477   end
478 end
479
480 class Repo_system < Repo_generic
481   def load(pool)
482     @handle = pool.add_repo(@name)
483     @handle.appdata = self
484     pool.installed = @handle
485     print "rpm database: "
486     @cookie = calc_cookie_file("/var/lib/rpm/Packages")
487     if usecachedrepo(nil)
488       puts "cached"
489       return true
490     end
491     puts "reading"
492     if @handle.respond_to? :add_products
493       @handle.add_products("/etc/products.d", Solv::Repo::REPO_NO_INTERNALIZE)
494     end
495     @handle.add_rpmdb(nil, Solv::Repo::REPO_REUSE_REPODATA)
496     writecachedrepo(nil)
497     return true
498   end
499 end
500
501 args = ARGV
502 cmd = args.shift
503
504 cmdabbrev = { 'li' => '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.new(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(0, 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()
601   sel.add_raw(Solv::Job::SOLVER_SOLVABLE_ALL, 0)
602   jobs += sel.jobs(cmdactionmap[cmd])
603 end
604
605 abort("no package matched.") if jobs.empty?
606
607 if cmd == 'list' || cmd == 'info'
608   for job in jobs
609     for s in job.solvables()
610       if cmd == 'info'
611         puts "Name:        #{s.str}"
612         puts "Repo:        #{s.repo.name}"
613         puts "Summary:     #{s.lookup_str(Solv::SOLVABLE_SUMMARY)}"
614         str = s.lookup_str(Solv::SOLVABLE_URL)
615         puts "Url:         #{str}" if str
616         str = s.lookup_str(Solv::SOLVABLE_LICENSE)
617         puts "License:     #{str}" if str
618         puts "Description:\n#{s.lookup_str(Solv::SOLVABLE_DESCRIPTION)}"
619         puts
620       else
621         puts "  - #{s.str} [#{s.repo.name}]"
622         puts "    #{s.lookup_str(Solv::SOLVABLE_SUMMARY)}"
623       end
624     end
625   end
626   exit
627 end
628
629 for job in jobs
630   job.how ^= Solv::Job::SOLVER_UPDATE ^ Solv::Job::SOLVER_INSTALL if cmd == 'up' and job.isemptyupdate?
631 end
632
633 solver = nil
634 #pool.set_debuglevel(1)
635 while true
636   solver = pool.Solver
637   solver.set_flag(Solv::Solver::SOLVER_FLAG_SPLITPROVIDES, 1)
638   solver.set_flag(Solv::Solver::SOLVER_FLAG_ALLOW_UNINSTALL, 1) if cmd == 'erase'
639   problems = solver.solve(jobs)
640   break if problems.empty?
641   for problem in problems
642     puts "Problem #{problem.id}:"
643     puts problem.findproblemrule.info.problemstr
644     solutions = problem.solutions
645     for solution in solutions
646       puts "  Solution #{solution.id}:"
647       elements = solution.elements(true)
648       for element in elements
649         puts "  - #{element.str}"
650       end
651       puts
652     end
653     sol = nil
654     while true
655       print "Please choose a solution: "
656       STDOUT.flush
657       sol = STDIN.gets.strip
658       break if sol == 's' || sol == 'q'
659       break if sol =~ /^\d+$/ && sol.to_i >= 1 && sol.to_i <= solutions.length
660     end
661     next if sol == 's'
662     abort if sol == 'q'
663     solution = solutions[sol.to_i - 1]
664     for element in solution.elements
665       newjob = element.Job()
666       if element.type == Solv::Solver::SOLVER_SOLUTION_JOB
667         jobs[element.jobidx] = newjob
668       else
669         jobs.push(newjob) if newjob && !jobs.include?(newjob)
670       end
671     end
672   end
673 end
674
675 trans = solver.transaction
676 solver = nil
677 if trans.isempty?
678   puts "Nothing to do."
679   exit
680 end
681
682 puts "\nTransaction summary:\n"
683 for cl in trans.classify()
684   if cl.type == Solv::Transaction::SOLVER_TRANSACTION_ERASE
685     puts "#{cl.count} erased packages:"
686   elsif cl.type == Solv::Transaction::SOLVER_TRANSACTION_INSTALL
687     puts "#{cl.count} installed packages:"
688   elsif cl.type == Solv::Transaction::SOLVER_TRANSACTION_REINSTALLED
689     puts "#{cl.count} reinstalled packages:"
690   elsif cl.type == Solv::Transaction::SOLVER_TRANSACTION_DOWNGRADED
691     puts "#{cl.count} downgraded packages:"
692   elsif cl.type == Solv::Transaction::SOLVER_TRANSACTION_CHANGED
693     puts "#{cl.count} changed packages:"
694   elsif cl.type == Solv::Transaction::SOLVER_TRANSACTION_UPGRADED
695     puts "#{cl.count} upgraded packages:"
696   elsif cl.type == Solv::Transaction::SOLVER_TRANSACTION_VENDORCHANGE
697     puts "#{cl.count} vendor changes from '#{cl.fromdep}' to '#{cl.todep}':"
698   elsif cl.type == Solv::Transaction::SOLVER_TRANSACTION_ARCHCHANGE
699     puts "#{cl.count} arch changes from '#{cl.fromdep}' to '#{cl.todep}':"
700   else
701     next
702   end
703   for p in cl.solvables
704     if cl.type == Solv::Transaction::SOLVER_TRANSACTION_UPGRADED || cl.type == Solv::Transaction::SOLVER_TRANSACTION_DOWNGRADED
705       puts "  - #{p.str} -> #{trans.othersolvable(p).str}"
706     else
707       puts "  - #{p.str}"
708     end
709   end
710   puts
711 end
712 puts "install size change: #{trans.calc_installsizechange()} K\n\n"
713
714 while true:
715   print("OK to continue (y/n)? ")
716   STDOUT.flush
717   yn = STDIN.gets.strip
718   break if yn == 'y'
719   abort if yn == 'n'
720 end
721
722 newpkgs = trans.newpackages()
723 newpkgsfp = {}
724 if !newpkgs.empty?
725   downloadsize = 0
726   for p in newpkgs
727     downloadsize += p.lookup_num(Solv::SOLVABLE_DOWNLOADSIZE)
728   end
729   puts "Downloading #{newpkgs.length} packages, #{downloadsize} K"
730   for p in newpkgs
731     repo = p.repo.appdata
732     location, medianr = p.lookup_location()
733     next unless location
734     location = repo.packagespath + location
735     chksum = p.lookup_checksum(Solv::SOLVABLE_CHECKSUM)
736     f = repo.download(location, false, chksum)
737     abort("\n#{@name}: #{location} not found in repository\n") unless f
738     newpkgsfp[p.id] = f
739     print "."
740     STDOUT.flush()
741   end
742   puts
743 end
744
745 puts "Committing transaction:"
746 puts
747 trans.order(0)
748 for p in trans.steps
749   steptype = trans.steptype(p, Solv::Transaction::SOLVER_TRANSACTION_RPM_ONLY)
750   if steptype == Solv::Transaction::SOLVER_TRANSACTION_ERASE
751     puts "erase #{p.str}"
752     next unless p.lookup_num(Solv::RPM_RPMDBID)
753     evr = p.evr.sub(/^[0-9]+:/, '')
754     system('rpm', '-e', '--nodeps', '--nodigest', '--nosignature', "#{p.name}-#{evr}.#{p.arch}") || abort("rpm failed: #{$? >> 8}") 
755   elsif (steptype == Solv::Transaction::SOLVER_TRANSACTION_INSTALL || steptype == Solv::Transaction::SOLVER_TRANSACTION_MULTIINSTALL)
756     puts "install #{p.str}"
757     f = newpkgsfp.delete(p.id)
758     next unless f
759     mode = steptype == Solv::Transaction::SOLVER_TRANSACTION_INSTALL ? '-U' : '-i'
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