Logo Search packages:      
Sourcecode: bibledit version File versions  Download package

notes_utils.cpp

/*
** Copyright (C) 2003-2006 Teus Benschop.
**  
** This program is free software; you can redistribute it and/or modify
** it under the terms of the GNU General Public License as published by
** the Free Software Foundation; either version 2 of the License, or
** (at your option) any later version.
**  
** This program is distributed in the hope that it will be useful,
** but WITHOUT ANY WARRANTY; without even the implied warranty of
** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
** GNU General Public License for more details.
**  
** You should have received a copy of the GNU General Public License
** along with this program; if not, write to the Free Software
** Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
**  
*/


#include "libraries.h"
#include "utilities.h"
#include <libgen.h>
#include <glib.h>
#include <config.h>
#include "notes_utils.h"
#include "directories.h"
#include <sqlite3.h>
#include "sqlite_reader.h"
#include "bible.h"
#include "date_time_utils.h"
#include "gwrappers.h"
#include "shell.h"
#include "vacuum.h"
#include "progresswindow.h"
#include "generalconfig.h"
#include "projectutils.h"


void notes_database_verify (bool gui)
/*
This verifies the notes database is fine.
If it's not there, it creates one.
It upgrades older databases.
*/
{
  // Filename of database.
  ustring oldfilename;
  ustring filename = notes_database_filename ();

  // If the newest database is already there, bail out.
  if (g_file_test (filename.c_str(), G_FILE_TEST_IS_REGULAR)) return;
  
  // Update: the field "reference" will be upgraded.
  oldfilename = gw_build_filename (directories_get_notes (), "database");
  filename = gw_build_filename (directories_get_notes (), "notes.sql1");
  if (g_file_test (oldfilename.c_str(), G_FILE_TEST_IS_REGULAR)) {
    copy_file (oldfilename, filename);
    sqlite3 *db;
    int rc;
    char *error = NULL;
    ProgressWindow * progresswindow = NULL;
    if (gui) progresswindow = new ProgressWindow ("Upgrading projectnotes", false);
    try
    {
      // Connect to the database.
      rc = sqlite3_open(filename.c_str (), &db);
      if (rc) throw runtime_error (sqlite3_errmsg(db));
      // Read relevant selection of the data.
      SqliteReader reader (0);
      char * sql;
      sql = g_strdup_printf("select id, reference, ref_osis from notes;");
      rc = sqlite3_exec(db, sql, reader.callback, &reader, &error);
      g_free (sql);
      if (rc) throw runtime_error (sqlite3_errmsg(db));
      // Go through it all and upgrade it.
      if (gui) progresswindow->set_iterate (0, 1, reader.ustring0.size());
      for (unsigned int i = 0; i < reader.ustring0.size(); i++) {
        if (gui) progresswindow->iterate ();
        ustring id = reader.ustring0[i];
        ustring reference = reader.ustring1[i];
        ustring newreference = " ";
        Parse parse (reference);
        for (unsigned int i2 = 0; i2 < parse.words.size(); i2++) {
          int numerical_reference = convert_to_int (parse.words[i2]);
          int book = numerical_reference / 1000000;
          int chapter_verse = numerical_reference % 1000000;
          book++; // Here's the trick.
          int newref = (1000000 * book) + chapter_verse;
          newreference.append (convert_to_string (newref) + " ");
        }
        sql = g_strdup_printf ("update notes set reference = '%s' where id = '%s';", newreference.c_str(), id.c_str());
        rc = sqlite3_exec(db, sql, NULL, NULL, &error);
        g_free (sql);
        if (rc) throw runtime_error (sqlite3_errmsg(db));
      }
      // Remove old database.
      unlink (oldfilename.c_str());
    }
    catch (exception & ex)
    {
      gw_critical (ex.what ());
    }
    sqlite3_close (db);
    if (progresswindow) delete progresswindow;
  }

  // Update: Any project "None" will be renamed to "All",
  // and the version table will be dropped.
  oldfilename = gw_build_filename (directories_get_notes (), "notes.sql1");
  filename = gw_build_filename (directories_get_notes (), "notes.sql2");
  if (g_file_test (oldfilename.c_str(), G_FILE_TEST_IS_REGULAR)) {
    g_message ("Upgrading notes");
    ustring command = "mv";
    command.append (shell_quote_space (oldfilename));
    command.append (shell_quote_space (filename));
    system (command.c_str());
    sqlite3 *db;
    int rc;
    char *error = NULL;
    try
    {
      // Connect to the database.
      rc = sqlite3_open(filename.c_str (), &db);
      if (rc) throw runtime_error (sqlite3_errmsg(db));
      // Do the upgrade.
      rc = sqlite3_exec(db, "update notes set project = 'All' where project = 'None';", NULL, NULL, &error);
      if (rc) throw runtime_error (sqlite3_errmsg(db));
      rc = sqlite3_exec(db, "drop table version;", NULL, NULL, &error);
      if (rc) throw runtime_error (sqlite3_errmsg(db));
    }
    catch (exception & ex)
    {
      gw_critical (ex.what ());
    }
    sqlite3_close (db);
  }

  // Last thing: make new database if there is no db.
  filename = notes_database_filename ();
  if (!g_file_test (filename.c_str(), G_FILE_TEST_IS_REGULAR)) {
    sqlite3 *db;
    int rc;
    char *error = NULL;
    try
    {
      // Connect to the database.
      rc = sqlite3_open(filename.c_str (), &db);
      if (rc) {
        throw runtime_error (sqlite3_errmsg(db));
      }
      // Create the notes table.
      char * sql;
      sql = g_strdup_printf("create table '%s' (id integer, reference text, ref_osis text, project text, category text, note text, casefolded text, created integer, modified integer, user text, logbook text);", TABLE_NOTES);
      rc = sqlite3_exec (db, sql, NULL, NULL, &error);
      g_free (sql);
    }
    catch (exception & ex)
    {
      gw_critical (ex.what ());
    }
    // Close connection.  
    sqlite3_close (db);
  }
}


ustring notes_database_filename ()
// Returns the filename of the notes database.
{
  return gw_build_filename (directories_get_notes (), "notes.sql2");
}


void insert_link (GtkTextBuffer *buffer, ustring text, gint id)
/* Inserts a piece of text into the buffer, giving it the usual
 * appearance of a hyperlink in a web browser: blue and underlined.
 * Additionally, attaches some data on the tag, to make it recognizable
 * as a link. 
 */
{
  GtkTextTag *tag;
  tag = gtk_text_buffer_create_tag (buffer, NULL, 
                            "foreground", "blue", 
                            "underline", PANGO_UNDERLINE_SINGLE, 
                            NULL);
  g_object_set_data (G_OBJECT (tag), "id", GINT_TO_POINTER (id));
  GtkTextIter iter;
  gtk_text_buffer_get_end_iter (buffer, &iter);
  gtk_text_buffer_insert_with_tags (buffer, &iter, text.c_str(), -1, tag, NULL);
}
  

gint notes_database_get_unique_id ()
// This generates a unique id, one that is not yet used in the notes database.
{
  gint32 result = 0;
  sqlite3 *db;
  int rc;
  char *error = NULL;
  try
  {
    rc = sqlite3_open(notes_database_filename().c_str (), &db);
    if (rc) {
      throw runtime_error (sqlite3_errmsg(db));
    }
    bool found = false;
    while (!found) {
      result = g_random_int_range (1, 100000000);
      SqliteReader reader (0);
      char * sql;
      sql = g_strdup_printf ("select count(*) from '%s' where id=%i;", TABLE_NOTES, result);
      rc = sqlite3_exec (db, sql, reader.callback, &reader, &error);
      g_free (sql);
      if (rc != SQLITE_OK) {
        throw runtime_error (error);
      }
      gint count = convert_to_int (reader.ustring0[0]);
      if (count == 0) {
        found = true;
      }
    }
  }
  catch (exception & ex)
  {
    gw_critical (ex.what ());
  }
  sqlite3_close (db);
  return result;
}


void notes_delete_one (int id)
// Deletes the note with id.
{
  sqlite3 *db;
  int rc;
  char *error = NULL;
  try
  {
    // Connect to database and start transaction
    rc = sqlite3_open(notes_database_filename ().c_str (), &db);
    if (rc) {
      throw runtime_error (sqlite3_errmsg(db));
    }
    rc = sqlite3_exec (db, "begin;", NULL, NULL, &error);
    // Delete data with "id".
    char * sql;
    sql = g_strdup_printf("delete from %s where id = %d;", TABLE_NOTES, id);
    rc = sqlite3_exec (db, sql, NULL, NULL, &error);
    g_free (sql);
    // Commit the transaction and close connection.
    rc = sqlite3_exec (db, "commit;", NULL, NULL, &error);
  }
  catch (exception & ex)
  {
    gw_critical (ex.what ());
  }
  sqlite3_close (db);
}


void notes_sort (vector<unsigned int>& ids, const vector<ustring>& refs, const vector<ustring>& allrefs, const vector<int>& dates)
/*
This sorts notes.
ids         - ID's of the notes.
              The ids will be modified and sorted, so that these can be used 
              to fetch data from the database in the right order.
refs        - References to sort on first.
allrefs     - All the references to sort on next.
dates       - Modification dates to sort on third.
*/
{
  // No sorting when less than two entries.
  if (ids.size() < 2)
    return;

  // The sorting is done by putting everything in one string, and then
  // sorting the vector of strings, and then taking out the ids again.

  // Storage for strings to sort
  vector<ustring> strings_to_sort;
  
  // Go through all data to sort.
  for (unsigned int i = 0; i < ids.size(); i++) {
  
    // Storage for string to build.
    ustring string_to_build;
    
    // First part of the string is the reference.
    // It does not always has the same length, so make it 9.
    string_to_build = "0000" + refs[i];
    string_to_build = string_to_build.substr (string_to_build.length() - 9, 9);

    // Next sort on all references.
    string_to_build.append (allrefs[i]);
    
    // Third part to sort on is the date. This Julian day has a length of 6
    // characters at the time of programming, and will continue so until
    // Deo Volente the year 2739 A.D. Therefore there is no need to modify the
    // length of it.
    {
      ustring date;
      date = convert_to_string (dates[i]);
      string_to_build.append(date);
    }
    
    // Store built string
    strings_to_sort.push_back(string_to_build);    
  }

  // Sort the data.
  quick_sort (strings_to_sort, ids, 0, strings_to_sort.size());  
}


void notes_select (vector<unsigned int>& ids, const ustring& currentreference)
/*
This selects notes for display.
It does this based on the criteria passed.
The resulting selection will be stored in ids.
*/
{
  // Configuration.
  GeneralConfiguration genconfig (0);
  // Clear ids.
  ids.clear();
  // Variables.
  sqlite3 *db;
  int rc;
  char *error = NULL;
  try
  {
    // Connect to database.
    rc = sqlite3_open(notes_database_filename().c_str (), &db);
    if (rc) {
      throw runtime_error (sqlite3_errmsg(db));
    }
    // Read from database, and sort the results.
    SqliteReader sqlitereader (0);
    // See which notes to select.
    switch ((ShowNotesSelector) genconfig.show_notes_selector()) {
      case snsCurrentVerse:
      {
        // This selects any notes which refer to the current verse.
        ustring book, chapter, verse;
        decode_reference (currentreference, book, chapter, verse);
        unsigned int verse_zero;
        verse_zero = reference_to_numerical_equivalent (book, chapter, "0");
        vector<int> verses = verses_encode (verse);
        for (unsigned int i = 0; i < verses.size(); i++) {
          ustring this_verse = convert_to_string (int (verse_zero + verses[i]));
          char * sql;
          sql = g_strdup_printf ("select id, reference, modified, project from '%s' where reference glob ('* %s *');", TABLE_NOTES, this_verse.c_str());
          rc = sqlite3_exec (db, sql, sqlitereader.callback, &sqlitereader, &error);
          g_free (sql);
          if (rc != SQLITE_OK) {
            throw runtime_error (error);
          }
        }
        break;
      }
      case snsToday:
      {
        int currentdate = date_time_julian_day_get_current ();
        char * sql;
        sql = g_strdup_printf ("select id, reference, modified, project from '%s' where modified = %d;", TABLE_NOTES, currentdate);
        rc = sqlite3_exec (db, sql, sqlitereader.callback, &sqlitereader, &error);
        g_free (sql);
        if (rc != SQLITE_OK) {
          throw runtime_error (error);
        }
        break;
      }
      case snsAll:
      {
        char * sql;
        sql = g_strdup_printf ("select id, reference, modified, project from '%s';", TABLE_NOTES);
        rc = sqlite3_exec (db, sql, sqlitereader.callback, &sqlitereader, &error);
        g_free (sql);
        if (rc != SQLITE_OK) {
          throw runtime_error (error);
        }
        break;
      }
      case snsCategory:
      {
        char * sql;
        sql = g_strdup_printf ("select id, reference, modified, project from '%s' where category = '%s';", TABLE_NOTES, genconfig.show_notes_category().c_str());
        rc = sqlite3_exec (db, sql, sqlitereader.callback, &sqlitereader, &error);
        g_free (sql);
        if (rc != SQLITE_OK) {
          throw runtime_error (error);
        }
        break;
      }
      case snsDateRange:
      {
        char * sql;
        sql = g_strdup_printf ("select id, reference, modified, project from '%s' where modified  >= %d and modified <= %d;", TABLE_NOTES, genconfig.show_notes_date_range_from(), genconfig.show_notes_date_range_to());
        rc = sqlite3_exec (db, sql, sqlitereader.callback, &sqlitereader, &error);
        g_free (sql);
        if (rc != SQLITE_OK) {
          throw runtime_error (error);
        }
        break;
      }
    }
    // Storage for sorting purposes.
    vector<ustring> references;
    vector<ustring> allreferences;
    vector<int> dates;
    set<gint32> already_stored_ids;
    // Read all resulting data from the db.
    for (unsigned int rc = 0; rc < sqlitereader.ustring0.size(); rc++) {
      // See whether we show notes for current project only.
      if (genconfig.show_notes_for_current_project_only()) {
        bool project_ok = false;
        ustring project_in_db = sqlitereader.ustring3[rc];
        if (project_in_db == genconfig.project())
          project_ok = true;
        // Current notes can use "All".
        if (project_in_db == "All")
          project_ok = true;
        // Only show notes when the project is fine.
        if (!project_ok)
          continue;
      }
      // Get id.
      gint32 id = convert_to_int (sqlitereader.ustring0[rc]);
      // Get the numerical equivalent of the reference.
      ustring reference = sqlitereader.ustring1[rc];
      {
        // Parse the string into its possible several references.
        Parse parse (reference);
        // Take the first refence available.
        if (parse.words.size() > 0)
          reference = parse.words[0];
      }
      // Get date modified.
      int date = convert_to_int (sqlitereader.ustring2[rc]);
      // Store data. 
      // As we now work with half-verses (10a, 10b), because of the way we select 
      // notes we might have repeating ids. Filter these out.
      if (already_stored_ids.find (id) == already_stored_ids.end()) {
        ids.push_back(id);
        already_stored_ids.insert (id);
        references.push_back(reference);
        // Sort not only on the first reference, but on the other ones as well.
        allreferences.push_back (sqlitereader.ustring1[rc]);
        dates.push_back(date);
      }
    }
    // Sort the notes.
    notes_sort (ids, references, allreferences, dates);
  }
  catch (exception & ex)
  {
    gw_critical (ex.what ());
  }
  // Close database
  sqlite3_close (db);
}


void notes_display_one (GtkWidget *textview, GtkTextBuffer *textbuffer, gint32 id)
/*
This cares for the actual displaying of the notes.
It displays the note with an ID of id.
*/
{
  GeneralConfiguration genconfig (0);
  sqlite3 *db;
  int rc;
  char *error = NULL;
  try
  {
    // Connect to database.
    rc = sqlite3_open(notes_database_filename().c_str (), &db);
    if (rc) {
      throw runtime_error (sqlite3_errmsg(db));
    }

    // Get from the database.
    SqliteReader reader (0);
    char * sql;
    sql = g_strdup_printf ("select id, ref_osis, note, project from '%s' where id = %d;", TABLE_NOTES, id);
    rc = sqlite3_exec (db, sql, reader.callback, &reader, &error);
    g_free (sql);
    if (rc != SQLITE_OK) {
      throw runtime_error (error);
    }
    
    // Go through the results (there should be only one anyway).
    for (unsigned int r = 0; r < reader.ustring0.size(); r++) {

      // Get the numerical equivalent of the reference.
      ustring reference;
      reference = reader.ustring1[r];

      // Parse the string into its possible several references.
      Parse parse (reference, false);
      reference.clear();

      // Go through each reference.
      for (unsigned int i2 = 0; i2 < parse.words.size(); i2++) {
        // Make it human readable.
        ustring book, chapter, verse;
        reference_discover ("", "", "", parse.words[i2], book, chapter, verse);
        if (!reference.empty())
          reference.append (", ");
        reference.append (book + " " + chapter + ":" + verse);
      }

      // Get project.
      ustring project = reader.ustring3[r];
      
      // Store position of cursor.
      gint linenumber, offset;
      {
        GtkTextIter cursorposition;
        GtkTextMark * textmark;
        textmark = gtk_text_buffer_get_insert (textbuffer);
        gtk_text_buffer_get_iter_at_mark (textbuffer, &cursorposition, textmark);
        linenumber = gtk_text_iter_get_line (&cursorposition);
        offset = gtk_text_iter_get_line_offset (&cursorposition);
      }

      // Move cursor to end of buffer.
      GtkTextIter endposition;
      gtk_text_buffer_get_end_iter (textbuffer, &endposition);
      gtk_text_buffer_place_cursor (textbuffer, &endposition);
      
      // Insert a link with this heading.
      ustring linkheading (reference);
      if (genconfig.notes_show_project())
        linkheading.append (" " + project);
      insert_link (textbuffer, linkheading, id); 
      gtk_text_buffer_insert_at_cursor (textbuffer, "\n", -1);
        
      // Get the text of the note.
      ustring note;
      note = reader.ustring2[r];
      gtk_text_buffer_insert_at_cursor (textbuffer, note.c_str(), -1);
      gtk_text_buffer_insert_at_cursor (textbuffer, "\n", -1);
      
      // Move cursor to where it was before.
      GtkTextIter formerposition;
      gtk_text_buffer_get_iter_at_line_offset (textbuffer, &formerposition, linenumber, offset);
      gtk_text_buffer_place_cursor (textbuffer, &formerposition);
    }
  }
  catch (exception & ex)
  {
    gw_critical (ex.what ());
  }
  // Close connection.  
  sqlite3_close (db);
}


void notes_get_references_from_editor (GtkTextBuffer *textbuffer, vector<ustring>& references, vector<ustring>& messages)
/*
Gets all references from the notes editor.
Normalizes them.
Produces messages on trouble.
Handles notes that span more than one chapter.
*/
{
  // Get all lines from the textbuffer.
  vector<ustring> lines;
  textbuffer_get_lines (textbuffer, lines);
  // When discovering a reference from a user's entry, use previous values,
  // so that it becomes quicker for a user to enter new references.
  // If Leviticus 10:11 is already there, and the user wishes to add verse
  // 12 also, he just enters 12 on a line, and that' it.
  ustring previousbook, previouschapter, previousverse;
  for (unsigned int i = 0; i < lines.size(); i++) {
    if (!lines[i].empty ()) {
      // Normalize reference.
      ustring book;
      ustring chapter;
      ustring verse;
      if (reference_discover (previousbook, previouschapter, previousverse, lines[i], book, chapter, verse)) {
        ustring ch1, vs1, ch2, vs2;
        if (chapter_span_discover (lines[i], ch1, vs1, ch2, vs2)) {
          // We cross the chapter boundaries. Store as two references, the first
          // one going up to the end of the chapter, and the second one starting
          // at the next chapter verse 1.
          GeneralConfiguration genconfig (0);
          ustring ref = book + " " + ch1 + ":" + vs1;
          vector<ustring> verses1 = project_get_verses (genconfig.project(), book, convert_to_int (ch1));
          if (!verses1.empty()) {
            ref.append ("-" + verses1[verses1.size() - 1]);
            references.push_back (ref);
          }
          ref = book + " " + ch2 + ":1-" + vs2;
          references.push_back (ref);
          // Store values to discover next reference.
          previousbook = book;
          previouschapter = ch2;
          previousverse = vs2;
        } else {
          // We've a normal reference.
          // Store reference.
          references.push_back (book + " " + chapter + ":" + verse);
          // Store values to discover next reference.
          previousbook = book;
          previouschapter = chapter;
          previousverse = verse;
        } 
      } else {
        messages.push_back ("Reference " + lines[i] + " is not valid and has been removed.");
      }
    }
  }
}


ustring notes_categories_filename ()
// Returns the filename of the notes database.
{
  return gw_build_filename (directories_get_notes (), "categories");
}


void notes_categories_check ()
// Check categories are there - if not, create default set.
{
  if (!g_file_test (notes_categories_filename ().c_str(), G_FILE_TEST_IS_REGULAR)) {
    vector<ustring> categories;
    categories.push_back ("No issue");
    categories.push_back ("For myself");
    categories.push_back ("For subteam");
    categories.push_back ("For team");
    categories.push_back ("For scholar");
    categories.push_back ("For panel");
    categories.push_back ("For church");
    categories.push_back ("For consultant");
    categories.push_back ("For Bible society");
    write_lines (notes_categories_filename (), categories);
  }
}


void notes_categories_add_from_database (vector<ustring> & categories)
// Takes the existing notes categories, if there are any, and adds any
// extra categories found in the database.
{
  sqlite3 *db;
  int rc;
  char *error = NULL;
  try
  {
    // Get the unique categories.
    set<ustring> database_categories;
    rc = sqlite3_open(notes_database_filename ().c_str (), &db);
    if (rc) {
      throw runtime_error (sqlite3_errmsg(db));
    }
    SqliteReader reader (0);
    char * sql;
    sql = g_strdup_printf("select category from '%s';", TABLE_NOTES);
    rc = sqlite3_exec(db, sql, reader.callback, &reader, &error);
    g_free (sql);
    if (rc != SQLITE_OK) {
      throw runtime_error (error);
    }
    for (unsigned int i = 0; i < reader.ustring0.size(); i++) {
      database_categories.insert (reader.ustring0[i]);
    }
    // Add any new categories to the container.
    set<ustring> localcategories (categories.begin(), categories.end());
    vector<ustring> db_categories (database_categories.begin(), database_categories.end());
    for (unsigned int i = 0; i < db_categories.size(); i++) {
      if (localcategories.find (db_categories[i]) == localcategories.end()) {
        categories.push_back (db_categories[i]);
      }
    }
  }
  catch (exception & ex)
  {
    gw_critical (ex.what ());
  }
  sqlite3_close (db);
}


void notes_vacuum (unsigned int starttime)
// Run external program to vacuum the database.
{
  vacuum_database (notes_database_filename (), starttime);
}

Generated by  Doxygen 1.6.0   Back to index