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