]> git.ipfire.org Git - people/stevee/pakfire.git/blame - src/pakfire/repository/database.py
Use autotools.
[people/stevee/pakfire.git] / src / pakfire / repository / database.py
CommitLineData
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 22import os
5cc35aa4 23import random
2568a6d1 24import shutil
47a4cb89 25import sqlite3
66af936c 26import time
47a4cb89 27
8b6bc023
MT
28import logging
29log = logging.getLogger("pakfire")
30
c605d735
MT
31import pakfire.packages as packages
32
a2d1644c 33from pakfire.constants import *
ba5dc639 34from pakfire.errors import *
85a1120f 35from pakfire.i18n import _
66af936c 36
47a4cb89 37class 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
102class 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()