]>
Commit | Line | Data |
---|---|---|
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 | 22 | import os |
5cc35aa4 | 23 | import random |
2568a6d1 | 24 | import shutil |
47a4cb89 | 25 | import sqlite3 |
66af936c | 26 | import time |
47a4cb89 | 27 | |
8b6bc023 MT |
28 | import logging |
29 | log = logging.getLogger("pakfire") | |
30 | ||
c605d735 MT |
31 | import pakfire.packages as packages |
32 | ||
a2d1644c | 33 | from pakfire.constants import * |
ba5dc639 | 34 | from pakfire.errors import * |
85a1120f | 35 | from pakfire.i18n import _ |
66af936c | 36 | |
b6da0663 MT |
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 | ||
47a4cb89 | 45 | class 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 |
110 | class 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() |