3.LoadConfig及LoadProfile函数

一、读取配置文件

/*
 * 读取用户的配置文件
 */
void LoadConfig (Bool bMode)
{
    FILE    *fp;
    char    buf[PATH_MAX], *pbuf, *pbuf1;
    Bool    bFromUser = True;// 用以标识配置文件是用户家目录下的,还是从安装目录下拷贝过来的。
    int     group_idx, i;
    Configure   *tmpconfig;// 见解说[1]

    bIsReloadConfig = bMode;// 这是一个全局变量,定义于“src/tool.c[193]"

    pbuf = getenv("HOME");// 从环境变量中获取当前用户家目录的绝对路径
    if(!pbuf){
        fprintf(stderr, "error: get environment variable HOME\n");
        exit(1);    // 其实我绝对这儿也可以不退出的。只是处理起来有点烦。
                    // 需要注意需要直接使用安装目录里面的配置文件,而且不能网配置文件里面写了。直接推出的方法,简单。
    }
    snprintf(buf, PATH_MAX, "%s/.fcitx/config", pbuf);// 获取配置文件的绝对路径

    fp = fopen(buf, "r");// 打开配置文件,如果打不开,说明不存在。就从安装目录里面拷贝一份默认的过来。
    if(!fp && errno == ENOENT){ /* $HOME/.fcitx/config does not exist */
        snprintf(buf, PATH_MAX, PKGDATADIR "/data/config");
        bFromUser = False;
        fp = fopen(buf, "r");
        if(!fp){
            perror("fopen");
            exit(1);// 如果安装目录里面也没有配置文件,那就没办法了。只好告诉用户,无法运行了。
        }
    }

    if(!bFromUser) /* create default configure file */
        SaveConfig();// 解说[2]

    group_idx = -1;//用于标示group的index,在配置文件里面配置是分组的,类似与ini文件的分组。

    /* FIXME: 也许应该用另外更恰当的缓冲区长度 */
    while(fgets(buf, PATH_MAX, fp)){// 每次最多读入PATH_MAX大小的数据。
        i = strlen(buf);
        if(buf[i-1] != '\n'){// 这行代码决定了,fcitx的配置文件每行最多是PATH_MAX个字符。
                            // 如果多余PATH_MAX个字符,就会退出。这也是为什么在前面作者留下那个FIXME的原因。
            fprintf(stderr, "error: configure file: line length\n");
            exit(1);
        }else
            buf[i-1] = '\0';// 给字符串做个封口。否则可能会出现问题。
                            // 比如上一次读入25个字符,这一次仅仅读入2个,那么如果不在这儿加一个空字符的话下面就也会认为是25个字符

        pbuf = buf;
        while(*pbuf && isspace(*pbuf))// 将pbuf指向第一个非空字符
            pbuf++;
        if(!*pbuf || *pbuf == '#')// 如果改行是空数据或者是注释(以#开始的语句)
            continue;

        if(*pbuf == '['){ /* get a group name 组名的格式为“[组名]”*/
            pbuf++;
            pbuf1 = strchr(pbuf, ']');
            if(!pbuf1){
                fprintf(stderr, "error: configure file: configure group name\n");
                exit(1);
            }
            // 根据group的名字找到其在全局变量configure_groups中的index
            group_idx = -1;
            for(i = 0; configure_groups[i].name; i++)
                if(strncmp(configure_groups[i].name, pbuf, pbuf1-pbuf) == 0){
                    group_idx = i;
                    break;
                }
            if(group_idx < 0){// 我认为这儿没有必要退出。此处完全可以忽略这个错误,并且在后面也忽略这个组的配置即可。
                            // 因为这儿退出只会带来一个坏处,那就是扩展性。以后再添加新的组的时候,老版本的程序就无法使用新版本的配置文件了。
                            // 或者,添加了一个可选扩展,该扩展新添加一个组等等。
                            // 所以,此处应该给一个警告,而不是退出。
                fprintf(stderr, "error: invalid configure group name\n");
                exit(1);
            }
            continue;
        }

        // pbuf1指向第一个非空字符与=之间的字符
        pbuf1 = strchr(pbuf, '=');
        if(!pbuf1){// 和前面一样,这儿也应该是一个警告而不应该是提示出错并退出。
            fprintf(stderr, "error: configure file: configure entry name\n");
            exit(1);
        }

        // 这儿避免的是那样一种情况,即从文件头到第一个配置项(即类似与“配置名=配置值”的一行字符串)并没有任何分组。
        // 也就是防止出现下面的“配置1”和“配置2”
        ////////////////////////////////////////////
        //#文件头
        //配置1=123
        //配置2=123
        //[组名]
        //...
        //#文件尾
        ////////////////////////////////////////////
        if(group_idx < 0){
            fprintf(stderr, "error: configure file: no group name at beginning\n");
            exit(1);
        }

        // 找到该组中的配置项,并将其保存到对应的全局变量里面去
        for(tmpconfig = configure_groups[group_idx].configure;
                tmpconfig->name; tmpconfig++)
        {
            if(strncmp(tmpconfig->name, pbuf, pbuf1-pbuf) == 0)
                read_configure(tmpconfig, ++pbuf1);
        }
    }

    fclose(fp);

    // Ctrl+Space就是fcitx的开关快捷键,此处构造一个结构备用。至于为什么这样做,现在还不清楚。接下去看可能会明白作者的用意。
    if (!Trigger_Keys) {
        iTriggerKeyCount = 0;
        Trigger_Keys = (XIMTriggerKey *) malloc (sizeof (XIMTriggerKey) * (iTriggerKeyCount + 2));
        Trigger_Keys[0].keysym = XK_space;
        Trigger_Keys[0].modifier = ControlMask;
        Trigger_Keys[0].modifier_mask = ControlMask;
        Trigger_Keys[1].keysym = 0;
        Trigger_Keys[1].modifier = 0;
        Trigger_Keys[1].modifier_mask = 0;
    }
 }
 // 相比与LoadConfig来说,LoadProfile实在是简单了很多。因为它没有分组的概念,也不处理注释
 void LoadProfile (void)
 {
    FILE           *fp;
    char            buf[PATH_MAX], *pbuf, *pbuf1;
    int             i;
    Configure       *tmpconfig;
    // 前将窗口的位置设定为最原始的默认值,接下来如果配置文件有,会从配置文件中读取,如果没有就使用这个了。
    iMainWindowX = MAINWND_STARTX;        // 主窗口位置X
    iMainWindowY = MAINWND_STARTY;        // 主窗口位置Y
    iInputWindowX = INPUTWND_STARTX;    // 输入窗口位置X
    iInputWindowY = INPUTWND_STARTY;    // 输入窗口位置Y

    pbuf = getenv("HOME");
    if(!pbuf){
        fprintf(stderr, "error: get environment variable HOME\n");
        exit(1);
    }
    snprintf(buf, PATH_MAX, "%s/.fcitx/profile", pbuf);

    fp = fopen(buf, "r");
    if(!fp){
        if(errno == ENOENT)
            SaveProfile();
        return;
    }

    /* FIXME: 也许应该用另外更恰当的缓冲区长度 */
    while(fgets(buf, PATH_MAX, fp)){
        i = strlen(buf);
        if(buf[i-1] != '\n'){
            fprintf(stderr, "error: profile file: line length\n");
            exit(1);
        }else
            buf[i-1] = '\0';

        pbuf = buf;
        while(*pbuf && isspace(*pbuf))
            pbuf++;
        if(!*pbuf || *pbuf == '#')
            continue;

        pbuf1 = strchr(pbuf, '=');
        if(!pbuf1){
            fprintf(stderr, "error: profile file: configure entry name\n");
            exit(1);
        }

        for(tmpconfig = profiles; tmpconfig->name; tmpconfig++)
            if(strncmp(tmpconfig->name, pbuf, pbuf1-pbuf) == 0)
                read_configure(tmpconfig, ++pbuf1);
    }
    fclose(fp);

    // 如果需要保存的话,保存配置。什么时候需要保存呢?
    if(bIsNeedSaveConfig){
        SaveConfig();
        SaveProfile();
    }
 }

解说:
1. Configure这个结构是作者定义的一个用于操作配置文件的结构。一半来说结构体是用来保存数据的,但是,我这儿却说是用来操作数据的结构体。是不是说错了?没有。这个结构体很有一点c++里面类的意思。即它既又数据,也有数据的处理函数。当然,在这儿就不是成员函数了。而是一个函数指针。
2.
其实,在C语言里面,此类用法是很多的。在linux内核代码的虚拟文件系统里面表现尤其突出。包括我们经常使用FILE都是这类结构体。

 至于,这儿的Configure就不做过多的讲解了,它被定义在"src/tool.c"[225]里面,里面有详细的注释。  
  1. 此时还没有从安装目录下读取配置信息。而SaveConfig()做的工作也仅仅是:
    (1). 检查.fcitx目录是否存在,如果目录不存在,创建
    (2). 检查config文件是否存在,如果不存在,创建
    (3). 将全局变量configure_groups中的数据写入到配置文件中。这儿写配置文件的方式是有一小点技巧的。我会在“写配置文件”这一节做详细说明。

    个人观点
    fcitx好像没有必要自己去分析配置文件,与其自己分析,不如使用现成的库去读写。类似的库应该有不少的。即便是自己做读写,也应该独立到一个模块里面去。比如封装到一个独立的so里面去。这样耦合性会很低,升级也方便的多。

二、写配置文件

关于配置文件的写,我就不再解说SaveProfile了,我只简单分析一下SaveConfig函数。

 /*
 * 保存配置信息
    */
     void SaveConfig (void)
     {
    FILE    *fp;
    char    buf[PATH_MAX], *pbuf;
    Configure_group *tmpgroup;

    pbuf = getenv("HOME");
    if(!pbuf){
        fprintf(stderr, "error: get environment variable HOME\n");
        exit(1);
    }

    snprintf(buf, PATH_MAX, "%s/.fcitx", pbuf);
    if(mkdir(buf, S_IRWXU) < 0 && errno != EEXIST){
        perror("mkdir");
        exit(1);
    }

    snprintf(buf, PATH_MAX, "%s/.fcitx/config", pbuf);
    fp = fopen (buf, "w");
    if (!fp) {
        perror("fopen");
        exit(1);
    }

    // 实际上,写配置文件很简单,就是从全局数组configure_groups里面分别把每个组的配置写入到文件里面去
    for(tmpgroup = configure_groups; tmpgroup->name; tmpgroup++){
        if(tmpgroup->comment)// 如果存在注释,先写入
            fprintf(fp, "# %s\n", tmpgroup->comment);
        fprintf(fp, "[%s]\n", tmpgroup->name);// 接下来写入组的名字
        write_configures(fp, tmpgroup->configure);// 最后将该组的每个配置项写入到文件中
        fprintf(fp, "\n");// 插入一个空行,没有什么用处,仅仅是为了增强配置文件的可读性。
    }
    fclose(fp);
    }

/* 将 configures 中的配置信息写入 fp */
static int write_configures(FILE *fp, Configure *configures)
{
    Configure *tc;

    for(tc = configures; tc->name; tc++){
        if(tc->comment)//如果有注释,先写入
            fprintf(fp, "# %s\n", tc->comment);
        if(tc->config_rw)// 解说[1]
            tc->config_rw(tc, fp, 0);
        else{
            switch(tc->value_type){
                case CONFIG_INTEGER:
                    generic_config_integer(tc, fp, 0);
                    break;
                case CONFIG_STRING:
                    generic_config_string(tc, fp, 0);
                    break;
                case CONFIG_COLOR:
                    generic_config_color(tc, fp, 0);
                    break;
                default:
                    fprintf(stderr, "error: shouldn't be here\n");// 个人认为这儿应该是一个断言。
                                                                  //因为,如果出现一个意料之外的类型,说明是程序的BUG,而不是运行时的错误。
                    exit(1);
            }
        }
    }
    return 0;
}

解说:
1. 如果存在特定的读写函数,使用其特定的读写函数写入。为什么这儿不使用通用的函数呢?

因为有些时候,数据写入到配置文件前可能需要进行一些特殊的处理,比如将16进制的颜色值转换成字符串等等。在比如写入一些特定的符号,如快捷键。在这儿,应该就是为了处理快捷键的内部标示与L_CTRL、R_CTRL之类的宏的转换。

你可能感兴趣的:(fcitx源码分析)