]>
Commit | Line | Data |
---|---|---|
9137135a MT |
1 | #!/usr/bin/python |
2 | ||
f6e6ff79 MT |
3 | import datetime |
4 | import logging | |
9137135a MT |
5 | import os |
6 | import shutil | |
9137135a | 7 | |
f6e6ff79 MT |
8 | import pakfire |
9 | import pakfire.packages as packages | |
10 | ||
2c909128 MT |
11 | from . import base |
12 | from . import database | |
13 | from . import misc | |
9137135a | 14 | |
612ee16e MT |
15 | log = logging.getLogger("packages") |
16 | log.propagate = 1 | |
17 | ||
2c909128 | 18 | from .constants import * |
6a0a519f | 19 | from .decorators import * |
9137135a MT |
20 | |
21 | class 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 |
199 | class 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 MT |
213 | def delete(self): |
214 | self.db.execute("INSERT INTO queue_delete(path) VALUES(%s)", 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): |
6a0a519f MT |
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) | |
9137135a | 473 | |
6a0a519f MT |
474 | # Update cache |
475 | self.property[key] = value | |
9137135a | 476 | |
6a0a519f | 477 | @lazy_property |
f6e6ff79 | 478 | def properties(self): |
6a0a519f | 479 | res = self.db.get("SELECT * FROM packages_properties WHERE name = %s", self.name) |
9137135a | 480 | |
6a0a519f | 481 | ret = {} |
9137135a | 482 | |
6a0a519f MT |
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 | |
9137135a MT |
491 | |
492 | @property | |
f6e6ff79 | 493 | def critical_path(self): |
6a0a519f | 494 | return self.properties.get("critical_path", False) |
9137135a | 495 | |
9137135a | 496 | |
01197c1d | 497 | class File(base.Object): |
6a0a519f | 498 | def init(self, data): |
01197c1d MT |
499 | self.data = data |
500 | ||
501 | def __getattr__(self, attr): | |
502 | try: | |
503 | return self.data[attr] | |
504 | except KeyError: | |
6a0a519f | 505 | raise AttributeError(attr) |
01197c1d MT |
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): | |
5669a87f MT |
514 | # Empty files cannot be viewed. |
515 | if self.size == 0: | |
516 | return False | |
517 | ||
01197c1d MT |
518 | for ext in FILE_EXTENSIONS_VIEWABLE: |
519 | if self.name.endswith(ext): | |
520 | return True | |
521 | ||
522 | return False |