cdh 元数据管理,cd数据库
CD节目
我们已经了解了环境和管理数据,现在是时候更新程序了。Dbm数据库似乎非常适合存储我们的CD信息,所以我们将使用dbm数据作为我们新实现的基础。
更新设计
因为这个更新涉及到一个重要的代码重写,现在我们需要查看我们的设计描述,以确定它是否需要修改。使用逗号分隔的可变文件来存储信息,虽然在Shell中很容易实现,但事实证明非常严格。大量的CD标题和音轨信息需要大量的逗号。如果使用dbm,可以抛弃这种分离方式,所以这是我们设计中需要修改的一个元素。
使用一个单独的文件来分离标题和音轨之间的信息似乎是一个好主意,所以我们也将使用这种逻辑安排。
以前的实现似乎在某种程度上混合了程序的访问部分和用户界面部分,至少因为它们都是在单个文件中实现的。在这个实现中,我们使用一个头文件来描述数据访问所需的数据和方法,并在另一个单独的文件中实现用户界面和数据操作。
尽管我们可以保留用户界面的curses实现,但我们将返回到一个简单的基于命令行的系统。这将使程序的用户界面部分更短更简单,并允许我们专注于其他实现部分。
虽然我们不能在dbm代码中使用SQL,但我们可以使用SQL术语和更常见的方式来表达我们的新数据库。如果我们不熟悉SQL也不用担心;我们将解释这些定义。我们将在第8章学习更多关于SQL的知识。在代码中,数据表可以用以下方式描述:
创建表cdc_entry(
目录字符(30)主键引用cdt_entry(目录),
标题字符(70),
类型充电器(30),
艺人CHAR(70)
);
创建表cdt_entry(
目录字符(30)引用cdc_entry(目录),
track_no整数,
track_txt字符(70),
主键(目录,跟踪号)
);
这个简短的描述告诉我们数据字段的名称和大小。对于cdc_entry表,他告诉我们每个实现记录都有一个唯一的类别。对于cdc_entry表,他告诉我们轨道信息号不能为0,catalog和track_no的组合是唯一的。
使用dbm的光盘数据库程序
现在我们将使用dbm数据来实现我们的程序,以存储我们需要的信息。使用的文件有cd_data.h、app_ui.c和cd_access.c
同时,我们将把我们的用户界面重写为命令行程序。在本书的后面部分,当我们讨论如何使用不同的客户机/服务器机制来实现我们的程序,并最终使用Web浏览器跨网络访问程序时,我们将重用这个程序的数据库接口和一些用户接口。把界面改造成简单的命令行驱动的界面,很容易让我们把注意力集中在程序的重要部分,而不是界面。
下面我们要讨论的是头文件cd_data.h以及后面章节中cd_access.c中多次重复使用的函数。
Test-CD _ data.h
我们将从头文件开始,定义数据的结构,以及我们将用来访问数据的函数。
1这是为CD数据库定义的数据结构。他定义了构成数据库的两个数据表的结构和大小。我们将首先定义我们将使用的数据字段的大小和两个结构:一个用于用户类别记录,另一个用于音轨记录。
/*目录表*/
#定义卡特彼勒30
#定义CAT_TITLE_LEN 70
#定义卡特彼勒类型30
#定义CAT_ARTIST_LEN 70
typedef结构{
char目录[CAT _ CAT _ l EN 1];
char TITLE[CAT _ TITLE _ l EN 1];
char TYPE[CAT _ TYPE _ l EN 1];
char艺人[CAT _ ARTIST _ l EN 1];
} cdc _ entry
/*曲目表,每个曲目一个条目*/
#定义TRACK_CAT_LEN CAT_CAT_LEN
#define TRACK_TTEXT_LEN 70
typedef结构{
char目录[TRACK _ CAT _ l EN 1];
int track _ no
char TRACK _ txt[TRACK _ TTEXT _ l EN 1];
} cdt _ entry
2现在我们有了一些数据结构,我们可以定义我们将会用到的访问函数了。以疾控中心_开始的函数用于类别记录;而以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);
试验- app_ui.c
现在我们开始探讨用户接口。这会给我们一个相对简单的程序,通过他可以访问数据库函数。我们将会在一个单独的文件中实现这个接口。
一如平时一样,我们由头文件开始:
#define _XOPEN_SOURCE
#包含标准库
#包括unistd.h
#包含标准视频
#包含字符串。h
#include "cd_data.h "
#定义TMP_STRING_LEN 125 /*该数字必须大于最大值
任何数据库结构中的单个字符串*/
2我们定义我们的菜单选项。在这里使用#已定义常量定义的形式,因为他会允许编译器检查菜单选项变量的类型。
数据类型说明枚举{
莫_无效,
mo_add_cat,
mo_add_tracks,
莫德尔猫,
mo_find_cat,
mo_list_cat_tracks,
莫_德尔_轨道,
维护对象计数条目,
维护对象_退出
}菜单_选项
3现在我们编写局部函数的原型。要记住,实际访问数据的原型包含在cd_data.h中。
静态int command_mode(int argc,char * argv[]);
静态void announce(作废);
静态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);
静态void enter _ new _ track _ entries(const CDC _ entry * entry _ to _ add _ to);
静态void del _ cat _ entry(const CDC _ entry * entry _ to _ delete);
静态void del _ track _ entries(const CDC _ entry * entry _ to _ delete);
静态CDC _ entry find _ cat(void);
静态void list _ tracks(const CDC _ entry * entry _ to _ use);
静态void count _ all _ entries(无效);
静态void display _ CDC(const CDC _ entry * CDC _ to _ show);
静态void display _ CDT(const CDT _ entry * CDT _ to _ show);
静态void strip _ return(char * string _ to _ strip);
四最后我们进行主要的函数。这个函数由以保证我们用来保持当前选中的激光唱片类别记录的音轨信息的当前疾病控制中心条目已经进行了初始化而开始的。同时我们也要分析命令行,从而得到所运行的程序,并且初始化数据库。
void main(int argc,char *argv[])
{
菜单选项当前选项;
cdc _ entry当前_ cdc _ entry
(同Internationalorganizations)国际组织命令_结果;
memset( current_cdc_entry,/0 ,sizeof(current _ CDC _ entry));
if (argc 1) {
command _ result=command _ mode(argc,argv);
退出(命令_结果);
}
announce();
如果(!database_initialize(0)) {
fprintf(stderr,"抱歉,无法初始化数据库/n ");
fprintf(stderr,"使用%s -i/n "创建新数据库“,argv[0]);
退出(退出_失败);
}
5现在我们已经准备好处理用户输入了。我们在一个循环中,提示菜单选项,并且进行处理,直到用户选择了退出选项。注意,在这里我们将当前疾病控制中心条目结构传递给显示菜单函数。我们这样做就要使得如果当前选中了一个类别时菜单选项可以发生变化。
而(当前_选项!=mo_exit) {
当前选项=显示菜单(当前_ CDC _ entry);
开关(当前选项){
案例mo_add_cat:
if(enter _ new _ cat _ entry(current _ CDC _ entry)){
如果(!add _ CDC _ entry(current _ CDC _ entry)){
fprintf(stderr,"未能添加新条目/n ");
memset( current_cdc_entry,/0 ,
(当前_ CDC _ entry)的大小;
}
}
打破;
案例维护对象_添加_跟踪:
enter _ new _ track _ entries(current _ CDC _ entry);
打破;
案例模型目录:
del _ cat _ entry(current _ CDC _ entry);
打破;
案例mo_find_cat:
current _ CDC _ entry=find _ cat();
打破;
案例mo_list_cat_tracks:
列表_曲目(当前_ CDC _ entry);
打破;
案例模型_模型_轨迹:
del _ track _ entries(current _ CDC _ entry);
打破;
案例维护对象计数条目:
count_all_entries()。
打破;
案例维护对象_退出:
打破;
案例维护对象_无效:
打破;
默认值:
打破;
} /*开关*/
} /*当*/
6 当主要的循环退出时,我们关闭数据并且退回到环境。通过宣布函数来打印欢迎界面:
数据库_关闭();
退出(退出_成功);
} /* main */
静态空的宣告(无效)
{
printf("/n/n欢迎访问演示激光唱片目录数据库/
program/n”);
}
七在这里我们实现了显示菜单函数。这个函数会检测当前类别是否使用类别名的第一个字母被选中。如果一个类别被选中就会出现更多的选项。
静态菜单选项显示菜单(常量cdc_entry *cdc_selected)
{
char TMP _ str[TMP _ STRING _ l EN 1];
menu _ options option _ choosed=mo _ invalid;
while(option _ choosed==mo _ invalid){
if (cdc_selected- catalog[0]) {
printf("/n/n当前条目:");
printf("%s,%s,%s,%s/n ",cdc_selected- catalog,
cdc_selected- title,
cdc_selected- type,
cdc_selected-艺人);
printf("/n ");
printf("1 -添加新的CD/n”);
printf("2 -搜索CD/n”);
printf("3 -统计数据库中的激光唱片和曲目/n ");
printf("4 -重新输入当前CD/n的曲目");
printf("5 -删除这张激光唱片及其所有曲目/n ");
printf("6 -列出此CD/n的曲目");
printf(" q-quit/n ");
printf("/no选项:");
fgets(tmp_str,TMP_STRING_LEN,stdin);
switch(tmp_str[0]) {
案例" 1 ":option _ choosed=mo _ add _ cat;打破;
案例" 2 ":option _ choosed=mo _ find _ cat;打破;
案例" 3 ":option _ choosed=mo _ count _ entries;打破;
情况" 4 ":option _ choosed=mo _ add _ tracks;打破;
案例" 5 ":option _ choosed=mo _ del _ cat;打破;
情况" 6 ":option _ choosed=mo _ list _ cat _ tracks;打破;
案例“q”:option _ choosed=mo _ exit;打破;
}
}
否则{
printf("/n/n ");
printf("1 -添加新的CD/n”);
printf("2 -搜索CD/n”);
printf("3 -统计数据库中的激光唱片和曲目/n ");
printf(" q-quit/n ");
printf("/no选项:");
fgets(tmp_str,TMP_STRING_LEN,stdin);
switch(tmp_str[0]) {
案例" 1 ":option _ choosed=mo _ add _ cat;打破;
案例" 2 ":option _ choosed=mo _ find _ cat;打破;
案例" 3 ":option _ choosed=mo _ count _ entries;打破;
case q :option _ choosed=mo _ exit;打破;
}
}
} /*当*/
return(option _ choosed);
}
8有多个地方我们希望询问用户他是否确定所请求的动作。我们并不是在代码中的多个地方询问用户,相反,我们会将其作为一个单独的获取_确认函数来实现:
static int get _ confirm(const char * question)
{
char TMP _ str[TMP _ STRING _ l EN 1];
printf("%s ",问题);
fgets(tmp_str,TMP_STRING_LEN,stdin);
if(tmp _ str[0]== Y tmp _ str[0]== Y ){
返回(1);
}
return(0);
}
9 函数enter_new_cat_entry允许用户可以输入新的类别记录。但是并不希望存储由从文件指针中读取一行函数所返回的回车,所以我们要去掉回车。
静态输入新条目(疾病预防控制中心_条目*条目_更新)
{
cdc_entry新条目;
char TMP _ str[TMP _ STRING _ l EN 1];
memset( new_entry,/0 ,sizeof(new _ entry));
printf("输入目录条目:");
(void)fgets(tmp_str,TMP_STRING_LEN,stdin);
strip _ return(tmp _ str);
strncpy(new_entry.catalog,tmp_str,CAT _ CAT _ LEN-1);
printf("输入标题:");
(void)fgets(tmp_str,TMP_STRING_LEN,stdin);
strip _ return(tmp _ str);
strncpy(new_entry.title,tmp_str,CAT _ TITLE _ LEN-1);
printf("输入类型:");
(void)fgets(tmp_str,TMP_STRING_LEN,stdin);
strip _ return(tmp _ str);
strncpy(new_entry.type,tmp_str,CAT _ TYPE _ LEN-1);
printf("输入艺术家:");
(void)fgets(tmp_str,TMP_STRING_LEN,stdin);
strip _ return(tmp _ str);
strncpy(new_entry.artist,tmp_str,CAT _ ARTIST _ LEN-1);
printf("/nNew新目录条目条目是:-/n ");
display _ CDC(new _ entry);
if (get_confirm("添加此条目?")) {
memcpy(entry_to_update,new_entry,sizeof(new _ entry));
返回(1);
}
return(0);
}
10 现在我们要来讨论输入音轨信息的输入_新_跟踪_条目函数。相比类别记录函数,这个函数要复杂一些,因为我们允许保存已经存在的音轨记录。
静态void enter_new_track_entries(常量cdc_entry *entry_to_add_to)
{
cdt_entry新轨道,现有轨道
char TMP _ str[TMP _ STRING _ l EN 1];
int track _ no=1;
if(entry _ to _ add _ to-catalog[0]==/0 )返回;
printf("/n更新%s/n的曲目“,entry _ to _ add _ to-catalog);
printf("按返回保持现有描述不变,/n ");
printf("一个d删除这个和剩余的音轨,/n ");
printf("或新曲目描述/n ");
while(1) {
11 首先,我们需要检测是否存在当前音轨号的音轨记录。依据我们需要查找的内容,我们可以改变提示符。
memset( new_track,/0 ,sizeof(new _ track));
existing _ track=get _ CDT _ entry(entry _ to _ add _ to-catalog,
track _ no);
if (existing_track.catalog[0]) {
printf("/tTrack %d: %s/n ",track_no
现有_跟踪。track _ txt);
printf("/tNew text:");
}
否则{
printf("/tTrack %d description:",track _ no);
}
fgets(tmp_str,TMP_STRING_LEN,stdin);
strip _ return(tmp _ str);
12 如果并不存在所要查找的音轨信息,而用户并没有添加音轨信息,我们假设用户并没有音轨信息需要添加。
if (strlen(tmp_str)==0) {
如果(现有_轨道。目录[0]==/0 ){
/*没有现有条目,因此添加完毕*/
打破;
}
否则{
/*离开现有条目,跳到下一首曲目*/
轨道_否
继续;
}
}
13 如果用户输入了一个d字符,这会删除当前以及更高记录号的音轨信息。如果并没有查找到要删除的音轨信息,删除目录条目函数会返回错误。
if((strlen(tmp _ str)==1)tmp _ str[0]== d ){
/*删除此曲目和剩余曲目*/
while(del _ CDT _ entry(entry _ to _ add _ to-catalog,track_no)) {
轨道_否
}
打破;
}
14 现在我们要来编码添加一个新的音轨信息或是更新一个已经存在的音轨信息。我们使用cdt_entry构成new_track,然后调用数据库函数添加_ cdt _条目将其添加到数据库中。
strncpy(new_track.track_txt,tmp_str,TRACK _ TTEXT _ LEN-1);
strcpy(new_track.catalog,entry _ to _ add _ to-catalog);
new _ track.track _ no=跟踪编号
如果(!add_cdt_entry(new_track)) {
fprintf(stderr,"未能添加新曲目/n ");
打破;
}
轨道_否
} /*当*/
}
15 函数删除_目录_条目删除一个类别记录。我们绝不会允许一个不存在类别的音轨记录存在。
静态void del_cat_entry(常量cdc_entry *entry_to_delete)
{
int track _ no=1;
int delete _ ok
display _ CDC(entry _ to _ delete);
if (get_confirm("删除这个条目及其所有轨迹?")) {
做{
delete _ ok=del _ CDT _ entry(entry _ to _ delete-catalog,
track _ no);
轨道_否
} while(delete _ ok);
如果(!del _ CDC _ entry(entry _ to _ delete-catalog)){
fprintf(stderr,"未能删除条目/n ");
}
}
}
16 下一个函数是删除一个类别的所有音轨信息的程序:
静态void del_track_entries(常量cdc_entry *entry_to_delete)
{
int track _ no=1;
int delete _ ok
display _ CDC(entry _ to _ delete);
if (get_confirm("删除该条目的音轨?")) {
做{
delete _ ok=del _ CDT _ entry(entry _ to _ delete-catalog,track _ no);
轨道_否
} while(delete _ ok);
}
}
17 下面,我们创建一个非常简单的类别查找程序。我们允许用户输入一个字符串,然后检测包含这个字符串的类别。因为也许会匹配多个记录,我们只是简单的依次用户提供匹配的记录:
静态cdc_entry find_cat(空)
{
发现cdc _ entry项目
char TMP _ str[TMP _ STRING _ l EN 1];
int first _ call=1;
int any _ entry _ found=0;
int string _ ok
int entry _ selected=0;
做{
string _ ok=1;
printf("输入要在目录条目中搜索的字符串:");
fgets(tmp_str,TMP_STRING_LEN,stdin);
strip _ return(tmp _ str);
if(strlen(tmp _ str)CAT _ CAT _ LEN){
fprintf(stderr,"对不起,字符串太长,最大%d /
characters/n ",CAT _ CAT _ LEN);
string _ ok=0;
}
} while(!string _ ok);
而(!entry_selected) {
item _ found=search _ CDC _ entry(tmp _ str,first _ call);
if (item_found.catalog[0]!=/0) {
any _ entry _ found=1;
printf("/n ");
display _ CDC(item _ found);
if (get_confirm("此条目?)) {
entry _ selected=1;
}
}
否则{
if (any_entry_found) printf("抱歉,没找到更多匹配/n ");
else printf("抱歉,什么都没找到/n ");
打破;
}
}
return(item _ found);
}
18首曲目列表是一个打印一个指定的类别内的所音轨的函数:
静态void list_tracks(常量cdc_entry *entry_to_use)
{
int track _ no=1;
cdt _ entry条目_已找到
display _ CDC(entry _ to _ use);
printf("/nTracks/n ");
做{
entry _ found=get _ CDT _ entry(entry _ to _ use-catalog,
track _ no);
if (entry_found.catalog[0]) {
display _ CDT(entry _ found);
轨道_否
}
} while(entry _ found。目录[0]);
(void)get_confirm("按回车键");
} /*列表_曲目*/
19计数_所有_条目函数统计所有的音轨信息记录数:
静态无效计数_所有条目(无效)
{
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=
做{
CDC _ found=search _ CDC _ entry(search _ string,first _ time);
if (cdc_found.catalog[0]) {
cd _ entries _ found
track _ no=1;
做{
CDT _ found=get _ CDT _ entry(CDC _ found。目录,track _ no);
if (cdt_found.catalog[0]) {
已找到跟踪_条目
轨道_否
}
} while(CDT _ found。目录[0]);
}
} while(CDC _ found。目录[0]);
printf("找到了%d张cd,共%d首曲目/n ",cd_entries_found,
track _ entries _ found);
(void)get_confirm("按回车键");
}
20 现在我们编写显示_cdc函数,一个用于显示一个类别记录的函数:
静态空显示_cdc(常量cdc_entry *cdc_to_show)
{
printf("目录:%s/n ",CDC _ to _ show-Catalog);
printf("/ttitle: %s/n ",CDC _ to _ show-title);
printf("/ttype: %s/n ",CDC _ to _ show-type);
printf("/tartist: %s/n ",CDC _ to _ show-artist);
}
以及用于显示单一音轨记录的显示_cdt函数:
静态void display _ CDT(const CDT _ entry * CDT _ to _ show)
{
printf("%d: %s/n ",cdt_to_show- track_no,CDT _ to _ show-track _ txt);
}
21 函数剥离_返回用于移除字符串末尾的回车符。记住,与Unix操作系统操作系统类似,在Linux操作系统操作系统中,使用一个回车来代表一行的结束:
静态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]=/0 ;
}
22命令模式是一个用于分析命令行参数的函数getopt .函数是一个很好的方法,可以用来保证我们的程序接受符合标准Linux操作系统操作系统约定的参数。
静态int command_mode(int argc,char *argv[])
{
int c;
int result=退出_成功
char * Prog _ name=argv[0];
/* getopt使用的这些外部函数*/
extern char * optarg
extern optind,opterr,optopt
while ((c=getopt(argc,argv,":I))!=-1) {
开关(c) {
案例“我”:
如果(!database_initialize(1)) {
结果=退出_失败;
fprintf(stderr,"未能初始化数据库/n ");
}
打破;
案例":":
案子吗?
默认值:
fprintf(stderr,"用法:%s [-i]/n ",Prog _ name);
结果=退出_失败;
打破;
} /*开关*/
} /*当*/
返回(结果);
}
试验- cd_access.c
现在我们来讨论访问功率数据的函数。
一如平时一样,我们由#包括语句开始。然后我们使用#定义语句来指定我们将会用于存储数据文件。
#define _XOPEN_SOURCE
#包括unistd.h
#包含标准库
#包含标准视频
#包含fcntl.h
#包含字符串。h
#包含ndbm.h
#include "cd_data.h "
#定义CDC_FILE_BASE "cdc_data "
#定义CDT_FILE_BASE "cdt_data "
#定义CDC_FILE_DIR "cdc_data.dir "
#定义PAG疾病控制中心文件
#定义CDT_FILE_DIR "cdt_data.dir "
#定义CDT_FILE_PAG
2我们使用下面的两个局部变量来保存当前数据库的信息:
静态DBM *疾控中心_ DBM _ ptr=NULL;
静态DBM * CDT _ DBM _ ptr=NULL;
3默认情况下,数据库_初始化函数会打开一个已经存在数据库,但是通过传递一个非零的参数新建_数据库,我们可以强制其创建一个新的数据库,但是移除当前已存在的数据库。如果数据库成功的进行初始化,两个数据库也会进行初始,来表明已打开了一个数据库。
int database_initialize(常量(同Internationalorganizations)国际组织新数据库)
{
int open _ mode=O _ CREAT O _ RDWR
/*如果任何现有数据库处于打开状态,则将其关闭*/
if(CDC _ DBM _ ptr)DBM _克洛斯(CDC _ DBM _ ptr);
if(CDT _ DBM _ ptr)DBM _ close(CDT _ DBM _ ptr);
如果(新数据库){
/*删除旧文件*/
(void)unlink(CDC _ FILE _ PAG);
(void)unlink(CDC _ FILE _ DIR);
(void)unlink(CDT _ FILE _ PAG);
(void)unlink(CDT _ FILE _ DIR);
}
/*打开一些新文件,在需要时创建它们*/
CDC _ DBM _ ptr=DBM _开放(CDC _文件_基础,开放_模式,0644);
CDT _ DBM _ ptr=DBM _开放(CDT _文件_基础,开放_模式,0644);
如果(!cdc_dbm_ptr !cdt_dbm_ptr) {
fprintf(stderr,"无法创建数据库/n ");
cdc _ dbm _ ptr=cdt _ dbm _ ptr=NULL
return(0);
}
返回(1);
}
四数据库_关闭函数只是简单的关闭所打开的数据库,并且设置两个数据库指针指向空来表明当前没有数据库打开。
空的数据库_关闭(无效)
{
if(CDC _ DBM _ ptr)DBM _克洛斯(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条目_到_返回
char entry _ to _ find[CAT _ CAT _ l EN 1];
基准本地数据基准
基准局部_关键_数据
memset( entry_to_return,/0 ,sizeof(entry _ to _ return));
6 我们会在开始时进行一些检测,来保证数据库已经打开,并且我们传递了合理的参数-也就是说,查找关键字只包含可用的字符串以及零。
如果(!cdc_dbm_ptr !cdt_dbm_ptr)返回(entry _ to _ return);
如果(!CD _ catalog _ ptr)return(entry _ to _ return);
if(strlen(CD _ catalog _ ptr)=CAT _ CAT _ LEN)return(entry _ to _ return);
memset( entry_to_find,/0 ,sizeof(entry _ to _ find));
strcpy(entry_to_find,CD _ catalog _ ptr);
七我们设置功率函数所需要的数据结构,然后使用dbm_fetch函数来取出数据。如果没有取回任何数据,我们就会返回我们先前所初始化的空的入口_至_返回结构。
局部_关键_数据。dptr=(void *)entry _ to _ find;
局部_关键_数据。dsize=sizeof(entry _ to _ find);
memset( local_data_datum,/0 ,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,
本地数据数据。dsize);
}
return(entry _ to _ return);
} /* get_cdc_entry */
8我们最好也可以得到一个单一的音轨信息,与get_cdc_entry函数相类似,而这也正是下一个函数所要做的,但是需要一个指向类别字符的指针以及一个音轨序号作为参数。
cdt_entry get_cdt_entry(常量char *cd_catalog_ptr常量int track_no)
{
cdt _ entry进入_返回
char entry _ to _ find[CAT _ CAT _ l EN 10];
基准本地数据基准
基准局部_关键_数据
memset( entry_to_return,/0 ,sizeof(entry _ to _ return));
如果(!cdc_dbm_ptr !cdt_dbm_ptr)返回(entry _ to _ return);
如果(!CD _ catalog _ ptr)return(entry _ to _ return);
if(strlen(CD _ catalog _ ptr)=CAT _ CAT _ LEN)return(entry _ to _ return);
/*设置搜索关键字,它是目录条目的组合键
和曲目编号*/
memset( entry_to_find,/0 ,sizeof(entry _ to _ find));
sprintf(entry_to_find,“%s %d”,cd_catalog_ptr,track _ no);
局部_关键_数据。dptr=(void *)entry _ to _ find;
局部_关键_数据。dsize=sizeof(entry _ to _ find);
memset( local_data_datum,/0 ,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,
本地数据数据。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 _ l EN 1];
基准本地数据基准
基准局部_关键_数据
(同Internationalorganizations)国际组织结果;
/*检查数据库是否已初始化以及参数是否有效*/
如果(!cdc_dbm_ptr !cdt_dbm_ptr)返回(0);
if(strlen(entry _ to _ add。目录)=CAT _ CAT _ LEN)返回(0);
/*确保搜索关键字只包含有效的字符串和空值*/
memset( key_to_add,/0 ,sizeof(key _ to _ add));
strcpy(key_to_add,entry _ to _ add。目录);
局部_关键_数据。dptr=(void *)key _ to _ add;
局部_关键_数据。dsize=sizeof(key _ to _ add);
本地数据数据。dptr=(void *)entry _ to _ add;
本地数据数据。dsize=sizeof(entry _ to _ add);
结果=dbm_store(cdc_dbm_ptr,local_key_datum,local_data_datum
DBM _替换);
/* dbm_store()使用0表示成功*/
if(结果==0)返回(1);
return(0);
}
10添加_ cdt _条目添加一新的音轨记录。访问关键字是类别字符串,并且音轨序号作为组合。
int add _ CDT _ entry(const CDT _ entry entry _ to _ add)
{
char key _ to _ add[CAT _ CAT _ l EN 10];
基准本地数据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, ‘/0’, 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_datum.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() uses 0 for success and -ve numbers for errors */
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, ‘/0’, 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() uses 0 for success */
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, ‘/0’, 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() uses 0 for success */
if (result == 0) return (1);
return (0);
}
13 最后但不并不是不重要的一点,我们需要一个简单的搜索函数。他并不是非常高级,但是他确实演示了如果在不知道更多关键字的情况下如何搜索dbm记录。
因为我们并不知道存在多少记录,我们实现这个函数,在每次调用时只返回一个记录。如果没有查找到任何记录,记录就会空。要搜索整个数据库,我们通过使用一个指向整数的指针*first_call_ptr来调用这个函数,在第一次调用时此参数值应为1。然后此函数就会知道他应由数据的起始处开始搜索。在接下来的调用中,变量为0,而函数就会上一次他所查找的记录之后恢复查找。
当我们需要重新启动我们的查找时,很可能是查找另一个不同的类别记录,我们必须再一次将*first_call_ptr设置为真来调用这个函数,将参数设置为真用来初始化搜索。
在函数调用之间,函数会维护一些内部状态信息。这会隐藏客户继续查找的复杂性,并且保持查找函数实现的细节。
如果搜索字符串指向一个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; /* notice this must be static */
memset( entry_to_return, ‘/0’, 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);
/* protect against never passing *first_call_ptr true */
if (local_first_call) {
local_first_call = 0;
*first_call_ptr = 1;
}
15 如果这个函数已经由设置为真的*first_call_ptr参数进行了调用,我们需要由开始(或是重新开始)搜索数据库的超始处。如果*first_call_ptr并不会为真,我们只是简单的移到数据中的下一个关键字上:
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) {
/* an entry was found */
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);
16 我们的搜索程序进行简单的检测以确定查找字符串是否包含在当前的类别记录中。
/* check if search string occurs in the entry */
if (!strstr(entry_to_return.catalog, cd_catalog_ptr))
{
memset( entry_to_return, ‘/0’,
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] == ‘/0’));
return (entry_to_return);
} /* search_cdc_entry */
现在我们就可以所有的内容放在一个makefile文件中。现在不要太担心,因为我们会在下一章讨论他是如何工作的。就目前而言,输入下面的内容,并且将其保存为Makefile。
all: application
INCLUDE=/usr/include/gdbm
LIBS=gdbm
CFLAGS=
app_ui.o: app_ui.c cd_data.h
gcc $(CFLAGS) -c app_ui.c
access.o: access.c cd_data.h
gcc $(CFLAGS) -I$(INCLUDE) -c access.c
application: app_ui.o access.o
gcc $(CFLAGS) -o application app_ui.o access.o -l$(LIBS)
clean:
rm -f application *.o
nodbmfiles:
rm -f *.dir *.pag
要编译我们的CD程序,在提示符下输入下面的命令:
$ make
如果一切顺利,就会在当前目录下编译成功application可执行程序。
总结
在这一章,我们已经了解了数据管理的三个方面。首先,我们了解了Linux内存系统,以及其使用是如何简单,尽管按需调度分页虚拟内存的实现非常复杂。我们同时也会发现Linux系统通过合法的内存访问来保护操作系统与其他程序。
然后我们探讨了文件锁如何使得多个程序合作访问数据。我们首先了解了一个简单的二进制信号量,然而了解一个更为复杂的情况,此是我们锁住文件的不同部分用于共享或是排他访问。接下来我们了解了dbm库,以及其存储和使用一个非常灵活的索引机制读取数据的能力。
最后,我们使用dbm库作为存储技术重新设计并且实现了我们的CD数据库程序。