]> git.ipfire.org Git - people/jschlag/pbs.git/blame - src/buildservice/packages.py
Merge branch 'master' of git://git.ipfire.org/pbs
[people/jschlag/pbs.git] / src / buildservice / packages.py
CommitLineData
9137135a
MT
1#!/usr/bin/python
2
f6e6ff79
MT
3import datetime
4import logging
9137135a
MT
5import os
6import shutil
9137135a 7
f6e6ff79
MT
8import pakfire
9import pakfire.packages as packages
10
2c909128
MT
11from . import base
12from . import database
13from . import misc
9137135a 14
612ee16e
MT
15log = logging.getLogger("packages")
16log.propagate = 1
17
2c909128 18from .constants import *
6a0a519f 19from .decorators import *
9137135a
MT
20
21class Packages(base.Object):
4b92e0a0
MT
22 def _get_package(self, query, *args):
23 res = self.db.get(query, *args)
24
25 if res:
26 return Package(self.backend, res.id, data=res)
27
28 def _get_packages(self, query, *args):
29 res = self.db.query(query, *args)
30
31 for row in res:
32 yield Package(self.backend, row.id, data=row)
33
326664a5
MT
34 def get_by_id(self, pkg_id):
35 return self._get_package("SELECT * FROM packages \
36 WHERE id = %s", pkg_id)
37
1104bcbf 38 def get_all_names(self, user=None, states=None):
3a0302c1 39 query = "SELECT DISTINCT packages.name AS name, summary FROM packages \
f6e6ff79
MT
40 JOIN builds ON builds.pkg_id = packages.id \
41 WHERE packages.type = 'source'"
42
43 conditions = []
44 args = []
45
f6e6ff79
MT
46 if user and not user.is_admin():
47 conditions.append("builds.owner_id = %s")
48 args.append(user.id)
49
50 if states:
51 for state in states:
52 conditions.append("builds.state = %s")
53 args.append(state)
54
55 if conditions:
56 query += " AND (%s)" % " OR ".join(conditions)
57
58 query += " ORDER BY packages.name"
59
60 return [(n.name, n.summary) for n in self.db.query(query, *args)]
61
62 def get_by_uuid(self, uuid):
1d2a49fe 63 pkg = self.db.get("SELECT * FROM packages WHERE uuid = %s LIMIT 1", uuid)
f6e6ff79
MT
64 if not pkg:
65 return
66
6a0a519f 67 return Package(self.backend, pkg.id, pkg)
f6e6ff79 68
e153d3f6
MT
69 def create(self, path):
70 # Just check if the file really exist
71 assert os.path.exists(path)
72
73 _pkg = packages.open(pakfire.PakfireServer(), None, path)
74
75 hash_sha512 = misc.calc_hash(path, "sha512")
76 assert hash_sha512
77
78 query = [
79 ("name", _pkg.name),
80 ("epoch", _pkg.epoch),
81 ("version", _pkg.version),
82 ("release", _pkg.release),
83 ("type", _pkg.type),
84 ("arch", _pkg.arch),
85
86 ("groups", " ".join(_pkg.groups)),
87 ("maintainer", _pkg.maintainer),
88 ("license", _pkg.license),
89 ("url", _pkg.url),
90 ("summary", _pkg.summary),
91 ("description", _pkg.description),
92 ("size", _pkg.inst_size),
93 ("uuid", _pkg.uuid),
94
95 # Build information.
96 ("build_id", _pkg.build_id),
97 ("build_host", _pkg.build_host),
98 ("build_time", datetime.datetime.utcfromtimestamp(_pkg.build_time)),
99
100 # File "metadata".
101 ("path", path),
102 ("filesize", os.path.getsize(path)),
103 ("hash_sha512", hash_sha512),
104 ]
105
106 if _pkg.type == "source":
107 query.append(("supported_arches", _pkg.supported_arches))
108
109 keys = []
110 vals = []
111 for key, val in query:
112 keys.append(key)
113 vals.append(val)
114
115 _query = "INSERT INTO packages(%s)" % ", ".join(keys)
116 _query += " VALUES(%s) RETURNING *" % ", ".join("%s" for v in vals)
117
118 # Create package entry in the database.
119 pkg = self._get_package(_query, *vals)
120
121 # Dependency information.
122 for d in _pkg.prerequires:
123 pkg.add_dependency("prerequires", d)
124
125 for d in _pkg.requires:
126 pkg.add_dependency("requires", d)
127
128 for d in _pkg.provides:
129 pkg.add_dependency("provides", d)
130
131 for d in _pkg.conflicts:
132 pkg.add_dependency("conflicts", d)
133
134 for d in _pkg.obsoletes:
135 pkg.add_dependency("obsoletes", d)
136
137 # Add all files to filelists table
138 for f in _pkg.filelist:
139 pkg.add_file(f.name, f.size, f.hash1, f.type, f.config, f.mode,
140 f.user, f.group, f.mtime, f.capabilities)
141
142 # Return the newly created object
143 return pkg
144
f6e6ff79
MT
145 def search(self, pattern, limit=None):
146 """
147 Searches for packages that do match the query.
148
149 This function does not work for UUIDs or filenames.
150 """
ce7abd33
MT
151 query = "SELECT * FROM packages \
152 WHERE type = %s AND ( \
153 name LIKE %s OR \
154 summary LIKE %s OR \
155 description LIKE %s \
156 ) \
f6e6ff79
MT
157 GROUP BY name"
158
ce7abd33
MT
159 pattern = "%%%s%%" % pattern
160 args = ("source", pattern, pattern, pattern)
161
162 res = self.db.query(query, *args)
163
f6e6ff79 164 pkgs = []
ce7abd33 165 for row in res:
6a0a519f 166 pkg = Package(self.backend, row.id, row)
f6e6ff79
MT
167 pkgs.append(pkg)
168
169 if limit and len(pkgs) >= limit:
170 break
9137135a 171
f6e6ff79 172 return pkgs
9137135a 173
f6e6ff79
MT
174 def search_by_filename(self, filename, limit=None):
175 query = "SELECT filelists.* FROM filelists \
176 JOIN packages ON filelists.pkg_id = packages.id \
177 WHERE filelists.name = %s ORDER BY packages.build_time DESC"
178 args = [filename,]
9137135a 179
f6e6ff79
MT
180 if limit:
181 query += " LIMIT %s"
182 args.append(limit)
9137135a 183
f6e6ff79
MT
184 files = []
185 for result in self.db.query(query, *args):
6a0a519f 186 pkg = Package(self.backend, result.pkg_id)
f6e6ff79 187 files.append((pkg, result))
9137135a 188
f6e6ff79 189 return files
9137135a 190
35c46db4
MT
191 def autocomplete(self, query, limit=8):
192 res = self.db.query("SELECT DISTINCT name FROM packages \
193 WHERE packages.name LIKE %s AND packages.type = %s \
194 ORDER BY packages.name LIMIT %s", "%%%s%%" % query, "source", limit)
195
196 return [row.name for row in res]
197
9137135a 198
6a0a519f
MT
199class Package(base.DataObject):
200 table = "packages"
9137135a 201
f6e6ff79
MT
202 def __repr__(self):
203 return "<%s %s>" % (self.__class__.__name__, self.friendly_name)
9137135a 204
6a0a519f
MT
205 def __eq__(self, other):
206 if isinstance(other, self.__class__):
207 return self.id == other.id
208
209 def __lt__(self, other):
210 if isinstance(other, self.__class__):
211 return pakfire.util.version_compare(self.backend, self.friendly_name, other.friendly_name) < 0
9137135a 212
f6e6ff79 213 def delete(self):
b2737501 214 self.backend.delete_file(os.path.join(PACKAGES_DIR, self.path))
9137135a 215
f6e6ff79
MT
216 # Delete all files from the filelist.
217 self.db.execute("DELETE FROM filelists WHERE pkg_id = %s", self.id)
9137135a 218
f6e6ff79
MT
219 # Delete the package.
220 self.db.execute("DELETE FROM packages WHERE id = %s", self.id)
9137135a 221
f6e6ff79
MT
222 @property
223 def uuid(self):
224 return self.data.uuid
9137135a
MT
225
226 @property
227 def name(self):
f6e6ff79 228 return self.data.name
9137135a
MT
229
230 @property
231 def epoch(self):
f6e6ff79 232 return self.data.epoch
9137135a
MT
233
234 @property
235 def version(self):
f6e6ff79 236 return self.data.version
9137135a
MT
237
238 @property
239 def release(self):
f6e6ff79
MT
240 return self.data.release
241
242 @property
243 def arch(self):
22b715d7 244 return self.data.arch
f6e6ff79
MT
245
246 @property
247 def type(self):
248 return self.data.type
9137135a
MT
249
250 @property
251 def friendly_name(self):
22b715d7 252 return "%s-%s.%s" % (self.name, self.friendly_version, self.arch)
9137135a
MT
253
254 @property
255 def friendly_version(self):
256 s = "%s-%s" % (self.version, self.release)
257
258 if self.epoch:
6a0a519f 259 s = "%s:%s" % (self.epoch, s)
9137135a
MT
260
261 return s
262
263 @property
f6e6ff79
MT
264 def groups(self):
265 return self.data.groups.split()
9137135a 266
6a0a519f 267 @lazy_property
f6e6ff79 268 def maintainer(self):
6a0a519f 269 return self.backend.users.find_maintainer(self.data.maintainer) or self.data.maintainer
9137135a
MT
270
271 @property
f6e6ff79
MT
272 def license(self):
273 return self.data.license
9137135a
MT
274
275 @property
f6e6ff79
MT
276 def url(self):
277 return self.data.url
9137135a
MT
278
279 @property
f6e6ff79
MT
280 def summary(self):
281 return self.data.summary
9137135a
MT
282
283 @property
f6e6ff79
MT
284 def description(self):
285 return self.data.description
9137135a
MT
286
287 @property
f6e6ff79
MT
288 def supported_arches(self):
289 return self.data.supported_arches
9137135a
MT
290
291 @property
f6e6ff79
MT
292 def size(self):
293 return self.data.size
9137135a 294
e153d3f6
MT
295 def add_dependency(self, type, what):
296 self.db.execute("INSERT INTO packages_deps(pkg_id, type, what) \
297 VALUES(%s, %s, %s)", self.id, type, what)
298
6a0a519f
MT
299 self.deps.append((type, what))
300
5669a87f
MT
301 def has_deps(self):
302 """
303 Returns True if the package has got dependencies.
304
305 Always filter out the uuid provides.
306 """
307 return len(self.deps) > 1
308
6a0a519f 309 @lazy_property
f6e6ff79 310 def deps(self):
6a0a519f
MT
311 res = self.db.query("SELECT type, what FROM packages_deps \
312 WHERE pkg_id = %s", self.id)
9137135a 313
6a0a519f
MT
314 ret = []
315 for row in res:
316 ret.append((row.type, row.what))
9137135a 317
6a0a519f 318 return ret
9137135a
MT
319
320 @property
f6e6ff79
MT
321 def prerequires(self):
322 return [d[1] for d in self.deps if d[0] == "prerequires"]
9137135a
MT
323
324 @property
f6e6ff79
MT
325 def requires(self):
326 return [d[1] for d in self.deps if d[0] == "requires"]
9137135a
MT
327
328 @property
f6e6ff79 329 def provides(self):
5669a87f 330 return [d[1] for d in self.deps if d[0] == "provides" and not d[1].startswith("uuid(")]
9137135a
MT
331
332 @property
f6e6ff79
MT
333 def conflicts(self):
334 return [d[1] for d in self.deps if d[0] == "conflicts"]
9137135a 335
f6e6ff79
MT
336 @property
337 def obsoletes(self):
338 return [d[1] for d in self.deps if d[0] == "obsoletes"]
9137135a 339
5669a87f
MT
340 @property
341 def suggests(self):
342 return [d[1] for d in self.deps if d[0] == "suggests"]
343
344 @property
345 def recommends(self):
346 return [d[1] for d in self.deps if d[0] == "recommends"]
347
f6e6ff79 348 def get_commit(self):
6a0a519f
MT
349 if self.data.commit_id:
350 return self.backend.sources.get_commit_by_id(self.data.commit_id)
9137135a 351
f6e6ff79 352 def set_commit(self, commit):
6a0a519f 353 self._set_attribute("commit_id", commit.id)
9137135a 354
6a0a519f 355 commit = lazy_property(get_commit, set_commit)
9137135a 356
f6e6ff79
MT
357 @property
358 def distro(self):
f6e6ff79 359 # XXX THIS CANNOT RETURN None
9137135a 360
6a0a519f
MT
361 if self.commit:
362 return self.commit.distro
9137135a 363
f6e6ff79
MT
364 @property
365 def build_id(self):
366 return self.data.build_id
9137135a
MT
367
368 @property
f6e6ff79
MT
369 def build_host(self):
370 return self.data.build_host
9137135a 371
f6e6ff79
MT
372 @property
373 def build_time(self):
374 return self.data.build_time
9137135a 375
f6e6ff79
MT
376 @property
377 def path(self):
378 return self.data.path
9137135a 379
612ee16e
MT
380 @property
381 def filename(self):
382 return os.path.basename(self.path)
383
f6e6ff79
MT
384 @property
385 def hash_sha512(self):
386 return self.data.hash_sha512
9137135a 387
f6e6ff79
MT
388 @property
389 def filesize(self):
390 return self.data.filesize
9137135a 391
612ee16e
MT
392 def copy(self, dst):
393 if os.path.isdir(dst):
394 dst = os.path.join(dst, self.filename)
395
396 if os.path.exists(dst):
397 raise IOError("Destination file exists: %s" % dst)
398
399 src = os.path.join(PACKAGES_DIR, self.path)
400
401 log.debug("Copying %s to %s" % (src, dst))
402
403 shutil.copy2(src, dst)
404
f6e6ff79
MT
405 def move(self, target_dir):
406 # Create directory if it does not exist, yet.
407 if not os.path.exists(target_dir):
408 os.makedirs(target_dir)
9137135a 409
f6e6ff79
MT
410 # Make full path where to put the file.
411 target = os.path.join(target_dir, os.path.basename(self.path))
9137135a 412
f6e6ff79
MT
413 # Copy the file to the target directory (keeping metadata).
414 shutil.move(self.path, target)
9137135a 415
f6e6ff79 416 # Update file path in the database.
6a0a519f 417 self._set_attribute("path", os.path.relpath(target, PACKAGES_DIR))
9137135a 418
6a0a519f 419 @lazy_property
f6e6ff79 420 def build(self):
80ee2515
MT
421 if self.job:
422 return self.job.build
423
6a0a519f
MT
424 return self.backend.builds._get_build("SELECT * FROM builds \
425 WHERE pkg_id = %s" % self.id)
9137135a 426
6a0a519f 427 @lazy_property
f6e6ff79 428 def job(self):
6a0a519f
MT
429 return self.backend.jobs._get_job("SELECT jobs.* FROM jobs \
430 LEFT JOIN jobs_packages pkgs ON jobs.id = pkgs.job_id \
431 WHERE pkgs.pkg_id = %s", self.id)
9137135a 432
6a0a519f 433 @lazy_property
f6e6ff79 434 def filelist(self):
6a0a519f
MT
435 res = self.db.query("SELECT * FROM filelists \
436 WHERE pkg_id = %s ORDER BY name", self.id)
01197c1d 437
6a0a519f
MT
438 ret = []
439 for row in res:
440 f = File(self.backend, row)
441 ret.append(f)
9137135a 442
6a0a519f 443 return ret
9137135a 444
6a6aaccd
MT
445 def get_file(self, filename):
446 res = self.db.get("SELECT * FROM filelists \
447 WHERE pkg_id = %s AND name = %s", self.id, filename)
448
449 if res:
450 return File(self.backend, res)
451
e153d3f6
MT
452 def add_file(self, name, size, hash_sha512, type, config, mode, user, group, mtime, capabilities):
453 # Convert mtime from seconds since epoch to datetime
454 mtime = datetime.datetime.utcfromtimestamp(float(mtime))
455
456 self.db.execute("INSERT INTO filelists(pkg_id, name, size, hash_sha512, type, config, mode, \
457 \"user\", \"group\", mtime, capabilities) VALUES(%s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s)",
458 self.id, name, size, hash_sha512, type, config, mode, user, group, mtime, capabilities)
459
6a6aaccd 460 def open(self):
01197c1d
MT
461 path = os.path.join(PACKAGES_DIR, self.path)
462
463 if os.path.exists(path):
464 return pakfire.packages.open(None, None, path)
9137135a 465
f6e6ff79
MT
466 ## properties
467
f6e6ff79 468 def update_property(self, key, value):
dca4779d
MT
469 if self.properties:
470 self.db.execute("UPDATE packages_properties SET %s = %%s \
471 WHERE name = %%s" % key, value, self.name)
6a0a519f 472 else:
dca4779d
MT
473 self.db.execute("INSERT INTO packages_properties(name, %s) \
474 VALUES(%%s, %%s)" % key, self.name, value)
9137135a 475
6a0a519f 476 # Update cache
dca4779d 477 self.properties[key] = value
9137135a 478
6a0a519f 479 @lazy_property
f6e6ff79 480 def properties(self):
6a0a519f 481 res = self.db.get("SELECT * FROM packages_properties WHERE name = %s", self.name)
9137135a 482
6a0a519f 483 ret = {}
6a0a519f
MT
484 if res:
485 for key in res:
486 if key in ("id", "name"):
487 continue
488
489 ret[key] = res[key]
490
491 return ret
9137135a
MT
492
493 @property
f6e6ff79 494 def critical_path(self):
6a0a519f 495 return self.properties.get("critical_path", False)
9137135a 496
9137135a 497
01197c1d 498class File(base.Object):
6a0a519f 499 def init(self, data):
01197c1d
MT
500 self.data = data
501
502 def __getattr__(self, attr):
503 try:
504 return self.data[attr]
505 except KeyError:
6a0a519f 506 raise AttributeError(attr)
01197c1d
MT
507
508 @property
509 def downloadable(self):
510 # All regular files are downloadable.
511 return self.type == 0
512
513 @property
514 def viewable(self):
5669a87f
MT
515 # Empty files cannot be viewed.
516 if self.size == 0:
517 return False
518
01197c1d
MT
519 for ext in FILE_EXTENSIONS_VIEWABLE:
520 if self.name.endswith(ext):
521 return True
522
523 return False