- added TransactionClass, more rbsolv porting
[platform/upstream/libsolv.git] / examples / rbsolv
1 #!/usr/bin/ruby
2
3 #  bool: method?
4 #  inplace mod: method!
5 #  set method: method=
6
7 # map  => collect
8 # grep => find_all
9
10 require 'solv'
11 require 'rubygems'
12 require 'inifile'
13 require 'tempfile'
14
15 class Repo_generic
16   def initialize(name, type, attribs = {})
17     @name = name
18     @type = type
19     @attribs = attribs.dup
20     @incomplete = false
21   end
22
23   def enabled?
24     return @attribs['enabled'].to_i != 0
25   end
26
27   def autorefresh?
28     return @attribs['autorefresh'].to_i != 0
29   end
30
31   def calc_cookie_fp(f)
32     chksum = Solv::Chksum.new(Solv::REPOKEY_TYPE_SHA256)
33     chksum.add_fp(f)
34     return chksum.raw
35   end
36
37   def calc_cookie_file(filename)
38     chksum = Solv::Chksum.new(Solv::REPOKEY_TYPE_SHA256)
39     chksum.add("1.1")
40     chksum.add_stat(filename)
41     return chksum.raw
42   end
43
44   def cachepath(ext = nil)
45     path = @name.sub(/^\./, '_')
46     path += ext ? "_#{ext}.solvx" : '.solv'
47     return '/var/cache/solv/' + path.gsub(/\//, '_')
48   end
49
50   def load(pool)
51     @handle = pool.add_repo(@name)
52     @handle.appdata = self
53     @handle.priority = 99 - @attribs['priority'].to_i if @attribs['priority']
54     dorefresh = autorefresh?
55     if dorefresh
56       begin
57         s = File.stat(cachepath)
58         dorefresh = false if s && Time.now - s.mtime < @attribs['metadata_expire'].to_i
59       rescue SystemCallError
60       end
61     end
62     @cookie = nil
63     if !dorefresh && usecachedrepo(nil)
64       puts "repo: '#{@name}' cached"
65       return true
66     end
67     return load_if_changed()
68   end
69
70   def load_ext(repodata)
71     return false
72   end
73
74   def load_if_changed
75     return false
76   end
77
78   def download(file, uncompress, chksum, markincomplete = false)
79     url = @attribs['baseurl']
80     if !url
81       puts "%{@name}: no baseurl"
82       return nil
83     end
84     url = url.sub(/\/$/, '') + "/#{file}"
85     f =  Tempfile.new('rbsolv')
86     f.unlink
87     st = system('curl', '-f', '-s', '-L', '-o', "/dev/fd/" + f.fileno.to_s, '--', url)
88     return nil if f.stat.size == 0 && (st || !chksum)
89     if !st
90         puts "#{file}: download error #{$? >> 8}"
91         @incomplete = true if markincomplete
92         return nil
93     end
94     if chksum
95       fchksum = Solv::Chksum.new(chksum.type)
96       fchksum.add_fd(f.fileno)
97       if !fchksum.matches(chksum)
98         puts "#{file}: checksum error"
99         @incomplete = true if markincomplete
100         return nil
101       end
102     end
103     if uncompress
104       return Solv::xfopen_dup(file, f.fileno)
105     else
106       return Solv::xfopen_dup('', f.fileno)
107     end
108   end
109
110   def usecachedrepo(ext, mark = false)
111     cookie = ext ? @extcookie : @cookie
112     begin
113       repopath = cachepath(ext)
114       f = File.new(repopath, "r")
115       f.sysseek(-32, IO::SEEK_END)
116       fcookie = f.sysread(32)
117       return false if fcookie.length != 32
118       return false if cookie && fcookie != cookie
119       if !ext && @type != 'system'
120         f.sysseek(-32 * 2, IO::SEEK_END)
121         fextcookie = f.sysread(32)
122         return false if fextcookie.length != 32
123       end
124       f.sysseek(0, IO::SEEK_SET)
125       f = Solv::xfopen_dup('', f.fileno)
126       flags = ext ? Solv::Repo::REPO_USE_LOADING|Solv::Repo::REPO_EXTEND_SOLVABLES : 0
127       flags |= Solv::Repo::REPO_LOCALPOOL if ext && ext != 'DL'
128       if ! @handle.add_solv(f, flags)
129         Solv::xfclose(f)
130         return false
131       end
132       Solv::xfclose(f)
133       @cookie = fcookie unless ext
134       @extcookie = fextcookie if !ext && @type != 'system'
135       now = Time.now
136       begin
137         File::utime(now, now, repopath) if mark
138       rescue SystemCallError
139       end
140       return true
141     rescue SystemCallError
142       return false
143     end
144     return true
145   end
146
147   def genextcookie(f)
148     chksum = Solv::Chksum.new(Solv::REPOKEY_TYPE_SHA256)
149     chksum.add(@cookie)
150     if f
151       s = f.stat()
152       chksum.add(s.dev.to_s);
153       chksum.add(s.ino.to_s);
154       chksum.add(s.size.to_s);
155       chksum.add(s.mtime.to_s);
156     end
157     @extcookie = chksum.raw()
158     @extcookie[0] = 1 if @extcookie[0] == 0
159   end
160
161   def writecachedrepo(ext, info = nil)
162     begin
163       Dir::mkdir("/var/cache/solv", 0755) unless FileTest.directory?("/var/cache/solv")
164       f =  Tempfile.new('.newsolv-', '/var/cache/solv')
165       f.chmod(0444)
166       sf = Solv::xfopen_dup('', f.fileno)
167       if !info
168         @handle.write(sf)
169       elsif ext
170         info.write(sf)
171       else
172         @handle.write_first_repodata(sf)
173       end
174       Solv::xfclose(sf)
175       f.sysseek(0, IO::SEEK_END)
176       if @type != 'system' && !ext
177         genextcookie(f) unless @extcookie
178         f.syswrite(@extcookie)
179       end
180       f.syswrite(ext ? @extcookie : @cookie)
181       f.close(false)
182       File.rename(f.path, cachepath(ext))
183       f.unlink
184       return true
185     rescue SystemCallError
186       return false
187     end
188   end
189
190 end
191
192 class Repo_rpmmd < Repo_generic
193
194   def find(what)
195     di = @handle.Dataiterator(Solv::SOLVID_META, Solv::REPOSITORY_REPOMD_TYPE, what, Solv::Dataiterator::SEARCH_STRING)
196     di.prepend_keyname(Solv::REPOSITORY_REPOMD)
197     for d in di
198       d.setpos_parent()
199       filename = d.pool.lookup_str(Solv::SOLVID_POS, Solv::REPOSITORY_REPOMD_LOCATION)
200       next unless filename
201       checksum = d.pool.lookup_checksum(Solv::SOLVID_POS, Solv::REPOSITORY_REPOMD_CHECKSUM)
202       if !checksum
203         puts "no #{filename} checksum!"
204         return nil, nil
205       end
206       return filename, checksum
207     end
208     return nil, nil
209   end
210
211   def load_if_changed
212     print "rpmmd repo '#{@name}: "
213     f = download("repodata/repomd.xml", false, nil, nil)
214     if !f
215       puts "no repomd.xml file, skipped"
216       @handle.free(true)
217       @handle = nil
218       return false
219     end
220     @cookie = calc_cookie_fp(f)
221     if usecachedrepo(nil, true)
222       puts "cached"
223       Solv.xfclose(f)
224       return true
225     end
226     @handle.add_repomdxml(f, 0)
227     Solv::xfclose(f)
228     puts "fetching"
229     filename, filechksum = find('primary')
230     if filename
231       f = download(filename, true, filechksum, true)
232       if f
233         @handle.add_rpmmd(f, nil, 0)
234         Solv::xfclose(f)
235       end
236       return false if @incomplete
237     end
238     filename, filechksum = find('updateinfo')
239     if filename
240       f = download(filename, true, filechksum, true)
241       if f
242         @handle.add_updateinfoxml(f, 0)
243         Solv::xfclose(f)
244       end
245     end
246     add_exts()
247     writecachedrepo(nil) unless @incomplete
248     @handle.create_stubs()
249     return true
250   end
251
252   def add_ext(repodata, what, ext)
253     filename, filechksum = find(what)
254     filename, filechksum = find('prestodelta') if !filename && what == 'deltainfo'
255     return unless filename
256     h = repodata.new_handle()
257     repodata.set_poolstr(h, Solv::REPOSITORY_REPOMD_TYPE, what)
258     repodata.set_str(h, Solv::REPOSITORY_REPOMD_LOCATION, filename)
259     repodata.set_checksum(h, Solv::REPOSITORY_REPOMD_CHECKSUM, filechksum)
260     if ext == 'DL'
261       repodata.add_idarray(h, Solv::REPOSITORY_KEYS, Solv::REPOSITORY_DELTAINFO)
262       repodata.add_idarray(h, Solv::REPOSITORY_KEYS, Solv::REPOKEY_TYPE_FLEXARRAY)
263     elsif ext == 'FL'
264       repodata.add_idarray(h, Solv::REPOSITORY_KEYS, Solv::SOLVABLE_FILELIST)
265       repodata.add_idarray(h, Solv::REPOSITORY_KEYS, Solv::REPOKEY_TYPE_DIRSTRARRAY)
266     end
267     repodata.add_flexarray(Solv::SOLVID_META, Solv::REPOSITORY_EXTERNAL, h)
268   end
269
270   def add_exts
271     repodata = @handle.add_repodata(0)
272     add_ext(repodata, 'deltainfo', 'DL')
273     add_ext(repodata, 'filelists', 'FL')
274     repodata.internalize()
275   end
276
277   def load_ext(repodata)
278     repomdtype = repodata.lookup_str(Solv::SOLVID_META, Solv::REPOSITORY_REPOMD_TYPE)
279     if repomdtype == 'filelists'
280       ext = 'FL'
281     elsif repomdtype == 'deltainfo'
282       ext = 'DL'
283     else
284       return false
285     end
286     print "[#{@name}:#{ext}: "
287     STDOUT.flush
288     if usecachedrepo(ext)
289       puts "cached]\n"
290       return true
291     end
292     puts "fetching]\n"
293     filename = repodata.lookup_str(Solv::SOLVID_META, Solv::REPOSITORY_REPOMD_LOCATION)
294     filechksum = repodata.lookup_checksum(Solv::SOLVID_META, Solv::REPOSITORY_REPOMD_CHECKSUM)
295     f = download(filename, true, filechksum)
296     return false unless f
297     if ext == 'FL'
298       @handle.add_rpmmd(f, 'FL', Solv::Repo::REPO_USE_LOADING|Solv::Repo::REPO_EXTEND_SOLVABLES)
299     elsif ext == 'DL'
300       @handle.add_deltainfoxml(f, Solv::Repo::REPO_USE_LOADING)
301     end
302     Solv::xfclose(f)
303     writecachedrepo(ext, repodata)
304     return true
305   end
306
307 end
308
309 class Repo_susetags < Repo_generic
310
311   def find(what)
312     di = @handle.Dataiterator(Solv::SOLVID_META, Solv::SUSETAGS_FILE_NAME, what, Solv::Dataiterator::SEARCH_STRING)
313     di.prepend_keyname(Solv::SUSETAGS_FILE)
314     for d in di
315       d.setpos_parent()
316       checksum = d.pool.lookup_checksum(Solv::SOLVID_POS, Solv::SUSETAGS_FILE_CHECKSUM)
317       return what, checksum
318     end
319     return nil, nil
320   end
321
322   def load_if_changed
323     print "susetags repo '#{@name}: "
324     f = download("content", false, nil, nil)
325     if !f
326       puts "no content file, skipped"
327       @handle.free(true)
328       @handle = nil
329       return false
330     end
331     @cookie = calc_cookie_fp(f)
332     if usecachedrepo(nil, true)
333       puts "cached"
334       Solv.xfclose(f)
335       return true
336     end
337     @handle.add_content(f, 0)
338     Solv::xfclose(f)
339     puts "fetching"
340     defvendorid = @handle.lookup_id(Solv::SOLVID_META, Solv::SUSETAGS_DEFAULTVENDOR)
341     descrdir = @handle.lookup_str(Solv::SOLVID_META, Solv::SUSETAGS_DESCRDIR)
342     descrdir = "suse/setup/descr" unless descrdir
343     (filename, filechksum) = find('packages.gz')
344     (filename, filechksum) = find('packages') unless filename
345     if filename
346       f = download("#{descrdir}/#{filename}", true, filechksum, true)
347       if f
348         @handle.add_susetags(f, defvendorid, nil, Solv::Repo::REPO_NO_INTERNALIZE|Solv::Repo::SUSETAGS_RECORD_SHARES)
349         Solv::xfclose(f)
350         (filename, filechksum) = find('packages.en.gz')
351         (filename, filechksum) = find('packages.en') unless filename
352         if filename
353           f = download("#{descrdir}/#{filename}", true, filechksum, true)
354           if f
355             @handle.add_susetags(f, defvendorid, nil, Solv::Repo::REPO_NO_INTERNALIZE|Solv::Repo::REPO_REUSE_REPODATA|Solv::Repo::REPO_EXTEND_SOLVABLES)
356             Solv::xfclose(f)
357           end
358         end
359         @handle.internalize()
360       end
361     end
362     add_exts()
363     writecachedrepo(nil) unless @incomplete
364     @handle.create_stubs()
365     return true
366   end
367
368   def add_exts
369     repodata = @handle.add_repodata(0)
370     repodata.internalize()
371   end
372
373 end
374
375 class Repo_unknown < Repo_generic
376   def load(pool)
377     puts "unsupported repo '#{@name}: skipped"
378     return false
379   end
380 end
381
382 class Repo_system < Repo_generic
383   def load(pool)
384     @handle = pool.add_repo(@name)
385     @handle.appdata = self
386     pool.installed = @handle
387     print "rpm database: "
388     @cookie = calc_cookie_file("/var/lib/rpm/Packages")
389     if usecachedrepo(nil)
390       puts "cached"
391       return true
392     end
393     puts "reading"
394     @handle.add_products("/etc/products.d", Solv::Repo::REPO_NO_INTERNALIZE)
395     @handle.add_rpmdb(nil, Solv::Repo::REPO_REUSE_REPODATA)
396     writecachedrepo(nil)
397     return true
398   end
399 end
400
401
402 def validarch?(pool, arch)
403   return false unless arch && arch != ''
404   id = pool.str2id(arch, false)
405   return id != 0 && pool.isknownarch?(id)
406 end
407
408 def depglob(pool, name, globname, globdep)
409   id = pool.str2id(name, false)
410   if id != 0
411     match = false
412     providers = pool.providers(id)
413     if globname && providers.find {|s| s.nameid == id }
414       return [ pool.Job(Solv::Job::SOLVER_SOLVABLE_NAME, id) ]
415     end
416     if !providers.empty?
417       puts "[using capability match for '#{name}']" if globname && globdep
418       return [ pool.Job(Solv::Job::SOLVER_SOLVABLE_PROVIDES, id) ]
419     end
420   end
421   return [] unless name =~ /[\[*?]/;
422   if globname
423     idmatches = {}
424     for d in pool.Dataiterator(0, Solv::SOLVABLE_NAME, name, Solv::Dataiterator::SEARCH_GLOB)
425       s = d.solvable
426       idmatches[s.nameid] = 1 if s.installable?
427     end
428     if !idmatches.empty?
429       return idmatches.keys.sort.collect { |id| pool.Job(Solv::Job::SOLVER_SOLVABLE_NAME, id) }
430     end
431   end
432   if globdep
433     idmatches = pool.matchprovidingids(name, Solv::Dataiterator::SEARCH_GLOB);
434     if !idmatches.empty?
435       puts "[using capability match for '#{name}']"
436       return idmatches.sort.collect { |id| pool.Job(Solv::Job::SOLVER_SOLVABLE_PROVIDES, id) }
437     end
438   end
439   return []
440 end
441
442 def limitjobs(pool, jobs, flags, evrstr)
443   njobs = []
444   evr = pool.str2id(evrstr)
445   for j in jobs
446     how = j.how
447     sel = how & Solv::Job::SOLVER_SELECTMASK
448     what = pool.rel2id(j.what, evr, flags)
449     if flags == Solv::REL_ARCH
450       how |= Solv::Job::SOLVER_SETARCH
451     elsif flags == Solv::REL_EQ && sel == Solv::Job::SOLVER_SOLVABLE_NAME
452       how |= evrstr.include?(?-) ? Solv::Job::SOLVER_SETEVR : Solv::Job::SOLVER_SETEV
453     end
454     njobs << pool.Job(how, what)
455   end
456   return njobs
457 end
458
459 def limitjobs_evrarch(pool, jobs, flags, evrstr)
460   if evrstr =~ /^(.+)\.(.+?)$/ && validarch?(pool, $2)
461     evrstr = $1
462     jobs = limitjobs(pool, jobs, Solv::REL_ARCH, $2)
463   end
464   return limitjobs(pool, jobs, flags, evrstr)
465 end
466
467 def mkjobs_rel(pool, cmd, name, rel, evr)
468   flags = 0
469   flags |= Solv::REL_LT if rel.include?(?<)
470   flags |= Solv::REL_EQ if rel.include?(?=)
471   flags |= Solv::REL_GT if rel.include?(?>)
472   jobs = depglob(pool, name, true, true)
473   return limitjobs(pool, jobs, flags, evr) unless jobs.empty?
474   if (name =~ /^(.+)\.(.+?)$/) && validarch?(pool, $2)
475     arch = $2
476     jobs = depglob(pool, name, true, true)
477     return [] if jobs.empty?
478     jobs = limitjobs(pool, jobs, Solv::REL_ARCH, arch)
479     return limitjobs(pool, jobs, flags, evr)
480   end
481   return []
482 end
483
484 def mkjobs_nevra(pool, cmd, arg)
485   jobs = depglob(pool, arg, true, true)
486   return jobs unless jobs.empty?
487   if ((arg =~ /^(.+)\.(.+?)$/) && validarch?(pool, $2))
488     arch = $2
489     jobs = depglob(pool, $1, true, true)
490     return limitjobs(pool, jobs, Solv::REL_ARCH, arch) unless jobs.empty?
491   end
492   if (arg =~ /^(.+)-(.+?)$/)
493     evr = $2
494     jobs = depglob(pool, $1, true, false)
495     return limitjobs_evrarch(pool, jobs, Solv::REL_EQ, evr) unless jobs.empty?
496   end
497   if (arg =~ /^(.+)-(.+?-.+?)$/)
498     evr = $2
499     jobs = depglob(pool, $1, true, false)
500     return limitjobs_evrarch(pool, jobs, Solv::REL_EQ, evr) unless jobs.empty?
501   end
502   return []
503 end
504
505 def mkjobs_filelist(pool, cmd, arg)
506   type = Solv::Dataiterator::SEARCH_STRING
507   type = Solv::Dataiterator::SEARCH_GLOB if arg =~ /[\[*?]/
508   if cmd == 'erase'
509     di = pool.installed.Dataiterator(0, Solv::SOLVABLE_FILELIST, arg, type | Solv::Dataiterator::SEARCH_FILES|Solv::Dataiterator::SEARCH_COMPLETE_FILELIST)
510   else
511     di = pool.Dataiterator(0, Solv::SOLVABLE_FILELIST, arg, type | Solv::Dataiterator::SEARCH_FILES|Solv::Dataiterator::SEARCH_COMPLETE_FILELIST)
512   end
513   matches = []
514   for d in di
515     s = d.solvable
516     next unless s && s.installable?
517     matches.push(s.id)
518     di.skip_solvable()
519   end
520   return [] if matches.empty?
521   puts "[using file list match for '#{arg}'"
522   if matches.length > 1
523     return [ pool.Job(Solv::Job::SOLVER_SOLVABLE_ONE_OF, pool.towhatprovides(matches)) ]
524   else
525     return [ pool.Job(Solv::Job::SOLVER_SOLVABLE | Solv::Job::SOLVER_NOAUTOSET, matches[0]) ]
526   end
527 end
528
529 def mkjobs(pool, cmd, arg)
530   if arg =~ /^\//
531     jobs = mkjobs_filelist(pool, cmd, arg)
532     return jobs unless jobs.empty?
533   end
534   if (arg =~ /^(.+?)\s*([<=>]+)\s*(.+?)$/)
535     return mkjobs_rel(pool, cmd, $1, $2, $3)
536   else
537     return mkjobs_nevra(pool, cmd, arg)
538   end
539 end
540
541 args = ARGV
542 cmd = args.shift
543 cmd = 'list' if cmd == 'li'
544 cmd = 'install' if cmd == 'in'
545 cmd = 'erase' if cmd == 'rm'
546 cmd = 'verify' if cmd == 've'
547 cmd = 'search' if cmd == 'se'
548
549 repos = []
550 for reposdir in [ '/etc/zypp/repos.d' ] do
551   next unless FileTest.directory?(reposdir)
552   for reponame in Dir["#{reposdir}/*.repo"].sort do
553     cfg = IniFile.new(reponame)
554     cfg.each_section do |ali|
555       repoattr = { 'alias' => ali, 'enabled' => 0, 'priority' => 99, 'autorefresh' => 1, 'type' => 'rpm-md', 'metadata_expire' => 900}
556       repoattr.update(cfg[ali])
557       if repoattr['type'] == 'rpm-md'
558         repo = Repo_rpmmd.new(ali, 'repomd', repoattr)
559       elsif repoattr['type'] == 'yast2'
560         repo = Repo_susetags.new(ali, 'susetags', repoattr)
561       else
562         repo = Repo_unknown.new(ali, 'unknown', repoattr)
563       end
564       repos.push(repo)
565     end
566   end
567 end
568
569 pool = Solv::Pool.new()
570 # require 'sys/uname' ; sysarch = Sys::Uname.machine
571 sysarch = `uname -p`.strip
572 pool.setarch(sysarch)
573
574 pool.set_loadcallback { |repodata|
575   repo = repodata.repo.appdata
576   repo ? repo.load_ext(repodata) : false
577 }
578
579 sysrepo = Repo_system.new('@System', 'system')
580 sysrepo.load(pool)
581 for repo in repos
582   repo.load(pool) if repo.enabled?
583 end
584
585 if cmd == 'search'
586   matches = {}
587   for di in pool.Dataiterator(0, Solv::SOLVABLE_NAME, args[0], Solv::Dataiterator::SEARCH_SUBSTRING | Solv::Dataiterator::SEARCH_NOCASE)
588     matches[di.solvid] = true
589   end
590   for solvid in matches.keys.sort
591     s = pool.solvables[solvid]
592     puts "- #{s.str} [#{s.repo.name}]"
593   end
594   exit
595 end
596
597 pool.addfileprovides
598 pool.createwhatprovides
599
600 jobs = []
601 for arg in args
602   njobs = mkjobs(pool, cmd, ARGV[0])
603   abort("nothing matches '#{arg}'") if njobs.empty?
604   jobs += njobs
605 end
606
607 if cmd == 'list' || cmd == 'info'
608   abort("no package matched.") if jobs.empty?
609     for job in jobs
610       for s in job.solvables()
611         if cmd == 'info'
612           puts "Name:        #{s.str}"
613           puts "Repo:        #{s.repo.name}"
614           puts "Summary:     #{s.lookup_str(Solv::SOLVABLE_SUMMARY)}"
615           str = s.lookup_str(Solv::SOLVABLE_URL)
616           puts "Url:         #{str}" if str
617           str = s.lookup_str(Solv::SOLVABLE_LICENSE)
618           puts "License:     #{str}" if str
619           puts "Description:\n#{s.lookup_str(Solv::SOLVABLE_DESCRIPTION)}"
620           puts
621         else
622           puts "  - #{s.str} [#{s.repo.name}]"
623           puts "    #{s.lookup_str(Solv::SOLVABLE_SUMMARY)}"
624         end
625       end
626     end
627   exit
628 end
629
630 if cmd == 'install' || cmd == 'erase' || cmd == 'up' || cmd == 'dup' || cmd == 'verify'
631   if jobs.empty?
632     if cmd == 'up' || cmd == 'verify'
633       jobs = [ pool.Job(Solv::Job::SOLVER_SOLVABLE_ALL, 0) ]
634     elsif cmd != 'dup'
635       abort("no package matched.")
636     end
637   end
638   for job in jobs
639     if cmd == 'up'
640       if job.how == Solv::Job::SOLVER_SOLVABLE_ALL || job.solvables.any? {|s| s.isinstalled?}
641         job.how |= Solv::Job::SOLVER_UPDATE
642       else
643         job.how |= Solv::Job::SOLVER_INSTALL
644       end
645     elsif cmd == 'install'
646       job.how |= Solv::Job::SOLVER_INSTALL
647     elsif cmd == 'erase'
648       job.how |= Solv::Job::SOLVER_ERASE
649     elsif cmd == 'dup'
650       job.how |= Solv::Job::SOLVER_DISTUPGRADE
651     elsif cmd == 'verify'
652       job.how |= Solv::Job::SOLVER_VERIFY
653     end
654   end
655
656   solver = nil
657   #pool.set_debuglevel(1)
658   while true
659     solver = pool.Solver
660     solver.ignorealreadyrecommended = true
661     solver.allowuninstall = true if cmd == 'erase'
662     if cmd == 'dup' && jobs.empty?
663       solver.distupgrade = true
664       solver.updatesystem = true
665       solver.allowdowngrade = true
666       solver.allowvendorchange = true
667       solver.allowarchchange = true
668       solver.dosplitprovides = true
669     elsif cmd == 'up' && jobs.length == 1 && jobs[0].how == (Solv::Job::SOLVER_UPDATE | Solv::Job::SOLVER_SOLVABLE_ALL)
670       solver.dosplitprovides = true
671     end
672     problems = solver.solve(jobs)
673     break if problems.empty?
674     for problem in problems
675       puts "Problem #{problem.id}:"
676       puts problem.findproblemrule.info.problemstr
677       solutions = problem.solutions
678       for solution in solutions
679         puts "  Solution #{solution.id}:"
680         elements = solution.elements(true)
681         for element in elements
682           puts "  - #{element.str}"
683         end
684       end
685       exit
686     end
687   end
688   trans = solver.transaction
689   solver = nil
690   if trans.isempty?
691     puts "Nothing to do."
692     exit
693   end
694   puts "\nTransaction summary:\n"
695   for cl in trans.classify()
696     if cl.type == Solv::Transaction::SOLVER_TRANSACTION_ERASE
697       puts "#{cl.count} erased packages:"
698     elsif cl.type == Solv::Transaction::SOLVER_TRANSACTION_INSTALL
699       puts "#{cl.count} installed packages:"
700     elsif cl.type == Solv::Transaction::SOLVER_TRANSACTION_REINSTALLED
701       puts "#{cl.count} reinstalled packages:"
702     elsif cl.type == Solv::Transaction::SOLVER_TRANSACTION_DOWNGRADED
703       puts "#{cl.count} downgraded packages:"
704     elsif cl.type == Solv::Transaction::SOLVER_TRANSACTION_CHANGED
705       puts "#{cl.count} changed packages:"
706     elsif cl.type == Solv::Transaction::SOLVER_TRANSACTION_UPGRADED
707       puts "#{cl.count} upgraded packages:"
708     elsif cl.type == Solv::Transaction::SOLVER_TRANSACTION_VENDORCHANGE
709       puts "#{cl.count} vendor changes from '#{pool.id2str(cl.fromid)}' to '#{pool.id2str(cl.toid)}':"
710     elsif cl.type == Solv::Transaction::SOLVER_TRANSACTION_ARCHCHANGE
711       puts "#{cl.count} arch changes from '#{pool.id2str(cl.fromid)}' to '#{pool.id2str(cl.toid)}':"
712     else
713       next
714     end
715     for p in cl.solvables
716       if cl.type == Solv::Transaction::SOLVER_TRANSACTION_UPGRADED || cl.type == Solv::Transaction::SOLVER_TRANSACTION_DOWNGRADED
717         puts "  - #{p.str} -> #{trans.othersolvable(p).str}"
718       else
719         puts "  - #{p.str}"
720       end
721     end
722     puts
723   end
724   puts "install size change: #{trans.calc_installsizechange()} K\n\n"
725 end