Книга: Основы программирования в Linux

Приложение управления базой данных компакт-дисков, использующее dbm

Приложение управления базой данных компакт-дисков, использующее dbm

Сейчас заново вы создадите приложение, использующее базу данных dbm для хранения нужной вам информации, в виде файлов cd_data.h, app_ui.c и cd_access.c (упражнения 7.14–7.16).

Вы также перепишите пользовательский интерфейс в виде программы с вводом команд. Позже в этой книге интерфейс базы данных и части пользовательского интерфейса будут пересмотрены, когда будет рассматриваться реализация вашего приложения с помощью различных клиент-серверных механизмов и затем как приложения, к которому можно обращаться по сети, используя Web-обозреватель. Преобразование интерфейса в более простой строковый интерфейс позволит сосредоточиться не на нем, а на более важных частях приложения.

Примечание

В последующих главах вы не раз встретитесь с заголовочным файлом базы данных cd_data.h и функциями из файла cd_access.c. Помните о том, что некоторые дистрибутивы Linux требуют немного отличающихся формирующих опций, например, применения в вашем файле на языке С заголовочного файла gdbm-ndbm.h вместо файла ndbm.h и опций -lgdbm_compat -lgdbm вместо просто опции -lgdbm. Если в вашем дистрибутиве Linux дело обстоит именно так, вы должны внести соответствующие изменения в файлы access.с и Makefile.

Упражнение 7.14. Файл cd_data.h

Начните с заголовочного файла для определения структуры ваших данных и подпрограмм, которые будут использоваться для доступа к данным.

1. Далее приведено определение структуры данных для базы данных компакт-дисков. В него включено определение структур и размеров двух таблиц, формирующих базу данных. Начните с задания размеров некоторых полей, которые вы будете применять, и двух структур: одной для элемента каталога, а другой для элемента дорожки.

/* Таблица catalog */
#define CAT_CAT_LEN 30
#define CAT_TITLE_LEN 70
#define CAT_TYPE_LEN 30
#define CAT_ARTIST_LEN 70
typedef struct {
 char catalog[CAT_CAT_LEN + 1];
 char title[CAT_TITLE_LEN + 1];
 char type [CAT_TYPE_LEN + 1];
 char artist[CAT_ARTIST_LEN + 1];
} cdc_entry;
/* Таблица дорожек, по одному элементу на дорожку */
#define TRACK_CAT_LEN CAT_CAT_LEN
#define TRACK_TTEXT_LEN 70
typedef struct {
 char catalog[TRACK_CAT_LEN + 1];
 int track_no;
 char track_txt[TRACK_TTEXT_LEN + 1];
} cdt_entry;

2. Теперь, имея структуры данных, можно определить нужные вам подпрограммы доступа. Функции с префиксом cdc_ в имени предназначены для элементов каталога, с префиксом cdt_ — для элементов-дорожек.

Примечание

Учтите, что некоторые функции возвращают структуры данных. Вы можете указать на аварийное завершение таких функций, сделав содержимое структур пустым.

/* Функции инициализации и завершения */
int database_initialize(const int new_database);
void database_close(void);
/* Две функции для простого извлечения данных */
cdc_entry get_cdc_entry(const char *cd_catalog_ptr);
cdt_entry get_cdt_entry(const char *cd_catalog_ptr, const int track_no);
/* Две функции для добавления данных */
int add_cdc_entry(const cdc_entry entry_to_add);
int add_cdt_entry(const cdt_entry entry_to_add);
/* Две функции для удаления данных */
int del_cdc_entry(const char *cd_catalog_ptr);
int del_cdt_entry(const char *cd_catalog_ptr, const int track_no);
/* Одна функция поиска */
cdc_entry search_cdc_entry(const char *cd_catalog_ptr,
 int *first_call_ptr);

Упражнение 7.15. Файл app_ui.c

Теперь перейдем к пользовательскому интерфейсу. Вам предлагается простая программа, с помощью которой вы сможете обращаться к функциям вашей базы данных. Интерфейс реализуется в отдельном файле.

1. Как обычно, начните с некоторых заголовочных файлов:

#define _XOPEN_SOURCE
#include <stdlib.h>
#include <unistd.h>
#include <stdio.h>
#include <string.h>
#include "cd_data.h"
#define TMP_STRING_LEN 125 /* это число должно быть больше
                              самой длинной строки в структуре базы данных */

2. Опишите пункты вашего меню с помощью typedef. Этот вариант лучше применения констант, заданных в директивах #define, т.к. позволяет компилятору проверить типы переменных, задающих пункт меню.

typedef enum {
 mo_invalid,
 mo_add_cat,
 mo_add_tracks,
 mo_del_cat,
 mo_find_cat,
 mo_list_cat_tracks,
 mo_del_tracks,
 mo_count_entries,
 mo_exit
} menu_options;

3. Теперь введите прототипы локальных функций. Помните о том, что прототипы функций, обеспечивающих реальный доступ к базе данных, включены в файл cd_data.h.

static int command_mode(int argc, char *argv[]);
static void announce(void);
static menu_options show_menu(const cdc_entry *current_cdc);
static int get_confirm(const char *question);
static int enter_new_cat_entry(cdc_entry *entry_to_update);
static void enter_new_track_entries(const cdc_entry* entry_to_add_to);
static void del_cat_entry(const cdc_entry *entry_to_delete);
static void del_track_entries(const cdc_entry *entry_to_delete);
static cdc_entry find_cat(void);
static void list_tracks(const cdc_entry *entry_to_use);
static void count_all_entries(void);
static void display_cdc(const cdc_entry *cdc_to_show);
static void display_cdt(const cdt_entry *cdt_to_show);
static void strip_return(char *string_to_strip);

4. И наконец, вы добрались до функции main. Она начинается с проверки того, что текущий элемент current_cdc_entry, который применяется для сохранения дорожки выбранного в данный момент элемента каталога компакт-дисков, инициализирован. Также проводится грамматический разбор командной строки, выдается оповещение о том, какая программа выполняется, и инициализируется база данных.

void main(int argc, char *argv[]) {
 menu_options current_option;
 cdc_entry current_cdc_entry;
 int command_result;
 memset(&current_cdc_entry, '', sizeof(current_cdc_entry));
if (argc >1) {
  command_result = command_mode(argc, argv);
  exit(command_result);
 }
 announce();
 if (!database_initialize(0)) {
  fprintf(stderr, "Sorry, unable to initialize databasen");
  fprintf(stderr, "To create a new database use %s -in", argv[0]);
  exit(EXIT_FAILURE);
 }

5. Теперь вы готовы обрабатывать ввод пользователя. Вы остаетесь в цикле, запрашивая пункт меню и обрабатывая его до тех пор, пока пользователь не выберет выход. Обратите вниманий на то, что вы передаете структуру current_cdc_entry в функцию show_menu, чтобы разрешить изменять варианты пунктов меню, когда выбран текущий элемент каталога:

 while (current_option != mo_exit) {
  current_option = show_menu(&current_cdc_entry);
  switch(current_option) {
  case mo_add_cat:
   if (enter_new_cat_entry(&current_cdc_entry)) {
    if (!add_cdc_entry(current_cdc_entry)) {
     fprintf(stderr, "Failed to add new entryn");
     memset(&current_cdc_entry, '',
      sizeof(current_cdc_entry));
    }
   }
   break;
  case mo_add_tracks:
   enter_new_track_entries(&current_cdc_entry);
   break;
  case mo_del_cat:
   del_cat_entry(&current_cdc_entry);
   break;
  case mo_find_cat:
   current_cdc_entry = find_cat();
   break;
  case mo_list_cat_tracks:
   list_tracks(&current_cdc_entry);
   break;
  case mo_del_tracks:
   del_track_entries(&current_cdc_entry);
   break;
  case mo_count_entries:
   count_all_entries();
   break;
  case mo_exit:
   break;
  case mo_invalid:
   break;
  default:
   break;
  } /* switch */
 } /* while */

6. Когда цикл в функции main завершится, закройте базу данных и вернитесь в окружение. Функция announce выводит приглашающее предложение:

 database_close();
 exit(EXIT_SUCCESS);
} /* main */
static void announce(void) {
 printf("nnWelcome to the demonstration CD catalog database
  programn");
}

7. Здесь вы реализуете функцию show_menu. Эта функция проверяет, выбран ли текущий элемент каталога, используя первый символ имени в каталоге. Если элемент каталога выбран, становятся доступными дополнительные пункты меню:

static menu_options show_menu(const cdc_entry *cdc_selected) {
 char tmp_str[TMP_STRING_LEN + 1];
 menu_options option_chosen = mo_invalid;
 while (option_chosen == mo_invalid) {
  if (cdc_selected->catalog[0]) {
   printf("nnCurrent entry: ");
   printf("%s, %s, %a, %sn",
    cdc_selected->catalog, cdc_selected->title,
    cdc_selected->type, cdc_selected->artist);
   printf("n");
   printf("1 - add new CDn");
   printf("2 — search for a CDn");
   printf("3 — count the CDs and tracks in the databasen");
   printf("4 — re-enter tracks for current CDn");
   printf("5 - delete this CD, and all its tracksn");
   printf("6 - list tracks for this CDn");
   printf("q — quitn");
   printf("nOption: ");
   fgets(tmp_str, TMP_STRING_LEN, stdin);
   switch(tmp_str[0]) {
   case '1':
    option_chosen = mo_add_cat;
    break;
   case '2':
    option_chosen = mo_find_cat;
    break;
   case '3':
    option_chosen = mo_count_entries;
    break;
   case '4':
    option_chosen = mo_add_tracks;
    break;
   case '5':
    option_chosen = mo_del_cat;
    break;
   case '6':
    option_chosen = mo_list_cat_tracks;
    break;
   case 'q':
    option_chosen = mo_exit;
    break;
   }
  } else {
   printf("nn");
   printf("1 - add new CDn");
   printf("2 - search for a CDn");
   printf("3 — count the CDs and tracks in the databasen");
   printf("q — quitn");
   printf("nOption: ");
   fgets(tmp_str, TMP_STRING_LEN, stdin);
   switch(tmp_str[0]) {
   case '1':
    option_chosen = mo_add_cat;
    break;
   case '2':
    option_chosen = mo_find_cat;
    break;
   case '3':
    option_chosen = mo_count_entries;
    break;
   case 'q':
    option_chosen = mo_exit;
    break;
   }
  }
 } /* while */
 return(option_chosen);
}

Примечание

Учтите, что для выбора пунктов меню теперь используются номера, а не начальные буквы, применявшиеся в двух предыдущих примерах.

8. В программе есть несколько участков, в которых хотелось бы спросить пользователя о том, уверен ли он в своем запросе. Вместо того чтобы вставлять в эти места программный код, задающий вопрос, поместим его в отдельную функцию get_confirm:

static int get_confirm(const char *question) {
 char tmp_str[TMP_STRING_LEN + 1];
 printf("%s", question);
 fgets(tmp_str, TMP_STRING_LEN, stdin);
 if (tmp_str[0] == 'Y' || tmp_str[0] = 'y') {
  return(1);
 }
 return(0);
}

9. Функция enter_new_cat_entry позволяет вводить новый элемент каталога. Вам не нужно сохранять перевод строки, который возвращает функция fgets, поэтому отбросьте его:

static int enter_new_cat_entry(cdc_entry *entry_to_update) {
 cdc_entry new_entry;
 char tmp_str[TMP_STRING_LEN + 1];
 memset(&new_entry, '', sizeof(new_entry));
 printf("Enter catalog entry: ");
 (void)fgets(tmp_str, TMP_STRING_LEN, stdin);
 strip_return(tmp_str);
 strncpy(new_entry.catalog, tmp_str, CAT_CAT_LEN - 1);
 printf("Enter title: ");
 (void)fgets(tmp_str, TMP_STRING_LEN, stdin);
 strip_return(tmp_str);
 strncpy(new_entry.title, tmp_str, CAT_TITLE_LEN - 1);
 printf("Enter type: ");
 (void)fgets(tmp_str, TMP_STRING_LEN, stdin);
 strip_return(tmp_str);
 strncpy(new_entry.type, tmp_str, CAT_TYPE_LEN - 1);
 printf("Enter artist: ");
 (void)fgets(tmp_str, TMP_STRING_LEN, stdin);
 strip_return(tmp_str);
 strncpy(new_entry.artist, tmp_str, CAT_ARTIST_LEN - 1);
 printf("nNew catalog entry entry is :-n");
 display_cdc(&new_entry);
 if (get_confirm("Add this entry ? ")) {
  memcpy(entry_to_update, &new_entry, sizeof(new_entry));
  return(1);
 }
 return(0);
}

Примечание

Обратите внимание на то, что вы не применяете функцию gets, поскольку нет способа проверить переполнение буфера. Всегда избегайте применения функции gets

10. Теперь вы переходите к функции enter_new_track_entries для ввода информации о дорожке. Эта функция немного сложнее функции ввода элемента каталога, поскольку вы разрешаете существующему элементу-дорожке оставаться неизменным:

static void enter_new_track_entries(const cdc_entry *entry_to_add_to) {
 cdt_entry new_track, existing_track;
 char tmp_str[TMP_STRING_LEN + 1];
 int track_no = 1;
 if (entry_to_add_to->catalog[0] == '') return;
 printf("nUpdating tracks for %sn", entry_to_add_to->catalog);
 printf("Press return to leave existing description unchanged, n");
 printf(" a single d to delete this and remaining tracks, n");
 printf(" or new track descriptionn");
 while(1) {

11. Сначала вы должны проверить, существует ли уже дорожка с текущим номером дорожки. В зависимости от результатов проверки меняется строка приглашения:

  memset(&new_track, '', sizeof(new_track));
  existing_track = get_cdt_entry(entry_to_add_to->catalog,
   track_no);
  if (existing_track.catalog[0]) {
   printf("tTrack %d: %sn", track_no,
    existing_track.track_txt);
   printf("tNew text: ");
  } else {
   printf("tTrack %d description: ", track_no);
  }
  fgets(tmp_str, TMP_STRING_LEN, stdin);
  strip_return(tmp_str);

12. Если для данной дорожки не существует элемент и пользователь его не добавил, предположите, что больше нет дорожек, которые надо добавить:

  if (strlen(tmp_str) == 0) {
   if (existing_track.catalog[0] == '') {
    /* Нет в наличии элемента, поэтому вставка завершается */
    break;
   } else {
    /* Оставляем существующий элемент,
       переходам к следующей дорожке */
    track_no++;
    continue;
   }
  }

13. Если пользователь введет единичный символ d, это приведет к удалению текущей дорожки и дорожек с большими номерами. Функция del_cdt_entry вернет false, если не сможет найти дорожку, которую следует удалить:

  if ((strlen(tmp_str) == 1) && tmp_str[0] == 'd') { /* Удаляет эту и оставшиеся дорожки */
   while (del_cdt_entry(entry_to_add_to->catalog, track_no)) {
    track_no++;
   }
   break;
  }

14. В этом пункте приводится код для вставки новой дорожки или обновления существующей. Вы формируете элемент cdt_entry структуры new_track и затем вызываете функцию базы данных add_cdt_entry для того, чтобы включить его в базу данных:

  strncpy(new_track. track_txt, tmp_str, TRACK_TTEXT_LEN - 1);
  strcpy(new_track.catalog, entry_to_add_to->catalog);
  new_track.track_no = track_no;
  if (!add_cdt_entry(new_track)) {
   fprintf(stderr, "Failed to add new trackn");
   break;
  }
  track_no++;
 } /* while */
}

15. Функция del_cat_entry удаляет элемент каталога. Никогда не разрешайте хранить дорожки для несуществующего элемента каталога.

static void del_cat_entry(const cdc_entry *entry_to_delete) {
 int track_no = 1;
 int delete_ok;
 display_cdc(entry_to_delete);
 if (get_confirm("Delete this entry and all it's tracks? ")) {
  do {
   delete_ok = del_cdt_entry(entry_to_delete->catalog, track_no);
   track_no++;
  } while(delete_ok);
  if (!del_cdc_entry(entry_to_delete->catalog)) {
   fprintf(stderr, "Failed to delete entryn");
  }
 }
}

16. Следующая функция — утилита для удаления всех дорожек элемента каталога:

static void del_track_entries(const cdc_entry *entry_to_delete) {
 int track_no = 1;
 int delete_ok;
 display_cdc(entry_to_delete);
 if (get_confirm("Delete tracks for this entry? ")) {
  do {
   delete_ok = del_cdt_entry(entry_to_delete->catalog, track_no);
   track_no++;
  } while(delete_ok);
 }
}

17. Создайте очень простое средство поиска, в котором разрешите пользователю ввести строку и затем поищите элементы каталога, содержащие строку. Поскольку может быть несколько элементов с такой строкой, просто по очереди предлагаются пользователю все найденные:

static cdc_entry find_cat(void) {
 cdc_entry item_found;
 char tmp_str[TMP_STRING_LEN + 1];
 int first_call = 1;
 int any_entry_found = 0;
 int string ok;
 int entry_selected = 0;
 do {
  string_ok = 1;
  printf("Enter string to search for in catalog entry: ");
  fgets(tmp_str, TMP_STRING_LEN, stdin);
  strip_return(tmp_str);
  if (strlen(tmp_str) > CAT_CAT_LEN) {
   fprintf(stderr, "Sorry, string too long, maximum %d
    charactersn", CAT_CAT_LEN);
   string_ok = 0;
  }
 } while (!string_ok);
 while (!entry_selected) {
  item_found = search_cdc_entry(tmp_str, &firstcall);
  if (item_found.catalog[0] != '') {
   any_entry_found = 1;
   printf("n");
   display_cdc(&item_found);
   if (get_confirm("This entry? ")) {
    entry_selected = 1;
   }
  } else {
   if (any_entry_found) printf("Sorry, no more matches foundn");
   else printf("Sorry, nothing foundn");
   break;
  }
 }
 return(item_found);
}

18. Функция list_tracks — утилита, которая выводит все дорожки для заданного элемента каталога:

static void list_tracks(const cdc_entry *entry_to_use) {
 int track_no = 1;
 cdt_entry entry_found;
 display_cdc(entry_to_use);
 printf("nTracksn");
 do {
  entry_found = get_cdt_entry(entry_to_use->catalog, track_no);
  if (entry_found.catalog[0]) {
   display_cdt(&entry_found);
   track_no++;
  }
 } while(entry_found.catalog[0]);
 (void)get_confirm("Press return");
} /* list_tracks */

19. Функция count_all_entries подсчитывает все дорожки:

static void count_all_entries(void) {
 int cd_entries_found = 0;
 int track_entries_found = 0;
 cdc_entry cdc_found;
 cdt_entry cdt_found;
 int track_no = 1;
 int first_time = 1;
 char *search_string = "";
 do {
  cdc_found = search_cdc_entry(search_string, &first_time);
  if (cdc_found.catalog[0]) {
   cd_entries_found++;
   track_no = 1;
   do {
    cdt_found = get_cdt_entry(cdc_found.catalog, track_no);
    if (cdt_found.catalog[0]) {
     track_entries_found++;
     track_no++;
    }
   } while (cdt_found.catalog[0]);
  }
 } while (cdc_found.catalog[0]);
 printf("Found %d CDs, with a total of %d tracksn",
  cd_entries_found, track_entries_found);
 (void)get_confirm("Press return");
}

20. Теперь у вас есть утилита display_cdc для вывода элемента каталога:

static void display_cdc(const cdc_entry *cdc_to_show) {
 printf("Catalog: %sn", cdc_to_show->catalog);
 printf("ttitle: %sn", cdc_to_show->title);
 printf("ttype: %sn", cdc_to_show->type);
 printf("tartist: %sn", cdc_to_show->artist);
}

и утилита display_cdt для отображения элемента-дорожки:

static void display_cdt(const cdt_entry *cdt_to_show) {
 printf("%d: %sn", cdt_to_show->track_no,
  cdt_to_show->track_txt);
}

21. Служебная функция strip_return удаляет завершающий строку символ перевода строки. Помните о том, что Linux, как и UNIX, использует один символ перевода строки для обозначения конца строки.

static void strip_return(char *string_to_strip) {
 int len;
 len = strlen(string_to_strip);
 if (string_to_strip[len - 1] == 'n')
 string_to_strip[len - 1] = '';
}

22. Функция command_mode предназначена для синтаксического анализа аргументов командной строки. Функция getopt — хороший способ убедиться в том, что ваша программа принимает аргументы, соответствующие стандартным соглашениям, принятым в системе Linux.

static int command_mode(int argc, char *argv[]) {
 int c;
 int result = EXIT_SUCCESS;
 char *prog_name = argv[0];
 /* Эти внешние переменные используются функцией getopt */
 extern char *optarg;
 extern optind, opterr, optopt;
 while ((c = getopt(argc, argv, ":i")) != -1) {
  switch(c) {
  case 'i':
   if (!database_initialize(1)) {
    result = EXIT_FAILURE;
    fprintf(stderr, "Failed to initialize databasen");
   }
   break;
  case ':':
  case '?':
  default:
   fprintf(stderr, "Usage: %s [-i]n", prog_name);
   result = EXIT_FAILURE;
   break;
  } /* switch */
 } /* while */
 return(result);
}

Упражнение 7.16. Файл cd_access.c

Теперь переходите к функциям доступа к базе данных dbm.

1. Как обычно, начните с нескольких файлов #include. Далее примените директивы #define для задания файлов, которые будут использоваться для хранения данных:

#define _XOPEN_SOURCE
#include <unistd.h>
#include <stdlib.h>
#include <stdio.h>
#include <fcntl.h>
#include <string.h>
#include <ndbm.h>
/* В некоторых дистрибутивах файл в предыдущей строке может быть придется заменить на gdbm-ndbm.h */
#include "cd_data.h"
#define CDC_FILE_BASE "cdc_data"
#define CDT_FILE_BASE "cdt_data"
#define CDC_FILE_DIR "cdc_data.dir"
#define CDC_FILE_PAG "cdc_data.pag"
#define CDT_FILE_DIR "cdt_data.dir"
#define CDT_FILE_PAG "cdt_data.pag"

2. Используйте эти две переменные области действия файла для отслеживания текущей базы данных:

static DBM *cdc_dbm_ptr = NULL;
static DBM *cdt_dbm_ptr = NULL;

3. По умолчанию функция database_initialize открывает существующую базу данных, но передав ненулевой (т.е. true) параметр new_database, вы можете заставить ее создать новую (пустую) базу данных, при этом существующая база данных удаляется. Если база данных успешно инициализирована, также инициализированы и два ее указателя, указывающие на то, что база данных открыта.

int database_initialize(const int new_database) {
 int open_mode = O_CREAT | O_RDWR;
 /* Если открыта какая-либо имеющаяся база данных, закрывает ее */
 if (cdc_dbm_ptr) dbm_close(cdc_dbm_ptr);
 if (cdt_dbm_ptr) dbm_close(cdt_dbm_ptr);
 if (new_database) {
  /* Удаляет старые файлы */
  (void)unlink(CDC_FILE_PAG);
  (void)unlink(CDC_FILE_DIR);
  (void)unlink(CDT_FILE_PAG);
  (void)unlink(CDT_FILE_DIR);
 }
 /* Открывает несколько новых файлов, создавая их при необходимости */
 cdc_dbm_ptr = dbm_open(CDC_FILE_BASE, open_mode, 0644);
 cdt_dbm_ptr = dbm_open(CDT_FILE_BASE, open_mode, 0644);
 if (!cdc_dbm_ptr || !cdt_dbm_ptr) {
  fprintf(stderr, "Unable to create databasen");
  cdc_dbm_ptr = cdt_dbm_ptr = NULL;
  return (0);
 }
 return (1);
}

4. Функция database_close просто закрывает базу данных, если она была открыта и устанавливает указатели базы данных в null, чтобы показать, что нет открытой базы данных:

void database_close(void) {
 if (cdc_dbm_ptr) dbm_close(cdc_dbm_ptr);
 if (cdt_dbm_ptr) dbm_close(cdt_dbm_ptr);
 cdc_dbm_ptr = cdt_dbm_ptr = NULL;
}

5. Далее у вас появляется функция, извлекающая единственный элемент каталога, когда передан указатель на строку текста из каталога. Если элемент не найден, у возвращенных данных пустое поле каталога:

cdc_entry get_cdc_entry(const char *cd_catalog_ptr) {
 cdc_entry entry_to_return;
 char entry_to_find[CAT_CAT_LEN + 1];
 datum local data datum;
 datum local_key_datum;
 memset(&entry_to_return, '', sizeof(entry_to_return));

6. Начните с некоторых имеющих смысл проверок, чтобы убедиться в том, что база данных открыта, и вы передали приемлемые параметры, т.е. ключ поиска содержит только допустимую строку и значения null:

 if (!cdc_dbm_ptr || !cdt_dbm_ptr) return (entry_to_return);
 if (!cd_catalog_ptr) return (entry_to_return);
 if (strlen(cd_catalog_ptr) >= CAT_CAT_LEN) return (entry_to_return);
 memset(&entry_to_find, '', sizeof(entry_to_find));
 strcpy(entry_to_find, cd_catalog_ptr);

7. Задайте структуру datum, нужную функциям базы данных dbm, и используйте функцию dbm_fetch для извлечения данных. Если не извлечены никакие данные, вы возвращаете пустую структуру entry_to_return, которая была инициализирована ранее:

 local_key_datum.dptr = (void *) entry_to_find;
 local_key_datum.dsize = sizeof(entry_to_find);
 memset(&local_data_datum, '', sizeof(local_data_datum));
 local_data_datum = dbm_fetch(cdc_dbm_ptr, local_key_datum);
 if (local_data_datum.dptr) {
  memcpy(&entry_to_return, (char*)local_data_datum.dptr, local_data_datum.dsize);
 }
 return (entry_to_return);
} /* get_cdc_entry */

8. Было бы неплохо иметь возможность получать также и одиночный элемент-дорожку, именно этим занимается следующая функция аналогично функции get_cdc_entry, но с указателем на строку каталога и номер дорожки в качестве параметров:

cdt_entry get_cdt_entry(const char *cd_catalog_ptr, const int track_no) {
 cdt_entry entry_to_return;
 char entry_to_find[CAT_CAT_LEN + 10];
 datum local_data_datum;
 datum local_key_datum;
 memset(&entry_to_return, '', sizeof(entry_to_return));
 if (!cdc_dbm_ptr || !cdt_dbm_ptr) return (entry_to_return);
 if (!cd_catalog_ptr) return (entry_to_return);
 if (strlen(cd_catalog_ptr) >= CAT_CAT_LEN) return (entry_to_return);
 /* Устанавливает ключ поиска, представляющий собой комбинацию
    элемента каталога и номера дорожки */
 memset(&entry_to_find, '', sizeof(entry_to_find));
 sprintf(entry_to_find, "%s %d", cd_catalog_ptr, track_no);
 local_key_datum.dptr = (void*)entry_to_find;
 local_key_datum.dsize = sizeof(entry_to_find);
 memset(&local_data_datum, '', sizeof(local_data_datum));
 local_data_datum = dbm_fetch(cdt_dbm_ptr, local_key_datum);
 if (local_data_datum.dptr) {
  memcpy(&entry_to_return, (char*)local_data_datum.dptr, local_data_datum.dsize);
 }
 return (entry_to_return);
}

9. Следующая функция add_cdc_entry добавляет новый элемент каталога:

int add_cdc_entry(const cdc_entry entry_to_add) {
 char key_to_add[CAT_CAT_LEN + 1];
 datum local_data_datum;
 datum local_key_datum;
 int result;
 /* Проверяет инициализацию базы данных и корректность параметров */
 if (!cdc_dbm_ptr || !cdt_dbm_ptr) return (0);
 if (strlen(entry_to_add.catalog) >= CAT_CAT_LEN) return (0);
 /* Гарантирует включение в ключ поиска только корректной строки
    и значений null */
 memset(&key_to_add, '', sizeof(key_to_add));
 strcpy(key_to_add, entry_to_add.catalog);
 local_key_datum.dptr = (void*)key_to_add;
 local_key_datum.dsize = sizeof(key_to_add);
 local_data_datum.dptr = (void*)&entry_to_add;
 local_data_datum.dsize = sizeof(entry_to_add);
 result = dbm_store(cdc_dbm_ptr, local_key_datum, local_data_datum, DBM_REPLACE);
 /* dbm_store() применяет 0 для успешного завершения */
 if (result == 0) return (1);
 return (0);
}

10. Функция add_cdt_entry добавляет новый элемент-дорожку. Ключ доступа — это комбинация строки из каталога и номера дорожки:

int add_cdt_entry(const cdt_entry entry_to_add) {
 char key_to_add[CAT_CAT_LEN + 10];
 datum local_data_datum;
 datum local_key_datum;
 int result;
 if (!cdc_dbm_ptr || !cdt_dbm_ptr) return (0);
 if (strlen(entry_to_add.catalog) >= CAT_CAT_LEN) return (0);
 memset(&key_to_add, ' ', sizeof(key_to_add));
 sprintf(key_to_add, "%s %d", entry_to_add.catalog, entry_to_add.track_no);
 local_key_datum.dptr = (void*)key_to_add;
 local_key_datum.dsize = sizeof(key_to_add);
 local_data_daturn.dptr = (void*)&entry_to_add;
 local_data_datum.dsize = sizeof(entry_to_add);
 result = dbm_store(cdt_dbm_ptr, local_key_datum, local_data_datum, DBM_REPLACE);
 /* dbm_store() применяет 0 в случае успешного завершения
    и отрицательные числа для обозначения ошибок */
 if (result == 0) return (1);
 return (0);
}

11. Если вы можете вставлять строки, было бы лучше, если вы могли бы и удалять их. Следующая функция удаляет элементы каталога;

int del_cdc_entry(const char *cd_catalog_ptr) {
 char key_to_del[CAT_CAT_LEN +1];
 datum local_key_datum;
 int result;
 if (!cdc_dbm_ptr || !cdt_dbm_ptr) return (0);
 if (strlen(cd_catalog_ptr) >= CAT_CAT_LEN) return (0);
 memset(&key_to_del, '', sizeof(key_to_del));
 strcpy(key_to_del, cd_catalog_ptr);
 local_key_datum.dptr = (void*)key_to_del;
 local_key_datum.dsize = sizeof(key_to_del);
 result = dbm_delete(cdc_dbm_ptr, local_key_datum);
 /* dbm_delete() применяет 0 в случае успешного завершения */
 if (result == 0) return (1);
 return (0);
}

12. Далее приведена аналогичная функция для удаления дорожки. Помните о том, что ключ дорожки — это сложный индекс, состоящий из строки, принадлежащей элементу каталога, и номера дорожки:

int del_cdt_entry(const char *cd_catalog_ptr, const int track_no) {
 char key_to_del[CAT_CAT_LEN + 10];
 datum local_key_datum;
 int result;
 if (!cdc_dbm_ptr || !cdt_dbm_ptr) return (0);
 if (strlen(cd_catalog_ptr) >= CAT_CAT_LEN) return (0);
 memset(&key_to_del, '', sizeof(key_to_del));
 sprintf(key_to_del, "%s %d", cd_catalog_ptr, track_no);
 local_key_datum.dptr = (void*)key_to_del;
 local_key_datum.dsize = sizeof(key_to_del);
 result = dbm_delete(cdt_dbm_ptr, local_key_datum);
 /* dbm_delete() применяет 0 в случае успешного завершения */
 if (result == 0) return (1);
 return (0);
}
 

13. И последнее, но не по значимости, у вас есть простая функция поиска. Она не очень замысловата, но, тем не менее, показывает, как просматривать элементы dbm, если ключи заранее неизвестны.

Поскольку вы не знаете наперед, сколько будет элементов, вы создаете такую функцию, которая будет возвращать один элемент после каждого вызова. Если ничего не найдено, элемент будет пустым. Для просмотра всей базы данных начните с вызова этой функции с указателем на целое число *first_call_ptr, которое должно равняться 1 при первом вызове функции. Благодаря этому функция знает, что должна начать поиск с начала базы данных. В последующих вызовах переменная равна 0 и функция возобновляет поиск следом за последним найденным элементом.

Когда вы хотите перезапустить поиск, возможно, указав другой элемент каталога, вы должны снова вызвать эту функцию с параметром *first_call_ptr, равным true, что приведет к выполнению нового поиска.

Между вызовами функция хранит некоторую внутреннюю информацию о состоянии. Это скрывает от клиента сложность продолжения поиска и защищает секреты реализации функции поиска.

Если искомый текст указывает на символ null, все элементы считаются удовлетворяющими критериям поиска.

cdc_entry search_cdc_entry(const char *cd_catalog_ptr,
 int *first_call_ptr) {
 static int local_first_call = 1;
 cdc_entry entry_to_return;
 datum local_data_datum;
 static datum local_key_datum; /* обратите внимание,
                                  должна быть static */
 memset(&entry_to_return, '', sizeof(entry_to_return));

14. Как всегда, начните с имеющих смысл проверок:

 if (!cdc_dbm_ptr || !cdt_dbm_ptr) return (entry_to_return);
 if (!cd_catalog_ptr || !first_call_ptr) return (entry_to_return);
 if (strlen(cd_catalog_ptr) >= CAT_CAT_LEN) return (entry_to_return);
 /* Защита от пропуска вызова с *first_call_ptr, равным true */
 if (local_first_call) {
  local_first_call = 0;
  *first_call_ptr = 1;
 }

15. Если эта функция была вызвана с параметром *first_call_ptr, равным true, вы должны продолжить (или заново начать) поиск от начала базы данных. Если *first_call_ptr не равен true, просто переходите к следующему ключу в базе данных:

 if (*first_call_ptr) {
  *first_call_ptr = 0;
  local_key_datum = dbm_firstkey(cdc_dbm_ptr);
 } else {
  local_key_datum = dbm_nextkey(cdc_dbm_ptr);
 }
 do {
  if (local_key_datum.dptr != NULL) {
   /* Элемент был найден */
   local_data_datum = dbm_fetch(cdc_dhm_ptr, local_key_datum);
   if (local_data_datum.dptr) {
    memcpy(&entry_to_return, (char*)local_data_datum.dptr, local_data_datum, dsize);

16. Функция поиска включает очень простую проверку, позволяющую увидеть, входит ли строка поиска в текущий элемент каталога.

    /* Проверяет, входит ли строка в текущий элемент */
    if (!strstr(entry_to_return.catalog, cd_catalog_ptr)) {
     memset(&entry_to_return, '', sizeof(entry_to_return));
     local_key_datum = dbm_nextkey(cdc_dbm_ptr);
    }
   }
  }
 } while (local_key_datum.dptr && local_data_datum.dptr &&
    (entry_to_return.catalog[0] == ''));
 return (entry_to_return);
} /* search_cdc_entry */

Теперь вы готовы собрать все вместе с помощью следующего make-файла или файла сборки. Не слишком углубляйтесь в него сейчас, поскольку мы обсудим его работу в следующей главе. В данный момент просто наберите его и сохраните как Makefile.

all: application
INCLUDE=/usr/include/gdbm LIBS=gdbm
# В некоторых дистрибутивах вам, возможно, придется изменить предыдущую
# строку, чтобы включить библиотеку совместимости, как показано далее
# LIBS= -lgdbm_compat -lgdbm
CFIAGS=
app_ui.о: app_ui.с cd_data.h
 gcc $(CFLAGS) -c app_ui.c
access.о: access.с cd_data.h
 gcc $(CFLAGS) -I$(INCLUDE) -c access.с
application: app_ui.o access.о
 gcc $(CFLAGS) -o application app_ui.o access.о -l$(LIBS)
clean:
 rm -f application *.o
nodbmfiles:
 rm -f *.dir *.pag

Для компиляции вашего нового приложения управления коллекцией компакт- дисков наберите следующую команду в командной строке:

$ make

Если все пройдет нормально, выполняемый файл application будет откомпилирован и помещен в текущий каталог.

Оглавление книги


Генерация: 1.147. Запросов К БД/Cache: 3 / 1
поделиться
Вверх Вниз