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