ermetadb.c

Go to the documentation of this file.
00001 /*
00002  * File Name: ermetadb.c
00003  */
00004 
00005 /*
00006  * This file is part of libermetadb.
00007  *
00008  * libermetadb is free software: you can redistribute it and/or modify
00009  * it under the terms of the GNU General Public License as published by
00010  * the Free Software Foundation, either version 2 of the License, or
00011  * (at your option) any later version.
00012  *
00013  * libermetadb is distributed in the hope that it will be useful,
00014  * but WITHOUT ANY WARRANTY; without even the implied warranty of
00015  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
00016  * GNU General Public License for more details.
00017  *
00018  * You should have received a copy of the GNU General Public License
00019  * along with this program. If not, see <http://www.gnu.org/licenses/>.
00020  */
00021 
00022 /**
00023  * Copyright (C) 2008 iRex Technologies B.V.
00024  * All rights reserved.
00025  */
00026 
00027 //----------------------------------------------------------------------------
00028 // Include Files
00029 //----------------------------------------------------------------------------
00030 
00031 #include "config.h"
00032 
00033 #include <errno.h>
00034 #include <glib.h>
00035 #include <limits.h>
00036 #include <libgen.h>
00037 #include <sqlite3.h>
00038 #include <stdlib.h>
00039 #include <string.h>
00040 #include <sys/types.h>
00041 #include <sys/stat.h>
00042 #include <sys/wait.h>
00043 #include <unistd.h>
00044 
00045 #include "ermetadb_log.h"
00046 #include "ermetadb.h"
00047 #include "ermetadb_error.h"
00048 #include "ermetadb_private.h"
00049 #include "sqlite3_wrapper.h"
00050 
00051 //----------------------------------------------------------------------------
00052 // Global Constants
00053 //----------------------------------------------------------------------------
00054 
00055 #define DATABASE_VERSION_MAJOR  0
00056 #define DATABASE_VERSION_MINOR  6
00057 
00058 #define MAX_COLUMNS  17
00059 static const struct
00060              {
00061                  const char     *table_name;                    // table name
00062                  const char     *column_names[MAX_COLUMNS+1];   // array with column names
00063              } global_database_tables[]
00064              =
00065              {    // Table name
00066                   //     Column names
00067                 {
00068                     "database_properties",
00069                     {
00070                         "version_major",
00071                         "version_minor",
00072                         NULL
00073                     }
00074                 },
00075                 { 
00076                     "file_metadata",
00077                     {
00078                         "file_id",
00079                         "filename",
00080                         "directory_path",
00081                         "sort_priority",
00082                         "is_directory",
00083                         "is_template",
00084                         "file_type",
00085                         "file_size",
00086                         "file_time_modified",
00087                         "file_time_lastread",
00088                         "file_time_added",
00089                         "title",
00090                         "author",
00091                         "number_of_pages",
00092                         "current_page",
00093                         "tag",
00094                         NULL
00095                     }
00096                 },
00097                 {
00098                     "thumbnails",
00099                     {
00100                         "file_id",
00101                         "thumb_data_mini",
00102                         "thumb_data_small",
00103                         "thumb_data_medium",
00104                         "thumb_data_large",
00105                         NULL
00106                     }
00107                 },
00108                 {
00109                     NULL,
00110                     {}
00111                 }
00112              };
00113 
00114 
00115 //============================================================================
00116 // Local Function Definitions
00117 //============================================================================
00118 
00119 static gboolean update_version_number(sqlite3 *db, int major, int minor)
00120 {
00121     char query[255];
00122     sprintf(query, "UPDATE database_properties SET version_major=%d, version_minor=%d;",
00123                 major, minor);
00124     int rc = sql3_execute_query(db, query, NULL, NULL);
00125     if (rc != ER_OK) {
00126         ERRORPRINTF("cannot update version number to %d.%d", major, minor);
00127     }
00128     return (rc == ER_OK);
00129 }
00130 
00131 
00132 static void convert_database_01_06(erMetadb thiz, int *minor)
00133 {
00134     WARNPRINTF("Trying to convert db version 0.1 to 0.6");
00135     // create temp table for file_metadata
00136     // copy file_metadata to temp table
00137     // drop old file_metadata table
00138     // rename temp to file_metadata
00139     // DROP thumbnails
00140     const char* sql = "CREATE TABLE temp (\n"
00141                       "  file_id  INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL,\n"
00142                       "  filename VARCHAR(250) NOT NULL\n"
00143                       ");";
00144     if (sql3_execute_query(thiz->database, sql, NULL, NULL) != ER_OK)
00145     {
00146         ERRORPRINTF("cannot create temp table to convert version 0.1->0.6, dir %s", thiz->directory);
00147         return;
00148     }
00149 
00150     sql = "INSERT INTO temp SELECT file_id, filename FROM file_metadata;\n"
00151           "DROP TABLE file_metadata;";
00152     if (sql3_execute_query(thiz->database, sql, NULL, NULL) != ER_OK)
00153     {
00154         ERRORPRINTF("cannot fill temp to convert version 0.1->0.6, dir %s", thiz->directory);
00155         return;
00156     }
00157 
00158     sql = "ALTER TABLE temp RENAME TO file_metadata;\n"
00159           "DROP TABLE thumbnails;";
00160     if (sql3_execute_query(thiz->database, sql, NULL, NULL) != ER_OK)
00161     {
00162         ERRORPRINTF("cannot convert version 0.1->0.6, dir %s", thiz->directory);
00163         return;
00164     }
00165 
00166     // get list of anchors
00167     sql = "SELECT annotation_id, start_anchor from annotations;";
00168     metadata_table *result = NULL;
00169     if (sql3_execute_query(thiz->database, sql, NULL, &result) != ER_OK)
00170     {
00171         ERRORPRINTF("cannot get annotations to convert version 0.1->0.6, dir %s", thiz->directory);
00172         return;
00173     }
00174 
00175     static const char* begin = "pdf:/page:";
00176     unsigned int begin_len = strlen(begin);
00177     static const char* new_anchor = "adobe:|type:0|pf_page";
00178     // convert anchors:  pdf:/page:1   ->   adobe:|type:0|pf_page:0.000000
00179     if (result) {
00180         const metadata_cell *cell = (const metadata_cell*) result->cell_data->data;
00181         unsigned int row;
00182         for (row=0; row<result->n_rows; row++) {
00183             int id = cell->value.v_int64;
00184             cell++;
00185             const char* anchor = cell->value.v_text->str;
00186             cell++;
00187             if (strncmp(begin, anchor, begin_len) == 0 && strlen(anchor) > begin_len)
00188             {
00189                 int page = atoi(anchor + begin_len);
00190                 if (page > 0) page--;   // new system starts at 0 instead of 1
00191                 char query[256];
00192                 sprintf(query, "UPDATE annotations set start_anchor='%s:%d.0000' WHERE annotation_id=%d;",
00193                     new_anchor, page, id);
00194                 if (sql3_execute_query(thiz->database, query, NULL, NULL) != ER_OK) {
00195                     ERRORPRINTF("cannot update annotations");
00196                 }
00197             }
00198 
00199         }
00200         metadata_table_free(result);
00201     }
00202 
00203     // convert last_read locations in application_data
00204     sql = "SELECT file_id, key, value FROM application_data;";
00205     result = NULL;
00206     if (sql3_execute_query(thiz->database, sql, NULL, &result) != ER_OK)
00207     {
00208         ERRORPRINTF("cannot get application data to convert version 0.1->0.6, dir %s", thiz->directory);
00209         return;
00210     }
00211 
00212     // convert uds_last_read_location anchor:  pdf:/page:1   ->   adobe:|type:0|pf_page:0.000000
00213     if (result) {
00214         const metadata_cell *cell = (const metadata_cell*) result->cell_data->data;
00215         unsigned int row;
00216         for (row=0; row<result->n_rows; row++) {
00217             int id = cell->value.v_int64;
00218             cell++;
00219             const char* key = cell->value.v_text->str;
00220             cell++;
00221             const char* value = cell->value.v_text->str;
00222             cell++;
00223             static const char* lastread_key = "uds_last_read_location";
00224             if (strcmp(lastread_key, key) == 0) {
00225                 if (strncmp(begin, value, begin_len) == 0 && strlen(value) > begin_len)
00226                 {
00227                     int page = atoi(value + begin_len);
00228                     if (page > 0) page--;   // new system starts at 0 instead of 1
00229                     char query[256];
00230                     sprintf(query, "UPDATE application_data set value='%s:%d.0000' WHERE file_id=%d AND key='%s';",
00231                         new_anchor, page, id, lastread_key);
00232                     if (sql3_execute_query(thiz->database, query, NULL, NULL) != ER_OK) {
00233                         ERRORPRINTF("cannot update application_data");
00234                     }
00235                 }
00236             }
00237         }
00238         metadata_table_free(result);
00239     }
00240 
00241     if (update_version_number(thiz->database, 0, 6)) {
00242         LOGPRINTF("version updated to 0.6");
00243         *minor = 6;
00244     }
00245 }
00246 
00247 
00248 static int add_file_ids(sqlite3* db, GSList **list, const char* table_name)
00249 {
00250     char sql[255];
00251     sprintf(sql, "SELECT DISTINCT file_id FROM %s;", table_name);
00252     metadata_table *result = NULL;
00253     int rc = sql3_execute_query(db, sql, NULL, &result);
00254     if (rc != ER_OK) {
00255         ERRORPRINTF("cannot read file_id's for %s", table_name);
00256         return rc;
00257     }
00258     int n_rows = metadata_table_n_rows(result);
00259     int row;
00260     for (row=0; row<n_rows; row++) {
00261         const metadata_cell *cell = metadata_table_get_cell(result, row);
00262         if (cell->type != METADATA_INT64) {
00263             ERRORPRINTF("invalid data type for file_id [%d]", cell->type);
00264         } else {
00265             int id = cell->value.v_int64;
00266             GSList *found = g_slist_find(*list, (gpointer)id);
00267             if (found == NULL) {
00268                 *list = g_slist_append(*list, (gpointer)id);
00269             }
00270         }
00271     }
00272     metadata_table_free(result);
00273     return ER_OK;
00274 }
00275 
00276 
00277 // results should be a table with <fileid, filename>
00278 static const char* get_filename_from_results(const metadata_table *table, int id)
00279 {
00280     g_assert(table->n_columns == 2);
00281     if (table->n_rows == 0) return NULL;
00282 
00283     const metadata_cell *cell = (const metadata_cell*) table->cell_data->data;
00284     unsigned int row;
00285     for (row=0; row<table->n_rows; row++) {
00286         g_assert_cmpint(cell->type, ==, METADATA_INT64);
00287 
00288         int id2 = cell->value.v_int64;
00289         if (id == id2) {
00290             cell++;
00291             g_assert_cmpint(cell->type, ==, METADATA_TEXT);
00292             return cell->value.v_text->str;
00293         }
00294         cell += table->n_columns;
00295     }
00296     return NULL;
00297 }
00298 
00299 
00300 // changes from version 0.5 -> 0.6
00301 // - added file_metadata table to local db's to get rid of global db dependency
00302 //   this table has file_id, filename as fields
00303 static void convert_database_05_06(erMetadb thiz, int *minor)
00304 {
00305     WARNPRINTF("Trying to convert %s db version 0.5 to 0.6", (thiz->is_global ? "global" : "local"));
00306     GSList *id_list = NULL;
00307     
00308     if (thiz->is_global)
00309     {
00310         // Nothing needs to be done; only update version number
00311         goto ok;
00312     }
00313 
00314     // create new file_metadata table
00315     const char *sql = "CREATE TABLE file_metadata (file_id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, filename VARCHAR(250) NOT NULL);";
00316     if (sql3_execute_query(thiz->database, sql, NULL, NULL) != ER_OK)
00317     {
00318         ERRORPRINTF("Failed to create table 'file_metadata'");
00319         return;
00320     }
00321 
00322     // get all used local id's
00323     int rc1 = add_file_ids(thiz->database, &id_list, "application_data");
00324     int rc2 = add_file_ids(thiz->database, &id_list, "annotations");
00325     if (rc1 != ER_OK || rc2 != ER_OK) goto err;
00326 
00327     if (id_list == NULL) goto ok; // empty database, just update version
00328 
00329     // open global db for lookups
00330     // TODO Q: how to get path? (seach upwards?)
00331     const char* global_path = "/media/mmcblk0p1";
00332     erMetadb global_db = ermetadb_global_open(global_path, TRUE);
00333     if (global_db == NULL) {
00334         ERRORPRINTF("cannot open global db for lookups");
00335         goto err;
00336     }
00337 
00338     // get all filename, id's from global for path
00339     char query[512];
00340     sprintf(query, "SELECT file_id, filename FROM file_metadata WHERE directory_path = '%s';", thiz->directory);
00341     metadata_table *global_results = NULL;
00342     int rc = sql3_execute_query(global_db->database, query, NULL, &global_results);
00343     ermetadb_close(global_db);
00344     if (rc != ER_OK || global_results == NULL) {
00345         ERRORPRINTF("cannot get file_id's from global");
00346         goto err;
00347     }
00348 
00349     // for all id's, find matching filename and insert into local file_metadata
00350     GSList *iter = id_list;
00351     while (iter) {
00352         int id = (int)iter->data;
00353         const char* filename = get_filename_from_results(global_results, id);
00354 
00355         if (filename == NULL) {
00356             WARNPRINTF("cannot find filename for id %d, removing data", id);
00357             local_delete_all_data_for_id(thiz, id); // dont care about result
00358             goto next;
00359         }
00360 
00361         // find matching filename from global_resultss
00362         char *sql2 = sqlite3_mprintf("INSERT INTO file_metadata (filename, file_id) VALUES (%Q,%d);", filename, id);
00363         rc = sql3_execute_query(thiz->database, sql2, NULL, NULL);
00364         sqlite3_free(sql2);
00365         if (rc != ER_OK) {
00366             ERRORPRINTF("cannot insert <'%s', %d> into database", filename, id);
00367         }
00368 next:
00369         iter = g_slist_next(iter); 
00370     }
00371     metadata_table_free(global_results);
00372 ok:
00373     // update version number
00374     if (update_version_number(thiz->database, 0, 6)) {
00375         LOGPRINTF("version updated to 0.6");
00376         *minor = 6;
00377     }
00378 err:
00379     if (id_list) g_slist_free(id_list);
00380 }
00381 
00382 
00383 static int convert_database(erMetadb thiz, const int v_major, const int v_minor)
00384 {
00385     g_assert(thiz->database);
00386 
00387     int ret = ER_OK;
00388     int major = v_major;
00389     int minor = v_minor;
00390 
00391     TRACE("entry: db [%p] version [%d].[%d]  is_global=%d", db, v_major, v_minor, is_global);
00392 
00393     if (major == 0 && minor == 1)
00394     {
00395         convert_database_01_06(thiz, &minor);
00396     }
00397 
00398     if (major == 0 && minor == 5)
00399     {
00400         convert_database_05_06(thiz, &minor);
00401     }
00402 
00403     // check conversion succeeded
00404     if (   major != DATABASE_VERSION_MAJOR
00405         || minor != DATABASE_VERSION_MINOR )
00406     {
00407         ERRORPRINTF( "cannot convert database version [%d.%d] to %d.%d",
00408                      v_major,
00409                      v_minor,
00410                      DATABASE_VERSION_MAJOR,
00411                      DATABASE_VERSION_MINOR );
00412         ret = ERMETADB_DATABASE_STRUCTURE;
00413     }
00414 
00415     return ret;
00416 }
00417 
00418 
00419 // check database version, convert when needed
00420 static int check_database_version(erMetadb thiz)
00421 {
00422     TRACE("entry: db [%p]  global=%d", thiz->database, thiz->is_global);
00423     g_assert(thiz->database);
00424 
00425     int ret = ER_OK;
00426     // get current database version
00427     const char* sql = "SELECT version_major, version_minor FROM database_properties;";
00428     metadata_table *result = NULL;
00429     int rc = sql3_execute_query(thiz->database, sql, NULL, &result);
00430     if (rc != ER_OK)
00431     {
00432         ERRORPRINTF("cannot get version from database");
00433         ret = rc;
00434         goto out;
00435     }
00436 
00437     // extract version numbers
00438     const metadata_cell *cell = metadata_table_get_cell(result, 0);
00439     if (cell->type != METADATA_INT64)
00440     {
00441         ERRORPRINTF("invalid data type database version major [%d]", cell->type);
00442         ret = ERMETADB_DATABASE_STRUCTURE;
00443         goto out;
00444     }
00445     int v_major = cell->value.v_int64;
00446 
00447     cell = metadata_table_get_cell(result, 1);
00448     if (cell->type != METADATA_INT64)
00449     {
00450         ERRORPRINTF("invalid data type database version minor [%d]", cell->type);
00451         ret = ERMETADB_DATABASE_STRUCTURE;
00452         goto out;
00453     }
00454     int v_minor = cell->value.v_int64;
00455 
00456     // check database version
00457     if (v_major > DATABASE_VERSION_MAJOR)
00458     {
00459         ERRORPRINTF("incompatible database version [%d.%d]", v_major, v_minor);
00460         ret = ERMETADB_DATABASE_STRUCTURE;
00461         goto out;
00462     }
00463     if (v_major != DATABASE_VERSION_MAJOR || v_minor != DATABASE_VERSION_MINOR )
00464     {
00465         ret = convert_database(thiz, v_major, v_minor);
00466     }
00467 
00468 out:
00469     metadata_table_free(result);
00470     return ret;
00471 }
00472 
00473 
00474 // check consistency of database tables:
00475 //   all tables present
00476 //   no additional tables
00477 static int check_global_database_tables(erMetadb thiz)
00478 {
00479     TRACE("entry: thiz [%p]", thiz);
00480     g_assert(thiz->database);
00481     g_assert(thiz->is_global);
00482 
00483     // get tablenames from database
00484     metadata_table *result   = NULL;    // result from query
00485     const char *sql = "SELECT name FROM sqlite_master WHERE type='table';";
00486     int ret = sql3_execute_query(thiz->database, sql, NULL, &result);
00487     if (ret != ER_OK) goto out;
00488 
00489     int n_tables = metadata_table_n_rows(result);
00490     int col = metadata_table_find_column(result, "name");
00491     if (col < 0)
00492     {
00493         ERRORPRINTF("No 'names' column in result");
00494         ret = ERMETADB_FAIL;
00495         goto out;
00496     }
00497 
00498     // check table names
00499     int i;
00500     for ( i = 0 ; global_database_tables[i].table_name ; i++)
00501     {
00502         const char *name = global_database_tables[i].table_name;
00503 
00504         gboolean found = FALSE;
00505         int row;
00506         for (row = 0 ; row < n_tables && !found ; row++)
00507         {
00508             GString *string = metadata_table_get_string(result, metadata_table_cell_index(result, row, col));
00509             if (string)
00510             {
00511                 if ( string->str  &&  strcmp(string->str, name) == 0 )
00512                 {
00513                     found = TRUE;
00514                 }
00515                 g_string_free(string, TRUE);
00516             }
00517         }
00518         if (!found)
00519         {
00520             ERRORPRINTF("table [%s] not present", name);
00521             ret = ERMETADB_DATABASE_STRUCTURE;
00522         }
00523     }
00524 out:
00525     metadata_table_free(result);
00526     return ret;
00527 }
00528 
00529 
00530 // check consistency of database columns:
00531 //   per table all columns present
00532 //   no additional columns
00533 static int check_global_database_columns(erMetadb thiz)
00534 {
00535     TRACE("entry: thiz [%p]", thiz);
00536     g_assert(thiz->database);
00537     g_assert(thiz->is_global);
00538 
00539     // for each table expected
00540     int ret = ER_OK;
00541     int tbl;
00542     for ( tbl = 0 ; global_database_tables[tbl].table_name  &&  ret == ER_OK ; tbl++ )
00543     {
00544         const char* table_name = global_database_tables[tbl].table_name;
00545         int n_columns = 0;
00546         int col;
00547         metadata_table *result = NULL;
00548 
00549         // get column names from database
00550         GString *sql = g_string_new("");
00551         g_string_printf( sql, "PRAGMA table_info(%s);", table_name );
00552         ret = sql3_execute_query(thiz->database, sql->str, NULL, &result);
00553         g_string_free(sql, TRUE);
00554         if (ret == ER_OK)
00555         {
00556             n_columns = metadata_table_n_rows(result);
00557             col = metadata_table_find_column(result, "name");
00558             if (col < 0)
00559             {
00560                 ERRORPRINTF("No 'names' column in result");
00561                 ret = ERMETADB_FAIL;
00562             }
00563         }
00564 
00565         // check column names
00566         if (ret == ER_OK)
00567         {
00568             // find column name
00569             int i;
00570             for ( i = 0 ; global_database_tables[tbl].column_names[i] ; i++ )
00571             {
00572                 const char* name = global_database_tables[tbl].column_names[i];
00573 
00574                 gboolean found = FALSE;
00575                 int row;
00576                 for ( row = 0 ; row < n_columns  &&  !found ; row++)
00577                 {
00578                     GString *string = metadata_table_get_string(result, metadata_table_cell_index(result, row, col));
00579                     if (string)
00580                     {
00581                         if ( string->str  &&  strcmp(string->str, name) == 0 )
00582                         {
00583                             found = TRUE;
00584                         }
00585                         g_string_free(string, TRUE);
00586                     }
00587                 }
00588                 if (!found)
00589                 {
00590                     ERRORPRINTF("column [%s] not present in table [%s]", name, global_database_tables[tbl].table_name);
00591                     ret = ERMETADB_DATABASE_STRUCTURE;
00592                 }
00593             }
00594         }
00595         metadata_table_free(result);
00596     }
00597 
00598     return ret;
00599 }
00600 
00601 
00602 // check database content is consistent:
00603 //   all tables present
00604 //   per table all columns present
00605 //   no additional tables or columns
00606 static int check_global_database_consistency(erMetadb thiz)
00607 {
00608     TRACE("entry: thiz [%p]", thiz);
00609 
00610     int rc = check_database_version(thiz);
00611     if (rc != ER_OK)
00612     {
00613         ERRORPRINTF("checking database version failed with code [%d]", rc);
00614         return rc;
00615     }
00616 
00617     rc = check_global_database_tables(thiz);
00618     if (rc != ER_OK)
00619     {
00620         ERRORPRINTF("checking global tables failed with code [%d]", rc);
00621         return rc;
00622     }
00623 
00624     rc = check_global_database_columns(thiz);
00625     if (rc != ER_OK)
00626     {
00627         ERRORPRINTF("checking database columns failed with code [%d]", rc);
00628         return rc;
00629     }
00630 
00631     return ER_OK;
00632 }
00633 
00634 
00635 static int check_local_database_consistency(erMetadb thiz)
00636 {
00637     TRACE("entry: thiz [%p]", thiz);
00638 
00639     int rc = check_database_version(thiz);
00640     if (rc != ER_OK)
00641     {
00642         ERRORPRINTF("checking database version failed with code [%d]", rc);
00643         return rc;
00644     }
00645 
00646     return ER_OK;
00647 }
00648 
00649 
00650 static int open_sqlite_database(const gchar *filename, gboolean do_open_readonly, sqlite3 **database)
00651 {
00652     g_assert(filename);
00653     g_assert(database && *database == NULL);
00654 
00655     // open database read-write
00656     sqlite3 *db = NULL;
00657     int rc = sqlite3_open_v2( filename,
00658                           &db,
00659                           do_open_readonly ? SQLITE_OPEN_READONLY : SQLITE_OPEN_READWRITE,
00660                           NULL );
00661     int ret = ER_OK;
00662     if (rc != SQLITE_OK)
00663     {
00664         ERRORPRINTF("sqlite open error [%d] [%s] on [%s]", rc, sqlite3_errmsg(db), filename);
00665         ret = ER_OPEN_ERROR;
00666         sqlite3_close(db);
00667         db = NULL;
00668     }
00669 
00670     // install custom collation sequence
00671     if (ret == ER_OK)
00672     {
00673         rc = sqlite3_create_collation( db,                              // database
00674                                        SQLITE_COLLATION_STRCASECMP,     // collation name
00675                                        SQLITE_UTF8,                     // text representation
00676                                        NULL,                            // user data
00677                                        sql3_strcase_compare );          // compare function
00678         if (rc != SQLITE_OK)
00679         {
00680             ERRORPRINTF("sqlite collation install error [%d] [%s]", rc, sqlite3_errmsg(db));
00681         }
00682     }
00683 
00684     if (ret == ER_OK)
00685     {
00686         *database = db;
00687     }
00688     TRACE("leave: ret [%d]", ret);
00689     return ret;
00690 }
00691 
00692 
00693 static int check_database(erMetadb thiz)
00694 {
00695     if (thiz->is_global)
00696         return check_global_database_consistency(thiz);
00697     else
00698         return check_local_database_consistency(thiz);
00699 }
00700 
00701 
00702 static int open_sql_database(erMetadb thiz, const char* filename)
00703 {
00704     // check if directory exists
00705     struct stat statbuf;
00706     int rc = stat(thiz->directory, &statbuf);
00707     if (rc != 0 || !S_ISDIR(statbuf.st_mode))
00708     {
00709         // directory not present or not a directory: report error
00710         LOGPRINTF("folder doesn't exist or is not folder [%s]", thiz->directory);
00711         return ER_NOT_FOUND;
00712     }
00713 
00714     // create fullname
00715     char fullname[PATH_MAX];
00716     sprintf(fullname, "%s/%s", thiz->directory, filename);
00717 
00718     // check if database exists
00719     struct stat statbuf2;
00720     rc = stat(fullname, &statbuf2);
00721     if (rc != 0 || !S_ISREG(statbuf2.st_mode))
00722     {
00723         // database not present: report error
00724         LOGPRINTF("database not present or is not regular file [%s]", fullname);
00725         return ER_NOT_FOUND;
00726     }
00727 
00728     sqlite3* db = NULL;
00729     rc = open_sqlite_database(fullname, thiz->readonly, &db);
00730     if (rc != ER_OK)
00731     {
00732         ERRORPRINTF("open sqlite failed, rc [%d] filename [%s] readonly [%d]",
00733                                                         rc, fullname, thiz->readonly);
00734         return rc;
00735     }
00736     thiz->database = db;
00737 
00738     int ret = ER_OK;
00739     if (thiz->readonly)
00740     {
00741         ret = sql3_begin_transaction_readonly(thiz);
00742     }
00743     else
00744     {
00745         ret = sql3_begin_transaction(thiz);
00746     }
00747     ret = check_database(thiz);
00748     ret = sql3_commit_or_rollback(thiz, ret);
00749     if (ret != ER_OK) goto error;
00750 
00751     // enable asynchronous mode,
00752     // i.e. sqlite writes to filesystem but does not wait for flush to disk
00753     const char *sql = "PRAGMA synchronous = OFF;";
00754     metadata_table *result = NULL;
00755     ret = sql3_execute_query(thiz->database, sql, NULL, &result);
00756     metadata_table_free(result);
00757 
00758     if (ret != ER_OK) goto error;
00759     return ER_OK;
00760 error:
00761     sqlite3_close(thiz->database);
00762     thiz->database = NULL;
00763     return ret;
00764 }
00765 
00766 
00767 static int create_db(erMetadb thiz, const char* filename)
00768 {
00769     WARNPRINTF("(re-)generating database in %s", thiz->directory);
00770     int res = ER_OK;
00771     if (thiz->is_global) {
00772         res = ermetadb_global_create_database(thiz->directory);
00773     } else {
00774         res = ermetadb_local_create_database(thiz->directory, filename);
00775     }
00776     if (res != ER_OK) {
00777         ERRORPRINTF("error creating database %s/%s", thiz->directory, filename);
00778     }
00779     return res;
00780 }
00781 
00782 
00783 static erMetadb database_open (const gchar *folder,
00784                                const gchar *filename,
00785                                gboolean global,
00786                                gboolean readonly,
00787                                gboolean create)
00788 {
00789     g_assert(folder && folder[0]);
00790     g_assert(filename && filename[0]);
00791 
00792     erMetadb thiz = g_new0(struct erMetadb_t, 1);
00793 
00794     thiz->database = NULL;
00795     thiz->directory = g_strdup(folder);
00796     thiz->is_global = global;
00797     thiz->readonly = readonly;
00798     thiz->transaction_nesting = 0;
00799     thiz->is_transaction_readonly = FALSE;
00800     thiz->do_force_rollback = FALSE;
00801 
00802     int res = open_sql_database(thiz, filename);
00803     if (res != ER_OK) {
00804         // create database on some conditions
00805         if (global && !readonly) res = create_db(thiz, filename);
00806         if (!global && create)   res = create_db(thiz, filename);
00807 
00808         if (res == ER_OK) {
00809             res = open_sql_database(thiz, filename);
00810         }
00811     }
00812 
00813     if (res != ER_OK) {
00814         g_assert(thiz->database == NULL);
00815         ermetadb_close(thiz);
00816         return NULL;
00817     }
00818     g_assert(thiz->database);
00819     return thiz;
00820 
00821 }
00822 
00823 
00824 erMetadb ermetadb_global_open (const gchar* folder, gboolean readonly)
00825 {
00826     LOGPRINTF("folder [%s]", folder);
00827     return database_open(folder, ERMETADB_GLOBAL_DATABASE_FILE, TRUE, readonly, FALSE);
00828 }
00829 
00830 
00831 erMetadb ermetadb_local_open (const gchar* folder, gboolean create)
00832 {
00833     LOGPRINTF("folder [%s]  create [%d]", folder, create);
00834     return database_open(folder, ERMETADB_LOCAL_DATABASE_FILE, FALSE, FALSE, create);
00835 }
00836 
00837 
00838 erMetadb ermetadb_local_open_custom (const gchar* folder, const gchar* filename)
00839 {
00840     LOGPRINTF("folder [%s]  file [%s]", folder, filename);
00841     return database_open(folder, filename, FALSE, FALSE, FALSE);
00842 }
00843 
00844 
00845 const gchar* ermetadb_get_dir (erMetadb thiz)
00846 {
00847     return thiz->directory;
00848 }
00849 
00850 
00851 void ermetadb_close (erMetadb thiz)
00852 {
00853     LOGPRINTF("entry");
00854 
00855     if (thiz == NULL) return;
00856 
00857     if (thiz->database) {   // close database if open
00858         // abort transaction, if any
00859         if (thiz->transaction_nesting != 0)
00860         {
00861             thiz->transaction_nesting = 1;
00862             sql3_commit_or_rollback(thiz, ER_FAIL);
00863         }
00864 
00865         // close database
00866         int rc = sqlite3_close(thiz->database);
00867         if (rc != SQLITE_OK)
00868         {
00869             ERRORPRINTF("sqlite close error [%d] [%s]", rc, sqlite3_errmsg(thiz->database));
00870         }
00871         thiz->database = NULL;
00872     }
00873     g_free(thiz->directory);
00874     g_free(thiz);
00875 }
00876 
00877 
00878 // create a database file from template dir
00879 static int create_database(const char *source_name, const char *target_dir, const char *target_name)
00880 {
00881     // determine path for database files
00882     gchar *file_source = g_strdup_printf("%s/%s",  SYSCONFDIR, source_name);
00883     gchar *file_target = g_strdup_printf("%s/%s",  target_dir, target_name);
00884     gchar *file_target_old = g_strdup_printf("%s.old", file_target);
00885 
00886     // remove existing .old file, if present
00887     unlink(file_target_old);
00888     // rename current file (if any) to .old
00889     rename(file_target, file_target_old);
00890 
00891     // copy source database to specified location
00892     unsigned int argc = 0;
00893     const char *argv[10];
00894     argv[argc++] = "cp";
00895     argv[argc++] = "-f";
00896     argv[argc++] = file_source;
00897     argv[argc++] = file_target;
00898     argv[argc]   = NULL;
00899     g_assert(argc < sizeof(argv)/sizeof(argv[0]));
00900     gint stat;
00901     GError *err = NULL;
00902     gboolean ok = g_spawn_sync( NULL,                   // working directory: inherit
00903                        (char**)argv,           // child's argument vector
00904                        NULL,                   // environment: inherit
00905                        G_SPAWN_SEARCH_PATH,    // flags
00906                        NULL,                   // child_setup: none
00907                        NULL,                   // child setup data: none
00908                        NULL,                   // stdout: not interested
00909                        NULL,                   // stderr: not interested
00910                        &stat,                  // exit status
00911                        &err                );  // error return
00912     int ret = ER_OK;
00913     if ( !ok )
00914     {
00915         // error: command not executed
00916         ERRORPRINTF("g_spawn_sync error [%s] on copy [%s] [%s]", err->message, file_source, file_target);
00917         g_clear_error(&err);
00918     }
00919     else
00920     {
00921         // command executed: check its exit status
00922         if ( !WIFEXITED(stat) )
00923         {
00924             // error: something weird happened
00925             ERRORPRINTF("g_spawn_sync ok but child not exited, stat [0x%04X]", stat);
00926             ret = ER_FAIL;
00927         }
00928         else
00929         {
00930             // ok: check exit status
00931             int rc = WEXITSTATUS(stat);
00932             if (rc != 0)
00933             {
00934                 // command failed
00935                 ERRORPRINTF( "Failed to copy [%s] [%s], error [%d] [%s]",
00936                              file_source,
00937                              file_target,
00938                              rc,
00939                              strerror(rc) );
00940                 switch (rc)
00941                 {
00942                     case EROFS:
00943                     case EPERM:
00944                         ret = ER_READONLY;
00945                         break;
00946                     default:
00947                         ret = ER_FAIL;
00948                 }
00949             }
00950         }
00951     }
00952     g_free(file_target_old);
00953     g_free(file_target    );
00954     g_free(file_source    );
00955     return ret;
00956 }
00957 
00958 
00959 int ermetadb_global_create_database(const gchar *folder)
00960 {
00961     LOGPRINTF("folder [%s]", folder);
00962 
00963     g_assert(folder && folder[0]);
00964 
00965     return create_database(ERMETADB_GLOBAL_DATABASE_FILE, folder, ERMETADB_GLOBAL_DATABASE_FILE);
00966 }
00967 
00968 
00969 int ermetadb_local_create_database(const gchar *folder, const gchar* filename)
00970 {
00971     LOGPRINTF("folder [%s]  file [%s]", folder, filename);
00972 
00973     g_assert(folder && folder[0]);
00974     g_assert(filename && filename[0]);
00975 
00976     return create_database(ERMETADB_LOCAL_DATABASE_FILE, folder, filename);
00977 }
00978 
00979 
00980 const char* const * ermetadb_private_get_global_column_names(const char* table)
00981 {
00982     LOGPRINTF("entry: table [%s]", table);
00983     g_assert(table);
00984 
00985     int i;
00986     for (i = 0 ; global_database_tables[i].table_name; i++)
00987     {
00988         if ( strcmp(global_database_tables[i].table_name, table) == 0 )
00989         {
00990             return global_database_tables[i].column_names;
00991         }
00992     }
00993 
00994     return NULL;
00995 }
00996 
00997 
00998 int ermetadb_begin_transaction (erMetadb thiz)
00999 {
01000     LOGPRINTF("entry nesting [%d]", thiz->transaction_nesting);
01001 
01002     g_assert(thiz);
01003     g_assert(thiz->database);
01004     g_return_val_if_fail( thiz->transaction_nesting == 0, ER_FORBIDDEN );
01005 
01006     return sql3_begin_transaction(thiz);
01007 }
01008 
01009 
01010 int ermetadb_begin_transaction_readonly (erMetadb thiz)
01011 {
01012     LOGPRINTF("entry nesting [%d]", thiz->transaction_nesting);
01013 
01014     g_assert(thiz);
01015     g_assert(thiz->database);
01016     g_return_val_if_fail( thiz->transaction_nesting == 0, ER_FORBIDDEN );
01017 
01018     return sql3_begin_transaction_readonly(thiz);
01019 }
01020 
01021 
01022 int ermetadb_end_transaction (erMetadb thiz)
01023 {
01024     LOGPRINTF("entry nesting [%d]", thiz->transaction_nesting);
01025 
01026     g_assert(thiz);
01027     g_assert(thiz->database);
01028 
01029     if (thiz->transaction_nesting != 1)
01030     {
01031         // nesting level unbalanced: force rollback
01032         WARNPRINTF("entry with transaction_nesting [%d]", thiz->transaction_nesting);
01033         thiz->transaction_nesting = 1;
01034         thiz->do_force_rollback   = TRUE;
01035     }
01036 
01037     return sql3_commit_or_rollback(thiz, ER_OK);
01038 }
01039 
Generated by  doxygen 1.6.2-20100208