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