Imported Upstream version 1.1.4
[tools/yum-metadata-parser.git] / sqlitecache.c
1 /* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
2
3 /* This program is free software; you can redistribute it and/or
4  * modify it under the terms of the GNU General Public License,
5  * version 2, as published by the Free Software Foundation
6  *
7  * This program is distributed in the hope that it will be useful, but
8  * WITHOUT ANY WARRANTY; without even the implied warranty of
9  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
10  * General Public License for more details.
11  *
12  * You should have received a copy of the GNU General Public License
13  * along with this program; if not, write to the Free Software
14  * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
15  * 02111-1307, USA.
16  */
17
18 #include <Python.h>
19
20 #include "xml-parser.h"
21 #include "db.h"
22 #include "package.h"
23
24 /* Make room for 2500 package ids, 40 bytes + '\0' each */
25 #define PACKAGE_IDS_CHUNK 41 * 2500
26
27 typedef struct _UpdateInfo UpdateInfo;
28
29 typedef void (*InfoInitFn) (UpdateInfo *update_info, sqlite3 *db, GError **err);
30 typedef void (*InfoCleanFn) (UpdateInfo *update_info);
31
32 typedef void (*XmlParseFn)  (const char *filename,
33                              CountFn count_callback,
34                              PackageFn package_callback,
35                              gpointer user_data,
36                              GError **err);
37
38 typedef void (*WriteDbPackageFn) (UpdateInfo *update_info, Package *package);
39
40 typedef void (*IndexTablesFn) (sqlite3 *db, GError **err);
41
42 struct _UpdateInfo {
43     sqlite3 *db;
44     sqlite3_stmt *remove_handle;
45     guint32 count_from_md;
46     guint32 packages_seen;
47     guint32 add_count;
48     guint32 del_count;
49     GHashTable *current_packages;
50     GHashTable *all_packages;
51     GStringChunk *package_ids_chunk;
52     GTimer *timer;
53     gpointer python_callback;
54     
55     InfoInitFn info_init;
56     InfoCleanFn info_clean;
57     CreateTablesFn create_tables;
58     WriteDbPackageFn write_package;
59     XmlParseFn xml_parse;
60     IndexTablesFn index_tables;
61
62     gpointer user_data;
63 };
64
65 static void
66 update_info_init (UpdateInfo *info, GError **err)
67 {
68     const char *sql;
69     int rc;
70
71     sql = "DELETE FROM packages WHERE pkgKey = ?";
72     rc = sqlite3_prepare (info->db, sql, -1, &info->remove_handle, NULL);
73     if (rc != SQLITE_OK) {
74         g_set_error (err, YUM_DB_ERROR, YUM_DB_ERROR,
75                      "Can not prepare package removal: %s",
76                      sqlite3_errmsg (info->db));
77         sqlite3_finalize (info->remove_handle);
78         return;
79     }
80
81     info->count_from_md = 0;
82     info->packages_seen = 0;
83     info->add_count = 0;
84     info->del_count = 0;
85     info->all_packages = g_hash_table_new (g_str_hash, g_str_equal);
86     info->package_ids_chunk = g_string_chunk_new (PACKAGE_IDS_CHUNK);
87     info->timer = g_timer_new ();
88     g_timer_start (info->timer);
89     info->current_packages = yum_db_read_package_ids (info->db, err);
90 }
91
92 static void
93 remove_entry (gpointer key, gpointer value, gpointer user_data)
94 {
95     UpdateInfo *info = (UpdateInfo *) user_data;
96
97     if (g_hash_table_lookup (info->all_packages, key) == NULL) {
98         int rc;
99
100         sqlite3_bind_int (info->remove_handle, 1, GPOINTER_TO_INT (value));
101         rc = sqlite3_step (info->remove_handle);
102         sqlite3_reset (info->remove_handle);
103
104         if (rc != SQLITE_DONE)
105             g_warning ("Error removing package from SQL: %s",
106                        sqlite3_errmsg (info->db));
107
108         info->del_count++;
109     }
110 }
111
112 static void
113 update_info_remove_old_entries (UpdateInfo *info)
114 {
115     g_hash_table_foreach (info->current_packages, remove_entry, info);
116 }
117
118 static void
119 count_cb (guint32 count, gpointer user_data)
120 {
121     UpdateInfo *info = (UpdateInfo *) user_data;
122
123     info->count_from_md = count;
124 }
125
126 static void
127 update_info_done (UpdateInfo *info, GError **err)
128 {
129     if (info->remove_handle)
130         sqlite3_finalize (info->remove_handle);
131     if (info->current_packages)
132         g_hash_table_destroy (info->current_packages);
133     if (info->all_packages)
134         g_hash_table_destroy (info->all_packages);
135     if (info->package_ids_chunk)
136         g_string_chunk_free (info->package_ids_chunk);
137
138     g_timer_stop (info->timer);
139     if (!*err) {
140         g_message ("Added %d new packages, deleted %d old in %.2f seconds",
141                    info->add_count, info->del_count,
142                    g_timer_elapsed (info->timer, NULL));
143     }
144
145     g_timer_destroy (info->timer);
146 }
147
148
149 /* Primary */
150
151 typedef struct {
152     UpdateInfo update_info;
153     sqlite3_stmt *pkg_handle;
154     sqlite3_stmt *requires_handle;
155     sqlite3_stmt *provides_handle;
156     sqlite3_stmt *conflicts_handle;
157     sqlite3_stmt *obsoletes_handle;
158     sqlite3_stmt *files_handle;
159 } PackageWriterInfo;
160
161 static void
162 package_writer_info_init (UpdateInfo *update_info, sqlite3 *db, GError **err)
163 {
164     PackageWriterInfo *info = (PackageWriterInfo *) update_info;
165
166     info->pkg_handle = yum_db_package_prepare (db, err);
167     if (*err)
168         return;
169     info->requires_handle = yum_db_dependency_prepare (db, "requires", err);
170     if (*err)
171         return;
172     info->provides_handle = yum_db_dependency_prepare (db, "provides", err);
173     if (*err)
174         return;
175     info->conflicts_handle = yum_db_dependency_prepare (db, "conflicts", err);
176     if (*err)
177         return;
178     info->obsoletes_handle = yum_db_dependency_prepare (db, "obsoletes", err);
179     if (*err)
180         return;
181     info->files_handle = yum_db_file_prepare (db, err);
182 }
183
184 static void
185 write_deps (sqlite3 *db, sqlite3_stmt *handle, gint64 pkgKey, 
186             GSList *deps)
187 {
188     GSList *iter;
189
190     for (iter = deps; iter; iter = iter->next)
191         yum_db_dependency_write (db, handle, pkgKey, (Dependency *) iter->data,
192                                  FALSE);
193 }
194
195 static void
196 write_requirements (sqlite3 *db, sqlite3_stmt *handle, gint64 pkgKey,
197             GSList *deps)
198 {
199     GSList *iter;
200
201     for (iter = deps; iter; iter = iter->next)
202         yum_db_dependency_write (db, handle, pkgKey, (Dependency *) iter->data,
203                                  TRUE);
204 }
205
206
207 static void
208 write_files (sqlite3 *db, sqlite3_stmt *handle, Package *pkg)
209 {
210     GSList *iter;
211
212     for (iter = pkg->files; iter; iter = iter->next)
213         yum_db_file_write (db, handle, pkg->pkgKey,
214                            (PackageFile *) iter->data);
215 }
216
217 static void
218 write_package_to_db (UpdateInfo *update_info, Package *package)
219 {
220     PackageWriterInfo *info = (PackageWriterInfo *) update_info;
221
222     yum_db_package_write (update_info->db, info->pkg_handle, package);
223
224     write_requirements (update_info->db, info->requires_handle,
225                     package->pkgKey, package->requires);
226     write_deps (update_info->db, info->provides_handle,
227                 package->pkgKey, package->provides);
228     write_deps (update_info->db, info->conflicts_handle,
229                 package->pkgKey, package->conflicts);
230     write_deps (update_info->db, info->obsoletes_handle,
231                 package->pkgKey, package->obsoletes);
232
233     write_files (update_info->db, info->files_handle, package);
234 }
235
236 static void
237 package_writer_info_clean (UpdateInfo *update_info)
238 {
239     PackageWriterInfo *info = (PackageWriterInfo *) update_info;
240     
241     if (info->pkg_handle)
242         sqlite3_finalize (info->pkg_handle);
243     if (info->requires_handle)
244         sqlite3_finalize (info->requires_handle);
245     if (info->provides_handle)
246         sqlite3_finalize (info->provides_handle);
247     if (info->conflicts_handle)
248         sqlite3_finalize (info->conflicts_handle);
249     if (info->obsoletes_handle)
250         sqlite3_finalize (info->obsoletes_handle);
251     if (info->files_handle)
252         sqlite3_finalize (info->files_handle);
253 }
254
255
256 /* Filelists */
257
258 typedef struct {
259     UpdateInfo update_info;
260     sqlite3_stmt *pkg_handle;
261     sqlite3_stmt *file_handle;
262 } FileListInfo;
263
264 static void
265 update_filelist_info_init (UpdateInfo *update_info, sqlite3 *db, GError **err)
266 {
267     FileListInfo *info = (FileListInfo *) update_info;
268
269     info->pkg_handle = yum_db_package_ids_prepare (db, err);
270     if (*err)
271         return;
272
273     info->file_handle = yum_db_filelists_prepare (db, err);
274 }
275
276 static void
277 update_filelist_info_clean (UpdateInfo *update_info)
278 {
279     FileListInfo *info = (FileListInfo *) update_info;
280
281     if (info->pkg_handle)
282         sqlite3_finalize (info->pkg_handle);
283     if (info->file_handle)
284         sqlite3_finalize (info->file_handle);
285 }
286
287 static void
288 write_filelist_package_to_db (UpdateInfo *update_info, Package *package)
289 {
290     FileListInfo *info = (FileListInfo *) update_info;
291
292     yum_db_package_ids_write (update_info->db, info->pkg_handle, package);
293     yum_db_filelists_write (update_info->db, info->file_handle, package);
294 }
295
296
297 /* Other */
298
299 typedef struct {
300     UpdateInfo update_info;
301     sqlite3_stmt *pkg_handle;
302     sqlite3_stmt *changelog_handle;
303 } UpdateOtherInfo;
304
305 static void
306 update_other_info_init (UpdateInfo *update_info, sqlite3 *db, GError **err)
307 {
308     UpdateOtherInfo *info = (UpdateOtherInfo *) update_info;
309     info->pkg_handle = yum_db_package_ids_prepare (db, err);
310     if (*err)
311         return;
312
313     info->changelog_handle = yum_db_changelog_prepare (db, err);
314 }
315
316 static void
317 update_other_info_clean (UpdateInfo *update_info)
318 {
319     UpdateOtherInfo *info = (UpdateOtherInfo *) update_info;
320
321     if (info->pkg_handle)
322         sqlite3_finalize (info->pkg_handle);
323     if (info->changelog_handle)
324         sqlite3_finalize (info->changelog_handle);
325 }
326
327 static void
328 write_other_package_to_db (UpdateInfo *update_info, Package *package)
329 {
330     UpdateOtherInfo *info = (UpdateOtherInfo *) update_info;
331
332     yum_db_package_ids_write (update_info->db, info->pkg_handle, package);
333     yum_db_changelog_write (update_info->db, info->changelog_handle, package);
334 }
335
336
337 /*****************************************************************************/
338
339 static void
340 progress_cb (UpdateInfo *update_info)
341 {
342     PyObject *progress = (PyObject *) update_info->python_callback;
343     PyObject *repoid = (PyObject *) update_info->user_data;
344     PyObject *args;
345     PyObject *result;
346
347     Py_INCREF(repoid);
348    
349     args = PyTuple_New (3);
350     PyTuple_SET_ITEM (args, 0, PyInt_FromLong (update_info->packages_seen));
351     PyTuple_SET_ITEM (args, 1, PyInt_FromLong (update_info->count_from_md));
352     PyTuple_SET_ITEM (args, 2, repoid);
353
354     result = PyEval_CallObject (progress, args);
355     Py_DECREF (args);
356     Py_XDECREF (result);
357 }
358
359 static void
360 update_package_cb (Package *p, gpointer user_data)
361 {
362     UpdateInfo *update_info = (UpdateInfo *) user_data;
363
364     /* TODO: Wire in logging of skipped packages */
365     if (p->pkgId == NULL) {
366         return;
367     }
368
369     g_hash_table_insert (update_info->all_packages,
370                          g_string_chunk_insert (update_info->package_ids_chunk,
371                                                 p->pkgId),
372                          GINT_TO_POINTER (1));
373
374     if (g_hash_table_lookup (update_info->current_packages,
375                              p->pkgId) == NULL) {
376         
377         update_info->write_package (update_info, p);
378         update_info->add_count++;
379     }
380
381     if (update_info->count_from_md > 0 && update_info->python_callback) {
382         update_info->packages_seen++;
383         progress_cb (update_info);
384     }
385 }
386
387 static char *
388 update_packages (UpdateInfo *update_info,
389                  const char *md_filename,
390                  const char *checksum,
391                  gpointer python_callback,
392                  gpointer user_data,
393                  GError **err)
394 {
395     char *db_filename;
396
397     db_filename = yum_db_filename (md_filename);
398     update_info->db = yum_db_open (db_filename, checksum,
399                                    update_info->create_tables,
400                                    err);
401
402     if (*err)
403         goto cleanup;
404
405     if (!update_info->db)
406         return db_filename;
407
408     update_info_init (update_info, err);
409     if (*err)
410         goto cleanup;
411     
412     update_info->python_callback = python_callback;
413     update_info->user_data = user_data;
414
415     update_info->info_init (update_info, update_info->db, err);
416     if (*err)
417         goto cleanup;
418
419     sqlite3_exec (update_info->db, "BEGIN", NULL, NULL, NULL);
420     update_info->xml_parse (md_filename,
421                             count_cb,
422                             update_package_cb,
423                             update_info,
424                             err);
425     if (*err)
426         goto cleanup;
427     sqlite3_exec (update_info->db, "COMMIT", NULL, NULL, NULL);
428
429     update_info->index_tables (update_info->db, err);
430     if (*err)
431         goto cleanup;
432
433     update_info_remove_old_entries (update_info);
434     yum_db_dbinfo_update (update_info->db, checksum, err);
435
436  cleanup:
437     update_info->info_clean (update_info);
438     update_info_done (update_info, err);
439
440     if (update_info->db)
441         sqlite3_close (update_info->db);
442
443     if (*err) {
444         g_free (db_filename);
445         db_filename = NULL;
446     }
447
448     return db_filename;
449 }
450
451 /*********************************************************************/
452
453 static gboolean
454 py_parse_args (PyObject *args,
455                const char **md_filename,
456                const char **checksum,
457                PyObject **log,
458                PyObject **progress,
459                PyObject **repoid)
460 {
461     PyObject *callback;
462
463     if (!PyArg_ParseTuple (args, "ssOO", md_filename, checksum, &callback,
464                            repoid))
465         return FALSE;
466
467     if (PyObject_HasAttrString (callback, "log")) {
468         *log = PyObject_GetAttrString (callback, "log");
469
470         if (!PyCallable_Check (*log)) {
471             PyErr_SetString (PyExc_TypeError, "parameter must be callable");
472             return FALSE;
473         }
474     }
475
476     if (PyObject_HasAttrString (callback, "progressbar")) {
477         *progress = PyObject_GetAttrString (callback, "progressbar");
478
479         if (!PyCallable_Check (*progress)) {
480             PyErr_SetString (PyExc_TypeError, "parameter must be callable");
481             return FALSE;
482         }
483     }
484
485     return TRUE;
486 }
487
488 static void
489 log_cb (const gchar *log_domain,
490         GLogLevelFlags log_level,
491         const gchar *message,
492         gpointer user_data)
493 {
494     PyObject *callback = (PyObject *) user_data;
495     int level;
496     PyObject *args;
497     PyObject *result;
498
499     if (!callback)
500         return;
501
502     args = PyTuple_New (2);
503
504     switch (log_level) {
505     case G_LOG_LEVEL_DEBUG:
506         level = 2;
507         break;
508     case G_LOG_LEVEL_MESSAGE:
509         level = 1;
510         break;
511     case G_LOG_LEVEL_WARNING:
512         level = 0;
513         break;
514     case G_LOG_LEVEL_CRITICAL:
515     default:
516         level = -1;
517         break;
518     }
519
520     PyTuple_SET_ITEM (args, 0, PyInt_FromLong (level));
521     PyTuple_SET_ITEM (args, 1, PyString_FromString (message));
522
523     result = PyEval_CallObject (callback, args);
524     Py_DECREF (args);
525     Py_XDECREF (result);
526 }
527
528 static PyObject *
529 py_update (PyObject *self, PyObject *args, UpdateInfo *update_info)
530 {
531     const char *md_filename = NULL;
532     const char *checksum = NULL;
533     PyObject *log = NULL;
534     PyObject *progress = NULL;
535     PyObject *repoid = NULL;
536     guint log_id = 0;
537     char *db_filename;
538     PyObject *ret = NULL;
539     GError *err = NULL;
540
541     if (!py_parse_args (args, &md_filename, &checksum, &log, &progress,
542                         &repoid))
543         return NULL;
544
545     GLogLevelFlags level = G_LOG_LEVEL_MESSAGE | G_LOG_LEVEL_WARNING |
546         G_LOG_LEVEL_CRITICAL | G_LOG_LEVEL_DEBUG;
547     log_id = g_log_set_handler (NULL, level, log_cb, log);
548
549     db_filename = update_packages (update_info, md_filename, checksum,
550                                    progress, repoid, &err);
551
552     g_log_remove_handler (NULL, log_id);
553
554     if (db_filename) {
555         ret = PyString_FromString (db_filename);
556         g_free (db_filename);
557     } else {
558         PyErr_SetString (PyExc_TypeError, err->message);
559         g_error_free (err);
560     }
561
562     return ret;
563 }
564
565 static PyObject *
566 py_update_primary (PyObject *self, PyObject *args)
567 {
568     PackageWriterInfo info;
569     memset (&info, 0, sizeof (PackageWriterInfo));
570
571     info.update_info.info_init = package_writer_info_init;
572     info.update_info.info_clean = package_writer_info_clean;
573     info.update_info.create_tables = yum_db_create_primary_tables;
574     info.update_info.write_package = write_package_to_db;
575     info.update_info.xml_parse = yum_xml_parse_primary;
576     info.update_info.index_tables = yum_db_index_primary_tables;
577
578     return py_update (self, args, (UpdateInfo *) &info);
579 }
580
581 static PyObject *
582 py_update_filelist (PyObject *self, PyObject *args)
583 {
584     FileListInfo info;
585     memset (&info, 0, sizeof (FileListInfo));
586
587     info.update_info.info_init = update_filelist_info_init;
588     info.update_info.info_clean = update_filelist_info_clean;
589     info.update_info.create_tables = yum_db_create_filelist_tables;
590     info.update_info.write_package = write_filelist_package_to_db;
591     info.update_info.xml_parse = yum_xml_parse_filelists;
592     info.update_info.index_tables = yum_db_index_filelist_tables;
593
594     return py_update (self, args, (UpdateInfo *) &info);
595 }
596
597 static PyObject *
598 py_update_other (PyObject *self, PyObject *args)
599 {
600     UpdateOtherInfo info;
601     memset (&info, 0, sizeof (UpdateOtherInfo));
602
603     info.update_info.info_init = update_other_info_init;
604     info.update_info.info_clean = update_other_info_clean;
605     info.update_info.create_tables = yum_db_create_other_tables;
606     info.update_info.write_package = write_other_package_to_db;
607     info.update_info.xml_parse = yum_xml_parse_other;
608     info.update_info.index_tables = yum_db_index_other_tables;
609
610     return py_update (self, args, (UpdateInfo *) &info);
611 }
612
613 static PyMethodDef SqliteMethods[] = {
614     {"update_primary", py_update_primary, METH_VARARGS,
615      "Parse YUM primary.xml metadata."},
616     {"update_filelist", py_update_filelist, METH_VARARGS,
617      "Parse YUM filelists.xml metadata."},
618     {"update_other", py_update_other, METH_VARARGS,
619      "Parse YUM other.xml metadata."},
620
621     {NULL, NULL, 0, NULL}
622 };
623
624 PyMODINIT_FUNC
625 init_sqlitecache (void)
626 {
627     PyObject * m, * d;
628
629     m = Py_InitModule ("_sqlitecache", SqliteMethods);
630
631     d = PyModule_GetDict(m);
632     PyDict_SetItemString(d, "DBVERSION", PyInt_FromLong(YUM_SQLITE_CACHE_DBVERSION));
633 }