- finish rbsolv repository handling
[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       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       d.setpos_parent()
225       filename = d.pool.lookup_str(Solv::SOLVID_POS, Solv::REPOSITORY_REPOMD_LOCATION)
226       next unless filename
227       checksum = d.pool.lookup_checksum(Solv::SOLVID_POS, 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       d.setpos_parent()
342       checksum = d.pool.lookup_checksum(Solv::SOLVID_POS, 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.match_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 end
462
463 class Repo_unknown < Repo_generic
464   def load(pool)
465     puts "unsupported repo '#{@name}: skipped"
466     return false
467   end
468 end
469
470 class Repo_system < Repo_generic
471   def load(pool)
472     @handle = pool.add_repo(@name)
473     @handle.appdata = self
474     pool.installed = @handle
475     print "rpm database: "
476     @cookie = calc_cookie_file("/var/lib/rpm/Packages")
477     if usecachedrepo(nil)
478       puts "cached"
479       return true
480     end
481     puts "reading"
482     @handle.add_products("/etc/products.d", Solv::Repo::REPO_NO_INTERNALIZE)
483     @handle.add_rpmdb(nil, Solv::Repo::REPO_REUSE_REPODATA)
484     writecachedrepo(nil)
485     return true
486   end
487 end
488
489
490 def validarch?(pool, arch)
491   return false unless arch && arch != ''
492   id = pool.str2id(arch, false)
493   return id != 0 && pool.isknownarch?(id)
494 end
495
496 def depglob(pool, name, globname, globdep)
497   id = pool.str2id(name, false)
498   if id != 0
499     match = false
500     providers = pool.providers(id)
501     if globname && providers.find {|s| s.nameid == id }
502       return [ pool.Job(Solv::Job::SOLVER_SOLVABLE_NAME, id) ]
503     end
504     if !providers.empty?
505       puts "[using capability match for '#{name}']" if globname && globdep
506       return [ pool.Job(Solv::Job::SOLVER_SOLVABLE_PROVIDES, id) ]
507     end
508   end
509   return [] unless name =~ /[\[*?]/;
510   if globname
511     idmatches = {}
512     for d in pool.Dataiterator(0, Solv::SOLVABLE_NAME, name, Solv::Dataiterator::SEARCH_GLOB)
513       s = d.solvable
514       idmatches[s.nameid] = 1 if s.installable?
515     end
516     if !idmatches.empty?
517       return idmatches.keys.sort.collect { |id| pool.Job(Solv::Job::SOLVER_SOLVABLE_NAME, id) }
518     end
519   end
520   if globdep
521     idmatches = pool.matchprovidingids(name, Solv::Dataiterator::SEARCH_GLOB);
522     if !idmatches.empty?
523       puts "[using capability match for '#{name}']"
524       return idmatches.sort.collect { |id| pool.Job(Solv::Job::SOLVER_SOLVABLE_PROVIDES, id) }
525     end
526   end
527   return []
528 end
529
530 def limitjobs(pool, jobs, flags, evrstr)
531   njobs = []
532   evr = pool.str2id(evrstr)
533   for j in jobs
534     how = j.how
535     sel = how & Solv::Job::SOLVER_SELECTMASK
536     what = pool.rel2id(j.what, evr, flags)
537     if flags == Solv::REL_ARCH
538       how |= Solv::Job::SOLVER_SETARCH
539     elsif flags == Solv::REL_EQ && sel == Solv::Job::SOLVER_SOLVABLE_NAME
540       how |= evrstr.include?(?-) ? Solv::Job::SOLVER_SETEVR : Solv::Job::SOLVER_SETEV
541     end
542     njobs << pool.Job(how, what)
543   end
544   return njobs
545 end
546
547 def limitjobs_evrarch(pool, jobs, flags, evrstr)
548   if evrstr =~ /^(.+)\.(.+?)$/ && validarch?(pool, $2)
549     evrstr = $1
550     jobs = limitjobs(pool, jobs, Solv::REL_ARCH, $2)
551   end
552   return limitjobs(pool, jobs, flags, evrstr)
553 end
554
555 def mkjobs_rel(pool, cmd, name, rel, evr)
556   flags = 0
557   flags |= Solv::REL_LT if rel.include?(?<)
558   flags |= Solv::REL_EQ if rel.include?(?=)
559   flags |= Solv::REL_GT if rel.include?(?>)
560   jobs = depglob(pool, name, true, true)
561   return limitjobs(pool, jobs, flags, evr) unless jobs.empty?
562   if (name =~ /^(.+)\.(.+?)$/) && validarch?(pool, $2)
563     arch = $2
564     jobs = depglob(pool, name, true, true)
565     return [] if jobs.empty?
566     jobs = limitjobs(pool, jobs, Solv::REL_ARCH, arch)
567     return limitjobs(pool, jobs, flags, evr)
568   end
569   return []
570 end
571
572 def mkjobs_nevra(pool, cmd, arg)
573   jobs = depglob(pool, arg, true, true)
574   return jobs unless jobs.empty?
575   if ((arg =~ /^(.+)\.(.+?)$/) && validarch?(pool, $2))
576     arch = $2
577     jobs = depglob(pool, $1, true, true)
578     return limitjobs(pool, jobs, Solv::REL_ARCH, arch) unless jobs.empty?
579   end
580   if (arg =~ /^(.+)-(.+?)$/)
581     evr = $2
582     jobs = depglob(pool, $1, true, false)
583     return limitjobs_evrarch(pool, jobs, Solv::REL_EQ, evr) unless jobs.empty?
584   end
585   if (arg =~ /^(.+)-(.+?-.+?)$/)
586     evr = $2
587     jobs = depglob(pool, $1, true, false)
588     return limitjobs_evrarch(pool, jobs, Solv::REL_EQ, evr) unless jobs.empty?
589   end
590   return []
591 end
592
593 def mkjobs_filelist(pool, cmd, arg)
594   type = Solv::Dataiterator::SEARCH_STRING
595   type = Solv::Dataiterator::SEARCH_GLOB if arg =~ /[\[*?]/
596   if cmd == 'erase'
597     di = pool.installed.Dataiterator(0, Solv::SOLVABLE_FILELIST, arg, type | Solv::Dataiterator::SEARCH_FILES|Solv::Dataiterator::SEARCH_COMPLETE_FILELIST)
598   else
599     di = pool.Dataiterator(0, Solv::SOLVABLE_FILELIST, arg, type | Solv::Dataiterator::SEARCH_FILES|Solv::Dataiterator::SEARCH_COMPLETE_FILELIST)
600   end
601   matches = []
602   for d in di
603     s = d.solvable
604     next unless s && s.installable?
605     matches.push(s.id)
606     di.skip_solvable()
607   end
608   return [] if matches.empty?
609   puts "[using file list match for '#{arg}']"
610   if matches.length > 1
611     return [ pool.Job(Solv::Job::SOLVER_SOLVABLE_ONE_OF, pool.towhatprovides(matches)) ]
612   else
613     return [ pool.Job(Solv::Job::SOLVER_SOLVABLE | Solv::Job::SOLVER_NOAUTOSET, matches[0]) ]
614   end
615 end
616
617 def mkjobs(pool, cmd, arg)
618   if arg =~ /^\//
619     jobs = mkjobs_filelist(pool, cmd, arg)
620     return jobs unless jobs.empty?
621   end
622   if (arg =~ /^(.+?)\s*([<=>]+)\s*(.+?)$/)
623     return mkjobs_rel(pool, cmd, $1, $2, $3)
624   else
625     return mkjobs_nevra(pool, cmd, arg)
626   end
627 end
628
629 args = ARGV
630 cmd = args.shift
631 cmd = 'list' if cmd == 'li'
632 cmd = 'install' if cmd == 'in'
633 cmd = 'erase' if cmd == 'rm'
634 cmd = 'verify' if cmd == 've'
635 cmd = 'search' if cmd == 'se'
636
637 repos = []
638 for reposdir in [ '/etc/zypp/repos.d' ] do
639   next unless FileTest.directory?(reposdir)
640   for reponame in Dir["#{reposdir}/*.repo"].sort do
641     cfg = IniFile.new(reponame)
642     cfg.each_section do |ali|
643       repoattr = { 'alias' => ali, 'enabled' => 0, 'priority' => 99, 'autorefresh' => 1, 'type' => 'rpm-md', 'metadata_expire' => 900}
644       repoattr.update(cfg[ali])
645       if repoattr['type'] == 'rpm-md'
646         repo = Repo_rpmmd.new(ali, 'repomd', repoattr)
647       elsif repoattr['type'] == 'yast2'
648         repo = Repo_susetags.new(ali, 'susetags', repoattr)
649       else
650         repo = Repo_unknown.new(ali, 'unknown', repoattr)
651       end
652       repos.push(repo)
653     end
654   end
655 end
656
657 pool = Solv::Pool.new()
658 # require 'sys/uname' ; sysarch = Sys::Uname.machine
659 sysarch = `uname -p`.strip
660 pool.setarch(sysarch)
661
662 pool.set_loadcallback { |repodata|
663   repo = repodata.repo.appdata
664   repo ? repo.load_ext(repodata) : false
665 }
666
667 sysrepo = Repo_system.new('@System', 'system')
668 sysrepo.load(pool)
669 for repo in repos
670   repo.load(pool) if repo.enabled?
671 end
672
673 if cmd == 'search'
674   matches = {}
675   for di in pool.Dataiterator(0, Solv::SOLVABLE_NAME, args[0], Solv::Dataiterator::SEARCH_SUBSTRING | Solv::Dataiterator::SEARCH_NOCASE)
676     matches[di.solvid] = true
677   end
678   for solvid in matches.keys.sort
679     s = pool.solvables[solvid]
680     puts "- #{s.str} [#{s.repo.name}]"
681   end
682   exit
683 end
684
685 addedprovides = pool.addfileprovides_ids()
686 if !addedprovides.empty?
687   sysrepo.updateaddedprovides(addedprovides)
688   for repo in repos
689     repo.updateaddedprovides(addedprovides)
690   end
691 end
692 pool.createwhatprovides()
693
694 jobs = []
695 for arg in args
696   njobs = mkjobs(pool, cmd, ARGV[0])
697   abort("nothing matches '#{arg}'") if njobs.empty?
698   jobs += njobs
699 end
700
701 if cmd == 'list' || cmd == 'info'
702   abort("no package matched.") if jobs.empty?
703     for job in jobs
704       for s in job.solvables()
705         if cmd == 'info'
706           puts "Name:        #{s.str}"
707           puts "Repo:        #{s.repo.name}"
708           puts "Summary:     #{s.lookup_str(Solv::SOLVABLE_SUMMARY)}"
709           str = s.lookup_str(Solv::SOLVABLE_URL)
710           puts "Url:         #{str}" if str
711           str = s.lookup_str(Solv::SOLVABLE_LICENSE)
712           puts "License:     #{str}" if str
713           puts "Description:\n#{s.lookup_str(Solv::SOLVABLE_DESCRIPTION)}"
714           puts
715         else
716           puts "  - #{s.str} [#{s.repo.name}]"
717           puts "    #{s.lookup_str(Solv::SOLVABLE_SUMMARY)}"
718         end
719       end
720     end
721   exit
722 end
723
724 if cmd == 'install' || cmd == 'erase' || cmd == 'up' || cmd == 'dup' || cmd == 'verify'
725   if jobs.empty?
726     if cmd == 'up' || cmd == 'verify'
727       jobs = [ pool.Job(Solv::Job::SOLVER_SOLVABLE_ALL, 0) ]
728     elsif cmd != 'dup'
729       abort("no package matched.")
730     end
731   end
732   for job in jobs
733     if cmd == 'up'
734       if job.how == Solv::Job::SOLVER_SOLVABLE_ALL || job.solvables.any? {|s| s.isinstalled?}
735         job.how |= Solv::Job::SOLVER_UPDATE
736       else
737         job.how |= Solv::Job::SOLVER_INSTALL
738       end
739     elsif cmd == 'install'
740       job.how |= Solv::Job::SOLVER_INSTALL
741     elsif cmd == 'erase'
742       job.how |= Solv::Job::SOLVER_ERASE
743     elsif cmd == 'dup'
744       job.how |= Solv::Job::SOLVER_DISTUPGRADE
745     elsif cmd == 'verify'
746       job.how |= Solv::Job::SOLVER_VERIFY
747     end
748   end
749
750   solver = nil
751   #pool.set_debuglevel(1)
752   while true
753     solver = pool.Solver
754     solver.ignorealreadyrecommended = true
755     solver.allowuninstall = true if cmd == 'erase'
756     if cmd == 'dup' && jobs.empty?
757       solver.distupgrade = true
758       solver.updatesystem = true
759       solver.allowdowngrade = true
760       solver.allowvendorchange = true
761       solver.allowarchchange = true
762       solver.dosplitprovides = true
763     elsif cmd == 'up' && jobs.length == 1 && jobs[0].how == (Solv::Job::SOLVER_UPDATE | Solv::Job::SOLVER_SOLVABLE_ALL)
764       solver.dosplitprovides = true
765     end
766     problems = solver.solve(jobs)
767     break if problems.empty?
768     for problem in problems
769       puts "Problem #{problem.id}:"
770       puts problem.findproblemrule.info.problemstr
771       solutions = problem.solutions
772       for solution in solutions
773         puts "  Solution #{solution.id}:"
774         elements = solution.elements(true)
775         for element in elements
776           puts "  - #{element.str}"
777         end
778       end
779       exit
780     end
781   end
782   trans = solver.transaction
783   solver = nil
784   if trans.isempty?
785     puts "Nothing to do."
786     exit
787   end
788   puts "\nTransaction summary:\n"
789   for cl in trans.classify()
790     if cl.type == Solv::Transaction::SOLVER_TRANSACTION_ERASE
791       puts "#{cl.count} erased packages:"
792     elsif cl.type == Solv::Transaction::SOLVER_TRANSACTION_INSTALL
793       puts "#{cl.count} installed packages:"
794     elsif cl.type == Solv::Transaction::SOLVER_TRANSACTION_REINSTALLED
795       puts "#{cl.count} reinstalled packages:"
796     elsif cl.type == Solv::Transaction::SOLVER_TRANSACTION_DOWNGRADED
797       puts "#{cl.count} downgraded packages:"
798     elsif cl.type == Solv::Transaction::SOLVER_TRANSACTION_CHANGED
799       puts "#{cl.count} changed packages:"
800     elsif cl.type == Solv::Transaction::SOLVER_TRANSACTION_UPGRADED
801       puts "#{cl.count} upgraded packages:"
802     elsif cl.type == Solv::Transaction::SOLVER_TRANSACTION_VENDORCHANGE
803       puts "#{cl.count} vendor changes from '#{pool.id2str(cl.fromid)}' to '#{pool.id2str(cl.toid)}':"
804     elsif cl.type == Solv::Transaction::SOLVER_TRANSACTION_ARCHCHANGE
805       puts "#{cl.count} arch changes from '#{pool.id2str(cl.fromid)}' to '#{pool.id2str(cl.toid)}':"
806     else
807       next
808     end
809     for p in cl.solvables
810       if cl.type == Solv::Transaction::SOLVER_TRANSACTION_UPGRADED || cl.type == Solv::Transaction::SOLVER_TRANSACTION_DOWNGRADED
811         puts "  - #{p.str} -> #{trans.othersolvable(p).str}"
812       else
813         puts "  - #{p.str}"
814       end
815     end
816     puts
817   end
818   puts "install size change: #{trans.calc_installsizechange()} K\n\n"
819 end