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