]>
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, |
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() |