]> git.ipfire.org Git - people/jschlag/pbs.git/blob - src/buildservice/packages.py
Redesign mastering repositories
[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 base
12 from . import database
13 from . import misc
14
15 log = logging.getLogger("packages")
16 log.propagate = 1
17
18 from .constants import *
19 from .decorators import *
20
21 class Packages(base.Object):
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
34 def get_by_id(self, pkg_id):
35 return self._get_package("SELECT * FROM packages \
36 WHERE id = %s", pkg_id)
37
38 def get_all_names(self, user=None, states=None):
39 query = "SELECT DISTINCT packages.name AS name, summary FROM packages \
40 JOIN builds ON builds.pkg_id = packages.id \
41 WHERE packages.type = 'source'"
42
43 conditions = []
44 args = []
45
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):
63 pkg = self.db.get("SELECT * FROM packages WHERE uuid = %s LIMIT 1", uuid)
64 if not pkg:
65 return
66
67 return Package(self.backend, pkg.id, pkg)
68
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
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 """
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 ) \
157 GROUP BY name"
158
159 pattern = "%%%s%%" % pattern
160 args = ("source", pattern, pattern, pattern)
161
162 res = self.db.query(query, *args)
163
164 pkgs = []
165 for row in res:
166 pkg = Package(self.backend, row.id, row)
167 pkgs.append(pkg)
168
169 if limit and len(pkgs) >= limit:
170 break
171
172 return pkgs
173
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,]
179
180 if limit:
181 query += " LIMIT %s"
182 args.append(limit)
183
184 files = []
185 for result in self.db.query(query, *args):
186 pkg = Package(self.backend, result.pkg_id)
187 files.append((pkg, result))
188
189 return files
190
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
198
199 class Package(base.DataObject):
200 table = "packages"
201
202 def __repr__(self):
203 return "<%s %s>" % (self.__class__.__name__, self.friendly_name)
204
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
212
213 def delete(self):
214 self.db.execute("INSERT INTO queue_delete(path) VALUES(%s)", self.path)
215
216 # Delete all files from the filelist.
217 self.db.execute("DELETE FROM filelists WHERE pkg_id = %s", self.id)
218
219 # Delete the package.
220 self.db.execute("DELETE FROM packages WHERE id = %s", self.id)
221
222 @property
223 def uuid(self):
224 return self.data.uuid
225
226 @property
227 def name(self):
228 return self.data.name
229
230 @property
231 def epoch(self):
232 return self.data.epoch
233
234 @property
235 def version(self):
236 return self.data.version
237
238 @property
239 def release(self):
240 return self.data.release
241
242 @property
243 def arch(self):
244 return self.data.arch
245
246 @property
247 def type(self):
248 return self.data.type
249
250 @property
251 def friendly_name(self):
252 return "%s-%s.%s" % (self.name, self.friendly_version, self.arch)
253
254 @property
255 def friendly_version(self):
256 s = "%s-%s" % (self.version, self.release)
257
258 if self.epoch:
259 s = "%s:%s" % (self.epoch, s)
260
261 return s
262
263 @property
264 def groups(self):
265 return self.data.groups.split()
266
267 @lazy_property
268 def maintainer(self):
269 return self.backend.users.find_maintainer(self.data.maintainer) or self.data.maintainer
270
271 @property
272 def license(self):
273 return self.data.license
274
275 @property
276 def url(self):
277 return self.data.url
278
279 @property
280 def summary(self):
281 return self.data.summary
282
283 @property
284 def description(self):
285 return self.data.description
286
287 @property
288 def supported_arches(self):
289 return self.data.supported_arches
290
291 @property
292 def size(self):
293 return self.data.size
294
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
299 self.deps.append((type, what))
300
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
309 @lazy_property
310 def deps(self):
311 res = self.db.query("SELECT type, what FROM packages_deps \
312 WHERE pkg_id = %s", self.id)
313
314 ret = []
315 for row in res:
316 ret.append((row.type, row.what))
317
318 return ret
319
320 @property
321 def prerequires(self):
322 return [d[1] for d in self.deps if d[0] == "prerequires"]
323
324 @property
325 def requires(self):
326 return [d[1] for d in self.deps if d[0] == "requires"]
327
328 @property
329 def provides(self):
330 return [d[1] for d in self.deps if d[0] == "provides" and not d[1].startswith("uuid(")]
331
332 @property
333 def conflicts(self):
334 return [d[1] for d in self.deps if d[0] == "conflicts"]
335
336 @property
337 def obsoletes(self):
338 return [d[1] for d in self.deps if d[0] == "obsoletes"]
339
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
348 def get_commit(self):
349 if self.data.commit_id:
350 return self.backend.sources.get_commit_by_id(self.data.commit_id)
351
352 def set_commit(self, commit):
353 self._set_attribute("commit_id", commit.id)
354
355 commit = lazy_property(get_commit, set_commit)
356
357 @property
358 def distro(self):
359 # XXX THIS CANNOT RETURN None
360
361 if self.commit:
362 return self.commit.distro
363
364 @property
365 def build_id(self):
366 return self.data.build_id
367
368 @property
369 def build_host(self):
370 return self.data.build_host
371
372 @property
373 def build_time(self):
374 return self.data.build_time
375
376 @property
377 def path(self):
378 return self.data.path
379
380 @property
381 def filename(self):
382 return os.path.basename(self.path)
383
384 @property
385 def hash_sha512(self):
386 return self.data.hash_sha512
387
388 @property
389 def filesize(self):
390 return self.data.filesize
391
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
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)
409
410 # Make full path where to put the file.
411 target = os.path.join(target_dir, os.path.basename(self.path))
412
413 # Copy the file to the target directory (keeping metadata).
414 shutil.move(self.path, target)
415
416 # Update file path in the database.
417 self._set_attribute("path", os.path.relpath(target, PACKAGES_DIR))
418
419 @lazy_property
420 def build(self):
421 if self.job:
422 return self.job.build
423
424 return self.backend.builds._get_build("SELECT * FROM builds \
425 WHERE pkg_id = %s" % self.id)
426
427 @lazy_property
428 def job(self):
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)
432
433 @lazy_property
434 def filelist(self):
435 res = self.db.query("SELECT * FROM filelists \
436 WHERE pkg_id = %s ORDER BY name", self.id)
437
438 ret = []
439 for row in res:
440 f = File(self.backend, row)
441 ret.append(f)
442
443 return ret
444
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
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
460 def open(self):
461 path = os.path.join(PACKAGES_DIR, self.path)
462
463 if os.path.exists(path):
464 return pakfire.packages.open(None, None, path)
465
466 ## properties
467
468 def update_property(self, key, value):
469 if self.property:
470 self.db.execute("UPDATE packages_properties SET %s = %%s WHERE name = %%s" % key, value, self.name)
471 else:
472 self.db.execute("INSERT INTO packages_properties(name, %s) VALUES(%%s, %%s)" % key, self.name, value)
473
474 # Update cache
475 self.property[key] = value
476
477 @lazy_property
478 def properties(self):
479 res = self.db.get("SELECT * FROM packages_properties WHERE name = %s", self.name)
480
481 ret = {}
482
483 if res:
484 for key in res:
485 if key in ("id", "name"):
486 continue
487
488 ret[key] = res[key]
489
490 return ret
491
492 @property
493 def critical_path(self):
494 return self.properties.get("critical_path", False)
495
496
497 class File(base.Object):
498 def init(self, data):
499 self.data = data
500
501 def __getattr__(self, attr):
502 try:
503 return self.data[attr]
504 except KeyError:
505 raise AttributeError(attr)
506
507 @property
508 def downloadable(self):
509 # All regular files are downloadable.
510 return self.type == 0
511
512 @property
513 def viewable(self):
514 # Empty files cannot be viewed.
515 if self.size == 0:
516 return False
517
518 for ext in FILE_EXTENSIONS_VIEWABLE:
519 if self.name.endswith(ext):
520 return True
521
522 return False