[hotfix] Relax solvable_identical check for producs [bnc#881493]
[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_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_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.meta.lookup_id(Solv::SUSETAGS_DEFAULTVENDOR)
370     descrdir = @handle.meta.lookup_str(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_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.meta.lookup_id(Solv::SUSETAGS_DEFAULTVENDOR)
453     descrdir = @handle.meta.lookup_str(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.meta.lookup_str(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     f = Solv::xfopen(cachepath())
496     @handle.add_rpmdb_reffp(f, Solv::Repo::REPO_REUSE_REPODATA)
497     f.close
498     writecachedrepo(nil)
499     return true
500   end
501 end
502
503 args = ARGV
504 cmd = args.shift
505
506 cmdabbrev = { 'ls' => 'list', 'in' => 'install', 'rm' => 'erase',
507               've' => 'verify', 'se' => 'search' }
508 cmd = cmdabbrev[cmd] if cmdabbrev.has_key?(cmd)
509
510 cmdactionmap = { 
511   'install' => Solv::Job::SOLVER_INSTALL,
512   'erase'   => Solv::Job::SOLVER_ERASE,
513   'up'      => Solv::Job::SOLVER_UPDATE,
514   'dup'     => Solv::Job::SOLVER_DISTUPGRADE,
515   'verify'  => Solv::Job::SOLVER_VERIFY,
516   'list'    => 0,  
517   'info'    => 0,
518 }
519
520 repos = []
521 reposdirs = []
522 if FileTest.directory?('/etc/zypp/repos.d')
523   reposdirs = [ '/etc/zypp/repos.d' ]
524 else
525   reposdirs = [ '/etc/yum/repos.d' ]
526 end
527 for reposdir in reposdirs do
528   next unless FileTest.directory?(reposdir)
529   for reponame in Dir["#{reposdir}/*.repo"].sort do
530     cfg = IniFile.load(reponame)
531     cfg.each_section do |ali|
532       repoattr = { 'alias' => ali, 'enabled' => 0, 'priority' => 99, 'autorefresh' => 1, 'type' => 'rpm-md', 'metadata_expire' => 900}
533       repoattr.update(cfg[ali])
534       if repoattr['type'] == 'rpm-md'
535         repo = Repo_rpmmd.new(ali, 'repomd', repoattr)
536       elsif repoattr['type'] == 'yast2'
537         repo = Repo_susetags.new(ali, 'susetags', repoattr)
538       else
539         repo = Repo_unknown.new(ali, 'unknown', repoattr)
540       end
541       repos.push(repo)
542     end
543   end
544 end
545
546 pool = Solv::Pool.new()
547 pool.setarch()
548
549 pool.set_loadcallback { |repodata|
550   repo = repodata.repo.appdata
551   repo ? repo.load_ext(repodata) : false
552 }
553
554 sysrepo = Repo_system.new('@System', 'system')
555 sysrepo.load(pool)
556 for repo in repos
557   repo.load(pool) if repo.enabled?
558 end
559
560 if cmd == 'search'
561   pool.createwhatprovides()
562   sel = pool.Selection
563   for di in pool.Dataiterator(Solv::SOLVABLE_NAME, args[0], Solv::Dataiterator::SEARCH_SUBSTRING | Solv::Dataiterator::SEARCH_NOCASE)
564     sel.add_raw(Solv::Job::SOLVER_SOLVABLE, di.solvid)
565   end
566   for s in sel.solvables
567     puts "- #{s.str} [#{s.repo.name}]: #{s.lookup_str(Solv::SOLVABLE_SUMMARY)}"
568   end
569   exit
570 end
571
572 abort("unknown command '#{cmd}'\n") unless cmdactionmap.has_key?(cmd)
573
574 addedprovides = pool.addfileprovides_queue()
575 if !addedprovides.empty?
576   sysrepo.updateaddedprovides(addedprovides)
577   for repo in repos
578     repo.updateaddedprovides(addedprovides)
579   end
580 end
581 pool.createwhatprovides()
582
583 jobs = []
584 for arg in args
585   flags = Solv::Selection::SELECTION_NAME | Solv::Selection::SELECTION_PROVIDES | Solv::Selection::SELECTION_GLOB
586   flags |= Solv::Selection::SELECTION_CANON | Solv::Selection::SELECTION_DOTARCH | Solv::Selection::SELECTION_REL
587   if arg =~ /^\//
588     flags |= Solv::Selection::SELECTION_FILELIST
589     flags |= Solv::Selection::SELECTION_INSTALLED_ONLY if cmd == 'erase'
590   end
591   sel = pool.select(arg, flags)
592   if sel.isempty?
593     sel = pool.select(arg, flags |  Solv::Selection::SELECTION_NOCASE)
594     puts "[ignoring case for '#{arg}']" unless sel.isempty?
595   end
596   puts "[using file list match for '#{arg}']" if sel.flags & Solv::Selection::SELECTION_FILELIST != 0
597   puts "[using capability match for '#{arg}']" if sel.flags & Solv::Selection::SELECTION_PROVIDES != 0
598   jobs += sel.jobs(cmdactionmap[cmd])
599 end
600
601 if jobs.empty? && (cmd == 'up' || cmd == 'dup' || cmd == 'verify')
602   sel = pool.Selection_all()
603   jobs += sel.jobs(cmdactionmap[cmd])
604 end
605
606 abort("no package matched.") if jobs.empty?
607
608 if cmd == 'list' || cmd == 'info'
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 for job in jobs
631   job.how ^= Solv::Job::SOLVER_UPDATE ^ Solv::Job::SOLVER_INSTALL if cmd == 'up' and job.isemptyupdate?
632 end
633
634 solver = pool.Solver
635 solver.set_flag(Solv::Solver::SOLVER_FLAG_SPLITPROVIDES, 1)
636 solver.set_flag(Solv::Solver::SOLVER_FLAG_ALLOW_UNINSTALL, 1) if cmd == 'erase'
637 #pool.set_debuglevel(1)
638
639 while true
640   problems = solver.solve(jobs)
641   break if problems.empty?
642   for problem in problems
643     puts "Problem #{problem.id}/#{problems.count}:"
644     puts problem
645     solutions = problem.solutions
646     for solution in solutions
647       puts "  Solution #{solution.id}:"
648       elements = solution.elements(true)
649       for element in elements
650         puts "  - #{element.str}"
651       end
652       puts
653     end
654     sol = nil
655     while true
656       print "Please choose a solution: "
657       STDOUT.flush
658       sol = STDIN.gets.strip
659       break if sol == 's' || sol == 'q'
660       break if sol =~ /^\d+$/ && sol.to_i >= 1 && sol.to_i <= solutions.length
661     end
662     next if sol == 's'
663     abort if sol == 'q'
664     solution = solutions[sol.to_i - 1]
665     for element in solution.elements
666       newjob = element.Job()
667       if element.type == Solv::Solver::SOLVER_SOLUTION_JOB
668         jobs[element.jobidx] = newjob
669       else
670         jobs.push(newjob) if newjob && !jobs.include?(newjob)
671       end
672     end
673   end
674 end
675
676 trans = solver.transaction
677 solver = nil
678 if trans.isempty?
679   puts "Nothing to do."
680   exit
681 end
682
683 puts "\nTransaction summary:\n"
684 for cl in trans.classify(Solv::Transaction::SOLVER_TRANSACTION_SHOW_OBSOLETES | Solv::Transaction::SOLVER_TRANSACTION_OBSOLETE_IS_UPGRADE)
685   if cl.type == Solv::Transaction::SOLVER_TRANSACTION_ERASE
686     puts "#{cl.count} erased packages:"
687   elsif cl.type == Solv::Transaction::SOLVER_TRANSACTION_INSTALL
688     puts "#{cl.count} installed packages:"
689   elsif cl.type == Solv::Transaction::SOLVER_TRANSACTION_REINSTALLED
690     puts "#{cl.count} reinstalled packages:"
691   elsif cl.type == Solv::Transaction::SOLVER_TRANSACTION_DOWNGRADED
692     puts "#{cl.count} downgraded packages:"
693   elsif cl.type == Solv::Transaction::SOLVER_TRANSACTION_CHANGED
694     puts "#{cl.count} changed packages:"
695   elsif cl.type == Solv::Transaction::SOLVER_TRANSACTION_UPGRADED
696     puts "#{cl.count} upgraded packages:"
697   elsif cl.type == Solv::Transaction::SOLVER_TRANSACTION_VENDORCHANGE
698     puts "#{cl.count} vendor changes from '#{cl.fromstr}' to '#{cl.tostr}':"
699   elsif cl.type == Solv::Transaction::SOLVER_TRANSACTION_ARCHCHANGE
700     puts "#{cl.count} arch changes from '#{cl.fromstr}' to '#{cl.tostr}':"
701   else
702     next
703   end
704   for p in cl.solvables
705     if cl.type == Solv::Transaction::SOLVER_TRANSACTION_UPGRADED || cl.type == Solv::Transaction::SOLVER_TRANSACTION_DOWNGRADED
706       puts "  - #{p.str} -> #{trans.othersolvable(p).str}"
707     else
708       puts "  - #{p.str}"
709     end
710   end
711   puts
712 end
713 puts "install size change: #{trans.calc_installsizechange()} K\n\n"
714
715 while true
716   print("OK to continue (y/n)? ")
717   STDOUT.flush
718   yn = STDIN.gets.strip
719   break if yn == 'y'
720   abort if yn == 'n' || yn == 'q'
721 end
722
723 newpkgs = trans.newsolvables()
724 newpkgsfp = {}
725 if !newpkgs.empty?
726   downloadsize = 0
727   for p in newpkgs
728     downloadsize += p.lookup_num(Solv::SOLVABLE_DOWNLOADSIZE)
729   end
730   puts "Downloading #{newpkgs.length} packages, #{downloadsize} K"
731   for p in newpkgs
732     repo = p.repo.appdata
733     location, medianr = p.lookup_location()
734     next unless location
735     location = repo.packagespath + location
736     chksum = p.lookup_checksum(Solv::SOLVABLE_CHECKSUM)
737     f = repo.download(location, false, chksum)
738     abort("\n#{@name}: #{location} not found in repository\n") unless f
739     newpkgsfp[p.id] = f
740     print "."
741     STDOUT.flush()
742   end
743   puts
744 end
745
746 puts "Committing transaction:"
747 puts
748 trans.order()
749 for p in trans.steps
750   steptype = trans.steptype(p, Solv::Transaction::SOLVER_TRANSACTION_RPM_ONLY)
751   if steptype == Solv::Transaction::SOLVER_TRANSACTION_ERASE
752     puts "erase #{p.str}"
753     next unless p.lookup_num(Solv::RPM_RPMDBID)
754     evr = p.evr.sub(/^[0-9]+:/, '')
755     system('rpm', '-e', '--nodeps', '--nodigest', '--nosignature', "#{p.name}-#{evr}.#{p.arch}") || abort("rpm failed: #{$? >> 8}") 
756   elsif (steptype == Solv::Transaction::SOLVER_TRANSACTION_INSTALL || steptype == Solv::Transaction::SOLVER_TRANSACTION_MULTIINSTALL)
757     puts "install #{p.str}"
758     f = newpkgsfp.delete(p.id)
759     next unless f
760     mode = steptype == Solv::Transaction::SOLVER_TRANSACTION_INSTALL ? '-U' : '-i'
761     system('rpm', mode, '--force', '--nodeps', '--nodigest', '--nosignature', "/dev/fd/#{f.fileno().to_s}") || abort("rpm failed: #{$? >> 8}")
762     f.close
763   end
764 end