]> git.ipfire.org Git - people/stevee/pakfire.git/blame - python/pakfire/repository/database.py
database: Add inst_size column when creating new databases.
[people/stevee/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,
0d6d6fd2 193 inst_size INTEGER,
47a4cb89
MT
194 hash1 TEXT,
195 provides TEXT,
196 requires TEXT,
197 conflicts TEXT,
198 obsoletes TEXT,
a60f0f7d
MT
199 recommends TEXT,
200 suggests TEXT,
47a4cb89
MT
201 license TEXT,
202 summary TEXT,
203 description TEXT,
1317485d 204 uuid TEXT,
85a1120f 205 vendor TEXT,
47a4cb89
MT
206 build_id TEXT,
207 build_host TEXT,
0c665250 208 build_date TEXT,
c605d735
MT
209 build_time INTEGER,
210 installed INT,
211 reason TEXT,
c07a3ca7
MT
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
47a4cb89 227 );
85a1120f 228 """ % DATABASE_FORMAT)
47a4cb89 229 # XXX add some indexes here
47a4cb89
MT
230 self.commit()
231 c.close()
232
85a1120f
MT
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
cabf1fbe
MT
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
4cdde79f
MT
242 log.info(_("Migrating database from format %(old)s to %(new)s.") % \
243 { "old" : self.format, "new" : DATABASE_FORMAT })
85a1120f
MT
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
c2808056
MT
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
cabf1fbe
MT
259 if self.format < 3:
260 c.execute("ALTER TABLE files ADD COLUMN `capabilities` TEXT")
261
a60f0f7d
MT
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
3c5a85f3
MT
266 if self.format < 5:
267 c.execute("ALTER TABLE files ADD COLUMN datafile INTEGER AFTER config")
268
ba5dc639
MT
269 if self.format < 6:
270 c.execute("ALTER TABLE packages ADD COLUMN inst_size INTEGER AFTER size")
271
85a1120f
MT
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
c2808056 276 self.commit()
85a1120f
MT
277 c.close()
278
66af936c 279 def add_package(self, pkg, reason=None):
8b6bc023 280 log.debug("Adding package to database: %s" % pkg.friendly_name)
fa6d335b
MT
281
282 c = self.cursor()
fa6d335b 283
c605d735
MT
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,
ba5dc639 295 inst_size,
c605d735
MT
296 hash1,
297 provides,
298 requires,
299 conflicts,
300 obsoletes,
a60f0f7d
MT
301 recommends,
302 suggests,
c605d735
MT
303 license,
304 summary,
305 description,
306 uuid,
85a1120f 307 vendor,
c605d735
MT
308 build_id,
309 build_host,
310 build_date,
311 build_time,
312 installed,
313 repository,
c07a3ca7 314 reason
ba5dc639 315 ) VALUES(?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)""",
c605d735
MT
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,
ba5dc639 325 pkg.inst_size,
c605d735 326 pkg.hash1,
d92f3c4e
MT
327 "\n".join(pkg.provides),
328 "\n".join(pkg.requires),
329 "\n".join(pkg.conflicts),
330 "\n".join(pkg.obsoletes),
a60f0f7d
MT
331 "\n".join(pkg.recommends),
332 "\n".join(pkg.suggests),
c605d735
MT
333 pkg.license,
334 pkg.summary,
335 pkg.description,
336 pkg.uuid,
85a1120f 337 pkg.vendor or "",
c605d735
MT
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 "",
c605d735
MT
345 )
346 )
66af936c 347
c605d735 348 pkg_id = c.lastrowid
66af936c 349
3c5a85f3
MT
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))
66af936c 353
c605d735
MT
354 except:
355 raise
66af936c 356
c605d735
MT
357 else:
358 self.commit()
66af936c 359
66af936c 360 c.close()
fa6d335b 361
e871a081 362 def rem_package(self, pkg):
8b6bc023 363 log.debug("Removing package from database: %s" % pkg.friendly_name)
e871a081
MT
364
365 assert pkg.uuid
366
367 # Get the ID of the package in the database.
368 c = self.cursor()
b8f51d98
MT
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,))
e871a081 372
9b68f47c
MT
373 row = c.fetchone()
374 if not row:
375 return
376
377 id = row["id"]
e871a081
MT
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
862bea4d
MT
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
c605d735
MT
397 @property
398 def packages(self):
9b68f47c 399 c = self.db.execute("SELECT * FROM packages ORDER BY name")
47a4cb89 400
9b68f47c 401 for row in c.fetchall():
c605d735 402 yield packages.DatabasePackage(self.pakfire, self.repo, self, row)
47a4cb89 403
66af936c 404 c.close()
6ee3d6b9 405
0f8d6745 406 def get_filelist(self):
9b68f47c 407 c = self.db.execute("SELECT name FROM files")
0f8d6745 408
9b68f47c 409 return [r["name"] for r in c.fetchall()]
0f8d6745 410
6ee3d6b9 411 def get_package_from_solv(self, solv_pkg):
9b68f47c
MT
412 assert solv_pkg.uuid
413
414 c = self.db.execute("SELECT * FROM packages WHERE uuid = ? LIMIT 1", (solv_pkg.uuid,))
6ee3d6b9
MT
415
416 try:
9b68f47c
MT
417 row = c.fetchone()
418 if row is None:
419 return
420
421 return packages.DatabasePackage(self.pakfire, self.repo, self, row)
6ee3d6b9
MT
422
423 finally:
424 c.close()