Already use LOCALPOOL when converting filelist/language extensions
[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 cmd = 'list' if cmd == 'li'
504 cmd = 'install' if cmd == 'in'
505 cmd = 'erase' if cmd == 'rm'
506 cmd = 'verify' if cmd == 've'
507 cmd = 'search' if cmd == 'se'
508
509 repos = []
510 reposdirs = []
511 if FileTest.directory?('/etc/zypp/repos.d')
512   reposdirs = [ '/etc/zypp/repos.d' ]
513 else
514   reposdirs = [ '/etc/yum/repos.d' ]
515 end
516 for reposdir in reposdirs do
517   next unless FileTest.directory?(reposdir)
518   for reponame in Dir["#{reposdir}/*.repo"].sort do
519     cfg = IniFile.new(reponame)
520     cfg.each_section do |ali|
521       repoattr = { 'alias' => ali, 'enabled' => 0, 'priority' => 99, 'autorefresh' => 1, 'type' => 'rpm-md', 'metadata_expire' => 900}
522       repoattr.update(cfg[ali])
523       if repoattr['type'] == 'rpm-md'
524         repo = Repo_rpmmd.new(ali, 'repomd', repoattr)
525       elsif repoattr['type'] == 'yast2'
526         repo = Repo_susetags.new(ali, 'susetags', repoattr)
527       else
528         repo = Repo_unknown.new(ali, 'unknown', repoattr)
529       end
530       repos.push(repo)
531     end
532   end
533 end
534
535 pool = Solv::Pool.new()
536 pool.setarch()
537
538 pool.set_loadcallback { |repodata|
539   repo = repodata.repo.appdata
540   repo ? repo.load_ext(repodata) : false
541 }
542
543 sysrepo = Repo_system.new('@System', 'system')
544 sysrepo.load(pool)
545 for repo in repos
546   repo.load(pool) if repo.enabled?
547 end
548
549 if cmd == 'search'
550   matches = {}
551   for di in pool.Dataiterator(0, Solv::SOLVABLE_NAME, args[0], Solv::Dataiterator::SEARCH_SUBSTRING | Solv::Dataiterator::SEARCH_NOCASE)
552     matches[di.solvid] = true
553   end
554   for solvid in matches.keys.sort
555     s = pool.solvables[solvid]
556     puts "- #{s.str} [#{s.repo.name}]"
557   end
558   exit
559 end
560
561 addedprovides = pool.addfileprovides_queue()
562 if !addedprovides.empty?
563   sysrepo.updateaddedprovides(addedprovides)
564   for repo in repos
565     repo.updateaddedprovides(addedprovides)
566   end
567 end
568 pool.createwhatprovides()
569
570 jobs = []
571 for arg in args
572   flags = Solv::Selection::SELECTION_NAME | Solv::Selection::SELECTION_PROVIDES|Solv::Selection::SELECTION_GLOB
573   if arg =~ /^\//
574     flags |= Solv::Selection::SELECTION_FILELIST
575     flags |= Solv::Selection::SELECTION_INSTALLED_ONLY if cmd == 'erase'
576   end
577   sel = pool.select(arg, flags)
578   if sel.isempty?
579     sel = pool.select(arg, flags |  Solv::Selection::SELECTION_NOCASE)
580     puts "[ignoring case for '#{arg}']" unless sel.isempty?
581   end
582   puts "[using file list match for '#{arg}']" if sel.flags & Solv::Selection::SELECTION_FILELIST != 0
583   puts "[using capability match for '#{arg}']" if sel.flags & Solv::Selection::SELECTION_PROVIDES != 0
584   jobs += sel.jobs(0)
585 end
586
587 if jobs.empty? && (cmd == 'up' || cmd == 'dup' || cmd == 'verify')
588   sel = pool.Selection()
589   sel.addsimple(Solv::Job::SOLVER_SOLVABLE_ALL, 0)
590   jobs += sel.jobs(0)
591 end
592
593 if cmd == 'list' || cmd == 'info'
594   abort("no package matched.") if jobs.empty?
595     for job in jobs
596       for s in job.solvables()
597         if cmd == 'info'
598           puts "Name:        #{s.str}"
599           puts "Repo:        #{s.repo.name}"
600           puts "Summary:     #{s.lookup_str(Solv::SOLVABLE_SUMMARY)}"
601           str = s.lookup_str(Solv::SOLVABLE_URL)
602           puts "Url:         #{str}" if str
603           str = s.lookup_str(Solv::SOLVABLE_LICENSE)
604           puts "License:     #{str}" if str
605           puts "Description:\n#{s.lookup_str(Solv::SOLVABLE_DESCRIPTION)}"
606           puts
607         else
608           puts "  - #{s.str} [#{s.repo.name}]"
609           puts "    #{s.lookup_str(Solv::SOLVABLE_SUMMARY)}"
610         end
611       end
612     end
613   exit
614 end
615
616 if cmd == 'install' || cmd == 'erase' || cmd == 'up' || cmd == 'dup' || cmd == 'verify'
617   abort("no package matched.") if jobs.empty?
618   for job in jobs
619     if cmd == 'up'
620       job.how |= Solv::Job::SOLVER_UPDATE
621       job.how ^= Solv::Job::SOLVER_UPDATE ^ Solv::Job::SOLVER_INSTALL if job.isemptyupdate?
622     elsif cmd == 'install'
623       job.how |= Solv::Job::SOLVER_INSTALL
624     elsif cmd == 'erase'
625       job.how |= Solv::Job::SOLVER_ERASE
626     elsif cmd == 'dup'
627       job.how |= Solv::Job::SOLVER_DISTUPGRADE
628     elsif cmd == 'verify'
629       job.how |= Solv::Job::SOLVER_VERIFY
630     end
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   trans = solver.transaction
675   solver = nil
676   if trans.isempty?
677     puts "Nothing to do."
678     exit
679   end
680   puts "\nTransaction summary:\n"
681   for cl in trans.classify()
682     if cl.type == Solv::Transaction::SOLVER_TRANSACTION_ERASE
683       puts "#{cl.count} erased packages:"
684     elsif cl.type == Solv::Transaction::SOLVER_TRANSACTION_INSTALL
685       puts "#{cl.count} installed packages:"
686     elsif cl.type == Solv::Transaction::SOLVER_TRANSACTION_REINSTALLED
687       puts "#{cl.count} reinstalled packages:"
688     elsif cl.type == Solv::Transaction::SOLVER_TRANSACTION_DOWNGRADED
689       puts "#{cl.count} downgraded packages:"
690     elsif cl.type == Solv::Transaction::SOLVER_TRANSACTION_CHANGED
691       puts "#{cl.count} changed packages:"
692     elsif cl.type == Solv::Transaction::SOLVER_TRANSACTION_UPGRADED
693       puts "#{cl.count} upgraded packages:"
694     elsif cl.type == Solv::Transaction::SOLVER_TRANSACTION_VENDORCHANGE
695       puts "#{cl.count} vendor changes from '#{cl.fromdep}' to '#{cl.todep}':"
696     elsif cl.type == Solv::Transaction::SOLVER_TRANSACTION_ARCHCHANGE
697       puts "#{cl.count} arch changes from '#{cl.fromdep}' to '#{cl.todep}':"
698     else
699       next
700     end
701     for p in cl.solvables
702       if cl.type == Solv::Transaction::SOLVER_TRANSACTION_UPGRADED || cl.type == Solv::Transaction::SOLVER_TRANSACTION_DOWNGRADED
703         puts "  - #{p.str} -> #{trans.othersolvable(p).str}"
704       else
705         puts "  - #{p.str}"
706       end
707     end
708     puts
709   end
710   puts "install size change: #{trans.calc_installsizechange()} K\n\n"
711   while true:
712     print("OK to continue (y/n)? ")
713     STDOUT.flush
714     yn = STDIN.gets.strip
715     break if yn == 'y'
716     abort if yn == 'n'
717   end
718   newpkgs = trans.newpackages()
719   newpkgsfp = {}
720   if !newpkgs.empty?
721     downloadsize = 0
722     for p in newpkgs
723       downloadsize += p.lookup_num(Solv::SOLVABLE_DOWNLOADSIZE)
724     end
725     puts "Downloading #{newpkgs.length} packages, #{downloadsize} K"
726     for p in newpkgs
727       repo = p.repo.appdata
728       location, medianr = p.lookup_location()
729       next unless location
730       location = repo.packagespath + location
731       chksum = p.lookup_checksum(Solv::SOLVABLE_CHECKSUM)
732       f = repo.download(location, false, chksum)
733       abort("\n#{@name}: #{location} not found in repository\n") unless f
734       newpkgsfp[p.id] = f
735       print "."
736       STDOUT.flush()
737     end
738     puts
739   end
740   puts "Committing transaction:"
741   puts
742   trans.order(0)
743   for p in trans.steps
744     steptype = trans.steptype(p, Solv::Transaction::SOLVER_TRANSACTION_RPM_ONLY)
745     if steptype == Solv::Transaction::SOLVER_TRANSACTION_ERASE
746       puts "erase #{p.str}"
747       next unless p.lookup_num(Solv::RPM_RPMDBID)
748       evr = p.evr.sub(/^[0-9]+:/, '')
749       system('rpm', '-e', '--nodeps', '--nodigest', '--nosignature', "#{p.name}-#{evr}.#{p.arch}") || abort("rpm failed: #{$? >> 8}") 
750     elsif (steptype == Solv::Transaction::SOLVER_TRANSACTION_INSTALL || steptype == Solv::Transaction::SOLVER_TRANSACTION_MULTIINSTALL)
751       puts "install #{p.str}"
752       f = newpkgsfp.delete(p.id)
753       next unless f
754       mode = steptype == Solv::Transaction::SOLVER_TRANSACTION_INSTALL ? '-U' : '-i'
755       system('rpm', mode, '--force', '--nodeps', '--nodigest', '--nosignature', "/dev/fd/#{f.fileno().to_s}") || abort("rpm failed: #{$? >> 8}")
756       f.close
757     end
758   end
759 end