]> git.ipfire.org Git - people/stevee/pakfire.git/blame - pakfire/repository/index.py
Add copyright information to all files.
[people/stevee/pakfire.git] / pakfire / repository / index.py
CommitLineData
47a4cb89 1#!/usr/bin/python
b792d887
MT
2###############################################################################
3# #
4# Pakfire - The IPFire package management system #
5# Copyright (C) 2011 Pakfire development team #
6# #
7# This program is free software: you can redistribute it and/or modify #
8# it under the terms of the GNU General Public License as published by #
9# the Free Software Foundation, either version 3 of the License, or #
10# (at your option) any later version. #
11# #
12# This program is distributed in the hope that it will be useful, #
13# but WITHOUT ANY WARRANTY; without even the implied warranty of #
14# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the #
15# GNU General Public License for more details. #
16# #
17# You should have received a copy of the GNU General Public License #
18# along with this program. If not, see <http://www.gnu.org/licenses/>. #
19# #
20###############################################################################
47a4cb89
MT
21
22import logging
23import os
24
fa6d335b 25import database
3cf7127f 26import metadata
47a4cb89 27
c1fbb0b7 28import pakfire.compress as compress
8656150e 29import pakfire.downloader as downloader
a2d1644c 30import pakfire.packages as packages
89fac8cf 31import pakfire.satsolver as satsolver
a2d1644c
MT
32import pakfire.util as util
33
34from pakfire.constants import *
35from pakfire.i18n import _
ad963cd6 36
47a4cb89 37class Index(object):
3723913b 38 def __init__(self, pakfire, repo):
47a4cb89 39 self.pakfire = pakfire
d4c94aa5 40
c605d735
MT
41 # Create reference to repository and the solver repo.
42 self.repo = repo
43 self.solver_repo = repo.solver_repo
d4c94aa5 44
c605d735 45 self.init()
b6da0663 46
c605d735
MT
47 # Check, if initialization was okay.
48 self.check()
b6da0663 49
c605d735
MT
50 def __repr__(self):
51 return "<%s %s>" % (self.__class__.__name__, self.repo)
a2d1644c 52
c605d735
MT
53 def __len(self):
54 return len(self.repo)
47a4cb89 55
ae20b05f 56 @property
c605d735
MT
57 def cache(self):
58 return self.repo.cache
ae20b05f 59
c605d735 60 def init(self):
a2d1644c 61 pass
47a4cb89 62
c605d735
MT
63 def check(self):
64 """
65 Check if everything was correctly initialized.
66 """
66af936c
MT
67 raise NotImplementedError
68
47a4cb89 69 def update(self, force=False):
a2d1644c
MT
70 raise NotImplementedError
71
c605d735 72 def read(self, filename):
a2d1644c 73 """
c605d735 74 Read file in SOLV format from filename.
a2d1644c 75 """
c605d735 76 self.solver_repo.read(filename)
2568a6d1 77
c605d735
MT
78 def write(self, filename):
79 """
80 Write content to filename in SOLV format.
81 """
82 self.solver_repo.write(filename)
2568a6d1 83
018127aa
MT
84 def create_relation(self, *args, **kwargs):
85 return self.pakfire.create_relation(*args, **kwargs)
a2d1644c 86
c605d735
MT
87 def add_package(self, pkg):
88 # XXX Skip packages without a UUID
89 #if not pkg.uuid:
90 # logging.warning("Skipping package which lacks UUID: %s" % pkg)
91 # return
92 if not pkg.build_time:
93 return
a2d1644c 94
c605d735
MT
95 logging.debug("Adding package to index %s: %s" % (self, pkg))
96
97 solvable = satsolver.Solvable(self.solver_repo, pkg.name,
98 pkg.friendly_version, pkg.arch)
99
100 # Save metadata.
714392de
MT
101 if pkg.vendor:
102 solvable.set_vendor(pkg.vendor)
103
104 hash1 = pkg.hash1
105 assert hash1
106 solvable.set_hash1(hash1)
107
108 assert pkg.uuid
c605d735 109 solvable.set_uuid(pkg.uuid)
714392de
MT
110
111 if pkg.maintainer:
112 solvable.set_maintainer(pkg.maintainer)
113
114 if pkg.groups:
115 solvable.set_groups(" ".join(pkg.groups))
c605d735
MT
116
117 # Save upstream information (summary, description, license, url).
714392de
MT
118 if pkg.summary:
119 solvable.set_summary(pkg.summary)
120
121 if pkg.description:
122 solvable.set_description(pkg.description)
123
124 if pkg.license:
125 solvable.set_license(pkg.license)
126
127 if pkg.url:
128 solvable.set_url(pkg.url)
c605d735
MT
129
130 # Save build information.
714392de
MT
131 if pkg.build_host:
132 solvable.set_buildhost(pkg.build_host)
133
134 if pkg.build_time:
135 solvable.set_buildtime(pkg.build_time)
c605d735
MT
136
137 # Save filename.
138 filename = os.path.basename(pkg.filename)
714392de 139 assert filename
c605d735 140 solvable.set_filename(filename)
714392de 141
c605d735
MT
142 solvable.set_downloadsize(pkg.size)
143 solvable.set_installsize(pkg.inst_size)
144
145 # Import all requires.
71d3b468
MT
146 requires = pkg.requires
147 prerequires = pkg.prerequires
148 if prerequires:
149 requires.append("solvable:prereqmarker")
150 requires += prerequires
151
c605d735
MT
152 for req in pkg.requires:
153 rel = self.create_relation(req)
154 solvable.add_requires(rel)
155
156 # Import all provides.
157 for prov in pkg.provides:
158 rel = self.create_relation(prov)
159 solvable.add_provides(rel)
160
161 # Import all conflicts.
162 for conf in pkg.conflicts:
163 rel = self.create_relation(conf)
164 solvable.add_conflicts(rel)
165
166 # Import all obsoletes.
167 for obso in pkg.obsoletes:
168 rel = self.create_relation(obso)
169 solvable.add_obsoletes(rel)
170
171 # Import all files that are in the package.
172 rel = self.create_relation("solvable:filemarker")
173 solvable.add_provides(rel)
174 for file in pkg.filelist:
175 rel = self.create_relation(file)
176 solvable.add_provides(rel)
177
e871a081
MT
178 def rem_package(self, pkg):
179 # XXX delete the solvable from the index.
180 self.db.rem_package(pkg)
181
31267a64
MT
182 def clear(self):
183 """
184 Forget all packages from memory.
185 """
186 self.solver_repo.clear()
187
c605d735
MT
188
189class IndexSolv(Index):
190 def check(self):
191 pass # XXX to be done
a2d1644c 192
c605d735
MT
193 def update(self, force=False):
194 self._update_metadata(force)
195 self._update_database(force)
b6da0663 196
3cf7127f 197 def _update_metadata(self, force):
2568a6d1 198 filename = os.path.join(METADATA_DOWNLOAD_PATH, METADATA_DOWNLOAD_FILE)
3cf7127f
MT
199
200 # Marker if we need to do the download.
201 download = True
202
203 # Marker for the current metadata.
204 old_metadata = None
205
206 if not force:
207 # Check if file does exists and is not too old.
c605d735
MT
208 if self.cache.exists(filename):
209 age = self.cache.age(filename)
3cf7127f
MT
210 if age and age < TIME_10M:
211 download = False
212 logging.debug("Metadata is recent enough. I don't download it again.")
213
214 # Open old metadata for comparison.
215 old_metadata = metadata.Metadata(self.pakfire, self,
c605d735 216 self.cache.abspath(filename))
3cf7127f 217
6a509182
MT
218 # If no metadata was downloaded and we are in offline mode.
219 elif self.pakfire.offline:
220 raise OfflineModeError, _("There is no metadata for the repository '%s' and"
221 " we cannot download any because we are running in offline mode."
222 " Connect to a network or disable this repository.") % self.repo.name
223
224 elif force and self.pakfire.offline:
225 raise OfflineModeError, _("I cannot be forced to re-download the metadata for"
226 " the repository '%s' when running in offline mode.") % self.repo.name
227
3cf7127f 228 if download:
6a509182
MT
229 # We are supposed to download new metadata, but we are running in
230 # offline mode. That's okay. Just doing nothing.
231 if not self.pakfire.offline:
232 logging.debug("Going to (re-)download the repository metadata.")
3cf7127f 233
6a509182
MT
234 # Initialize a grabber for download.
235 grabber = downloader.MetadataDownloader(self.pakfire)
236 grabber = self.repo.mirrors.group(grabber)
3cf7127f 237
6a509182 238 data = grabber.urlread(filename, limit=METADATA_DOWNLOAD_LIMIT)
3cf7127f 239
6a509182
MT
240 # Parse new metadata for comparison.
241 new_metadata = metadata.Metadata(self.pakfire, self, metadata=data)
3cf7127f 242
6a509182
MT
243 if old_metadata and new_metadata < old_metadata:
244 logging.warning("The downloaded metadata was less recent than the current one. Trashing that.")
3cf7127f 245
6a509182
MT
246 else:
247 # We explicitely rewrite the metadata if it is equal to have
248 # a new timestamp and do not download it over and over again.
249 with self.cache.open(filename, "w") as o:
250 o.write(data)
3cf7127f
MT
251
252 # Parse the metadata that we just downloaded or load it from cache.
253 self.metadata = metadata.Metadata(self.pakfire, self,
c605d735 254 self.cache.abspath(filename))
3cf7127f
MT
255
256 def _update_database(self, force):
3cf7127f
MT
257 # Construct cache and download filename.
258 filename = os.path.join(METADATA_DOWNLOAD_PATH, self.metadata.database)
259
c605d735 260 if not self.cache.exists(filename):
6a509182
MT
261 if self.pakfire.offline:
262 raise OfflineModeError, _("Your repository metadata is outdated "
263 " and a new version needs to be downloaded.")
264
3cf7127f
MT
265 # Initialize a grabber for download.
266 grabber = downloader.DatabaseDownloader(
80104a80 267 self.pakfire,
3cf7127f
MT
268 text = _("%s: package database") % self.repo.name,
269 )
270 grabber = self.repo.mirrors.group(grabber)
271
272 data = grabber.urlread(filename)
273
c605d735 274 with self.cache.open(filename, "w") as o:
3cf7127f
MT
275 o.write(data)
276
93135ecb
MT
277 # decompress the database
278 if self.metadata.database_compression:
279 # Open input file and remove the file immediately.
280 # The fileobj is still open and the data will be removed
281 # when it is closed.
c605d735 282 compress.decompress(self.cache.abspath(filename),
c1fbb0b7 283 algo=self.metadata.database_compression)
3cf7127f 284
05e398fd 285 # check the hashsum of the downloaded file
c605d735 286 if not util.calc_hash1(self.cache.abspath(filename)) == self.metadata.database_hash1:
05e398fd
MT
287 # XXX an exception is not a very good idea because this file could
288 # be downloaded from another mirror. need a better way to handle this.
34102d90
MT
289
290 # Remove bad file from cache.
c605d735 291 self.cache.remove(filename)
34102d90 292
05e398fd
MT
293 raise Exception, "Downloaded file did not match the hashsum. Need to re-download it."
294
3cf7127f 295 # (Re-)open the database.
c605d735
MT
296 self.read(self.cache.abspath(filename))
297
298
299class IndexDir(Index):
8276111d
MT
300 def init(self):
301 self.pkg_type = None
302
303 if self.repo.type == "binary":
304 self.pkg_type = packages.BinaryPackage
305 elif self.repo.type == "source":
306 self.pkg_type = packages.SourcePackage
307
308 assert self.pkg_type
309
c605d735
MT
310 def check(self):
311 pass # XXX to be done
312
313 @property
314 def path(self):
315 path = self.repo.path
316
317 if path.startswith("file://"):
318 path = path[7:]
319
320 return path
3cf7127f
MT
321
322 def update(self, force=False):
c605d735 323 logging.debug("Updating repository index '%s' (force=%s)" % (self.path, force))
3cf7127f 324
c605d735
MT
325 # Do nothing if the update is not forced but populate the database
326 # if no packages are present.
327 if not force and len(self.repo):
2568a6d1
MT
328 return
329
c605d735
MT
330 # Collect all packages from default path.
331 self.collect_packages(self.path)
3cf7127f 332
c605d735 333 def collect_packages(self, path):
898278a2
MT
334 logging.debug("Collecting all packages from %s" % path)
335 pkgs = []
336
337 # Get a filelist of all files that could possibly be packages.
338 files = []
de7fba8f
MT
339
340 if os.path.isdir(path):
341 for dir, subdirs, _files in os.walk(path):
342 for file in sorted(_files):
343 # Skip files that do not have the right extension
344 if not file.endswith(".%s" % PACKAGE_EXTENSION):
345 continue
346
347 file = os.path.join(dir, file)
348 files.append(file)
349 elif os.path.isfile(path) and path.endswith(".%s" % PACKAGE_EXTENSION):
350 files.append(path)
898278a2
MT
351
352 if not files:
353 return pkgs
354
355 # Create progress bar.
356 pb = util.make_progress(_("Loading from %s") % path, len(files))
357 i = 0
358
359 for file in files:
360 if pb:
361 i += 1
362 pb.update(i)
363
364 package = packages.open(self.pakfire, self.repo, file)
d4c94aa5 365
8276111d
MT
366 # Find all packages with the given type and skip those of
367 # the other type.
368 if isinstance(package, self.pkg_type):
369 # Check for binary packages if the architecture matches.
370 if isinstance(package, packages.BinaryPackage) and \
371 not package.arch in (self.repo.arch, "noarch"):
c605d735
MT
372 logging.warning("Skipped package with wrong architecture: %s (%s)" \
373 % (package.filename, package.arch))
c605d735
MT
374 continue
375
376 # Skip all source packages.
8276111d 377 else:
c605d735
MT
378 continue
379
380 self.add_package(package)
898278a2
MT
381 pkgs.append(package)
382
383 if pb:
384 pb.finish()
c605d735 385
898278a2 386 return pkgs
c605d735
MT
387
388
389class IndexLocal(Index):
390 def init(self):
391 self.db = database.DatabaseLocal(self.pakfire, self.repo)
392
393 def check(self):
394 # XXX Create the database and lock it or something.
395 pass
396
397 def update(self, force=True):
398 if self.solver_repo.size() == 0:
399 force = True
400
401 if force:
402 package_count = len(self.db)
403
404 # Nothing to do here, if there are no packages in the database.
405 if not package_count:
406 return
407
408 # Add all packages from the database to the index.
409 pb = util.make_progress(_("Loading installed packages"), package_count)
410
411 i = 0
412 for pkg in self.db.packages:
413 if pb:
414 i += 1
415 pb.update(i)
416
714392de 417 self.add_package(pkg)
c605d735
MT
418
419 if pb:
420 pb.finish()