]> git.ipfire.org Git - pakfire.git/blob - src/pakfire/repository/database.py
4b3506e69bb76f7a52e03d10b88234e94891e3bb
[pakfire.git] / src / pakfire / repository / database.py
1 #!/usr/bin/python
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 ###############################################################################
21
22 import os
23 import random
24 import shutil
25 import sqlite3
26 import time
27
28 import logging
29 log = logging.getLogger("pakfire")
30
31 import pakfire.packages as packages
32
33 from pakfire.constants import *
34 from pakfire.errors import *
35 from pakfire.i18n import _
36
37 class Database(object):
38 def __init__(self, pakfire, filename):
39 self.pakfire = pakfire
40 self.filename = filename
41
42 self._db = None
43
44 def __del__(self):
45 if self._db:
46 self._db.close()
47 self._db = None
48
49 @property
50 def db(self):
51 if self._db is None:
52 self.open()
53
54 return self._db
55
56 def create(self):
57 pass
58
59 def migrate(self):
60 pass
61
62 def open(self):
63 if self._db is None:
64 log.debug("Open database %s" % self.filename)
65
66 dirname = os.path.dirname(self.filename)
67 if not os.path.exists(dirname):
68 os.makedirs(dirname)
69
70 database_exists = os.path.exists(self.filename)
71
72 # Make a connection to the database.
73 self._db = sqlite3.connect(self.filename)
74 self._db.row_factory = sqlite3.Row
75
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:
82 self.create()
83
84 def close(self):
85 if self._db:
86 self._db.close()
87 self._db = None
88
89 def commit(self):
90 if self._db:
91 self._db.commit()
92
93 def cursor(self):
94 self.open()
95 return self._db.cursor()
96
97 def executescript(self, *args, **kwargs):
98 self.open()
99 return self._db.executescript(*args, **kwargs)
100
101
102 class DatabaseLocal(Database):
103 def __init__(self, pakfire, repo):
104 self.repo = repo
105
106 # Generate filename for package database
107 filename = os.path.join(pakfire.path, PACKAGES_DB)
108
109 # Cache format number.
110 self.__format = None
111
112 Database.__init__(self, pakfire, filename)
113
114 def initialize(self):
115 # Open the database.
116 self.open()
117
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
122 def __len__(self):
123 count = 0
124
125 c = self.cursor()
126 c.execute("SELECT COUNT(*) AS count FROM packages")
127 for row in c:
128 count = row["count"]
129 c.close()
130
131 return count
132
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
150 def create(self):
151 c = self.cursor()
152 c.executescript("""
153 CREATE TABLE settings(
154 key TEXT,
155 val TEXT
156 );
157 INSERT INTO settings(key, val) VALUES('version', '%s');
158
159 CREATE TABLE files(
160 id INTEGER PRIMARY KEY,
161 name TEXT,
162 pkg INTEGER,
163 size INTEGER,
164 type INTEGER,
165 config INTEGER,
166 datafile INTEGER,
167 mode INTEGER,
168 user TEXT,
169 `group` TEXT,
170 hash1 TEXT,
171 mtime INTEGER,
172 capabilities TEXT
173 );
174 CREATE INDEX files_pkg_index ON files(pkg);
175
176 CREATE TABLE packages(
177 id INTEGER PRIMARY KEY,
178 name TEXT,
179 epoch INTEGER,
180 version TEXT,
181 release TEXT,
182 arch TEXT,
183 groups TEXT,
184 filename TEXT,
185 size INTEGER,
186 inst_size INTEGER,
187 hash1 TEXT,
188 license TEXT,
189 summary TEXT,
190 description TEXT,
191 uuid TEXT,
192 vendor TEXT,
193 build_id TEXT,
194 build_host TEXT,
195 build_date TEXT,
196 build_time INTEGER,
197 installed INT,
198 reason TEXT,
199 repository TEXT
200 );
201 CREATE INDEX packages_name_index ON packages(name);
202
203 CREATE TABLE scriptlets(
204 id INTEGER PRIMARY KEY,
205 pkg INTEGER,
206 action TEXT,
207 scriptlet TEXT
208 );
209 CREATE INDEX scriptlets_pkg_index ON scriptlets(pkg);
210
211 CREATE TABLE dependencies(
212 pkg INTEGER,
213 type TEXT,
214 dependency TEXT
215 );
216 CREATE INDEX dependencies_pkg_index ON dependencies(pkg);
217 """ % DATABASE_FORMAT)
218 # XXX add some indexes here
219 self.commit()
220 c.close()
221
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
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
231 log.info(_("Migrating database from format %(old)s to %(new)s.") % \
232 { "old" : self.format, "new" : DATABASE_FORMAT })
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
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
248 if self.format < 3:
249 c.execute("ALTER TABLE files ADD COLUMN `capabilities` TEXT")
250
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
255 if self.format < 5:
256 c.execute("ALTER TABLE files ADD COLUMN datafile INTEGER AFTER config")
257
258 if self.format < 6:
259 c.execute("ALTER TABLE packages ADD COLUMN inst_size INTEGER AFTER size")
260
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.splitlines()))
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
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
331 self.commit()
332 c.close()
333
334 def add_package(self, pkg, reason=None):
335 log.debug("Adding package to database: %s" % pkg.friendly_name)
336
337 c = self.cursor()
338
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,
350 inst_size,
351 hash1,
352 license,
353 summary,
354 description,
355 uuid,
356 vendor,
357 build_id,
358 build_host,
359 build_date,
360 build_time,
361 installed,
362 repository,
363 reason
364 ) VALUES(?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)""",
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,
374 pkg.inst_size,
375 pkg.hash1,
376 pkg.license,
377 pkg.summary,
378 pkg.description,
379 pkg.uuid,
380 pkg.vendor or "",
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 "",
388 )
389 )
390
391 pkg_id = c.lastrowid
392
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
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))
408
409 except:
410 raise
411
412 else:
413 self.commit()
414
415 c.close()
416
417 def rem_package(self, pkg):
418 assert isinstance(pkg, packages.DatabasePackage), pkg
419
420 log.debug("Removing package from database: %s" % pkg.friendly_name)
421
422 # First, delete all files from the database and then delete the pkg itself.
423 c = self.cursor()
424 c.execute("DELETE FROM files WHERE pkg = ?", (pkg.id,))
425 c.execute("DELETE FROM packages WHERE id = ?", (pkg.id,))
426 c.close()
427
428 self.commit()
429
430 def get_package_by_id(self, id):
431 c = self.cursor()
432 c.execute("SELECT * FROM packages WHERE id = ?", (id,))
433
434 try:
435 for row in c:
436 return packages.DatabasePackage(self.pakfire, self.repo, self, row)
437
438 finally:
439 c.close()
440
441 def get_package_by_uuid(self, uuid):
442 c = self.cursor()
443 c.execute("SELECT * FROM packages WHERE uuid = ?", (uuid,))
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
452 @property
453 def packages(self):
454 c = self.db.execute("SELECT * FROM packages ORDER BY name")
455
456 for row in c.fetchall():
457 yield packages.DatabasePackage(self.pakfire, self.repo, self, row)
458
459 c.close()
460
461 def get_filelist(self):
462 c = self.db.execute("SELECT name FROM files")
463
464 return [r["name"] for r in c.fetchall()]
465
466 def get_package_from_solv(self, solv_pkg):
467 assert solv_pkg.uuid
468
469 c = self.cursor()
470 c.execute("SELECT * FROM packages WHERE uuid = ? LIMIT 1", (solv_pkg.uuid,))
471
472 try:
473 for row in c:
474 return packages.DatabasePackage(self.pakfire, self.repo, self, row)
475
476 finally:
477 c.close()