]> git.ipfire.org Git - pakfire.git/blame - python/pakfire/repository/database.py
database: Save installed package size.
[pakfire.git] / python / 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
b6da0663
MT
37class Cursor(sqlite3.Cursor):
38 def execute(self, *args, **kwargs):
39 # For debugging of SQL queries.
40 #print args, kwargs
41
42 return sqlite3.Cursor.execute(self, *args, **kwargs)
43
44
47a4cb89 45class Database(object):
3723913b
MT
46 def __init__(self, pakfire, filename):
47 self.pakfire = pakfire
5cc35aa4
MT
48 self.filename = filename
49
c605d735 50 self._db = None
47a4cb89
MT
51
52 def __del__(self):
53 if self._db:
47a4cb89 54 self._db.close()
c605d735 55 self._db = None
47a4cb89 56
9b68f47c
MT
57 @property
58 def db(self):
59 if self._db is None:
60 self.open()
61
62 return self._db
63
47a4cb89
MT
64 def create(self):
65 pass
66
85a1120f
MT
67 def migrate(self):
68 pass
69
47a4cb89 70 def open(self):
c605d735 71 if self._db is None:
8b6bc023 72 log.debug("Open database %s" % self.filename)
47a4cb89 73
5cc35aa4
MT
74 dirname = os.path.dirname(self.filename)
75 if not os.path.exists(dirname):
76 os.makedirs(dirname)
47a4cb89 77
5cc35aa4 78 database_exists = os.path.exists(self.filename)
47a4cb89
MT
79
80 # Make a connection to the database.
81 self._db = sqlite3.connect(self.filename)
82 self._db.row_factory = sqlite3.Row
83
85a1120f
MT
84 # In the case, the database was not existant, it is
85 # filled with content. In case it has been there
86 # we call the migrate method to update it if neccessary.
87 if database_exists:
88 self.migrate()
89 else:
47a4cb89
MT
90 self.create()
91
92 def close(self):
0f8d6745
MT
93 if self._db:
94 self._db.close()
95 self._db = None
5cc35aa4 96
47a4cb89 97 def commit(self):
0f8d6745
MT
98 if self._db:
99 self._db.commit()
47a4cb89
MT
100
101 def cursor(self):
c605d735 102 self.open()
b6da0663 103 return self._db.cursor(Cursor)
47a4cb89 104
a2d1644c 105 def executescript(self, *args, **kwargs):
c605d735 106 self.open()
a2d1644c
MT
107 return self._db.executescript(*args, **kwargs)
108
2568a6d1 109
c605d735
MT
110class DatabaseLocal(Database):
111 def __init__(self, pakfire, repo):
112 self.repo = repo
2568a6d1 113
c605d735
MT
114 # Generate filename for package database
115 filename = os.path.join(pakfire.path, PACKAGES_DB)
116
85a1120f
MT
117 # Cache format number.
118 self.__format = None
119
c605d735
MT
120 Database.__init__(self, pakfire, filename)
121
36b328f2
MT
122 def initialize(self):
123 # Open the database.
124 self.open()
125
85a1120f
MT
126 # Check if we actually can open the database.
127 if not self.format in DATABASE_FORMATS_SUPPORTED:
128 raise DatabaseFormatError, _("The format of the database is not supported by this version of pakfire.")
129
c605d735
MT
130 def __len__(self):
131 count = 0
47a4cb89 132
47a4cb89 133 c = self.cursor()
c605d735
MT
134 c.execute("SELECT COUNT(*) AS count FROM packages")
135 for row in c:
136 count = row["count"]
137 c.close()
47a4cb89 138
c605d735
MT
139 return count
140
85a1120f
MT
141 @property
142 def format(self):
143 if self.__format is None:
144 c = self.cursor()
145
146 c.execute("SELECT val FROM settings WHERE key = 'version' LIMIT 1")
147 for row in c:
148 try:
149 self.__format = int(row["val"])
150 break
151 except ValueError:
152 pass
153
154 c.close()
155
156 return self.__format
157
c605d735
MT
158 def create(self):
159 c = self.cursor()
47a4cb89 160 c.executescript("""
c605d735
MT
161 CREATE TABLE settings(
162 key TEXT,
163 val TEXT
164 );
85a1120f 165 INSERT INTO settings(key, val) VALUES('version', '%s');
c605d735 166
47a4cb89 167 CREATE TABLE files(
c2808056 168 id INTEGER PRIMARY KEY,
47a4cb89 169 name TEXT,
c2808056 170 pkg INTEGER,
47a4cb89
MT
171 size INTEGER,
172 type INTEGER,
c2808056 173 config INTEGER,
3c5a85f3 174 datafile INTEGER,
c2808056
MT
175 mode INTEGER,
176 user TEXT,
177 `group` TEXT,
178 hash1 TEXT,
cabf1fbe
MT
179 mtime INTEGER,
180 capabilities TEXT
47a4cb89
MT
181 );
182
183 CREATE TABLE packages(
c2808056 184 id INTEGER PRIMARY KEY,
47a4cb89
MT
185 name TEXT,
186 epoch INTEGER,
187 version TEXT,
188 release TEXT,
c560c27a 189 arch TEXT,
8537c16d 190 groups TEXT,
fa6d335b 191 filename TEXT,
ba8c383d 192 size INTEGER,
47a4cb89
MT
193 hash1 TEXT,
194 provides TEXT,
195 requires TEXT,
196 conflicts TEXT,
197 obsoletes TEXT,
a60f0f7d
MT
198 recommends TEXT,
199 suggests TEXT,
47a4cb89
MT
200 license TEXT,
201 summary TEXT,
202 description TEXT,
1317485d 203 uuid TEXT,
85a1120f 204 vendor TEXT,
47a4cb89
MT
205 build_id TEXT,
206 build_host TEXT,
0c665250 207 build_date TEXT,
c605d735
MT
208 build_time INTEGER,
209 installed INT,
210 reason TEXT,
c07a3ca7
MT
211 repository TEXT
212 );
213
214 CREATE TABLE scriptlets(
215 id INTEGER PRIMARY KEY,
216 pkg INTEGER,
217 action TEXT,
218 scriptlet TEXT
219 );
220
221 CREATE TABLE triggers(
222 id INTEGER PRIMARY KEY,
223 pkg INTEGER,
224 dependency TEXT,
225 scriptlet TEXT
47a4cb89 226 );
85a1120f 227 """ % DATABASE_FORMAT)
47a4cb89 228 # XXX add some indexes here
47a4cb89
MT
229 self.commit()
230 c.close()
231
85a1120f
MT
232 def migrate(self):
233 # If we have already the latest version, there is nothing to do.
234 if self.format == DATABASE_FORMAT:
235 return
236
cabf1fbe
MT
237 # Check if database version is supported.
238 if self.format > DATABASE_FORMAT:
239 raise DatabaseError, _("Cannot use database with version greater than %s.") % DATABASE_FORMAT
240
4cdde79f
MT
241 log.info(_("Migrating database from format %(old)s to %(new)s.") % \
242 { "old" : self.format, "new" : DATABASE_FORMAT })
85a1120f
MT
243
244 # Get a database cursor.
245 c = self.cursor()
246
247 # 1) The vendor column was added.
248 if self.format < 1:
249 c.execute("ALTER TABLE packages ADD COLUMN vendor TEXT AFTER uuid")
250
c2808056
MT
251 if self.format < 2:
252 c.execute("ALTER TABLE files ADD COLUMN `config` INTEGER")
253 c.execute("ALTER TABLE files ADD COLUMN `mode` INTEGER")
254 c.execute("ALTER TABLE files ADD COLUMN `user` TEXT")
255 c.execute("ALTER TABLE files ADD COLUMN `group` TEXT")
256 c.execute("ALTER TABLE files ADD COLUMN `mtime` INTEGER")
257
cabf1fbe
MT
258 if self.format < 3:
259 c.execute("ALTER TABLE files ADD COLUMN `capabilities` TEXT")
260
a60f0f7d
MT
261 if self.format < 4:
262 c.execute("ALTER TABLE packages ADD COLUMN recommends TEXT AFTER obsoletes")
263 c.execute("ALTER TABLE packages ADD COLUMN suggests TEXT AFTER recommends")
264
3c5a85f3
MT
265 if self.format < 5:
266 c.execute("ALTER TABLE files ADD COLUMN datafile INTEGER AFTER config")
267
ba5dc639
MT
268 if self.format < 6:
269 c.execute("ALTER TABLE packages ADD COLUMN inst_size INTEGER AFTER size")
270
85a1120f
MT
271 # In the end, we can easily update the version of the database.
272 c.execute("UPDATE settings SET val = ? WHERE key = 'version'", (DATABASE_FORMAT,))
273 self.__format = DATABASE_FORMAT
274
c2808056 275 self.commit()
85a1120f
MT
276 c.close()
277
66af936c 278 def add_package(self, pkg, reason=None):
8b6bc023 279 log.debug("Adding package to database: %s" % pkg.friendly_name)
fa6d335b
MT
280
281 c = self.cursor()
fa6d335b 282
c605d735
MT
283 try:
284 c.execute("""
285 INSERT INTO packages(
286 name,
287 epoch,
288 version,
289 release,
290 arch,
291 groups,
292 filename,
293 size,
ba5dc639 294 inst_size,
c605d735
MT
295 hash1,
296 provides,
297 requires,
298 conflicts,
299 obsoletes,
a60f0f7d
MT
300 recommends,
301 suggests,
c605d735
MT
302 license,
303 summary,
304 description,
305 uuid,
85a1120f 306 vendor,
c605d735
MT
307 build_id,
308 build_host,
309 build_date,
310 build_time,
311 installed,
312 repository,
c07a3ca7 313 reason
ba5dc639 314 ) VALUES(?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)""",
c605d735
MT
315 (
316 pkg.name,
317 pkg.epoch,
318 pkg.version,
319 pkg.release,
320 pkg.arch,
321 " ".join(pkg.groups),
322 pkg.filename,
323 pkg.size,
ba5dc639 324 pkg.inst_size,
c605d735 325 pkg.hash1,
d92f3c4e
MT
326 "\n".join(pkg.provides),
327 "\n".join(pkg.requires),
328 "\n".join(pkg.conflicts),
329 "\n".join(pkg.obsoletes),
a60f0f7d
MT
330 "\n".join(pkg.recommends),
331 "\n".join(pkg.suggests),
c605d735
MT
332 pkg.license,
333 pkg.summary,
334 pkg.description,
335 pkg.uuid,
85a1120f 336 pkg.vendor or "",
c605d735
MT
337 pkg.build_id,
338 pkg.build_host,
339 pkg.build_date,
340 pkg.build_time,
341 time.time(),
342 pkg.repo.name,
343 reason or "",
c605d735
MT
344 )
345 )
66af936c 346
c605d735 347 pkg_id = c.lastrowid
66af936c 348
3c5a85f3
MT
349 c.executemany("INSERT INTO files(`name`, `pkg`, `size`, `config`, `datafile`, `type`, `hash1`, `mode`, `user`, `group`, `mtime`, `capabilities`)"
350 " VALUES(?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)",
351 ((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 352
c605d735
MT
353 except:
354 raise
66af936c 355
c605d735
MT
356 else:
357 self.commit()
66af936c 358
66af936c 359 c.close()
fa6d335b 360
e871a081 361 def rem_package(self, pkg):
8b6bc023 362 log.debug("Removing package from database: %s" % pkg.friendly_name)
e871a081
MT
363
364 assert pkg.uuid
365
366 # Get the ID of the package in the database.
367 c = self.cursor()
b8f51d98
MT
368 c.execute("SELECT id FROM packages WHERE uuid = ? LIMIT 1", (pkg.uuid,))
369 #c.execute("SELECT id FROM packages WHERE name = ? AND epoch = ? AND version = ?"
370 # " AND release = ? LIMIT 1", (pkg.name, pkg.epoch, pkg.version, pkg.release,))
e871a081 371
9b68f47c
MT
372 row = c.fetchone()
373 if not row:
374 return
375
376 id = row["id"]
e871a081
MT
377
378 # First, delete all files from the database and then delete the pkg itself.
379 c.execute("DELETE FROM files WHERE pkg = ?", (id,))
380 c.execute("DELETE FROM packages WHERE id = ?", (id,))
381
382 c.close()
383 self.commit()
384
862bea4d
MT
385 def get_package_by_id(self, id):
386 c = self.cursor()
387 c.execute("SELECT * FROM packages WHERE id = ?", (id,))
388
389 try:
390 for row in c:
391 return packages.DatabasePackage(self.pakfire, self.repo, self, row)
392
393 finally:
394 c.close()
395
c605d735
MT
396 @property
397 def packages(self):
9b68f47c 398 c = self.db.execute("SELECT * FROM packages ORDER BY name")
47a4cb89 399
9b68f47c 400 for row in c.fetchall():
c605d735 401 yield packages.DatabasePackage(self.pakfire, self.repo, self, row)
47a4cb89 402
66af936c 403 c.close()
6ee3d6b9 404
0f8d6745 405 def get_filelist(self):
9b68f47c 406 c = self.db.execute("SELECT name FROM files")
0f8d6745 407
9b68f47c 408 return [r["name"] for r in c.fetchall()]
0f8d6745 409
6ee3d6b9 410 def get_package_from_solv(self, solv_pkg):
9b68f47c
MT
411 assert solv_pkg.uuid
412
413 c = self.db.execute("SELECT * FROM packages WHERE uuid = ? LIMIT 1", (solv_pkg.uuid,))
6ee3d6b9
MT
414
415 try:
9b68f47c
MT
416 row = c.fetchone()
417 if row is None:
418 return
419
420 return packages.DatabasePackage(self.pakfire, self.repo, self, row)
6ee3d6b9
MT
421
422 finally:
423 c.close()