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