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