]>
Commit | Line | Data |
---|---|---|
47a4cb89 | 1 | #!/usr/bin/python |
b792d887 MT |
2 | ############################################################################### |
3 | # # | |
4 | # Pakfire - The IPFire package management system # | |
5 | # Copyright (C) 2011 Pakfire development team # | |
6 | # # | |
7 | # This program is free software: you can redistribute it and/or modify # | |
8 | # it under the terms of the GNU General Public License as published by # | |
9 | # the Free Software Foundation, either version 3 of the License, or # | |
10 | # (at your option) any later version. # | |
11 | # # | |
12 | # This program is distributed in the hope that it will be useful, # | |
13 | # but WITHOUT ANY WARRANTY; without even the implied warranty of # | |
14 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # | |
15 | # GNU General Public License for more details. # | |
16 | # # | |
17 | # You should have received a copy of the GNU General Public License # | |
18 | # along with this program. If not, see <http://www.gnu.org/licenses/>. # | |
19 | # # | |
20 | ############################################################################### | |
47a4cb89 | 21 | |
47a4cb89 | 22 | import os |
5cc35aa4 | 23 | import random |
2568a6d1 | 24 | import shutil |
47a4cb89 | 25 | import sqlite3 |
66af936c | 26 | import time |
47a4cb89 | 27 | |
8b6bc023 MT |
28 | import logging |
29 | log = logging.getLogger("pakfire") | |
30 | ||
c605d735 MT |
31 | import pakfire.packages as packages |
32 | ||
a2d1644c | 33 | from pakfire.constants import * |
ba5dc639 | 34 | from pakfire.errors import * |
85a1120f | 35 | from pakfire.i18n import _ |
66af936c | 36 | |
47a4cb89 | 37 | class Database(object): |
3723913b MT |
38 | def __init__(self, pakfire, filename): |
39 | self.pakfire = pakfire | |
5cc35aa4 MT |
40 | self.filename = filename |
41 | ||
c605d735 | 42 | self._db = None |
47a4cb89 MT |
43 | |
44 | def __del__(self): | |
45 | if self._db: | |
47a4cb89 | 46 | self._db.close() |
c605d735 | 47 | self._db = None |
47a4cb89 | 48 | |
9b68f47c MT |
49 | @property |
50 | def db(self): | |
51 | if self._db is None: | |
52 | self.open() | |
53 | ||
54 | return self._db | |
55 | ||
47a4cb89 MT |
56 | def create(self): |
57 | pass | |
58 | ||
85a1120f MT |
59 | def migrate(self): |
60 | pass | |
61 | ||
47a4cb89 | 62 | def open(self): |
c605d735 | 63 | if self._db is None: |
8b6bc023 | 64 | log.debug("Open database %s" % self.filename) |
47a4cb89 | 65 | |
5cc35aa4 MT |
66 | dirname = os.path.dirname(self.filename) |
67 | if not os.path.exists(dirname): | |
68 | os.makedirs(dirname) | |
47a4cb89 | 69 | |
5cc35aa4 | 70 | database_exists = os.path.exists(self.filename) |
47a4cb89 MT |
71 | |
72 | # Make a connection to the database. | |
73 | self._db = sqlite3.connect(self.filename) | |
74 | self._db.row_factory = sqlite3.Row | |
75 | ||
85a1120f MT |
76 | # In the case, the database was not existant, it is |
77 | # filled with content. In case it has been there | |
78 | # we call the migrate method to update it if neccessary. | |
79 | if database_exists: | |
80 | self.migrate() | |
81 | else: | |
47a4cb89 MT |
82 | self.create() |
83 | ||
84 | def close(self): | |
0f8d6745 MT |
85 | if self._db: |
86 | self._db.close() | |
87 | self._db = None | |
5cc35aa4 | 88 | |
47a4cb89 | 89 | def commit(self): |
0f8d6745 MT |
90 | if self._db: |
91 | self._db.commit() | |
47a4cb89 MT |
92 | |
93 | def cursor(self): | |
c605d735 | 94 | self.open() |
87da3448 | 95 | return self._db.cursor() |
47a4cb89 | 96 | |
a2d1644c | 97 | def executescript(self, *args, **kwargs): |
c605d735 | 98 | self.open() |
a2d1644c MT |
99 | return self._db.executescript(*args, **kwargs) |
100 | ||
2568a6d1 | 101 | |
c605d735 MT |
102 | class DatabaseLocal(Database): |
103 | def __init__(self, pakfire, repo): | |
104 | self.repo = repo | |
2568a6d1 | 105 | |
c605d735 MT |
106 | # Generate filename for package database |
107 | filename = os.path.join(pakfire.path, PACKAGES_DB) | |
108 | ||
85a1120f MT |
109 | # Cache format number. |
110 | self.__format = None | |
111 | ||
c605d735 MT |
112 | Database.__init__(self, pakfire, filename) |
113 | ||
36b328f2 MT |
114 | def initialize(self): |
115 | # Open the database. | |
116 | self.open() | |
117 | ||
85a1120f MT |
118 | # Check if we actually can open the database. |
119 | if not self.format in DATABASE_FORMATS_SUPPORTED: | |
120 | raise DatabaseFormatError, _("The format of the database is not supported by this version of pakfire.") | |
121 | ||
c605d735 MT |
122 | def __len__(self): |
123 | count = 0 | |
47a4cb89 | 124 | |
47a4cb89 | 125 | c = self.cursor() |
c605d735 MT |
126 | c.execute("SELECT COUNT(*) AS count FROM packages") |
127 | for row in c: | |
128 | count = row["count"] | |
129 | c.close() | |
47a4cb89 | 130 | |
c605d735 MT |
131 | return count |
132 | ||
85a1120f MT |
133 | @property |
134 | def format(self): | |
135 | if self.__format is None: | |
136 | c = self.cursor() | |
137 | ||
138 | c.execute("SELECT val FROM settings WHERE key = 'version' LIMIT 1") | |
139 | for row in c: | |
140 | try: | |
141 | self.__format = int(row["val"]) | |
142 | break | |
143 | except ValueError: | |
144 | pass | |
145 | ||
146 | c.close() | |
147 | ||
148 | return self.__format | |
149 | ||
c605d735 MT |
150 | def create(self): |
151 | c = self.cursor() | |
47a4cb89 | 152 | c.executescript(""" |
c605d735 MT |
153 | CREATE TABLE settings( |
154 | key TEXT, | |
155 | val TEXT | |
156 | ); | |
85a1120f | 157 | INSERT INTO settings(key, val) VALUES('version', '%s'); |
c605d735 | 158 | |
47a4cb89 | 159 | CREATE TABLE files( |
c2808056 | 160 | id INTEGER PRIMARY KEY, |
47a4cb89 | 161 | name TEXT, |
c2808056 | 162 | pkg INTEGER, |
47a4cb89 MT |
163 | size INTEGER, |
164 | type INTEGER, | |
c2808056 | 165 | config INTEGER, |
3c5a85f3 | 166 | datafile INTEGER, |
c2808056 MT |
167 | mode INTEGER, |
168 | user TEXT, | |
169 | `group` TEXT, | |
170 | hash1 TEXT, | |
cabf1fbe MT |
171 | mtime INTEGER, |
172 | capabilities TEXT | |
47a4cb89 | 173 | ); |
87da3448 | 174 | CREATE INDEX files_pkg_index ON files(pkg); |
47a4cb89 MT |
175 | |
176 | CREATE TABLE packages( | |
c2808056 | 177 | id INTEGER PRIMARY KEY, |
47a4cb89 MT |
178 | name TEXT, |
179 | epoch INTEGER, | |
180 | version TEXT, | |
181 | release TEXT, | |
c560c27a | 182 | arch TEXT, |
8537c16d | 183 | groups TEXT, |
fa6d335b | 184 | filename TEXT, |
ba8c383d | 185 | size INTEGER, |
0d6d6fd2 | 186 | inst_size INTEGER, |
47a4cb89 | 187 | hash1 TEXT, |
47a4cb89 MT |
188 | license TEXT, |
189 | summary TEXT, | |
190 | description TEXT, | |
1317485d | 191 | uuid TEXT, |
85a1120f | 192 | vendor TEXT, |
47a4cb89 MT |
193 | build_id TEXT, |
194 | build_host TEXT, | |
0c665250 | 195 | build_date TEXT, |
c605d735 MT |
196 | build_time INTEGER, |
197 | installed INT, | |
198 | reason TEXT, | |
c07a3ca7 MT |
199 | repository TEXT |
200 | ); | |
87da3448 | 201 | CREATE INDEX packages_name_index ON packages(name); |
c07a3ca7 MT |
202 | |
203 | CREATE TABLE scriptlets( | |
204 | id INTEGER PRIMARY KEY, | |
205 | pkg INTEGER, | |
206 | action TEXT, | |
207 | scriptlet TEXT | |
208 | ); | |
87da3448 | 209 | CREATE INDEX scriptlets_pkg_index ON scriptlets(pkg); |
c07a3ca7 | 210 | |
87da3448 MT |
211 | CREATE TABLE dependencies( |
212 | pkg INTEGER, | |
213 | type TEXT, | |
214 | dependency TEXT | |
47a4cb89 | 215 | ); |
87da3448 | 216 | CREATE INDEX dependencies_pkg_index ON dependencies(pkg); |
85a1120f | 217 | """ % DATABASE_FORMAT) |
47a4cb89 | 218 | # XXX add some indexes here |
47a4cb89 MT |
219 | self.commit() |
220 | c.close() | |
221 | ||
85a1120f MT |
222 | def migrate(self): |
223 | # If we have already the latest version, there is nothing to do. | |
224 | if self.format == DATABASE_FORMAT: | |
225 | return | |
226 | ||
cabf1fbe MT |
227 | # Check if database version is supported. |
228 | if self.format > DATABASE_FORMAT: | |
229 | raise DatabaseError, _("Cannot use database with version greater than %s.") % DATABASE_FORMAT | |
230 | ||
4cdde79f MT |
231 | log.info(_("Migrating database from format %(old)s to %(new)s.") % \ |
232 | { "old" : self.format, "new" : DATABASE_FORMAT }) | |
85a1120f MT |
233 | |
234 | # Get a database cursor. | |
235 | c = self.cursor() | |
236 | ||
237 | # 1) The vendor column was added. | |
238 | if self.format < 1: | |
239 | c.execute("ALTER TABLE packages ADD COLUMN vendor TEXT AFTER uuid") | |
240 | ||
c2808056 MT |
241 | if self.format < 2: |
242 | c.execute("ALTER TABLE files ADD COLUMN `config` INTEGER") | |
243 | c.execute("ALTER TABLE files ADD COLUMN `mode` INTEGER") | |
244 | c.execute("ALTER TABLE files ADD COLUMN `user` TEXT") | |
245 | c.execute("ALTER TABLE files ADD COLUMN `group` TEXT") | |
246 | c.execute("ALTER TABLE files ADD COLUMN `mtime` INTEGER") | |
247 | ||
cabf1fbe MT |
248 | if self.format < 3: |
249 | c.execute("ALTER TABLE files ADD COLUMN `capabilities` TEXT") | |
250 | ||
a60f0f7d MT |
251 | if self.format < 4: |
252 | c.execute("ALTER TABLE packages ADD COLUMN recommends TEXT AFTER obsoletes") | |
253 | c.execute("ALTER TABLE packages ADD COLUMN suggests TEXT AFTER recommends") | |
254 | ||
3c5a85f3 MT |
255 | if self.format < 5: |
256 | c.execute("ALTER TABLE files ADD COLUMN datafile INTEGER AFTER config") | |
257 | ||
ba5dc639 MT |
258 | if self.format < 6: |
259 | c.execute("ALTER TABLE packages ADD COLUMN inst_size INTEGER AFTER size") | |
260 | ||
87da3448 MT |
261 | if self.format < 7: |
262 | c.executescript(""" | |
263 | CREATE TABLE dependencies(pkg INTEGER, type TEXT, dependency TEXT); | |
264 | CREATE INDEX dependencies_pkg_index ON dependencies(pkg); | |
265 | """) | |
266 | ||
267 | c.execute("SELECT id, provides, requires, conflicts, obsoletes, recommends, suggests FROM packages") | |
268 | pkgs = c.fetchall() | |
269 | ||
270 | for pkg in pkgs: | |
271 | (pkg_id, provides, requires, conflicts, obsoletes, recommends, suggests) = pkg | |
272 | ||
273 | dependencies = ( | |
274 | ("provides", provides), | |
275 | ("requires", requires), | |
276 | ("conflicts", conflicts), | |
277 | ("obsoletes", obsoletes), | |
278 | ("recommends", recommends), | |
279 | ("suggests", suggests), | |
280 | ) | |
281 | ||
282 | for type, deps in dependencies: | |
283 | c.executemany("INSERT INTO dependencies(pkg, type, dependency) VALUES(?, ?, ?)", | |
284 | ((pkg_id, type, d) for d in deps)) | |
285 | ||
286 | c.executescript(""" | |
287 | CREATE TABLE packages_( | |
288 | id INTEGER PRIMARY KEY, | |
289 | name TEXT, | |
290 | epoch INTEGER, | |
291 | version TEXT, | |
292 | release TEXT, | |
293 | arch TEXT, | |
294 | groups TEXT, | |
295 | filename TEXT, | |
296 | size INTEGER, | |
297 | inst_size INTEGER, | |
298 | hash1 TEXT, | |
299 | license TEXT, | |
300 | summary TEXT, | |
301 | description TEXT, | |
302 | uuid TEXT, | |
303 | vendor TEXT, | |
304 | build_id TEXT, | |
305 | build_host TEXT, | |
306 | build_date TEXT, | |
307 | build_time INTEGER, | |
308 | installed INT, | |
309 | reason TEXT, | |
310 | repository TEXT | |
311 | ); | |
312 | ||
313 | INSERT INTO packages_ SELECT id, name, epoch, version, release, arch, groups, filename, | |
314 | size, inst_size, hash1, license, summary, description, uuid, vendor, build_id, | |
315 | build_host, build_date, build_time, installed, reason, repository FROM packages; | |
316 | ||
317 | DROP TABLE packages; | |
318 | ALTER TABLE packages_ RENAME TO packages; | |
319 | ||
320 | DROP TABLE triggers; | |
321 | ||
322 | CREATE INDEX files_pkg_index ON files(pkg); | |
323 | CREATE INDEX scriptlets_pkg_index ON scriptlets(pkg); | |
324 | CREATE INDEX packages_name_index ON packages(name); | |
325 | """) | |
326 | ||
85a1120f MT |
327 | # In the end, we can easily update the version of the database. |
328 | c.execute("UPDATE settings SET val = ? WHERE key = 'version'", (DATABASE_FORMAT,)) | |
329 | self.__format = DATABASE_FORMAT | |
330 | ||
c2808056 | 331 | self.commit() |
85a1120f MT |
332 | c.close() |
333 | ||
66af936c | 334 | def add_package(self, pkg, reason=None): |
8b6bc023 | 335 | log.debug("Adding package to database: %s" % pkg.friendly_name) |
fa6d335b MT |
336 | |
337 | c = self.cursor() | |
fa6d335b | 338 | |
c605d735 MT |
339 | try: |
340 | c.execute(""" | |
341 | INSERT INTO packages( | |
342 | name, | |
343 | epoch, | |
344 | version, | |
345 | release, | |
346 | arch, | |
347 | groups, | |
348 | filename, | |
349 | size, | |
ba5dc639 | 350 | inst_size, |
c605d735 | 351 | hash1, |
c605d735 MT |
352 | license, |
353 | summary, | |
354 | description, | |
355 | uuid, | |
85a1120f | 356 | vendor, |
c605d735 MT |
357 | build_id, |
358 | build_host, | |
359 | build_date, | |
360 | build_time, | |
361 | installed, | |
362 | repository, | |
c07a3ca7 | 363 | reason |
87da3448 | 364 | ) VALUES(?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)""", |
c605d735 MT |
365 | ( |
366 | pkg.name, | |
367 | pkg.epoch, | |
368 | pkg.version, | |
369 | pkg.release, | |
370 | pkg.arch, | |
371 | " ".join(pkg.groups), | |
372 | pkg.filename, | |
373 | pkg.size, | |
ba5dc639 | 374 | pkg.inst_size, |
c605d735 | 375 | pkg.hash1, |
c605d735 MT |
376 | pkg.license, |
377 | pkg.summary, | |
378 | pkg.description, | |
379 | pkg.uuid, | |
85a1120f | 380 | pkg.vendor or "", |
c605d735 MT |
381 | pkg.build_id, |
382 | pkg.build_host, | |
383 | pkg.build_date, | |
384 | pkg.build_time, | |
385 | time.time(), | |
386 | pkg.repo.name, | |
387 | reason or "", | |
c605d735 MT |
388 | ) |
389 | ) | |
66af936c | 390 | |
c605d735 | 391 | pkg_id = c.lastrowid |
66af936c | 392 | |
87da3448 MT |
393 | # Add all dependencies. |
394 | dependencies = ( | |
395 | ("provides", pkg.provides), | |
396 | ("requires", pkg.requires), | |
397 | ("conflicts", pkg.conflicts), | |
398 | ("obsoletes", pkg.obsoletes), | |
399 | ("recommends", pkg.recommends), | |
400 | ("suggests", pkg.suggests), | |
401 | ) | |
402 | for type, deps in dependencies: | |
403 | c.executemany("INSERT INTO dependencies(pkg, type, dependency) VALUES(?, ?, ?)", ((pkg_id, type, d) for d in deps)) | |
404 | ||
3c5a85f3 MT |
405 | c.executemany("INSERT INTO files(`name`, `pkg`, `size`, `config`, `datafile`, `type`, `hash1`, `mode`, `user`, `group`, `mtime`, `capabilities`)" |
406 | " VALUES(?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)", | |
407 | ((f.name, pkg_id, f.size, f.is_config(), f.is_datafile(), f.type, f.hash1, f.mode, f.user, f.group, f.mtime, f.capabilities or "") for f in pkg.filelist)) | |
66af936c | 408 | |
c605d735 MT |
409 | except: |
410 | raise | |
66af936c | 411 | |
c605d735 MT |
412 | else: |
413 | self.commit() | |
66af936c | 414 | |
66af936c | 415 | c.close() |
fa6d335b | 416 | |
e871a081 | 417 | def rem_package(self, pkg): |
8b6bc023 | 418 | log.debug("Removing package from database: %s" % pkg.friendly_name) |
e871a081 MT |
419 | |
420 | assert pkg.uuid | |
421 | ||
422 | # Get the ID of the package in the database. | |
423 | c = self.cursor() | |
b8f51d98 MT |
424 | c.execute("SELECT id FROM packages WHERE uuid = ? LIMIT 1", (pkg.uuid,)) |
425 | #c.execute("SELECT id FROM packages WHERE name = ? AND epoch = ? AND version = ?" | |
426 | # " AND release = ? LIMIT 1", (pkg.name, pkg.epoch, pkg.version, pkg.release,)) | |
e871a081 | 427 | |
9b68f47c MT |
428 | row = c.fetchone() |
429 | if not row: | |
430 | return | |
431 | ||
432 | id = row["id"] | |
e871a081 MT |
433 | |
434 | # First, delete all files from the database and then delete the pkg itself. | |
435 | c.execute("DELETE FROM files WHERE pkg = ?", (id,)) | |
436 | c.execute("DELETE FROM packages WHERE id = ?", (id,)) | |
437 | ||
438 | c.close() | |
439 | self.commit() | |
440 | ||
862bea4d MT |
441 | def get_package_by_id(self, id): |
442 | c = self.cursor() | |
443 | c.execute("SELECT * FROM packages WHERE id = ?", (id,)) | |
444 | ||
445 | try: | |
446 | for row in c: | |
447 | return packages.DatabasePackage(self.pakfire, self.repo, self, row) | |
448 | ||
449 | finally: | |
450 | c.close() | |
451 | ||
c605d735 MT |
452 | @property |
453 | def packages(self): | |
9b68f47c | 454 | c = self.db.execute("SELECT * FROM packages ORDER BY name") |
47a4cb89 | 455 | |
9b68f47c | 456 | for row in c.fetchall(): |
c605d735 | 457 | yield packages.DatabasePackage(self.pakfire, self.repo, self, row) |
47a4cb89 | 458 | |
66af936c | 459 | c.close() |
6ee3d6b9 | 460 | |
0f8d6745 | 461 | def get_filelist(self): |
9b68f47c | 462 | c = self.db.execute("SELECT name FROM files") |
0f8d6745 | 463 | |
9b68f47c | 464 | return [r["name"] for r in c.fetchall()] |
0f8d6745 | 465 | |
6ee3d6b9 | 466 | def get_package_from_solv(self, solv_pkg): |
9b68f47c MT |
467 | assert solv_pkg.uuid |
468 | ||
469 | c = self.db.execute("SELECT * FROM packages WHERE uuid = ? LIMIT 1", (solv_pkg.uuid,)) | |
6ee3d6b9 MT |
470 | |
471 | try: | |
9b68f47c MT |
472 | row = c.fetchone() |
473 | if row is None: | |
474 | return | |
475 | ||
476 | return packages.DatabasePackage(self.pakfire, self.repo, self, row) | |
6ee3d6b9 MT |
477 | |
478 | finally: | |
479 | c.close() |