]> git.ipfire.org Git - people/stevee/pakfire.git/blob - pakfire/packages/base.py
Add copyright information to all files.
[people/stevee/pakfire.git] / pakfire / packages / base.py
1 #!/usr/bin/python
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 ###############################################################################
21
22 import datetime
23 import logging
24 import os
25 import xml.sax.saxutils
26
27 import pakfire.util as util
28 from pakfire.i18n import _
29
30 class Package(object):
31 def __init__(self, pakfire, repo=None):
32 self.pakfire = pakfire
33 self._repo = repo
34
35 # Pointer to a package that is updated by this one.
36 self.old_package = None
37
38 def __repr__(self):
39 return "<%s %s>" % (self.__class__.__name__, self.friendly_name)
40
41 def __cmp__(self, other):
42 # if packages differ names return in alphabetical order
43 if not self.name == other.name:
44 return cmp(self.name, other.name)
45
46 ret = util.version_compare(self.pakfire.pool,
47 self.friendly_version, other.friendly_version)
48
49 # XXX this is to move packages that have been built a while ago and
50 # do not have all the meta information that they won't be evaluated
51 # as the best match.
52 if not ret:
53 if "X"*3 in (self.build_id, other.build_id):
54 if self.build_id == "X"*3 and not other.build_id == "X"*3:
55 ret = -1
56
57 elif not self.build_id == "X"*3 and other.build_id == "X"*3:
58 ret = 1
59 # XXX hack end
60
61 # Compare the build times if we have a rebuilt package.
62 if not ret and self.build_time and other.build_time:
63 ret = cmp(self.build_time, other.build_time)
64
65 #if ret == 0:
66 # logging.debug("%s is equal to %s" % (self, other))
67 #elif ret < 0:
68 # logging.debug("%s is more recent than %s" % (other, self))
69 #elif ret > 0:
70 # logging.debug("%s is more recent than %s" % (self, other))
71
72 # If no rank could be created, sort by repository priority
73 if not ret:
74 ret = cmp(self.repo, other.repo)
75
76 return ret
77
78 def __hash__(self):
79 hashstr = ["%s" % s for s in (self.name, self.epoch, self.version,
80 self.release, self.arch,)]
81
82 return hash("-".join(hashstr))
83
84 def dump(self, short=False, long=False):
85 if short:
86 return "%s.%s : %s" % (self.name, self.arch, self.summary)
87
88 items = [
89 (_("Name"), self.name),
90 (_("Arch"), self.arch),
91 (_("Version"), self.version),
92 (_("Release"), self.release),
93 (_("Size"), util.format_size(self.size)),
94 (_("Repo"), self.repo.name),
95 (_("Summary"), self.summary),
96 (_("Groups"), " ".join(self.groups)),
97 (_("URL"), self.url),
98 (_("License"), self.license),
99 ]
100
101 caption = _("Description")
102 for line in util.text_wrap(self.description):
103 items.append((caption, line))
104 caption = ""
105
106 if long:
107 items.append((_("UUID"), self.uuid))
108 items.append((_("Build ID"), self.build_id))
109 items.append((_("Build date"), self.build_date))
110 items.append((_("Build host"), self.build_host))
111
112 caption = _("Provides")
113 for prov in sorted(self.provides):
114 items.append((caption, prov))
115 caption = ""
116
117 caption = _("Requires")
118 for req in sorted(self.requires):
119 items.append((caption, req))
120 caption = ""
121
122 format = "%%-%ds : %%s" % (max([len(k) for k, v in items]))
123
124 s = []
125 for caption, value in items:
126 s.append(format % (caption, value))
127
128 s.append("") # New line at the end
129
130 # XXX why do we need to decode this?
131 return "\n".join([str.decode("utf-8") for str in s])
132
133 @property
134 def info(self):
135 info = {
136 "name" : self.name,
137 "version" : self.version,
138 "release" : self.release,
139 "epoch" : self.epoch,
140 "arch" : self.arch,
141 "groups" : self.groups,
142 "summary" : self.summary,
143 "description" : self.description,
144 "maintainer" : self.maintainer,
145 "url" : self.url,
146 "license" : self.license,
147 "hash1" : self.hash1,
148 "vendor" : self.vendor,
149 "build_host" : self.build_host,
150 "build_time" : self.build_time,
151 "size" : self.size,
152 "inst_size" : self.inst_size,
153 }
154
155 return info
156
157 @property
158 def hash1(self):
159 return "0"*40
160
161 @property
162 def size(self):
163 """
164 Return the size of the package file.
165
166 This should be overloaded by another class and returns 0 for
167 virtual packages.
168 """
169 return 0
170
171 @property
172 def inst_size(self):
173 # XXX to be done
174 return 0
175
176 @property
177 def local(self):
178 """
179 Indicates whether a package is located "local" means on disk
180 and has not be downloaded.
181 """
182 return False
183
184 ### META INFORMATION ###
185
186 @property
187 def metadata(self):
188 raise NotImplementedError
189
190 @property
191 def friendly_name(self):
192 return "%s-%s.%s" % (self.name, self.friendly_version, self.arch)
193
194 @property
195 def friendly_version(self):
196 s = "%s-%s" % (self.version, self.release)
197
198 if self.epoch:
199 s = "%d:%s" % (self.epoch, s)
200
201 return s
202
203 @property
204 def repo(self):
205 if self._repo:
206 return self._repo
207
208 # By default, every package is connected to a dummy repository
209 return self.pakfire.repos.dummy
210
211 @property
212 def name(self):
213 return self.metadata.get("PKG_NAME")
214
215 @property
216 def version(self):
217 return self.metadata.get("PKG_VER")
218
219 @property
220 def release(self):
221 ret = None
222
223 for i in ("PKG_RELEASE", "PKG_REL"):
224 ret = self.metadata.get(i, None)
225 if ret:
226 break
227
228 return ret
229
230 @property
231 def epoch(self):
232 epoch = self.metadata.get("PKG_EPOCH", 0)
233
234 return int(epoch)
235
236 @property
237 def arch(self):
238 raise NotImplementedError
239
240 @property
241 def base(self):
242 """
243 Say if a package belongs to the basic set
244 that is installed by default.
245 """
246 return "Base" in self.groups
247
248 @property
249 def critical(self):
250 """
251 Return if a package is marked "critial".
252 """
253 return "Critical" in self.groups
254
255 @property
256 def type(self):
257 return self.metadata.get("TYPE", "unknown")
258
259 @property
260 def maintainer(self):
261 return self.metadata.get("PKG_MAINTAINER")
262
263 @property
264 def license(self):
265 return self.metadata.get("PKG_LICENSE")
266
267 @property
268 def summary(self):
269 return self.metadata.get("PKG_SUMMARY")
270
271 @property
272 def description(self):
273 return self.metadata.get("PKG_DESCRIPTION")
274
275 @property
276 def groups(self):
277 return self.metadata.get("PKG_GROUPS", "").split()
278
279 @property
280 def url(self):
281 return self.metadata.get("PKG_URL")
282
283 @property
284 def triggers(self):
285 triggers = self.metadata.get("PKG_TRIGGERS", "")
286
287 return triggers.split()
288
289 @property
290 def signature(self):
291 raise NotImplementedError
292
293 @property
294 def build_date(self):
295 """
296 Automatically convert the UNIX timestamp from self.build_time to
297 a humanly readable format.
298 """
299 return "%s UTC" % datetime.datetime.utcfromtimestamp(self.build_time)
300
301 @property
302 def build_host(self):
303 return self.metadata.get("BUILD_HOST")
304
305 @property
306 def build_id(self):
307 return self.metadata.get("BUILD_ID")
308
309 @property
310 def build_time(self):
311 build_time = self.metadata.get("BUILD_TIME", 0)
312
313 return int(build_time)
314
315 @property
316 def uuid(self):
317 return self.metadata.get("PKG_UUID", None)
318
319 @property
320 def supported_arches(self):
321 return self.metadata.get("PKG_SUPPORTED_ARCHES", "all")
322
323 @property
324 def vendor(self):
325 return self.metadata.get("PKG_VENDOR", "")
326
327 @property
328 def prerequires(self):
329 requires = self.metadata.get("PKG_PREREQUIRES", "")
330
331 return requires.split()
332
333 @property
334 def requires(self):
335 ret = ""
336
337 # The default attributes, that are process for the requires.
338 attrs = ["PKG_REQUIRES", "PKG_DEPS",]
339
340 if self.arch == "src":
341 attrs += ["PKG_BUILD_DEPS",]
342
343 for i in attrs:
344 ret = self.metadata.get(i, ret)
345 if ret:
346 break
347
348 return ret.split()
349
350 @property
351 def provides(self):
352 return self.metadata.get("PKG_PROVIDES", "").split()
353
354 @property
355 def conflicts(self):
356 return self.metadata.get("PKG_CONFLICTS", "").split()
357
358 @property
359 def obsoletes(self):
360 return self.metadata.get("PKG_OBSOLETES", "").split()
361
362 def extract(self, path, prefix=None):
363 raise NotImplementedError, "%s" % repr(self)
364
365 def remove(self, message=None, prefix=None):
366 # Make two filelists. One contains all binary files that need to be
367 # removed, the other one contains the configuration files which are
368 # kept. files and configfiles are disjunct.
369 files = []
370 configfiles = self.configfiles
371
372 for file in self.filelist:
373 if file in configfiles:
374 continue
375
376 assert file.startswith("/")
377 files.append(file)
378
379 self._remove_files(files, message, prefix)
380
381 def _remove_files(self, files, message, prefix):
382 if prefix in ("/", None):
383 prefix = ""
384
385 # Load progressbar.
386 pb = None
387 if message:
388 message = "%-10s : %s" % (message, self.friendly_name)
389 pb = util.make_progress(message, len(files), eta=False)
390
391 # Sort files by the length of their name to remove all files in
392 # a directory first and then check, if there are any files left.
393 files.sort(cmp=lambda x,y: cmp(len(x), len(y)), reverse=True)
394
395 i = 0
396 for _file in files:
397 # Update progress.
398 if pb:
399 i += 1
400 pb.update(i)
401
402 logging.debug("Removing file: %s" % _file)
403
404 if prefix:
405 file = os.path.join(prefix, _file[1:])
406 assert file.startswith("%s/" % prefix)
407 else:
408 file = _file
409
410 # If the file was removed by the user, we can skip it.
411 if not os.path.exists(file):
412 continue
413
414 # Handle regular files and symlinks.
415 if os.path.isfile(file) or os.path.islink(file):
416 try:
417 os.remove(file)
418 except OSError:
419 logging.error("Cannot remove file: %s. Remove manually." % _file)
420
421 # Handle directories.
422 # Skip removal if the directory is a mountpoint.
423 elif os.path.isdir(file) and not os.path.ismount(file):
424 # Try to remove the directory. If it is not empty, OSError is raised,
425 # but we are okay with that.
426 try:
427 os.rmdir(file)
428 except OSError:
429 pass
430
431 # Log all unhandled types.
432 else:
433 logging.warning("Cannot remove file: %s. Filetype is unhandled." % _file)
434
435 if pb:
436 pb.finish()
437
438 # XXX Rename config files