]> git.ipfire.org Git - people/stevee/pakfire.git/blob - python/pakfire/repository/database.py
database: Add inst_size column when creating new databases.
[people/stevee/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 inst_size INTEGER,
194 hash1 TEXT,
195 provides TEXT,
196 requires TEXT,
197 conflicts TEXT,
198 obsoletes TEXT,
199 recommends TEXT,
200 suggests TEXT,
201 license TEXT,
202 summary TEXT,
203 description TEXT,
204 uuid TEXT,
205 vendor TEXT,
206 build_id TEXT,
207 build_host TEXT,
208 build_date TEXT,
209 build_time INTEGER,
210 installed INT,
211 reason TEXT,
212 repository TEXT
213 );
214
215 CREATE TABLE scriptlets(
216 id INTEGER PRIMARY KEY,
217 pkg INTEGER,
218 action TEXT,
219 scriptlet TEXT
220 );
221
222 CREATE TABLE triggers(
223 id INTEGER PRIMARY KEY,
224 pkg INTEGER,
225 dependency TEXT,
226 scriptlet TEXT
227 );
228 """ % DATABASE_FORMAT)
229 # XXX add some indexes here
230 self.commit()
231 c.close()
232
233 def migrate(self):
234 # If we have already the latest version, there is nothing to do.
235 if self.format == DATABASE_FORMAT:
236 return
237
238 # Check if database version is supported.
239 if self.format > DATABASE_FORMAT:
240 raise DatabaseError, _("Cannot use database with version greater than %s.") % DATABASE_FORMAT
241
242 log.info(_("Migrating database from format %(old)s to %(new)s.") % \
243 { "old" : self.format, "new" : DATABASE_FORMAT })
244
245 # Get a database cursor.
246 c = self.cursor()
247
248 # 1) The vendor column was added.
249 if self.format < 1:
250 c.execute("ALTER TABLE packages ADD COLUMN vendor TEXT AFTER uuid")
251
252 if self.format < 2:
253 c.execute("ALTER TABLE files ADD COLUMN `config` INTEGER")
254 c.execute("ALTER TABLE files ADD COLUMN `mode` INTEGER")
255 c.execute("ALTER TABLE files ADD COLUMN `user` TEXT")
256 c.execute("ALTER TABLE files ADD COLUMN `group` TEXT")
257 c.execute("ALTER TABLE files ADD COLUMN `mtime` INTEGER")
258
259 if self.format < 3:
260 c.execute("ALTER TABLE files ADD COLUMN `capabilities` TEXT")
261
262 if self.format < 4:
263 c.execute("ALTER TABLE packages ADD COLUMN recommends TEXT AFTER obsoletes")
264 c.execute("ALTER TABLE packages ADD COLUMN suggests TEXT AFTER recommends")
265
266 if self.format < 5:
267 c.execute("ALTER TABLE files ADD COLUMN datafile INTEGER AFTER config")
268
269 if self.format < 6:
270 c.execute("ALTER TABLE packages ADD COLUMN inst_size INTEGER AFTER size")
271
272 # In the end, we can easily update the version of the database.
273 c.execute("UPDATE settings SET val = ? WHERE key = 'version'", (DATABASE_FORMAT,))
274 self.__format = DATABASE_FORMAT
275
276 self.commit()
277 c.close()
278
279 def add_package(self, pkg, reason=None):
280 log.debug("Adding package to database: %s" % pkg.friendly_name)
281
282 c = self.cursor()
283
284 try:
285 c.execute("""
286 INSERT INTO packages(
287 name,
288 epoch,
289 version,
290 release,
291 arch,
292 groups,
293 filename,
294 size,
295 inst_size,
296 hash1,
297 provides,
298 requires,
299 conflicts,
300 obsoletes,
301 recommends,
302 suggests,
303 license,
304 summary,
305 description,
306 uuid,
307 vendor,
308 build_id,
309 build_host,
310 build_date,
311 build_time,
312 installed,
313 repository,
314 reason
315 ) VALUES(?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)""",
316 (
317 pkg.name,
318 pkg.epoch,
319 pkg.version,
320 pkg.release,
321 pkg.arch,
322 " ".join(pkg.groups),
323 pkg.filename,
324 pkg.size,
325 pkg.inst_size,
326 pkg.hash1,
327 "\n".join(pkg.provides),
328 "\n".join(pkg.requires),
329 "\n".join(pkg.conflicts),
330 "\n".join(pkg.obsoletes),
331 "\n".join(pkg.recommends),
332 "\n".join(pkg.suggests),
333 pkg.license,
334 pkg.summary,
335 pkg.description,
336 pkg.uuid,
337 pkg.vendor or "",
338 pkg.build_id,
339 pkg.build_host,
340 pkg.build_date,
341 pkg.build_time,
342 time.time(),
343 pkg.repo.name,
344 reason or "",
345 )
346 )
347
348 pkg_id = c.lastrowid
349
350 c.executemany("INSERT INTO files(`name`, `pkg`, `size`, `config`, `datafile`, `type`, `hash1`, `mode`, `user`, `group`, `mtime`, `capabilities`)"
351 " VALUES(?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)",
352 ((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))
353
354 except:
355 raise
356
357 else:
358 self.commit()
359
360 c.close()
361
362 def rem_package(self, pkg):
363 log.debug("Removing package from database: %s" % pkg.friendly_name)
364
365 assert pkg.uuid
366
367 # Get the ID of the package in the database.
368 c = self.cursor()
369 c.execute("SELECT id FROM packages WHERE uuid = ? LIMIT 1", (pkg.uuid,))
370 #c.execute("SELECT id FROM packages WHERE name = ? AND epoch = ? AND version = ?"
371 # " AND release = ? LIMIT 1", (pkg.name, pkg.epoch, pkg.version, pkg.release,))
372
373 row = c.fetchone()
374 if not row:
375 return
376
377 id = row["id"]
378
379 # First, delete all files from the database and then delete the pkg itself.
380 c.execute("DELETE FROM files WHERE pkg = ?", (id,))
381 c.execute("DELETE FROM packages WHERE id = ?", (id,))
382
383 c.close()
384 self.commit()
385
386 def get_package_by_id(self, id):
387 c = self.cursor()
388 c.execute("SELECT * FROM packages WHERE id = ?", (id,))
389
390 try:
391 for row in c:
392 return packages.DatabasePackage(self.pakfire, self.repo, self, row)
393
394 finally:
395 c.close()
396
397 @property
398 def packages(self):
399 c = self.db.execute("SELECT * FROM packages ORDER BY name")
400
401 for row in c.fetchall():
402 yield packages.DatabasePackage(self.pakfire, self.repo, self, row)
403
404 c.close()
405
406 def get_filelist(self):
407 c = self.db.execute("SELECT name FROM files")
408
409 return [r["name"] for r in c.fetchall()]
410
411 def get_package_from_solv(self, solv_pkg):
412 assert solv_pkg.uuid
413
414 c = self.db.execute("SELECT * FROM packages WHERE uuid = ? LIMIT 1", (solv_pkg.uuid,))
415
416 try:
417 row = c.fetchone()
418 if row is None:
419 return
420
421 return packages.DatabasePackage(self.pakfire, self.repo, self, row)
422
423 finally:
424 c.close()