]> git.ipfire.org Git - pakfire.git/blob - python/pakfire/repository/database.py
database: Save installed package size.
[pakfire.git] / python / 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 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
45 class Database(object):
46 def __init__(self, pakfire, filename):
47 self.pakfire = pakfire
48 self.filename = filename
49
50 self._db = None
51
52 def __del__(self):
53 if self._db:
54 self._db.close()
55 self._db = None
56
57 @property
58 def db(self):
59 if self._db is None:
60 self.open()
61
62 return self._db
63
64 def create(self):
65 pass
66
67 def migrate(self):
68 pass
69
70 def open(self):
71 if self._db is None:
72 log.debug("Open database %s" % self.filename)
73
74 dirname = os.path.dirname(self.filename)
75 if not os.path.exists(dirname):
76 os.makedirs(dirname)
77
78 database_exists = os.path.exists(self.filename)
79
80 # Make a connection to the database.
81 self._db = sqlite3.connect(self.filename)
82 self._db.row_factory = sqlite3.Row
83
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:
90 self.create()
91
92 def close(self):
93 if self._db:
94 self._db.close()
95 self._db = None
96
97 def commit(self):
98 if self._db:
99 self._db.commit()
100
101 def cursor(self):
102 self.open()
103 return self._db.cursor(Cursor)
104
105 def executescript(self, *args, **kwargs):
106 self.open()
107 return self._db.executescript(*args, **kwargs)
108
109
110 class DatabaseLocal(Database):
111 def __init__(self, pakfire, repo):
112 self.repo = repo
113
114 # Generate filename for package database
115 filename = os.path.join(pakfire.path, PACKAGES_DB)
116
117 # Cache format number.
118 self.__format = None
119
120 Database.__init__(self, pakfire, filename)
121
122 def initialize(self):
123 # Open the database.
124 self.open()
125
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
130 def __len__(self):
131 count = 0
132
133 c = self.cursor()
134 c.execute("SELECT COUNT(*) AS count FROM packages")
135 for row in c:
136 count = row["count"]
137 c.close()
138
139 return count
140
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
158 def create(self):
159 c = self.cursor()
160 c.executescript("""
161 CREATE TABLE settings(
162 key TEXT,
163 val TEXT
164 );
165 INSERT INTO settings(key, val) VALUES('version', '%s');
166
167 CREATE TABLE files(
168 id INTEGER PRIMARY KEY,
169 name TEXT,
170 pkg INTEGER,
171 size INTEGER,
172 type INTEGER,
173 config INTEGER,
174 datafile INTEGER,
175 mode INTEGER,
176 user TEXT,
177 `group` TEXT,
178 hash1 TEXT,
179 mtime INTEGER,
180 capabilities TEXT
181 );
182
183 CREATE TABLE packages(
184 id INTEGER PRIMARY KEY,
185 name TEXT,
186 epoch INTEGER,
187 version TEXT,
188 release TEXT,
189 arch TEXT,
190 groups TEXT,
191 filename TEXT,
192 size INTEGER,
193 hash1 TEXT,
194 provides TEXT,
195 requires TEXT,
196 conflicts TEXT,
197 obsoletes TEXT,
198 recommends TEXT,
199 suggests TEXT,
200 license TEXT,
201 summary TEXT,
202 description TEXT,
203 uuid TEXT,
204 vendor TEXT,
205 build_id TEXT,
206 build_host TEXT,
207 build_date TEXT,
208 build_time INTEGER,
209 installed INT,
210 reason TEXT,
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
226 );
227 """ % DATABASE_FORMAT)
228 # XXX add some indexes here
229 self.commit()
230 c.close()
231
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
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
241 log.info(_("Migrating database from format %(old)s to %(new)s.") % \
242 { "old" : self.format, "new" : DATABASE_FORMAT })
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
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
258 if self.format < 3:
259 c.execute("ALTER TABLE files ADD COLUMN `capabilities` TEXT")
260
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
265 if self.format < 5:
266 c.execute("ALTER TABLE files ADD COLUMN datafile INTEGER AFTER config")
267
268 if self.format < 6:
269 c.execute("ALTER TABLE packages ADD COLUMN inst_size INTEGER AFTER size")
270
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
275 self.commit()
276 c.close()
277
278 def add_package(self, pkg, reason=None):
279 log.debug("Adding package to database: %s" % pkg.friendly_name)
280
281 c = self.cursor()
282
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,
294 inst_size,
295 hash1,
296 provides,
297 requires,
298 conflicts,
299 obsoletes,
300 recommends,
301 suggests,
302 license,
303 summary,
304 description,
305 uuid,
306 vendor,
307 build_id,
308 build_host,
309 build_date,
310 build_time,
311 installed,
312 repository,
313 reason
314 ) VALUES(?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)""",
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,
324 pkg.inst_size,
325 pkg.hash1,
326 "\n".join(pkg.provides),
327 "\n".join(pkg.requires),
328 "\n".join(pkg.conflicts),
329 "\n".join(pkg.obsoletes),
330 "\n".join(pkg.recommends),
331 "\n".join(pkg.suggests),
332 pkg.license,
333 pkg.summary,
334 pkg.description,
335 pkg.uuid,
336 pkg.vendor or "",
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 "",
344 )
345 )
346
347 pkg_id = c.lastrowid
348
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))
352
353 except:
354 raise
355
356 else:
357 self.commit()
358
359 c.close()
360
361 def rem_package(self, pkg):
362 log.debug("Removing package from database: %s" % pkg.friendly_name)
363
364 assert pkg.uuid
365
366 # Get the ID of the package in the database.
367 c = self.cursor()
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,))
371
372 row = c.fetchone()
373 if not row:
374 return
375
376 id = row["id"]
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
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
396 @property
397 def packages(self):
398 c = self.db.execute("SELECT * FROM packages ORDER BY name")
399
400 for row in c.fetchall():
401 yield packages.DatabasePackage(self.pakfire, self.repo, self, row)
402
403 c.close()
404
405 def get_filelist(self):
406 c = self.db.execute("SELECT name FROM files")
407
408 return [r["name"] for r in c.fetchall()]
409
410 def get_package_from_solv(self, solv_pkg):
411 assert solv_pkg.uuid
412
413 c = self.db.execute("SELECT * FROM packages WHERE uuid = ? LIMIT 1", (solv_pkg.uuid,))
414
415 try:
416 row = c.fetchone()
417 if row is None:
418 return
419
420 return packages.DatabasePackage(self.pakfire, self.repo, self, row)
421
422 finally:
423 c.close()